From cef006904e875b7424b7fa13ea3dc2f9762081d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pasi=20Kera=CC=88nen?= Date: Fri, 12 Apr 2019 12:47:08 +0300 Subject: Flatten OpenGL source directory hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flatten Source,Include and PrivateInclude directories away. Replace camelcase directory names with lowercase names. Remove random Qt3DS prefixes from directory names. Task-number: QT3DS-16 Change-Id: I5b23349232c760afa9a8efcc5e8945ee19d2e31a Reviewed-by: Pasi Keränen --- .../Source/runtimerender/Qt3DSRenderPathMath.h | 713 +++++++++++++++++++++ 1 file changed, 713 insertions(+) create mode 100644 src/Runtime/Source/runtimerender/Qt3DSRenderPathMath.h (limited to 'src/Runtime/Source/runtimerender/Qt3DSRenderPathMath.h') diff --git a/src/Runtime/Source/runtimerender/Qt3DSRenderPathMath.h b/src/Runtime/Source/runtimerender/Qt3DSRenderPathMath.h new file mode 100644 index 00000000..e9eb2220 --- /dev/null +++ b/src/Runtime/Source/runtimerender/Qt3DSRenderPathMath.h @@ -0,0 +1,713 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2012 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QT3DS_RENDER_PATH_MATH_H +#define QT3DS_RENDER_PATH_MATH_H +#include "Qt3DSRender.h" +#include "foundation/Qt3DSVec2.h" +#include "foundation/Qt3DSVec3.h" +namespace qt3ds { +namespace render { +namespace path { +// Solve quadratic equation in with a templated real number system. +template + +int quadratic(REAL b, REAL c, REAL rts[2]) +{ + int nquad; + REAL dis; + REAL rtdis; + + dis = b * b - 4 * c; + rts[0] = 0; + rts[1] = 0; + if (b == 0) { + if (c == 0) { + nquad = 2; + } else { + if (c < 0) { + nquad = 2; + rts[0] = sqrt(-c); + rts[1] = -rts[0]; + } else { + nquad = 0; + } + } + } else if (c == 0) { + nquad = 2; + rts[0] = -b; + } else if (dis >= 0) { + nquad = 2; + rtdis = sqrt(dis); + if (b > 0) + rts[0] = (-b - rtdis) * (1 / REAL(2)); + else + rts[0] = (-b + rtdis) * (1 / REAL(2)); + if (rts[0] == 0) + rts[1] = -b; + else + rts[1] = c / rts[0]; + } else { + nquad = 0; + } + + return (nquad); +} /* quadratic */ + +float interest_range[2] = {0, 1}; + +void cubicInflectionPoint(const QT3DSVec2 cp[4], nvvector &key_point) +{ + // Convert control points to cubic monomial polynomial coefficients + const QT3DSVec2 A = cp[3] - cp[0] + (cp[1] - cp[2]) * 3.0; + const QT3DSVec2 B = (cp[0] - cp[1] * 2.0 + cp[2]) * 3.0, C = (cp[1] - cp[0]) * 3.0; + const QT3DSVec2 D = cp[0]; + + double a = 3 * (B.x * A.y - A.x * B.y); + double b = 3 * (C.x * A.y - C.y * A.x); + double c = C.x * B.y - C.y * B.x; + + double roots[2]; + int solutions; + // Is the quadratic really a degenerate line? + if (a == 0) { + // Is the line really a degenerate point? + if (b == 0) { + solutions = 0; + } else { + solutions = 1; + roots[0] = c / b; + } + } else { + solutions = quadratic(b / a, c / a, roots); + } + for (int i = 0; i < solutions; i++) { + QT3DSF32 t = static_cast(roots[i]); + + QT3DSVec2 p = ((A * t + B) * t + C) * t + D; + if (t >= interest_range[0] && t <= interest_range[1]) + key_point.push_back(t); + // else; Outside range of interest, ignore. + } +} + +typedef enum { + CT_POINT, + CT_LINE, + CT_QUADRATIC, + CT_CUSP, + CT_LOOP, + CT_SERPENTINE +} CurveType; + +static inline bool isZero(double v) +{ +#if 0 + const double eps = 6e-008; + + if (fabs(v) < eps) + return true; + else + return false; +#else + return v == 0.0; +#endif +} + +inline QT3DSVec3 crossv1(const QT3DSVec2 &a, const QT3DSVec2 &b) +{ + return QT3DSVec3(a[1] - b[1], b[0] - a[0], a[0] * b[1] - a[1] * b[0]); +} + +inline bool sameVertex(const QT3DSVec2 &a, const QT3DSVec2 &b) +{ + return (a.x == b.x && a.y == b.y); +} + +inline bool sameVertex(const QT3DSVec3 &a, const QT3DSVec3 &b) +{ + return (a.x == b.x && a.y == b.y && a.z == b.z); +} + +// This function "normalizes" the input vector so the larger of its components +// is in the range [512,1024]. Exploit integer math on the exponent bits to +// do this without expensive DP exponentiation. +inline void scaleTo512To1024(QT3DSVec2 &d, int e) +{ + union { + QT3DSU64 u64; + double f64; + } x; + int ie = 10 - (int)e + 1023; + QT3DS_ASSERT(ie > 0); + x.u64 = ((QT3DSU64)ie) << 52; + d *= static_cast(x.f64); +} + +inline double fastfrexp(double d, int *exponent) +{ + union { + QT3DSU64 u64; + double f64; + } x; + x.f64 = d; + *exponent = (((int)(x.u64 >> 52)) & 0x7ff) - 0x3ff; + x.u64 &= (1ULL << 63) - (1ULL << 52); + x.u64 |= (0x3ffULL << 52); + return x.f64; +} + +QT3DSVec3 CreateVec3(QT3DSVec2 xy, float z) +{ + return QT3DSVec3(xy.x, xy.y, z); +} + +QT3DSVec2 GetXY(const QT3DSVec3 &data) +{ + return QT3DSVec2(data.x, data.y); +} + +CurveType cubicDoublePoint(const QT3DSVec2 points[4], nvvector &key_point) +{ +#if 0 + const QT3DSVec2 AA = points[3] - points[0] + (points[1] - points[2]) * 3.0; + const QT3DSVec3 BB = (points[0] - points[1] * 2.0 + points[2]) * 3.0; + const QT3DSVec3 CC = (points[1] - points[0]) * 3.0, DD = points[0]; +#endif + + // Assume control points of the cubic curve are A, B, C, and D. + const QT3DSVec3 A = CreateVec3(points[0], 1); + const QT3DSVec3 B = CreateVec3(points[1], 1); + const QT3DSVec3 C = CreateVec3(points[2], 1); + const QT3DSVec3 D = CreateVec3(points[3], 1); + + // Compute the discriminant of the roots of + // H(s,t) = -36*(d1^2*s^2 - d1*d2*s*t + (d2^2 - d1*d3)*t^2) + // where H is the Hessian (the square matrix of second-order + // partial derivatives of a function) of I(s,t) + // where I(s,t) determine the inflection points of the cubic + // Bezier curve C(s,t). + // + // d1, d2, and d3 functions of the determinants constructed + // from the cubic control points. + // + // Recall dot(a,cross(b,c)) is determinant of a 3x3 matrix + // with a, b, c the rows of the matrix. + const QT3DSVec3 DC = crossv1(GetXY(D), GetXY(C)); + const QT3DSVec3 AD = crossv1(GetXY(A), GetXY(D)); + const QT3DSVec3 BA = crossv1(GetXY(B), GetXY(A)); + + const double a1 = A.dot(DC); + const double a2 = B.dot(AD); + const double a3 = C.dot(BA); + const double d1 = a1 - 2 * a2 + 3 * a3; + const double d2 = -a2 + 3 * a3; + const double d3 = 3 * a3; + const double discriminant = (3 * d2 * d2 - 4 * d1 * d3); + + // The sign of the discriminant of I classifies the curbic curve + // C into one of 6 classifications: + // 1) discriminant>0 ==> serpentine + // 2) discriminant=0 ==> cusp + // 3) discriminant<0 ==> loop + + // If the discriminant or d1 are almost but not exactly zero, the + // result is really noisy unacceptable (k,l,m) interpolation. + // If it looks almost like a quadratic or linear case, treat it that way. + if (isZero(discriminant) && isZero(d1)) { + // Cusp case + + if (isZero(d2)) { + // degenerate cases (points, lines, quadratics)... + if (isZero(d3)) { + if (sameVertex(A, B) && sameVertex(A, C) && sameVertex(A, D)) + return CT_POINT; + else + return CT_LINE; + } else { + return CT_QUADRATIC; + } + } else { + return CT_CUSP; + } + } else if (discriminant < 0) { + // Loop case + + const QT3DSF32 t = static_cast(d2 + sqrt(-discriminant)); + QT3DSVec2 d = QT3DSVec2(t, static_cast(2 * d1)); + QT3DSVec2 e = QT3DSVec2(static_cast(2 * (d2 * d2 - d1 * d3)), + static_cast(d1 * t)); + + // There is the situation where r2=c/t results in division by zero, but + // in this case, the two roots represent a double root at zero so + // subsitute l for (the otherwise NaN) m in this case. + // + // This situation can occur when the 1st and 2nd (or 3rd and 4th?) + // control point of a cubic Bezier path SubPath are identical. + if (e.x == 0 && e.y == 0) + e = d; + + // d, e, or both could be very large values. To mitigate the risk of + // floating-point overflow in subsequent calculations + // scale both vectors to be in the range [768,1024] since their relative + // scale of their x & y components is irrelevant. + + // Be careful to divide by a power-of-two to disturb mantissa bits. + + double d_max_mag = NVMax(fabs(d.x), fabs(d.y)); + int exponent; + fastfrexp(d_max_mag, &exponent); + scaleTo512To1024(d, exponent); + + double e_max_mag = NVMax(fabs(e.x), fabs(e.y)); + fastfrexp(e_max_mag, &exponent); + scaleTo512To1024(e, exponent); + + const QT3DSVec2 roots = QT3DSVec2(d.x / d.y, e.x / e.y); + + double tt; +#if 0 + tt = roots[0]; + if (tt >= interest_range[0] && tt <= interest_range[1]) + // key_point.push_back(tt); + tt = roots[1]; + if (tt >= interest_range[0] && tt <= interest_range[1]) + // key_point.push_back(tt); +#endif + tt = (roots[0] + roots[1]) / 2; + if (tt >= interest_range[0] && tt <= interest_range[1]) + key_point.push_back(static_cast(tt)); + + return CT_LOOP; + } else { + QT3DS_ASSERT(discriminant >= 0); + cubicInflectionPoint(points, key_point); + if (discriminant > 0) { + // Serpentine case + return CT_SERPENTINE; + } else { + // Cusp with inflection at infinity (treat like serpentine) + return CT_CUSP; + } + } +} + +QT3DSVec4 CreateVec4(QT3DSVec2 p1, QT3DSVec2 p2) +{ + return QT3DSVec4(p1.x, p1.y, p2.x, p2.y); +} + +QT3DSVec2 lerp(QT3DSVec2 p1, QT3DSVec2 p2, QT3DSF32 distance) +{ + return p1 + (p2 - p1) * distance; +} + +QT3DSF32 lerp(QT3DSF32 p1, QT3DSF32 p2, QT3DSF32 distance) +{ + return p1 + (p2 - p1) * distance; +} + +// Using first derivative to get tangent. +// If this equation does not make immediate sense consider that it is the first derivative +// of the de Casteljau bezier expansion, not the polynomial expansion. +float TangentAt(float inT, float p1, float c1, float c2, float p2) +{ + float a = c1 - p1; + float b = c2 - c1 - a; + float c = p2 - c2 - a - (2.0f * b); + float retval = 3.0f * (a + (2.0f * b * inT) + (c * inT * inT)); + return retval; +} + +QT3DSVec2 midpoint(QT3DSVec2 p1, QT3DSVec2 p2) +{ + return lerp(p1, p2, .5f); +} + +QT3DSF32 LineLength(QT3DSVec2 inStart, QT3DSVec2 inStop) +{ + return (inStop - inStart).magnitude(); +} + +struct SCubicBezierCurve +{ + QT3DSVec2 m_Points[4]; + SCubicBezierCurve(QT3DSVec2 a1, QT3DSVec2 c1, QT3DSVec2 c2, QT3DSVec2 a2) + { + m_Points[0] = a1; + m_Points[1] = c1; + m_Points[2] = c2; + m_Points[3] = a2; + } + + // Normal is of course orthogonal to the tangent. + QT3DSVec2 NormalAt(float inT) const + { + QT3DSVec2 tangent = QT3DSVec2( + TangentAt(inT, m_Points[0].x, m_Points[1].x, m_Points[2].x, m_Points[3].x), + TangentAt(inT, m_Points[0].y, m_Points[1].y, m_Points[2].y, m_Points[3].y)); + + QT3DSVec2 result(tangent.y, -tangent.x); + result.normalize(); + return result; + } + + eastl::pair SplitCubicBezierCurve(float inT) + { + // compute point on curve based on inT + // using de Casteljau algorithm + QT3DSVec2 p12 = lerp(m_Points[0], m_Points[1], inT); + QT3DSVec2 p23 = lerp(m_Points[1], m_Points[2], inT); + QT3DSVec2 p34 = lerp(m_Points[2], m_Points[3], inT); + QT3DSVec2 p123 = lerp(p12, p23, inT); + QT3DSVec2 p234 = lerp(p23, p34, inT); + QT3DSVec2 p1234 = lerp(p123, p234, inT); + + return eastl::make_pair(SCubicBezierCurve(m_Points[0], p12, p123, p1234), + SCubicBezierCurve(p1234, p234, p34, m_Points[3])); + } +}; + +#if 0 + static QT3DSVec2 NormalToLine( QT3DSVec2 startPoint, QT3DSVec2 endPoint ) + { + QT3DSVec2 lineDxDy = endPoint - startPoint; + QT3DSVec2 result( lineDxDy.y, -lineDxDy.x ); + result.normalize(); + return result; + } +#endif + +struct SResultCubic +{ + enum Mode { + Normal = 0, + BeginTaper = 1, + EndTaper = 2, + }; + QT3DSVec2 m_P1; + QT3DSVec2 m_C1; + QT3DSVec2 m_C2; + QT3DSVec2 m_P2; + // Location in the original data where this cubic is taken from + QT3DSU32 m_EquationIndex; + QT3DSF32 m_TStart; + QT3DSF32 m_TStop; + QT3DSF32 m_Length; + QT3DSVec2 m_TaperMultiplier; // normally 1, goes to zero at very end of taper if any taper. + Mode m_Mode; + + SResultCubic(QT3DSVec2 inP1, QT3DSVec2 inC1, QT3DSVec2 inC2, QT3DSVec2 inP2, + QT3DSU32 equationIndex, QT3DSF32 tStart, QT3DSF32 tStop, QT3DSF32 length) + : m_P1(inP1) + , m_C1(inC1) + , m_C2(inC2) + , m_P2(inP2) + , m_EquationIndex(equationIndex) + , m_TStart(tStart) + , m_TStop(tStop) + , m_Length(length) + , m_TaperMultiplier(1.0f, 1.0f) + , m_Mode(Normal) + { + } + // Note the vec2 items are *not* initialized in any way here. + SResultCubic() {} + QT3DSF32 GetP1Width(QT3DSF32 inPathWidth, QT3DSF32 beginTaperWidth, QT3DSF32 endTaperWidth) + { + return GetPathWidth(inPathWidth, beginTaperWidth, endTaperWidth, 0); + } + + QT3DSF32 GetP2Width(QT3DSF32 inPathWidth, QT3DSF32 beginTaperWidth, QT3DSF32 endTaperWidth) + { + return GetPathWidth(inPathWidth, beginTaperWidth, endTaperWidth, 1); + } + + QT3DSF32 GetPathWidth(QT3DSF32 inPathWidth, QT3DSF32 beginTaperWidth, QT3DSF32 endTaperWidth, + QT3DSU32 inTaperIndex) + { + QT3DSF32 retval = inPathWidth; + switch (m_Mode) { + case BeginTaper: + retval = beginTaperWidth * m_TaperMultiplier[inTaperIndex]; + break; + case EndTaper: + retval = endTaperWidth * m_TaperMultiplier[inTaperIndex]; + break; + default: + break; + } + return retval; + } +}; + +void PushLine(nvvector &ioResultVec, QT3DSVec2 inStart, QT3DSVec2 inStop, + QT3DSU32 inEquationIndex) +{ + QT3DSVec2 range = inStop - inStart; + ioResultVec.push_back(SResultCubic(inStart, inStart + range * .333f, + inStart + range * .666f, inStop, inEquationIndex, + 0.0f, 1.0f, LineLength(inStart, inStop))); +} + +struct PathDirtyFlagValues +{ + enum Enum { + SourceData = 1, + PathType = 1 << 1, + Width = 1 << 2, + BeginTaper = 1 << 3, + EndTaper = 1 << 4, + CPUError = 1 << 5, + }; +}; + +struct SPathDirtyFlags : public NVFlags +{ + typedef NVFlags TBase; + SPathDirtyFlags() {} + SPathDirtyFlags(int inFlags) + : TBase(static_cast(inFlags)) + { + } + void Clear() + { + *this = SPathDirtyFlags(); + } +}; + +struct STaperInformation +{ + QT3DSF32 m_CapOffset; + QT3DSF32 m_CapOpacity; + QT3DSF32 m_CapWidth; + + STaperInformation() + : m_CapOffset(0) + , m_CapOpacity(0) + , m_CapWidth(0) + { + } + STaperInformation(QT3DSF32 capOffset, QT3DSF32 capOpacity, QT3DSF32 capWidth) + : m_CapOffset(capOffset) + , m_CapOpacity(capOpacity) + , m_CapWidth(capWidth) + { + } + + bool operator==(const STaperInformation &inOther) const + { + return m_CapOffset == inOther.m_CapOffset && m_CapOpacity == inOther.m_CapOpacity + && m_CapWidth == inOther.m_CapWidth; + } +}; + +template +bool OptionEquals(const Option &lhs, const Option &rhs) +{ + if (lhs.hasValue() != rhs.hasValue()) + return false; + if (lhs.hasValue()) + return lhs.getValue() == rhs.getValue(); + return true; +} +void OuterAdaptiveSubdivideBezierCurve(nvvector &ioResultVec, + nvvector &keyPointVec, + SCubicBezierCurve inCurve, QT3DSF32 inLinearError, + QT3DSU32 inEquationIndex); + +void AdaptiveSubdivideBezierCurve(nvvector &ioResultVec, + SCubicBezierCurve &inCurve, QT3DSF32 inLinearError, + QT3DSU32 inEquationIndex, QT3DSF32 inTStart, QT3DSF32 inTStop); + +// Adaptively subdivide source data to produce m_PatchData. +void AdaptiveSubdivideSourceData(NVConstDataRef inSourceData, + nvvector &ioResultVec, + nvvector &keyPointVec, QT3DSF32 inLinearError) +{ + ioResultVec.clear(); + if (inSourceData.size() < 2) + return; + // Assuming no attributes in the source data. + QT3DSU32 numEquations = (inSourceData.size() - 1); + for (QT3DSU32 idx = 0, end = numEquations; idx < end; ++idx) { + const SPathAnchorPoint &beginAnchor = inSourceData[idx]; + const SPathAnchorPoint &endAnchor = inSourceData[idx + 1]; + + QT3DSVec2 anchor1(beginAnchor.m_Position); + QT3DSVec2 control1(IPathManagerCore::GetControlPointFromAngleDistance( + beginAnchor.m_Position, beginAnchor.m_OutgoingAngle, + beginAnchor.m_OutgoingDistance)); + + QT3DSVec2 control2(IPathManagerCore::GetControlPointFromAngleDistance( + endAnchor.m_Position, endAnchor.m_IncomingAngle, + endAnchor.m_IncomingDistance)); + QT3DSVec2 anchor2(endAnchor.m_Position); + + OuterAdaptiveSubdivideBezierCurve( + ioResultVec, keyPointVec, + SCubicBezierCurve(anchor1, control1, control2, anchor2), inLinearError, idx); + } +} + +// The outer subdivide function topologically analyzes the curve to ensure that +// the sign of the second derivative does not change, no inflection points. +// Once that condition is held, then we proceed with a simple adaptive subdivision algorithm +// until the curve is accurately approximated by a straight line. +void OuterAdaptiveSubdivideBezierCurve(nvvector &ioResultVec, + nvvector &keyPointVec, + SCubicBezierCurve inCurve, QT3DSF32 inLinearError, + QT3DSU32 inEquationIndex) +{ + // Step 1, find what type of curve we are dealing with and the inflection points. + keyPointVec.clear(); + CurveType theCurveType = cubicDoublePoint(inCurve.m_Points, keyPointVec); + + QT3DSF32 tStart = 0; + switch (theCurveType) { + case CT_POINT: + ioResultVec.push_back(SResultCubic(inCurve.m_Points[0], inCurve.m_Points[0], + inCurve.m_Points[0], inCurve.m_Points[0], + inEquationIndex, 0.0f, 1.0f, 0.0f)); + return; // don't allow further recursion + case CT_LINE: + PushLine(ioResultVec, inCurve.m_Points[0], inCurve.m_Points[3], inEquationIndex); + return; // don't allow further recursion + case CT_CUSP: + case CT_LOOP: + case CT_SERPENTINE: { + // Break the curve at the inflection points if there is one. If there aren't + // inflection points + // the treat as linear (degenerate case that should not happen except in limiting + // ranges of floating point accuracy) + if (!keyPointVec.empty()) { + // It is not clear that the code results in a sorted vector, + // or a vector where all values are within the range of 0-1 + if (keyPointVec.size() > 1) + eastl::sort(keyPointVec.begin(), keyPointVec.end()); + for (QT3DSU32 idx = 0, end = (QT3DSU32)keyPointVec.size(); + idx < end && keyPointVec[idx] < 1.0f; ++idx) { + // We have a list of T values I believe sorted from beginning to end, we + // will create a set of bezier curves + // Since we split the curves, tValue is relative to tSTart, not 0. + QT3DSF32 range = 1.0f - tStart; + QT3DSF32 splitPoint = keyPointVec[idx] - tStart; + QT3DSF32 tValue = splitPoint / range; + if (tValue > 0.0f) { + eastl::pair newCurves + = inCurve.SplitCubicBezierCurve(tValue); + AdaptiveSubdivideBezierCurve(ioResultVec, newCurves.first, + inLinearError, inEquationIndex, tStart, + splitPoint); + inCurve = newCurves.second; + tStart = splitPoint; + } + } + } + } + // fallthrough intentional + break; + // fallthrough intentional + case CT_QUADRATIC: + break; + } + AdaptiveSubdivideBezierCurve(ioResultVec, inCurve, inLinearError, inEquationIndex, + tStart, 1.0f); +} + +static QT3DSF32 DistanceFromPointToLine(QT3DSVec2 inLineDxDy, QT3DSVec2 lineStart, QT3DSVec2 point) +{ + QT3DSVec2 pointToLineStart = lineStart - point; + return fabs((inLineDxDy.x * pointToLineStart.y) - (inLineDxDy.y * pointToLineStart.x)); +} + +// There are two options here. The first is to just subdivide below a given error +// tolerance. +// The second is to fit a quadratic to the curve and then precisely find the length of the +// quadratic. +// Obviously we are choosing the subdivide method at this moment but I think the fitting +// method is probably more robust. +QT3DSF32 LengthOfBezierCurve(SCubicBezierCurve &inCurve) +{ + // Find distance of control points from line. Note that both control points should be + // on same side of line else we have a serpentine which should have been removed by topological + // analysis. + QT3DSVec2 lineDxDy = inCurve.m_Points[3] - inCurve.m_Points[0]; + QT3DSF32 c1Distance = DistanceFromPointToLine( + lineDxDy, inCurve.m_Points[0], inCurve.m_Points[1]); + QT3DSF32 c2Distance = DistanceFromPointToLine( + lineDxDy, inCurve.m_Points[0], inCurve.m_Points[2]); + const float lineTolerance = 100.0f; // error in world coordinates, squared. + if (c1Distance > lineTolerance || c2Distance > lineTolerance) { + eastl::pair subdivCurve + = inCurve.SplitCubicBezierCurve(.5f); + return LengthOfBezierCurve(subdivCurve.first) + + LengthOfBezierCurve(subdivCurve.second); + } else { + return LineLength(inCurve.m_Points[0], inCurve.m_Points[3]); + } +} + +// The assumption here is the the curve type is not cusp, loop, or serpentine. +// It is either linear or it is a constant curve meaning we can use very simple means to +// figure out the curvature. There is a possibility to use some math to figure out the point of +// maximum curvature, where the second derivative will have a max value. This is probably not +// necessary. +void AdaptiveSubdivideBezierCurve(nvvector &ioResultVec, + SCubicBezierCurve &inCurve, QT3DSF32 inLinearError, + QT3DSU32 inEquationIndex, QT3DSF32 inTStart, QT3DSF32 inTStop) +{ + // Find distance of control points from line. Note that both control points should be + // on same side of line else we have a serpentine which should have been removed by topological + // analysis. + QT3DSVec2 lineDxDy = inCurve.m_Points[3] - inCurve.m_Points[0]; + QT3DSF32 c1Distance = DistanceFromPointToLine(lineDxDy, inCurve.m_Points[0], + inCurve.m_Points[1]); + QT3DSF32 c2Distance = DistanceFromPointToLine(lineDxDy, inCurve.m_Points[0], + inCurve.m_Points[2]); + const float lineTolerance = inLinearError * inLinearError; // error in world coordinates + if (c1Distance > lineTolerance || c2Distance > lineTolerance) { + eastl::pair subdivCurve + = inCurve.SplitCubicBezierCurve(.5f); + QT3DSF32 halfway = lerp(inTStart, inTStop, .5f); + AdaptiveSubdivideBezierCurve(ioResultVec, subdivCurve.first, inLinearError, + inEquationIndex, inTStart, halfway); + AdaptiveSubdivideBezierCurve(ioResultVec, subdivCurve.second, inLinearError, + inEquationIndex, halfway, inTStop); + } else { + ioResultVec.push_back(SResultCubic(inCurve.m_Points[0], inCurve.m_Points[1], + inCurve.m_Points[2], inCurve.m_Points[3], + inEquationIndex, inTStart, inTStop, + LengthOfBezierCurve(inCurve))); + } +} +} +} +} +#endif -- cgit v1.2.3