Browse Source

Merge branch 'master' into dynamic-bones

LeoVgr 7 months ago
parent
commit
e6f998b6da

+ 5 - 0
bin/style.css

@@ -4518,6 +4518,11 @@ blend-space-2d-root main-panel graph-container svg .preview-axis {
   stroke: #008a00;
   stroke-width: 2;
 }
+blend-space-2d-root main-panel graph-container svg .preview-axis-real {
+  fill: none;
+  stroke: #008a00;
+  stroke-width: 1;
+}
 blend-space-2d-root main-panel graph-container drag-handler {
   z-index: 1;
   position: absolute;

+ 6 - 0
bin/style.less

@@ -5361,6 +5361,12 @@ blend-space-2d-root {
 					stroke: #008a00;
 					stroke-width: 2;
 				}
+
+				.preview-axis-real {
+					fill: none;
+					stroke: #008a00;
+					stroke-width: 1;
+				}
 			}
 
 			drag-handler {

+ 6 - 6
hide/view/GraphEditor.hx

@@ -123,12 +123,12 @@ class GraphEditor extends hide.comp.Component {
 		fn(false);
 	}
 
-	public function commitUndo() {
+	public function commitUndo(noDataChange: Bool = false) {
 		if (currentUndoBuffer.length <= 0) {
 			return;
 		}
 		var buffer = currentUndoBuffer;
-		editor.getUndo().change(Custom(execUndo.bind(buffer)));
+		editor.getUndo().change(Custom(execUndo.bind(buffer)), null, noDataChange);
 		currentUndoBuffer = [];
 	}
 
@@ -257,7 +257,7 @@ class GraphEditor extends hide.comp.Component {
 						opSelect(id, true, save.buffer);
 					}
 					currentUndoBuffer = save.buffer;
-					commitUndo();
+					commitUndo(true);
 					undoSave = null;
 					return;
 				}
@@ -1041,7 +1041,7 @@ class GraphEditor extends hide.comp.Component {
 		for (id => _ in boxes) {
 			opSelect(id, true, currentUndoBuffer);
 		}
-		commitUndo();
+		commitUndo(true);
 	}
 
 	public function setSelection(nodes: Array<IGraphNode>) {
@@ -1051,7 +1051,7 @@ class GraphEditor extends hide.comp.Component {
 			opSelect(node.id, true, currentUndoBuffer);
 		}
 
-		commitUndo();
+		commitUndo(true);
 	}
 
 	public function centerSelection() {
@@ -1281,7 +1281,7 @@ class GraphEditor extends hide.comp.Component {
 					clearSelectionBoxesUndo(currentUndoBuffer);
 				}
 				opSelect(box.node.id, true, currentUndoBuffer);
-				commitUndo();
+				commitUndo(true);
 			}
 			elt.get(0).setPointerCapture(e.pointerId);
 			beginMove(e);

+ 4 - 4
hide/view/animgraph/AnimGraphEditor.hx

@@ -55,7 +55,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         refreshPamamList();
 
         var dl = new Element("<dl></dl>").appendTo(propertiesContainer);
-        addAnimSetSelector(dl, undo, () -> previewState.providerIndex, (i: Int) -> {
+        addAnimSetSelector(dl, {animDirectory: animGraph.animFolder, assetPath: state.path}, undo, () -> previewState.providerIndex, (i: Int) -> {
 			previewState.providerIndex = i;
             savePreviewState();
 			refreshPreview();
@@ -238,13 +238,13 @@ class AnimGraphEditor extends GenericGraphEditor {
         return options;
     }
 
-    static public function addAnimSetSelector(target: Element, undo: hide.ui.UndoHistory, getIndex: () -> Int, setIndex:(Int) -> Void) {
+    static public function addAnimSetSelector(target: Element, context:hrt.animgraph.AnimGraph.EditorProviderContext, undo: hide.ui.UndoHistory, getIndex: () -> Int, setIndex:(Int) -> Void) {
         if (hrt.animgraph.AnimGraph.customEditorResolverProvider != null)
         {
             var div = new Element("<div></div>").appendTo(target);
             div.append(new Element("<dt>Anim Set</dt>"));
 
-            var providers = hrt.animgraph.AnimGraph.customEditorResolverProvider(_);
+            var providers = hrt.animgraph.AnimGraph.customEditorResolverProvider(context);
 
             var button = new hide.comp.Button(div, null, null, {hasDropdown: true});
             button.label = providers[getIndex()].name;
@@ -297,7 +297,7 @@ class AnimGraphEditor extends GenericGraphEditor {
 
             var resolver = null;
             if (AnimGraph.customEditorResolverProvider != null) {
-                var providers = AnimGraph.customEditorResolverProvider(_);
+                var providers = AnimGraph.customEditorResolverProvider({animDirectory: animGraph.animFolder, assetPath: state.path});
                 if (providers != null && previewState.providerIndex > providers.length) {
                     previewState.providerIndex = 0;
                     savePreviewState();

+ 41 - 3
hide/view/animgraph/BlendSpace2DEditor.hx

@@ -38,6 +38,8 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 	var animPreview : hrt.animgraph.AnimGraphInstance;
 
+	var previewBlendPos : hide.Element;
+
 	inline function getPointPos(clientX : Float, clientY : Float, snap: Bool) : h2d.col.Point {
 		var x = hxd.Math.clamp(graphXToLocal(clientX), blendSpace2D.minX, blendSpace2D.maxX);
 		var y = hxd.Math.clamp(graphYToLocal(clientY), blendSpace2D.minY, blendSpace2D.maxY);
@@ -361,7 +363,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 				@:privateAccess blendSpaceNode.blendSpace = blendSpace2D;
 				var resolver = null;
 				if (hrt.animgraph.AnimGraph.customEditorResolverProvider != null) {
-					var resolvers = hrt.animgraph.AnimGraph.customEditorResolverProvider(_);
+					var resolvers = hrt.animgraph.AnimGraph.customEditorResolverProvider({animDirectory: blendSpace2D.animFolder, assetPath: state.path});
 					if (resolvers != null) {
 						if (previewState.providerIndex > resolvers.length) {
 							previewState.providerIndex = 0;
@@ -380,7 +382,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 				var resolver = null;
 				if (hrt.animgraph.AnimGraph.customEditorResolverProvider != null) {
-					var resolvers = hrt.animgraph.AnimGraph.customEditorResolverProvider(_);
+					var resolvers = hrt.animgraph.AnimGraph.customEditorResolverProvider({animDirectory: blendSpace2D.animFolder, assetPath: state.path});
 					if (resolvers != null) {
 						resolver = resolvers[previewState.providerIndex]?.resolver;
 					}
@@ -401,6 +403,9 @@ class BlendSpace2DEditor extends hide.view.FileView {
 					}
 				}
 			}
+
+			updatePreviewAxis();
+			animPreview.resetSmoothedValues();
 		}
 	}
 
@@ -418,7 +423,9 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		<div class="group" name="BlendSpace">
 			<dl>
 				<dt>Min/MaxX</dt><dd><input type="number" field="minX"/><input type="number" field="maxX"/></dd>
+				<dt>Smooth X</dt><dd><input type="range" min="0.0" max="1.0" field="smoothX"/></dd>
 				<dt>Min/MaxY</dt><dd><input type="number" field="minY"/><input type="number" field="maxY"/></dd>
+				<dt>Smooth Y</dt><dd><input type="range" min="0.0" max="1.0" field="smoothY"/></dd>
 			</dl>
 		</div>
 		'), blendSpace2D, (_) -> {
@@ -467,7 +474,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			updatePreviewAxis();
 		});
 
-		AnimGraphEditor.addAnimSetSelector(preview.find("dl"), undo, () -> previewState.providerIndex, (i: Int) -> {
+		AnimGraphEditor.addAnimSetSelector(preview.find("dl"), {animDirectory: blendSpace2D.animFolder, assetPath: state.path}, undo, () -> previewState.providerIndex, (i: Int) -> {
 			previewState.providerIndex = i;
 			savePreviewState();
 			refreshPreviewAnimation();
@@ -552,6 +559,20 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 	}
 
+	var queuedRequest : Int = -1;
+	function onRequestAnimationFrame(dt: Float) {
+		if (previewBlendPos != null && animPreview != null) {
+			var root : hrt.animgraph.nodes.BlendSpace2D.BlendSpace2D = cast @:privateAccess animPreview.rootNode;
+			var rx = @:privateAccess root.realX;
+			var ry = @:privateAccess root.realY;
+			previewBlendPos.attr("transform", 'translate(${localXToGraph(rx)}, ${localYToGraph(ry)})');
+
+			queuedRequest = js.Browser.window.requestAnimationFrame(onRequestAnimationFrame);
+			return;
+		}
+		queuedRequest = -1;
+	}
+
 	function createPoint() {
 
 	}
@@ -669,6 +690,23 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			final size = 10;
 			graph.line(g, -size, -size, size, size).addClass("preview-axis");
 			graph.line(g, -size, size, size, -size).addClass("preview-axis");
+
+			if (animPreview != null) {
+				var root : hrt.animgraph.nodes.BlendSpace2D.BlendSpace2D = cast @:privateAccess animPreview.rootNode;
+				var rx = @:privateAccess root.realX;
+				var ry = @:privateAccess root.realY;
+
+				previewBlendPos = graph.group(graph.element);
+				previewBlendPos.attr("transform", 'translate(${localXToGraph(rx)}, ${localYToGraph(ry)})');
+				final size = 10;
+				graph.line(previewBlendPos, -size, -size, size, size).addClass("preview-axis-real");
+				graph.line(previewBlendPos, -size, size, size, -size).addClass("preview-axis-real");
+
+				if (queuedRequest < 0) {
+					queuedRequest = js.Browser.window.requestAnimationFrame(onRequestAnimationFrame);
+				}
+			}
+
 		}
 	}
 

+ 2 - 2
hrt/animgraph/AnimGraph.hx

@@ -42,11 +42,11 @@ class AnimGraph extends hrt.prefab.Prefab {
 		Get the animation "template" for this AnimGraph.
 		This anim should be instanciated using getInstance() after that (or use the h3d.scene.Object.playAnimation() function that does this for you)
 	**/
-	public function getAnimation(previewNode: hrt.animgraph.nodes.AnimNode = null, resolver: hrt.animgraph.AnimGraphInstance.AnimResolver = null) : AnimGraphInstance {
+	public function getAnimation(previewNode: hrt.animgraph.nodes.AnimNode = null, modelCache: h3d.prim.ModelCache = null, resolver: hrt.animgraph.AnimGraphInstance.AnimResolver = null) : AnimGraphInstance {
 		if (resolver == null && customResolverProvider != null) {
 			resolver = customResolverProvider(this);
 		}
-		return AnimGraphInstance.fromAnimGraph(this, previewNode, resolver);
+		return AnimGraphInstance.fromAnimGraph(this, previewNode, modelCache, resolver);
 	}
 
 	override function save() {

+ 34 - 3
hrt/animgraph/AnimGraphInstance.hx

@@ -28,6 +28,9 @@ class AnimGraphInstance extends h3d.anim.Animation {
 	var defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 
 	var resolver : AnimResolver = null;
+	var modelCache: h3d.prim.ModelCache;
+
+	var tmpMatrix : h3d.Matrix = new h3d.Matrix();
 
 	var tmpMatrix : h3d.Matrix = new h3d.Matrix();
 
@@ -35,20 +38,21 @@ class AnimGraphInstance extends h3d.anim.Animation {
 	var editorSkipClone : Bool = false;
 	#end
 
-	static function fromAnimGraph(animGraph:AnimGraph, outputNode: hrt.animgraph.nodes.AnimNode = null, resolver: AnimResolver) : AnimGraphInstance {
+	static function fromAnimGraph(animGraph:AnimGraph, outputNode: hrt.animgraph.nodes.AnimNode = null, modelCache: h3d.prim.ModelCache = null, resolver: AnimResolver) : AnimGraphInstance {
 		outputNode ??= cast animGraph.nodes.find((node) -> Std.downcast(node, hrt.animgraph.nodes.Output) != null);
 		if (outputNode == null)
 			throw "Animgraph has no output node";
 
-		var inst = new AnimGraphInstance(outputNode, resolver, animGraph.name, 1000, 1/60.0);
+		var inst = new AnimGraphInstance(outputNode, modelCache, resolver, animGraph.name, 1000, 1/60.0);
 		return inst;
 	}
 
-	public function new(rootNode: hrt.animgraph.nodes.AnimNode, resolver: AnimResolver = null, name: String, framesCount: Int, sampling: Float) {
+	public function new(rootNode: hrt.animgraph.nodes.AnimNode, modelCache: h3d.prim.ModelCache = null, resolver: AnimResolver = null, name: String, framesCount: Int, sampling: Float) {
 		// Todo : Define a true length for the animation OR make so animations can have an undefined length
 		super(name, framesCount, sampling);
 		this.rootNode = rootNode;
 		this.resolver = resolver ?? defaultResolver;
+		this.modelCache = modelCache ?? new h3d.prim.ModelCache();
 		defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 	}
 
@@ -80,6 +84,17 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		}
 	}
 
+	/**
+		Force nodes in the graph that smooth their input over time to match the
+		current value of their parameters
+	**/
+	public function resetSmoothedValues() {
+		tickRec(rootNode, 0.0);
+		map(rootNode, (node) -> {
+			node.resetSmoothedValues();
+		});
+	}
+
 	override function clone(?target: h3d.anim.Animation) : h3d.anim.Animation {
 		#if editor
 		if (editorSkipClone) {
@@ -128,9 +143,24 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		map(rootNode, updateNodeInputs);
 
 		boneMap = rootNode.getBones(ctx);
+		rootNode.onEvent = onEventHandler;
+
+		map(rootNode, (node) -> {
+			var animNode =	Std.downcast(node, hrt.animgraph.nodes.AnimNode);
+			if (animNode != null) {
+				animNode.setupAnimEvents();
+			}
+		});
+
 		return boneMap;
 	}
 
+	function onEventHandler(name: String) {
+		if (onEvent != null) {
+			onEvent(name);
+		}
+	}
+
 	override function bind(base:h3d.scene.Object) {
 		objects = [];
 		target = base;
@@ -138,6 +168,7 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		var ctx = new hrt.animgraph.nodes.AnimNode.GetBoneContext();
 		ctx.targetObject = base;
 		ctx.resolver = resolver.bind(this, base);
+		ctx.modelCache = this.modelCache;
 
 		var bones = getBones(ctx);
 		if (bones != null) {

+ 4 - 0
hrt/animgraph/BlendSpace2D.hx

@@ -17,8 +17,12 @@ class BlendSpace2D extends hrt.prefab.Prefab {
 
 	@:s var minX = 0.0;
 	@:s var maxX = 1.0;
+	@:s var smoothX = 0.0;
+
 	@:s var minY = 0.0;
 	@:s var maxY = 1.0;
+	@:s var smoothY = 0.0;
+
 
 	var instance : BlendSpace2DInstance;
 

+ 8 - 0
hrt/animgraph/Node.hx

@@ -48,6 +48,14 @@ implements hide.view.GraphInterface.IGraphNode
 
 	}
 
+	/**
+		Called on the node when AnimGraphInstance.resetSmoothedValues is called
+		Should reset all the smoothed parameters to their input value OR default values
+	**/
+	public function resetSmoothedValues() : Void {
+
+	}
+
 
 	// Serialization api
 

+ 2 - 2
hrt/animgraph/Resource.hx

@@ -40,8 +40,8 @@ class Resource extends hxd.res.Resource {
 		return animGraph;
 	}
 
-	public function loadAnim(resolver: hrt.animgraph.AnimGraphInstance.AnimResolver = null) : AnimGraphInstance {
-		return load().getAnimation(resolver);
+	public function loadAnim(modelCache: h3d.prim.ModelCache = null, resolver: hrt.animgraph.AnimGraphInstance.AnimResolver = null) : AnimGraphInstance {
+		return load().getAnimation(modelCache, resolver);
 	}
 
 	public static var CACHE_VERSION = 0;

+ 7 - 1
hrt/animgraph/nodes/AnimNode.hx

@@ -10,6 +10,7 @@ class GetBoneContext {
 
 	public var targetObject:h3d.scene.Object;
 	public var resolver : (path: String) -> Null<String>;
+	public var modelCache : h3d.prim.ModelCache;
 }
 
 class GetBoneTransformContext {
@@ -48,10 +49,12 @@ class GetBoneTransformContext {
 /**
 	An anim node outpus a animation that can be consumed as input parameter by other nodes
 **/
-class AnimNode extends Node {
+abstract class AnimNode extends Node {
 	var numAnimInput : Int;
 	var boneIdToAnimInputBone : Array<Int>;
 
+	var onEvent : (String) -> Void;
+
 	inline function getInputBoneId(boneId: Int, inputId: Int) {
 		return boneId * numAnimInput + inputId;
 	}
@@ -102,6 +105,9 @@ class AnimNode extends Node {
 	function getBoneTransform(boneId: Int, outMatrix: h3d.Matrix, ctx: GetBoneTransformContext) : Void {
 	}
 
+	abstract function setupAnimEvents() : Void;
+
+
 	#if editor
 
 	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {

+ 9 - 0
hrt/animgraph/nodes/Blend.hx

@@ -59,6 +59,15 @@ class Blend extends AnimNode {
 		outMatrix._33 = m1._33 * a + m2._33 * b;
 	}
 
+	function setupAnimEvents() {
+		a.onEvent = (name:String) -> {
+			if (alpha < 0.5) onEvent(name);
+		}
+		b.onEvent = (name:String) -> {
+			if (alpha > 0.5) onEvent(name);
+		}
+	}
+
 	#if editor
 	override function getSize():Int {
 		return Node.SIZE_SMALL;

+ 9 - 0
hrt/animgraph/nodes/BlendPerBone.hx

@@ -49,6 +49,15 @@ class BlendPerBone extends AnimNode {
 		}
 	}
 
+	function setupAnimEvents() {
+		a.onEvent = (name:String) -> {
+			onEvent(name);
+		}
+		b.onEvent = (name:String) -> {
+			onEvent(name);
+		}
+	}
+
 	#if editor
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 		var arr =  super.getPropertiesHTML(width);

+ 87 - 3
hrt/animgraph/nodes/BlendSpace2D.hx

@@ -19,12 +19,20 @@ typedef AnimInfo = {
 @:access(hrt.animgraph.BlendSpace2D)
 class BlendSpace2D extends AnimNode {
 	@:input var bsX(default, set): Float = 0.5;
+	var realX : Float = 0.5;
+	var vX : Float = 0.0;
+
 	@:input var bsY(default, set): Float = 0.5;
+	var realY : Float = 0.5;
+	var vY : Float = 0.0;
+
 
 	@:s var path : String = "";
 
 	var dirtyPos: Bool = true;
 
+	var prevAnimEventBind : h3d.anim.Animation;
+
 	function set_bsX(v: Float) : Float {
 		if (v != bsX)
 			currentTriangle = -1;
@@ -50,6 +58,35 @@ class BlendSpace2D extends AnimNode {
 	var workQuats : Array<h3d.Quat> = [new h3d.Quat(), new h3d.Quat(), new h3d.Quat()];
 	var refQuat = new h3d.Quat();
 
+
+	static function halfLifeToDamping(halfLife: Float) {
+    	return (4.0 * 0.69314718056) / (halfLife + 1e-5);
+	}
+
+	static function fastNegexp(x: Float) : Float
+	{
+    	return 1.0 / (1.0 + x + 0.48*x*x + 0.235*x*x*x);
+	}
+
+
+	inline static function criticalSpringDamper(x: Float, v: Float, xGloal: Float, vGoal: Float, halfLife: Float, dt: Float) : {x: Float, v: Float} {
+		final damping = halfLifeToDamping(halfLife);
+		final c = xGloal + (damping * vGoal) / (damping * damping ) / 4.0;
+		final half_damping = damping / 2.0;
+		final j0 = x - c;
+		final j1 = v + j0 * half_damping;
+		final eydt = fastNegexp(half_damping * dt);
+
+		return {x: eydt * (j0 + j1 * dt) + c, v: eydt *(v - j1*half_damping*dt)};
+	}
+
+	override function resetSmoothedValues() {
+		realX = bsX;
+		realY = bsY;
+		vX = 0.0;
+		vY = 0.0;
+	}
+
 	override function getBones(ctx: hrt.animgraph.nodes.AnimNode.GetBoneContext):Map<String, Int> {
 		var boneMap : Map<String, Int> = [];
 		animInfos = [];
@@ -57,6 +94,9 @@ class BlendSpace2D extends AnimNode {
 		triangles = [];
 		currentTriangle = -1;
 
+		realX = bsX;
+		realY = bsY;
+
 		var curOurBoneId = 0;
 
 		if (blendSpace == null) {
@@ -79,8 +119,8 @@ class BlendSpace2D extends AnimNode {
 						function makeAnim() : Int {
 							// Create a new animation
 							var index = animInfos.length;
-							var animBase = hxd.res.Loader.currentInstance.load(path).toModel().toHmd().loadAnimation();
-
+							var animModel = hxd.res.Loader.currentInstance.load(path).toModel();
+							var animBase = ctx.modelCache.loadAnimation(animModel);
 							var proxy = new hrt.animgraph.nodes.Input.AnimProxy(null);
 							var animInstance = animBase.createInstance(proxy);
 
@@ -137,6 +177,26 @@ class BlendSpace2D extends AnimNode {
 	override function tick(dt:Float) {
 		super.tick(dt);
 
+		if (blendSpace.smoothX > 0) {
+			var r = criticalSpringDamper(realX, vX, bsX, 0, blendSpace.smoothX, dt);
+			realX = r.x;
+			vX = r.v;
+
+			currentTriangle = -1;
+		} else {
+			realX = bsX;
+		}
+
+		if (blendSpace.smoothX > 0) {
+			var r = criticalSpringDamper(realY, vY, bsY, 0, blendSpace.smoothY, dt);
+			realY = r.x;
+			vY = r.v;
+
+			currentTriangle = -1;
+		} else {
+			realY = bsY;
+		}
+
 		for (animInfo in animInfos) {
 			// keep all the animations in sync
 			var scale = animInfo.selfSpeed;
@@ -159,7 +219,7 @@ class BlendSpace2D extends AnimNode {
 			return;
 
 		if (currentTriangle == -1) {
-			var curPos = inline new h2d.col.Point(bsX, bsY);
+			var curPos = inline new h2d.col.Point(realX, realY);
 
 			// find the triangle our curPos resides in
 			var collided = false;
@@ -213,6 +273,22 @@ class BlendSpace2D extends AnimNode {
 			if (currentTriangle == -1)
 				throw "assert";
 
+			var max = 0;
+			for (i in 1...3) {
+				if (weights[i] > weights[max]) {
+					max = i;
+				}
+			}
+
+			var strongestAnim = triangles[currentTriangle][max].animInfo?.anim;
+			if (prevAnimEventBind != strongestAnim) {
+				if (prevAnimEventBind != null)
+					prevAnimEventBind.onEvent = null;
+				if (strongestAnim != null)
+					strongestAnim.onEvent = animEventHander;
+				prevAnimEventBind = strongestAnim;
+			}
+
 			currentAnimLenght = 0.0;
 
 			// Compensate for null animations that don't have length
@@ -290,6 +366,14 @@ class BlendSpace2D extends AnimNode {
 		outMatrix._23 = workQuat.w;
 	}
 
+	function animEventHander(name: String) {
+		onEvent(name);
+	}
+
+	function setupAnimEvents() {
+		// handled by the triangle setup
+	}
+
 	#if editor
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 		var elts = super.getPropertiesHTML(width);

+ 4 - 0
hrt/animgraph/nodes/DefaultPose.hx

@@ -27,6 +27,10 @@ class DefaultPose extends AnimNode {
 		return bones;
 	}
 
+	function setupAnimEvents() {
+		// no events to handle
+	}
+
 	override function getBoneTransform(boneId:Int, outMatrix:h3d.Matrix, ctx:hrt.animgraph.nodes.AnimNode.GetBoneTransformContext) {
 		var bone = objects[boneId];
 		if (bone.matDecomposed == null) {

+ 6 - 0
hrt/animgraph/nodes/Input.hx

@@ -55,6 +55,12 @@ class Input extends AnimNode {
 		matrix.load(anim.getObjects()[id].targetObject.defaultTransform);
 	}
 
+	function setupAnimEvents() {
+		if (anim != null) {
+			anim.onEvent = onEvent;
+		}
+	}
+
 	#if editor
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 		var elts = super.getPropertiesHTML(width);

+ 4 - 0
hrt/animgraph/nodes/Output.hx

@@ -11,6 +11,10 @@ class Output extends AnimNode {
 		// update out using inputs
 	}
 
+	function setupAnimEvents() {
+		a.onEvent = onEvent;
+	}
+
 	#if editor
 	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
 		var info = super.getInfo();

+ 1 - 1
hrt/prefab/l3d/Spline.hx

@@ -619,7 +619,7 @@ class Spline extends hrt.prefab.Object3D {
 		var l = 0.0;
 		for( i in 0 ... samples.length - 1 ) {
 			samples[i].t = l/length;
-			samples[i].length = length;
+			samples[i].length = l;
 			l += samples[i].pos.distance(samples[i+1].pos);
 		}
 		samples[samples.length - 1].t = 1;