浏览代码

[animgraph] Improved layout of blendspace parameters

- new anim picker
Clément Espeute 8 月之前
父节点
当前提交
ecaf05df51

+ 54 - 0
bin/style.css

@@ -4176,6 +4176,12 @@ button-2 {
 button-2:hover {
 button-2:hover {
   background-color: var(--hover);
   background-color: var(--hover);
 }
 }
+button-2 value {
+  flex-grow: 1;
+  overflow-x: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
 button-2:active {
 button-2:active {
   box-shadow: inset var(--sublte-shadow);
   box-shadow: inset var(--sublte-shadow);
 }
 }
@@ -4451,6 +4457,54 @@ blend-space-2d-root properties-container {
 blend-space-2d-root properties-container parameters-container {
 blend-space-2d-root properties-container parameters-container {
   flex-grow: 1;
   flex-grow: 1;
 }
 }
+blend-space-2d-root properties-container .hide-properties {
+  padding: 1em;
+  display: flex;
+  flex-direction: column;
+  gap: 0.5em;
+}
+blend-space-2d-root properties-container .hide-properties input {
+  min-width: 0;
+  width: 0;
+}
+blend-space-2d-root properties-container .hide-properties dl {
+  display: grid;
+  grid-template-columns: 6em minmax(0, 1fr);
+  row-gap: 2px;
+  column-gap: 1em;
+  width: 100%;
+}
+blend-space-2d-root properties-container .hide-properties dl > div {
+  display: grid;
+  grid-column: 1 / -1;
+  grid-template-columns: subgrid;
+}
+blend-space-2d-root properties-container .hide-properties dl > div > dt {
+  width: unset;
+  text-align: right;
+}
+blend-space-2d-root properties-container .hide-properties dl > div > dd {
+  width: unset;
+  margin-left: 0;
+  display: flex;
+  justify-content: stretch;
+}
+blend-space-2d-root properties-container .hide-properties dl > div > dd > * {
+  flex: 1 1;
+}
+blend-space-2d-root properties-container .hide-properties dl > div .hide-range {
+  display: flex;
+}
+blend-space-2d-root properties-container .hide-properties dl > div .hide-range input[type="range"] {
+  min-width: 0;
+  width: 0;
+  flex: 1 1;
+}
+blend-space-2d-root properties-container .hide-properties dl > div .hide-range input[type="text"] {
+  min-width: 0;
+  width: 0;
+  flex: 0 0 4em;
+}
 .basic-border {
 .basic-border {
   padding: var(--basic-padding);
   padding: var(--basic-padding);
   border: var(--basic-border);
   border: var(--basic-border);

+ 66 - 0
bin/style.less

@@ -4934,6 +4934,13 @@ button-2 {
 		background-color: var(--hover);
 		background-color: var(--hover);
 	}
 	}
 
 
+	value {
+		flex-grow: 1;
+		overflow-x: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+	}
+
 	&:active {
 	&:active {
 		box-shadow: inset var(--sublte-shadow);
 		box-shadow: inset var(--sublte-shadow);
 	}
 	}
@@ -5277,6 +5284,65 @@ blend-space-2d-root {
 		parameters-container {
 		parameters-container {
 			flex-grow: 1;
 			flex-grow: 1;
 		}
 		}
+
+		.hide-properties {
+			padding: 1em;
+			display: flex;
+			flex-direction: column;
+
+			gap: 0.5em;
+
+			input {
+				min-width: 0;
+				width: 0;
+			}
+
+			dl {
+				display: grid;
+				grid-template-columns: 6em minmax(0, 1fr);
+				row-gap: 2px;
+				column-gap: 1em;
+				width: 100%;
+
+				> div {
+					display: grid;
+					grid-column: 1 / -1;
+					grid-template-columns: subgrid;
+					> dt {
+						width: unset;
+						text-align: right;
+					}
+
+					> dd {
+						width: unset;
+						margin-left: 0;
+
+						display: flex;
+						justify-content: stretch;
+
+						> * {
+							flex: 1 1;
+						}
+					}
+
+					.hide-range {
+						display: flex;
+						input[type="range"] {
+							min-width: 0;
+							width: 0;
+							flex: 1 1;
+						}
+
+						input[type="text"] {
+							min-width: 0;
+							width: 0;
+							flex: 0 0 4em;
+						}
+					}
+				}
+			}
+		}
+
 	}
 	}
 
 
 }
 }

+ 28 - 4
hide/view/animgraph/BlendSpace2DEditor.hx

@@ -277,7 +277,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 
 		propertiesContainer = new hide.Element("<properties-container></properties-container>").appendTo(root);
 		propertiesContainer = new hide.Element("<properties-container></properties-container>").appendTo(root);
 		{
 		{
-			var paramContainer = new Element('<parameters-container></parameters-container>').appendTo(propertiesContainer).addClass("hide-properties");
+			var paramContainer = new Element('<parameters-container></parameters-container>').appendTo(propertiesContainer);
 			new Element("<h1>Parameters</h1>").appendTo(paramContainer);
 			new Element("<h1>Parameters</h1>").appendTo(paramContainer);
 			propsEditor = new hide.comp.PropsEditor(undo, paramContainer);
 			propsEditor = new hide.comp.PropsEditor(undo, paramContainer);
 			refreshPropertiesPannel();
 			refreshPropertiesPannel();
@@ -346,6 +346,7 @@ class BlendSpace2DEditor extends hide.view.FileView {
 	function refreshPropertiesPannel() {
 	function refreshPropertiesPannel() {
 		propsEditor.clear();
 		propsEditor.clear();
 
 
+
 		propsEditor.add(new hide.Element('
 		propsEditor.add(new hide.Element('
 		<div class="group" name="BlendSpace">
 		<div class="group" name="BlendSpace">
 			<dl>
 			<dl>
@@ -358,20 +359,43 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		});
 		});
 
 
 		if (selectedPoint != -1) {
 		if (selectedPoint != -1) {
-			propsEditor.add(new hide.Element('
+			var editor = new hide.Element('
 				<div class="group" name="Point">
 				<div class="group" name="Point">
 					<dl>
 					<dl>
 						<dt>X</dt><dd><input type="range" min="0.0" max="1.0" field="x"/></dd>
 						<dt>X</dt><dd><input type="range" min="0.0" max="1.0" field="x"/></dd>
 						<dt>Y</dt><dd><input type="range" min="0.0" max="1.0" field="y"/></dd>
 						<dt>Y</dt><dd><input type="range" min="0.0" max="1.0" field="y"/></dd>
 						<dt>Anim speed</dt><dd><input type="range" min="0.1" max="2.0" field="speed"/></dd>
 						<dt>Anim speed</dt><dd><input type="range" min="0.1" max="2.0" field="speed"/></dd>
-						<dt>Anim</dt><dd><input type="fileselect" extensions="fbx" field="animPath"/></dd>
 					</dl>
 					</dl>
 				</div>
 				</div>
-			'), blendSpace2D.points[selectedPoint], (_) -> {
+			');
+
+			propsEditor.add(editor, blendSpace2D.points[selectedPoint], (_) -> {
 				blendSpace2D.triangulate();
 				blendSpace2D.triangulate();
 				refreshGraph();
 				refreshGraph();
 				refreshPreviewAnimation();
 				refreshPreviewAnimation();
 			});
 			});
+
+			var div = new Element("<div></div>").appendTo(editor.find("dl"));
+			new Element("<dt>Anim</dt>").appendTo(div);
+			var dd = new Element("<dd>").appendTo(div);
+			var button = new hide.comp.Button(dd, null, "", {hasDropdown: true});
+			button.label = blendSpace2D.points[selectedPoint].animPath;
+			button.onClick = () -> {
+				hide.comp.ContextMenu.createDropdown(button.element.get(0), [
+					{
+						label: "Choose File ...",
+						click: () -> {
+						ide.chooseFile(["fbx"], (path) -> {
+								var old = blendSpace2D.points[selectedPoint].animPath;
+								blendSpace2D.points[selectedPoint].animPath = path;
+								undo.change(Field(blendSpace2D.points[selectedPoint], "animPath", old), () -> {
+									button.label = blendSpace2D.points[selectedPoint].animPath;
+								});
+							}, true);
+						}
+					}
+				], {search: Visible, autoWidth: true});
+			};
 		}
 		}
 
 
 		propsEditor.add(new hide.Element('
 		propsEditor.add(new hide.Element('

+ 3 - 2
hrt/animgraph/AnimGraph.hx

@@ -16,6 +16,7 @@ typedef SerializedEdge = {
 	outputId: Int,
 	outputId: Int,
 };
 };
 
 
+
 @:access(hrt.animgraph.AnimGraphInstance)
 @:access(hrt.animgraph.AnimGraphInstance)
 class AnimGraph extends hrt.prefab.Prefab {
 class AnimGraph extends hrt.prefab.Prefab {
 
 
@@ -34,8 +35,8 @@ 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) : AnimGraphInstance {
-		return AnimGraphInstance.fromAnimGraph(this, previewNode);
+	public function getAnimation(previewNode: hrt.animgraph.nodes.AnimNode = null, resolver: hrt.animgraph.AnimGraphInstance.AnimResolver = null) : AnimGraphInstance {
+		return AnimGraphInstance.fromAnimGraph(this, previewNode, resolver);
 	}
 	}
 
 
 	override function save() {
 	override function save() {

+ 19 - 6
hrt/animgraph/AnimGraphInstance.hx

@@ -10,6 +10,8 @@ class AnimGraphAnimatedObject extends h3d.anim.Animation.AnimatedObject {
 	}
 	}
 }
 }
 
 
+typedef AnimResolver = (instance: AnimGraphInstance, target: h3d.scene.Object, path: String ) -> Null<String>;
+
 @:access(hrt.animgraph.AnimGraph)
 @:access(hrt.animgraph.AnimGraph)
 @:access(hrt.animgraph.Node)
 @:access(hrt.animgraph.Node)
 class AnimGraphInstance extends h3d.anim.Animation {
 class AnimGraphInstance extends h3d.anim.Animation {
@@ -24,28 +26,33 @@ class AnimGraphInstance extends h3d.anim.Animation {
 	var syncCtx = new hrt.animgraph.nodes.AnimNode.GetBoneTransformContext();
 	var syncCtx = new hrt.animgraph.nodes.AnimNode.GetBoneTransformContext();
 	var defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 	var defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 
 
+	var resolver : AnimResolver = null;
+
 	#if editor
 	#if editor
 	var editorSkipClone : Bool = false;
 	var editorSkipClone : Bool = false;
 	#end
 	#end
 
 
-	static function fromAnimGraph(animGraph:AnimGraph, outputNode: hrt.animgraph.nodes.AnimNode = null) : AnimGraphInstance {
+	static function fromAnimGraph(animGraph:AnimGraph, outputNode: hrt.animgraph.nodes.AnimNode = 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, animGraph.name, 1000, 1/60.0);
-
+		var inst = new AnimGraphInstance(outputNode, resolver, animGraph.name, 1000, 1/60.0);
 		return inst;
 		return inst;
 	}
 	}
 
 
-	public function new(rootNode: hrt.animgraph.nodes.AnimNode, name: String, framesCount: Int, sampling: Float) {
+	public function new(rootNode: hrt.animgraph.nodes.AnimNode, 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;
 		defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 		defaultPoseNode = new hrt.animgraph.nodes.DefaultPose();
 	}
 	}
 
 
+	static function defaultResolver(instance: AnimGraphInstance, target: h3d.scene.Object, path: String) : Null<String> {
+		return path;
+	}
+
 	public function setParam(name: String, value: Float) {
 	public function setParam(name: String, value: Float) {
 		var param = parameterMap.get(name);
 		var param = parameterMap.get(name);
 		if (param != null) {
 		if (param != null) {
@@ -78,7 +85,7 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		#end
 		#end
 		if (target != null) throw "Unexpected";
 		if (target != null) throw "Unexpected";
 
 
-		var inst = new AnimGraphInstance(null, name, frameCount, sampling);
+		var inst = new AnimGraphInstance(null, resolver, name, frameCount, sampling);
 		inst.rootNode = cast cloneRec(rootNode, inst);
 		inst.rootNode = cast cloneRec(rootNode, inst);
 		super.clone(inst);
 		super.clone(inst);
 		return inst;
 		return inst;
@@ -127,6 +134,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);
 
 
 		var bones = getBones(ctx);
 		var bones = getBones(ctx);
 		if (bones != null) {
 		if (bones != null) {
@@ -223,4 +231,9 @@ class AnimGraphInstance extends h3d.anim.Animation {
 		node.tick(dt);
 		node.tick(dt);
 		node.tickedThisFrame = true;
 		node.tickedThisFrame = true;
 	}
 	}
+
+	/**
+		Set this function in your project to programaticaly return an animation path
+	**/
+	static var customAnimResolver : (instance: AnimGraphInstance, name: String) -> Null<String> = null;
 }
 }

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

@@ -9,6 +9,7 @@ class GetBoneContext {
 	}
 	}
 
 
 	public var targetObject:h3d.scene.Object;
 	public var targetObject:h3d.scene.Object;
+	public var resolver : (path: String) -> Null<String>;
 }
 }
 
 
 class GetBoneTransformContext {
 class GetBoneTransformContext {

+ 19 - 15
hrt/animgraph/nodes/BlendSpace2D.hx

@@ -71,26 +71,30 @@ class BlendSpace2D extends AnimNode {
 			if (blendSpacePoint.animPath != null && blendSpacePoint.animPath.length > 0) {
 			if (blendSpacePoint.animPath != null && blendSpacePoint.animPath.length > 0) {
 				try
 				try
 				{
 				{
-					var animIndex = animMap.getOrPut(blendSpacePoint.animPath, {
-						// Create a new animation
-						var index = animInfos.length;
-						var animBase = hxd.res.Loader.currentInstance.load(blendSpacePoint.animPath).toModel().toHmd().loadAnimation();
+					var path = ctx.resolver(blendSpacePoint.animPath);
+					if (path != null) {
+						var animIndex = animMap.getOrPut(path, {
+							// Create a new animation
+							var index = animInfos.length;
+							var animBase = hxd.res.Loader.currentInstance.load(path).toModel().toHmd().loadAnimation();
 
 
-						var proxy = new hrt.animgraph.nodes.Input.AnimProxy(null);
-						var animInstance = animBase.createInstance(proxy);
+							var proxy = new hrt.animgraph.nodes.Input.AnimProxy(null);
+							var animInstance = animBase.createInstance(proxy);
 
 
-						var indexRemap : Array<Null<Int>> = [];
+							var indexRemap : Array<Null<Int>> = [];
 
 
-						for (boneId => obj in animInstance.getObjects()) {
-							var ourId = boneMap.getOrPut(obj.objectName, curOurBoneId++);
-							indexRemap[ourId] = boneId;
-						}
+							for (boneId => obj in animInstance.getObjects()) {
+								var ourId = boneMap.getOrPut(obj.objectName, curOurBoneId++);
+								indexRemap[ourId] = boneId;
+							}
+
+							animInfos.push({anim: animInstance, proxy: proxy, indexRemap: indexRemap});
+							index;
+						});
 
 
-						animInfos.push({anim: animInstance, proxy: proxy, indexRemap: indexRemap});
-						index;
-					});
+						point.animInfo = animInfos[animIndex];
+					}
 
 
-					point.animInfo = animInfos[animIndex];
 				} catch (e) {
 				} catch (e) {
 					trace('Couldn\'t load anim ${blendSpacePoint.animPath} : ${e.toString()}');
 					trace('Couldn\'t load anim ${blendSpacePoint.animPath} : ${e.toString()}');
 				}
 				}