Browse Source

completely rework Spline editor (#156)

Jed974 4 years ago
parent
commit
acd1db9d56
5 changed files with 313 additions and 246 deletions
  1. 1 1
      hide/comp/IconTree.hx
  2. 9 3
      hide/comp/SceneEditor.hx
  3. 116 140
      hide/prefab/SplineEditor.hx
  4. 1 1
      hrt/prefab/Prefab.hx
  5. 186 101
      hrt/prefab/l3d/Spline.hx

+ 1 - 1
hide/comp/IconTree.hx

@@ -124,7 +124,7 @@ class IconTree<T:{}> extends Component {
 					}
 					if( operation == "move_node" ) {
 						if( extra.ref == null ) return true;
-						return onAllowMove(getVal(node.id), getVal(extra.ref.id));
+						return onAllowMove(getVal(node.id), getVal(node_parent.id));
 					}
 					return false;
 				},

+ 9 - 3
hide/comp/SceneEditor.hx

@@ -587,8 +587,14 @@ class SceneEditor {
 			refreshScene();
 			return true;
 		};
-		tree.onAllowMove = function(_, _) {
-			return true;
+		tree.onAllowMove = function(e, to) {
+			var allowMove = false;
+			if (to == null && e.getHideProps().allowParent == null) allowMove = true;
+			else if (to == null) allowMove = false;
+			else if (to.getHideProps().allowChildren != null && to.getHideProps().allowChildren(e.type) 
+			&& e.getHideProps().allowParent != null && e.getHideProps().allowParent(to))
+				allowMove = true;
+			return allowMove;
 		};
 
 		// Batch tree.onMove, which is called for every node moved, causing problems with undo and refresh
@@ -1197,7 +1203,7 @@ class SceneEditor {
 
 		if(p != sceneData) {
 			var el = tree.getElement(p);
-			if( el != null ) applyTreeStyle(p, el, pname);
+			if( el != null && el.toggleClass != null ) applyTreeStyle(p, el, pname);
 		}
 
 		applySceneStyle(p);

+ 116 - 140
hide/prefab/SplineEditor.hx

@@ -22,14 +22,14 @@ class NewSplinePointViewer extends h3d.scene.Object {
 		connectionViewer = new h3d.scene.Graphics(this);
 		connectionViewer.name = "connectionViewer";
 		connectionViewer.lineStyle(3, 0xFFFF00);
-		connectionViewer.material.mainPass.setPassName("overlay");
+		connectionViewer.material.mainPass.setPassName("ui");
 		connectionViewer.material.mainPass.depthTest = Always;
 		connectionViewer.clear();
 
 		tangentViewer = new h3d.scene.Graphics(this);
 		tangentViewer.name = "tangentViewerViewer";
 		tangentViewer.lineStyle(3, 0xFFFF00);
-		tangentViewer.material.mainPass.setPassName("overlay");
+		tangentViewer.material.mainPass.setPassName("ui");
 		tangentViewer.material.mainPass.depthTest = Always;
 		tangentViewer.clear();
 	}
@@ -72,73 +72,6 @@ class NewSplinePointViewer extends h3d.scene.Object {
 	}
 }
 
-class SplinePointViewer extends h3d.scene.Object {
-
-	var pointViewer : h3d.scene.Mesh;
-	var controlPointsViewer : h3d.scene.Graphics;
-	var indexText : h2d.ObjectFollower;
-	var spline : Spline;
-
-	public function new( sp : SplinePoint, spline : Spline, ctx : hrt.prefab.Context ) {
-		super(sp);
-		this.spline = spline;
-		name = "SplinePointViewer";
-		pointViewer = new h3d.scene.Mesh(h3d.prim.Sphere.defaultUnitSphere(), null, this);
-		pointViewer.name = "pointViewer";
-		pointViewer.material.setDefaultProps("ui");
-		pointViewer.material.color.set(1,1,1,1);
-		pointViewer.material.mainPass.depthTest = Always;
-
-		controlPointsViewer = new h3d.scene.Graphics(this);
-		controlPointsViewer.name = "controlPointsViewer";
-		controlPointsViewer.lineStyle(4, 0xffffff);
-		controlPointsViewer.material.mainPass.setPassName("overlay");
-		controlPointsViewer.material.mainPass.depthTest = Always;
-		controlPointsViewer.ignoreParentTransform = false;
-		controlPointsViewer.clear();
-		controlPointsViewer.moveTo(1, 0, 0);
-		controlPointsViewer.lineTo(-1, 0, 0);
-
-		indexText = new h2d.ObjectFollower(pointViewer,  @:privateAccess ctx.local2d.getScene());
-		var t = new h2d.Text(hxd.res.DefaultFont.get(), indexText);
-		t.textColor = 0xff00ff;
-		t.textAlign = Center;
-		t.dropShadow = { dx : 0.5, dy : 0.5, color : 0x202020, alpha : 1.0 };
-		t.setScale(2.5);
-	}
-
-	override function sync( ctx : h3d.scene.RenderContext ) {
-		var cam = ctx.camera;
-		var gpos = getAbsPos().getPosition();
-		var distToCam = cam.pos.sub(gpos).length();
-		var engine = h3d.Engine.getCurrent();
-		var ratio = 18 / engine.height;
-		var correctionFromParents =  1.0 / getAbsPos().getScale().x;
-		pointViewer.setScale(correctionFromParents * ratio * distToCam * Math.tan(cam.fovY * 0.5 * Math.PI / 180.0));
-		calcAbsPos();
-
-		var t = Std.downcast(indexText.getChildAt(0), h2d.Text);
-		var sp : SplinePoint = cast parent;
-		t.text = "" + spline.points.indexOf(sp);
-
-		super.sync(ctx);
-	}
-
-	public function interset( ray : h3d.col.Ray ) : Bool {
-		return pointViewer.getCollider().rayIntersection(ray, false) != -1;
-	}
-
-	public function setColor( color : Int ) {
-		controlPointsViewer.setColor(color);
-		pointViewer.material.color.setColor(color);
-	}
-
-	override function onRemove() {
-		super.onRemove();
-		indexText.remove();
-	}
-}
-
 @:access(hrt.prefab.l3d.Spline)
 class SplineEditor {
 
@@ -150,7 +83,6 @@ class SplineEditor {
 	var interactive : h2d.Interactive;
 
 	 // Easy way to keep track of viewers
-	var splinePointViewers : Array<SplinePointViewer> = [];
 	var gizmos : Array<hide.view.l3d.Gizmo> = [];
 	var newSplinePointViewer : NewSplinePointViewer;
 
@@ -161,7 +93,7 @@ class SplineEditor {
 
 	public function update( ctx : hrt.prefab.Context , ?propName : String ) {
 		if( editMode ) {
-			showViewers(ctx);
+			showViewers();
 		}
 	}
 
@@ -317,32 +249,40 @@ class SplineEditor {
 			scale = (spd.prev.scaleX + spd.next.scaleX) * 0.5;
 		}
 
-		var sp = new SplinePoint(pos.x, pos.y, pos.z, ctx.local3d);
-		prefab.points.insert(index, sp);
+		var sp = new SplinePoint(prefab);
+		sp.x = pos.x;
+		sp.y = pos.y;
+		sp.z = pos.z;
+		prefab.children.remove(sp);
+		prefab.children.insert(index, sp);
 		if( spd.tangent != null ) {
 			var dir = spd.tangent.toVector();
 			dir.transform3x3(invMatrix); // Don't take the translation
 			dir.scale3(-1);
-			var rot = h3d.Matrix.lookAtX(dir);
-			sp.setDirection(rot.front());
+			sp.rotationX = h3d.Matrix.lookAtX(dir).getFloats()[0];
+			sp.rotationY = h3d.Matrix.lookAtX(dir).getFloats()[1];
+			sp.rotationZ = h3d.Matrix.lookAtX(dir).getFloats()[2];
+			
 		}
-		sp.scale(scale);
+		sp.scaleX = scale;
+		sp.scaleY = scale;
+		sp.scaleZ = scale;
+		editContext.scene.editor.addElements([sp], false, true, true);
 
 		prefab.updateInstance(ctx);
+		showViewers();
 		return sp;
 	}
 
 	function removeViewers() {
-		for( v in splinePointViewers )
-			v.remove();
-		splinePointViewers = [];
+		for( sp in prefab.points ) {
+			sp.setViewerVisible(false);
+		}
 	}
 
-	function showViewers( ctx : hrt.prefab.Context ) {
-		removeViewers(); // Security, avoid duplication
+	function showViewers() {
 		for( sp in prefab.points ) {
-			var spv = new SplinePointViewer(sp, prefab, ctx);
-			splinePointViewers.insert(splinePointViewers.length, spv);
+			sp.setViewerVisible(true);
 		}
 	}
 
@@ -361,36 +301,42 @@ class SplineEditor {
 			var gizmo = new hide.view.l3d.Gizmo(editContext.scene);
 			gizmo.getRotationQuat().identity();
 			gizmo.visible = true;
-			var worldPos = ctx.local3d.localToGlobal(new h3d.col.Point(sp.x, sp.y, sp.z));
-			gizmo.setPosition(worldPos.x, worldPos.y, worldPos.z);
+			var tmpMat = new h3d.Matrix();
+			tmpMat.load(sp.getAbsPos());
+			var tmpScale = new h3d.Vector();
+			tmpMat.getScale(tmpScale);
+			tmpMat.prependScale(1.0/tmpScale.x, 1.0/tmpScale.y, 1.0/tmpScale.z);
+			gizmo.setTransform(tmpMat);
 			@:privateAccess sceneEditor.updates.push( gizmo.update );
 			gizmos.insert(gizmos.length, gizmo);
 			gizmo.visible = false; // Not visible by default, only show the closest in the onMove of interactive
 
+			var posQuant = @:privateAccess sceneEditor.view.config.get("sceneeditor.xyzPrecision");
+			var scaleQuant = @:privateAccess sceneEditor.view.config.get("sceneeditor.scalePrecision");
+			var rotQuant = @:privateAccess sceneEditor.view.config.get("sceneeditor.rotatePrecision");
+
+			inline function quantize(x: Float, step: Float) {
+				if(step > 0) {
+					x = Math.round(x / step) * step;
+					x = untyped parseFloat(x.toFixed(5)); // Snap to closest nicely displayed float :cold_sweat:
+				}
+				return x;
+			}
+
 			gizmo.onStartMove = function(mode) {
 
-				var sceneObj = sp;
+				var sceneObj = sceneEditor.getContext(sp).local3d;
+				var obj3d = sp.to(hrt.prefab.Object3D);
 				var pivotPt = sceneObj.getAbsPos().getPosition();
+				var pivot = new h3d.Matrix();
+				pivot.initTranslation(pivotPt.x, pivotPt.y, pivotPt.z);
+				var invPivot = pivot.clone();
+				invPivot.invert();
+
 				var localMat : h3d.Matrix = sceneEditor.worldMat(sceneObj).clone();
-				localMat.translate(-pivotPt.x, -pivotPt.y, -pivotPt.z);
-				var parentInvMat = sceneObj.parent.getAbsPos().clone();
-				parentInvMat.initInverse(parentInvMat);
-
-				var posQuant = @:privateAccess sceneEditor.view.config.get("sceneeditor.xyzPrecision");
-				var scaleQuant = @:privateAccess sceneEditor.view.config.get("sceneeditor.scalePrecision");
-				var rotQuant = @:privateAccess sceneEditor.view.config.get("sceneeditor.rotatePrecision");
-
-				inline function quantize(x: Float, step: Float) {
-					if(step > 0) {
-						x = Math.round(x / step) * step;
-						x = untyped parseFloat(x.toFixed(5)); // Snap to closest nicely displayed float :cold_sweat:
-					}
-					return x;
-				}
+				localMat.multiply(localMat, invPivot);
 
-				var rot = sceneObj.getRotationQuat().toEuler();
-				var prevState = sceneObj.getAbsPos().clone();
-				prevState.multiply(prevState, parentInvMat);
+				var prevState = obj3d.saveTransform();
 				gizmo.onMove = function(translate: h3d.Vector, rot: h3d.Quat, scale: h3d.Vector) {
 					var transf = new h3d.Matrix();
 					transf.identity();
@@ -400,15 +346,23 @@ class SplineEditor {
 
 					var newMat = localMat.clone();
 					newMat.multiply(newMat, transf);
-					newMat.translate(pivotPt.x, pivotPt.y, pivotPt.z);
+					newMat.multiply(newMat, pivot);
+					if(sceneEditor.snapToGround && mode == MoveXY) {
+						newMat.tz = sceneEditor.getZ(newMat.tx, newMat.ty);
+					}
+
+					var parentInvMat = sceneObj.parent.getAbsPos().clone();
+					parentInvMat.initInverse(parentInvMat);
 					newMat.multiply(newMat, parentInvMat);
 					if(scale != null) newMat.prependScale(scale.x, scale.y, scale.z);
 
 					var rot = newMat.getEulerAngles();
-					sceneObj.x = quantize(newMat.tx, posQuant);
-					sceneObj.y = quantize(newMat.ty, posQuant);
-					sceneObj.z = quantize(newMat.tz, posQuant);
-					sceneObj.setRotation(hxd.Math.degToRad(quantize(hxd.Math.radToDeg(rot.x), rotQuant)), hxd.Math.degToRad(quantize(hxd.Math.radToDeg(rot.y), rotQuant)), hxd.Math.degToRad(quantize(hxd.Math.radToDeg(rot.z), rotQuant)));
+					obj3d.x = quantize(newMat.tx, posQuant);
+					obj3d.y = quantize(newMat.ty, posQuant);
+					obj3d.z = quantize(newMat.tz, posQuant);
+					obj3d.rotationX = quantize(hxd.Math.radToDeg(rot.x), rotQuant);
+					obj3d.rotationY = quantize(hxd.Math.radToDeg(rot.y), rotQuant);
+					obj3d.rotationZ = quantize(hxd.Math.radToDeg(rot.z), rotQuant);
 					if(scale != null) {
 						inline function scaleSnap(x: Float) {
 							if(K.isDown(K.CTRL)) {
@@ -418,31 +372,35 @@ class SplineEditor {
 							return x;
 						}
 						var s = newMat.getScale();
-						sceneObj.scaleX = quantize(scaleSnap(s.x), scaleQuant);
-						sceneObj.scaleY = quantize(scaleSnap(s.y), scaleQuant);
-						sceneObj.scaleZ = quantize(scaleSnap(s.z), scaleQuant);
+						obj3d.scaleX = quantize(scaleSnap(s.x), scaleQuant);
+						obj3d.scaleY = quantize(scaleSnap(s.y), scaleQuant);
+						obj3d.scaleZ = quantize(scaleSnap(s.z), scaleQuant);
 					}
-
-					prefab.updateInstance(ctx);
+					obj3d.applyTransform(sceneObj);
 				}
 
 				gizmo.onFinishMove = function() {
-					var newState = sceneObj.getAbsPos().clone();
-					newState.multiply(newState, parentInvMat);
+					var newState = obj3d.saveTransform();
 					undo.change(Custom(function(undo) {
 						if( undo ) {
-							sceneObj.setTransform(prevState);
+							obj3d.loadTransform(prevState);
+							obj3d.applyTransform(sceneObj);
 							prefab.updateInstance(ctx);
-							showViewers(ctx);
+							showViewers();
+							@:privateAccess editContext.scene.editor.refresh(Partial);
+							showViewers();
 							createGizmos(ctx);
 						}
 						else {
-							sceneObj.setTransform(newState);
+							obj3d.loadTransform(newState);
+							obj3d.applyTransform(sceneObj);
 							prefab.updateInstance(ctx);
-							showViewers(ctx);
+							showViewers();
 							createGizmos(ctx);
 						}
 					}));
+					var worldPos = ctx.local3d.localToGlobal(new h3d.col.Point(sp.x, sp.y, sp.z));
+					gizmo.setPosition(worldPos.x, worldPos.y, worldPos.z);
 				}
 			}
 		}
@@ -468,19 +426,22 @@ class SplineEditor {
 						e.propagate = false;
 						var pt = getNewPointPosition(s2d.mouseX, s2d.mouseY, ctx);
 						var sp = addSplinePoint(pt, ctx);
-						showViewers(ctx);
+						showViewers();
 						createGizmos(ctx);
 
 						undo.change(Custom(function(undo) {
 							if( undo ) {
-								prefab.points.remove(sp);
+								editContext.scene.editor.deleteElements([sp], () -> {}, false, false);
+								for (sp in prefab.points)
+									sp.computeName(editContext.getContext(sp));
+								@:privateAccess editContext.scene.editor.refresh(Partial);
 								prefab.updateInstance(ctx);
-								showViewers(ctx);
+								showViewers();
 								createGizmos(ctx);
 							}
 							else {
 								addSplinePoint(pt, ctx);
-								showViewers(ctx);
+								showViewers();
 								createGizmos(ctx);
 							}
 						}));
@@ -491,22 +452,30 @@ class SplineEditor {
 						e.propagate = false;
 						var sp = getClosestSplinePointFromMouse(s2d.mouseX, s2d.mouseY, ctx);
 						var index = prefab.points.indexOf(sp);
-						prefab.points.remove(sp);
+						editContext.scene.editor.deleteElements([sp], () -> {}, false, false);
+						for (sp in prefab.points)
+							sp.computeName(editContext.getContext(sp));
+						@:privateAccess editContext.scene.editor.refresh(Partial);
+						
 						prefab.updateInstance(ctx);
-						showViewers(ctx);
+						showViewers();
 						createGizmos(ctx);
 
 						undo.change(Custom(function(undo) {
 							if( undo ) {
-								prefab.points.insert(index, sp);
+								prefab.children.insert(index, sp);
+								editContext.scene.editor.addElements([sp], false, true, true);
 								prefab.updateInstance(ctx);
-								showViewers(ctx);
+								showViewers();
 								createGizmos(ctx);
 							}
 							else {
-								prefab.points.remove(sp);
+								editContext.scene.editor.deleteElements([sp], () -> {}, false, false);
+								for (sp in prefab.points)
+									sp.computeName(editContext.getContext(sp));
+								@:privateAccess editContext.scene.editor.refresh(Partial);
 								prefab.updateInstance(ctx);
-								showViewers(ctx);
+								showViewers();
 								createGizmos(ctx);
 							}
 						}));
@@ -541,11 +510,11 @@ class SplineEditor {
 
 					if( K.isDown( K.SHIFT ) ) {
 						var index = prefab.points.indexOf(getClosestSplinePointFromMouse(s2d.mouseX, s2d.mouseY, ctx));
-						for( spv in splinePointViewers ) {
-							if( index == splinePointViewers.indexOf(spv) )
-								spv.setColor(0xFFFF0000);
+						for( sp in prefab.points ) {
+							if( index == prefab.points.indexOf(sp) )
+								sp.setColor(0xFFFF0000);
 							else
-								spv.setColor(0xFFFFFFFF);
+								sp.setColor(0xFF0000FF);
 						}
 					}
 
@@ -577,14 +546,21 @@ class SplineEditor {
 
 		var reverseButton = props.find(".reverse");
 		reverseButton.click(function(_) {
-			prefab.points.reverse();
-			for( p in prefab.points )
-				p.rotate(0, 0, hxd.Math.degToRad(180));
+			prefab.children.reverse();
+			for (sp in prefab.points) {
+				sp.rotationZ += hxd.Math.degToRad(180);
+				sp.computeName(editContext.getContext(sp));
+			}
+			@:privateAccess editContext.scene.editor.refresh(Partial);
 
 			undo.change(Custom(function(undo) {
-				prefab.points.reverse();
-				for( p in prefab.points )
-					p.rotate(0, 0, hxd.Math.degToRad(180));
+				prefab.children.reverse();
+				for (sp in prefab.points) {
+					sp.rotationZ += hxd.Math.degToRad(180);
+					sp.computeName(editContext.getContext(sp));
+				}
+				@:privateAccess editContext.scene.editor.refresh(Partial);
+				
 			}));
 			ctx.onChange(prefab, null);
 			removeGizmos();

+ 1 - 1
hrt/prefab/Prefab.hx

@@ -49,7 +49,7 @@ class Prefab {
 	/**
 		Creates a new prefab with the given parent.
 	**/
-	public function new(?parent) {
+	public function new(?parent : Prefab) {
 		this.parent = parent;
 		children = [];
 	}

+ 186 - 101
hrt/prefab/l3d/Spline.hx

@@ -18,18 +18,142 @@ typedef SplinePointData = {
 
 class SplineData {
 	public var length : Float;
-	public var step : Float;
-	public var threshold : Float;
+	public var step : Int;
 	public var samples : Array<SplinePointData> = [];
 	public function new() {}
 }
 
-class SplinePoint extends h3d.scene.Object {
+class SplinePointObject extends h3d.scene.Object {
+	override function sync(ctx : h3d.scene.RenderContext)
+	{
+		onSync(ctx);
+		super.sync(ctx);
+	}
+
+	override function onRemove() {
+		super.onRemove();
+		onRemoveDynamic();
+	}
+	public dynamic function onRemoveDynamic() {}
+	public dynamic function onSync(rctx: h3d.scene.RenderContext) {}
+}
 
-	public function new(x : Float, y : Float, z : Float, parent : h3d.scene.Object) {
+class SplinePoint extends Object3D {
+
+	var pointViewer : h3d.scene.Mesh;
+	var controlPointsViewer : h3d.scene.Graphics;
+	var indexText : h2d.ObjectFollower;
+	var spline(get, default) : Spline;
+	var obj : SplinePointObject;
+	function get_spline() {
+		return parent.to(Spline);
+	}
+
+	override public function new(?parent) {
 		super(parent);
-		setPosition(x,y,z);
+		type = "splinePoint";
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		ctx.local3d = createObject(ctx);
+		pointViewer = new h3d.scene.Mesh(h3d.prim.Sphere.defaultUnitSphere(), null, ctx.local3d.getScene());
+		pointViewer.ignoreParentTransform = true;
+		pointViewer.follow = ctx.local3d;
+		pointViewer.followPositionOnly = true;
+		pointViewer.setScale(0.2);
+		pointViewer.name = "pointViewer";
+		pointViewer.material.setDefaultProps("ui");
+		pointViewer.material.color.set(0,0,1,1);
+		pointViewer.material.mainPass.depthTest = Always;
+
+		controlPointsViewer = new h3d.scene.Graphics(ctx.local3d);
+		controlPointsViewer.name = "controlPointsViewer";
+		controlPointsViewer.lineStyle(4, 0xffffff);
+		controlPointsViewer.material.mainPass.setPassName("ui");
+		controlPointsViewer.material.mainPass.depthTest = Always;
+		controlPointsViewer.ignoreParentTransform = false;
+		controlPointsViewer.clear();
+		controlPointsViewer.moveTo(1, 0, 0);
+		controlPointsViewer.lineTo(-1, 0, 0);
+
+		indexText = new h2d.ObjectFollower(pointViewer,  @:privateAccess ctx.local2d.getScene());
+		var t = new h2d.Text(hxd.res.DefaultFont.get(), indexText);
+		t.textColor = 0xff00ff;
+		t.textAlign = Center;
+		t.dropShadow = { dx : 0.5, dy : 0.5, color : 0x202020, alpha : 1.0 };
+		t.setScale(2.5);
+		applyTransform(ctx.local3d);
+		setViewerVisible(false);
+		obj = new SplinePointObject(ctx.local3d);
+		obj.onSync = function(rctx) {
+			var cam = rctx.camera;
+			var gpos = obj.getAbsPos().getPosition();
+			var distToCam = cam.pos.sub(gpos).length();
+			var engine = h3d.Engine.getCurrent();
+			var ratio = 18 / engine.height;
+			pointViewer.setScale(ratio * distToCam * Math.tan(cam.fovY * 0.5 * Math.PI / 180.0));
+			@:privateAccess obj.calcAbsPos();
+
+			var t = Std.downcast(indexText.getChildAt(0), h2d.Text);
+			t.text = "" + spline.points.indexOf(this);
+		}
+		obj.onRemoveDynamic = function() {
+			indexText.remove();
+			pointViewer.remove();
+		}
+		updateInstance(ctx);
+		return ctx;
+	}
+
+	override function applyTransform(o : h3d.scene.Object) {
+		super.applyTransform(o);
+		#if editor
+			if (spline.editor != null)
+				@:privateAccess spline.computeSpline(spline.editor.editContext.getContext(spline));
+		#end
+	}
+
+	override function updateInstance(ctx : Context, ?propName : String) {
+		super.updateInstance(ctx, propName);
+		#if editor
+			if( spline.editor != null ) {
+				spline.editor.setSelected(spline.editor.editContext.getContext(spline), true);
+				spline.editor.update(spline.editor.editContext.getContext(spline));
+			}
+		#end
+		for (sp in spline.points) {
+			sp.computeName(ctx);
+		}
+	}
+
+	override function removeInstance( ctx : Context) : Bool {
+		haxe.Timer.delay(() -> { // wait for next frame, need the point to be removed from children to recompute spline accurately
+			#if editor
+				if (spline.editor != null && spline.editor.editContext.getContext(spline) != null)
+					@:privateAccess spline.computeSpline(spline.editor.editContext.getContext(spline));
+			#end
+		}, 0);
+		return super.removeInstance(ctx);
+	}
+
+	public function computeName(ctx) {
+		name = "SplinePoint" + spline.points.indexOf(this);
+		ctx.local3d.name = name;
+	}
+
+	#if editor
+	override function edit(ctx : EditContext) {
+		super.edit(ctx);
+		if( spline.editor == null ) {
+			spline.editor = new hide.prefab.SplineEditor(spline, ctx.properties.undo);
+		}
+		spline.editor.editContext = ctx;
+	}
+	override function getHideProps() : HideProps {
+		return { icon : "arrows-v", name : "SplinePoint", allowParent: function(p) return p.to(Spline) != null, allowChildren: function(s) return false};
 	}
+	#end
 
 	inline public function getPoint() : h3d.col.Point {
 		return getAbsPos().getPosition().toPoint();
@@ -44,7 +168,7 @@ class SplinePoint extends h3d.scene.Object {
 	public function getFirstControlPoint() : h3d.col.Point {
 		var absPos = getAbsPos();
 		var right = absPos.front();
-		right.scale3(scaleX);
+		right.scale3(scaleX*scaleY);
 		var pos = new h3d.col.Point(absPos.tx, absPos.ty, absPos.tz);
 		pos = pos.add(right.toPoint());
 		return pos;
@@ -53,21 +177,39 @@ class SplinePoint extends h3d.scene.Object {
 	public function getSecondControlPoint() : h3d.col.Point {
 		var absPos = getAbsPos();
 		var left = absPos.front();
-		left.scale3(-scaleX);
+		left.scale3(-scaleX*scaleZ);
 		var pos = new h3d.col.Point(absPos.tx, absPos.ty, absPos.tz);
 		pos = pos.add(left.toPoint());
 		return pos;
 	}
+	public function setViewerVisible(visible : Bool) {
+		pointViewer.visible = visible;
+		indexText.visible = visible;
+		controlPointsViewer.visible = visible;
+	}
+	public function setColor( color : Int ) {
+		controlPointsViewer.setColor(color);
+		pointViewer.material.color.setColor(color);
+	}
+
+	static var _ = hrt.prefab.Library.register("splinePoint", SplinePoint);
 }
 
 class Spline extends Object3D {
+	public var points(get, null) : Array<SplinePoint>;
+	function get_points() {
+		var pts : Array<SplinePoint> = [];
+		for (c in children) {
+			var sp = c.to(SplinePoint);
+			if (sp != null) pts.push(sp);
+		}
+		return pts;
+	}
 
-	public var points : Array<SplinePoint> = [];
-	@:c public var shape : CurveShape = Quadratic;
+	@:c public var shape : CurveShape = Linear;
 
 	var data : SplineData;
-	@:s var step : Float = 1.0;
-	@:s var threshold : Float = 0.01;
+	@:s var step : Int = 1;
 
 	// Save/Load the curve as an array of local transform
 	@:c public var pointsData : Array<h3d.Matrix> = [];
@@ -87,43 +229,14 @@ class Spline extends Object3D {
 	override function save() {
 		var obj : Dynamic = super.save();
 
-		var tmp = new h3d.Matrix();
-		inline function getTransform( o : h3d.scene.Object, m : h3d.Matrix ) : h3d.Matrix {
-			m.identity();
-			@:privateAccess o.qRot.toMatrix(m);
-			m._11 *= o.scaleX;
-			m._12 *= o.scaleX;
-			m._13 *= o.scaleX;
-			m._21 *= o.scaleY;
-			m._22 *= o.scaleY;
-			m._23 *= o.scaleY;
-			m._31 *= o.scaleZ;
-			m._32 *= o.scaleZ;
-			m._33 *= o.scaleZ;
-			m._41 = o.x;
-			m._42 = o.y;
-			m._43 = o.z;
-			return m;
-		}
-
-		if( points != null && points.length > 0 && wasEdited ) {
-			obj.points = [ for(sp in points) {
-								var m = getTransform(sp, tmp);
-								[for(f in m.getFloats()) hxd.Math.fmt(f) ];
-							} ];
-		}
-		// Clone support
-		else if( pointsData != null && pointsData.length > 0 ) {
-			obj.points =  [ for(abs in pointsData) {
-								[for(f in abs.getFloats()) hxd.Math.fmt(f) ];
-							} ];
-		}
 		obj.shape = shape.getIndex();
 		return obj;
 	}
 
 	override function load( obj : Dynamic ) {
 		super.load(obj);
+
+		// Backward compatibility
 		pointsData = [];
 		if( obj.points != null ) {
 			var points : Array<Dynamic> = obj.points;
@@ -141,31 +254,30 @@ class Spline extends Object3D {
 		var tmp = new h3d.Matrix();
 		points = [];
 		for( pd in pointsData ) {
-			var sp = new SplinePoint(0, 0, 0, null);
+			var sp = new SplinePoint(this);
 			tmp.load(pd);
 			tmp.multiply(tmp, m);
 			sp.setTransform(tmp);
 			sp.getAbsPos();
-			points.push(sp);
 		}
 		computeSplineData();
 	}
 
+
 	override function makeInstance( ctx : hrt.prefab.Context ) : hrt.prefab.Context {
 		var ctx = ctx.clone(this);
-		ctx.local3d = new h3d.scene.Object(ctx.local3d);
+		ctx.local3d = createObject(ctx);
 		ctx.local3d.name = name;
 
-		points = [];
+		// Backward compatibility
 		for( pd in pointsData ) {
-			var sp = new SplinePoint(0, 0, 0, ctx.local3d);
+			var sp = new SplinePoint(this);
 			sp.setTransform(pd);
 			sp.getAbsPos();
-			points.push(sp);
 		}
 
 		if( points.length == 0 )
-			points.push(new SplinePoint(0,0,0, ctx.local3d));
+			new SplinePoint(this);
 
 		updateInstance(ctx);
 		return ctx;
@@ -173,13 +285,11 @@ class Spline extends Object3D {
 
 	override function updateInstance( ctx : hrt.prefab.Context , ?propName : String ) {
 		super.updateInstance(ctx, propName);
-		computeSplineData();
-
 		#if editor
 		if( editor != null )
 			editor.update(ctx, propName);
-		generateSplineGraph(ctx);
 		#end
+		computeSpline(ctx);
 	}
 
 	// Return an interpolation of two samples at t, 0 <= t <= 1
@@ -191,8 +301,8 @@ class Spline extends Object3D {
 
 		// The last point is not at the same distance, be aware of that case
 		t = hxd.Math.clamp(t);
-		var s1 : Int = hxd.Math.floor(l / step);
-		var s2 : Int = hxd.Math.ceil(l / step);
+		var s1 : Int = hxd.Math.floor(l / (1./step));
+		var s2 : Int = hxd.Math.ceil(l / (1./step));
 		s1 = hxd.Math.iclamp(s1, 0, data.samples.length - 1);
 		s2 = hxd.Math.iclamp(s2, 0, data.samples.length - 1);
 
@@ -208,7 +318,7 @@ class Spline extends Object3D {
 		// Linear interpolation between the two samples
 		else {
 			var segmentLength = data.samples[s1].pos.distance(data.samples[s2].pos);
-			var t = (l - (s1 * step)) / segmentLength;
+			var t = (l - (s1 * 1./step)) / segmentLength;
 			pos.lerp(data.samples[s1].pos, data.samples[s2].pos, t);
 			if(tangent != null)
 				tangent.lerp(data.samples[s1].tangent, data.samples[s2].tangent, t);
@@ -237,13 +347,13 @@ class Spline extends Object3D {
 		return data.length;
 	}
 
-	// Sample the spline with the step and threshold
+	// Sample the spline with the step
 	function computeSplineData() {
 
 		var sd = new SplineData();
 		data = sd;
 
-		if( step <= 0 || threshold <= 0 )
+		if( step <= 0 )
 			return;
 
 		if( points == null || points.length <= 1 )
@@ -251,50 +361,20 @@ class Spline extends Object3D {
 
 		// Sample the spline
 		var samples : Array<SplinePointData> = [{ pos : points[0].getPoint(), tangent : points[0].getTangent(), prev : points[0], next : points[1] }];
-		var i = 0;
-		var sumT = 0.0;
-		var maxT = 1.0;
-		var minT = 0.0;
 		var maxI = loop ? points.length : points.length - 1;
 		var curP = points[0];
 		var nextP = points[1];
-		while( i < maxI ) {
-			var t = (maxT + minT) * 0.5;
-
-			var p = getPointBetween(t, curP, nextP);
-			var curSegmentLength = p.distance(samples[samples.length - 1].pos);
-
-			// Point found
-			if( hxd.Math.abs(curSegmentLength - step) <= threshold ) {
+		for (i in 1...maxI + 1) {
+			var t = 0.;
+			while (t <= 1.) {
+				var p = getPointBetween(t, curP, nextP);
 				samples.insert(samples.length, { pos : p, tangent : getTangentBetween(t, curP, nextP), prev : curP, next : nextP });
-				sumT = t;
-				maxT = 1.0;
-				minT = sumT;
-				// Last point of the curve too close from the last sample
-				if( nextP.getPoint().distance(samples[samples.length - 1].pos) < step ) {
-					// End of the spline
-					if( i == maxI - 1 ) {
-						samples.insert(samples.length, { pos : nextP.getPoint(), tangent : nextP.getTangent(), prev : curP, next : nextP, t : 1.0 });
-						break;
-					}
-					// End of the current curve
-					else {
-						i++;
-						curP = points[i];
-						nextP = points[(i+1) % points.length];
-						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;
+				t += 1./step;
 			}
+			samples.insert(samples.length, { pos : nextP.getPoint(), tangent : nextP.getTangent(), prev : curP, next : nextP, t : 1.0 });
+			curP = points[i];
+			nextP = points[(i + 1) % points.length];
+
 		}
 		sd.samples = samples;
 
@@ -419,6 +499,13 @@ class Spline extends Object3D {
 		}
 	}
 
+	public function computeSpline(ctx : hrt.prefab.Context) {
+		computeSplineData();
+		#if editor
+			generateSplineGraph(ctx);
+		#end
+	}
+
 	#if editor
 
 	public function onEdit( b : Bool ) {
@@ -442,16 +529,14 @@ 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>Step</dt><dd><input type="range" min="0.1" max="1" field="step"/></dd>
-					<dt>Threshold</dt><dd><input type="range" min="0.001" max="1" field="threshold"/></dd>
+					<dt>Step</dt><dd><input type="range" min="1" max="10" step="1" field="step"/></dd>
 					<dt>Loop</dt><dd><input type="checkbox" field="loop"/></dd>
 					<dt>Show Spline</dt><dd><input type="checkbox" field="showSpline"/></dd>
 					<dt>Type</dt>
 						<dd>
 							<select field="shape" >
 								<option value="Linear">Linear</option>
-								<option value="Quadratic">Quadratic</option>
-								<option value="Cubic">Cubic</option>
+								<option value="Cubic">Curve</option>
 							</select>
 						</dd>
 				</dl>
@@ -466,7 +551,7 @@ class Spline extends Object3D {
 	}
 
 	override function getHideProps() : HideProps {
-		return { icon : "arrows-v", name : "Spline" };
+		return { icon : "arrows-v", name : "Spline", allowChildren: function(s) return Library.isOfType(s, SplinePoint) };
 	}
 	#end