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: #008a00;
   stroke-width: 2;
   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 {
 blend-space-2d-root main-panel graph-container drag-handler {
   z-index: 1;
   z-index: 1;
   position: absolute;
   position: absolute;

+ 6 - 0
bin/style.less

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

+ 6 - 6
hide/view/GraphEditor.hx

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

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

@@ -55,7 +55,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         refreshPamamList();
         refreshPamamList();
 
 
         var dl = new Element("<dl></dl>").appendTo(propertiesContainer);
         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;
 			previewState.providerIndex = i;
             savePreviewState();
             savePreviewState();
 			refreshPreview();
 			refreshPreview();
@@ -238,13 +238,13 @@ class AnimGraphEditor extends GenericGraphEditor {
         return options;
         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)
         if (hrt.animgraph.AnimGraph.customEditorResolverProvider != null)
         {
         {
             var div = new Element("<div></div>").appendTo(target);
             var div = new Element("<div></div>").appendTo(target);
             div.append(new Element("<dt>Anim Set</dt>"));
             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});
             var button = new hide.comp.Button(div, null, null, {hasDropdown: true});
             button.label = providers[getIndex()].name;
             button.label = providers[getIndex()].name;
@@ -297,7 +297,7 @@ class AnimGraphEditor extends GenericGraphEditor {
 
 
             var resolver = null;
             var resolver = null;
             if (AnimGraph.customEditorResolverProvider != 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) {
                 if (providers != null && previewState.providerIndex > providers.length) {
                     previewState.providerIndex = 0;
                     previewState.providerIndex = 0;
                     savePreviewState();
                     savePreviewState();

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

@@ -38,6 +38,8 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 
 	var animPreview : hrt.animgraph.AnimGraphInstance;
 	var animPreview : hrt.animgraph.AnimGraphInstance;
 
 
+	var previewBlendPos : hide.Element;
+
 	inline function getPointPos(clientX : Float, clientY : Float, snap: Bool) : h2d.col.Point {
 	inline function getPointPos(clientX : Float, clientY : Float, snap: Bool) : h2d.col.Point {
 		var x = hxd.Math.clamp(graphXToLocal(clientX), blendSpace2D.minX, blendSpace2D.maxX);
 		var x = hxd.Math.clamp(graphXToLocal(clientX), blendSpace2D.minX, blendSpace2D.maxX);
 		var y = hxd.Math.clamp(graphYToLocal(clientY), blendSpace2D.minY, blendSpace2D.maxY);
 		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;
 				@:privateAccess blendSpaceNode.blendSpace = blendSpace2D;
 				var resolver = null;
 				var resolver = null;
 				if (hrt.animgraph.AnimGraph.customEditorResolverProvider != 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 (resolvers != null) {
 						if (previewState.providerIndex > resolvers.length) {
 						if (previewState.providerIndex > resolvers.length) {
 							previewState.providerIndex = 0;
 							previewState.providerIndex = 0;
@@ -380,7 +382,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 
 				var resolver = null;
 				var resolver = null;
 				if (hrt.animgraph.AnimGraph.customEditorResolverProvider != 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 (resolvers != null) {
 						resolver = resolvers[previewState.providerIndex]?.resolver;
 						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">
 		<div class="group" name="BlendSpace">
 			<dl>
 			<dl>
 				<dt>Min/MaxX</dt><dd><input type="number" field="minX"/><input type="number" field="maxX"/></dd>
 				<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>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>
 			</dl>
 		</div>
 		</div>
 		'), blendSpace2D, (_) -> {
 		'), blendSpace2D, (_) -> {
@@ -467,7 +474,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			updatePreviewAxis();
 			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;
 			previewState.providerIndex = i;
 			savePreviewState();
 			savePreviewState();
 			refreshPreviewAnimation();
 			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() {
 	function createPoint() {
 
 
 	}
 	}
@@ -669,6 +690,23 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			final size = 10;
 			final size = 10;
 			graph.line(g, -size, -size, size, size).addClass("preview-axis");
 			graph.line(g, -size, -size, size, size).addClass("preview-axis");
 			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.
 		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)
 		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) {
 		if (resolver == null && customResolverProvider != null) {
 			resolver = customResolverProvider(this);
 			resolver = customResolverProvider(this);
 		}
 		}
-		return AnimGraphInstance.fromAnimGraph(this, previewNode, resolver);
+		return AnimGraphInstance.fromAnimGraph(this, previewNode, modelCache, resolver);
 	}
 	}
 
 
 	override function save() {
 	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 defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 
 
 	var resolver : AnimResolver = null;
 	var resolver : AnimResolver = null;
+	var modelCache: h3d.prim.ModelCache;
+
+	var tmpMatrix : h3d.Matrix = new h3d.Matrix();
 
 
 	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;
 	var editorSkipClone : Bool = false;
 	#end
 	#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);
 		outputNode ??= cast animGraph.nodes.find((node) -> Std.downcast(node, hrt.animgraph.nodes.Output) != null);
 		if (outputNode == null)
 		if (outputNode == null)
 			throw "Animgraph has no output node";
 			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;
 		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
 		// Todo : Define a true length for the animation OR make so animations can have an undefined length
 		super(name, framesCount, sampling);
 		super(name, framesCount, sampling);
 		this.rootNode = rootNode;
 		this.rootNode = rootNode;
 		this.resolver = resolver ?? defaultResolver;
 		this.resolver = resolver ?? defaultResolver;
+		this.modelCache = modelCache ?? new h3d.prim.ModelCache();
 		defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 		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 {
 	override function clone(?target: h3d.anim.Animation) : h3d.anim.Animation {
 		#if editor
 		#if editor
 		if (editorSkipClone) {
 		if (editorSkipClone) {
@@ -128,9 +143,24 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		map(rootNode, updateNodeInputs);
 		map(rootNode, updateNodeInputs);
 
 
 		boneMap = rootNode.getBones(ctx);
 		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;
 		return boneMap;
 	}
 	}
 
 
+	function onEventHandler(name: String) {
+		if (onEvent != null) {
+			onEvent(name);
+		}
+	}
+
 	override function bind(base:h3d.scene.Object) {
 	override function bind(base:h3d.scene.Object) {
 		objects = [];
 		objects = [];
 		target = base;
 		target = base;
@@ -138,6 +168,7 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		var ctx = new hrt.animgraph.nodes.AnimNode.GetBoneContext();
 		var ctx = new hrt.animgraph.nodes.AnimNode.GetBoneContext();
 		ctx.targetObject = base;
 		ctx.targetObject = base;
 		ctx.resolver = resolver.bind(this, base);
 		ctx.resolver = resolver.bind(this, base);
+		ctx.modelCache = this.modelCache;
 
 
 		var bones = getBones(ctx);
 		var bones = getBones(ctx);
 		if (bones != null) {
 		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 minX = 0.0;
 	@:s var maxX = 1.0;
 	@:s var maxX = 1.0;
+	@:s var smoothX = 0.0;
+
 	@:s var minY = 0.0;
 	@:s var minY = 0.0;
 	@:s var maxY = 1.0;
 	@:s var maxY = 1.0;
+	@:s var smoothY = 0.0;
+
 
 
 	var instance : BlendSpace2DInstance;
 	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
 	// Serialization api
 
 

+ 2 - 2
hrt/animgraph/Resource.hx

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

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

@@ -19,12 +19,20 @@ typedef AnimInfo = {
 @:access(hrt.animgraph.BlendSpace2D)
 @:access(hrt.animgraph.BlendSpace2D)
 class BlendSpace2D extends AnimNode {
 class BlendSpace2D extends AnimNode {
 	@:input var bsX(default, set): Float = 0.5;
 	@: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;
 	@:input var bsY(default, set): Float = 0.5;
+	var realY : Float = 0.5;
+	var vY : Float = 0.0;
+
 
 
 	@:s var path : String = "";
 	@:s var path : String = "";
 
 
 	var dirtyPos: Bool = true;
 	var dirtyPos: Bool = true;
 
 
+	var prevAnimEventBind : h3d.anim.Animation;
+
 	function set_bsX(v: Float) : Float {
 	function set_bsX(v: Float) : Float {
 		if (v != bsX)
 		if (v != bsX)
 			currentTriangle = -1;
 			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 workQuats : Array<h3d.Quat> = [new h3d.Quat(), new h3d.Quat(), new h3d.Quat()];
 	var refQuat = 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> {
 	override function getBones(ctx: hrt.animgraph.nodes.AnimNode.GetBoneContext):Map<String, Int> {
 		var boneMap : Map<String, Int> = [];
 		var boneMap : Map<String, Int> = [];
 		animInfos = [];
 		animInfos = [];
@@ -57,6 +94,9 @@ class BlendSpace2D extends AnimNode {
 		triangles = [];
 		triangles = [];
 		currentTriangle = -1;
 		currentTriangle = -1;
 
 
+		realX = bsX;
+		realY = bsY;
+
 		var curOurBoneId = 0;
 		var curOurBoneId = 0;
 
 
 		if (blendSpace == null) {
 		if (blendSpace == null) {
@@ -79,8 +119,8 @@ class BlendSpace2D extends AnimNode {
 						function makeAnim() : Int {
 						function makeAnim() : Int {
 							// Create a new animation
 							// Create a new animation
 							var index = animInfos.length;
 							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 proxy = new hrt.animgraph.nodes.Input.AnimProxy(null);
 							var animInstance = animBase.createInstance(proxy);
 							var animInstance = animBase.createInstance(proxy);
 
 
@@ -137,6 +177,26 @@ class BlendSpace2D extends AnimNode {
 	override function tick(dt:Float) {
 	override function tick(dt:Float) {
 		super.tick(dt);
 		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) {
 		for (animInfo in animInfos) {
 			// keep all the animations in sync
 			// keep all the animations in sync
 			var scale = animInfo.selfSpeed;
 			var scale = animInfo.selfSpeed;
@@ -159,7 +219,7 @@ class BlendSpace2D extends AnimNode {
 			return;
 			return;
 
 
 		if (currentTriangle == -1) {
 		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
 			// find the triangle our curPos resides in
 			var collided = false;
 			var collided = false;
@@ -213,6 +273,22 @@ class BlendSpace2D extends AnimNode {
 			if (currentTriangle == -1)
 			if (currentTriangle == -1)
 				throw "assert";
 				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;
 			currentAnimLenght = 0.0;
 
 
 			// Compensate for null animations that don't have length
 			// Compensate for null animations that don't have length
@@ -290,6 +366,14 @@ class BlendSpace2D extends AnimNode {
 		outMatrix._23 = workQuat.w;
 		outMatrix._23 = workQuat.w;
 	}
 	}
 
 
+	function animEventHander(name: String) {
+		onEvent(name);
+	}
+
+	function setupAnimEvents() {
+		// handled by the triangle setup
+	}
+
 	#if editor
 	#if editor
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 		var elts = super.getPropertiesHTML(width);
 		var elts = super.getPropertiesHTML(width);

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

@@ -27,6 +27,10 @@ class DefaultPose extends AnimNode {
 		return bones;
 		return bones;
 	}
 	}
 
 
+	function setupAnimEvents() {
+		// no events to handle
+	}
+
 	override function getBoneTransform(boneId:Int, outMatrix:h3d.Matrix, ctx:hrt.animgraph.nodes.AnimNode.GetBoneTransformContext) {
 	override function getBoneTransform(boneId:Int, outMatrix:h3d.Matrix, ctx:hrt.animgraph.nodes.AnimNode.GetBoneTransformContext) {
 		var bone = objects[boneId];
 		var bone = objects[boneId];
 		if (bone.matDecomposed == null) {
 		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);
 		matrix.load(anim.getObjects()[id].targetObject.defaultTransform);
 	}
 	}
 
 
+	function setupAnimEvents() {
+		if (anim != null) {
+			anim.onEvent = onEvent;
+		}
+	}
+
 	#if editor
 	#if editor
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 	override function getPropertiesHTML(width:Float):Array<hide.Element> {
 		var elts = super.getPropertiesHTML(width);
 		var elts = super.getPropertiesHTML(width);

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

@@ -11,6 +11,10 @@ class Output extends AnimNode {
 		// update out using inputs
 		// update out using inputs
 	}
 	}
 
 
+	function setupAnimEvents() {
+		a.onEvent = onEvent;
+	}
+
 	#if editor
 	#if editor
 	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
 	override function getInfo():hide.view.GraphInterface.GraphNodeInfo {
 		var info = super.getInfo();
 		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;
 		var l = 0.0;
 		for( i in 0 ... samples.length - 1 ) {
 		for( i in 0 ... samples.length - 1 ) {
 			samples[i].t = l/length;
 			samples[i].t = l/length;
-			samples[i].length = length;
+			samples[i].length = l;
 			l += samples[i].pos.distance(samples[i+1].pos);
 			l += samples[i].pos.distance(samples[i+1].pos);
 		}
 		}
 		samples[samples.length - 1].t = 1;
 		samples[samples.length - 1].t = 1;