build(deps): vendor JUCE 7.0.12
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
AffineTransform::AffineTransform (float m00, float m01, float m02,
|
||||
float m10, float m11, float m12) noexcept
|
||||
: mat00 (m00), mat01 (m01), mat02 (m02),
|
||||
mat10 (m10), mat11 (m11), mat12 (m12)
|
||||
{
|
||||
}
|
||||
|
||||
bool AffineTransform::operator== (const AffineTransform& other) const noexcept
|
||||
{
|
||||
const auto tie = [] (const AffineTransform& a)
|
||||
{
|
||||
return std::tie (a.mat00, a.mat01, a.mat02, a.mat10, a.mat11, a.mat12);
|
||||
};
|
||||
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool AffineTransform::operator!= (const AffineTransform& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool AffineTransform::isIdentity() const noexcept
|
||||
{
|
||||
return operator== (AffineTransform());
|
||||
}
|
||||
|
||||
const AffineTransform AffineTransform::identity (1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
//==============================================================================
|
||||
AffineTransform AffineTransform::followedBy (const AffineTransform& other) const noexcept
|
||||
{
|
||||
return { other.mat00 * mat00 + other.mat01 * mat10,
|
||||
other.mat00 * mat01 + other.mat01 * mat11,
|
||||
other.mat00 * mat02 + other.mat01 * mat12 + other.mat02,
|
||||
other.mat10 * mat00 + other.mat11 * mat10,
|
||||
other.mat10 * mat01 + other.mat11 * mat11,
|
||||
other.mat10 * mat02 + other.mat11 * mat12 + other.mat12 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::translated (float dx, float dy) const noexcept
|
||||
{
|
||||
return { mat00, mat01, mat02 + dx,
|
||||
mat10, mat11, mat12 + dy };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::translation (float dx, float dy) noexcept
|
||||
{
|
||||
return { 1.0f, 0.0f, dx,
|
||||
0.0f, 1.0f, dy };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::withAbsoluteTranslation (float tx, float ty) const noexcept
|
||||
{
|
||||
return { mat00, mat01, tx,
|
||||
mat10, mat11, ty };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::rotated (float rad) const noexcept
|
||||
{
|
||||
auto cosRad = std::cos (rad);
|
||||
auto sinRad = std::sin (rad);
|
||||
|
||||
return { cosRad * mat00 - sinRad * mat10,
|
||||
cosRad * mat01 - sinRad * mat11,
|
||||
cosRad * mat02 - sinRad * mat12,
|
||||
sinRad * mat00 + cosRad * mat10,
|
||||
sinRad * mat01 + cosRad * mat11,
|
||||
sinRad * mat02 + cosRad * mat12 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::rotation (float rad) noexcept
|
||||
{
|
||||
auto cosRad = std::cos (rad);
|
||||
auto sinRad = std::sin (rad);
|
||||
|
||||
return { cosRad, -sinRad, 0,
|
||||
sinRad, cosRad, 0 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::rotation (float rad, float pivotX, float pivotY) noexcept
|
||||
{
|
||||
auto cosRad = std::cos (rad);
|
||||
auto sinRad = std::sin (rad);
|
||||
|
||||
return { cosRad, -sinRad, -cosRad * pivotX + sinRad * pivotY + pivotX,
|
||||
sinRad, cosRad, -sinRad * pivotX + -cosRad * pivotY + pivotY };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::rotated (float angle, float pivotX, float pivotY) const noexcept
|
||||
{
|
||||
return followedBy (rotation (angle, pivotX, pivotY));
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::scaled (float factorX, float factorY) const noexcept
|
||||
{
|
||||
return { factorX * mat00, factorX * mat01, factorX * mat02,
|
||||
factorY * mat10, factorY * mat11, factorY * mat12 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::scaled (float factor) const noexcept
|
||||
{
|
||||
return { factor * mat00, factor * mat01, factor * mat02,
|
||||
factor * mat10, factor * mat11, factor * mat12 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::scale (float factorX, float factorY) noexcept
|
||||
{
|
||||
return { factorX, 0, 0, 0, factorY, 0 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::scale (float factor) noexcept
|
||||
{
|
||||
return { factor, 0, 0, 0, factor, 0 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::scaled (float factorX, float factorY,
|
||||
float pivotX, float pivotY) const noexcept
|
||||
{
|
||||
return { factorX * mat00, factorX * mat01, factorX * mat02 + pivotX * (1.0f - factorX),
|
||||
factorY * mat10, factorY * mat11, factorY * mat12 + pivotY * (1.0f - factorY) };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::scale (float factorX, float factorY,
|
||||
float pivotX, float pivotY) noexcept
|
||||
{
|
||||
return { factorX, 0, pivotX * (1.0f - factorX),
|
||||
0, factorY, pivotY * (1.0f - factorY) };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::shear (float shearX, float shearY) noexcept
|
||||
{
|
||||
return { 1.0f, shearX, 0,
|
||||
shearY, 1.0f, 0 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::sheared (float shearX, float shearY) const noexcept
|
||||
{
|
||||
return { mat00 + shearX * mat10,
|
||||
mat01 + shearX * mat11,
|
||||
mat02 + shearX * mat12,
|
||||
mat10 + shearY * mat00,
|
||||
mat11 + shearY * mat01,
|
||||
mat12 + shearY * mat02 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::verticalFlip (float height) noexcept
|
||||
{
|
||||
return { 1.0f, 0.0f, 0.0f,
|
||||
0.0f, -1.0f, height };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::inverted() const noexcept
|
||||
{
|
||||
double determinant = getDeterminant();
|
||||
|
||||
if (! approximatelyEqual (determinant, 0.0))
|
||||
{
|
||||
determinant = 1.0 / determinant;
|
||||
|
||||
auto dst00 = (float) ( mat11 * determinant);
|
||||
auto dst10 = (float) (-mat10 * determinant);
|
||||
auto dst01 = (float) (-mat01 * determinant);
|
||||
auto dst11 = (float) ( mat00 * determinant);
|
||||
|
||||
return { dst00, dst01, -mat02 * dst00 - mat12 * dst01,
|
||||
dst10, dst11, -mat02 * dst10 - mat12 * dst11 };
|
||||
}
|
||||
|
||||
// singularity..
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AffineTransform::isSingularity() const noexcept
|
||||
{
|
||||
return exactlyEqual (mat00 * mat11 - mat10 * mat01, 0.0f);
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::fromTargetPoints (float x00, float y00,
|
||||
float x10, float y10,
|
||||
float x01, float y01) noexcept
|
||||
{
|
||||
return { x10 - x00, x01 - x00, x00,
|
||||
y10 - y00, y01 - y00, y00 };
|
||||
}
|
||||
|
||||
AffineTransform AffineTransform::fromTargetPoints (float sx1, float sy1, float tx1, float ty1,
|
||||
float sx2, float sy2, float tx2, float ty2,
|
||||
float sx3, float sy3, float tx3, float ty3) noexcept
|
||||
{
|
||||
return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3)
|
||||
.inverted()
|
||||
.followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3));
|
||||
}
|
||||
|
||||
bool AffineTransform::isOnlyTranslation() const noexcept
|
||||
{
|
||||
return exactlyEqual (mat01, 0.0f)
|
||||
&& exactlyEqual (mat10, 0.0f)
|
||||
&& exactlyEqual (mat00, 1.0f)
|
||||
&& exactlyEqual (mat11, 1.0f);
|
||||
}
|
||||
|
||||
float AffineTransform::getDeterminant() const noexcept
|
||||
{
|
||||
return (mat00 * mat11) - (mat01 * mat10);
|
||||
}
|
||||
|
||||
float AffineTransform::getScaleFactor() const noexcept
|
||||
{
|
||||
return (std::abs (mat00) + std::abs (mat11)) / 2.0f;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class AffineTransformTests final : public UnitTest
|
||||
{
|
||||
public:
|
||||
AffineTransformTests()
|
||||
: UnitTest ("AffineTransform", UnitTestCategories::maths)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Determinant");
|
||||
{
|
||||
constexpr float scale1 = 1.5f, scale2 = 1.3f;
|
||||
|
||||
auto transform = AffineTransform::scale (scale1)
|
||||
.followedBy (AffineTransform::rotation (degreesToRadians (72.0f)))
|
||||
.followedBy (AffineTransform::translation (100.0f, 20.0f))
|
||||
.followedBy (AffineTransform::scale (scale2));
|
||||
|
||||
expect (approximatelyEqual (std::sqrt (std::abs (transform.getDeterminant())), scale1 * scale2));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static AffineTransformTests timeTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a 2D affine-transformation matrix.
|
||||
|
||||
An affine transformation is a transformation such as a rotation, scale, shear,
|
||||
resize or translation.
|
||||
|
||||
These are used for various 2D transformation tasks, e.g. with Path objects.
|
||||
|
||||
@see Path, Point, Line
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API AffineTransform final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an identity transform. */
|
||||
AffineTransform() = default;
|
||||
|
||||
/** Creates a copy of another transform. */
|
||||
AffineTransform (const AffineTransform&) = default;
|
||||
|
||||
/** Creates a transform from a set of raw matrix values.
|
||||
|
||||
The resulting matrix is:
|
||||
|
||||
(mat00 mat01 mat02)
|
||||
(mat10 mat11 mat12)
|
||||
( 0 0 1 )
|
||||
*/
|
||||
AffineTransform (float mat00, float mat01, float mat02,
|
||||
float mat10, float mat11, float mat12) noexcept;
|
||||
|
||||
/** Copies from another AffineTransform object */
|
||||
AffineTransform& operator= (const AffineTransform&) = default;
|
||||
|
||||
/** Compares two transforms. */
|
||||
bool operator== (const AffineTransform& other) const noexcept;
|
||||
|
||||
/** Compares two transforms. */
|
||||
bool operator!= (const AffineTransform& other) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Transforms a 2D coordinate using this matrix. */
|
||||
template <typename ValueType>
|
||||
void transformPoint (ValueType& x, ValueType& y) const noexcept
|
||||
{
|
||||
auto oldX = x;
|
||||
x = static_cast<ValueType> (mat00 * oldX + mat01 * y + mat02);
|
||||
y = static_cast<ValueType> (mat10 * oldX + mat11 * y + mat12);
|
||||
}
|
||||
|
||||
/** Transforms two 2D coordinates using this matrix.
|
||||
This is just a shortcut for calling transformPoint() on each of these pairs of
|
||||
coordinates in turn. (And putting all the calculations into one function hopefully
|
||||
also gives the compiler a bit more scope for pipelining it).
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void transformPoints (ValueType& x1, ValueType& y1,
|
||||
ValueType& x2, ValueType& y2) const noexcept
|
||||
{
|
||||
auto oldX1 = x1, oldX2 = x2;
|
||||
x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02);
|
||||
y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12);
|
||||
x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02);
|
||||
y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12);
|
||||
}
|
||||
|
||||
/** Transforms three 2D coordinates using this matrix.
|
||||
This is just a shortcut for calling transformPoint() on each of these pairs of
|
||||
coordinates in turn. (And putting all the calculations into one function hopefully
|
||||
also gives the compiler a bit more scope for pipelining it).
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void transformPoints (ValueType& x1, ValueType& y1,
|
||||
ValueType& x2, ValueType& y2,
|
||||
ValueType& x3, ValueType& y3) const noexcept
|
||||
{
|
||||
auto oldX1 = x1, oldX2 = x2, oldX3 = x3;
|
||||
x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02);
|
||||
y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12);
|
||||
x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02);
|
||||
y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12);
|
||||
x3 = static_cast<ValueType> (mat00 * oldX3 + mat01 * y3 + mat02);
|
||||
y3 = static_cast<ValueType> (mat10 * oldX3 + mat11 * y3 + mat12);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a new transform which is the same as this one followed by a translation. */
|
||||
AffineTransform translated (float deltaX,
|
||||
float deltaY) const noexcept;
|
||||
|
||||
/** Returns a new transform which is the same as this one followed by a translation. */
|
||||
template <typename PointType>
|
||||
AffineTransform translated (PointType delta) const noexcept
|
||||
{
|
||||
return translated ((float) delta.x, (float) delta.y);
|
||||
}
|
||||
|
||||
/** Returns a new transform which is a translation. */
|
||||
static AffineTransform translation (float deltaX,
|
||||
float deltaY) noexcept;
|
||||
|
||||
/** Returns a new transform which is a translation. */
|
||||
template <typename PointType>
|
||||
static AffineTransform translation (PointType delta) noexcept
|
||||
{
|
||||
return translation ((float) delta.x, (float) delta.y);
|
||||
}
|
||||
|
||||
/** Returns a copy of this transform with the specified translation matrix values. */
|
||||
AffineTransform withAbsoluteTranslation (float translationX,
|
||||
float translationY) const noexcept;
|
||||
|
||||
/** Returns a transform which is the same as this one followed by a rotation.
|
||||
|
||||
The rotation is specified by a number of radians to rotate clockwise, centred around
|
||||
the origin (0, 0).
|
||||
*/
|
||||
AffineTransform rotated (float angleInRadians) const noexcept;
|
||||
|
||||
/** Returns a transform which is the same as this one followed by a rotation about a given point.
|
||||
|
||||
The rotation is specified by a number of radians to rotate clockwise, centred around
|
||||
the coordinates passed in.
|
||||
*/
|
||||
AffineTransform rotated (float angleInRadians,
|
||||
float pivotX,
|
||||
float pivotY) const noexcept;
|
||||
|
||||
/** Returns a new transform which is a rotation about (0, 0). */
|
||||
static AffineTransform rotation (float angleInRadians) noexcept;
|
||||
|
||||
/** Returns a new transform which is a rotation about a given point. */
|
||||
static AffineTransform rotation (float angleInRadians,
|
||||
float pivotX,
|
||||
float pivotY) noexcept;
|
||||
|
||||
/** Returns a transform which is the same as this one followed by a re-scaling.
|
||||
The scaling is centred around the origin (0, 0).
|
||||
*/
|
||||
AffineTransform scaled (float factorX,
|
||||
float factorY) const noexcept;
|
||||
|
||||
/** Returns a transform which is the same as this one followed by a re-scaling.
|
||||
The scaling is centred around the origin (0, 0).
|
||||
*/
|
||||
AffineTransform scaled (float factor) const noexcept;
|
||||
|
||||
/** Returns a transform which is the same as this one followed by a re-scaling.
|
||||
The scaling is centred around the origin provided.
|
||||
*/
|
||||
AffineTransform scaled (float factorX, float factorY,
|
||||
float pivotX, float pivotY) const noexcept;
|
||||
|
||||
/** Returns a new transform which is a re-scale about the origin. */
|
||||
static AffineTransform scale (float factorX,
|
||||
float factorY) noexcept;
|
||||
|
||||
/** Returns a new transform which is a re-scale about the origin. */
|
||||
static AffineTransform scale (float factor) noexcept;
|
||||
|
||||
/** Returns a new transform which is a re-scale centred around the point provided. */
|
||||
static AffineTransform scale (float factorX, float factorY,
|
||||
float pivotX, float pivotY) noexcept;
|
||||
|
||||
/** Returns a transform which is the same as this one followed by a shear.
|
||||
The shear is centred around the origin (0, 0).
|
||||
*/
|
||||
AffineTransform sheared (float shearX, float shearY) const noexcept;
|
||||
|
||||
/** Returns a shear transform, centred around the origin (0, 0). */
|
||||
static AffineTransform shear (float shearX, float shearY) noexcept;
|
||||
|
||||
/** Returns a transform that will flip coordinates vertically within a window of the given height.
|
||||
This is handy for converting between upside-down coordinate systems such as OpenGL or CoreGraphics.
|
||||
*/
|
||||
static AffineTransform verticalFlip (float height) noexcept;
|
||||
|
||||
/** Returns a matrix which is the inverse operation of this one.
|
||||
|
||||
Some matrices don't have an inverse - in this case, the method will just return
|
||||
an identity transform.
|
||||
*/
|
||||
AffineTransform inverted() const noexcept;
|
||||
|
||||
/** Returns the transform that will map three known points onto three coordinates
|
||||
that are supplied.
|
||||
|
||||
This returns the transform that will transform (0, 0) into (x00, y00),
|
||||
(1, 0) to (x10, y10), and (0, 1) to (x01, y01).
|
||||
*/
|
||||
static AffineTransform fromTargetPoints (float x00, float y00,
|
||||
float x10, float y10,
|
||||
float x01, float y01) noexcept;
|
||||
|
||||
/** Returns the transform that will map three specified points onto three target points. */
|
||||
static AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1,
|
||||
float sourceX2, float sourceY2, float targetX2, float targetY2,
|
||||
float sourceX3, float sourceY3, float targetX3, float targetY3) noexcept;
|
||||
|
||||
/** Returns the transform that will map three specified points onto three target points. */
|
||||
template <typename PointType>
|
||||
static AffineTransform fromTargetPoints (PointType source1, PointType target1,
|
||||
PointType source2, PointType target2,
|
||||
PointType source3, PointType target3) noexcept
|
||||
{
|
||||
return fromTargetPoints (source1.x, source1.y, target1.x, target1.y,
|
||||
source2.x, source2.y, target2.x, target2.y,
|
||||
source3.x, source3.y, target3.x, target3.y);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the result of concatenating another transformation after this one. */
|
||||
AffineTransform followedBy (const AffineTransform& other) const noexcept;
|
||||
|
||||
/** Returns true if this transform has no effect on points. */
|
||||
bool isIdentity() const noexcept;
|
||||
|
||||
/** Returns true if this transform maps to a singularity - i.e. if it has no inverse. */
|
||||
bool isSingularity() const noexcept;
|
||||
|
||||
/** Returns true if the transform only translates, and doesn't scale or rotate the
|
||||
points. */
|
||||
bool isOnlyTranslation() const noexcept;
|
||||
|
||||
/** If this transform is only a translation, this returns the X offset.
|
||||
@see isOnlyTranslation
|
||||
*/
|
||||
float getTranslationX() const noexcept { return mat02; }
|
||||
|
||||
/** If this transform is only a translation, this returns the X offset.
|
||||
@see isOnlyTranslation
|
||||
*/
|
||||
float getTranslationY() const noexcept { return mat12; }
|
||||
|
||||
/** Returns the determinant of the transform. */
|
||||
float getDeterminant() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** This method has been deprecated.
|
||||
|
||||
You can calculate the scale factor using:
|
||||
@code
|
||||
std::sqrt (std::abs (AffineTransform::getDeterminant()))
|
||||
@endcode
|
||||
|
||||
This method produces incorrect values for transforms containing rotations.
|
||||
|
||||
Returns the approximate scale factor by which lengths will be transformed.
|
||||
Obviously a length may be scaled by entirely different amounts depending on its
|
||||
direction, so this is only appropriate as a rough guide.
|
||||
*/
|
||||
[[deprecated ("This method produces incorrect values for transforms containing rotations. "
|
||||
"See the method docs for a code example on how to calculate the correct scale factor.")]]
|
||||
float getScaleFactor() const noexcept;
|
||||
|
||||
[[deprecated ("If you need an identity transform, just use AffineTransform() or {}.")]]
|
||||
static const AffineTransform identity;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/* The transform matrix is:
|
||||
|
||||
(mat00 mat01 mat02)
|
||||
(mat10 mat11 mat12)
|
||||
( 0 0 1 )
|
||||
*/
|
||||
float mat00 { 1.0f }, mat01 { 0.0f }, mat02 { 0.0f };
|
||||
float mat10 { 0.0f }, mat11 { 1.0f }, mat12 { 0.0f };
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Specifies a set of gaps to be left around the sides of a rectangle.
|
||||
|
||||
This is basically the size of the spaces at the top, bottom, left and right of
|
||||
a rectangle. It's used by various component classes to specify borders.
|
||||
|
||||
@see Rectangle
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
template <typename ValueType>
|
||||
class BorderSize
|
||||
{
|
||||
auto tie() const { return std::tie (top, left, bottom, right); }
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a null border.
|
||||
All sizes are left as 0.
|
||||
*/
|
||||
BorderSize() = default;
|
||||
|
||||
/** Creates a border with the given gaps. */
|
||||
BorderSize (ValueType topGap, ValueType leftGap, ValueType bottomGap, ValueType rightGap) noexcept
|
||||
: top (topGap), left (leftGap), bottom (bottomGap), right (rightGap)
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a border with the given gap on all sides. */
|
||||
explicit BorderSize (ValueType allGaps) noexcept
|
||||
: top (allGaps), left (allGaps), bottom (allGaps), right (allGaps)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the gap that should be left at the top of the region. */
|
||||
ValueType getTop() const noexcept { return top; }
|
||||
|
||||
/** Returns the gap that should be left at the left of the region. */
|
||||
ValueType getLeft() const noexcept { return left; }
|
||||
|
||||
/** Returns the gap that should be left at the bottom of the region. */
|
||||
ValueType getBottom() const noexcept { return bottom; }
|
||||
|
||||
/** Returns the gap that should be left at the right of the region. */
|
||||
ValueType getRight() const noexcept { return right; }
|
||||
|
||||
/** Returns the sum of the top and bottom gaps. */
|
||||
ValueType getTopAndBottom() const noexcept { return top + bottom; }
|
||||
|
||||
/** Returns the sum of the left and right gaps. */
|
||||
ValueType getLeftAndRight() const noexcept { return left + right; }
|
||||
|
||||
/** Returns true if this border has no thickness along any edge. */
|
||||
bool isEmpty() const noexcept { return left + right + top + bottom == ValueType(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the top gap. */
|
||||
void setTop (ValueType newTopGap) noexcept { top = newTopGap; }
|
||||
|
||||
/** Changes the left gap. */
|
||||
void setLeft (ValueType newLeftGap) noexcept { left = newLeftGap; }
|
||||
|
||||
/** Changes the bottom gap. */
|
||||
void setBottom (ValueType newBottomGap) noexcept { bottom = newBottomGap; }
|
||||
|
||||
/** Changes the right gap. */
|
||||
void setRight (ValueType newRightGap) noexcept { right = newRightGap; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a rectangle with these borders removed from it. */
|
||||
Rectangle<ValueType> subtractedFrom (const Rectangle<ValueType>& original) const noexcept
|
||||
{
|
||||
return { original.getX() + left,
|
||||
original.getY() + top,
|
||||
original.getWidth() - (left + right),
|
||||
original.getHeight() - (top + bottom) };
|
||||
}
|
||||
|
||||
/** Removes this border from a given rectangle. */
|
||||
void subtractFrom (Rectangle<ValueType>& rectangle) const noexcept
|
||||
{
|
||||
rectangle = subtractedFrom (rectangle);
|
||||
}
|
||||
|
||||
/** Returns a rectangle with these borders added around it. */
|
||||
Rectangle<ValueType> addedTo (const Rectangle<ValueType>& original) const noexcept
|
||||
{
|
||||
return { original.getX() - left,
|
||||
original.getY() - top,
|
||||
original.getWidth() + (left + right),
|
||||
original.getHeight() + (top + bottom) };
|
||||
}
|
||||
|
||||
/** Adds this border around a given rectangle. */
|
||||
void addTo (Rectangle<ValueType>& rectangle) const noexcept
|
||||
{
|
||||
rectangle = addedTo (rectangle);
|
||||
}
|
||||
|
||||
/** Removes this border from another border. */
|
||||
BorderSize<ValueType> subtractedFrom (const BorderSize<ValueType>& other) const noexcept
|
||||
{
|
||||
return { other.top - top,
|
||||
other.left - left,
|
||||
other.bottom - bottom,
|
||||
other.right - right };
|
||||
}
|
||||
|
||||
/** Adds this border to another border. */
|
||||
BorderSize<ValueType> addedTo (const BorderSize<ValueType>& other) const noexcept
|
||||
{
|
||||
return { other.top + top,
|
||||
other.left + left,
|
||||
other.bottom + bottom,
|
||||
other.right + right };
|
||||
}
|
||||
|
||||
/** Multiplies each member of the border by a scalar. */
|
||||
template <typename ScalarType>
|
||||
BorderSize<ValueType> multipliedBy (ScalarType scalar) const noexcept
|
||||
{
|
||||
return { static_cast<ValueType> (scalar * top),
|
||||
static_cast<ValueType> (scalar * left),
|
||||
static_cast<ValueType> (scalar * bottom),
|
||||
static_cast<ValueType> (scalar * right) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool operator== (const BorderSize& other) const noexcept { return tie() == other.tie(); }
|
||||
bool operator!= (const BorderSize& other) const noexcept { return tie() != other.tie(); }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ValueType top{}, left{}, bottom{}, right{};
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,843 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6255 6263 6386)
|
||||
|
||||
EdgeTable::EdgeTable (Rectangle<int> area, const Path& path, const AffineTransform& transform)
|
||||
: bounds (area),
|
||||
// this is a very vague heuristic to make a rough guess at a good table size
|
||||
// for a given path, such that it's big enough to mostly avoid remapping, but also
|
||||
// not so big that it's wasteful for simple paths.
|
||||
maxEdgesPerLine (jmax (defaultEdgesPerLine / 2,
|
||||
4 * (int) std::sqrt (path.data.size()))),
|
||||
lineStrideElements (maxEdgesPerLine * 2 + 1)
|
||||
{
|
||||
allocate();
|
||||
int* t = table;
|
||||
|
||||
for (int i = bounds.getHeight(); --i >= 0;)
|
||||
{
|
||||
*t = 0;
|
||||
t += lineStrideElements;
|
||||
}
|
||||
|
||||
auto leftLimit = scale * static_cast<int64_t> (bounds.getX());
|
||||
auto topLimit = scale * static_cast<int64_t> (bounds.getY());
|
||||
auto rightLimit = scale * static_cast<int64_t> (bounds.getRight());
|
||||
auto heightLimit = scale * static_cast<int64_t> (bounds.getHeight());
|
||||
|
||||
PathFlatteningIterator iter (path, transform);
|
||||
|
||||
while (iter.next())
|
||||
{
|
||||
const auto scaleIterY = [] (auto y)
|
||||
{
|
||||
return static_cast<int64_t> (y * 256.0f + (y >= 0 ? 0.5f : -0.5f));
|
||||
};
|
||||
|
||||
auto y1 = scaleIterY (iter.y1);
|
||||
auto y2 = scaleIterY (iter.y2);
|
||||
|
||||
if (y1 != y2)
|
||||
{
|
||||
y1 -= topLimit;
|
||||
y2 -= topLimit;
|
||||
|
||||
auto startY = y1;
|
||||
int direction = -1;
|
||||
|
||||
if (y1 > y2)
|
||||
{
|
||||
std::swap (y1, y2);
|
||||
direction = 1;
|
||||
}
|
||||
|
||||
if (y1 < 0)
|
||||
y1 = 0;
|
||||
|
||||
if (y2 > heightLimit)
|
||||
y2 = heightLimit;
|
||||
|
||||
if (y1 < y2)
|
||||
{
|
||||
const double startX = 256.0f * iter.x1;
|
||||
const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1);
|
||||
auto stepSize = static_cast<int64_t> (jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))));
|
||||
|
||||
do
|
||||
{
|
||||
auto step = jmin (stepSize, y2 - y1, 256 - (y1 & 255));
|
||||
auto x = static_cast<int64_t> (startX + multiplier * static_cast<double> ((y1 + (step >> 1)) - startY));
|
||||
auto clampedX = static_cast<int> (jlimit (leftLimit, rightLimit - 1, x));
|
||||
|
||||
addEdgePoint (clampedX, static_cast<int> (y1 / scale), static_cast<int> (direction * step));
|
||||
y1 += step;
|
||||
}
|
||||
while (y1 < y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sanitiseLevels (path.isUsingNonZeroWinding());
|
||||
}
|
||||
|
||||
EdgeTable::EdgeTable (Rectangle<int> rectangleToAdd)
|
||||
: bounds (rectangleToAdd),
|
||||
maxEdgesPerLine (defaultEdgesPerLine),
|
||||
lineStrideElements (defaultEdgesPerLine * 2 + 1)
|
||||
{
|
||||
allocate();
|
||||
table[0] = 0;
|
||||
|
||||
auto x1 = scale * rectangleToAdd.getX();
|
||||
auto x2 = scale * rectangleToAdd.getRight();
|
||||
int* t = table;
|
||||
|
||||
for (int i = rectangleToAdd.getHeight(); --i >= 0;)
|
||||
{
|
||||
t[0] = 2;
|
||||
t[1] = x1;
|
||||
t[2] = 255;
|
||||
t[3] = x2;
|
||||
t[4] = 0;
|
||||
t += lineStrideElements;
|
||||
}
|
||||
}
|
||||
|
||||
EdgeTable::EdgeTable (const RectangleList<int>& rectanglesToAdd)
|
||||
: bounds (rectanglesToAdd.getBounds()),
|
||||
maxEdgesPerLine (defaultEdgesPerLine),
|
||||
lineStrideElements (defaultEdgesPerLine * 2 + 1),
|
||||
needToCheckEmptiness (true)
|
||||
{
|
||||
allocate();
|
||||
clearLineSizes();
|
||||
|
||||
for (auto& r : rectanglesToAdd)
|
||||
{
|
||||
auto x1 = scale * r.getX();
|
||||
auto x2 = scale * r.getRight();
|
||||
auto y = r.getY() - bounds.getY();
|
||||
|
||||
for (int j = r.getHeight(); --j >= 0;)
|
||||
addEdgePointPair (x1, x2, y++, 255);
|
||||
}
|
||||
|
||||
sanitiseLevels (true);
|
||||
}
|
||||
|
||||
EdgeTable::EdgeTable (const RectangleList<float>& rectanglesToAdd)
|
||||
: bounds (rectanglesToAdd.getBounds().getSmallestIntegerContainer()),
|
||||
maxEdgesPerLine (rectanglesToAdd.getNumRectangles() * 2),
|
||||
lineStrideElements (rectanglesToAdd.getNumRectangles() * 4 + 1)
|
||||
{
|
||||
bounds.setHeight (bounds.getHeight() + 1);
|
||||
allocate();
|
||||
clearLineSizes();
|
||||
|
||||
for (auto& r : rectanglesToAdd)
|
||||
{
|
||||
auto x1 = roundToInt ((float) scale * r.getX());
|
||||
auto x2 = roundToInt ((float) scale * r.getRight());
|
||||
|
||||
auto y1 = roundToInt ((float) scale * r.getY()) - (bounds.getY() * scale);
|
||||
auto y2 = roundToInt ((float) scale * r.getBottom()) - (bounds.getY() * scale);
|
||||
|
||||
if (x2 <= x1 || y2 <= y1)
|
||||
continue;
|
||||
|
||||
auto y = y1 / scale;
|
||||
auto lastLine = y2 / scale;
|
||||
|
||||
if (y == lastLine)
|
||||
{
|
||||
addEdgePointPair (x1, x2, y, y2 - y1);
|
||||
}
|
||||
else
|
||||
{
|
||||
addEdgePointPair (x1, x2, y++, 255 - (y1 & 255));
|
||||
|
||||
while (y < lastLine)
|
||||
addEdgePointPair (x1, x2, y++, 255);
|
||||
|
||||
jassert (y < bounds.getHeight());
|
||||
addEdgePointPair (x1, x2, y, y2 & 255);
|
||||
}
|
||||
}
|
||||
|
||||
sanitiseLevels (true);
|
||||
}
|
||||
|
||||
EdgeTable::EdgeTable (Rectangle<float> rectangleToAdd)
|
||||
: bounds ((int) std::floor (rectangleToAdd.getX()),
|
||||
roundToInt (rectangleToAdd.getY() * 256.0f) / scale,
|
||||
2 + (int) rectangleToAdd.getWidth(),
|
||||
2 + (int) rectangleToAdd.getHeight()),
|
||||
maxEdgesPerLine (defaultEdgesPerLine),
|
||||
lineStrideElements ((defaultEdgesPerLine * 2) + 1)
|
||||
{
|
||||
jassert (! rectangleToAdd.isEmpty());
|
||||
allocate();
|
||||
table[0] = 0;
|
||||
|
||||
auto x1 = roundToInt ((float) scale * rectangleToAdd.getX());
|
||||
auto x2 = roundToInt ((float) scale * rectangleToAdd.getRight());
|
||||
auto y1 = roundToInt ((float) scale * rectangleToAdd.getY()) - (bounds.getY() * scale);
|
||||
auto y2 = roundToInt ((float) scale * rectangleToAdd.getBottom()) - (bounds.getY() * scale);
|
||||
jassert (y1 < 256);
|
||||
|
||||
if (x2 <= x1 || y2 <= y1)
|
||||
{
|
||||
bounds.setHeight (0);
|
||||
return;
|
||||
}
|
||||
|
||||
int lineY = 0;
|
||||
int* t = table;
|
||||
|
||||
if ((y1 / scale) == (y2 / scale))
|
||||
{
|
||||
t[0] = 2;
|
||||
t[1] = x1;
|
||||
t[2] = y2 - y1;
|
||||
t[3] = x2;
|
||||
t[4] = 0;
|
||||
++lineY;
|
||||
t += lineStrideElements;
|
||||
}
|
||||
else
|
||||
{
|
||||
t[0] = 2;
|
||||
t[1] = x1;
|
||||
t[2] = 255 - (y1 & 255);
|
||||
t[3] = x2;
|
||||
t[4] = 0;
|
||||
++lineY;
|
||||
t += lineStrideElements;
|
||||
|
||||
while (lineY < (y2 / scale))
|
||||
{
|
||||
t[0] = 2;
|
||||
t[1] = x1;
|
||||
t[2] = 255;
|
||||
t[3] = x2;
|
||||
t[4] = 0;
|
||||
++lineY;
|
||||
t += lineStrideElements;
|
||||
}
|
||||
|
||||
jassert (lineY < bounds.getHeight());
|
||||
t[0] = 2;
|
||||
t[1] = x1;
|
||||
t[2] = y2 & 255;
|
||||
t[3] = x2;
|
||||
t[4] = 0;
|
||||
++lineY;
|
||||
t += lineStrideElements;
|
||||
}
|
||||
|
||||
while (lineY < bounds.getHeight())
|
||||
{
|
||||
t[0] = 0;
|
||||
t += lineStrideElements;
|
||||
++lineY;
|
||||
}
|
||||
}
|
||||
|
||||
EdgeTable::EdgeTable (const EdgeTable& other)
|
||||
{
|
||||
operator= (other);
|
||||
}
|
||||
|
||||
EdgeTable& EdgeTable::operator= (const EdgeTable& other)
|
||||
{
|
||||
bounds = other.bounds;
|
||||
maxEdgesPerLine = other.maxEdgesPerLine;
|
||||
lineStrideElements = other.lineStrideElements;
|
||||
needToCheckEmptiness = other.needToCheckEmptiness;
|
||||
|
||||
allocate();
|
||||
copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight());
|
||||
return *this;
|
||||
}
|
||||
|
||||
EdgeTable::~EdgeTable()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static size_t getEdgeTableAllocationSize (int lineStride, int height) noexcept
|
||||
{
|
||||
// (leave an extra line at the end for use as scratch space)
|
||||
return (size_t) (lineStride * (2 + jmax (0, height)));
|
||||
}
|
||||
|
||||
void EdgeTable::allocate()
|
||||
{
|
||||
table.malloc (getEdgeTableAllocationSize (lineStrideElements, bounds.getHeight()));
|
||||
}
|
||||
|
||||
void EdgeTable::clearLineSizes() noexcept
|
||||
{
|
||||
int* t = table;
|
||||
|
||||
for (int i = bounds.getHeight(); --i >= 0;)
|
||||
{
|
||||
*t = 0;
|
||||
t += lineStrideElements;
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::copyEdgeTableData (int* dest, int destLineStride, const int* src, int srcLineStride, int numLines) noexcept
|
||||
{
|
||||
while (--numLines >= 0)
|
||||
{
|
||||
memcpy (dest, src, (size_t) (src[0] * 2 + 1) * sizeof (int));
|
||||
src += srcLineStride;
|
||||
dest += destLineStride;
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) noexcept
|
||||
{
|
||||
// Convert the table from relative windings to absolute levels..
|
||||
int* lineStart = table;
|
||||
|
||||
for (int y = bounds.getHeight(); --y >= 0;)
|
||||
{
|
||||
auto num = lineStart[0];
|
||||
|
||||
if (num > 0)
|
||||
{
|
||||
auto* items = reinterpret_cast<LineItem*> (lineStart + 1);
|
||||
auto* itemsEnd = items + num;
|
||||
|
||||
// sort the X coords
|
||||
std::sort (items, itemsEnd);
|
||||
|
||||
auto* src = items;
|
||||
auto correctedNum = num;
|
||||
int level = 0;
|
||||
|
||||
while (src < itemsEnd)
|
||||
{
|
||||
level += src->level;
|
||||
auto x = src->x;
|
||||
++src;
|
||||
|
||||
while (src < itemsEnd && src->x == x)
|
||||
{
|
||||
level += src->level;
|
||||
++src;
|
||||
--correctedNum;
|
||||
}
|
||||
|
||||
auto corrected = std::abs (level);
|
||||
|
||||
if (corrected / scale)
|
||||
{
|
||||
if (useNonZeroWinding)
|
||||
{
|
||||
corrected = 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
corrected &= 511;
|
||||
|
||||
if (corrected / scale)
|
||||
corrected = 511 - corrected;
|
||||
}
|
||||
}
|
||||
|
||||
items->x = x;
|
||||
items->level = corrected;
|
||||
++items;
|
||||
}
|
||||
|
||||
lineStart[0] = correctedNum;
|
||||
(items - 1)->level = 0; // force the last level to 0, just in case something went wrong in creating the table
|
||||
}
|
||||
|
||||
lineStart += lineStrideElements;
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine)
|
||||
{
|
||||
if (newNumEdgesPerLine != maxEdgesPerLine)
|
||||
{
|
||||
maxEdgesPerLine = newNumEdgesPerLine;
|
||||
|
||||
jassert (bounds.getHeight() > 0);
|
||||
auto newLineStrideElements = maxEdgesPerLine * 2 + 1;
|
||||
|
||||
HeapBlock<int> newTable (getEdgeTableAllocationSize (newLineStrideElements, bounds.getHeight()));
|
||||
|
||||
copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight());
|
||||
|
||||
table.swapWith (newTable);
|
||||
lineStrideElements = newLineStrideElements;
|
||||
}
|
||||
}
|
||||
|
||||
inline void EdgeTable::remapWithExtraSpace (int numPoints)
|
||||
{
|
||||
remapTableForNumEdges (numPoints * 2);
|
||||
jassert (numPoints < maxEdgesPerLine);
|
||||
}
|
||||
|
||||
void EdgeTable::optimiseTable()
|
||||
{
|
||||
int maxLineElements = 0;
|
||||
|
||||
for (int i = bounds.getHeight(); --i >= 0;)
|
||||
maxLineElements = jmax (maxLineElements, table[i * lineStrideElements]);
|
||||
|
||||
remapTableForNumEdges (maxLineElements);
|
||||
}
|
||||
|
||||
void EdgeTable::addEdgePoint (const int x, const int y, const int winding)
|
||||
{
|
||||
jassert (y >= 0 && y < bounds.getHeight());
|
||||
|
||||
auto* line = table + lineStrideElements * y;
|
||||
auto numPoints = line[0];
|
||||
|
||||
if (numPoints >= maxEdgesPerLine)
|
||||
{
|
||||
remapWithExtraSpace (numPoints);
|
||||
line = table + lineStrideElements * y;
|
||||
}
|
||||
|
||||
line[0] = numPoints + 1;
|
||||
line += numPoints * 2;
|
||||
line[1] = x;
|
||||
line[2] = winding;
|
||||
}
|
||||
|
||||
void EdgeTable::addEdgePointPair (int x1, int x2, int y, int winding)
|
||||
{
|
||||
jassert (y >= 0 && y < bounds.getHeight());
|
||||
|
||||
auto* line = table + lineStrideElements * y;
|
||||
auto numPoints = line[0];
|
||||
|
||||
if (numPoints + 1 >= maxEdgesPerLine)
|
||||
{
|
||||
remapWithExtraSpace (numPoints + 1);
|
||||
line = table + lineStrideElements * y;
|
||||
}
|
||||
|
||||
line[0] = numPoints + 2;
|
||||
line += numPoints * 2;
|
||||
line[1] = x1;
|
||||
line[2] = winding;
|
||||
line[3] = x2;
|
||||
line[4] = -winding;
|
||||
}
|
||||
|
||||
void EdgeTable::translate (float dx, int dy) noexcept
|
||||
{
|
||||
bounds.translate ((int) std::floor (dx), dy);
|
||||
|
||||
int* lineStart = table;
|
||||
auto intDx = (int) (dx * 256.0f);
|
||||
|
||||
for (int i = bounds.getHeight(); --i >= 0;)
|
||||
{
|
||||
auto* line = lineStart;
|
||||
lineStart += lineStrideElements;
|
||||
auto num = *line++;
|
||||
|
||||
while (--num >= 0)
|
||||
{
|
||||
*line += intDx;
|
||||
line += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::multiplyLevels (float amount)
|
||||
{
|
||||
int* lineStart = table;
|
||||
auto multiplier = (int) (amount * 256.0f);
|
||||
|
||||
for (int y = 0; y < bounds.getHeight(); ++y)
|
||||
{
|
||||
auto numPoints = lineStart[0];
|
||||
auto* item = reinterpret_cast<LineItem*> (lineStart + 1);
|
||||
lineStart += lineStrideElements;
|
||||
|
||||
while (--numPoints > 0)
|
||||
{
|
||||
item->level = jmin (255, (item->level * multiplier) / scale);
|
||||
++item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::intersectWithEdgeTableLine (const int y, const int* const otherLine)
|
||||
{
|
||||
jassert (y >= 0 && y < bounds.getHeight());
|
||||
|
||||
auto* srcLine = table + lineStrideElements * y;
|
||||
auto srcNum1 = *srcLine;
|
||||
|
||||
if (srcNum1 == 0)
|
||||
return;
|
||||
|
||||
auto srcNum2 = *otherLine;
|
||||
|
||||
if (srcNum2 == 0)
|
||||
{
|
||||
*srcLine = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
auto right = bounds.getRight() * scale;
|
||||
|
||||
// optimise for the common case where our line lies entirely within a
|
||||
// single pair of points, as happens when clipping to a simple rect.
|
||||
if (srcNum2 == 2 && otherLine[2] >= 255)
|
||||
{
|
||||
clipEdgeTableLineToRange (srcLine, otherLine[1], jmin (right, otherLine[3]));
|
||||
return;
|
||||
}
|
||||
|
||||
bool isUsingTempSpace = false;
|
||||
|
||||
const int* src1 = srcLine + 1;
|
||||
auto x1 = *src1++;
|
||||
|
||||
const int* src2 = otherLine + 1;
|
||||
auto x2 = *src2++;
|
||||
|
||||
int destIndex = 0, destTotal = 0;
|
||||
int level1 = 0, level2 = 0;
|
||||
int lastX = std::numeric_limits<int>::min(), lastLevel = 0;
|
||||
|
||||
while (srcNum1 > 0 && srcNum2 > 0)
|
||||
{
|
||||
int nextX;
|
||||
|
||||
if (x1 <= x2)
|
||||
{
|
||||
if (x1 == x2)
|
||||
{
|
||||
level2 = *src2++;
|
||||
x2 = *src2++;
|
||||
--srcNum2;
|
||||
}
|
||||
|
||||
nextX = x1;
|
||||
level1 = *src1++;
|
||||
x1 = *src1++;
|
||||
--srcNum1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextX = x2;
|
||||
level2 = *src2++;
|
||||
x2 = *src2++;
|
||||
--srcNum2;
|
||||
}
|
||||
|
||||
if (nextX > lastX)
|
||||
{
|
||||
if (nextX >= right)
|
||||
break;
|
||||
|
||||
lastX = nextX;
|
||||
|
||||
auto nextLevel = (level1 * (level2 + 1)) / scale;
|
||||
jassert (isPositiveAndBelow (nextLevel, 256));
|
||||
|
||||
if (nextLevel != lastLevel)
|
||||
{
|
||||
if (destTotal >= maxEdgesPerLine)
|
||||
{
|
||||
srcLine[0] = destTotal;
|
||||
|
||||
if (isUsingTempSpace)
|
||||
{
|
||||
auto tempSize = (size_t) srcNum1 * 2 * sizeof (int);
|
||||
auto oldTemp = static_cast<int*> (alloca (tempSize));
|
||||
memcpy (oldTemp, src1, tempSize);
|
||||
|
||||
remapTableForNumEdges (jmax (256, destTotal * 2));
|
||||
srcLine = table + lineStrideElements * y;
|
||||
|
||||
auto* newTemp = table + lineStrideElements * bounds.getHeight();
|
||||
memcpy (newTemp, oldTemp, tempSize);
|
||||
src1 = newTemp;
|
||||
}
|
||||
else
|
||||
{
|
||||
remapTableForNumEdges (jmax (256, destTotal * 2));
|
||||
srcLine = table + lineStrideElements * y;
|
||||
}
|
||||
}
|
||||
|
||||
++destTotal;
|
||||
lastLevel = nextLevel;
|
||||
|
||||
if (! isUsingTempSpace)
|
||||
{
|
||||
isUsingTempSpace = true;
|
||||
auto* temp = table + lineStrideElements * bounds.getHeight();
|
||||
memcpy (temp, src1, (size_t) srcNum1 * 2 * sizeof (int));
|
||||
src1 = temp;
|
||||
}
|
||||
|
||||
srcLine[++destIndex] = nextX;
|
||||
srcLine[++destIndex] = nextLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastLevel > 0)
|
||||
{
|
||||
if (destTotal >= maxEdgesPerLine)
|
||||
{
|
||||
srcLine[0] = destTotal;
|
||||
remapTableForNumEdges (jmax (256, destTotal * 2));
|
||||
srcLine = table + lineStrideElements * y;
|
||||
}
|
||||
|
||||
++destTotal;
|
||||
srcLine[++destIndex] = right;
|
||||
srcLine[++destIndex] = 0;
|
||||
}
|
||||
|
||||
srcLine[0] = destTotal;
|
||||
}
|
||||
|
||||
void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) noexcept
|
||||
{
|
||||
int* lastItem = dest + (dest[0] * 2 - 1);
|
||||
|
||||
if (x2 < lastItem[0])
|
||||
{
|
||||
if (x2 <= dest[1])
|
||||
{
|
||||
dest[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
while (x2 < lastItem[-2])
|
||||
{
|
||||
--(dest[0]);
|
||||
lastItem -= 2;
|
||||
}
|
||||
|
||||
lastItem[0] = x2;
|
||||
lastItem[1] = 0;
|
||||
}
|
||||
|
||||
if (x1 > dest[1])
|
||||
{
|
||||
while (lastItem[0] > x1)
|
||||
lastItem -= 2;
|
||||
|
||||
auto itemsRemoved = (int) (lastItem - (dest + 1)) / 2;
|
||||
|
||||
if (itemsRemoved > 0)
|
||||
{
|
||||
dest[0] -= itemsRemoved;
|
||||
memmove (dest + 1, lastItem, (size_t) dest[0] * (sizeof (int) * 2));
|
||||
}
|
||||
|
||||
dest[1] = x1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void EdgeTable::clipToRectangle (Rectangle<int> r)
|
||||
{
|
||||
auto clipped = r.getIntersection (bounds);
|
||||
|
||||
if (clipped.isEmpty())
|
||||
{
|
||||
needToCheckEmptiness = false;
|
||||
bounds.setHeight (0);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto top = clipped.getY() - bounds.getY();
|
||||
auto bottom = clipped.getBottom() - bounds.getY();
|
||||
|
||||
if (bottom < bounds.getHeight())
|
||||
bounds.setHeight (bottom);
|
||||
|
||||
for (int i = 0; i < top; ++i)
|
||||
table[lineStrideElements * i] = 0;
|
||||
|
||||
if (clipped.getX() > bounds.getX() || clipped.getRight() < bounds.getRight())
|
||||
{
|
||||
auto x1 = scale * clipped.getX();
|
||||
auto x2 = scale * jmin (bounds.getRight(), clipped.getRight());
|
||||
int* line = table + lineStrideElements * top;
|
||||
|
||||
for (int i = bottom - top; --i >= 0;)
|
||||
{
|
||||
if (line[0] != 0)
|
||||
clipEdgeTableLineToRange (line, x1, x2);
|
||||
|
||||
line += lineStrideElements;
|
||||
}
|
||||
}
|
||||
|
||||
needToCheckEmptiness = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::excludeRectangle (Rectangle<int> r)
|
||||
{
|
||||
auto clipped = r.getIntersection (bounds);
|
||||
|
||||
if (! clipped.isEmpty())
|
||||
{
|
||||
auto top = clipped.getY() - bounds.getY();
|
||||
auto bottom = clipped.getBottom() - bounds.getY();
|
||||
|
||||
const int rectLine[] = { 4, std::numeric_limits<int>::min(), 255,
|
||||
scale * clipped.getX(), 0,
|
||||
scale * clipped.getRight(), 255,
|
||||
std::numeric_limits<int>::max(), 0 };
|
||||
|
||||
for (int i = top; i < bottom; ++i)
|
||||
intersectWithEdgeTableLine (i, rectLine);
|
||||
|
||||
needToCheckEmptiness = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::clipToEdgeTable (const EdgeTable& other)
|
||||
{
|
||||
auto clipped = other.bounds.getIntersection (bounds);
|
||||
|
||||
if (clipped.isEmpty())
|
||||
{
|
||||
needToCheckEmptiness = false;
|
||||
bounds.setHeight (0);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto top = clipped.getY() - bounds.getY();
|
||||
auto bottom = clipped.getBottom() - bounds.getY();
|
||||
|
||||
if (bottom < bounds.getHeight())
|
||||
bounds.setHeight (bottom);
|
||||
|
||||
if (clipped.getRight() < bounds.getRight())
|
||||
bounds.setRight (clipped.getRight());
|
||||
|
||||
for (int i = 0; i < top; ++i)
|
||||
table[lineStrideElements * i] = 0;
|
||||
|
||||
auto* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY());
|
||||
|
||||
for (int i = top; i < bottom; ++i)
|
||||
{
|
||||
intersectWithEdgeTableLine (i, otherLine);
|
||||
otherLine += other.lineStrideElements;
|
||||
}
|
||||
|
||||
needToCheckEmptiness = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels)
|
||||
{
|
||||
y -= bounds.getY();
|
||||
|
||||
if (y < 0 || y >= bounds.getHeight())
|
||||
return;
|
||||
|
||||
needToCheckEmptiness = true;
|
||||
|
||||
if (numPixels <= 0)
|
||||
{
|
||||
table[lineStrideElements * y] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* tempLine = static_cast<int*> (alloca ((size_t) (numPixels * 2 + 4) * sizeof (int)));
|
||||
int destIndex = 0, lastLevel = 0;
|
||||
|
||||
while (--numPixels >= 0)
|
||||
{
|
||||
auto alpha = *mask;
|
||||
mask += maskStride;
|
||||
|
||||
if (alpha != lastLevel)
|
||||
{
|
||||
tempLine[++destIndex] = (x * scale);
|
||||
tempLine[++destIndex] = alpha;
|
||||
lastLevel = alpha;
|
||||
}
|
||||
|
||||
++x;
|
||||
}
|
||||
|
||||
if (lastLevel > 0)
|
||||
{
|
||||
tempLine[++destIndex] = (x * scale);
|
||||
tempLine[++destIndex] = 0;
|
||||
}
|
||||
|
||||
tempLine[0] = destIndex >> 1;
|
||||
|
||||
intersectWithEdgeTableLine (y, tempLine);
|
||||
}
|
||||
|
||||
bool EdgeTable::isEmpty() noexcept
|
||||
{
|
||||
if (needToCheckEmptiness)
|
||||
{
|
||||
needToCheckEmptiness = false;
|
||||
int* t = table;
|
||||
|
||||
for (int i = bounds.getHeight(); --i >= 0;)
|
||||
{
|
||||
if (t[0] > 1)
|
||||
return false;
|
||||
|
||||
t += lineStrideElements;
|
||||
}
|
||||
|
||||
bounds.setHeight (0);
|
||||
}
|
||||
|
||||
return bounds.getHeight() == 0;
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A table of horizontal scan-line segments - used for rasterising Paths.
|
||||
|
||||
@see Path, Graphics
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API EdgeTable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an edge table containing a path.
|
||||
|
||||
A table is created with a fixed vertical range, and only sections of the path
|
||||
which lie within this range will be added to the table.
|
||||
|
||||
@param clipLimits only the region of the path that lies within this area will be added
|
||||
@param pathToAdd the path to add to the table
|
||||
@param transform a transform to apply to the path being added
|
||||
*/
|
||||
EdgeTable (Rectangle<int> clipLimits,
|
||||
const Path& pathToAdd,
|
||||
const AffineTransform& transform);
|
||||
|
||||
/** Creates an edge table containing a rectangle. */
|
||||
explicit EdgeTable (Rectangle<int> rectangleToAdd);
|
||||
|
||||
/** Creates an edge table containing a rectangle. */
|
||||
explicit EdgeTable (Rectangle<float> rectangleToAdd);
|
||||
|
||||
/** Creates an edge table containing a rectangle list. */
|
||||
explicit EdgeTable (const RectangleList<int>& rectanglesToAdd);
|
||||
|
||||
/** Creates an edge table containing a rectangle list. */
|
||||
explicit EdgeTable (const RectangleList<float>& rectanglesToAdd);
|
||||
|
||||
/** Creates a copy of another edge table. */
|
||||
EdgeTable (const EdgeTable&);
|
||||
|
||||
/** Copies from another edge table. */
|
||||
EdgeTable& operator= (const EdgeTable&);
|
||||
|
||||
/** Destructor. */
|
||||
~EdgeTable();
|
||||
|
||||
//==============================================================================
|
||||
void clipToRectangle (Rectangle<int> r);
|
||||
void excludeRectangle (Rectangle<int> r);
|
||||
void clipToEdgeTable (const EdgeTable&);
|
||||
void clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels);
|
||||
bool isEmpty() noexcept;
|
||||
const Rectangle<int>& getMaximumBounds() const noexcept { return bounds; }
|
||||
void translate (float dx, int dy) noexcept;
|
||||
|
||||
/** Scales all the alpha-levels in the table by the given multiplier. */
|
||||
void multiplyLevels (float factor);
|
||||
|
||||
/** Reduces the amount of space the table has allocated.
|
||||
|
||||
This will shrink the table down to use as little memory as possible - useful for
|
||||
read-only tables that get stored and re-used for rendering.
|
||||
*/
|
||||
void optimiseTable();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Iterates the lines in the table, for rendering.
|
||||
|
||||
This function will iterate each line in the table, and call a user-defined class
|
||||
to render each pixel or continuous line of pixels that the table contains.
|
||||
|
||||
@param iterationCallback this templated class must contain the following methods:
|
||||
@code
|
||||
inline void setEdgeTableYPos (int y);
|
||||
inline void handleEdgeTablePixel (int x, int alphaLevel) const;
|
||||
inline void handleEdgeTablePixelFull (int x) const;
|
||||
inline void handleEdgeTableLine (int x, int width, int alphaLevel) const;
|
||||
inline void handleEdgeTableLineFull (int x, int width) const;
|
||||
@endcode
|
||||
(these don't necessarily have to be 'const', but it might help it go faster)
|
||||
*/
|
||||
template <class EdgeTableIterationCallback>
|
||||
void iterate (EdgeTableIterationCallback& iterationCallback) const noexcept
|
||||
{
|
||||
const int* lineStart = table;
|
||||
|
||||
for (int y = 0; y < bounds.getHeight(); ++y)
|
||||
{
|
||||
const int* line = lineStart;
|
||||
lineStart += lineStrideElements;
|
||||
int numPoints = line[0];
|
||||
|
||||
if (--numPoints > 0)
|
||||
{
|
||||
int x = *++line;
|
||||
jassert ((x / scale) >= bounds.getX() && (x / scale) < bounds.getRight());
|
||||
int levelAccumulator = 0;
|
||||
|
||||
iterationCallback.setEdgeTableYPos (bounds.getY() + y);
|
||||
|
||||
while (--numPoints >= 0)
|
||||
{
|
||||
const int level = *++line;
|
||||
jassert (isPositiveAndBelow (level, scale));
|
||||
const int endX = *++line;
|
||||
jassert (endX >= x);
|
||||
const int endOfRun = (endX / scale);
|
||||
|
||||
if (endOfRun == (x / scale))
|
||||
{
|
||||
// small segment within the same pixel, so just save it for the next
|
||||
// time round..
|
||||
levelAccumulator += (endX - x) * level;
|
||||
}
|
||||
else
|
||||
{
|
||||
// plot the fist pixel of this segment, including any accumulated
|
||||
// levels from smaller segments that haven't been drawn yet
|
||||
levelAccumulator += (0x100 - (x & 0xff)) * level;
|
||||
levelAccumulator /= scale;
|
||||
x /= scale;
|
||||
|
||||
if (levelAccumulator > 0)
|
||||
{
|
||||
if (levelAccumulator >= 255)
|
||||
iterationCallback.handleEdgeTablePixelFull (x);
|
||||
else
|
||||
iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
|
||||
}
|
||||
|
||||
// if there's a run of similar pixels, do it all in one go..
|
||||
if (level > 0)
|
||||
{
|
||||
jassert (endOfRun <= bounds.getRight());
|
||||
const int numPix = endOfRun - ++x;
|
||||
|
||||
if (numPix > 0)
|
||||
iterationCallback.handleEdgeTableLine (x, numPix, level);
|
||||
}
|
||||
|
||||
// save the bit at the end to be drawn next time round the loop.
|
||||
levelAccumulator = (endX & 0xff) * level;
|
||||
}
|
||||
|
||||
x = endX;
|
||||
}
|
||||
|
||||
levelAccumulator /= scale;
|
||||
|
||||
if (levelAccumulator > 0)
|
||||
{
|
||||
x /= scale;
|
||||
jassert (x >= bounds.getX() && x < bounds.getRight());
|
||||
|
||||
if (levelAccumulator >= 255)
|
||||
iterationCallback.handleEdgeTablePixelFull (x);
|
||||
else
|
||||
iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static constexpr auto defaultEdgesPerLine = 32;
|
||||
static constexpr auto scale = 256;
|
||||
|
||||
//==============================================================================
|
||||
// table line format: number of points; point0 x, point0 levelDelta, point1 x, point1 levelDelta, etc
|
||||
struct LineItem
|
||||
{
|
||||
int x, level;
|
||||
|
||||
bool operator< (const LineItem& other) const noexcept { return x < other.x; }
|
||||
};
|
||||
|
||||
HeapBlock<int> table;
|
||||
Rectangle<int> bounds;
|
||||
int maxEdgesPerLine, lineStrideElements;
|
||||
bool needToCheckEmptiness = true;
|
||||
|
||||
void allocate();
|
||||
void clearLineSizes() noexcept;
|
||||
void addEdgePoint (int x, int y, int winding);
|
||||
void addEdgePointPair (int x1, int x2, int y, int winding);
|
||||
void remapTableForNumEdges (int newNumEdgesPerLine);
|
||||
void remapWithExtraSpace (int numPointsNeeded);
|
||||
void intersectWithEdgeTableLine (int y, const int* otherLine);
|
||||
void clipEdgeTableLineToRange (int* line, int x1, int x2) noexcept;
|
||||
void sanitiseLevels (bool useNonZeroWinding) noexcept;
|
||||
static void copyEdgeTableData (int* dest, int destLineStride, const int* src, int srcLineStride, int numLines) noexcept;
|
||||
|
||||
JUCE_LEAK_DETECTOR (EdgeTable)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a line.
|
||||
|
||||
This class contains a bunch of useful methods for various geometric
|
||||
tasks.
|
||||
|
||||
The ValueType template parameter should be a primitive type - float or double
|
||||
are what it's designed for. Integer types will work in a basic way, but some methods
|
||||
that perform mathematical operations may not compile, or they may not produce
|
||||
sensible results.
|
||||
|
||||
@see Point, Rectangle, Path, Graphics::drawLine
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
template <typename ValueType>
|
||||
class Line
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a line, using (0, 0) as its start and end points. */
|
||||
Line() = default;
|
||||
|
||||
/** Creates a copy of another line. */
|
||||
Line (const Line&) = default;
|
||||
|
||||
/** Creates a line based on the coordinates of its start and end points. */
|
||||
Line (ValueType startX, ValueType startY, ValueType endX, ValueType endY) noexcept
|
||||
: start (startX, startY), end (endX, endY)
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a line from its start and end points. */
|
||||
Line (Point<ValueType> startPoint, Point<ValueType> endPoint) noexcept
|
||||
: start (startPoint), end (endPoint)
|
||||
{
|
||||
}
|
||||
|
||||
/** Copies a line from another one. */
|
||||
Line& operator= (const Line&) = default;
|
||||
|
||||
/** Destructor. */
|
||||
~Line() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the x coordinate of the line's start point. */
|
||||
inline ValueType getStartX() const noexcept { return start.x; }
|
||||
|
||||
/** Returns the y coordinate of the line's start point. */
|
||||
inline ValueType getStartY() const noexcept { return start.y; }
|
||||
|
||||
/** Returns the x coordinate of the line's end point. */
|
||||
inline ValueType getEndX() const noexcept { return end.x; }
|
||||
|
||||
/** Returns the y coordinate of the line's end point. */
|
||||
inline ValueType getEndY() const noexcept { return end.y; }
|
||||
|
||||
/** Returns the line's start point. */
|
||||
inline Point<ValueType> getStart() const noexcept { return start; }
|
||||
|
||||
/** Returns the line's end point. */
|
||||
inline Point<ValueType> getEnd() const noexcept { return end; }
|
||||
|
||||
/** Changes this line's start point */
|
||||
void setStart (ValueType newStartX, ValueType newStartY) noexcept { start.setXY (newStartX, newStartY); }
|
||||
|
||||
/** Changes this line's end point */
|
||||
void setEnd (ValueType newEndX, ValueType newEndY) noexcept { end.setXY (newEndX, newEndY); }
|
||||
|
||||
/** Changes this line's start point */
|
||||
void setStart (const Point<ValueType> newStart) noexcept { start = newStart; }
|
||||
|
||||
/** Changes this line's end point */
|
||||
void setEnd (const Point<ValueType> newEnd) noexcept { end = newEnd; }
|
||||
|
||||
/** Returns a line that is the same as this one, but with the start and end reversed, */
|
||||
Line reversed() const noexcept { return { end, start }; }
|
||||
|
||||
/** Applies an affine transform to the line's start and end points. */
|
||||
void applyTransform (const AffineTransform& transform) noexcept
|
||||
{
|
||||
start.applyTransform (transform);
|
||||
end.applyTransform (transform);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the length of the line. */
|
||||
ValueType getLength() const noexcept { return start.getDistanceFrom (end); }
|
||||
|
||||
/** Returns the length of the line. */
|
||||
ValueType getLengthSquared() const noexcept { return start.getDistanceSquaredFrom (end); }
|
||||
|
||||
/** Returns true if the line's start and end x coordinates are the same. */
|
||||
bool isVertical() const noexcept { return start.x == end.x; }
|
||||
|
||||
/** Returns true if the line's start and end y coordinates are the same. */
|
||||
bool isHorizontal() const noexcept { return start.y == end.y; }
|
||||
|
||||
/** Returns the line's angle.
|
||||
|
||||
This value is the number of radians clockwise from the 12 o'clock direction,
|
||||
where the line's start point is considered to be at the centre.
|
||||
*/
|
||||
typename Point<ValueType>::FloatType getAngle() const noexcept { return start.getAngleToPoint (end); }
|
||||
|
||||
/** Creates a line from a start point, length and angle.
|
||||
|
||||
This angle is the number of radians clockwise from the 12 o'clock direction,
|
||||
where the line's start point is considered to be at the centre.
|
||||
*/
|
||||
static Line fromStartAndAngle (Point<ValueType> startPoint, ValueType length, ValueType angle) noexcept
|
||||
{
|
||||
return { startPoint, startPoint.getPointOnCircumference (length, angle) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Casts this line to float coordinates. */
|
||||
Line<float> toFloat() const noexcept { return { start.toFloat(), end.toFloat() }; }
|
||||
|
||||
/** Casts this line to double coordinates. */
|
||||
Line<double> toDouble() const noexcept { return { start.toDouble(), end.toDouble() }; }
|
||||
|
||||
//==============================================================================
|
||||
/** Compares two lines. */
|
||||
bool operator== (Line other) const noexcept { return start == other.start && end == other.end; }
|
||||
|
||||
/** Compares two lines. */
|
||||
bool operator!= (Line other) const noexcept { return start != other.start || end != other.end; }
|
||||
|
||||
//==============================================================================
|
||||
/** Finds the intersection between two lines.
|
||||
|
||||
@param line the line to intersect with
|
||||
@returns the point at which the lines intersect, even if this lies beyond the end of the lines
|
||||
*/
|
||||
Point<ValueType> getIntersection (Line line) const noexcept
|
||||
{
|
||||
Point<ValueType> p;
|
||||
findIntersection (start, end, line.start, line.end, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Finds the intersection between two lines.
|
||||
|
||||
@param line the other line
|
||||
@param intersection the position of the point where the lines meet (or
|
||||
where they would meet if they were infinitely long)
|
||||
the intersection (if the lines intersect). If the lines
|
||||
are parallel, this will just be set to the position
|
||||
of one of the line's endpoints.
|
||||
@returns true if the line segments intersect; false if they don't. Even if they
|
||||
don't intersect, the intersection coordinates returned will still
|
||||
be valid
|
||||
*/
|
||||
bool intersects (Line line, Point<ValueType>& intersection) const noexcept
|
||||
{
|
||||
return findIntersection (start, end, line.start, line.end, intersection);
|
||||
}
|
||||
|
||||
/** Returns true if this line intersects another. */
|
||||
bool intersects (Line other) const noexcept
|
||||
{
|
||||
Point<ValueType> ignored;
|
||||
return findIntersection (start, end, other.start, other.end, ignored);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the location of the point which is a given distance along this line.
|
||||
|
||||
@param distanceFromStart the distance to move along the line from its
|
||||
start point. This value can be negative or longer
|
||||
than the line itself
|
||||
@see getPointAlongLineProportionally
|
||||
*/
|
||||
Point<ValueType> getPointAlongLine (ValueType distanceFromStart) const noexcept
|
||||
{
|
||||
const auto length = getLength();
|
||||
return approximatelyEqual (length, (ValueType) 0) ? start : start + (end - start) * (distanceFromStart / length);
|
||||
}
|
||||
|
||||
/** Returns a point which is a certain distance along and to the side of this line.
|
||||
|
||||
This effectively moves a given distance along the line, then another distance
|
||||
perpendicularly to this, and returns the resulting position.
|
||||
|
||||
@param distanceFromStart the distance to move along the line from its
|
||||
start point. This value can be negative or longer
|
||||
than the line itself
|
||||
@param perpendicularDistance how far to move sideways from the line. If you're
|
||||
looking along the line from its start towards its
|
||||
end, then a positive value here will move to the
|
||||
right, negative value move to the left.
|
||||
*/
|
||||
Point<ValueType> getPointAlongLine (ValueType distanceFromStart,
|
||||
ValueType perpendicularDistance) const noexcept
|
||||
{
|
||||
auto delta = end - start;
|
||||
auto length = juce_hypot ((double) delta.x,
|
||||
(double) delta.y);
|
||||
if (length <= 0)
|
||||
return start;
|
||||
|
||||
return { start.x + static_cast<ValueType> ((delta.x * distanceFromStart - delta.y * perpendicularDistance) / length),
|
||||
start.y + static_cast<ValueType> ((delta.y * distanceFromStart + delta.x * perpendicularDistance) / length) };
|
||||
}
|
||||
|
||||
/** Returns the location of the point which is a given distance along this line
|
||||
proportional to the line's length.
|
||||
|
||||
@param proportionOfLength the distance to move along the line from its
|
||||
start point, in multiples of the line's length.
|
||||
So a value of 0.0 will return the line's start point
|
||||
and a value of 1.0 will return its end point. (This value
|
||||
can be negative or greater than 1.0).
|
||||
@see getPointAlongLine
|
||||
*/
|
||||
Point<ValueType> getPointAlongLineProportionally (typename Point<ValueType>::FloatType proportionOfLength) const noexcept
|
||||
{
|
||||
return start + (end - start) * proportionOfLength;
|
||||
}
|
||||
|
||||
/** Returns the smallest distance between this line segment and a given point.
|
||||
|
||||
So if the point is close to the line, this will return the perpendicular
|
||||
distance from the line; if the point is a long way beyond one of the line's
|
||||
end-point's, it'll return the straight-line distance to the nearest end-point.
|
||||
|
||||
pointOnLine receives the position of the point that is found.
|
||||
|
||||
@returns the point's distance from the line
|
||||
@see getPositionAlongLineOfNearestPoint
|
||||
*/
|
||||
ValueType getDistanceFromPoint (Point<ValueType> targetPoint,
|
||||
Point<ValueType>& pointOnLine) const noexcept
|
||||
{
|
||||
auto delta = end - start;
|
||||
auto length = delta.x * delta.x + delta.y * delta.y;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
auto prop = ((targetPoint.x - start.x) * delta.x
|
||||
+ (targetPoint.y - start.y) * delta.y) / (double) length;
|
||||
|
||||
if (prop >= 0 && prop <= 1.0)
|
||||
{
|
||||
pointOnLine = start + delta * prop;
|
||||
return targetPoint.getDistanceFrom (pointOnLine);
|
||||
}
|
||||
}
|
||||
|
||||
auto fromStart = targetPoint.getDistanceFrom (start);
|
||||
auto fromEnd = targetPoint.getDistanceFrom (end);
|
||||
|
||||
if (fromStart < fromEnd)
|
||||
{
|
||||
pointOnLine = start;
|
||||
return fromStart;
|
||||
}
|
||||
|
||||
pointOnLine = end;
|
||||
return fromEnd;
|
||||
}
|
||||
|
||||
/** Finds the point on this line which is nearest to a given point, and
|
||||
returns its position as a proportional position along the line.
|
||||
|
||||
@returns a value 0 to 1.0 which is the distance along this line from the
|
||||
line's start to the point which is nearest to the point passed-in. To
|
||||
turn this number into a position, use getPointAlongLineProportionally().
|
||||
@see getDistanceFromPoint, getPointAlongLineProportionally
|
||||
*/
|
||||
ValueType findNearestProportionalPositionTo (Point<ValueType> point) const noexcept
|
||||
{
|
||||
auto delta = end - start;
|
||||
auto length = delta.x * delta.x + delta.y * delta.y;
|
||||
|
||||
return length <= 0 ? 0
|
||||
: jlimit (ValueType(), static_cast<ValueType> (1),
|
||||
static_cast<ValueType> ((((point.x - start.x) * delta.x
|
||||
+ (point.y - start.y) * delta.y) / length)));
|
||||
}
|
||||
|
||||
/** Finds the point on this line which is nearest to a given point.
|
||||
@see getDistanceFromPoint, findNearestProportionalPositionTo
|
||||
*/
|
||||
Point<ValueType> findNearestPointTo (Point<ValueType> point) const noexcept
|
||||
{
|
||||
return getPointAlongLineProportionally (findNearestProportionalPositionTo (point));
|
||||
}
|
||||
|
||||
/** Returns true if the given point lies above this line.
|
||||
|
||||
The return value is true if the point's y coordinate is less than the y
|
||||
coordinate of this line at the given x (assuming the line extends infinitely
|
||||
in both directions).
|
||||
*/
|
||||
bool isPointAbove (Point<ValueType> point) const noexcept
|
||||
{
|
||||
return start.x != end.x
|
||||
&& point.y < ((end.y - start.y) * (point.x - start.x)) / (end.x - start.x) + start.y;
|
||||
}
|
||||
|
||||
/** Returns a lengthened copy of this line.
|
||||
|
||||
This will extend the line by a certain amount by moving the start away from the end
|
||||
(leaving the end-point the same), and return the new line.
|
||||
*/
|
||||
Line withLengthenedStart (ValueType distanceToLengthenBy) const noexcept
|
||||
{
|
||||
return withShortenedStart (-distanceToLengthenBy);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a shortened copy of this line.
|
||||
|
||||
This will chop off part of the start of this line by a certain amount, (leaving the
|
||||
end-point the same), and return the new line.
|
||||
*/
|
||||
Line withShortenedStart (ValueType distanceToShortenBy) const noexcept
|
||||
{
|
||||
return { getPointAlongLine (jmin (distanceToShortenBy, getLength())), end };
|
||||
}
|
||||
|
||||
/** Returns a lengthened copy of this line.
|
||||
|
||||
This will extend the line by a certain amount by moving the end away from the start
|
||||
(leaving the start-point the same), and return the new line.
|
||||
*/
|
||||
Line withLengthenedEnd (ValueType distanceToLengthenBy) const noexcept
|
||||
{
|
||||
return withShortenedEnd (-distanceToLengthenBy);
|
||||
}
|
||||
|
||||
/** Returns a shortened copy of this line.
|
||||
|
||||
This will chop off part of the end of this line by a certain amount, (leaving the
|
||||
start-point the same), and return the new line.
|
||||
*/
|
||||
Line withShortenedEnd (ValueType distanceToShortenBy) const noexcept
|
||||
{
|
||||
auto length = getLength();
|
||||
return { start, getPointAlongLine (length - jmin (distanceToShortenBy, length)) };
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Point<ValueType> start, end;
|
||||
|
||||
static bool isZeroToOne (ValueType v) noexcept { return v >= 0 && v <= static_cast<ValueType> (1); }
|
||||
|
||||
static bool findIntersection (const Point<ValueType> p1, const Point<ValueType> p2,
|
||||
const Point<ValueType> p3, const Point<ValueType> p4,
|
||||
Point<ValueType>& intersection) noexcept
|
||||
{
|
||||
if (p2 == p3)
|
||||
{
|
||||
intersection = p2;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto d1 = p2 - p1;
|
||||
auto d2 = p4 - p3;
|
||||
auto divisor = d1.x * d2.y - d2.x * d1.y;
|
||||
|
||||
const auto zero = ValueType{};
|
||||
|
||||
if (approximatelyEqual (divisor, zero))
|
||||
{
|
||||
if (! (d1.isOrigin() || d2.isOrigin()))
|
||||
{
|
||||
if (approximatelyEqual (d1.y, zero) && ! approximatelyEqual (d2.y, zero))
|
||||
{
|
||||
auto along = (p1.y - p3.y) / d2.y;
|
||||
intersection = p1.withX (p3.x + along * d2.x);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
|
||||
if (approximatelyEqual (d2.y, zero) && ! approximatelyEqual (d1.y, zero))
|
||||
{
|
||||
auto along = (p3.y - p1.y) / d1.y;
|
||||
intersection = p3.withX (p1.x + along * d1.x);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
|
||||
if (approximatelyEqual (d1.x, zero) && ! approximatelyEqual (d2.x, zero))
|
||||
{
|
||||
auto along = (p1.x - p3.x) / d2.x;
|
||||
intersection = p1.withY (p3.y + along * d2.y);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
|
||||
if (approximatelyEqual (d2.x, zero) && ! approximatelyEqual (d1.x, zero))
|
||||
{
|
||||
auto along = (p3.x - p1.x) / d1.x;
|
||||
intersection = p3.withY (p1.y + along * d1.y);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
}
|
||||
|
||||
intersection = (p2 + p3) / static_cast<ValueType> (2);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto along1 = ((p1.y - p3.y) * d2.x - (p1.x - p3.x) * d2.y) / divisor;
|
||||
intersection = p1 + d1 * along1;
|
||||
|
||||
if (! isZeroToOne (along1))
|
||||
return false;
|
||||
|
||||
auto along2 = ((p1.y - p3.y) * d1.x - (p1.x - p3.x) * d1.y) / divisor;
|
||||
return isZeroToOne (along2);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a parallelogram that is defined by 3 points.
|
||||
@see Rectangle, Point, Line
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
template <typename ValueType>
|
||||
class Parallelogram
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a parallelogram with zero size at the origin.
|
||||
*/
|
||||
Parallelogram() = default;
|
||||
|
||||
/** Creates a copy of another parallelogram. */
|
||||
Parallelogram (const Parallelogram&) = default;
|
||||
|
||||
/** Creates a parallelogram based on 3 points. */
|
||||
Parallelogram (Point<ValueType> topLeftPosition,
|
||||
Point<ValueType> topRightPosition,
|
||||
Point<ValueType> bottomLeftPosition) noexcept
|
||||
: topLeft (topLeftPosition), topRight (topRightPosition), bottomLeft (bottomLeftPosition)
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a parallelogram from a rectangle. */
|
||||
Parallelogram (Rectangle<ValueType> rectangle) noexcept
|
||||
: topLeft (rectangle.getTopLeft()),
|
||||
topRight (rectangle.getTopRight()),
|
||||
bottomLeft (rectangle.getBottomLeft())
|
||||
{
|
||||
}
|
||||
|
||||
Parallelogram& operator= (const Parallelogram&) = default;
|
||||
|
||||
/** Destructor. */
|
||||
~Parallelogram() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the parallelogram has a width or height of more than zero. */
|
||||
bool isEmpty() const noexcept { return topLeft != topRight || topLeft != bottomLeft; }
|
||||
|
||||
/** Returns true if the parallelogram's coordinates are all finite numbers, i.e. not NaN or infinity. */
|
||||
inline bool isFinite() const noexcept { return topLeft.isFinite() && topRight.isFinite() && bottomLeft.isFinite(); }
|
||||
|
||||
/** Returns the width of the parallelogram (i.e. the straight-line distance between the top-left and top-right. */
|
||||
inline ValueType getWidth() const noexcept { return Line<ValueType> (topLeft, topRight).getLength(); }
|
||||
|
||||
/** Returns the height of the parallelogram (i.e. the straight-line distance between the top-left and bottom-left. */
|
||||
inline ValueType getHeight() const noexcept { return Line<ValueType> (topLeft, bottomLeft).getLength(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the parallelogram's top-left position as a Point. */
|
||||
Point<ValueType> getTopLeft() const noexcept { return topLeft; }
|
||||
|
||||
/** Returns the parallelogram's top-right position as a Point. */
|
||||
Point<ValueType> getTopRight() const noexcept { return topRight; }
|
||||
|
||||
/** Returns the parallelogram's bottom-left position as a Point. */
|
||||
Point<ValueType> getBottomLeft() const noexcept { return bottomLeft; }
|
||||
|
||||
/** Returns the parallelogram's bottom-right position as a Point. */
|
||||
Point<ValueType> getBottomRight() const noexcept { return topRight + (bottomLeft - topLeft); }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the two parallelograms are identical. */
|
||||
bool operator== (const Parallelogram& other) const noexcept { return topLeft == other.topLeft && topRight == other.topRight && bottomLeft == other.bottomLeft; }
|
||||
|
||||
/** Returns true if the two parallelograms are not identical. */
|
||||
bool operator!= (const Parallelogram& other) const noexcept { return ! operator== (other); }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a parallelogram which is the same as this one moved by a given amount. */
|
||||
Parallelogram operator+ (Point<ValueType> deltaPosition) const noexcept
|
||||
{
|
||||
auto p = *this;
|
||||
p += deltaPosition;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Moves this parallelogram by a given amount. */
|
||||
Parallelogram& operator+= (Point<ValueType> deltaPosition) noexcept
|
||||
{
|
||||
topLeft += deltaPosition;
|
||||
topRight += deltaPosition;
|
||||
bottomLeft += deltaPosition;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns a parallelogram which is the same as this one moved by a given amount. */
|
||||
Parallelogram operator- (Point<ValueType> deltaPosition) const noexcept
|
||||
{
|
||||
return operator+ (-deltaPosition);
|
||||
}
|
||||
|
||||
/** Moves this parallelogram by a given amount. */
|
||||
Parallelogram& operator-= (Point<ValueType> deltaPosition) noexcept
|
||||
{
|
||||
return operator-= (-deltaPosition);
|
||||
}
|
||||
|
||||
/** Returns a parallelogram that has been scaled by the given amount, centred around the origin. */
|
||||
template <typename PointOrScalarType>
|
||||
Parallelogram operator* (PointOrScalarType scaleFactor) const noexcept
|
||||
{
|
||||
auto p = *this;
|
||||
p *= scaleFactor;
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Scales this parallelogram by the given amount, centred around the origin. */
|
||||
template <typename PointOrScalarType>
|
||||
Parallelogram operator*= (PointOrScalarType scaleFactor) noexcept
|
||||
{
|
||||
topLeft *= scaleFactor;
|
||||
topRight *= scaleFactor;
|
||||
bottomLeft *= scaleFactor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a point within this parallelogram, specified as proportional coordinates.
|
||||
The relative X and Y values should be between 0 and 1, where 0 is the left or
|
||||
top of this parallelogram, and 1 is the right or bottom. (Out-of-bounds values
|
||||
will return a point outside the parallelogram).
|
||||
*/
|
||||
Point<ValueType> getRelativePoint (Point<ValueType> relativePosition) const noexcept
|
||||
{
|
||||
return topLeft
|
||||
+ (topRight - topLeft) * relativePosition.x
|
||||
+ (bottomLeft - topLeft) * relativePosition.y;
|
||||
}
|
||||
|
||||
/** Returns a transformed version of the parallelogram. */
|
||||
Parallelogram transformedBy (const AffineTransform& transform) const noexcept
|
||||
{
|
||||
auto p = *this;
|
||||
transform.transformPoints (p.topLeft.x, p.topLeft.y,
|
||||
p.topRight.x, p.topRight.y,
|
||||
p.bottomLeft.x, p.bottomLeft.y);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Returns the smallest rectangle that encloses this parallelogram. */
|
||||
Rectangle<ValueType> getBoundingBox() const noexcept
|
||||
{
|
||||
const Point<ValueType> points[] = { topLeft, topRight, bottomLeft, getBottomRight() };
|
||||
return Rectangle<ValueType>::findAreaContainingPoints (points, 4);
|
||||
}
|
||||
|
||||
Point<ValueType> topLeft, topRight, bottomLeft;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,839 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A path is a sequence of lines and curves that may either form a closed shape
|
||||
or be open-ended.
|
||||
|
||||
To use a path, you can create an empty one, then add lines and curves to it
|
||||
to create shapes, then it can be rendered by a Graphics context or used
|
||||
for geometric operations.
|
||||
|
||||
e.g. @code
|
||||
Path myPath;
|
||||
|
||||
myPath.startNewSubPath (10.0f, 10.0f); // move the current position to (10, 10)
|
||||
myPath.lineTo (100.0f, 200.0f); // draw a line from here to (100, 200)
|
||||
myPath.quadraticTo (0.0f, 150.0f, 5.0f, 50.0f); // draw a curve that ends at (5, 50)
|
||||
myPath.closeSubPath(); // close the subpath with a line back to (10, 10)
|
||||
|
||||
// add an ellipse as well, which will form a second sub-path within the path..
|
||||
myPath.addEllipse (50.0f, 50.0f, 40.0f, 30.0f);
|
||||
|
||||
// double the width of the whole thing..
|
||||
myPath.applyTransform (AffineTransform::scale (2.0f, 1.0f));
|
||||
|
||||
// and draw it to a graphics context with a 5-pixel thick outline.
|
||||
g.strokePath (myPath, PathStrokeType (5.0f));
|
||||
|
||||
@endcode
|
||||
|
||||
A path object can actually contain multiple sub-paths, which may themselves
|
||||
be open or closed.
|
||||
|
||||
@see PathFlatteningIterator, PathStrokeType, Graphics
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API Path final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty path. */
|
||||
Path();
|
||||
|
||||
/** Creates a copy of another path. */
|
||||
Path (const Path&);
|
||||
|
||||
/** Destructor. */
|
||||
~Path();
|
||||
|
||||
/** Copies this path from another one. */
|
||||
Path& operator= (const Path&);
|
||||
|
||||
/** Move constructor */
|
||||
Path (Path&&) noexcept;
|
||||
|
||||
/** Move assignment operator */
|
||||
Path& operator= (Path&&) noexcept;
|
||||
|
||||
bool operator== (const Path&) const noexcept;
|
||||
bool operator!= (const Path&) const noexcept;
|
||||
|
||||
static const float defaultToleranceForTesting;
|
||||
static const float defaultToleranceForMeasurement;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the path doesn't contain any lines or curves. */
|
||||
bool isEmpty() const noexcept;
|
||||
|
||||
/** Returns the smallest rectangle that contains all points within the path. */
|
||||
Rectangle<float> getBounds() const noexcept;
|
||||
|
||||
/** Returns the smallest rectangle that contains all points within the path
|
||||
after it's been transformed with the given transform matrix.
|
||||
*/
|
||||
Rectangle<float> getBoundsTransformed (const AffineTransform& transform) const noexcept;
|
||||
|
||||
/** Checks whether a point lies within the path.
|
||||
|
||||
This is only relevant for closed paths (see closeSubPath()), and
|
||||
may produce false results if used on a path which has open sub-paths.
|
||||
|
||||
The path's winding rule is taken into account by this method.
|
||||
|
||||
The tolerance parameter is the maximum error allowed when flattening the path,
|
||||
so this method could return a false positive when your point is up to this distance
|
||||
outside the path's boundary.
|
||||
|
||||
@see closeSubPath, setUsingNonZeroWinding
|
||||
*/
|
||||
bool contains (float x, float y,
|
||||
float tolerance = defaultToleranceForTesting) const;
|
||||
|
||||
/** Checks whether a point lies within the path.
|
||||
|
||||
This is only relevant for closed paths (see closeSubPath()), and
|
||||
may produce false results if used on a path which has open sub-paths.
|
||||
|
||||
The path's winding rule is taken into account by this method.
|
||||
|
||||
The tolerance parameter is the maximum error allowed when flattening the path,
|
||||
so this method could return a false positive when your point is up to this distance
|
||||
outside the path's boundary.
|
||||
|
||||
@see closeSubPath, setUsingNonZeroWinding
|
||||
*/
|
||||
bool contains (Point<float> point,
|
||||
float tolerance = defaultToleranceForTesting) const;
|
||||
|
||||
/** Checks whether a line crosses the path.
|
||||
|
||||
This will return positive if the line crosses any of the paths constituent
|
||||
lines or curves. It doesn't take into account whether the line is inside
|
||||
or outside the path, or whether the path is open or closed.
|
||||
|
||||
The tolerance parameter is the maximum error allowed when flattening the path,
|
||||
so this method could return a false positive when your point is up to this distance
|
||||
outside the path's boundary.
|
||||
*/
|
||||
bool intersectsLine (Line<float> line,
|
||||
float tolerance = defaultToleranceForTesting) const;
|
||||
|
||||
/** Cuts off parts of a line to keep the parts that are either inside or
|
||||
outside this path.
|
||||
|
||||
Note that this isn't smart enough to cope with situations where the
|
||||
line would need to be cut into multiple pieces to correctly clip against
|
||||
a re-entrant shape.
|
||||
|
||||
@param line the line to clip
|
||||
@param keepSectionOutsidePath if true, it's the section outside the path
|
||||
that will be kept; if false its the section inside
|
||||
the path
|
||||
*/
|
||||
Line<float> getClippedLine (Line<float> line, bool keepSectionOutsidePath) const;
|
||||
|
||||
/** Returns the length of the path.
|
||||
@see getPointAlongPath
|
||||
*/
|
||||
float getLength (const AffineTransform& transform = AffineTransform(),
|
||||
float tolerance = defaultToleranceForMeasurement) const;
|
||||
|
||||
/** Returns a point that is the specified distance along the path.
|
||||
If the distance is greater than the total length of the path, this will return the
|
||||
end point.
|
||||
@see getLength
|
||||
*/
|
||||
Point<float> getPointAlongPath (float distanceFromStart,
|
||||
const AffineTransform& transform = AffineTransform(),
|
||||
float tolerance = defaultToleranceForMeasurement) const;
|
||||
|
||||
/** Finds the point along the path which is nearest to a given position.
|
||||
This sets pointOnPath to the nearest point, and returns the distance of this point from the start
|
||||
of the path.
|
||||
*/
|
||||
float getNearestPoint (Point<float> targetPoint,
|
||||
Point<float>& pointOnPath,
|
||||
const AffineTransform& transform = AffineTransform(),
|
||||
float tolerance = defaultToleranceForMeasurement) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all lines and curves, resetting the path completely. */
|
||||
void clear() noexcept;
|
||||
|
||||
/** Begins a new subpath with a given starting position.
|
||||
|
||||
This will move the path's current position to the coordinates passed in and
|
||||
make it ready to draw lines or curves starting from this position.
|
||||
|
||||
After adding whatever lines and curves are needed, you can either
|
||||
close the current sub-path using closeSubPath() or call startNewSubPath()
|
||||
to move to a new sub-path, leaving the old one open-ended.
|
||||
|
||||
@see lineTo, quadraticTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void startNewSubPath (float startX, float startY);
|
||||
|
||||
/** Begins a new subpath with a given starting position.
|
||||
|
||||
This will move the path's current position to the coordinates passed in and
|
||||
make it ready to draw lines or curves starting from this position.
|
||||
|
||||
After adding whatever lines and curves are needed, you can either
|
||||
close the current sub-path using closeSubPath() or call startNewSubPath()
|
||||
to move to a new sub-path, leaving the old one open-ended.
|
||||
|
||||
@see lineTo, quadraticTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void startNewSubPath (Point<float> start);
|
||||
|
||||
/** Closes a the current sub-path with a line back to its start-point.
|
||||
|
||||
When creating a closed shape such as a triangle, don't use 3 lineTo()
|
||||
calls - instead use two lineTo() calls, followed by a closeSubPath()
|
||||
to join the final point back to the start.
|
||||
|
||||
This ensures that closes shapes are recognised as such, and this is
|
||||
important for tasks like drawing strokes, which needs to know whether to
|
||||
draw end-caps or not.
|
||||
|
||||
@see startNewSubPath, lineTo, quadraticTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void closeSubPath();
|
||||
|
||||
/** Adds a line from the shape's last position to a new end-point.
|
||||
|
||||
This will connect the end-point of the last line or curve that was added
|
||||
to a new point, using a straight line.
|
||||
|
||||
See the class description for an example of how to add lines and curves to a path.
|
||||
|
||||
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void lineTo (float endX, float endY);
|
||||
|
||||
/** Adds a line from the shape's last position to a new end-point.
|
||||
|
||||
This will connect the end-point of the last line or curve that was added
|
||||
to a new point, using a straight line.
|
||||
|
||||
See the class description for an example of how to add lines and curves to a path.
|
||||
|
||||
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void lineTo (Point<float> end);
|
||||
|
||||
/** Adds a quadratic bezier curve from the shape's last position to a new position.
|
||||
|
||||
This will connect the end-point of the last line or curve that was added
|
||||
to a new point, using a quadratic spline with one control-point.
|
||||
|
||||
See the class description for an example of how to add lines and curves to a path.
|
||||
|
||||
@see startNewSubPath, lineTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void quadraticTo (float controlPointX,
|
||||
float controlPointY,
|
||||
float endPointX,
|
||||
float endPointY);
|
||||
|
||||
/** Adds a quadratic bezier curve from the shape's last position to a new position.
|
||||
|
||||
This will connect the end-point of the last line or curve that was added
|
||||
to a new point, using a quadratic spline with one control-point.
|
||||
|
||||
See the class description for an example of how to add lines and curves to a path.
|
||||
|
||||
@see startNewSubPath, lineTo, cubicTo, closeSubPath
|
||||
*/
|
||||
void quadraticTo (Point<float> controlPoint,
|
||||
Point<float> endPoint);
|
||||
|
||||
/** Adds a cubic bezier curve from the shape's last position to a new position.
|
||||
|
||||
This will connect the end-point of the last line or curve that was added
|
||||
to a new point, using a cubic spline with two control-points.
|
||||
|
||||
See the class description for an example of how to add lines and curves to a path.
|
||||
|
||||
@see startNewSubPath, lineTo, quadraticTo, closeSubPath
|
||||
*/
|
||||
void cubicTo (float controlPoint1X,
|
||||
float controlPoint1Y,
|
||||
float controlPoint2X,
|
||||
float controlPoint2Y,
|
||||
float endPointX,
|
||||
float endPointY);
|
||||
|
||||
/** Adds a cubic bezier curve from the shape's last position to a new position.
|
||||
|
||||
This will connect the end-point of the last line or curve that was added
|
||||
to a new point, using a cubic spline with two control-points.
|
||||
|
||||
See the class description for an example of how to add lines and curves to a path.
|
||||
|
||||
@see startNewSubPath, lineTo, quadraticTo, closeSubPath
|
||||
*/
|
||||
void cubicTo (Point<float> controlPoint1,
|
||||
Point<float> controlPoint2,
|
||||
Point<float> endPoint);
|
||||
|
||||
/** Returns the last point that was added to the path by one of the drawing methods.
|
||||
*/
|
||||
Point<float> getCurrentPosition() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a rectangle to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRoundedRectangle, addTriangle
|
||||
*/
|
||||
void addRectangle (float x, float y, float width, float height);
|
||||
|
||||
/** Adds a rectangle to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRoundedRectangle, addTriangle
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void addRectangle (Rectangle<ValueType> rectangle)
|
||||
{
|
||||
addRectangle (static_cast<float> (rectangle.getX()), static_cast<float> (rectangle.getY()),
|
||||
static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight()));
|
||||
}
|
||||
|
||||
/** Adds a rectangle with rounded corners to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRectangle, addTriangle
|
||||
*/
|
||||
void addRoundedRectangle (float x, float y, float width, float height,
|
||||
float cornerSize);
|
||||
|
||||
/** Adds a rectangle with rounded corners to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRectangle, addTriangle
|
||||
*/
|
||||
void addRoundedRectangle (float x, float y, float width, float height,
|
||||
float cornerSizeX,
|
||||
float cornerSizeY);
|
||||
|
||||
/** Adds a rectangle with rounded corners to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRectangle, addTriangle
|
||||
*/
|
||||
void addRoundedRectangle (float x, float y, float width, float height,
|
||||
float cornerSizeX, float cornerSizeY,
|
||||
bool curveTopLeft, bool curveTopRight,
|
||||
bool curveBottomLeft, bool curveBottomRight);
|
||||
|
||||
/** Adds a rectangle with rounded corners to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRectangle, addTriangle
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void addRoundedRectangle (Rectangle<ValueType> rectangle, float cornerSizeX, float cornerSizeY)
|
||||
{
|
||||
addRoundedRectangle (static_cast<float> (rectangle.getX()), static_cast<float> (rectangle.getY()),
|
||||
static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight()),
|
||||
cornerSizeX, cornerSizeY);
|
||||
}
|
||||
|
||||
/** Adds a rectangle with rounded corners to the path.
|
||||
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addRectangle, addTriangle
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void addRoundedRectangle (Rectangle<ValueType> rectangle, float cornerSize)
|
||||
{
|
||||
addRoundedRectangle (rectangle, cornerSize, cornerSize);
|
||||
}
|
||||
|
||||
/** Adds a triangle to the path.
|
||||
|
||||
The triangle is added as a new closed sub-path. (Any currently open paths will be left open).
|
||||
|
||||
Note that whether the vertices are specified in clockwise or anticlockwise
|
||||
order will affect how the triangle is filled when it overlaps other
|
||||
shapes (the winding order setting will affect this of course).
|
||||
*/
|
||||
void addTriangle (float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3);
|
||||
|
||||
/** Adds a triangle to the path.
|
||||
|
||||
The triangle is added as a new closed sub-path. (Any currently open paths will be left open).
|
||||
|
||||
Note that whether the vertices are specified in clockwise or anticlockwise
|
||||
order will affect how the triangle is filled when it overlaps other
|
||||
shapes (the winding order setting will affect this of course).
|
||||
*/
|
||||
void addTriangle (Point<float> point1,
|
||||
Point<float> point2,
|
||||
Point<float> point3);
|
||||
|
||||
/** Adds a quadrilateral to the path.
|
||||
|
||||
The quad is added as a new closed sub-path. (Any currently open paths will be left open).
|
||||
|
||||
Note that whether the vertices are specified in clockwise or anticlockwise
|
||||
order will affect how the quad is filled when it overlaps other
|
||||
shapes (the winding order setting will affect this of course).
|
||||
*/
|
||||
void addQuadrilateral (float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3,
|
||||
float x4, float y4);
|
||||
|
||||
/** Adds an ellipse to the path.
|
||||
The shape is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addArc
|
||||
*/
|
||||
void addEllipse (float x, float y, float width, float height);
|
||||
|
||||
/** Adds an ellipse to the path.
|
||||
The shape is added as a new sub-path. (Any currently open paths will be left open).
|
||||
@see addArc
|
||||
*/
|
||||
void addEllipse (Rectangle<float> area);
|
||||
|
||||
/** Adds an elliptical arc to the current path.
|
||||
|
||||
Note that when specifying the start and end angles, the curve will be drawn either clockwise
|
||||
or anti-clockwise according to whether the end angle is greater than the start. This means
|
||||
that sometimes you may need to use values greater than 2*Pi for the end angle.
|
||||
|
||||
@param x the left-hand edge of the rectangle in which the elliptical outline fits
|
||||
@param y the top edge of the rectangle in which the elliptical outline fits
|
||||
@param width the width of the rectangle in which the elliptical outline fits
|
||||
@param height the height of the rectangle in which the elliptical outline fits
|
||||
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
|
||||
top-centre of the ellipse)
|
||||
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
|
||||
top-centre of the ellipse). This angle can be greater than 2*Pi, so for example to
|
||||
draw a curve clockwise from the 9 o'clock position to the 3 o'clock position via
|
||||
12 o'clock, you'd use 1.5*Pi and 2.5*Pi as the start and finish points.
|
||||
@param startAsNewSubPath if true, the arc will begin a new subpath from its starting point; if false,
|
||||
it will be added to the current sub-path, continuing from the current position
|
||||
|
||||
@see addCentredArc, arcTo, addPieSegment, addEllipse
|
||||
*/
|
||||
void addArc (float x, float y, float width, float height,
|
||||
float fromRadians,
|
||||
float toRadians,
|
||||
bool startAsNewSubPath = false);
|
||||
|
||||
/** Adds an arc which is centred at a given point, and can have a rotation specified.
|
||||
|
||||
Note that when specifying the start and end angles, the curve will be drawn either clockwise
|
||||
or anti-clockwise according to whether the end angle is greater than the start. This means
|
||||
that sometimes you may need to use values greater than 2*Pi for the end angle.
|
||||
|
||||
@param centreX the centre x of the ellipse
|
||||
@param centreY the centre y of the ellipse
|
||||
@param radiusX the horizontal radius of the ellipse
|
||||
@param radiusY the vertical radius of the ellipse
|
||||
@param rotationOfEllipse an angle by which the whole ellipse should be rotated about its centre, in radians (clockwise)
|
||||
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
|
||||
top-centre of the ellipse)
|
||||
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
|
||||
top-centre of the ellipse). This angle can be greater than 2*Pi, so for example to
|
||||
draw a curve clockwise from the 9 o'clock position to the 3 o'clock position via
|
||||
12 o'clock, you'd use 1.5*Pi and 2.5*Pi as the start and finish points.
|
||||
@param startAsNewSubPath if true, the arc will begin a new subpath from its starting point; if false,
|
||||
it will be added to the current sub-path, continuing from the current position
|
||||
|
||||
@see addArc, arcTo
|
||||
*/
|
||||
void addCentredArc (float centreX, float centreY,
|
||||
float radiusX, float radiusY,
|
||||
float rotationOfEllipse,
|
||||
float fromRadians,
|
||||
float toRadians,
|
||||
bool startAsNewSubPath = false);
|
||||
|
||||
/** Adds a "pie-chart" shape to the path.
|
||||
|
||||
The shape is added as a new sub-path. (Any currently open paths will be
|
||||
left open).
|
||||
|
||||
Note that when specifying the start and end angles, the curve will be drawn either clockwise
|
||||
or anti-clockwise according to whether the end angle is greater than the start. This means
|
||||
that sometimes you may need to use values greater than 2*Pi for the end angle.
|
||||
|
||||
@param x the left-hand edge of the rectangle in which the elliptical outline fits
|
||||
@param y the top edge of the rectangle in which the elliptical outline fits
|
||||
@param width the width of the rectangle in which the elliptical outline fits
|
||||
@param height the height of the rectangle in which the elliptical outline fits
|
||||
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
|
||||
top-centre of the ellipse)
|
||||
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
|
||||
top-centre of the ellipse)
|
||||
@param innerCircleProportionalSize if this is > 0, then the pie will be drawn as a curved band around a hollow
|
||||
ellipse at its centre, where this value indicates the inner ellipse's size with
|
||||
respect to the outer one.
|
||||
@see addArc
|
||||
*/
|
||||
void addPieSegment (float x, float y,
|
||||
float width, float height,
|
||||
float fromRadians,
|
||||
float toRadians,
|
||||
float innerCircleProportionalSize);
|
||||
|
||||
/** Adds a "pie-chart" shape to the path.
|
||||
|
||||
The shape is added as a new sub-path. (Any currently open paths will be left open).
|
||||
|
||||
Note that when specifying the start and end angles, the curve will be drawn either clockwise
|
||||
or anti-clockwise according to whether the end angle is greater than the start. This means
|
||||
that sometimes you may need to use values greater than 2*Pi for the end angle.
|
||||
|
||||
@param segmentBounds the outer rectangle in which the elliptical outline fits
|
||||
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
|
||||
top-centre of the ellipse)
|
||||
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
|
||||
top-centre of the ellipse)
|
||||
@param innerCircleProportionalSize if this is > 0, then the pie will be drawn as a curved band around a hollow
|
||||
ellipse at its centre, where this value indicates the inner ellipse's size with
|
||||
respect to the outer one.
|
||||
@see addArc
|
||||
*/
|
||||
void addPieSegment (Rectangle<float> segmentBounds,
|
||||
float fromRadians,
|
||||
float toRadians,
|
||||
float innerCircleProportionalSize);
|
||||
|
||||
/** Adds a line with a specified thickness.
|
||||
|
||||
The line is added as a new closed sub-path. (Any currently open paths will be
|
||||
left open).
|
||||
|
||||
@see addArrow
|
||||
*/
|
||||
void addLineSegment (Line<float> line, float lineThickness);
|
||||
|
||||
/** Adds a line with an arrowhead on the end.
|
||||
The arrow is added as a new closed sub-path. (Any currently open paths will be left open).
|
||||
@see PathStrokeType::createStrokeWithArrowheads
|
||||
*/
|
||||
void addArrow (Line<float> line,
|
||||
float lineThickness,
|
||||
float arrowheadWidth,
|
||||
float arrowheadLength);
|
||||
|
||||
/** Adds a polygon shape to the path.
|
||||
@see addStar
|
||||
*/
|
||||
void addPolygon (Point<float> centre,
|
||||
int numberOfSides,
|
||||
float radius,
|
||||
float startAngle = 0.0f);
|
||||
|
||||
/** Adds a star shape to the path.
|
||||
@see addPolygon
|
||||
*/
|
||||
void addStar (Point<float> centre,
|
||||
int numberOfPoints,
|
||||
float innerRadius,
|
||||
float outerRadius,
|
||||
float startAngle = 0.0f);
|
||||
|
||||
/** Adds a speech-bubble shape to the path.
|
||||
|
||||
@param bodyArea the area of the body of the bubble shape
|
||||
@param maximumArea an area which encloses the body area and defines the limits within which
|
||||
the arrow tip can be drawn - if the tip lies outside this area, the bubble
|
||||
will be drawn without an arrow
|
||||
@param arrowTipPosition the location of the tip of the arrow
|
||||
@param cornerSize the size of the rounded corners
|
||||
@param arrowBaseWidth the width of the base of the arrow where it joins the main rectangle
|
||||
*/
|
||||
void addBubble (Rectangle<float> bodyArea,
|
||||
Rectangle<float> maximumArea,
|
||||
Point<float> arrowTipPosition,
|
||||
float cornerSize,
|
||||
float arrowBaseWidth);
|
||||
|
||||
/** Adds another path to this one.
|
||||
|
||||
The new path is added as a new sub-path. (Any currently open paths in this
|
||||
path will be left open).
|
||||
|
||||
@param pathToAppend the path to add
|
||||
*/
|
||||
void addPath (const Path& pathToAppend);
|
||||
|
||||
/** Adds another path to this one, transforming it on the way in.
|
||||
|
||||
The new path is added as a new sub-path, its points being transformed by the given
|
||||
matrix before being added.
|
||||
|
||||
@param pathToAppend the path to add
|
||||
@param transformToApply an optional transform to apply to the incoming vertices
|
||||
*/
|
||||
void addPath (const Path& pathToAppend,
|
||||
const AffineTransform& transformToApply);
|
||||
|
||||
/** Swaps the contents of this path with another one.
|
||||
|
||||
The internal data of the two paths is swapped over, so this is much faster than
|
||||
copying it to a temp variable and back.
|
||||
*/
|
||||
void swapWithPath (Path&) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Preallocates enough space for adding the given number of coordinates to the path.
|
||||
If you're about to add a large number of lines or curves to the path, it can make
|
||||
the task much more efficient to call this first and avoid costly reallocations
|
||||
as the structure grows.
|
||||
The actual value to pass is a bit tricky to calculate because the space required
|
||||
depends on what you're adding - e.g. each lineTo() or startNewSubPath() will
|
||||
require 3 coords (x, y and a type marker). Each quadraticTo() will need 5, and
|
||||
a cubicTo() will require 7. Closing a sub-path will require 1.
|
||||
*/
|
||||
void preallocateSpace (int numExtraCoordsToMakeSpaceFor);
|
||||
|
||||
//==============================================================================
|
||||
/** Applies a 2D transform to all the vertices in the path.
|
||||
|
||||
@see AffineTransform, scaleToFit, getTransformToScaleToFit
|
||||
*/
|
||||
void applyTransform (const AffineTransform& transform) noexcept;
|
||||
|
||||
/** Rescales this path to make it fit neatly into a given space.
|
||||
|
||||
This is effectively a quick way of calling
|
||||
applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions))
|
||||
|
||||
@param x the x position of the rectangle to fit the path inside
|
||||
@param y the y position of the rectangle to fit the path inside
|
||||
@param width the width of the rectangle to fit the path inside
|
||||
@param height the height of the rectangle to fit the path inside
|
||||
@param preserveProportions if true, it will fit the path into the space without altering its
|
||||
horizontal/vertical scale ratio; if false, it will distort the
|
||||
path to fill the specified ratio both horizontally and vertically
|
||||
|
||||
@see applyTransform, getTransformToScaleToFit
|
||||
*/
|
||||
void scaleToFit (float x, float y, float width, float height,
|
||||
bool preserveProportions) noexcept;
|
||||
|
||||
/** Returns a transform that can be used to rescale the path to fit into a given space.
|
||||
|
||||
@param x the x position of the rectangle to fit the path inside
|
||||
@param y the y position of the rectangle to fit the path inside
|
||||
@param width the width of the rectangle to fit the path inside
|
||||
@param height the height of the rectangle to fit the path inside
|
||||
@param preserveProportions if true, it will fit the path into the space without altering its
|
||||
horizontal/vertical scale ratio; if false, it will distort the
|
||||
path to fill the specified ratio both horizontally and vertically
|
||||
@param justificationType if the proportions are preserved, the resultant path may be smaller
|
||||
than the available rectangle, so this describes how it should be
|
||||
positioned within the space.
|
||||
@returns an appropriate transformation
|
||||
|
||||
@see applyTransform, scaleToFit
|
||||
|
||||
*/
|
||||
AffineTransform getTransformToScaleToFit (float x, float y, float width, float height,
|
||||
bool preserveProportions,
|
||||
Justification justificationType = Justification::centred) const;
|
||||
|
||||
/** Returns a transform that can be used to rescale the path to fit into a given space.
|
||||
|
||||
@param area the rectangle to fit the path inside
|
||||
@param preserveProportions if true, it will fit the path into the space without altering its
|
||||
horizontal/vertical scale ratio; if false, it will distort the
|
||||
path to fill the specified ratio both horizontally and vertically
|
||||
@param justificationType if the proportions are preserved, the resultant path may be smaller
|
||||
than the available rectangle, so this describes how it should be
|
||||
positioned within the space.
|
||||
@returns an appropriate transformation
|
||||
|
||||
@see applyTransform, scaleToFit
|
||||
|
||||
*/
|
||||
AffineTransform getTransformToScaleToFit (Rectangle<float> area,
|
||||
bool preserveProportions,
|
||||
Justification justificationType = Justification::centred) const;
|
||||
|
||||
/** Creates a version of this path where all sharp corners have been replaced by curves.
|
||||
|
||||
Wherever two lines meet at an angle, this will replace the corner with a curve
|
||||
of the given radius.
|
||||
*/
|
||||
Path createPathWithRoundedCorners (float cornerRadius) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the winding-rule to be used when filling the path.
|
||||
|
||||
If set to true (which is the default), then the path uses a non-zero-winding rule
|
||||
to determine which points are inside the path. If set to false, it uses an
|
||||
alternate-winding rule.
|
||||
|
||||
The winding-rule comes into play when areas of the shape overlap other
|
||||
areas, and determines whether the overlapping regions are considered to be
|
||||
inside or outside.
|
||||
|
||||
Changing this value just sets a flag - it doesn't affect the contents of the
|
||||
path.
|
||||
|
||||
@see isUsingNonZeroWinding
|
||||
*/
|
||||
void setUsingNonZeroWinding (bool isNonZeroWinding) noexcept;
|
||||
|
||||
/** Returns the flag that indicates whether the path should use a non-zero winding rule.
|
||||
|
||||
The default for a new path is true.
|
||||
|
||||
@see setUsingNonZeroWinding
|
||||
*/
|
||||
bool isUsingNonZeroWinding() const { return useNonZeroWinding; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Iterates the lines and curves that a path contains.
|
||||
|
||||
@see Path, PathFlatteningIterator
|
||||
*/
|
||||
class JUCE_API Iterator
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Iterator (const Path& path) noexcept;
|
||||
~Iterator() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Moves onto the next element in the path.
|
||||
|
||||
If this returns false, there are no more elements. If it returns true,
|
||||
the elementType variable will be set to the type of the current element,
|
||||
and some of the x and y variables will be filled in with values.
|
||||
*/
|
||||
bool next() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
enum PathElementType
|
||||
{
|
||||
startNewSubPath, /**< For this type, x1 and y1 will be set to indicate the first point in the subpath. */
|
||||
lineTo, /**< For this type, x1 and y1 indicate the end point of the line. */
|
||||
quadraticTo, /**< For this type, x1, y1, x2, y2 indicate the control point and endpoint of a quadratic curve. */
|
||||
cubicTo, /**< For this type, x1, y1, x2, y2, x3, y3 indicate the two control points and the endpoint of a cubic curve. */
|
||||
closePath /**< Indicates that the sub-path is being closed. None of the x or y values are valid in this case. */
|
||||
};
|
||||
|
||||
PathElementType elementType;
|
||||
|
||||
float x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0;
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
const Path& path;
|
||||
const float* index;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Iterator)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Loads a stored path from a data stream.
|
||||
|
||||
The data in the stream must have been written using writePathToStream().
|
||||
|
||||
Note that this will append the stored path to whatever is currently in
|
||||
this path, so you might need to call clear() beforehand.
|
||||
|
||||
@see loadPathFromData, writePathToStream
|
||||
*/
|
||||
void loadPathFromStream (InputStream& source);
|
||||
|
||||
/** Loads a stored path from a block of data.
|
||||
|
||||
This is similar to loadPathFromStream(), but just reads from a block
|
||||
of data. Useful if you're including stored shapes in your code as a
|
||||
block of static data.
|
||||
|
||||
@see loadPathFromStream, writePathToStream
|
||||
*/
|
||||
void loadPathFromData (const void* data, size_t numberOfBytes);
|
||||
|
||||
/** Stores the path by writing it out to a stream.
|
||||
After writing out a path, you can reload it using loadPathFromStream().
|
||||
@see loadPathFromStream, loadPathFromData
|
||||
*/
|
||||
void writePathToStream (OutputStream& destination) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a string containing a textual representation of this path.
|
||||
@see restoreFromString
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Restores this path from a string that was created with the toString() method.
|
||||
@see toString()
|
||||
*/
|
||||
void restoreFromString (StringRef stringVersion);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class PathFlatteningIterator;
|
||||
friend class Path::Iterator;
|
||||
friend class EdgeTable;
|
||||
|
||||
Array<float> data;
|
||||
|
||||
struct PathBounds
|
||||
{
|
||||
PathBounds() noexcept;
|
||||
Rectangle<float> getRectangle() const noexcept;
|
||||
void reset() noexcept;
|
||||
void reset (float, float) noexcept;
|
||||
void extend (float, float) noexcept;
|
||||
|
||||
template <typename... Coords>
|
||||
void extend (float x, float y, Coords... coords) noexcept
|
||||
{
|
||||
extend (x, y);
|
||||
extend (coords...);
|
||||
}
|
||||
|
||||
float pathXMin = 0, pathXMax = 0, pathYMin = 0, pathYMax = 0;
|
||||
};
|
||||
|
||||
PathBounds bounds;
|
||||
bool useNonZeroWinding = true;
|
||||
|
||||
static constexpr float lineMarker = 100001.0f;
|
||||
static constexpr float moveMarker = 100002.0f;
|
||||
static constexpr float quadMarker = 100003.0f;
|
||||
static constexpr float cubicMarker = 100004.0f;
|
||||
static constexpr float closeSubPathMarker = 100005.0f;
|
||||
|
||||
JUCE_LEAK_DETECTOR (Path)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MSVC && JUCE_DEBUG
|
||||
#pragma optimize ("t", on)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
PathFlatteningIterator::PathFlatteningIterator (const Path& pathToUse,
|
||||
const AffineTransform& t,
|
||||
float tolerance)
|
||||
: x2 (0),
|
||||
y2 (0),
|
||||
closesSubPath (false),
|
||||
subPathIndex (-1),
|
||||
path (pathToUse),
|
||||
transform (t),
|
||||
source (path.data.begin()),
|
||||
toleranceSquared (tolerance * tolerance),
|
||||
isIdentityTransform (t.isIdentity())
|
||||
{
|
||||
stackPos = stackBase;
|
||||
}
|
||||
|
||||
PathFlatteningIterator::~PathFlatteningIterator()
|
||||
{
|
||||
}
|
||||
|
||||
bool PathFlatteningIterator::isLastInSubpath() const noexcept
|
||||
{
|
||||
return stackPos == stackBase.get()
|
||||
&& (source == path.data.end() || isMarker (*source, Path::moveMarker));
|
||||
}
|
||||
|
||||
bool PathFlatteningIterator::next()
|
||||
{
|
||||
x1 = x2;
|
||||
y1 = y2;
|
||||
|
||||
float x3 = 0;
|
||||
float y3 = 0;
|
||||
float x4 = 0;
|
||||
float y4 = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
float type;
|
||||
|
||||
if (stackPos == stackBase.get())
|
||||
{
|
||||
if (source == path.data.end())
|
||||
return false;
|
||||
|
||||
type = *source++;
|
||||
|
||||
if (! isMarker (type, Path::closeSubPathMarker))
|
||||
{
|
||||
x2 = *source++;
|
||||
y2 = *source++;
|
||||
|
||||
if (isMarker (type, Path::quadMarker))
|
||||
{
|
||||
x3 = *source++;
|
||||
y3 = *source++;
|
||||
|
||||
if (! isIdentityTransform)
|
||||
transform.transformPoints (x2, y2, x3, y3);
|
||||
}
|
||||
else if (isMarker (type, Path::cubicMarker))
|
||||
{
|
||||
x3 = *source++;
|
||||
y3 = *source++;
|
||||
x4 = *source++;
|
||||
y4 = *source++;
|
||||
|
||||
if (! isIdentityTransform)
|
||||
transform.transformPoints (x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! isIdentityTransform)
|
||||
transform.transformPoint (x2, y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
type = *--stackPos;
|
||||
|
||||
if (! isMarker (type, Path::closeSubPathMarker))
|
||||
{
|
||||
x2 = *--stackPos;
|
||||
y2 = *--stackPos;
|
||||
|
||||
if (isMarker (type, Path::quadMarker))
|
||||
{
|
||||
x3 = *--stackPos;
|
||||
y3 = *--stackPos;
|
||||
}
|
||||
else if (isMarker (type, Path::cubicMarker))
|
||||
{
|
||||
x3 = *--stackPos;
|
||||
y3 = *--stackPos;
|
||||
x4 = *--stackPos;
|
||||
y4 = *--stackPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMarker (type, Path::lineMarker))
|
||||
{
|
||||
++subPathIndex;
|
||||
|
||||
closesSubPath = stackPos == stackBase.get()
|
||||
&& source != path.data.end()
|
||||
&& isMarker (*source, Path::closeSubPathMarker)
|
||||
&& approximatelyEqual (x2, subPathCloseX)
|
||||
&& approximatelyEqual (y2, subPathCloseY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isMarker (type, Path::quadMarker))
|
||||
{
|
||||
const size_t offset = (size_t) (stackPos - stackBase);
|
||||
|
||||
if (offset >= stackSize - 10)
|
||||
{
|
||||
stackSize <<= 1;
|
||||
stackBase.realloc (stackSize);
|
||||
stackPos = stackBase + offset;
|
||||
}
|
||||
|
||||
auto m1x = (x1 + x2) * 0.5f;
|
||||
auto m1y = (y1 + y2) * 0.5f;
|
||||
auto m2x = (x2 + x3) * 0.5f;
|
||||
auto m2y = (y2 + y3) * 0.5f;
|
||||
auto m3x = (m1x + m2x) * 0.5f;
|
||||
auto m3y = (m1y + m2y) * 0.5f;
|
||||
|
||||
auto errorX = m3x - x2;
|
||||
auto errorY = m3y - y2;
|
||||
|
||||
auto outsideTolerance = errorX * errorX + errorY * errorY > toleranceSquared;
|
||||
auto canBeSubdivided = (! approximatelyEqual (m3x, m1x) && ! approximatelyEqual (m3x, m2x))
|
||||
|| (! approximatelyEqual (m3y, m1y) && ! approximatelyEqual (m3y, m2y));
|
||||
|
||||
if (outsideTolerance && canBeSubdivided)
|
||||
{
|
||||
*stackPos++ = y3;
|
||||
*stackPos++ = x3;
|
||||
*stackPos++ = m2y;
|
||||
*stackPos++ = m2x;
|
||||
*stackPos++ = Path::quadMarker;
|
||||
|
||||
*stackPos++ = m3y;
|
||||
*stackPos++ = m3x;
|
||||
*stackPos++ = m1y;
|
||||
*stackPos++ = m1x;
|
||||
*stackPos++ = Path::quadMarker;
|
||||
}
|
||||
else
|
||||
{
|
||||
*stackPos++ = y3;
|
||||
*stackPos++ = x3;
|
||||
*stackPos++ = Path::lineMarker;
|
||||
|
||||
*stackPos++ = m3y;
|
||||
*stackPos++ = m3x;
|
||||
*stackPos++ = Path::lineMarker;
|
||||
}
|
||||
|
||||
jassert (stackPos < stackBase + stackSize);
|
||||
}
|
||||
else if (isMarker (type, Path::cubicMarker))
|
||||
{
|
||||
const size_t offset = (size_t) (stackPos - stackBase);
|
||||
|
||||
if (offset >= stackSize - 16)
|
||||
{
|
||||
stackSize <<= 1;
|
||||
stackBase.realloc (stackSize);
|
||||
stackPos = stackBase + offset;
|
||||
}
|
||||
|
||||
auto m1x = (x1 + x2) * 0.5f;
|
||||
auto m1y = (y1 + y2) * 0.5f;
|
||||
auto m2x = (x3 + x2) * 0.5f;
|
||||
auto m2y = (y3 + y2) * 0.5f;
|
||||
auto m3x = (x3 + x4) * 0.5f;
|
||||
auto m3y = (y3 + y4) * 0.5f;
|
||||
auto m4x = (m1x + m2x) * 0.5f;
|
||||
auto m4y = (m1y + m2y) * 0.5f;
|
||||
auto m5x = (m3x + m2x) * 0.5f;
|
||||
auto m5y = (m3y + m2y) * 0.5f;
|
||||
|
||||
auto error1X = m4x - x2;
|
||||
auto error1Y = m4y - y2;
|
||||
auto error2X = m5x - x3;
|
||||
auto error2Y = m5y - y3;
|
||||
|
||||
auto outsideTolerance = error1X * error1X + error1Y * error1Y > toleranceSquared
|
||||
|| error2X * error2X + error2Y * error2Y > toleranceSquared;
|
||||
auto canBeSubdivided = (! approximatelyEqual (m4x, m1x) && ! approximatelyEqual (m4x, m2x))
|
||||
|| (! approximatelyEqual (m4y, m1y) && ! approximatelyEqual (m4y, m2y))
|
||||
|| (! approximatelyEqual (m5x, m3x) && ! approximatelyEqual (m5x, m2x))
|
||||
|| (! approximatelyEqual (m5y, m3y) && ! approximatelyEqual (m5y, m2y));
|
||||
|
||||
if (outsideTolerance && canBeSubdivided)
|
||||
{
|
||||
*stackPos++ = y4;
|
||||
*stackPos++ = x4;
|
||||
*stackPos++ = m3y;
|
||||
*stackPos++ = m3x;
|
||||
*stackPos++ = m5y;
|
||||
*stackPos++ = m5x;
|
||||
*stackPos++ = Path::cubicMarker;
|
||||
|
||||
*stackPos++ = (m4y + m5y) * 0.5f;
|
||||
*stackPos++ = (m4x + m5x) * 0.5f;
|
||||
*stackPos++ = m4y;
|
||||
*stackPos++ = m4x;
|
||||
*stackPos++ = m1y;
|
||||
*stackPos++ = m1x;
|
||||
*stackPos++ = Path::cubicMarker;
|
||||
}
|
||||
else
|
||||
{
|
||||
*stackPos++ = y4;
|
||||
*stackPos++ = x4;
|
||||
*stackPos++ = Path::lineMarker;
|
||||
|
||||
*stackPos++ = m5y;
|
||||
*stackPos++ = m5x;
|
||||
*stackPos++ = Path::lineMarker;
|
||||
|
||||
*stackPos++ = m4y;
|
||||
*stackPos++ = m4x;
|
||||
*stackPos++ = Path::lineMarker;
|
||||
}
|
||||
}
|
||||
else if (isMarker (type, Path::closeSubPathMarker))
|
||||
{
|
||||
if (! approximatelyEqual (x2, subPathCloseX) || ! approximatelyEqual (y2, subPathCloseY))
|
||||
{
|
||||
x1 = x2;
|
||||
y1 = y2;
|
||||
x2 = subPathCloseX;
|
||||
y2 = subPathCloseY;
|
||||
closesSubPath = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jassert (isMarker (type, Path::moveMarker));
|
||||
|
||||
subPathIndex = -1;
|
||||
subPathCloseX = x1 = x2;
|
||||
subPathCloseY = y1 = y2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if JUCE_MSVC && JUCE_DEBUG
|
||||
#pragma optimize ("", on) // resets optimisations to the project defaults
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Flattens a Path object into a series of straight-line sections.
|
||||
|
||||
Use one of these to iterate through a Path object, and it will convert
|
||||
all the curves into line sections so it's easy to render or perform
|
||||
geometric operations on.
|
||||
|
||||
@see Path
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API PathFlatteningIterator final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a PathFlatteningIterator.
|
||||
|
||||
After creation, use the next() method to initialise the fields in the
|
||||
object with the first line's position.
|
||||
|
||||
@param path the path to iterate along
|
||||
@param transform a transform to apply to each point in the path being iterated
|
||||
@param tolerance the amount by which the curves are allowed to deviate from the lines
|
||||
into which they are being broken down - a higher tolerance contains
|
||||
less lines, so can be generated faster, but will be less smooth.
|
||||
*/
|
||||
PathFlatteningIterator (const Path& path,
|
||||
const AffineTransform& transform = AffineTransform(),
|
||||
float tolerance = Path::defaultToleranceForMeasurement);
|
||||
|
||||
/** Destructor. */
|
||||
~PathFlatteningIterator();
|
||||
|
||||
//==============================================================================
|
||||
/** Fetches the next line segment from the path.
|
||||
|
||||
This will update the member variables x1, y1, x2, y2, subPathIndex and closesSubPath
|
||||
so that they describe the new line segment.
|
||||
|
||||
@returns false when there are no more lines to fetch.
|
||||
*/
|
||||
bool next();
|
||||
|
||||
float x1; /**< The x position of the start of the current line segment. */
|
||||
float y1; /**< The y position of the start of the current line segment. */
|
||||
float x2; /**< The x position of the end of the current line segment. */
|
||||
float y2; /**< The y position of the end of the current line segment. */
|
||||
|
||||
/** Indicates whether the current line segment is closing a sub-path.
|
||||
|
||||
If the current line is the one that connects the end of a sub-path
|
||||
back to the start again, this will be true.
|
||||
*/
|
||||
bool closesSubPath;
|
||||
|
||||
/** The index of the current line within the current sub-path.
|
||||
|
||||
E.g. you can use this to see whether the line is the first one in the
|
||||
subpath by seeing if it's 0.
|
||||
*/
|
||||
int subPathIndex;
|
||||
|
||||
/** Returns true if the current segment is the last in the current sub-path. */
|
||||
bool isLastInSubpath() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
const Path& path;
|
||||
const AffineTransform transform;
|
||||
const float* source;
|
||||
const float toleranceSquared;
|
||||
float subPathCloseX = 0, subPathCloseY = 0;
|
||||
const bool isIdentityTransform;
|
||||
|
||||
HeapBlock<float> stackBase { 32 };
|
||||
float* stackPos;
|
||||
size_t stackSize = 32;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathFlatteningIterator)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,747 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
PathStrokeType::PathStrokeType (float strokeThickness) noexcept
|
||||
: thickness (strokeThickness), jointStyle (mitered), endStyle (butt)
|
||||
{
|
||||
}
|
||||
|
||||
PathStrokeType::PathStrokeType (float strokeThickness, JointStyle joint, EndCapStyle end) noexcept
|
||||
: thickness (strokeThickness), jointStyle (joint), endStyle (end)
|
||||
{
|
||||
}
|
||||
|
||||
PathStrokeType::PathStrokeType (const PathStrokeType& other) noexcept
|
||||
: thickness (other.thickness),
|
||||
jointStyle (other.jointStyle),
|
||||
endStyle (other.endStyle)
|
||||
{
|
||||
}
|
||||
|
||||
PathStrokeType& PathStrokeType::operator= (const PathStrokeType& other) noexcept
|
||||
{
|
||||
thickness = other.thickness;
|
||||
jointStyle = other.jointStyle;
|
||||
endStyle = other.endStyle;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathStrokeType::~PathStrokeType() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
bool PathStrokeType::operator== (const PathStrokeType& other) const noexcept
|
||||
{
|
||||
const auto tie = [] (const PathStrokeType& p) { return std::tie (p.thickness, p.jointStyle, p.endStyle); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool PathStrokeType::operator!= (const PathStrokeType& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace PathStrokeHelpers
|
||||
{
|
||||
struct LineIntersection
|
||||
{
|
||||
Point<float> point;
|
||||
float distanceBeyondLine1EndSquared;
|
||||
bool intersects;
|
||||
};
|
||||
|
||||
static LineIntersection lineIntersection (const float x1, const float y1,
|
||||
const float x2, const float y2,
|
||||
const float x3, const float y3,
|
||||
const float x4, const float y4)
|
||||
{
|
||||
if (! approximatelyEqual (x2, x3) || ! approximatelyEqual (y2, y3))
|
||||
{
|
||||
const auto dx1 = x2 - x1;
|
||||
const auto dy1 = y2 - y1;
|
||||
const auto dx2 = x4 - x3;
|
||||
const auto dy2 = y4 - y3;
|
||||
const auto divisor = dx1 * dy2 - dx2 * dy1;
|
||||
|
||||
if (approximatelyEqual (divisor, 0.0f))
|
||||
{
|
||||
if (! ((approximatelyEqual (dx1, 0.0f) && approximatelyEqual (dy1, 0.0f))
|
||||
|| (approximatelyEqual (dx2, 0.0f) && approximatelyEqual (dy2, 0.0f))))
|
||||
{
|
||||
if (approximatelyEqual (dy1, 0.0f) && ! approximatelyEqual (dy2, 0.0f))
|
||||
{
|
||||
const auto along = (y1 - y3) / dy2;
|
||||
const auto intersectionX = x3 + along * dx2;
|
||||
const auto intersectionY = y1;
|
||||
|
||||
const auto distance = square (intersectionX - x2);
|
||||
const auto distanceBeyondLine1EndSquared = (x2 > x1) == (intersectionX < x2)
|
||||
? -distance
|
||||
: distance;
|
||||
|
||||
return { { intersectionX, intersectionY },
|
||||
distanceBeyondLine1EndSquared,
|
||||
along >= 0 && along <= 1.0f };
|
||||
}
|
||||
|
||||
if (approximatelyEqual (dy2, 0.0f) && ! approximatelyEqual (dy1, 0.0f))
|
||||
{
|
||||
const auto along = (y3 - y1) / dy1;
|
||||
const auto intersectionX = x1 + along * dx1;
|
||||
const auto intersectionY = y3;
|
||||
|
||||
const auto distance = square ((along - 1.0f) * dx1);
|
||||
const auto distanceBeyondLine1EndSquared = along < 1.0f ? -distance : distance;
|
||||
|
||||
return { { intersectionX, intersectionY },
|
||||
distanceBeyondLine1EndSquared,
|
||||
along >= 0 && along <= 1.0f };
|
||||
}
|
||||
|
||||
if (approximatelyEqual (dx1, 0.0f) && ! approximatelyEqual (dx2, 0.0f))
|
||||
{
|
||||
const auto along = (x1 - x3) / dx2;
|
||||
const auto intersectionX = x1;
|
||||
const auto intersectionY = y3 + along * dy2;
|
||||
|
||||
const auto distance = square (intersectionY - y2);
|
||||
const auto distanceBeyondLine1EndSquared = (y2 > y1) == (intersectionY < y2)
|
||||
? -distance
|
||||
: distance;
|
||||
|
||||
return { { intersectionX, intersectionY },
|
||||
distanceBeyondLine1EndSquared,
|
||||
along >= 0 && along <= 1.0f };
|
||||
}
|
||||
|
||||
if (approximatelyEqual (dx2, 0.0f) && ! approximatelyEqual (dx1, 0.0f))
|
||||
{
|
||||
const auto along = (x3 - x1) / dx1;
|
||||
const auto intersectionX = x3;
|
||||
const auto intersectionY = y1 + along * dy1;
|
||||
|
||||
const auto distance = square ((along - 1.0f) * dy1);
|
||||
const auto distanceBeyondLine1EndSquared = along < 1.0f ? -distance : distance;
|
||||
|
||||
return { { intersectionX, intersectionY },
|
||||
distanceBeyondLine1EndSquared,
|
||||
along >= 0 && along <= 1.0f };
|
||||
}
|
||||
}
|
||||
|
||||
const auto intersectionX = 0.5f * (x2 + x3);
|
||||
const auto intersectionY = 0.5f * (y2 + y3);
|
||||
|
||||
const auto distanceBeyondLine1EndSquared = 0.0f;
|
||||
|
||||
return { { intersectionX, intersectionY },
|
||||
distanceBeyondLine1EndSquared,
|
||||
false };
|
||||
}
|
||||
|
||||
const auto along = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor;
|
||||
|
||||
const auto intersectionX = x1 + along * dx1;
|
||||
const auto intersectionY = y1 + along * dy1;
|
||||
|
||||
if (along >= 0 && along <= 1.0f)
|
||||
{
|
||||
const auto along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1) / divisor;
|
||||
|
||||
if (along2 >= 0 && along2 <= 1.0f)
|
||||
{
|
||||
return { { intersectionX, intersectionY },
|
||||
0.0f,
|
||||
true };
|
||||
}
|
||||
}
|
||||
|
||||
const auto distance = square (along - 1.0f) * (dx1 * dx1 + dy1 * dy1);
|
||||
const auto distanceBeyondLine1EndSquared = along < 1.0f ? -distance : distance;
|
||||
|
||||
return { { intersectionX, intersectionY },
|
||||
distanceBeyondLine1EndSquared,
|
||||
false };
|
||||
}
|
||||
|
||||
return { Point { x2, y2 }, 0.0f, true };
|
||||
}
|
||||
|
||||
static void addEdgeAndJoint (Path& destPath,
|
||||
const PathStrokeType::JointStyle style,
|
||||
const float maxMiterExtensionSquared, const float width,
|
||||
const float x1, const float y1,
|
||||
const float x2, const float y2,
|
||||
const float x3, const float y3,
|
||||
const float x4, const float y4,
|
||||
const float midX, const float midY)
|
||||
{
|
||||
if (style == PathStrokeType::beveled
|
||||
|| (approximatelyEqual (x3, x4) && approximatelyEqual (y3, y4))
|
||||
|| (approximatelyEqual (x1, x2) && approximatelyEqual (y1, y2)))
|
||||
{
|
||||
destPath.lineTo (x2, y2);
|
||||
destPath.lineTo (x3, y3);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto intersection = lineIntersection (x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
|
||||
// if they intersect, use this point..
|
||||
if (intersection.intersects)
|
||||
{
|
||||
destPath.lineTo (intersection.point);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (style == PathStrokeType::mitered)
|
||||
{
|
||||
if (0.0f < intersection.distanceBeyondLine1EndSquared
|
||||
&& intersection.distanceBeyondLine1EndSquared < maxMiterExtensionSquared)
|
||||
{
|
||||
destPath.lineTo (intersection.point);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the end sticks out too far, so just use a blunt joint
|
||||
destPath.lineTo (x2, y2);
|
||||
destPath.lineTo (x3, y3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// curved joints
|
||||
float angle1 = std::atan2 (x2 - midX, y2 - midY);
|
||||
float angle2 = std::atan2 (x3 - midX, y3 - midY);
|
||||
const float angleIncrement = 0.1f;
|
||||
|
||||
destPath.lineTo (x2, y2);
|
||||
|
||||
if (std::abs (angle1 - angle2) > angleIncrement)
|
||||
{
|
||||
if (angle2 > angle1 + MathConstants<float>::pi
|
||||
|| (angle2 < angle1 && angle2 >= angle1 - MathConstants<float>::pi))
|
||||
{
|
||||
if (angle2 > angle1)
|
||||
angle2 -= MathConstants<float>::twoPi;
|
||||
|
||||
jassert (angle1 <= angle2 + MathConstants<float>::pi);
|
||||
|
||||
angle1 -= angleIncrement;
|
||||
while (angle1 > angle2)
|
||||
{
|
||||
destPath.lineTo (midX + width * std::sin (angle1),
|
||||
midY + width * std::cos (angle1));
|
||||
|
||||
angle1 -= angleIncrement;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (angle1 > angle2)
|
||||
angle1 -= MathConstants<float>::twoPi;
|
||||
|
||||
jassert (angle1 >= angle2 - MathConstants<float>::pi);
|
||||
|
||||
angle1 += angleIncrement;
|
||||
while (angle1 < angle2)
|
||||
{
|
||||
destPath.lineTo (midX + width * std::sin (angle1),
|
||||
midY + width * std::cos (angle1));
|
||||
|
||||
angle1 += angleIncrement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destPath.lineTo (x3, y3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addLineEnd (Path& destPath,
|
||||
const PathStrokeType::EndCapStyle style,
|
||||
const float x1, const float y1,
|
||||
const float x2, const float y2,
|
||||
const float width)
|
||||
{
|
||||
if (style == PathStrokeType::butt)
|
||||
{
|
||||
destPath.lineTo (x2, y2);
|
||||
}
|
||||
else
|
||||
{
|
||||
float offx1, offy1, offx2, offy2;
|
||||
|
||||
auto dx = x2 - x1;
|
||||
auto dy = y2 - y1;
|
||||
auto len = juce_hypot (dx, dy);
|
||||
|
||||
if (approximatelyEqual (len, 0.0f))
|
||||
{
|
||||
offx1 = offx2 = x1;
|
||||
offy1 = offy2 = y1;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto offset = width / len;
|
||||
dx *= offset;
|
||||
dy *= offset;
|
||||
|
||||
offx1 = x1 + dy;
|
||||
offy1 = y1 - dx;
|
||||
offx2 = x2 + dy;
|
||||
offy2 = y2 - dx;
|
||||
}
|
||||
|
||||
if (style == PathStrokeType::square)
|
||||
{
|
||||
// square ends
|
||||
destPath.lineTo (offx1, offy1);
|
||||
destPath.lineTo (offx2, offy2);
|
||||
destPath.lineTo (x2, y2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// rounded ends
|
||||
auto midx = (offx1 + offx2) * 0.5f;
|
||||
auto midy = (offy1 + offy2) * 0.5f;
|
||||
|
||||
destPath.cubicTo (x1 + (offx1 - x1) * 0.55f, y1 + (offy1 - y1) * 0.55f,
|
||||
offx1 + (midx - offx1) * 0.45f, offy1 + (midy - offy1) * 0.45f,
|
||||
midx, midy);
|
||||
|
||||
destPath.cubicTo (midx + (offx2 - midx) * 0.55f, midy + (offy2 - midy) * 0.55f,
|
||||
offx2 + (x2 - offx2) * 0.45f, offy2 + (y2 - offy2) * 0.45f,
|
||||
x2, y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Arrowhead
|
||||
{
|
||||
float startWidth, startLength;
|
||||
float endWidth, endLength;
|
||||
};
|
||||
|
||||
static void addArrowhead (Path& destPath,
|
||||
const float x1, const float y1,
|
||||
const float x2, const float y2,
|
||||
const float tipX, const float tipY,
|
||||
const float width,
|
||||
const float arrowheadWidth)
|
||||
{
|
||||
Line<float> line (x1, y1, x2, y2);
|
||||
destPath.lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0));
|
||||
destPath.lineTo (tipX, tipY);
|
||||
destPath.lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0));
|
||||
destPath.lineTo (x2, y2);
|
||||
}
|
||||
|
||||
struct LineSection
|
||||
{
|
||||
float x1, y1, x2, y2; // original line
|
||||
float lx1, ly1, lx2, ly2; // the left-hand stroke
|
||||
float rx1, ry1, rx2, ry2; // the right-hand stroke
|
||||
};
|
||||
|
||||
static void shortenSubPath (Array<LineSection>& subPath, float amountAtStart, float amountAtEnd)
|
||||
{
|
||||
while (amountAtEnd > 0 && subPath.size() > 0)
|
||||
{
|
||||
auto& l = subPath.getReference (subPath.size() - 1);
|
||||
auto dx = l.rx2 - l.rx1;
|
||||
auto dy = l.ry2 - l.ry1;
|
||||
auto len = juce_hypot (dx, dy);
|
||||
|
||||
if (len <= amountAtEnd && subPath.size() > 1)
|
||||
{
|
||||
LineSection& prev = subPath.getReference (subPath.size() - 2);
|
||||
prev.x2 = l.x2;
|
||||
prev.y2 = l.y2;
|
||||
subPath.removeLast();
|
||||
amountAtEnd -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prop = jmin (0.9999f, amountAtEnd / len);
|
||||
dx *= prop;
|
||||
dy *= prop;
|
||||
l.rx1 += dx;
|
||||
l.ry1 += dy;
|
||||
l.lx2 += dx;
|
||||
l.ly2 += dy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (amountAtStart > 0 && subPath.size() > 0)
|
||||
{
|
||||
auto& l = subPath.getReference (0);
|
||||
auto dx = l.rx2 - l.rx1;
|
||||
auto dy = l.ry2 - l.ry1;
|
||||
auto len = juce_hypot (dx, dy);
|
||||
|
||||
if (len <= amountAtStart && subPath.size() > 1)
|
||||
{
|
||||
LineSection& next = subPath.getReference (1);
|
||||
next.x1 = l.x1;
|
||||
next.y1 = l.y1;
|
||||
subPath.remove (0);
|
||||
amountAtStart -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prop = jmin (0.9999f, amountAtStart / len);
|
||||
dx *= prop;
|
||||
dy *= prop;
|
||||
l.rx2 -= dx;
|
||||
l.ry2 -= dy;
|
||||
l.lx1 -= dx;
|
||||
l.ly1 -= dy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addSubPath (Path& destPath, Array<LineSection>& subPath,
|
||||
const bool isClosed, const float width, const float maxMiterExtensionSquared,
|
||||
const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle,
|
||||
const Arrowhead* const arrowhead)
|
||||
{
|
||||
jassert (subPath.size() > 0);
|
||||
|
||||
if (arrowhead != nullptr)
|
||||
shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength);
|
||||
|
||||
auto& firstLine = subPath.getReference (0);
|
||||
|
||||
auto lastX1 = firstLine.lx1;
|
||||
auto lastY1 = firstLine.ly1;
|
||||
auto lastX2 = firstLine.lx2;
|
||||
auto lastY2 = firstLine.ly2;
|
||||
|
||||
if (isClosed)
|
||||
{
|
||||
destPath.startNewSubPath (lastX1, lastY1);
|
||||
}
|
||||
else
|
||||
{
|
||||
destPath.startNewSubPath (firstLine.rx2, firstLine.ry2);
|
||||
|
||||
if (arrowhead != nullptr && arrowhead->startWidth > 0.0f)
|
||||
addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1,
|
||||
width, arrowhead->startWidth);
|
||||
else
|
||||
addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width);
|
||||
}
|
||||
|
||||
for (int i = 1; i < subPath.size(); ++i)
|
||||
{
|
||||
const LineSection& l = subPath.getReference (i);
|
||||
|
||||
addEdgeAndJoint (destPath, jointStyle,
|
||||
maxMiterExtensionSquared, width,
|
||||
lastX1, lastY1, lastX2, lastY2,
|
||||
l.lx1, l.ly1, l.lx2, l.ly2,
|
||||
l.x1, l.y1);
|
||||
|
||||
lastX1 = l.lx1;
|
||||
lastY1 = l.ly1;
|
||||
lastX2 = l.lx2;
|
||||
lastY2 = l.ly2;
|
||||
}
|
||||
|
||||
auto& lastLine = subPath.getReference (subPath.size() - 1);
|
||||
|
||||
if (isClosed)
|
||||
{
|
||||
auto& l = subPath.getReference (0);
|
||||
|
||||
addEdgeAndJoint (destPath, jointStyle,
|
||||
maxMiterExtensionSquared, width,
|
||||
lastX1, lastY1, lastX2, lastY2,
|
||||
l.lx1, l.ly1, l.lx2, l.ly2,
|
||||
l.x1, l.y1);
|
||||
|
||||
destPath.closeSubPath();
|
||||
destPath.startNewSubPath (lastLine.rx1, lastLine.ry1);
|
||||
}
|
||||
else
|
||||
{
|
||||
destPath.lineTo (lastX2, lastY2);
|
||||
|
||||
if (arrowhead != nullptr && arrowhead->endWidth > 0.0f)
|
||||
addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2,
|
||||
width, arrowhead->endWidth);
|
||||
else
|
||||
addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width);
|
||||
}
|
||||
|
||||
lastX1 = lastLine.rx1;
|
||||
lastY1 = lastLine.ry1;
|
||||
lastX2 = lastLine.rx2;
|
||||
lastY2 = lastLine.ry2;
|
||||
|
||||
for (int i = subPath.size() - 1; --i >= 0;)
|
||||
{
|
||||
auto& l = subPath.getReference (i);
|
||||
|
||||
addEdgeAndJoint (destPath, jointStyle,
|
||||
maxMiterExtensionSquared, width,
|
||||
lastX1, lastY1, lastX2, lastY2,
|
||||
l.rx1, l.ry1, l.rx2, l.ry2,
|
||||
l.x2, l.y2);
|
||||
|
||||
lastX1 = l.rx1;
|
||||
lastY1 = l.ry1;
|
||||
lastX2 = l.rx2;
|
||||
lastY2 = l.ry2;
|
||||
}
|
||||
|
||||
if (isClosed)
|
||||
{
|
||||
addEdgeAndJoint (destPath, jointStyle,
|
||||
maxMiterExtensionSquared, width,
|
||||
lastX1, lastY1, lastX2, lastY2,
|
||||
lastLine.rx1, lastLine.ry1, lastLine.rx2, lastLine.ry2,
|
||||
lastLine.x2, lastLine.y2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// do the last line
|
||||
destPath.lineTo (lastX2, lastY2);
|
||||
}
|
||||
|
||||
destPath.closeSubPath();
|
||||
}
|
||||
|
||||
static void createStroke (const float thickness, const PathStrokeType::JointStyle jointStyle,
|
||||
const PathStrokeType::EndCapStyle endStyle,
|
||||
Path& destPath, const Path& source,
|
||||
const AffineTransform& transform,
|
||||
const float extraAccuracy, const Arrowhead* const arrowhead)
|
||||
{
|
||||
jassert (extraAccuracy > 0);
|
||||
|
||||
if (thickness <= 0)
|
||||
{
|
||||
destPath.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const Path* sourcePath = &source;
|
||||
Path temp;
|
||||
|
||||
if (sourcePath == &destPath)
|
||||
{
|
||||
destPath.swapWithPath (temp);
|
||||
sourcePath = &temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
destPath.clear();
|
||||
}
|
||||
|
||||
destPath.setUsingNonZeroWinding (true);
|
||||
|
||||
const float maxMiterExtensionSquared = 9.0f * thickness * thickness;
|
||||
const float width = 0.5f * thickness;
|
||||
|
||||
// Iterate the path, creating a list of the
|
||||
// left/right-hand lines along either side of it...
|
||||
PathFlatteningIterator it (*sourcePath, transform, Path::defaultToleranceForMeasurement / extraAccuracy);
|
||||
|
||||
Array<LineSection> subPath;
|
||||
subPath.ensureStorageAllocated (512);
|
||||
LineSection l;
|
||||
l.x1 = 0;
|
||||
l.y1 = 0;
|
||||
|
||||
const float minSegmentLength = 0.0001f;
|
||||
|
||||
while (it.next())
|
||||
{
|
||||
if (it.subPathIndex == 0)
|
||||
{
|
||||
if (subPath.size() > 0)
|
||||
{
|
||||
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
|
||||
subPath.clearQuick();
|
||||
}
|
||||
|
||||
l.x1 = it.x1;
|
||||
l.y1 = it.y1;
|
||||
}
|
||||
|
||||
l.x2 = it.x2;
|
||||
l.y2 = it.y2;
|
||||
|
||||
float dx = l.x2 - l.x1;
|
||||
float dy = l.y2 - l.y1;
|
||||
|
||||
auto hypotSquared = dx * dx + dy * dy;
|
||||
|
||||
if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath())
|
||||
{
|
||||
auto len = std::sqrt (hypotSquared);
|
||||
|
||||
if (approximatelyEqual (len, 0.0f))
|
||||
{
|
||||
l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1;
|
||||
l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto offset = width / len;
|
||||
dx *= offset;
|
||||
dy *= offset;
|
||||
|
||||
l.rx2 = l.x1 - dy;
|
||||
l.ry2 = l.y1 + dx;
|
||||
l.lx1 = l.x1 + dy;
|
||||
l.ly1 = l.y1 - dx;
|
||||
|
||||
l.lx2 = l.x2 + dy;
|
||||
l.ly2 = l.y2 - dx;
|
||||
l.rx1 = l.x2 - dy;
|
||||
l.ry1 = l.y2 + dx;
|
||||
}
|
||||
|
||||
subPath.add (l);
|
||||
|
||||
if (it.closesSubPath)
|
||||
{
|
||||
addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
|
||||
subPath.clearQuick();
|
||||
}
|
||||
else
|
||||
{
|
||||
l.x1 = it.x2;
|
||||
l.y1 = it.y2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subPath.size() > 0)
|
||||
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
|
||||
}
|
||||
}
|
||||
|
||||
void PathStrokeType::createStrokedPath (Path& destPath, const Path& sourcePath,
|
||||
const AffineTransform& transform, float extraAccuracy) const
|
||||
{
|
||||
PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath,
|
||||
transform, extraAccuracy, nullptr);
|
||||
}
|
||||
|
||||
void PathStrokeType::createDashedStroke (Path& destPath,
|
||||
const Path& sourcePath,
|
||||
const float* dashLengths,
|
||||
int numDashLengths,
|
||||
const AffineTransform& transform,
|
||||
float extraAccuracy) const
|
||||
{
|
||||
jassert (extraAccuracy > 0);
|
||||
|
||||
if (thickness <= 0)
|
||||
return;
|
||||
|
||||
Path newDestPath;
|
||||
PathFlatteningIterator it (sourcePath, transform, Path::defaultToleranceForMeasurement / extraAccuracy);
|
||||
|
||||
bool first = true;
|
||||
int dashNum = 0;
|
||||
float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f;
|
||||
float dx = 0.0f, dy = 0.0f;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const bool isSolid = ((dashNum & 1) == 0);
|
||||
const float dashLen = dashLengths [dashNum++ % numDashLengths];
|
||||
|
||||
jassert (dashLen >= 0); // must be a positive increment!
|
||||
if (dashLen <= 0)
|
||||
continue;
|
||||
|
||||
pos += dashLen;
|
||||
|
||||
while (pos > lineEndPos)
|
||||
{
|
||||
if (! it.next())
|
||||
{
|
||||
if (isSolid && ! first)
|
||||
newDestPath.lineTo (it.x2, it.y2);
|
||||
|
||||
createStrokedPath (destPath, newDestPath, AffineTransform(), extraAccuracy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSolid && ! first)
|
||||
newDestPath.lineTo (it.x1, it.y1);
|
||||
else
|
||||
newDestPath.startNewSubPath (it.x1, it.y1);
|
||||
|
||||
dx = it.x2 - it.x1;
|
||||
dy = it.y2 - it.y1;
|
||||
lineLen = juce_hypot (dx, dy);
|
||||
lineEndPos += lineLen;
|
||||
first = it.closesSubPath;
|
||||
}
|
||||
|
||||
const float alpha = (pos - (lineEndPos - lineLen)) / lineLen;
|
||||
|
||||
if (isSolid)
|
||||
newDestPath.lineTo (it.x1 + dx * alpha,
|
||||
it.y1 + dy * alpha);
|
||||
else
|
||||
newDestPath.startNewSubPath (it.x1 + dx * alpha,
|
||||
it.y1 + dy * alpha);
|
||||
}
|
||||
}
|
||||
|
||||
void PathStrokeType::createStrokeWithArrowheads (Path& destPath,
|
||||
const Path& sourcePath,
|
||||
const float arrowheadStartWidth, const float arrowheadStartLength,
|
||||
const float arrowheadEndWidth, const float arrowheadEndLength,
|
||||
const AffineTransform& transform,
|
||||
const float extraAccuracy) const
|
||||
{
|
||||
PathStrokeHelpers::Arrowhead head;
|
||||
head.startWidth = arrowheadStartWidth;
|
||||
head.startLength = arrowheadStartLength;
|
||||
head.endWidth = arrowheadEndWidth;
|
||||
head.endLength = arrowheadEndLength;
|
||||
|
||||
PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle,
|
||||
destPath, sourcePath, transform, extraAccuracy, &head);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Describes a type of stroke used to render a solid outline along a path.
|
||||
|
||||
A PathStrokeType object can be used directly to create the shape of an outline
|
||||
around a path, and is used by Graphics::strokePath to specify the type of
|
||||
stroke to draw.
|
||||
|
||||
@see Path, Graphics::strokePath
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API PathStrokeType
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** The type of shape to use for the corners between two adjacent line segments. */
|
||||
enum JointStyle
|
||||
{
|
||||
mitered, /**< Indicates that corners should be drawn with sharp joints.
|
||||
Note that for angles that curve back on themselves, drawing a
|
||||
mitre could require extending the point too far away from the
|
||||
path, so a mitre limit is imposed and any corners that exceed it
|
||||
are drawn as bevelled instead. */
|
||||
curved, /**< Indicates that corners should be drawn as rounded-off. */
|
||||
beveled /**< Indicates that corners should be drawn with a line flattening their
|
||||
outside edge. */
|
||||
};
|
||||
|
||||
/** The type shape to use for the ends of lines. */
|
||||
enum EndCapStyle
|
||||
{
|
||||
butt, /**< Ends of lines are flat and don't extend beyond the end point. */
|
||||
square, /**< Ends of lines are flat, but stick out beyond the end point for half
|
||||
the thickness of the stroke. */
|
||||
rounded /**< Ends of lines are rounded-off with a circular shape. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a stroke type with a given line-width, and default joint/end styles. */
|
||||
explicit PathStrokeType (float strokeThickness) noexcept;
|
||||
|
||||
/** Creates a stroke type.
|
||||
|
||||
@param strokeThickness the width of the line to use
|
||||
@param jointStyle the type of joints to use for corners
|
||||
@param endStyle the type of end-caps to use for the ends of open paths.
|
||||
*/
|
||||
PathStrokeType (float strokeThickness,
|
||||
JointStyle jointStyle,
|
||||
EndCapStyle endStyle = butt) noexcept;
|
||||
|
||||
/** Creates a copy of another stroke type. */
|
||||
PathStrokeType (const PathStrokeType&) noexcept;
|
||||
|
||||
/** Copies another stroke onto this one. */
|
||||
PathStrokeType& operator= (const PathStrokeType&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~PathStrokeType() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Applies this stroke type to a path and returns the resultant stroke as another Path.
|
||||
|
||||
@param destPath the resultant stroked outline shape will be copied into this path.
|
||||
Note that it's ok for the source and destination Paths to be
|
||||
the same object, so you can easily turn a path into a stroked version
|
||||
of itself.
|
||||
@param sourcePath the path to use as the source
|
||||
@param transform an optional transform to apply to the points from the source path
|
||||
as they are being used
|
||||
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to
|
||||
a higher resolution, which improves the quality if you'll later want
|
||||
to enlarge the stroked path. So for example, if you're planning on drawing
|
||||
the stroke at 3x the size that you're creating it, you should set this to 3.
|
||||
|
||||
@see createDashedStroke
|
||||
*/
|
||||
void createStrokedPath (Path& destPath,
|
||||
const Path& sourcePath,
|
||||
const AffineTransform& transform = AffineTransform(),
|
||||
float extraAccuracy = 1.0f) const;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Applies this stroke type to a path, creating a dashed line.
|
||||
|
||||
This is similar to createStrokedPath, but uses the array passed in to
|
||||
break the stroke up into a series of dashes.
|
||||
|
||||
@param destPath the resultant stroked outline shape will be copied into this path.
|
||||
Note that it's ok for the source and destination Paths to be
|
||||
the same object, so you can easily turn a path into a stroked version
|
||||
of itself.
|
||||
@param sourcePath the path to use as the source
|
||||
@param dashLengths An array of alternating on/off lengths. E.g. { 2, 3, 4, 5 } will create
|
||||
a line of length 2, then skip a length of 3, then add a line of length 4,
|
||||
skip 5, and keep repeating this pattern.
|
||||
@param numDashLengths The number of lengths in the dashLengths array. This should really be
|
||||
an even number, otherwise the pattern will get out of step as it
|
||||
repeats.
|
||||
@param transform an optional transform to apply to the points from the source path
|
||||
as they are being used
|
||||
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to
|
||||
a higher resolution, which improves the quality if you'll later want
|
||||
to enlarge the stroked path. So for example, if you're planning on drawing
|
||||
the stroke at 3x the size that you're creating it, you should set this to 3.
|
||||
*/
|
||||
void createDashedStroke (Path& destPath,
|
||||
const Path& sourcePath,
|
||||
const float* dashLengths,
|
||||
int numDashLengths,
|
||||
const AffineTransform& transform = AffineTransform(),
|
||||
float extraAccuracy = 1.0f) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Applies this stroke type to a path and returns the resultant stroke as another Path.
|
||||
|
||||
@param destPath the resultant stroked outline shape will be copied into this path.
|
||||
Note that it's ok for the source and destination Paths to be
|
||||
the same object, so you can easily turn a path into a stroked version
|
||||
of itself.
|
||||
@param sourcePath the path to use as the source
|
||||
@param arrowheadStartWidth the width of the arrowhead at the start of the path
|
||||
@param arrowheadStartLength the length of the arrowhead at the start of the path
|
||||
@param arrowheadEndWidth the width of the arrowhead at the end of the path
|
||||
@param arrowheadEndLength the length of the arrowhead at the end of the path
|
||||
@param transform an optional transform to apply to the points from the source path
|
||||
as they are being used
|
||||
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to
|
||||
a higher resolution, which improves the quality if you'll later want
|
||||
to enlarge the stroked path. So for example, if you're planning on drawing
|
||||
the stroke at 3x the size that you're creating it, you should set this to 3.
|
||||
@see createDashedStroke
|
||||
*/
|
||||
void createStrokeWithArrowheads (Path& destPath,
|
||||
const Path& sourcePath,
|
||||
float arrowheadStartWidth, float arrowheadStartLength,
|
||||
float arrowheadEndWidth, float arrowheadEndLength,
|
||||
const AffineTransform& transform = AffineTransform(),
|
||||
float extraAccuracy = 1.0f) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the stroke thickness. */
|
||||
float getStrokeThickness() const noexcept { return thickness; }
|
||||
|
||||
/** Sets the stroke thickness. */
|
||||
void setStrokeThickness (float newThickness) noexcept { thickness = newThickness; }
|
||||
|
||||
/** Returns the joint style. */
|
||||
JointStyle getJointStyle() const noexcept { return jointStyle; }
|
||||
|
||||
/** Sets the joint style. */
|
||||
void setJointStyle (JointStyle newStyle) noexcept { jointStyle = newStyle; }
|
||||
|
||||
/** Returns the end-cap style. */
|
||||
EndCapStyle getEndStyle() const noexcept { return endStyle; }
|
||||
|
||||
/** Sets the end-cap style. */
|
||||
void setEndStyle (EndCapStyle newStyle) noexcept { endStyle = newStyle; }
|
||||
|
||||
//==============================================================================
|
||||
/** Compares the stroke thickness, joint and end styles of two stroke types. */
|
||||
bool operator== (const PathStrokeType&) const noexcept;
|
||||
|
||||
/** Compares the stroke thickness, joint and end styles of two stroke types. */
|
||||
bool operator!= (const PathStrokeType&) const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
float thickness;
|
||||
JointStyle jointStyle;
|
||||
EndCapStyle endStyle;
|
||||
|
||||
JUCE_LEAK_DETECTOR (PathStrokeType)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A pair of (x, y) coordinates.
|
||||
|
||||
The ValueType template should be a primitive type such as int, float, double,
|
||||
rather than a class.
|
||||
|
||||
@see Line, Path, AffineTransform
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
template <typename ValueType>
|
||||
class Point
|
||||
{
|
||||
public:
|
||||
/** Creates a point at the origin */
|
||||
constexpr Point() = default;
|
||||
|
||||
/** Creates a copy of another point. */
|
||||
constexpr Point (const Point&) = default;
|
||||
|
||||
/** Creates a point from an (x, y) position. */
|
||||
constexpr Point (ValueType initialX, ValueType initialY) noexcept : x (initialX), y (initialY) {}
|
||||
|
||||
//==============================================================================
|
||||
/** Copies this point from another one. */
|
||||
Point& operator= (const Point&) = default;
|
||||
|
||||
constexpr inline bool operator== (Point other) const noexcept
|
||||
{
|
||||
const auto tie = [] (const Point& p) { return std::tie (p.x, p.y); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
constexpr inline bool operator!= (Point other) const noexcept { return ! operator== (other); }
|
||||
|
||||
/** Returns true if the point is (0, 0). */
|
||||
constexpr bool isOrigin() const noexcept { return operator== (Point()); }
|
||||
|
||||
/** Returns true if the coordinates are finite values. */
|
||||
constexpr inline bool isFinite() const noexcept { return juce_isfinite (x) && juce_isfinite (y); }
|
||||
|
||||
/** Returns the point's x coordinate. */
|
||||
constexpr inline ValueType getX() const noexcept { return x; }
|
||||
|
||||
/** Returns the point's y coordinate. */
|
||||
constexpr inline ValueType getY() const noexcept { return y; }
|
||||
|
||||
/** Sets the point's x coordinate. */
|
||||
inline void setX (ValueType newX) noexcept { x = newX; }
|
||||
|
||||
/** Sets the point's y coordinate. */
|
||||
inline void setY (ValueType newY) noexcept { y = newY; }
|
||||
|
||||
/** Returns a point which has the same Y position as this one, but a new X. */
|
||||
constexpr Point withX (ValueType newX) const noexcept { return Point (newX, y); }
|
||||
|
||||
/** Returns a point which has the same X position as this one, but a new Y. */
|
||||
constexpr Point withY (ValueType newY) const noexcept { return Point (x, newY); }
|
||||
|
||||
/** Changes the point's x and y coordinates. */
|
||||
void setXY (ValueType newX, ValueType newY) noexcept { x = newX; y = newY; }
|
||||
|
||||
/** Adds a pair of coordinates to this value. */
|
||||
void addXY (ValueType xToAdd, ValueType yToAdd) noexcept { x += xToAdd; y += yToAdd; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a point with a given offset from this one. */
|
||||
constexpr Point translated (ValueType deltaX, ValueType deltaY) const noexcept { return Point (x + deltaX, y + deltaY); }
|
||||
|
||||
/** Adds two points together */
|
||||
constexpr Point operator+ (Point other) const noexcept { return Point (x + other.x, y + other.y); }
|
||||
|
||||
/** Adds another point's coordinates to this one */
|
||||
Point& operator+= (Point other) noexcept { x += other.x; y += other.y; return *this; }
|
||||
|
||||
/** Subtracts one points from another */
|
||||
constexpr Point operator- (Point other) const noexcept { return Point (x - other.x, y - other.y); }
|
||||
|
||||
/** Subtracts another point's coordinates to this one */
|
||||
Point& operator-= (Point other) noexcept { x -= other.x; y -= other.y; return *this; }
|
||||
|
||||
/** Multiplies two points together */
|
||||
template <typename OtherType>
|
||||
constexpr Point operator* (Point<OtherType> other) const noexcept { return Point ((ValueType) (x * other.x), (ValueType) (y * other.y)); }
|
||||
|
||||
/** Multiplies another point's coordinates to this one */
|
||||
template <typename OtherType>
|
||||
Point& operator*= (Point<OtherType> other) noexcept { *this = *this * other; return *this; }
|
||||
|
||||
/** Divides one point by another */
|
||||
template <typename OtherType>
|
||||
constexpr Point operator/ (Point<OtherType> other) const noexcept { return Point ((ValueType) (x / other.x), (ValueType) (y / other.y)); }
|
||||
|
||||
/** Divides this point's coordinates by another */
|
||||
template <typename OtherType>
|
||||
Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; }
|
||||
|
||||
/** Returns a point whose coordinates are multiplied by a given scalar value. */
|
||||
template <typename OtherType>
|
||||
constexpr Point operator* (OtherType multiplier) const noexcept
|
||||
{
|
||||
using CommonType = std::common_type_t<ValueType, OtherType>;
|
||||
return Point ((ValueType) ((CommonType) x * (CommonType) multiplier),
|
||||
(ValueType) ((CommonType) y * (CommonType) multiplier));
|
||||
}
|
||||
|
||||
/** Returns a point whose coordinates are divided by a given scalar value. */
|
||||
template <typename OtherType>
|
||||
constexpr Point operator/ (OtherType divisor) const noexcept
|
||||
{
|
||||
using CommonType = std::common_type_t<ValueType, OtherType>;
|
||||
return Point ((ValueType) ((CommonType) x / (CommonType) divisor),
|
||||
(ValueType) ((CommonType) y / (CommonType) divisor));
|
||||
}
|
||||
|
||||
/** Multiplies the point's coordinates by a scalar value. */
|
||||
template <typename FloatType>
|
||||
Point& operator*= (FloatType multiplier) noexcept { x = (ValueType) (x * multiplier); y = (ValueType) (y * multiplier); return *this; }
|
||||
|
||||
/** Divides the point's coordinates by a scalar value. */
|
||||
template <typename FloatType>
|
||||
Point& operator/= (FloatType divisor) noexcept { x = (ValueType) (x / divisor); y = (ValueType) (y / divisor); return *this; }
|
||||
|
||||
/** Returns the inverse of this point. */
|
||||
constexpr Point operator-() const noexcept { return Point (-x, -y); }
|
||||
|
||||
//==============================================================================
|
||||
/** This type will be double if the Point's type is double, otherwise it will be float. */
|
||||
using FloatType = TypeHelpers::SmallestFloatType<ValueType>;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the straight-line distance between this point and the origin. */
|
||||
ValueType getDistanceFromOrigin() const noexcept { return juce_hypot (x, y); }
|
||||
|
||||
/** Returns the straight-line distance between this point and another one. */
|
||||
ValueType getDistanceFrom (Point other) const noexcept { return juce_hypot (x - other.x, y - other.y); }
|
||||
|
||||
/** Returns the square of the straight-line distance between this point and the origin. */
|
||||
constexpr ValueType getDistanceSquaredFromOrigin() const noexcept { return x * x + y * y; }
|
||||
|
||||
/** Returns the square of the straight-line distance between this point and another one. */
|
||||
constexpr ValueType getDistanceSquaredFrom (Point other) const noexcept { return (*this - other).getDistanceSquaredFromOrigin(); }
|
||||
|
||||
/** Returns the angle from this point to another one.
|
||||
|
||||
Taking this point to be the centre of a circle, and the other point being a position on
|
||||
the circumference, the return value is the number of radians clockwise from the 12 o'clock
|
||||
direction.
|
||||
So 12 o'clock = 0, 3 o'clock = Pi/2, 6 o'clock = Pi, 9 o'clock = -Pi/2
|
||||
*/
|
||||
FloatType getAngleToPoint (Point other) const noexcept
|
||||
{
|
||||
return static_cast<FloatType> (std::atan2 (static_cast<FloatType> (other.x - x),
|
||||
static_cast<FloatType> (y - other.y)));
|
||||
}
|
||||
|
||||
/** Returns the point that would be reached by rotating this point clockwise
|
||||
about the origin by the specified angle.
|
||||
*/
|
||||
Point rotatedAboutOrigin (ValueType angleRadians) const noexcept
|
||||
{
|
||||
return Point (x * std::cos (angleRadians) - y * std::sin (angleRadians),
|
||||
x * std::sin (angleRadians) + y * std::cos (angleRadians));
|
||||
}
|
||||
|
||||
/** Taking this point to be the centre of a circle, this returns a point on its circumference.
|
||||
@param radius the radius of the circle.
|
||||
@param angle the angle of the point, in radians clockwise from the 12 o'clock position.
|
||||
*/
|
||||
Point<FloatType> getPointOnCircumference (float radius, float angle) const noexcept
|
||||
{
|
||||
return Point<FloatType> (static_cast<FloatType> (x + radius * std::sin (angle)),
|
||||
static_cast<FloatType> (y - radius * std::cos (angle)));
|
||||
}
|
||||
|
||||
/** Taking this point to be the centre of an ellipse, this returns a point on its circumference.
|
||||
@param radiusX the horizontal radius of the circle.
|
||||
@param radiusY the vertical radius of the circle.
|
||||
@param angle the angle of the point, in radians clockwise from the 12 o'clock position.
|
||||
*/
|
||||
Point<FloatType> getPointOnCircumference (float radiusX, float radiusY, float angle) const noexcept
|
||||
{
|
||||
return Point<FloatType> (static_cast<FloatType> (x + radiusX * std::sin (angle)),
|
||||
static_cast<FloatType> (y - radiusY * std::cos (angle)));
|
||||
}
|
||||
|
||||
/** Returns the dot-product of two points (x1 * x2 + y1 * y2). */
|
||||
constexpr FloatType getDotProduct (Point other) const noexcept { return x * other.x + y * other.y; }
|
||||
|
||||
//==============================================================================
|
||||
/** Uses a transform to change the point's coordinates.
|
||||
This will only compile if ValueType = float!
|
||||
|
||||
@see AffineTransform::transformPoint
|
||||
*/
|
||||
void applyTransform (const AffineTransform& transform) noexcept { transform.transformPoint (x, y); }
|
||||
|
||||
/** Returns the position of this point, if it is transformed by a given AffineTransform. */
|
||||
Point transformedBy (const AffineTransform& transform) const noexcept
|
||||
{
|
||||
return Point (static_cast<ValueType> (transform.mat00 * (float) x + transform.mat01 * (float) y + transform.mat02),
|
||||
static_cast<ValueType> (transform.mat10 * (float) x + transform.mat11 * (float) y + transform.mat12));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Casts this point to a Point<int> object. */
|
||||
constexpr Point<int> toInt() const noexcept { return Point<int> (static_cast<int> (x), static_cast<int> (y)); }
|
||||
|
||||
/** Casts this point to a Point<float> object. */
|
||||
constexpr Point<float> toFloat() const noexcept { return Point<float> (static_cast<float> (x), static_cast<float> (y)); }
|
||||
|
||||
/** Casts this point to a Point<double> object. */
|
||||
constexpr Point<double> toDouble() const noexcept { return Point<double> (static_cast<double> (x), static_cast<double> (y)); }
|
||||
|
||||
/** Casts this point to a Point<int> object using roundToInt() to convert the values. */
|
||||
constexpr Point<int> roundToInt() const noexcept { return Point<int> (juce::roundToInt (x), juce::roundToInt (y)); }
|
||||
|
||||
/** Returns the point as a string in the form "x, y". */
|
||||
String toString() const { return String (x) + ", " + String (y); }
|
||||
|
||||
//==============================================================================
|
||||
ValueType x{}; /**< The point's X coordinate. */
|
||||
ValueType y{}; /**< The point's Y coordinate. */
|
||||
};
|
||||
|
||||
/** Multiplies the point's coordinates by a scalar value. */
|
||||
template <typename ValueType>
|
||||
Point<ValueType> operator* (ValueType value, Point<ValueType> p) noexcept { return p * value; }
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,685 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Maintains a set of rectangles as a complex region.
|
||||
|
||||
This class allows a set of rectangles to be treated as a solid shape, and can
|
||||
add and remove rectangular sections of it, and simplify overlapping or
|
||||
adjacent rectangles.
|
||||
|
||||
@see Rectangle
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
template <typename ValueType>
|
||||
class RectangleList final
|
||||
{
|
||||
public:
|
||||
using RectangleType = Rectangle<ValueType>;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an empty RectangleList */
|
||||
RectangleList() = default;
|
||||
|
||||
/** Creates a copy of another list */
|
||||
RectangleList (const RectangleList& other) : rects (other.rects)
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a list containing just one rectangle. */
|
||||
RectangleList (RectangleType rect)
|
||||
{
|
||||
addWithoutMerging (rect);
|
||||
}
|
||||
|
||||
/** Copies this list from another one. */
|
||||
RectangleList& operator= (const RectangleList& other)
|
||||
{
|
||||
rects = other.rects;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Move constructor */
|
||||
RectangleList (RectangleList&& other) noexcept
|
||||
: rects (std::move (other.rects))
|
||||
{
|
||||
}
|
||||
|
||||
/** Move assignment operator */
|
||||
RectangleList& operator= (RectangleList&& other) noexcept
|
||||
{
|
||||
rects = std::move (other.rects);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the region is empty. */
|
||||
bool isEmpty() const noexcept { return rects.isEmpty(); }
|
||||
|
||||
/** Returns the number of rectangles in the list. */
|
||||
int getNumRectangles() const noexcept { return rects.size(); }
|
||||
|
||||
/** Returns one of the rectangles at a particular index.
|
||||
@returns the rectangle at the index, or an empty rectangle if the index is out-of-range.
|
||||
*/
|
||||
RectangleType getRectangle (int index) const noexcept { return rects[index]; }
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all rectangles to leave an empty region. */
|
||||
void clear()
|
||||
{
|
||||
rects.clearQuick();
|
||||
}
|
||||
|
||||
/** Merges a new rectangle into the list.
|
||||
|
||||
The rectangle being added will first be clipped to remove any parts of it
|
||||
that overlap existing rectangles in the list, and adjacent rectangles will be
|
||||
merged into it.
|
||||
|
||||
The rectangle can have any size and may be empty, but if it's floating point
|
||||
then it's expected to not contain any INF values.
|
||||
*/
|
||||
void add (RectangleType rect)
|
||||
{
|
||||
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
||||
|
||||
if (! rect.isEmpty())
|
||||
{
|
||||
if (isEmpty())
|
||||
{
|
||||
rects.add (rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool anyOverlaps = false;
|
||||
|
||||
for (int j = rects.size(); --j >= 0;)
|
||||
{
|
||||
auto& ourRect = rects.getReference (j);
|
||||
|
||||
if (rect.intersects (ourRect))
|
||||
{
|
||||
if (rect.contains (ourRect))
|
||||
rects.remove (j);
|
||||
else if (! ourRect.reduceIfPartlyContainedIn (rect))
|
||||
anyOverlaps = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyOverlaps && ! isEmpty())
|
||||
{
|
||||
RectangleList r (rect);
|
||||
|
||||
for (auto& ourRect : rects)
|
||||
{
|
||||
if (rect.intersects (ourRect))
|
||||
{
|
||||
r.subtract (ourRect);
|
||||
|
||||
if (r.isEmpty())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rects.addArray (r.rects);
|
||||
}
|
||||
else
|
||||
{
|
||||
rects.add (rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges a new rectangle into the list.
|
||||
|
||||
The rectangle being added will first be clipped to remove any parts of it
|
||||
that overlap existing rectangles in the list.
|
||||
*/
|
||||
void add (ValueType x, ValueType y, ValueType width, ValueType height)
|
||||
{
|
||||
add (RectangleType (x, y, width, height));
|
||||
}
|
||||
|
||||
/** Dumbly adds a rectangle to the list without checking for overlaps.
|
||||
|
||||
This simply adds the rectangle to the end, it doesn't merge it or remove
|
||||
any overlapping bits.
|
||||
|
||||
The rectangle can have any size and may be empty, but if it's floating point
|
||||
then it's expected to not contain any INF values.
|
||||
*/
|
||||
void addWithoutMerging (RectangleType rect)
|
||||
{
|
||||
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
||||
|
||||
if (! rect.isEmpty())
|
||||
rects.add (rect);
|
||||
}
|
||||
|
||||
/** Merges another rectangle list into this one.
|
||||
|
||||
Any overlaps between the two lists will be clipped, so that the result is
|
||||
the union of both lists.
|
||||
*/
|
||||
void add (const RectangleList& other)
|
||||
{
|
||||
for (auto& r : other)
|
||||
add (r);
|
||||
}
|
||||
|
||||
/** Removes a rectangular region from the list.
|
||||
|
||||
Any rectangles in the list which overlap this will be clipped and subdivided
|
||||
if necessary.
|
||||
*/
|
||||
void subtract (const RectangleType rect)
|
||||
{
|
||||
if (auto numRects = rects.size())
|
||||
{
|
||||
const auto x1 = rect.getX();
|
||||
const auto y1 = rect.getY();
|
||||
const auto x2 = x1 + rect.getWidth();
|
||||
const auto y2 = y1 + rect.getHeight();
|
||||
|
||||
for (int i = numRects; --i >= 0;)
|
||||
{
|
||||
auto& r = rects.getReference (i);
|
||||
|
||||
const auto rx1 = r.getX();
|
||||
const auto ry1 = r.getY();
|
||||
const auto rx2 = rx1 + r.getWidth();
|
||||
const auto ry2 = ry1 + r.getHeight();
|
||||
|
||||
const auto isNotEqual = [&] (const RectangleType newRect)
|
||||
{
|
||||
// When subtracting tiny slices from relatively large rectangles, the
|
||||
// subtraction may have no effect (due to limited-precision floating point
|
||||
// maths) and the original rectangle may remain unchanged.
|
||||
// We check that any 'new' rectangle has different dimensions to the rectangle
|
||||
// being tested before adding it to the rects array.
|
||||
// Integer arithmetic is not susceptible to this problem, so there's no need
|
||||
// for this additional equality check when working with integral rectangles.
|
||||
if constexpr (std::is_floating_point_v<ValueType>)
|
||||
{
|
||||
return newRect != r;
|
||||
}
|
||||
else
|
||||
{
|
||||
ignoreUnused (newRect);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if (rx1 < x2 && x1 < rx2 && ry1 < y2 && y1 < ry2)
|
||||
{
|
||||
if (rx1 < x1 && x1 < rx2)
|
||||
{
|
||||
if (y1 <= ry1 && ry2 <= y2 && rx2 <= x2)
|
||||
{
|
||||
r.setWidth (x1 - rx1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const RectangleType newRect (rx1, ry1, x1 - rx1, ry2 - ry1); isNotEqual (newRect))
|
||||
{
|
||||
r.setX (x1);
|
||||
r.setWidth (rx2 - x1);
|
||||
|
||||
rects.insert (++i, newRect);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rx1 < x2 && x2 < rx2)
|
||||
{
|
||||
r.setX (x2);
|
||||
r.setWidth (rx2 - x2);
|
||||
|
||||
if (ry1 < y1 || y2 < ry2 || rx1 < x1)
|
||||
{
|
||||
if (const RectangleType newRect (rx1, ry1, x2 - rx1, ry2 - ry1); isNotEqual (newRect))
|
||||
{
|
||||
rects.insert (++i, newRect);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ry1 < y1 && y1 < ry2)
|
||||
{
|
||||
if (x1 <= rx1 && rx2 <= x2 && ry2 <= y2)
|
||||
{
|
||||
r.setHeight (y1 - ry1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const RectangleType newRect (rx1, ry1, rx2 - rx1, y1 - ry1); isNotEqual (newRect))
|
||||
{
|
||||
r.setY (y1);
|
||||
r.setHeight (ry2 - y1);
|
||||
|
||||
rects.insert (++i, newRect);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ry1 < y2 && y2 < ry2)
|
||||
{
|
||||
r.setY (y2);
|
||||
r.setHeight (ry2 - y2);
|
||||
|
||||
if (rx1 < x1 || x2 < rx2 || ry1 < y1)
|
||||
{
|
||||
if (const RectangleType newRect (rx1, ry1, rx2 - rx1, y2 - ry1); isNotEqual (newRect))
|
||||
{
|
||||
rects.insert (++i, newRect);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rects.remove (i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes all areas in another RectangleList from this one.
|
||||
|
||||
Any rectangles in the list which overlap this will be clipped and subdivided
|
||||
if necessary.
|
||||
|
||||
@returns true if the resulting list is non-empty.
|
||||
*/
|
||||
bool subtract (const RectangleList& otherList)
|
||||
{
|
||||
for (auto& r : otherList)
|
||||
{
|
||||
if (isEmpty())
|
||||
return false;
|
||||
|
||||
subtract (r);
|
||||
}
|
||||
|
||||
return ! isEmpty();
|
||||
}
|
||||
|
||||
/** Removes any areas of the region that lie outside a given rectangle.
|
||||
|
||||
Any rectangles in the list which overlap this will be clipped and subdivided
|
||||
if necessary.
|
||||
|
||||
Returns true if the resulting region is not empty, false if it is empty.
|
||||
|
||||
@see getIntersectionWith
|
||||
*/
|
||||
bool clipTo (RectangleType rect)
|
||||
{
|
||||
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
||||
|
||||
bool notEmpty = false;
|
||||
|
||||
if (rect.isEmpty())
|
||||
{
|
||||
clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = rects.size(); --i >= 0;)
|
||||
{
|
||||
auto& r = rects.getReference (i);
|
||||
|
||||
if (! rect.intersectRectangle (r))
|
||||
rects.remove (i);
|
||||
else
|
||||
notEmpty = true;
|
||||
}
|
||||
}
|
||||
|
||||
return notEmpty;
|
||||
}
|
||||
|
||||
/** Removes any areas of the region that lie outside a given rectangle list.
|
||||
|
||||
Any rectangles in this object which overlap the specified list will be clipped
|
||||
and subdivided if necessary.
|
||||
|
||||
Returns true if the resulting region is not empty, false if it is empty.
|
||||
|
||||
@see getIntersectionWith
|
||||
*/
|
||||
template <typename OtherValueType>
|
||||
bool clipTo (const RectangleList<OtherValueType>& other)
|
||||
{
|
||||
if (isEmpty())
|
||||
return false;
|
||||
|
||||
RectangleList result;
|
||||
|
||||
for (auto& rect : rects)
|
||||
{
|
||||
for (auto& r : other)
|
||||
{
|
||||
auto clipped = r.template toType<ValueType>();
|
||||
|
||||
if (rect.intersectRectangle (clipped))
|
||||
result.rects.add (clipped);
|
||||
}
|
||||
}
|
||||
|
||||
swapWith (result);
|
||||
return ! isEmpty();
|
||||
}
|
||||
|
||||
/** Creates a region which is the result of clipping this one to a given rectangle.
|
||||
|
||||
Unlike the other clipTo method, this one doesn't affect this object - it puts the
|
||||
resulting region into the list whose reference is passed-in.
|
||||
|
||||
Returns true if the resulting region is not empty, false if it is empty.
|
||||
|
||||
@see clipTo
|
||||
*/
|
||||
bool getIntersectionWith (RectangleType rect, RectangleList& destRegion) const
|
||||
{
|
||||
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
||||
|
||||
destRegion.clear();
|
||||
|
||||
if (! rect.isEmpty())
|
||||
for (auto r : rects)
|
||||
if (rect.intersectRectangle (r))
|
||||
destRegion.rects.add (r);
|
||||
|
||||
return ! destRegion.isEmpty();
|
||||
}
|
||||
|
||||
/** Swaps the contents of this and another list.
|
||||
|
||||
This swaps their internal pointers, so is hugely faster than using copy-by-value
|
||||
to swap them.
|
||||
*/
|
||||
void swapWith (RectangleList& otherList) noexcept
|
||||
{
|
||||
rects.swapWith (otherList.rects);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether the region contains a given point.
|
||||
@returns true if the point lies within one of the rectangles in the list
|
||||
*/
|
||||
bool containsPoint (Point<ValueType> point) const noexcept
|
||||
{
|
||||
for (auto& r : rects)
|
||||
if (r.contains (point))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks whether the region contains a given point.
|
||||
@returns true if the point lies within one of the rectangles in the list
|
||||
*/
|
||||
bool containsPoint (ValueType x, ValueType y) const noexcept
|
||||
{
|
||||
return containsPoint (Point<ValueType> (x, y));
|
||||
}
|
||||
|
||||
/** Checks whether the region contains the whole of a given rectangle.
|
||||
|
||||
@returns true all parts of the rectangle passed in lie within the region
|
||||
defined by this object
|
||||
@see intersectsRectangle, containsPoint
|
||||
*/
|
||||
bool containsRectangle (RectangleType rectangleToCheck) const
|
||||
{
|
||||
if (rects.size() > 1)
|
||||
{
|
||||
RectangleList r (rectangleToCheck);
|
||||
|
||||
for (auto& rect : rects)
|
||||
{
|
||||
r.subtract (rect);
|
||||
|
||||
if (r.isEmpty())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (! isEmpty())
|
||||
{
|
||||
return rects.getReference (0).contains (rectangleToCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks whether the region contains any part of a given rectangle.
|
||||
|
||||
@returns true if any part of the rectangle passed in lies within the region
|
||||
defined by this object
|
||||
@see containsRectangle
|
||||
*/
|
||||
bool intersectsRectangle (RectangleType rectangleToCheck) const noexcept
|
||||
{
|
||||
for (auto& r : rects)
|
||||
if (r.intersects (rectangleToCheck))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks whether this region intersects any part of another one.
|
||||
@see intersectsRectangle
|
||||
*/
|
||||
bool intersects (const RectangleList& other) const noexcept
|
||||
{
|
||||
for (auto& r : rects)
|
||||
if (other.intersectsRectangle (r))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the smallest rectangle that can enclose the whole of this region. */
|
||||
RectangleType getBounds() const noexcept
|
||||
{
|
||||
if (isEmpty())
|
||||
return {};
|
||||
|
||||
auto& r = rects.getReference (0);
|
||||
|
||||
if (rects.size() == 1)
|
||||
return r;
|
||||
|
||||
auto minX = r.getX();
|
||||
auto minY = r.getY();
|
||||
auto maxX = minX + r.getWidth();
|
||||
auto maxY = minY + r.getHeight();
|
||||
|
||||
for (int i = rects.size(); --i > 0;)
|
||||
{
|
||||
auto& r2 = rects.getReference (i);
|
||||
|
||||
minX = jmin (minX, r2.getX());
|
||||
minY = jmin (minY, r2.getY());
|
||||
maxX = jmax (maxX, r2.getRight());
|
||||
maxY = jmax (maxY, r2.getBottom());
|
||||
}
|
||||
|
||||
return { minX, minY, maxX - minX, maxY - minY };
|
||||
}
|
||||
|
||||
/** Optimises the list into a minimum number of constituent rectangles.
|
||||
|
||||
This will try to combine any adjacent rectangles into larger ones where
|
||||
possible, to simplify lists that might have been fragmented by repeated
|
||||
add/subtract calls.
|
||||
*/
|
||||
void consolidate()
|
||||
{
|
||||
for (int i = 0; i < rects.size() - 1; ++i)
|
||||
{
|
||||
auto& r = rects.getReference (i);
|
||||
auto rx1 = r.getX();
|
||||
auto ry1 = r.getY();
|
||||
auto rx2 = rx1 + r.getWidth();
|
||||
auto ry2 = ry1 + r.getHeight();
|
||||
|
||||
for (int j = rects.size(); --j > i;)
|
||||
{
|
||||
auto& r2 = rects.getReference (j);
|
||||
auto jrx1 = r2.getX();
|
||||
auto jry1 = r2.getY();
|
||||
auto jrx2 = jrx1 + r2.getWidth();
|
||||
auto jry2 = jry1 + r2.getHeight();
|
||||
|
||||
// if the vertical edges of any blocks are touching and their horizontals don't
|
||||
// line up, split them horizontally..
|
||||
if (jrx1 == rx2 || jrx2 == rx1)
|
||||
{
|
||||
if (jry1 > ry1 && jry1 < ry2)
|
||||
{
|
||||
r.setHeight (jry1 - ry1);
|
||||
rects.add (RectangleType (rx1, jry1, rx2 - rx1, ry2 - jry1));
|
||||
i = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (jry2 > ry1 && jry2 < ry2)
|
||||
{
|
||||
r.setHeight (jry2 - ry1);
|
||||
rects.add (RectangleType (rx1, jry2, rx2 - rx1, ry2 - jry2));
|
||||
i = -1;
|
||||
break;
|
||||
}
|
||||
else if (ry1 > jry1 && ry1 < jry2)
|
||||
{
|
||||
r2.setHeight (ry1 - jry1);
|
||||
rects.add (RectangleType (jrx1, ry1, jrx2 - jrx1, jry2 - ry1));
|
||||
i = -1;
|
||||
break;
|
||||
}
|
||||
else if (ry2 > jry1 && ry2 < jry2)
|
||||
{
|
||||
r2.setHeight (ry2 - jry1);
|
||||
rects.add (RectangleType (jrx1, ry2, jrx2 - jrx1, jry2 - ry2));
|
||||
i = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < rects.size() - 1; ++i)
|
||||
{
|
||||
auto& r = rects.getReference (i);
|
||||
|
||||
for (int j = rects.size(); --j > i;)
|
||||
{
|
||||
if (r.enlargeIfAdjacent (rects.getReference (j)))
|
||||
{
|
||||
rects.remove (j);
|
||||
i = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds an x and y value to all the coordinates. */
|
||||
void offsetAll (Point<ValueType> offset) noexcept
|
||||
{
|
||||
for (auto& r : rects)
|
||||
r += offset;
|
||||
}
|
||||
|
||||
/** Adds an x and y value to all the coordinates. */
|
||||
void offsetAll (ValueType dx, ValueType dy) noexcept
|
||||
{
|
||||
offsetAll (Point<ValueType> (dx, dy));
|
||||
}
|
||||
|
||||
/** Scales all the coordinates. */
|
||||
template <typename ScaleType>
|
||||
void scaleAll (ScaleType scaleFactor) noexcept
|
||||
{
|
||||
for (auto& r : rects)
|
||||
r *= scaleFactor;
|
||||
}
|
||||
|
||||
/** Applies a transform to all the rectangles.
|
||||
Obviously this will create a mess if the transform involves any
|
||||
rotation or skewing.
|
||||
*/
|
||||
void transformAll (const AffineTransform& transform) noexcept
|
||||
{
|
||||
for (auto& r : rects)
|
||||
r = r.transformedBy (transform);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a Path object to represent this region. */
|
||||
Path toPath() const
|
||||
{
|
||||
Path p;
|
||||
|
||||
for (auto& r : rects)
|
||||
p.addRectangle (r);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Standard method for iterating the rectangles in the list. */
|
||||
const RectangleType* begin() const noexcept { return rects.begin(); }
|
||||
/** Standard method for iterating the rectangles in the list. */
|
||||
const RectangleType* end() const noexcept { return rects.end(); }
|
||||
|
||||
/** Increases the internal storage to hold a minimum number of rectangles.
|
||||
Calling this before adding a large number of rectangles means that
|
||||
the array won't have to keep dynamically resizing itself as the elements
|
||||
are added, and it'll therefore be more efficient.
|
||||
@see Array::ensureStorageAllocated
|
||||
*/
|
||||
void ensureStorageAllocated (int minNumRectangles)
|
||||
{
|
||||
rects.ensureStorageAllocated (minNumRectangles);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<RectangleType> rects;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct RectangleUnitTest final : public UnitTest
|
||||
{
|
||||
RectangleUnitTest() : UnitTest ("Rectangle", UnitTestCategories::graphics) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Rectangle/string conversions can be round-tripped");
|
||||
{
|
||||
const Rectangle<float> a (0.1f, 0.2f, 0.3f, 0.4f);
|
||||
expect (Rectangle<float>::fromString (a.toString()) == a);
|
||||
|
||||
const Rectangle<double> b (0.1, 0.2, 0.3, 0.4);
|
||||
expect (Rectangle<double>::fromString (b.toString()) == b);
|
||||
|
||||
const Rectangle<int> c (1, 2, 3, 4);
|
||||
expect (Rectangle<int>::fromString (c.toString()) == c);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static RectangleUnitTest rectangleUnitTest;
|
||||
|
||||
} // namespace juce
|
||||
Reference in New Issue
Block a user