git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
280
deps/juce/modules/juce_graphics/geometry/juce_AffineTransform.cpp
vendored
Normal file
280
deps/juce/modules/juce_graphics/geometry/juce_AffineTransform.cpp
vendored
Normal file
@ -0,0 +1,280 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
return mat00 == other.mat00
|
||||
&& mat01 == other.mat01
|
||||
&& mat02 == other.mat02
|
||||
&& mat10 == other.mat10
|
||||
&& mat11 == other.mat11
|
||||
&& mat12 == other.mat12;
|
||||
}
|
||||
|
||||
bool AffineTransform::operator!= (const AffineTransform& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool AffineTransform::isIdentity() const noexcept
|
||||
{
|
||||
return mat01 == 0.0f
|
||||
&& mat02 == 0.0f
|
||||
&& mat10 == 0.0f
|
||||
&& mat12 == 0.0f
|
||||
&& mat00 == 1.0f
|
||||
&& mat11 == 1.0f;
|
||||
}
|
||||
|
||||
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 (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 mat01 == 0.0f
|
||||
&& mat10 == 0.0f
|
||||
&& mat00 == 1.0f
|
||||
&& 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 : 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
|
303
deps/juce/modules/juce_graphics/geometry/juce_AffineTransform.h
vendored
Normal file
303
deps/juce/modules/juce_graphics/geometry/juce_AffineTransform.h
vendored
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
148
deps/juce/modules/juce_graphics/geometry/juce_BorderSize.h
vendored
Normal file
148
deps/juce/modules/juce_graphics/geometry/juce_BorderSize.h
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a null border.
|
||||
All sizes are left as 0.
|
||||
*/
|
||||
BorderSize() = default;
|
||||
|
||||
/** Creates a copy of another border. */
|
||||
BorderSize (const 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 Rectangle<ValueType> (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 Rectangle<ValueType> (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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool operator== (const BorderSize& other) const noexcept
|
||||
{
|
||||
return top == other.top && left == other.left && bottom == other.bottom && right == other.right;
|
||||
}
|
||||
|
||||
bool operator!= (const BorderSize& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ValueType top{}, left{}, bottom{}, right{};
|
||||
};
|
||||
|
||||
} // namespace juce
|
842
deps/juce/modules/juce_graphics/geometry/juce_EdgeTable.cpp
vendored
Normal file
842
deps/juce/modules/juce_graphics/geometry/juce_EdgeTable.cpp
vendored
Normal file
@ -0,0 +1,842 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 * bounds.getX();
|
||||
auto topLimit = scale * bounds.getY();
|
||||
auto rightLimit = scale * bounds.getRight();
|
||||
auto heightLimit = scale * bounds.getHeight();
|
||||
|
||||
PathFlatteningIterator iter (path, transform);
|
||||
|
||||
while (iter.next())
|
||||
{
|
||||
auto y1 = roundToInt (iter.y1 * 256.0f);
|
||||
auto y2 = roundToInt (iter.y2 * 256.0f);
|
||||
|
||||
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 = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier)));
|
||||
|
||||
do
|
||||
{
|
||||
auto step = jmin (stepSize, y2 - y1, 256 - (y1 & 255));
|
||||
auto x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY));
|
||||
|
||||
if (x < leftLimit)
|
||||
x = leftLimit;
|
||||
else if (x >= rightLimit)
|
||||
x = rightLimit - 1;
|
||||
|
||||
addEdgePoint (x, y1 / scale, 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
|
226
deps/juce/modules/juce_graphics/geometry/juce_EdgeTable.h
vendored
Normal file
226
deps/juce/modules/juce_graphics/geometry/juce_EdgeTable.h
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
421
deps/juce/modules/juce_graphics/geometry/juce_Line.h
vendored
Normal file
421
deps/juce/modules/juce_graphics/geometry/juce_Line.h
vendored
Normal file
@ -0,0 +1,421 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
return start + (end - start) * (distanceFromStart / getLength());
|
||||
}
|
||||
|
||||
/** 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 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 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;
|
||||
|
||||
if (divisor == 0)
|
||||
{
|
||||
if (! (d1.isOrigin() || d2.isOrigin()))
|
||||
{
|
||||
if (d1.y == 0 && d2.y != 0)
|
||||
{
|
||||
auto along = (p1.y - p3.y) / d2.y;
|
||||
intersection = p1.withX (p3.x + along * d2.x);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
|
||||
if (d2.y == 0 && d1.y != 0)
|
||||
{
|
||||
auto along = (p3.y - p1.y) / d1.y;
|
||||
intersection = p3.withX (p1.x + along * d1.x);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
|
||||
if (d1.x == 0 && d2.x != 0)
|
||||
{
|
||||
auto along = (p1.x - p3.x) / d2.x;
|
||||
intersection = p1.withY (p3.y + along * d2.y);
|
||||
return isZeroToOne (along);
|
||||
}
|
||||
|
||||
if (d2.x == 0 && d1.x != 0)
|
||||
{
|
||||
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
|
185
deps/juce/modules/juce_graphics/geometry/juce_Parallelogram.h
vendored
Normal file
185
deps/juce/modules/juce_graphics/geometry/juce_Parallelogram.h
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
1539
deps/juce/modules/juce_graphics/geometry/juce_Path.cpp
vendored
Normal file
1539
deps/juce/modules/juce_graphics/geometry/juce_Path.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
839
deps/juce/modules/juce_graphics/geometry/juce_Path.h
vendored
Normal file
839
deps/juce/modules/juce_graphics/geometry/juce_Path.h
vendored
Normal file
@ -0,0 +1,839 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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);
|
||||
|
||||
/** 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,
|
||||
const Point<float> arrowTipPosition,
|
||||
const float cornerSize,
|
||||
const 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 const float lineMarker;
|
||||
static const float moveMarker;
|
||||
static const float quadMarker;
|
||||
static const float cubicMarker;
|
||||
static const float closeSubPathMarker;
|
||||
|
||||
JUCE_LEAK_DETECTOR (Path)
|
||||
};
|
||||
|
||||
} // namespace juce
|
295
deps/juce/modules/juce_graphics/geometry/juce_PathIterator.cpp
vendored
Normal file
295
deps/juce/modules/juce_graphics/geometry/juce_PathIterator.cpp
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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()
|
||||
&& *source == Path::closeSubPathMarker
|
||||
&& x2 == subPathCloseX
|
||||
&& 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 = (m3x != m1x && m3x != m2x)
|
||||
|| (m3y != m1y && 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 = (m4x != m1x && m4x != m2x)
|
||||
|| (m4y != m1y && m4y != m2y)
|
||||
|| (m5x != m3x && m5x != m2x)
|
||||
|| (m5y != m3y && 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 (x2 != subPathCloseX || 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
|
111
deps/juce/modules/juce_graphics/geometry/juce_PathIterator.h
vendored
Normal file
111
deps/juce/modules/juce_graphics/geometry/juce_PathIterator.h
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
744
deps/juce/modules/juce_graphics/geometry/juce_PathStrokeType.cpp
vendored
Normal file
744
deps/juce/modules/juce_graphics/geometry/juce_PathStrokeType.cpp
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
return thickness == other.thickness
|
||||
&& jointStyle == other.jointStyle
|
||||
&& endStyle == other.endStyle;
|
||||
}
|
||||
|
||||
bool PathStrokeType::operator!= (const PathStrokeType& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace PathStrokeHelpers
|
||||
{
|
||||
static bool 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,
|
||||
float& intersectionX,
|
||||
float& intersectionY,
|
||||
float& distanceBeyondLine1EndSquared) noexcept
|
||||
{
|
||||
if (x2 != x3 || y2 != y3)
|
||||
{
|
||||
auto dx1 = x2 - x1;
|
||||
auto dy1 = y2 - y1;
|
||||
auto dx2 = x4 - x3;
|
||||
auto dy2 = y4 - y3;
|
||||
auto divisor = dx1 * dy2 - dx2 * dy1;
|
||||
|
||||
if (divisor == 0.0f)
|
||||
{
|
||||
if (! ((dx1 == 0.0f && dy1 == 0.0f) || (dx2 == 0.0f && dy2 == 0.0f)))
|
||||
{
|
||||
if (dy1 == 0.0f && dy2 != 0.0f)
|
||||
{
|
||||
auto along = (y1 - y3) / dy2;
|
||||
intersectionX = x3 + along * dx2;
|
||||
intersectionY = y1;
|
||||
|
||||
distanceBeyondLine1EndSquared = intersectionX - x2;
|
||||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
|
||||
if ((x2 > x1) == (intersectionX < x2))
|
||||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
|
||||
|
||||
return along >= 0 && along <= 1.0f;
|
||||
}
|
||||
|
||||
if (dy2 == 0.0f && dy1 != 0.0f)
|
||||
{
|
||||
auto along = (y3 - y1) / dy1;
|
||||
intersectionX = x1 + along * dx1;
|
||||
intersectionY = y3;
|
||||
|
||||
distanceBeyondLine1EndSquared = (along - 1.0f) * dx1;
|
||||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
|
||||
if (along < 1.0f)
|
||||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
|
||||
|
||||
return along >= 0 && along <= 1.0f;
|
||||
}
|
||||
|
||||
if (dx1 == 0.0f && dx2 != 0.0f)
|
||||
{
|
||||
auto along = (x1 - x3) / dx2;
|
||||
intersectionX = x1;
|
||||
intersectionY = y3 + along * dy2;
|
||||
|
||||
distanceBeyondLine1EndSquared = intersectionY - y2;
|
||||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
|
||||
|
||||
if ((y2 > y1) == (intersectionY < y2))
|
||||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
|
||||
|
||||
return along >= 0 && along <= 1.0f;
|
||||
}
|
||||
|
||||
if (dx2 == 0.0f && dx1 != 0.0f)
|
||||
{
|
||||
auto along = (x3 - x1) / dx1;
|
||||
intersectionX = x3;
|
||||
intersectionY = y1 + along * dy1;
|
||||
|
||||
distanceBeyondLine1EndSquared = (along - 1.0f) * dy1;
|
||||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
|
||||
if (along < 1.0f)
|
||||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
|
||||
|
||||
return along >= 0 && along <= 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
intersectionX = 0.5f * (x2 + x3);
|
||||
intersectionY = 0.5f * (y2 + y3);
|
||||
|
||||
distanceBeyondLine1EndSquared = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor;
|
||||
|
||||
intersectionX = x1 + along1 * dx1;
|
||||
intersectionY = y1 + along1 * dy1;
|
||||
|
||||
if (along1 >= 0 && along1 <= 1.0f)
|
||||
{
|
||||
auto along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1) / divisor;
|
||||
|
||||
if (along2 >= 0 && along2 <= 1.0f)
|
||||
{
|
||||
distanceBeyondLine1EndSquared = 0.0f;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
distanceBeyondLine1EndSquared = along1 - 1.0f;
|
||||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
|
||||
distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1);
|
||||
|
||||
if (along1 < 1.0f)
|
||||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
intersectionX = x2;
|
||||
intersectionY = y2;
|
||||
|
||||
distanceBeyondLine1EndSquared = 0.0f;
|
||||
return 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
|
||||
|| (x3 == x4 && y3 == y4)
|
||||
|| (x1 == x2 && y1 == y2))
|
||||
{
|
||||
destPath.lineTo (x2, y2);
|
||||
destPath.lineTo (x3, y3);
|
||||
}
|
||||
else
|
||||
{
|
||||
float jx, jy, distanceBeyondLine1EndSquared;
|
||||
|
||||
// if they intersect, use this point..
|
||||
if (lineIntersection (x1, y1, x2, y2,
|
||||
x3, y3, x4, y4,
|
||||
jx, jy, distanceBeyondLine1EndSquared))
|
||||
{
|
||||
destPath.lineTo (jx, jy);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (style == PathStrokeType::mitered)
|
||||
{
|
||||
if (distanceBeyondLine1EndSquared < maxMiterExtensionSquared
|
||||
&& distanceBeyondLine1EndSquared > 0.0f)
|
||||
{
|
||||
destPath.lineTo (jx, jy);
|
||||
}
|
||||
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 (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 (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
|
206
deps/juce/modules/juce_graphics/geometry/juce_PathStrokeType.h
vendored
Normal file
206
deps/juce/modules/juce_graphics/geometry/juce_PathStrokeType.h
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
254
deps/juce/modules/juce_graphics/geometry/juce_Point.h
vendored
Normal file
254
deps/juce/modules/juce_graphics/geometry/juce_Point.h
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 { return x == other.x && y == other.y; }
|
||||
constexpr inline bool operator!= (Point other) const noexcept { return x != other.x || y != other.y; }
|
||||
|
||||
/** Returns true if the point is (0, 0). */
|
||||
constexpr bool isOrigin() const noexcept { return x == ValueType() && y == ValueType(); }
|
||||
|
||||
/** 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 = typename std::common_type<ValueType, OtherType>::type;
|
||||
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 = typename std::common_type<ValueType, OtherType>::type;
|
||||
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 = typename TypeHelpers::SmallestFloatType<ValueType>::type;
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
985
deps/juce/modules/juce_graphics/geometry/juce_Rectangle.h
vendored
Normal file
985
deps/juce/modules/juce_graphics/geometry/juce_Rectangle.h
vendored
Normal file
@ -0,0 +1,985 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages a rectangle and allows geometric operations to be performed on it.
|
||||
|
||||
@see RectangleList, Path, Line, Point
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
template <typename ValueType>
|
||||
class Rectangle
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a rectangle of zero size.
|
||||
The default coordinates will be (0, 0, 0, 0).
|
||||
*/
|
||||
Rectangle() = default;
|
||||
|
||||
/** Creates a copy of another rectangle. */
|
||||
Rectangle (const Rectangle&) = default;
|
||||
|
||||
/** Creates a rectangle with a given position and size. */
|
||||
Rectangle (ValueType initialX, ValueType initialY,
|
||||
ValueType width, ValueType height) noexcept
|
||||
: pos (initialX, initialY),
|
||||
w (width), h (height)
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a rectangle with a given size, and a position of (0, 0). */
|
||||
Rectangle (ValueType width, ValueType height) noexcept
|
||||
: w (width), h (height)
|
||||
{
|
||||
}
|
||||
|
||||
/** Creates a Rectangle from the positions of two opposite corners. */
|
||||
Rectangle (Point<ValueType> corner1, Point<ValueType> corner2) noexcept
|
||||
: pos (jmin (corner1.x, corner2.x),
|
||||
jmin (corner1.y, corner2.y)),
|
||||
w (corner1.x - corner2.x),
|
||||
h (corner1.y - corner2.y)
|
||||
{
|
||||
if (w < ValueType()) w = -w;
|
||||
if (h < ValueType()) h = -h;
|
||||
}
|
||||
|
||||
/** Creates a Rectangle from a set of left, right, top, bottom coordinates.
|
||||
The right and bottom values must be larger than the left and top ones, or the resulting
|
||||
rectangle will have a negative size.
|
||||
*/
|
||||
static Rectangle leftTopRightBottom (ValueType left, ValueType top,
|
||||
ValueType right, ValueType bottom) noexcept
|
||||
{
|
||||
return { left, top, right - left, bottom - top };
|
||||
}
|
||||
|
||||
/** Creates a copy of another rectangle. */
|
||||
Rectangle& operator= (const Rectangle&) = default;
|
||||
|
||||
/** Destructor. */
|
||||
~Rectangle() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the rectangle's width or height are zero or less */
|
||||
bool isEmpty() const noexcept { return w <= ValueType() || h <= ValueType(); }
|
||||
|
||||
/** Returns true if the rectangle's values are all finite numbers, i.e. not NaN or infinity. */
|
||||
inline bool isFinite() const noexcept { return pos.isFinite() && juce_isfinite (w) && juce_isfinite (h); }
|
||||
|
||||
/** Returns the x coordinate of the rectangle's left-hand-side. */
|
||||
inline ValueType getX() const noexcept { return pos.x; }
|
||||
|
||||
/** Returns the y coordinate of the rectangle's top edge. */
|
||||
inline ValueType getY() const noexcept { return pos.y; }
|
||||
|
||||
/** Returns the width of the rectangle. */
|
||||
inline ValueType getWidth() const noexcept { return w; }
|
||||
|
||||
/** Returns the height of the rectangle. */
|
||||
inline ValueType getHeight() const noexcept { return h; }
|
||||
|
||||
/** Returns the x coordinate of the rectangle's right-hand-side. */
|
||||
inline ValueType getRight() const noexcept { return pos.x + w; }
|
||||
|
||||
/** Returns the y coordinate of the rectangle's bottom edge. */
|
||||
inline ValueType getBottom() const noexcept { return pos.y + h; }
|
||||
|
||||
/** Returns the x coordinate of the rectangle's centre. */
|
||||
ValueType getCentreX() const noexcept { return pos.x + w / (ValueType) 2; }
|
||||
|
||||
/** Returns the y coordinate of the rectangle's centre. */
|
||||
ValueType getCentreY() const noexcept { return pos.y + h / (ValueType) 2; }
|
||||
|
||||
/** Returns the centre point of the rectangle. */
|
||||
Point<ValueType> getCentre() const noexcept { return { pos.x + w / (ValueType) 2,
|
||||
pos.y + h / (ValueType) 2 }; }
|
||||
|
||||
/** Returns the aspect ratio of the rectangle's width / height.
|
||||
If widthOverHeight is true, it returns width / height; if widthOverHeight is false,
|
||||
it returns height / width. */
|
||||
ValueType getAspectRatio (bool widthOverHeight = true) const noexcept { return widthOverHeight ? w / h : h / w; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the rectangle's top-left position as a Point. */
|
||||
inline Point<ValueType> getPosition() const noexcept { return pos; }
|
||||
|
||||
/** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */
|
||||
inline void setPosition (Point<ValueType> newPos) noexcept { pos = newPos; }
|
||||
|
||||
/** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */
|
||||
inline void setPosition (ValueType newX, ValueType newY) noexcept { pos.setXY (newX, newY); }
|
||||
|
||||
/** Returns the rectangle's top-left position as a Point. */
|
||||
Point<ValueType> getTopLeft() const noexcept { return pos; }
|
||||
|
||||
/** Returns the rectangle's top-right position as a Point. */
|
||||
Point<ValueType> getTopRight() const noexcept { return { pos.x + w, pos.y }; }
|
||||
|
||||
/** Returns the rectangle's bottom-left position as a Point. */
|
||||
Point<ValueType> getBottomLeft() const noexcept { return { pos.x, pos.y + h }; }
|
||||
|
||||
/** Returns the rectangle's bottom-right position as a Point. */
|
||||
Point<ValueType> getBottomRight() const noexcept { return { pos.x + w, pos.y + h }; }
|
||||
|
||||
/** Returns the rectangle's left and right positions as a Range. */
|
||||
Range<ValueType> getHorizontalRange() const noexcept { return Range<ValueType>::withStartAndLength (pos.x, w); }
|
||||
|
||||
/** Returns the rectangle's top and bottom positions as a Range. */
|
||||
Range<ValueType> getVerticalRange() const noexcept { return Range<ValueType>::withStartAndLength (pos.y, h); }
|
||||
|
||||
/** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */
|
||||
void setSize (ValueType newWidth, ValueType newHeight) noexcept { w = newWidth; h = newHeight; }
|
||||
|
||||
/** Changes all the rectangle's coordinates. */
|
||||
void setBounds (ValueType newX, ValueType newY,
|
||||
ValueType newWidth, ValueType newHeight) noexcept { pos.x = newX; pos.y = newY; w = newWidth; h = newHeight; }
|
||||
|
||||
/** Changes the rectangle's X coordinate */
|
||||
inline void setX (ValueType newX) noexcept { pos.x = newX; }
|
||||
|
||||
/** Changes the rectangle's Y coordinate */
|
||||
inline void setY (ValueType newY) noexcept { pos.y = newY; }
|
||||
|
||||
/** Changes the rectangle's width */
|
||||
inline void setWidth (ValueType newWidth) noexcept { w = newWidth; }
|
||||
|
||||
/** Changes the rectangle's height */
|
||||
inline void setHeight (ValueType newHeight) noexcept { h = newHeight; }
|
||||
|
||||
/** Changes the position of the rectangle's centre (leaving its size unchanged). */
|
||||
inline void setCentre (ValueType newCentreX, ValueType newCentreY) noexcept { pos.x = newCentreX - w / (ValueType) 2;
|
||||
pos.y = newCentreY - h / (ValueType) 2; }
|
||||
|
||||
/** Changes the position of the rectangle's centre (leaving its size unchanged). */
|
||||
inline void setCentre (Point<ValueType> newCentre) noexcept { setCentre (newCentre.x, newCentre.y); }
|
||||
|
||||
/** Changes the position of the rectangle's left and right edges. */
|
||||
void setHorizontalRange (Range<ValueType> range) noexcept { pos.x = range.getStart(); w = range.getLength(); }
|
||||
|
||||
/** Changes the position of the rectangle's top and bottom edges. */
|
||||
void setVerticalRange (Range<ValueType> range) noexcept { pos.y = range.getStart(); h = range.getLength(); }
|
||||
|
||||
/** Returns a rectangle which has the same size and y-position as this one, but with a different x-position. */
|
||||
Rectangle withX (ValueType newX) const noexcept { return { newX, pos.y, w, h }; }
|
||||
|
||||
/** Returns a rectangle which has the same size and x-position as this one, but with a different y-position. */
|
||||
Rectangle withY (ValueType newY) const noexcept { return { pos.x, newY, w, h }; }
|
||||
|
||||
/** Returns a rectangle which has the same size and y-position as this one, but whose right-hand edge has the given position. */
|
||||
Rectangle withRightX (ValueType newRightX) const noexcept { return { newRightX - w, pos.y, w, h }; }
|
||||
|
||||
/** Returns a rectangle which has the same size and x-position as this one, but whose bottom edge has the given position. */
|
||||
Rectangle withBottomY (ValueType newBottomY) const noexcept { return { pos.x, newBottomY - h, w, h }; }
|
||||
|
||||
/** Returns a rectangle with the same size as this one, but a new position. */
|
||||
Rectangle withPosition (ValueType newX, ValueType newY) const noexcept { return { newX, newY, w, h }; }
|
||||
|
||||
/** Returns a rectangle with the same size as this one, but a new position. */
|
||||
Rectangle withPosition (Point<ValueType> newPos) const noexcept { return { newPos.x, newPos.y, w, h }; }
|
||||
|
||||
/** Returns a rectangle whose size is the same as this one, but whose top-left position is (0, 0). */
|
||||
Rectangle withZeroOrigin() const noexcept { return { w, h }; }
|
||||
|
||||
/** Returns a rectangle with the same size as this one, but a new centre position. */
|
||||
Rectangle withCentre (Point<ValueType> newCentre) const noexcept { return { newCentre.x - w / (ValueType) 2,
|
||||
newCentre.y - h / (ValueType) 2, w, h }; }
|
||||
|
||||
/** Returns a rectangle which has the same position and height as this one, but with a different width. */
|
||||
Rectangle withWidth (ValueType newWidth) const noexcept { return { pos.x, pos.y, jmax (ValueType(), newWidth), h }; }
|
||||
|
||||
/** Returns a rectangle which has the same position and width as this one, but with a different height. */
|
||||
Rectangle withHeight (ValueType newHeight) const noexcept { return { pos.x, pos.y, w, jmax (ValueType(), newHeight) }; }
|
||||
|
||||
/** Returns a rectangle with the same top-left position as this one, but a new size. */
|
||||
Rectangle withSize (ValueType newWidth, ValueType newHeight) const noexcept { return { pos.x, pos.y, jmax (ValueType(), newWidth), jmax (ValueType(), newHeight) }; }
|
||||
|
||||
/** Returns a rectangle with the same centre position as this one, but a new size. */
|
||||
Rectangle withSizeKeepingCentre (ValueType newWidth, ValueType newHeight) const noexcept { return { pos.x + (w - newWidth) / (ValueType) 2,
|
||||
pos.y + (h - newHeight) / (ValueType) 2, newWidth, newHeight }; }
|
||||
|
||||
/** Moves the x position, adjusting the width so that the right-hand edge remains in the same place.
|
||||
If the x is moved to be on the right of the current right-hand edge, the width will be set to zero.
|
||||
@see withLeft
|
||||
*/
|
||||
void setLeft (ValueType newLeft) noexcept { w = jmax (ValueType(), pos.x + w - newLeft); pos.x = newLeft; }
|
||||
|
||||
/** Returns a new rectangle with a different x position, but the same right-hand edge as this one.
|
||||
If the new x is beyond the right of the current right-hand edge, the width will be set to zero.
|
||||
@see setLeft
|
||||
*/
|
||||
Rectangle withLeft (ValueType newLeft) const noexcept { return { newLeft, pos.y, jmax (ValueType(), pos.x + w - newLeft), h }; }
|
||||
|
||||
/** Moves the y position, adjusting the height so that the bottom edge remains in the same place.
|
||||
If the y is moved to be below the current bottom edge, the height will be set to zero.
|
||||
@see withTop
|
||||
*/
|
||||
void setTop (ValueType newTop) noexcept { h = jmax (ValueType(), pos.y + h - newTop); pos.y = newTop; }
|
||||
|
||||
/** Returns a new rectangle with a different y position, but the same bottom edge as this one.
|
||||
If the new y is beyond the bottom of the current rectangle, the height will be set to zero.
|
||||
@see setTop
|
||||
*/
|
||||
Rectangle withTop (ValueType newTop) const noexcept { return { pos.x, newTop, w, jmax (ValueType(), pos.y + h - newTop) }; }
|
||||
|
||||
/** Adjusts the width so that the right-hand edge of the rectangle has this new value.
|
||||
If the new right is below the current X value, the X will be pushed down to match it.
|
||||
@see getRight, withRight
|
||||
*/
|
||||
void setRight (ValueType newRight) noexcept { pos.x = jmin (pos.x, newRight); w = newRight - pos.x; }
|
||||
|
||||
/** Returns a new rectangle with a different right-hand edge position, but the same left-hand edge as this one.
|
||||
If the new right edge is below the current left-hand edge, the width will be set to zero.
|
||||
@see setRight
|
||||
*/
|
||||
Rectangle withRight (ValueType newRight) const noexcept { return { jmin (pos.x, newRight), pos.y, jmax (ValueType(), newRight - pos.x), h }; }
|
||||
|
||||
/** Adjusts the height so that the bottom edge of the rectangle has this new value.
|
||||
If the new bottom is lower than the current Y value, the Y will be pushed down to match it.
|
||||
@see getBottom, withBottom
|
||||
*/
|
||||
void setBottom (ValueType newBottom) noexcept { pos.y = jmin (pos.y, newBottom); h = newBottom - pos.y; }
|
||||
|
||||
/** Returns a new rectangle with a different bottom edge position, but the same top edge as this one.
|
||||
If the new y is beyond the bottom of the current rectangle, the height will be set to zero.
|
||||
@see setBottom
|
||||
*/
|
||||
Rectangle withBottom (ValueType newBottom) const noexcept { return { pos.x, jmin (pos.y, newBottom), w, jmax (ValueType(), newBottom - pos.y) }; }
|
||||
|
||||
/** Returns a version of this rectangle with the given amount removed from its left-hand edge. */
|
||||
Rectangle withTrimmedLeft (ValueType amountToRemove) const noexcept { return withLeft (pos.x + amountToRemove); }
|
||||
|
||||
/** Returns a version of this rectangle with the given amount removed from its right-hand edge. */
|
||||
Rectangle withTrimmedRight (ValueType amountToRemove) const noexcept { return withWidth (w - amountToRemove); }
|
||||
|
||||
/** Returns a version of this rectangle with the given amount removed from its top edge. */
|
||||
Rectangle withTrimmedTop (ValueType amountToRemove) const noexcept { return withTop (pos.y + amountToRemove); }
|
||||
|
||||
/** Returns a version of this rectangle with the given amount removed from its bottom edge. */
|
||||
Rectangle withTrimmedBottom (ValueType amountToRemove) const noexcept { return withHeight (h - amountToRemove); }
|
||||
|
||||
//==============================================================================
|
||||
/** Moves the rectangle's position by adding amount to its x and y coordinates. */
|
||||
void translate (ValueType deltaX,
|
||||
ValueType deltaY) noexcept
|
||||
{
|
||||
pos.x += deltaX;
|
||||
pos.y += deltaY;
|
||||
}
|
||||
|
||||
/** Returns a rectangle which is the same as this one moved by a given amount. */
|
||||
Rectangle translated (ValueType deltaX,
|
||||
ValueType deltaY) const noexcept
|
||||
{
|
||||
return { pos.x + deltaX, pos.y + deltaY, w, h };
|
||||
}
|
||||
|
||||
/** Returns a rectangle which is the same as this one moved by a given amount. */
|
||||
Rectangle operator+ (Point<ValueType> deltaPosition) const noexcept
|
||||
{
|
||||
return { pos.x + deltaPosition.x, pos.y + deltaPosition.y, w, h };
|
||||
}
|
||||
|
||||
/** Moves this rectangle by a given amount. */
|
||||
Rectangle& operator+= (Point<ValueType> deltaPosition) noexcept
|
||||
{
|
||||
pos += deltaPosition;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns a rectangle which is the same as this one moved by a given amount. */
|
||||
Rectangle operator- (Point<ValueType> deltaPosition) const noexcept
|
||||
{
|
||||
return { pos.x - deltaPosition.x, pos.y - deltaPosition.y, w, h };
|
||||
}
|
||||
|
||||
/** Moves this rectangle by a given amount. */
|
||||
Rectangle& operator-= (Point<ValueType> deltaPosition) noexcept
|
||||
{
|
||||
pos -= deltaPosition;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns a rectangle that has been scaled by the given amount, centred around the origin.
|
||||
Note that if the rectangle has int coordinates and it's scaled by a
|
||||
floating-point amount, then the result will be converted back to integer
|
||||
coordinates using getSmallestIntegerContainer().
|
||||
*/
|
||||
template <typename FloatType>
|
||||
Rectangle operator* (FloatType scaleFactor) const noexcept
|
||||
{
|
||||
Rectangle r (*this);
|
||||
r *= scaleFactor;
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Scales this rectangle by the given amount, centred around the origin.
|
||||
Note that if the rectangle has int coordinates and it's scaled by a
|
||||
floating-point amount, then the result will be converted back to integer
|
||||
coordinates using getSmallestIntegerContainer().
|
||||
*/
|
||||
template <typename FloatType>
|
||||
Rectangle operator*= (FloatType scaleFactor) noexcept
|
||||
{
|
||||
Rectangle<FloatType> ((FloatType) pos.x * scaleFactor,
|
||||
(FloatType) pos.y * scaleFactor,
|
||||
(FloatType) w * scaleFactor,
|
||||
(FloatType) h * scaleFactor).copyWithRounding (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Scales this rectangle by the given X and Y factors, centred around the origin.
|
||||
Note that if the rectangle has int coordinates and it's scaled by a
|
||||
floating-point amount, then the result will be converted back to integer
|
||||
coordinates using getSmallestIntegerContainer().
|
||||
*/
|
||||
template <typename FloatType>
|
||||
Rectangle operator*= (Point<FloatType> scaleFactor) noexcept
|
||||
{
|
||||
Rectangle<FloatType> ((FloatType) pos.x * scaleFactor.x,
|
||||
(FloatType) pos.y * scaleFactor.y,
|
||||
(FloatType) w * scaleFactor.x,
|
||||
(FloatType) h * scaleFactor.y).copyWithRounding (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Scales this rectangle by the given amount, centred around the origin. */
|
||||
template <typename FloatType>
|
||||
Rectangle operator/ (FloatType scaleFactor) const noexcept
|
||||
{
|
||||
Rectangle r (*this);
|
||||
r /= scaleFactor;
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Scales this rectangle by the given amount, centred around the origin. */
|
||||
template <typename FloatType>
|
||||
Rectangle operator/= (FloatType scaleFactor) noexcept
|
||||
{
|
||||
Rectangle<FloatType> ((FloatType) pos.x / scaleFactor,
|
||||
(FloatType) pos.y / scaleFactor,
|
||||
(FloatType) w / scaleFactor,
|
||||
(FloatType) h / scaleFactor).copyWithRounding (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Scales this rectangle by the given X and Y factors, centred around the origin. */
|
||||
template <typename FloatType>
|
||||
Rectangle operator/= (Point<FloatType> scaleFactor) noexcept
|
||||
{
|
||||
Rectangle<FloatType> ((FloatType) pos.x / scaleFactor.x,
|
||||
(FloatType) pos.y / scaleFactor.y,
|
||||
(FloatType) w / scaleFactor.x,
|
||||
(FloatType) h / scaleFactor.y).copyWithRounding (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Expands the rectangle by a given amount.
|
||||
|
||||
Effectively, its new size is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2).
|
||||
@see expanded, reduce, reduced
|
||||
*/
|
||||
void expand (ValueType deltaX,
|
||||
ValueType deltaY) noexcept
|
||||
{
|
||||
auto nw = jmax (ValueType(), w + deltaX * 2);
|
||||
auto nh = jmax (ValueType(), h + deltaY * 2);
|
||||
setBounds (pos.x - deltaX, pos.y - deltaY, nw, nh);
|
||||
}
|
||||
|
||||
/** Returns a rectangle that is larger than this one by a given amount.
|
||||
|
||||
Effectively, the rectangle returned is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2).
|
||||
@see expand, reduce, reduced
|
||||
*/
|
||||
Rectangle expanded (ValueType deltaX,
|
||||
ValueType deltaY) const noexcept
|
||||
{
|
||||
auto nw = jmax (ValueType(), w + deltaX * 2);
|
||||
auto nh = jmax (ValueType(), h + deltaY * 2);
|
||||
return { pos.x - deltaX, pos.y - deltaY, nw, nh };
|
||||
}
|
||||
|
||||
/** Returns a rectangle that is larger than this one by a given amount.
|
||||
|
||||
Effectively, the rectangle returned is (x - delta, y - delta, w + delta * 2, h + delta * 2).
|
||||
@see expand, reduce, reduced
|
||||
*/
|
||||
Rectangle expanded (ValueType delta) const noexcept
|
||||
{
|
||||
return expanded (delta, delta);
|
||||
}
|
||||
|
||||
/** Shrinks the rectangle by a given amount.
|
||||
|
||||
Effectively, its new size is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2).
|
||||
@see reduced, expand, expanded
|
||||
*/
|
||||
void reduce (ValueType deltaX,
|
||||
ValueType deltaY) noexcept
|
||||
{
|
||||
expand (-deltaX, -deltaY);
|
||||
}
|
||||
|
||||
/** Returns a rectangle that is smaller than this one by a given amount.
|
||||
|
||||
Effectively, the rectangle returned is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2).
|
||||
@see reduce, expand, expanded
|
||||
*/
|
||||
Rectangle reduced (ValueType deltaX,
|
||||
ValueType deltaY) const noexcept
|
||||
{
|
||||
return expanded (-deltaX, -deltaY);
|
||||
}
|
||||
|
||||
/** Returns a rectangle that is smaller than this one by a given amount.
|
||||
|
||||
Effectively, the rectangle returned is (x + delta, y + delta, w - delta * 2, h - delta * 2).
|
||||
@see reduce, expand, expanded
|
||||
*/
|
||||
Rectangle reduced (ValueType delta) const noexcept
|
||||
{
|
||||
return reduced (delta, delta);
|
||||
}
|
||||
|
||||
/** Removes a strip from the top of this rectangle, reducing this rectangle
|
||||
by the specified amount and returning the section that was removed.
|
||||
|
||||
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
|
||||
return (100, 100, 300, 50) and leave this rectangle as (100, 150, 300, 250).
|
||||
|
||||
If amountToRemove is greater than the height of this rectangle, it'll be clipped to
|
||||
that value.
|
||||
*/
|
||||
Rectangle removeFromTop (ValueType amountToRemove) noexcept
|
||||
{
|
||||
const Rectangle r (pos.x, pos.y, w, jmin (amountToRemove, h));
|
||||
pos.y += r.h; h -= r.h;
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Removes a strip from the left-hand edge of this rectangle, reducing this rectangle
|
||||
by the specified amount and returning the section that was removed.
|
||||
|
||||
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
|
||||
return (100, 100, 50, 300) and leave this rectangle as (150, 100, 250, 300).
|
||||
|
||||
If amountToRemove is greater than the width of this rectangle, it'll be clipped to
|
||||
that value.
|
||||
*/
|
||||
Rectangle removeFromLeft (ValueType amountToRemove) noexcept
|
||||
{
|
||||
const Rectangle r (pos.x, pos.y, jmin (amountToRemove, w), h);
|
||||
pos.x += r.w; w -= r.w;
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Removes a strip from the right-hand edge of this rectangle, reducing this rectangle
|
||||
by the specified amount and returning the section that was removed.
|
||||
|
||||
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
|
||||
return (350, 100, 50, 300) and leave this rectangle as (100, 100, 250, 300).
|
||||
|
||||
If amountToRemove is greater than the width of this rectangle, it'll be clipped to
|
||||
that value.
|
||||
*/
|
||||
Rectangle removeFromRight (ValueType amountToRemove) noexcept
|
||||
{
|
||||
amountToRemove = jmin (amountToRemove, w);
|
||||
const Rectangle r (pos.x + w - amountToRemove, pos.y, amountToRemove, h);
|
||||
w -= amountToRemove;
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Removes a strip from the bottom of this rectangle, reducing this rectangle
|
||||
by the specified amount and returning the section that was removed.
|
||||
|
||||
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
|
||||
return (100, 350, 300, 50) and leave this rectangle as (100, 100, 300, 250).
|
||||
|
||||
If amountToRemove is greater than the height of this rectangle, it'll be clipped to
|
||||
that value.
|
||||
*/
|
||||
Rectangle removeFromBottom (ValueType amountToRemove) noexcept
|
||||
{
|
||||
amountToRemove = jmin (amountToRemove, h);
|
||||
const Rectangle r (pos.x, pos.y + h - amountToRemove, w, amountToRemove);
|
||||
h -= amountToRemove;
|
||||
return r;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the nearest point to the specified point that lies within this rectangle. */
|
||||
Point<ValueType> getConstrainedPoint (Point<ValueType> point) const noexcept
|
||||
{
|
||||
return { jlimit (pos.x, pos.x + w, point.x),
|
||||
jlimit (pos.y, pos.y + h, point.y) };
|
||||
}
|
||||
|
||||
/** Returns a point within this rectangle, 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 rectangle, and 1 is the right or bottom. (Out-of-bounds values
|
||||
will return a point outside the rectangle).
|
||||
*/
|
||||
template <typename FloatType>
|
||||
Point<ValueType> getRelativePoint (FloatType relativeX, FloatType relativeY) const noexcept
|
||||
{
|
||||
return { pos.x + static_cast<ValueType> ((FloatType) w * relativeX),
|
||||
pos.y + static_cast<ValueType> ((FloatType) h * relativeY) };
|
||||
}
|
||||
|
||||
/** Returns a proportion of the width of this rectangle. */
|
||||
template <typename FloatType>
|
||||
ValueType proportionOfWidth (FloatType proportion) const noexcept
|
||||
{
|
||||
return static_cast<ValueType> ((FloatType) w * proportion);
|
||||
}
|
||||
|
||||
/** Returns a proportion of the height of this rectangle. */
|
||||
template <typename FloatType>
|
||||
ValueType proportionOfHeight (FloatType proportion) const noexcept
|
||||
{
|
||||
return static_cast<ValueType> ((FloatType) h * proportion);
|
||||
}
|
||||
|
||||
/** Returns a rectangle based on some proportional coordinates relative to this one.
|
||||
So for example getProportion ({ 0.25f, 0.25f, 0.5f, 0.5f }) would return a rectangle
|
||||
of half the original size, with the same centre.
|
||||
*/
|
||||
template <typename FloatType>
|
||||
Rectangle getProportion (Rectangle<FloatType> proportionalRect) const noexcept
|
||||
{
|
||||
return { pos.x + static_cast<ValueType> (w * proportionalRect.pos.x),
|
||||
pos.y + static_cast<ValueType> (h * proportionalRect.pos.y),
|
||||
proportionOfWidth (proportionalRect.w),
|
||||
proportionOfHeight (proportionalRect.h) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the two rectangles are identical. */
|
||||
bool operator== (const Rectangle& other) const noexcept { return pos == other.pos && w == other.w && h == other.h; }
|
||||
|
||||
/** Returns true if the two rectangles are not identical. */
|
||||
bool operator!= (const Rectangle& other) const noexcept { return pos != other.pos || w != other.w || h != other.h; }
|
||||
|
||||
/** Returns true if this coordinate is inside the rectangle. */
|
||||
bool contains (ValueType xCoord, ValueType yCoord) const noexcept
|
||||
{
|
||||
return xCoord >= pos.x && yCoord >= pos.y && xCoord < pos.x + w && yCoord < pos.y + h;
|
||||
}
|
||||
|
||||
/** Returns true if this coordinate is inside the rectangle. */
|
||||
bool contains (Point<ValueType> point) const noexcept
|
||||
{
|
||||
return point.x >= pos.x && point.y >= pos.y && point.x < pos.x + w && point.y < pos.y + h;
|
||||
}
|
||||
|
||||
/** Returns true if this other rectangle is completely inside this one. */
|
||||
bool contains (Rectangle other) const noexcept
|
||||
{
|
||||
return pos.x <= other.pos.x && pos.y <= other.pos.y
|
||||
&& pos.x + w >= other.pos.x + other.w && pos.y + h >= other.pos.y + other.h;
|
||||
}
|
||||
|
||||
/** Returns true if any part of another rectangle overlaps this one. */
|
||||
bool intersects (Rectangle other) const noexcept
|
||||
{
|
||||
return pos.x + w > other.pos.x
|
||||
&& pos.y + h > other.pos.y
|
||||
&& pos.x < other.pos.x + other.w
|
||||
&& pos.y < other.pos.y + other.h
|
||||
&& w > ValueType() && h > ValueType()
|
||||
&& other.w > ValueType() && other.h > ValueType();
|
||||
}
|
||||
|
||||
/** Returns true if any part of the given line lies inside this rectangle. */
|
||||
bool intersects (const Line<ValueType>& line) const noexcept
|
||||
{
|
||||
return contains (line.getStart()) || contains (line.getEnd())
|
||||
|| line.intersects (Line<ValueType> (getTopLeft(), getTopRight()))
|
||||
|| line.intersects (Line<ValueType> (getTopRight(), getBottomRight()))
|
||||
|| line.intersects (Line<ValueType> (getBottomRight(), getBottomLeft()))
|
||||
|| line.intersects (Line<ValueType> (getBottomLeft(), getTopLeft()));
|
||||
}
|
||||
|
||||
/** Returns the region that is the overlap between this and another rectangle.
|
||||
If the two rectangles don't overlap, the rectangle returned will be empty.
|
||||
*/
|
||||
Rectangle getIntersection (Rectangle other) const noexcept
|
||||
{
|
||||
auto nx = jmax (pos.x, other.pos.x);
|
||||
auto ny = jmax (pos.y, other.pos.y);
|
||||
auto nw = jmin (pos.x + w, other.pos.x + other.w) - nx;
|
||||
|
||||
if (nw >= ValueType())
|
||||
{
|
||||
auto nh = jmin (pos.y + h, other.pos.y + other.h) - ny;
|
||||
|
||||
if (nh >= ValueType())
|
||||
return { nx, ny, nw, nh };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Clips a set of rectangle coordinates so that they lie only within this one.
|
||||
This is a non-static version of intersectRectangles().
|
||||
Returns false if the two rectangles didn't overlap.
|
||||
*/
|
||||
bool intersectRectangle (ValueType& otherX, ValueType& otherY, ValueType& otherW, ValueType& otherH) const noexcept
|
||||
{
|
||||
auto maxX = jmax (otherX, pos.x);
|
||||
otherW = jmin (otherX + otherW, pos.x + w) - maxX;
|
||||
|
||||
if (otherW > ValueType())
|
||||
{
|
||||
auto maxY = jmax (otherY, pos.y);
|
||||
otherH = jmin (otherY + otherH, pos.y + h) - maxY;
|
||||
|
||||
if (otherH > ValueType())
|
||||
{
|
||||
otherX = maxX; otherY = maxY;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Clips a rectangle so that it lies only within this one.
|
||||
Returns false if the two rectangles didn't overlap.
|
||||
*/
|
||||
bool intersectRectangle (Rectangle<ValueType>& rectangleToClip) const noexcept
|
||||
{
|
||||
return intersectRectangle (rectangleToClip.pos.x, rectangleToClip.pos.y,
|
||||
rectangleToClip.w, rectangleToClip.h);
|
||||
}
|
||||
|
||||
/** Returns the smallest rectangle that contains both this one and the one passed-in.
|
||||
|
||||
If either this or the other rectangle are empty, they will not be counted as
|
||||
part of the resulting region.
|
||||
*/
|
||||
Rectangle getUnion (Rectangle other) const noexcept
|
||||
{
|
||||
if (other.isEmpty()) return *this;
|
||||
if (isEmpty()) return other;
|
||||
|
||||
auto newX = jmin (pos.x, other.pos.x);
|
||||
auto newY = jmin (pos.y, other.pos.y);
|
||||
|
||||
return { newX, newY,
|
||||
jmax (pos.x + w, other.pos.x + other.w) - newX,
|
||||
jmax (pos.y + h, other.pos.y + other.h) - newY };
|
||||
}
|
||||
|
||||
/** If this rectangle merged with another one results in a simple rectangle, this
|
||||
will set this rectangle to the result, and return true.
|
||||
|
||||
Returns false and does nothing to this rectangle if the two rectangles don't overlap,
|
||||
or if they form a complex region.
|
||||
*/
|
||||
bool enlargeIfAdjacent (Rectangle other) noexcept
|
||||
{
|
||||
if (pos.x == other.pos.x && getRight() == other.getRight()
|
||||
&& (other.getBottom() >= pos.y && other.pos.y <= getBottom()))
|
||||
{
|
||||
auto newY = jmin (pos.y, other.pos.y);
|
||||
h = jmax (getBottom(), other.getBottom()) - newY;
|
||||
pos.y = newY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pos.y == other.pos.y && getBottom() == other.getBottom()
|
||||
&& (other.getRight() >= pos.x && other.pos.x <= getRight()))
|
||||
{
|
||||
auto newX = jmin (pos.x, other.pos.x);
|
||||
w = jmax (getRight(), other.getRight()) - newX;
|
||||
pos.x = newX;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** If after removing another rectangle from this one the result is a simple rectangle,
|
||||
this will set this object's bounds to be the result, and return true.
|
||||
|
||||
Returns false and does nothing to this rectangle if the two rectangles don't overlap,
|
||||
or if removing the other one would form a complex region.
|
||||
*/
|
||||
bool reduceIfPartlyContainedIn (Rectangle other) noexcept
|
||||
{
|
||||
int inside = 0;
|
||||
auto otherR = other.getRight();
|
||||
if (pos.x >= other.pos.x && pos.x < otherR) inside = 1;
|
||||
auto otherB = other.getBottom();
|
||||
if (pos.y >= other.pos.y && pos.y < otherB) inside |= 2;
|
||||
auto r = pos.x + w;
|
||||
if (r >= other.pos.x && r < otherR) inside |= 4;
|
||||
auto b = pos.y + h;
|
||||
if (b >= other.pos.y && b < otherB) inside |= 8;
|
||||
|
||||
switch (inside)
|
||||
{
|
||||
case 1 + 2 + 8: w = r - otherR; pos.x = otherR; return true;
|
||||
case 1 + 2 + 4: h = b - otherB; pos.y = otherB; return true;
|
||||
case 2 + 4 + 8: w = other.pos.x - pos.x; return true;
|
||||
case 1 + 4 + 8: h = other.pos.y - pos.y; return true;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Tries to fit this rectangle within a target area, returning the result.
|
||||
|
||||
If this rectangle is not completely inside the target area, then it'll be
|
||||
shifted (without changing its size) so that it lies within the target. If it
|
||||
is larger than the target rectangle in either dimension, then that dimension
|
||||
will be reduced to fit within the target.
|
||||
*/
|
||||
Rectangle constrainedWithin (Rectangle areaToFitWithin) const noexcept
|
||||
{
|
||||
auto newPos = areaToFitWithin.withSize (areaToFitWithin.getWidth() - w,
|
||||
areaToFitWithin.getHeight() - h)
|
||||
.getConstrainedPoint (pos);
|
||||
|
||||
return { newPos.x, newPos.y,
|
||||
jmin (w, areaToFitWithin.getWidth()),
|
||||
jmin (h, areaToFitWithin.getHeight()) };
|
||||
}
|
||||
|
||||
/** Returns the smallest rectangle that can contain the shape created by applying
|
||||
a transform to this rectangle.
|
||||
|
||||
This should only be used on floating point rectangles.
|
||||
*/
|
||||
Rectangle transformedBy (const AffineTransform& transform) const noexcept
|
||||
{
|
||||
using FloatType = typename TypeHelpers::SmallestFloatType<ValueType>::type;
|
||||
|
||||
auto x1 = static_cast<FloatType> (pos.x), y1 = static_cast<FloatType> (pos.y);
|
||||
auto x2 = static_cast<FloatType> (pos.x + w), y2 = static_cast<FloatType> (pos.y);
|
||||
auto x3 = static_cast<FloatType> (pos.x), y3 = static_cast<FloatType> (pos.y + h);
|
||||
auto x4 = static_cast<FloatType> (x2), y4 = static_cast<FloatType> (y3);
|
||||
|
||||
transform.transformPoints (x1, y1, x2, y2);
|
||||
transform.transformPoints (x3, y3, x4, y4);
|
||||
|
||||
auto rx1 = jmin (x1, x2, x3, x4);
|
||||
auto rx2 = jmax (x1, x2, x3, x4);
|
||||
auto ry1 = jmin (y1, y2, y3, y4);
|
||||
auto ry2 = jmax (y1, y2, y3, y4);
|
||||
|
||||
Rectangle r;
|
||||
Rectangle<FloatType> (rx1, ry1, rx2 - rx1, ry2 - ry1).copyWithRounding (r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Returns the smallest integer-aligned rectangle that completely contains this one.
|
||||
This is only relevant for floating-point rectangles, of course.
|
||||
@see toFloat(), toNearestInt(), toNearestIntEdges()
|
||||
*/
|
||||
Rectangle<int> getSmallestIntegerContainer() const noexcept
|
||||
{
|
||||
return Rectangle<int>::leftTopRightBottom (floorAsInt (pos.x),
|
||||
floorAsInt (pos.y),
|
||||
ceilAsInt (pos.x + w),
|
||||
ceilAsInt (pos.y + h));
|
||||
}
|
||||
|
||||
/** Casts this rectangle to a Rectangle<int>.
|
||||
This uses roundToInt to snap x, y, width and height to the nearest integer (losing precision).
|
||||
If the rectangle already uses integers, this will simply return a copy.
|
||||
@see getSmallestIntegerContainer(), toNearestIntEdges()
|
||||
*/
|
||||
Rectangle<int> toNearestInt() const noexcept
|
||||
{
|
||||
return { roundToInt (pos.x), roundToInt (pos.y),
|
||||
roundToInt (w), roundToInt (h) };
|
||||
}
|
||||
|
||||
/** Casts this rectangle to a Rectangle<int>.
|
||||
This uses roundToInt to snap top, left, right and bottom to the nearest integer (losing precision).
|
||||
If the rectangle already uses integers, this will simply return a copy.
|
||||
@see getSmallestIntegerContainer(), toNearestInt()
|
||||
*/
|
||||
Rectangle<int> toNearestIntEdges() const noexcept
|
||||
{
|
||||
return Rectangle<int>::leftTopRightBottom (roundToInt (pos.x), roundToInt (pos.y),
|
||||
roundToInt (getRight()), roundToInt (getBottom()));
|
||||
}
|
||||
|
||||
/** Casts this rectangle to a Rectangle<float>.
|
||||
@see getSmallestIntegerContainer
|
||||
*/
|
||||
Rectangle<float> toFloat() const noexcept
|
||||
{
|
||||
return { static_cast<float> (pos.x), static_cast<float> (pos.y),
|
||||
static_cast<float> (w), static_cast<float> (h) };
|
||||
}
|
||||
|
||||
/** Casts this rectangle to a Rectangle<double>.
|
||||
@see getSmallestIntegerContainer
|
||||
*/
|
||||
Rectangle<double> toDouble() const noexcept
|
||||
{
|
||||
return { static_cast<double> (pos.x), static_cast<double> (pos.y),
|
||||
static_cast<double> (w), static_cast<double> (h) };
|
||||
}
|
||||
|
||||
/** Casts this rectangle to a Rectangle with the given type.
|
||||
If the target type is a conversion from float to int, then the conversion
|
||||
will be done using getSmallestIntegerContainer().
|
||||
*/
|
||||
template <typename TargetType>
|
||||
Rectangle<TargetType> toType() const noexcept
|
||||
{
|
||||
Rectangle<TargetType> r;
|
||||
copyWithRounding (r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Returns the smallest Rectangle that can contain a set of points. */
|
||||
static Rectangle findAreaContainingPoints (const Point<ValueType>* points, int numPoints) noexcept
|
||||
{
|
||||
if (numPoints <= 0)
|
||||
return {};
|
||||
|
||||
auto minX = points[0].x;
|
||||
auto maxX = minX;
|
||||
auto minY = points[0].y;
|
||||
auto maxY = minY;
|
||||
|
||||
for (int i = 1; i < numPoints; ++i)
|
||||
{
|
||||
minX = jmin (minX, points[i].x);
|
||||
maxX = jmax (maxX, points[i].x);
|
||||
minY = jmin (minY, points[i].y);
|
||||
maxY = jmax (maxY, points[i].y);
|
||||
}
|
||||
|
||||
return { minX, minY, maxX - minX, maxY - minY };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Static utility to intersect two sets of rectangular coordinates.
|
||||
Returns false if the two regions didn't overlap.
|
||||
@see intersectRectangle
|
||||
*/
|
||||
static bool intersectRectangles (ValueType& x1, ValueType& y1, ValueType& w1, ValueType& h1,
|
||||
ValueType x2, ValueType y2, ValueType w2, ValueType h2) noexcept
|
||||
{
|
||||
auto x = jmax (x1, x2);
|
||||
w1 = jmin (x1 + w1, x2 + w2) - x;
|
||||
|
||||
if (w1 > ValueType())
|
||||
{
|
||||
auto y = jmax (y1, y2);
|
||||
h1 = jmin (y1 + h1, y2 + h2) - y;
|
||||
|
||||
if (h1 > ValueType())
|
||||
{
|
||||
x1 = x; y1 = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a string describing this rectangle.
|
||||
|
||||
The string will be of the form "x y width height", e.g. "100 100 400 200".
|
||||
|
||||
Coupled with the fromString() method, this is very handy for things like
|
||||
storing rectangles (particularly component positions) in XML attributes.
|
||||
|
||||
@see fromString
|
||||
*/
|
||||
String toString() const
|
||||
{
|
||||
String s;
|
||||
s.preallocateBytes (32);
|
||||
s << pos.x << ' ' << pos.y << ' ' << w << ' ' << h;
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Parses a string containing a rectangle's details.
|
||||
|
||||
The string should contain 4 integer tokens, in the form "x y width height". They
|
||||
can be comma or whitespace separated.
|
||||
|
||||
This method is intended to go with the toString() method, to form an easy way
|
||||
of saving/loading rectangles as strings.
|
||||
|
||||
@see toString
|
||||
*/
|
||||
static Rectangle fromString (StringRef stringVersion)
|
||||
{
|
||||
StringArray toks;
|
||||
toks.addTokens (stringVersion.text.findEndOfWhitespace(), ",; \t\r\n", "");
|
||||
|
||||
return { parseIntAfterSpace (toks[0]),
|
||||
parseIntAfterSpace (toks[1]),
|
||||
parseIntAfterSpace (toks[2]),
|
||||
parseIntAfterSpace (toks[3]) };
|
||||
}
|
||||
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated ("This has been renamed to transformedBy in order to match the method names used in the Point class.")]]
|
||||
Rectangle transformed (const AffineTransform& t) const noexcept { return transformedBy (t); }
|
||||
#endif
|
||||
|
||||
private:
|
||||
template <typename OtherType> friend class Rectangle;
|
||||
|
||||
Point<ValueType> pos;
|
||||
ValueType w {}, h {};
|
||||
|
||||
static ValueType parseIntAfterSpace (StringRef s) noexcept
|
||||
{ return static_cast<ValueType> (s.text.findEndOfWhitespace().getIntValue32()); }
|
||||
|
||||
void copyWithRounding (Rectangle<int>& result) const noexcept { result = getSmallestIntegerContainer(); }
|
||||
void copyWithRounding (Rectangle<float>& result) const noexcept { result = toFloat(); }
|
||||
void copyWithRounding (Rectangle<double>& result) const noexcept { result = toDouble(); }
|
||||
|
||||
static int floorAsInt (int n) noexcept { return n; }
|
||||
static int floorAsInt (float n) noexcept { return n > (float) std::numeric_limits<int>::min() ? (int) std::floor (n) : std::numeric_limits<int>::min(); }
|
||||
static int floorAsInt (double n) noexcept { return n > (double) std::numeric_limits<int>::min() ? (int) std::floor (n) : std::numeric_limits<int>::min(); }
|
||||
static int ceilAsInt (int n) noexcept { return n; }
|
||||
static int ceilAsInt (float n) noexcept { return n < (float) std::numeric_limits<int>::max() ? (int) std::ceil (n) : std::numeric_limits<int>::max(); }
|
||||
static int ceilAsInt (double n) noexcept { return n < (double) std::numeric_limits<int>::max() ? (int) std::ceil (n) : std::numeric_limits<int>::max(); }
|
||||
};
|
||||
|
||||
} // namespace juce
|
653
deps/juce/modules/juce_graphics/geometry/juce_RectangleList.h
vendored
Normal file
653
deps/juce/modules/juce_graphics/geometry/juce_RectangleList.h
vendored
Normal file
@ -0,0 +1,653 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 (RectangleType rect)
|
||||
{
|
||||
if (auto numRects = rects.size())
|
||||
{
|
||||
auto x1 = rect.getX();
|
||||
auto y1 = rect.getY();
|
||||
auto x2 = x1 + rect.getWidth();
|
||||
auto y2 = y1 + rect.getHeight();
|
||||
|
||||
for (int i = numRects; --i >= 0;)
|
||||
{
|
||||
auto& r = rects.getReference (i);
|
||||
|
||||
auto rx1 = r.getX();
|
||||
auto ry1 = r.getY();
|
||||
auto rx2 = rx1 + r.getWidth();
|
||||
auto ry2 = ry1 + r.getHeight();
|
||||
|
||||
if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2))
|
||||
{
|
||||
if (x1 > rx1 && x1 < rx2)
|
||||
{
|
||||
if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2)
|
||||
{
|
||||
r.setWidth (x1 - rx1);
|
||||
}
|
||||
else
|
||||
{
|
||||
r.setX (x1);
|
||||
r.setWidth (rx2 - x1);
|
||||
|
||||
rects.insert (++i, RectangleType (rx1, ry1, x1 - rx1, ry2 - ry1));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else if (x2 > rx1 && x2 < rx2)
|
||||
{
|
||||
r.setX (x2);
|
||||
r.setWidth (rx2 - x2);
|
||||
|
||||
if (y1 > ry1 || y2 < ry2 || x1 > rx1)
|
||||
{
|
||||
rects.insert (++i, RectangleType (rx1, ry1, x2 - rx1, ry2 - ry1));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else if (y1 > ry1 && y1 < ry2)
|
||||
{
|
||||
if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2)
|
||||
{
|
||||
r.setHeight (y1 - ry1);
|
||||
}
|
||||
else
|
||||
{
|
||||
r.setY (y1);
|
||||
r.setHeight (ry2 - y1);
|
||||
|
||||
rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y1 - ry1));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else if (y2 > ry1 && y2 < ry2)
|
||||
{
|
||||
r.setY (y2);
|
||||
r.setHeight (ry2 - y2);
|
||||
|
||||
if (x1 > rx1 || x2 < rx2 || y1 > ry1)
|
||||
{
|
||||
rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y2 - ry1));
|
||||
++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
|
Reference in New Issue
Block a user