Bläddra i källkod

Update Spline editor

ShiroSmith 6 år sedan
förälder
incheckning
86526fcccf
3 ändrade filer med 334 tillägg och 54 borttagningar
  1. 176 28
      hide/prefab/SplineEditor.hx
  2. 1 0
      hrt/prefab/l3d/AdvancedDecal.hx
  3. 157 26
      hrt/prefab/l3d/Spline.hx

+ 176 - 28
hide/prefab/SplineEditor.hx

@@ -4,6 +4,58 @@ import hrt.prefab.l3d.Spline;
 
 #if editor
 
+/*class SplineViewer extends h3d.scene.Object {
+
+	public var gaphics : h3d.scene.Graphics;
+
+	public function new( s : Spline ) {
+		super(s);
+		gaphics = new h3d.scene.Graphics(this);
+		gaphics.lineStyle(4, 0xffffff);
+		gaphics.material.mainPass.setPassName("overlay");
+		gaphics.material.mainPass.depth(false, LessEqual);
+		gaphics.ignoreParentTransform = false;
+		gaphics.clear();
+		gaphics.moveTo(1, 0, 0);
+		gaphics.lineTo(-1, 0, 0);
+	}
+}*/
+
+class NewSplinePointViewer extends h3d.scene.Object {
+
+	var pointViewer : h3d.scene.Mesh;
+	var connectionViewer : h3d.scene.Graphics;
+
+	public function new( parent : h3d.scene.Object ) {
+		super(parent);
+		name = "SplinePointViewer";
+		pointViewer = new h3d.scene.Mesh(h3d.prim.Sphere.defaultUnitSphere(), null, this);
+		pointViewer.name = "sphereHandle";
+		pointViewer.material.setDefaultProps("ui");
+		pointViewer.material.color.set(1,1,0,1);
+
+		connectionViewer = new h3d.scene.Graphics(this);
+		connectionViewer.lineStyle(4, 0xFFFF00);
+		connectionViewer.material.mainPass.setPassName("overlay");
+		connectionViewer.material.mainPass.depth(false, LessEqual);
+		connectionViewer.ignoreParentTransform = false;
+		connectionViewer.clear();
+	}
+
+	public function update( spd : SplinePointData ) {
+		connectionViewer.clear();
+		pointViewer.setPosition(spd.pos.x, spd.pos.y, spd.pos.z);
+
+		// Only display the connection if we are adding the new point at the end or the beggining fo the spline
+		connectionViewer.visible = spd.prev == spd.next;
+		if( connectionViewer.visible ) {
+			var startPos = spd.prev == null ? spd.next.getPoint() : spd.prev.getPoint();
+			connectionViewer.moveTo(startPos.x, startPos.y, startPos.z);
+			connectionViewer.lineTo(spd.pos.x, spd.pos.y, spd.pos.z);
+		}
+	}
+}
+
 class SplinePointViewer extends h3d.scene.Object {
 
 	var pointViewer : h3d.scene.Mesh;
@@ -43,6 +95,7 @@ class SplinePointViewer extends h3d.scene.Object {
 	}
 }
 
+@:access(hrt.prefab.l3d.Spline)
 class SplineEditor {
 
 	public var prefab : Spline;
@@ -55,6 +108,7 @@ class SplineEditor {
 	 // Easy way to keep track of viewers
 	var splinePointViewers : Array<SplinePointViewer> = [];
 	var gizmos : Array<hide.view.l3d.Gizmo> = [];
+	var newSplinePointViewer : NewSplinePointViewer;
 
 	public function new( prefab : Spline, undo : hide.ui.UndoHistory ){
 		this.prefab = prefab;
@@ -62,18 +116,22 @@ class SplineEditor {
 	}
 
 	public function update( ctx : hrt.prefab.Context , ?propName : String ) {
-		if( editMode )
+		if( editMode ) {
 			showViewers(ctx);
-		else 
-			removeViewers();
-
+		}
 	}
 
 	function reset() {
 		removeViewers();
 		removeGizmos();
-		if( interactive != null )
+		if( interactive != null ) {
 			interactive.remove();
+			interactive = null;
+		}
+		if( newSplinePointViewer != null ) {
+			newSplinePointViewer.remove();
+			newSplinePointViewer = null;
+		}
 	}
 
 	function trySelectPoint( ray: h3d.col.Ray ) : SplinePointViewer {
@@ -87,6 +145,98 @@ class SplineEditor {
 		return editContext.getContext(prefab);
 	}
 
+	function getNewPointPosition( mouseX : Float, mouseY : Float, ctx : hrt.prefab.Context, ?precision = 1.0 ) : SplinePointData {
+		var closestPt = getClosestPointFromMouse(mouseX, mouseY, ctx, precision);
+		
+		// If ware are adding a new point at the end/beginning, just make a raycast cursor -> plane with the transform of the frit/last SplinePoint
+		if( closestPt.next == closestPt.prev ) {
+			var camera = @:privateAccess ctx.local3d.getScene().camera;
+			var ray = camera.rayFromScreen(mouseX, mouseY);
+			var normal = closestPt.prev.getAbsPos().up();
+			var plane = h3d.col.Plane.fromNormalPoint(normal.toPoint(), new h3d.col.Point(closestPt.prev.getAbsPos().tx, closestPt.prev.getAbsPos().ty, closestPt.prev.getAbsPos().tz));
+			var pt = ray.intersect(plane);
+			return { pos : pt, prev : closestPt.prev, next : closestPt.next };
+		}
+		else 
+			return closestPt;
+	}
+
+	function getClosestPointFromMouse( mouseX : Float, mouseY : Float, ctx : hrt.prefab.Context, ?precision = 1.0 ) : SplinePointData {
+		if( ctx == null || ctx.local3d == null || ctx.local3d.getScene() == null ) 
+			return null;
+
+		var result : SplinePointData = null;
+		var mousePos = new h3d.Vector( mouseX / h3d.Engine.getCurrent().width, 1.0 - mouseY / h3d.Engine.getCurrent().height, 0);
+		var length = prefab.getLength();
+		var stepCount = hxd.Math.ceil(length * precision);
+		var minDist = -1.0;
+		for( i in 0 ... stepCount ) {
+			var pt = prefab.getPoint( i / stepCount, precision);
+			var screenPos = pt.pos.toVector();
+			screenPos.project(ctx.local3d.getScene().camera.m);
+			screenPos.z = 0;
+			screenPos.scale3(0.5);
+			screenPos = screenPos.add(new h3d.Vector(0.5,0.5));
+			var dist = screenPos.distance(mousePos);
+			if( (dist < minDist || minDist == -1) && dist < 0.1 ) {
+				minDist = dist;
+				result = pt;
+			}
+		}
+
+		if( result == null ) {
+			result = { pos : null, prev : null, next : null};
+
+			var firstPt = prefab.points[0].getPoint();
+			var firstPtScreenPos = firstPt.toVector();
+			firstPtScreenPos.project(ctx.local3d.getScene().camera.m);
+			firstPtScreenPos.z = 0;
+			firstPtScreenPos.scale3(0.5);
+			firstPtScreenPos = firstPtScreenPos.add(new h3d.Vector(0.5,0.5));
+			var distToFirstPoint = firstPtScreenPos.distance(mousePos);
+
+			var lastPt = prefab.points[prefab.points.length - 1].getPoint();
+			var lastPtSreenPos = lastPt.toVector();
+			lastPtSreenPos.project(ctx.local3d.getScene().camera.m);
+			lastPtSreenPos.z = 0;
+			lastPtSreenPos.scale3(0.5);
+			lastPtSreenPos = lastPtSreenPos.add(new h3d.Vector(0.5,0.5));
+			var distTolastPoint = lastPtSreenPos.distance(mousePos);
+
+			if( distTolastPoint < distToFirstPoint ) {
+				result.pos = lastPt;
+				result.prev = prefab.points[prefab.points.length - 1];
+				result.next = prefab.points[prefab.points.length - 1];
+				minDist = distTolastPoint;
+			}
+			else {
+				result.pos = firstPt;
+				result.prev = prefab.points[0];
+				result.next = prefab.points[0]; 
+				minDist = distToFirstPoint;
+			}
+		}
+
+		return result;
+	}
+
+	function addSplinePoint( spd : SplinePointData, ctx : hrt.prefab.Context ) {
+		var invMatrix = prefab.getTransform().clone();
+		invMatrix.initInverse(invMatrix);
+		var pos = spd.pos.toVector();
+		pos.project(invMatrix);
+
+		var index = -1;
+		if( spd.prev == spd.next ) {
+			if( spd.prev ==  prefab.points[0] ) index = 0;
+			else if( spd.prev ==  prefab.points[prefab.points.length - 1] ) index = prefab.points.length;
+		}
+		else index = prefab.points.indexOf(spd.next);
+
+		prefab.points.insert(index, new SplinePoint(pos.x, pos.y, pos.z, ctx.local3d));
+		prefab.generateBezierCurve(ctx);
+	}
+
 	function removeViewers() {
 		for( v in splinePointViewers )
 			v.remove();
@@ -223,39 +373,36 @@ class SplineEditor {
 			@:privateAccess editContext.scene.editor.gizmo.visible = false;
 			@:privateAccess editContext.scene.editor.curEdit = null;
 			createGizmos(ctx);
-			/*var s2d = @:privateAccess ctx.local2d.getScene();
+			var s2d = @:privateAccess ctx.local2d.getScene();
 			interactive = new h2d.Interactive(10000, 10000, s2d);
 			interactive.propagateEvents = true;
-			interactive.cancelEvents = false;
-
-			interactive.onKeyDown =
-				function(e) {
-					e.propagate = false;
-				};
-
-			interactive.onKeyUp =
-				function(e) {
-					e.propagate = false;
-				};
-
 			interactive.onPush =
 				function(e) {
-					if( K.isDown( K.MOUSE_LEFT ) ) {
+					if( K.isDown( K.MOUSE_LEFT ) && K.isDown( K.CTRL )  ) {
 						e.propagate = false;
-						var ray = @:privateAccess ctx.local3d.getScene().camera.rayFromScreen(s2d.mouseX, s2d.mouseY);
-						var p = trySelectPoint(ray);
+						var pt = getNewPointPosition(s2d.mouseX, s2d.mouseY, ctx, 1);
+						addSplinePoint(pt, ctx);
+						showViewers(ctx);
+						createGizmos(ctx);
 					}
 				};
 
-			interactive.onRelease =
-				function(e) {
-
-				};
-
 			interactive.onMove =
 				function(e) {
+					if( K.isDown( K.CTRL ) ) {
+						if( newSplinePointViewer == null ) 
+							newSplinePointViewer = new NewSplinePointViewer(ctx.local3d.getScene());
+						newSplinePointViewer.visible = true;
 
-				};*/
+						var npt = getNewPointPosition(s2d.mouseX, s2d.mouseY, ctx, 1);
+						newSplinePointViewer.update(npt);
+					}
+					else {
+						if( newSplinePointViewer != null ) 
+							newSplinePointViewer.visible = false;
+					}
+						
+				};
 		}
 		else {
 			editMode = false;
@@ -268,7 +415,8 @@ class SplineEditor {
 		<div class="spline-editor">
 			<div class="group" name="Description">
 				<div class="description">
-					<i>Ctrl + Left Click</i> Destroy the world
+					<i>Ctrl + Left Click</i> Add a point on the spline
+					<i>Shift + Left Click</i> Delete a point from the spline
 				</div>
 			</div>
 			<div class="group" name="Tool">

+ 1 - 0
hrt/prefab/l3d/AdvancedDecal.hx

@@ -120,6 +120,7 @@ class AdvancedDecal extends Object3D {
 					shader.colorTexture = albedoMap != null ? ctx.loadTexture(albedoMap) : null;
 					if(shader.colorTexture != null) shader.colorTexture.wrap = Repeat;
 					shader.CENTERED = centered;
+					shader.GAMMA_CORRECT = renderMode == BeforeTonemapping;
 					shader.fadePower = fadePower;
 					shader.fadeStart = fadeStart;
 					shader.fadeEnd = fadeEnd;

+ 157 - 26
hrt/prefab/l3d/Spline.hx

@@ -6,18 +6,26 @@ enum CurveShape {
 	Cubic;
 }
 
+typedef SplinePointData = {
+	pos : h3d.col.Point,
+	prev : SplinePoint,
+	next : SplinePoint
+}
+
 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);
-		scale(1);
 	}
 
+	var p = new h3d.col.Point();
 	public function getPoint() : h3d.col.Point {
 		var absPos = getAbsPos();
-		var pos = new h3d.col.Point(absPos.tx, absPos.ty, absPos.tz);
-		return pos;
+		p.set(absPos.tx, absPos.ty, absPos.tz);
+		return p;
 	}
 
 	public function getFirstControlPoint() : h3d.col.Point {
@@ -44,18 +52,25 @@ class Spline extends Object3D {
 	public var pointsData : Array<h3d.Matrix> = [];
 	public var points : Array<SplinePoint> = [];
 	public var shape : CurveShape = Quadratic;
+	
+	public var lineGraphics : h3d.scene.Graphics;
+	public var linePrecision : Int = 15;
+	public var lineThickness : Int = 4;
+	public var color : Int = 0xFFFFFFFF;
 
 	#if editor
 	public var editor : hide.prefab.SplineEditor;
-	public var lineGraphics : h3d.scene.Graphics;
-	public var precision : Int = 15;
-	public var color : Int = 0xFFFFFFFF;
 	#end
 
+	var computedLength = -1.0;
+
 	override function save() {
 		var obj : Dynamic = super.save();
 		obj.points = [ for(sp in points) { sp.getAbsPos(); } ];
 		obj.shape = shape;
+		obj.color = color;
+		obj.linePrecision = linePrecision;
+		obj.lineThickness = lineThickness;
 		return obj;
 	}
 
@@ -63,6 +78,9 @@ class Spline extends Object3D {
 		super.load(obj);
 		pointsData = obj.points == null ? [] : obj.points;
 		shape = obj.shape == null ? Linear : obj.shape;
+		color = obj.color != null ? obj.color : 0xFFFFFFFF;
+		linePrecision = obj.linePrecision == null ? 15 : obj.linePrecision;
+		lineThickness = obj.lineThickness == null ? 4 : obj.lineThickness;
 	}
 
 	override function makeInstance( ctx : hrt.prefab.Context ) : hrt.prefab.Context {
@@ -71,20 +89,11 @@ class Spline extends Object3D {
 		ctx.local3d = new h3d.scene.Object(ctx.local3d);
 		ctx.local3d.name = name;
 
-		#if editor
 		lineGraphics = new h3d.scene.Graphics(ctx.local3d);
-		lineGraphics.lineStyle(2, color);
+		lineGraphics.lineStyle(lineThickness, color);
 		lineGraphics.material.mainPass.setPassName("overlay");
 		lineGraphics.material.mainPass.depth(false, LessEqual);
 		lineGraphics.ignoreParentTransform = false;
-
-		/*points = [];
-		points.push(new SplinePoint(0,0,0, ctx.local3d));
-		points.push(new SplinePoint(5,10,0, ctx.local3d));
-		points.push(new SplinePoint(-5,20,5, ctx.local3d));
-		points.push(new SplinePoint(-10,30,0, ctx.local3d));
-		points.push(new SplinePoint(10,40,-5, ctx.local3d));*/
-		#end
 	
 		for( pd in pointsData ) {
 			var sp = new SplinePoint(0, 0, 0, ctx.local3d);
@@ -93,6 +102,10 @@ class Spline extends Object3D {
 		}
 		pointsData = null;
 
+		if( points == null || points.length == 0 ) {
+			points.push(new SplinePoint(0,0,0, ctx.local3d));
+		}
+
 		updateInstance(ctx);
 		return ctx;
 	}
@@ -100,28 +113,142 @@ class Spline extends Object3D {
 	override function updateInstance( ctx : hrt.prefab.Context , ?propName : String ) {
 		super.updateInstance(ctx, propName);
 		#if editor
-		lineGraphics.lineStyle(2, color);
 		if( editor != null )
 			editor.update(ctx, propName);
+
 		generateBezierCurve(ctx);
 		#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 sum;
+	}
+
+	// Return the euclidean distance between the two points
+	inline function getMaxLengthBetween( p1 : SplinePoint, p2 : SplinePoint) : Float {
+		switch shape {
+			case Linear: return p1.getPoint().distance(p2.getPoint());
+			case Quadratic: return p1.getPoint().distance(p1.getSecondControlPoint()) + p1.getFirstControlPoint().distance(p2.getPoint());
+			case Cubic: return p1.getPoint().distance(p1.getSecondControlPoint()) + p1.getFirstControlPoint().distance(p2.getFirstControlPoint()) + p2.getFirstControlPoint().distance(p2.getPoint());
+		}
+	}
+
+	// Return the sum of the euclidean distances between each control points
+	inline function getMinLengthBetween( p1 : SplinePoint, p2 : SplinePoint) : Float {
+		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, ?precision : Float = 1.0 ) : Float {
+
+		if( shape == Linear ) {
+			return getMinLengthBetween(p1, p2);
+		}
+
+		if( p1.distanceToNextPoint > 0 ) 
+			return p1.distanceToNextPoint;
+
+		var sum = 0.0;
+		var maxLength = getMaxLengthBetween(p1, p2);
+		var minLength = getMinLengthBetween(p1, p2);
+		var stepCount = hxd.Math.ceil(precision * maxLength);
+
+		var curPt : h3d.col.Point = p1.getPoint();
+		var step = 1.0 / stepCount;
+		var t = step;
+		for( i in 0 ... stepCount ) {
+			var nextPt = getPointBetween(t, p1, p2);
+			sum += curPt.distance(nextPt);
+			curPt = nextPt;
+			t += step;
+		}
+
+		p1.distanceToNextPoint = sum;
+
+		return sum;
+	}
+
+	// Return the closest spline point on the spline from p
+	function getClosestSplinePoint( p : h3d.col.Point ) : SplinePoint {
+		var minDist = -1.0;
+		var curPt : SplinePoint = null;
+		for( sp in points ) {
+			var dist = p.distance(sp.getPoint());
+			if( dist < minDist || minDist == -1 ) {
+				minDist = dist;
+				curPt = sp;
+			}
+		}
+		return curPt;
+	}
+
+	// Return the closest point on the spline from p
+	function getClosestPoint( p : h3d.col.Point, ?precision : Float = 1.0 ) : SplinePointData {
+		var length = getLength();
+		var stepCount = hxd.Math.ceil(length * precision);
+		var minDist = -1.0;
+		var result : SplinePointData = { pos : null, prev : null, next : null };
+		for( i in 0 ... stepCount ) {
+			var pt = getPoint( i / stepCount );
+			var dist = pt.pos.distance(p);
+			if( dist < minDist || minDist == -1 ) {
+				minDist = dist;
+				result = pt;
+			}
+		}
+		return result;
+	}
+
+	// Return the point on the spline between p1 and p2 at t ( 0 -> 1 )
+	function getPoint( t : Float, ?precision : Float = 1.0 ) : SplinePointData {
+		t = hxd.Math.clamp(t, 0, 1);
+		if( t == 0 ) return { pos : points[0].getPoint(), prev : points[0], next : points[0] } ;
+		if( t == 1 ) return { pos : points[points.length - 1].getPoint(), prev : points[points.length - 1], next : points[points.length - 1] };
+
+		var result : SplinePointData;
+		var totalLength = getLength();
+		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 ) 
+				return { pos : getPointBetween( (length - (curlength - curSegmentLength)) / curSegmentLength, points[i], points[i+1]), prev : points[i], next : points[i+1] } ;
+		}
+		return { pos : points[points.length - 1].getPoint(), prev : points[points.length - 1], next : points[points.length - 1] };
+	}
+
+	// Return the point on the curve between p1 and p2 at t ( 0 -> 1 )
+	inline function getPointBetween( t : Float, p1 : SplinePoint, p2 : SplinePoint ) {
+		return switch (shape) {
+			case Linear: getLinearBezierPoint( t, p1.getPoint(), p2.getPoint() );
+			case Quadratic: getQuadraticBezierPoint( t, p1.getPoint(), p1.getSecondControlPoint(), p2.getPoint() );
+			case Cubic: getCubicBezierPoint( t, p1.getPoint(), p1.getSecondControlPoint(), p2.getFirstControlPoint(), p2.getPoint() );
+		}
+	}
+
 	// Linear Interpolation 
 	// p(t) = p0 + (p1 - p0) * t
-	function getLinearBezierPoint( t : Float, p0 : h3d.col.Point, p1 : h3d.col.Point ) : h3d.col.Point {
+	inline function getLinearBezierPoint( t : Float, p0 : h3d.col.Point, p1 : h3d.col.Point ) : h3d.col.Point {
 		return p0.add((p1.sub(p0).multiply(t)));
 	}
 
 	// Quadratic Interpolation 
 	// p(t) = p0 * (1 - t)² + p1 * t * 2 * (1 - t) + p2 * t²
-	function getQuadraticBezierPoint( t : Float, p0 : h3d.col.Point, p1 : h3d.col.Point, p2 : h3d.col.Point) : h3d.col.Point {
+	inline function getQuadraticBezierPoint( t : Float, p0 : h3d.col.Point, p1 : h3d.col.Point, p2 : h3d.col.Point) : h3d.col.Point {
 		return p0.multiply((1 - t) * (1 - t)).add(p1.multiply(t * 2 * (1 - t))).add(p2.multiply(t * t));
 	}
 
 	// Cubic Interpolation
-	// p(t) = p0 * (1 - t)³ + p1 * t * 3 * (1 - t)² + p2 * t² * 3 *(1 - t) + p3 * t³
-	function getCubicBezierPoint( t : Float, p0 : h3d.col.Point, p1 : h3d.col.Point, p2 : h3d.col.Point, p3 : h3d.col.Point) : h3d.col.Point {
+	// p(t) = p0 * (1 - t)³ + p1 * t * 3 * (1 - t)² + p2 * t² * 3 * (1 - t) + p3 * t³
+	inline function getCubicBezierPoint( t : Float, p0 : h3d.col.Point, p1 : h3d.col.Point, p2 : h3d.col.Point, p3 : h3d.col.Point) : h3d.col.Point {
 		return p0.multiply((1 - t) * (1 - t) * (1 - t)).add(p1.multiply(t * 3 * (1 - t) * (1 - t))).add(p2.multiply(t * t * 3 * (1 - t))).add(p3.multiply(t * t * t));
 	}
 
@@ -132,6 +259,8 @@ class Spline extends Object3D {
 		if( points == null )
 			return;
 
+		computedLength = -1;
+
 		var curve : Array<h3d.col.Point> = [];
 		switch (shape) {
 			case Linear:
@@ -140,21 +269,22 @@ class Spline extends Object3D {
 			case Quadratic:
 				var i = 0;
 				while( i < points.length - 1 ) {
-					for( v in 0 ... precision + 1 ) {
-						curve.push(getQuadraticBezierPoint( v / precision, points[i].getPoint(), points[i].getSecondControlPoint(), points[i+1].getPoint()));
+					for( v in 0 ... linePrecision + 1 ) {
+						curve.push(getQuadraticBezierPoint( v / linePrecision, points[i].getPoint(), points[i].getSecondControlPoint(), points[i+1].getPoint()));
 					}
 					++i;
 				}
 			case Cubic:
 				var i = 0;
 				while( i < points.length - 1 ) {
-					for( v in 0 ... precision + 1 ) {
-						curve.push(getCubicBezierPoint( v / precision, points[i].getPoint(), points[i].getSecondControlPoint(), points[i+1].getFirstControlPoint(), points[i+1].getPoint()));
+					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;
 				}
 		}
 
+		lineGraphics.lineStyle(lineThickness, color);
 		lineGraphics.clear();
 		var b = true;
 		for( p in curve ) {
@@ -180,6 +310,8 @@ class Spline extends Object3D {
 			<div class="group" name="Spline">
 				<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>Type</dt>
 						<dd>
 							<select field="shape" >
@@ -188,7 +320,6 @@ class Spline extends Object3D {
 								<option value="Cubic">Cubic</option>
 							</select>
 						</dd>
-					<dt>Precision</dt><dd><input type="range"step="1" min="1" max="100" field="precision"/></dd>
 				</dl>
 			</div>'), this, function(pname) { ctx.onChange(this, pname); });