|
@@ -13,26 +13,20 @@ typedef SplinePointData = {
|
|
|
tangent : h3d.col.Point,
|
|
|
prev : SplinePoint,
|
|
|
next : SplinePoint,
|
|
|
- ?lengthPos : Float
|
|
|
+ ?t : Float
|
|
|
}
|
|
|
|
|
|
-class CurveComputedData {
|
|
|
- public var length : Float;
|
|
|
- public var points : Array<h3d.col.Point> = [];
|
|
|
- public function new() {}
|
|
|
-}
|
|
|
|
|
|
-class SplineComputedData {
|
|
|
+class SplineData {
|
|
|
public var length : Float;
|
|
|
- public var precision : Float;
|
|
|
- public var curves : Array<CurveComputedData> = [];
|
|
|
+ public var step : Float;
|
|
|
+ public var threshold : Float;
|
|
|
+ public var samples : Array<SplinePointData> = [];
|
|
|
public function new() {}
|
|
|
}
|
|
|
|
|
|
class SplinePoint extends h3d.scene.Object {
|
|
|
|
|
|
- public var distanceToNextPoint = -1.0;
|
|
|
-
|
|
|
public function new(x : Float, y : Float, z : Float, parent : h3d.scene.Object) {
|
|
|
super(parent);
|
|
|
setPosition(x,y,z);
|
|
@@ -42,6 +36,12 @@ class SplinePoint extends h3d.scene.Object {
|
|
|
return getAbsPos().getPosition().toPoint();
|
|
|
}
|
|
|
|
|
|
+ public function getTangent() : h3d.col.Point {
|
|
|
+ var tangent = getAbsPos().front().toPoint();
|
|
|
+ tangent.scale(-1);
|
|
|
+ return tangent;
|
|
|
+ }
|
|
|
+
|
|
|
public function getFirstControlPoint() : h3d.col.Point {
|
|
|
var absPos = getAbsPos();
|
|
|
var right = absPos.front();
|
|
@@ -66,13 +66,16 @@ class Spline extends Object3D {
|
|
|
public var points : Array<SplinePoint> = [];
|
|
|
public var shape : CurveShape = Quadratic;
|
|
|
|
|
|
+ var data : SplineData;
|
|
|
+ var step : Float = 1.0;
|
|
|
+ var threshold : Float = 0.01;
|
|
|
+
|
|
|
// Save/Load the curve as an array of absPos
|
|
|
public var pointsData : Array<h3d.Matrix> = [];
|
|
|
|
|
|
// Graphic
|
|
|
public var showSpline : Bool = true;
|
|
|
public var lineGraphics : h3d.scene.Graphics;
|
|
|
- public var linePrecision : Int = 15;
|
|
|
public var lineThickness : Int = 4;
|
|
|
public var color : Int = 0xFFFFFFFF;
|
|
|
public var loop : Bool = false;
|
|
@@ -81,8 +84,6 @@ class Spline extends Object3D {
|
|
|
public var editor : hide.prefab.SplineEditor;
|
|
|
#end
|
|
|
|
|
|
- var computedLength = -1.0;
|
|
|
-
|
|
|
override function save() {
|
|
|
var obj : Dynamic = super.save();
|
|
|
|
|
@@ -97,10 +98,11 @@ class Spline extends Object3D {
|
|
|
}
|
|
|
obj.shape = shape.getIndex();
|
|
|
obj.color = color;
|
|
|
- obj.linePrecision = linePrecision;
|
|
|
obj.lineThickness = lineThickness;
|
|
|
obj.loop = loop;
|
|
|
obj.showSpline = showSpline;
|
|
|
+ obj.step = step;
|
|
|
+ obj.threshold = threshold;
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
@@ -109,10 +111,11 @@ class Spline extends Object3D {
|
|
|
pointsData = obj.points == null ? [] : obj.points;
|
|
|
shape = obj.shape == null ? Linear : CurveShape.createByIndex(obj.shape);
|
|
|
color = obj.color != null ? obj.color : 0xFFFFFFFF;
|
|
|
- linePrecision = obj.linePrecision == null ? 15 : obj.linePrecision;
|
|
|
lineThickness = obj.lineThickness == null ? 4 : obj.lineThickness;
|
|
|
loop = obj.loop == null ? false : obj.loop;
|
|
|
showSpline = obj.showSpline == null ? true : obj.showSpline;
|
|
|
+ step = obj.step == null ? 1.0 : obj.step;
|
|
|
+ obj.threshold = obj.threshold == null ? 0.01 : obj.threshold;
|
|
|
}
|
|
|
|
|
|
override function makeInstance( ctx : hrt.prefab.Context ) : hrt.prefab.Context {
|
|
@@ -126,7 +129,7 @@ class Spline extends Object3D {
|
|
|
sp.setTransform(pd);
|
|
|
points.push(sp);
|
|
|
}
|
|
|
- pointsData = null;
|
|
|
+ pointsData = [];
|
|
|
|
|
|
if( points == null || points.length == 0 ) {
|
|
|
points.push(new SplinePoint(0,0,0, ctx.local3d));
|
|
@@ -146,15 +149,29 @@ class Spline extends Object3D {
|
|
|
#end
|
|
|
}
|
|
|
|
|
|
- // Return the length of the spline, use the computed data if available
|
|
|
- function getLength( ?precision : Float = 1.0 ) : Float {
|
|
|
- if( computedLength > 0 )
|
|
|
- return computedLength;
|
|
|
- var sum = 0.0;
|
|
|
- for( i in 0 ... points.length - 1 ) {
|
|
|
- sum += getLengthBetween(points[i], points[i+1], precision);
|
|
|
+ // Return an interpolation of two samples at length l, 0 <= l <= splineLength
|
|
|
+ function getPointAtLength( l : Float ) : h3d.col.Point {
|
|
|
+ if( data == null )
|
|
|
+ computeSplineData();
|
|
|
+
|
|
|
+ // The last point is not at the same distance, be aware of that case
|
|
|
+ var s1 : Int = hxd.Math.floor(l / step);
|
|
|
+ var s2 : Int = hxd.Math.ceil(l / step);
|
|
|
+ s1 = cast hxd.Math.clamp(s1, 0, data.samples.length - 1);
|
|
|
+ s2 = cast hxd.Math.clamp(s2, 0, data.samples.length - 1);
|
|
|
+
|
|
|
+ // End/Beginning of the curve, just return the point
|
|
|
+ if( s1 == s2 )
|
|
|
+ return data.samples[s1].pos;
|
|
|
+ // Linear interpolation between the two samples
|
|
|
+ else {
|
|
|
+ var segmentLength = data.samples[s1].pos.distance(data.samples[s2].pos);
|
|
|
+ //trace(segmentLength);
|
|
|
+ var t = (l - (s1 * step)) / segmentLength;
|
|
|
+ var result = new h3d.Vector();
|
|
|
+ result.lerp(data.samples[s1].pos.toVector(), data.samples[s2].pos.toVector(), t);
|
|
|
+ return result.toPoint();
|
|
|
}
|
|
|
- return sum;
|
|
|
}
|
|
|
|
|
|
// Return the euclidean distance between the two points
|
|
@@ -171,46 +188,73 @@ class Spline extends Object3D {
|
|
|
return p1.getPoint().distance(p2.getPoint());
|
|
|
}
|
|
|
|
|
|
- // Approximative length calculation : compute the sum of the euclidean distance between a variable amount of points on the curve
|
|
|
- function getLengthBetween( p1 : SplinePoint, p2 : SplinePoint, ?stepSize : Float = 1.0, ?threshold : Float = 0.01 ) : Float {
|
|
|
+ // Return the sum of the euclidean distances between each samples
|
|
|
+ public function getLength() {
|
|
|
+ if( data == null )
|
|
|
+ computeSplineData();
|
|
|
+ return data.length;
|
|
|
+ }
|
|
|
|
|
|
- if( shape == Linear )
|
|
|
- return getMinLengthBetween(p1, p2);
|
|
|
-
|
|
|
- if( p1.distanceToNextPoint > 0 )
|
|
|
- return p1.distanceToNextPoint;
|
|
|
+ // Sample the spline with the step and threshold
|
|
|
+ function computeSplineData() {
|
|
|
|
|
|
- var pointList : Array<h3d.col.Point> = [];
|
|
|
- pointList.push( points[0].getPoint() );
|
|
|
+ if( step <= 0 )
|
|
|
+ return;
|
|
|
+
|
|
|
+ var sd = new SplineData();
|
|
|
|
|
|
+ // Sample the spline
|
|
|
+ var samples : Array<SplinePointData> = [{ pos : points[0].getPoint(), tangent : points[0].getTangent(), prev : null, next : points[1] }];
|
|
|
+ var i = 0;
|
|
|
var sumT = 0.0;
|
|
|
- var t = 1.0;
|
|
|
- while( sumT < 1 ) {
|
|
|
- var p = getPointBetween(t, points[0], points[1]);
|
|
|
- var distance = p.distance(pointList[pointList.length - 1]);
|
|
|
-
|
|
|
- if( hxd.Math.abs(distance - stepSize) < threshold ) {
|
|
|
- pointList.push( p );
|
|
|
+ var maxT = 1.0;
|
|
|
+ var minT = 0.0;
|
|
|
+ while( i < points.length - 1 ) {
|
|
|
+
|
|
|
+ var t = (maxT + minT) * 0.5;
|
|
|
+ var p = getPointBetween(t, points[i], points[i+1]);
|
|
|
+ var curSegmentLength = p.distance(samples[samples.length - 1].pos);
|
|
|
+
|
|
|
+ // Point found
|
|
|
+ if( hxd.Math.abs(curSegmentLength - step) <= threshold ) {
|
|
|
+ samples.insert(samples.length, { pos : p, tangent : getTangentBetween(t, points[i], points[i+1]), prev : points[i], next : points[i+1], t : t });
|
|
|
sumT = t;
|
|
|
- t = 1.0;
|
|
|
+ maxT = 1.0;
|
|
|
+ minT = sumT;
|
|
|
+ // Last point of the curve too close from the last sample
|
|
|
+ if( points[i+1].getPoint().distance(samples[samples.length - 1].pos) < step ) {
|
|
|
+ // End of the spline
|
|
|
+ if( i == points.length - 2 ) {
|
|
|
+ samples.insert(samples.length, { pos : points[points.length - 1].getPoint(), tangent : points[points.length - 1].getTangent(), prev : points[points.length - 2], next : null, t : 1.0 });
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // End of the current curve
|
|
|
+ else {
|
|
|
+ i++;
|
|
|
+ sumT = 0.0;
|
|
|
+ minT = 0.0;
|
|
|
+ maxT = 1.0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Point not found
|
|
|
+ else if( curSegmentLength > step ) {
|
|
|
+ maxT = maxT - (maxT - minT) * 0.5;
|
|
|
+ }
|
|
|
+ else if( curSegmentLength < step ) {
|
|
|
+ minT = minT + (maxT - minT) * 0.5;
|
|
|
}
|
|
|
- else if( distance > stepSize )
|
|
|
- t -= (t - sumT) * 0.5;
|
|
|
- else if( distance < stepSize )
|
|
|
- t += (t - sumT) * 0.5;
|
|
|
}
|
|
|
+ sd.samples = samples;
|
|
|
|
|
|
- var sum = 0.0;
|
|
|
- for( i in 0 ... pointList.length - 1 )
|
|
|
- sum += pointList[i].distance(pointList[i + 1]);
|
|
|
-
|
|
|
- p1.distanceToNextPoint = sum;
|
|
|
-
|
|
|
- return sum;
|
|
|
- }
|
|
|
+ // Compute the average length of the spline
|
|
|
+ var lengthSum = 0.0;
|
|
|
+ for( i in 0 ... samples.length - 1 ) {
|
|
|
+ lengthSum += samples[i].pos.distance(samples[i+1].pos);
|
|
|
+ }
|
|
|
+ sd.length = lengthSum;
|
|
|
|
|
|
- function computeSplineData( ?precision : Float = 1.0 ) {
|
|
|
- var d = new SplineComputedData();
|
|
|
+ data = sd;
|
|
|
}
|
|
|
|
|
|
// Return the closest spline point on the spline from p
|
|
@@ -228,130 +272,23 @@ class Spline extends Object3D {
|
|
|
}
|
|
|
|
|
|
// Return the closest point on the spline from p
|
|
|
- function getClosestPoint( p : h3d.col.Point, ?precision : Float = 1.0 ) : SplinePointData {
|
|
|
- var length = getLength(precision);
|
|
|
- var stepCount = hxd.Math.ceil(length * precision);
|
|
|
+ function getClosestPoint( p : h3d.col.Point ) : SplinePointData {
|
|
|
+
|
|
|
+ if( data == null )
|
|
|
+ computeSplineData();
|
|
|
+
|
|
|
var minDist = -1.0;
|
|
|
- var result : SplinePointData = { pos : null, tangent : null, prev : null, next : null };
|
|
|
- for( i in 0 ... stepCount ) {
|
|
|
- var pt = getPoint( i / stepCount );
|
|
|
- var dist = pt.pos.distance(p);
|
|
|
+ var result : SplinePointData = null;
|
|
|
+ for( s in data.samples ) {
|
|
|
+ var dist = s.pos.distance(p);
|
|
|
if( dist < minDist || minDist == -1 ) {
|
|
|
minDist = dist;
|
|
|
- result = pt;
|
|
|
+ result = s;
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- // Return the point on the spline at t, 0 <= t <= 1
|
|
|
- function getPoint( l : Float, ?step : Float = 1.0, ?threshold : Float = 0.01 ) : SplinePointData {
|
|
|
-
|
|
|
- // Early return, easy case
|
|
|
- if( points.length == 1 ) return { pos : points[0].getPoint(),
|
|
|
- tangent : points[0].getAbsPos().right().toPoint(),
|
|
|
- prev : null,
|
|
|
- next : null,
|
|
|
- lengthPos : 0 } ;
|
|
|
-
|
|
|
- if( l == 0 ) return { pos : points[0].getPoint(),
|
|
|
- tangent : points[0].getFirstControlPoint().sub(points[0].getPoint()),
|
|
|
- prev : points[0],
|
|
|
- next : points[0],
|
|
|
- lengthPos : 0 } ;
|
|
|
-
|
|
|
- if( l == 1 ) return { pos : points[points.length - 1].getPoint(),
|
|
|
- tangent : points[points.length - 1].getFirstControlPoint().sub(points[points.length - 1].getPoint()),
|
|
|
- prev : points[points.length - 1],
|
|
|
- next : points[points.length - 1],
|
|
|
- lengthPos : 1 };
|
|
|
-
|
|
|
- var targetLength = l * getLength(100);
|
|
|
- //trace("Try to find a point at " + targetLength);
|
|
|
-
|
|
|
- // Find the curve of the spline wich contain the asked length
|
|
|
- var p0 : SplinePoint = null;
|
|
|
- var p1 : SplinePoint = null;
|
|
|
- var curveLengthSum = 0.0;
|
|
|
- for( i in 0 ... points.length - 1 ) {
|
|
|
- var curveLength = getLengthBetween(points[i], points[i+1], step, threshold);
|
|
|
- // Step on the curve and get the segment wich contain the asked length
|
|
|
- if( targetLength <= curveLengthSum + curveLength ) {
|
|
|
- p0 = points[i];
|
|
|
- p1 = points[i + 1];
|
|
|
- //trace("Try to find a point on the curve " + i + " -> " + (i + 1));
|
|
|
- }
|
|
|
- curveLengthSum += curveLength;
|
|
|
- }
|
|
|
-
|
|
|
- // Step on the curve and get the segment wich contain the asked length
|
|
|
- var lastP = p0.getPoint();
|
|
|
- var lengthSum = 0.0;
|
|
|
- var minT = 0.0;
|
|
|
- var t = 1.0;
|
|
|
- while( minT < 1 ) {
|
|
|
- var p = getPointBetween(t, p0, p1);
|
|
|
- var curSegmentLength = p.distance(lastP);
|
|
|
- if( curSegmentLength - step < threshold ) {
|
|
|
- //trace("Try to find the point on the segment" + lastP + " - " + p + " of length " + lengthSum + " -> " + (lengthSum + curSegmentLength));
|
|
|
- // Lerp on the segment wich contain the asked length
|
|
|
- if( curSegmentLength + lengthSum > targetLength ) {
|
|
|
- //trace("Found the segment of the curve " + lastP + " - " + p );
|
|
|
- var finalT = hxd.Math.lerp(minT, t, (targetLength - lengthSum ) / curSegmentLength );
|
|
|
- trace("Found the segment of the curve " + finalT );
|
|
|
- return { pos : getPointBetween(finalT, p0, p1),
|
|
|
- tangent : getTangentBetween(finalT, p0, p1),
|
|
|
- prev : p0,
|
|
|
- next : p1,
|
|
|
- lengthPos : finalT };
|
|
|
- }
|
|
|
- lastP = p;
|
|
|
- minT = t;
|
|
|
- lengthSum += curSegmentLength;
|
|
|
- t = 1.0;
|
|
|
- }
|
|
|
- else if( curSegmentLength > threshold )
|
|
|
- t -= (t - minT) * 0.5;
|
|
|
- else if( curSegmentLength < threshold )
|
|
|
- t += (t - minT) * 0.5;
|
|
|
- }
|
|
|
-
|
|
|
- return { pos : null, tangent : null, prev : null, next : null };
|
|
|
- }
|
|
|
-
|
|
|
- // Return the point on the spline at t, 0 <= t <= 1
|
|
|
- /*function getPoint( t : Float, ?precision : Float = 1.0 ) : SplinePointData {
|
|
|
- t = hxd.Math.clamp(t, 0, 1);
|
|
|
-
|
|
|
- if( points.length == 1 ) return { pos : points[0].getPoint(),
|
|
|
- tangent : points[0].getAbsPos().right().toPoint(),
|
|
|
- prev : null,
|
|
|
- next : null } ;
|
|
|
-
|
|
|
- if( t == 0 ) return { pos : points[0].getPoint(),
|
|
|
- tangent : points[0].getFirstControlPoint().sub(points[0].getPoint()),
|
|
|
- prev : points[0],
|
|
|
- next : points[0] } ;
|
|
|
-
|
|
|
- if( t == 1 ) return { pos : points[points.length - 1].getPoint(),
|
|
|
- tangent : points[points.length - 1].getFirstControlPoint().sub(points[points.length - 1].getPoint()),
|
|
|
- prev : points[points.length - 1],
|
|
|
- next : points[points.length - 1] };
|
|
|
-
|
|
|
- var totalLength = getLength(precision);
|
|
|
- var length = totalLength * t;
|
|
|
- var curlength = 0.0;
|
|
|
- for( i in 0 ... points.length - 1 ) {
|
|
|
- var curSegmentLength = getLengthBetween(points[i], points[i+1], precision);
|
|
|
- curlength += curSegmentLength;
|
|
|
- if( length <= curlength ) {
|
|
|
- var t = (length - (curlength - curSegmentLength)) / curSegmentLength;
|
|
|
- return { pos : getPointBetween(t, points[i], points[i+1]), tangent : getTangentBetween(t, points[i], points[i+1]), prev : points[i], next : points[i+1] };
|
|
|
- }
|
|
|
- }
|
|
|
- return { pos : null, tangent : null, prev : null, next : null };
|
|
|
- }*/
|
|
|
-
|
|
|
// Return the point on the curve between p1 and p2 at t, 0 <= t <= 1
|
|
|
inline function getPointBetween( t : Float, p1 : SplinePoint, p2 : SplinePoint ) : h3d.col.Point {
|
|
|
return switch (shape) {
|
|
@@ -403,7 +340,9 @@ class Spline extends Object3D {
|
|
|
#if editor
|
|
|
|
|
|
function generateBezierCurve( ctx : hrt.prefab.Context ) {
|
|
|
-
|
|
|
+
|
|
|
+ computeSplineData();
|
|
|
+
|
|
|
if( !showSpline ) {
|
|
|
if( lineGraphics != null ) {
|
|
|
lineGraphics.remove();
|
|
@@ -412,9 +351,6 @@ class Spline extends Object3D {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if( points == null )
|
|
|
- return;
|
|
|
-
|
|
|
if( lineGraphics == null ) {
|
|
|
lineGraphics = new h3d.scene.Graphics(ctx.local3d);
|
|
|
lineGraphics.lineStyle(lineThickness, color);
|
|
@@ -423,51 +359,11 @@ class Spline extends Object3D {
|
|
|
lineGraphics.ignoreParentTransform = false;
|
|
|
}
|
|
|
|
|
|
- computedLength = -1;
|
|
|
- for( sp in points ) {
|
|
|
- sp.distanceToNextPoint = -1;
|
|
|
- }
|
|
|
-
|
|
|
- var curve : Array<h3d.col.Point> = [];
|
|
|
- switch (shape) {
|
|
|
- case Linear:
|
|
|
- for( sp in points )
|
|
|
- curve.push(sp.getPoint());
|
|
|
- if( loop && points.length > 1 )
|
|
|
- curve.push(points[0].getPoint());
|
|
|
- case Quadratic:
|
|
|
- var i = 0;
|
|
|
- while( i < points.length - 1 ) {
|
|
|
- for( v in 0 ... linePrecision + 1 ) {
|
|
|
- curve.push(getQuadraticBezierPoint( v / linePrecision, points[i].getPoint(), points[i].getSecondControlPoint(), points[i+1].getPoint()));
|
|
|
- }
|
|
|
- ++i;
|
|
|
- }
|
|
|
- if( loop && points.length > 1 ) {
|
|
|
- for( v in 0 ... linePrecision + 1 ) {
|
|
|
- curve.push(getQuadraticBezierPoint( v / linePrecision, points[points.length - 1].getPoint(), points[points.length - 1].getSecondControlPoint(), points[0].getPoint()));
|
|
|
- }
|
|
|
- }
|
|
|
- case Cubic:
|
|
|
- var i = 0;
|
|
|
- while( i < points.length - 1 ) {
|
|
|
- for( v in 0 ... linePrecision + 1 ) {
|
|
|
- curve.push(getCubicBezierPoint( v / linePrecision, points[i].getPoint(), points[i].getSecondControlPoint(), points[i+1].getFirstControlPoint(), points[i+1].getPoint()));
|
|
|
- }
|
|
|
- ++i;
|
|
|
- }
|
|
|
- if( loop && points.length > 1 ) {
|
|
|
- for( v in 0 ... linePrecision + 1 ) {
|
|
|
- curve.push(getCubicBezierPoint( v / linePrecision, points[points.length - 1].getPoint(), points[points.length - 1].getSecondControlPoint(), points[0].getFirstControlPoint(), points[0].getPoint()));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
lineGraphics.lineStyle(lineThickness, color);
|
|
|
lineGraphics.clear();
|
|
|
var b = true;
|
|
|
- for( p in curve ) {
|
|
|
- var localPos = ctx.local3d.globalToLocal(p.toVector());
|
|
|
+ for( s in data.samples ) {
|
|
|
+ var localPos = ctx.local3d.globalToLocal(s.pos.toVector());
|
|
|
b ? lineGraphics.moveTo(localPos.x, localPos.y, localPos.z) : lineGraphics.lineTo(localPos.x, localPos.y, localPos.z);
|
|
|
b = false;
|
|
|
}
|
|
@@ -488,7 +384,8 @@ class Spline extends Object3D {
|
|
|
<dl>
|
|
|
<dt>Color</dt><dd><input type="color" alpha="true" field="color"/></dd>
|
|
|
<dt>Thickness</dt><dd><input type="range" min="1" max="10" field="lineThickness"/></dd>
|
|
|
- <dt>Precision</dt><dd><input type="range" step="1" min="1" max="100" field="linePrecision"/></dd>
|
|
|
+ <dt>Step</dt><dd><input type="range" min="0.1" max="10" field="step"/></dd>
|
|
|
+ <dt>Threshold</dt><dd><input type="range" min="0.001" max="1" field="threshold"/></dd>
|
|
|
<dt>Show Spline</dt><dd><input type="checkbox" field="showSpline"/></dd>
|
|
|
<dt>Type</dt>
|
|
|
<dd>
|