浏览代码

[animgraph] Refactored previews into ScenePreview, fixed many issues in the BlendSpaceEditor

Clément Espeute 8 月之前
父节点
当前提交
f70e161aa7

+ 4 - 0
hide/comp/Scene.hx

@@ -153,6 +153,7 @@ class Scene extends hide.comp.Component implements h3d.IDrawable {
 			sevents.addScene(s2d);
 			sevents.addScene(s3d);
 			@:privateAccess window.checkResize();
+			preOnReady();
 			onReady();
 			onResize();
 			sync();
@@ -181,6 +182,9 @@ class Scene extends hide.comp.Component implements h3d.IDrawable {
 		errorThisFrame = true;
 	}
 
+	function preOnReady() {
+	}
+
 	function clearErrorMessage() {
 		errorMessageBox.html("");
 		errorMessageBox.css("visibility", "collapse");

+ 125 - 0
hide/comp/ScenePreview.hx

@@ -0,0 +1,125 @@
+package hide.comp;
+
+class ScenePreviewSettings {
+    public var modelPath: String = null;
+
+    public function new() {};
+}
+
+/**
+	A Scene that is specialised to load one prefab or mesh for preview.
+	Stores
+**/
+class ScenePreview extends Scene {
+	var cameraController : hide.comp.Scene.PreviewCamController;
+	var previewSettings : ScenePreviewSettings;
+
+	public var prefab(default, null) : hrt.prefab.Prefab; // The root prefab of the preview.
+
+	public function new(config, parent, el, save: String) {
+		this.saveDisplayKey = save;
+		super(config, parent, el);
+	}
+
+	/**
+		Called whenever prefab is loaded. Prefab can be null if the loading failed
+	**/
+	public dynamic function onObjectLoaded() {
+
+	}
+
+	override function preOnReady() {
+		super.preOnReady();
+
+		loadSettings();
+
+		cameraController = new hide.comp.Scene.PreviewCamController(s3d);
+
+		reloadObject();
+	}
+
+	function saveSettings() {
+		saveDisplayState("previewSettings", haxe.Json.stringify(previewSettings));
+	}
+
+	function loadSettings() {
+		var save = haxe.Json.parse(getDisplayState("previewSettings") ?? "{}");
+		previewSettings = new ScenePreviewSettings();
+		for (f in Reflect.fields(previewSettings)) {
+			var v = Reflect.field(save, f);
+			if (v != null) {
+				Reflect.setField(previewSettings, f, v);
+			}
+		}
+	}
+
+	public function resetPreviewCamera() {
+		if (prefab == null)
+			return;
+
+		var bounds = prefab.findFirstLocal3d().getBounds();
+		var sp = bounds.toSphere();
+		cameraController.set(sp.r * 3.0, Math.PI / 4, Math.PI * 5 / 13, sp.getCenter());
+	}
+
+	/**
+		Set the preview object path and reload the scene.
+		If path is a .prefab, `this.prefab` will be the loaded prefab
+		If path is a .fbx, `this.prefab` will be a hrt.prefab.Model with it's source = path
+	**/
+	public function setObjectPath(path: String) {
+		previewSettings.modelPath = path;
+		reloadObject();
+	}
+
+	public function getObjectPath() : String {
+		return previewSettings.modelPath;
+	}
+
+	public function reloadObject() {
+		if (prefab != null) {
+			prefab.dispose();
+			prefab.shared?.root3d.remove();
+			prefab.shared?.root2d.remove();
+			prefab = null;
+		}
+
+		if (previewSettings.modelPath == null || !hxd.res.Loader.currentInstance.exists(previewSettings.modelPath)) {
+			previewSettings.modelPath = null;
+			saveSettings();
+			onObjectLoaded();
+			return;
+		}
+
+		try {
+			if (StringTools.endsWith(previewSettings.modelPath, ".prefab")) {
+				try {
+					prefab = Ide.inst.loadPrefab(previewSettings.modelPath);
+				} catch (e) {
+					throw 'Could not load mesh ${previewSettings.modelPath}, error : $e';
+				}
+			} else if (StringTools.endsWith(previewSettings.modelPath, ".fbx")) {
+				var model = new hrt.prefab.Model(null, null);
+				model.source = previewSettings.modelPath;
+				prefab = model;
+			}
+			else {
+				throw "Unsupported model format";
+			}
+
+			var previewObject = new h3d.scene.Object(s3d);
+			var ctx = new hide.prefab.ContextShared(null, previewObject);
+			ctx.scene = this;
+			prefab = prefab.make(ctx);
+
+		} catch (e) {
+			previewSettings.modelPath = null;
+			ide.quickError("Couldn't load preview : " + e);
+			saveSettings();
+			reloadObject(); // cleanup
+			return;
+		}
+
+		onObjectLoaded();
+	}
+}

+ 1 - 0
hide/comp/cdb/Formulas.hx

@@ -70,6 +70,7 @@ class Formulas {
 	}
 
 	function reloadFile() {
+		throw "Non";
 		load();
 		evaluateAll();
 		editor.save();

+ 36 - 7
hide/view/animgraph/AnimGraphEditor.hx

@@ -16,6 +16,7 @@ class AnimGraphEditor extends GenericGraphEditor {
 
     var animGraph : hrt.animgraph.AnimGraph;
     public var previewModel : h3d.scene.Object;
+    public var previewPrefab : hrt.prefab.Prefab;
 
     var parametersList : hide.Element;
     var previewAnimation : AnimGraphInstance = null;
@@ -87,13 +88,17 @@ class AnimGraphEditor extends GenericGraphEditor {
 			graphEditor.opBox(inst, true, graphEditor.currentUndoBuffer);
 			graphEditor.commitUndo();
         });
+
+        if (previewSettings.modelPath == null) {
+            previewSettings.modelPath = gatherAllPreviewModels(animGraph.animFolder)[0];
+        }
     }
 
-    function gatherAllPreviewModels() : Array<String> {
+    static public function gatherAllPreviewModels(basePath : String) : Array<String> {
         var paths = [];
 
         function rec(dirPath: String) {
-            var files = sys.FileSystem.readDirectory(ide.getPath(dirPath));
+            var files = sys.FileSystem.readDirectory(hide.Ide.inst.getPath(dirPath));
             for (path in files) {
                 if (sys.FileSystem.isDirectory(path)) {
                     rec(dirPath + "/" + path);
@@ -111,7 +116,7 @@ class AnimGraphEditor extends GenericGraphEditor {
             }
         }
 
-        rec(animGraph.animFolder);
+        rec(basePath);
         return paths;
     }
 
@@ -119,7 +124,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         var options = super.getPreviewOptionsMenu();
 
         var models : Array<hide.comp.ContextMenu.MenuItem> = [];
-        var paths = gatherAllPreviewModels();
+        var paths = gatherAllPreviewModels(animGraph.animFolder);
         for (path in paths) {
             var basePath = StringTools.replace(path, animGraph.animFolder + "/", "");
             models.push({label: basePath, click: () -> {
@@ -355,6 +360,7 @@ class AnimGraphEditor extends GenericGraphEditor {
         super.onScenePreviewReady();
 
         reloadPreviewModel();
+        resetPreviewCamera();
     }
 
     function reloadPreviewModel() {
@@ -363,16 +369,35 @@ class AnimGraphEditor extends GenericGraphEditor {
             previewModel = null;
         }
 
+        if (previewPrefab != null) {
+            previewPrefab.dispose();
+            previewPrefab.shared.root3d?.remove();
+            previewPrefab.shared.root2d?.remove();
+            previewPrefab = null;
+        }
+
         if (previewSettings.modelPath == null)
             return;
+
         try {
             if (StringTools.endsWith(previewSettings.modelPath, ".prefab")) {
-                throw "todo prefab loading";
+                try {
+                    previewPrefab = Ide.inst.loadPrefab(previewSettings.modelPath);
+                } catch (e) {
+                    throw 'Could not load mesh ${previewSettings.modelPath}, error : $e';
+                }
+                var ctx = new hide.prefab.ContextShared(null, new h3d.scene.Object(scenePreview.s3d));
+                ctx.scene = scenePreview;
+                previewPrefab.setSharedRec(ctx);
+                previewPrefab = previewPrefab.make();
+
+                previewModel = previewPrefab.find(hrt.prefab.Model, (m) -> StringTools.startsWith(m.source, animGraph.animFolder))?.local3d;
+                if (previewModel == null) {
+                    throw "Linked prefab doesn't contain any suitable model";
+                }
             } else if (StringTools.endsWith(previewSettings.modelPath, ".fbx")) {
                 previewModel =  scenePreview.loadModel(previewSettings.modelPath);
                 scenePreview.s3d.addChild(previewModel);
-                setPreview(cast animGraph.nodes.find((f) -> Std.downcast(f, hrt.animgraph.nodes.Output) != null));
-                resetPreviewCamera();
             }
             else {
                 throw "Unsupported model format";
@@ -381,7 +406,11 @@ class AnimGraphEditor extends GenericGraphEditor {
             previewSettings.modelPath = null;
             ide.quickError("Couldn't load preview : " + e);
             savePreviewSettings();
+            reloadPreviewModel(); // cleanup
+            return;
         }
+
+        setPreview(cast animGraph.nodes.find((f) -> Std.downcast(f, hrt.animgraph.nodes.Output) != null));
     }
 
     override function getNodes() : Iterator<IGraphNode> {

+ 88 - 37
hide/view/animgraph/BlendSpace2DEditor.hx

@@ -1,5 +1,11 @@
 package hide.view.animgraph;
 
+class BlendSpacePreviewSettings {
+    public var modelPath: String = null;
+
+    public function new() {};
+}
+
 @:access(hrt.animgraph.BlendSpace2D)
 class BlendSpace2DEditor extends hide.view.FileView {
 	var root : hide.Element;
@@ -8,10 +14,9 @@ class BlendSpace2DEditor extends hide.view.FileView {
 	var propertiesContainer : hide.Element;
 	var mainPanel : hide.Element;
 
-	var scenePreview : hide.comp.Scene;
+	var scenePreview : hide.comp.ScenePreview;
 	var scenePreviewReady = false;
-	var previewModel : h3d.scene.Object;
-	var previewCamController : hide.comp.Scene.PreviewCamController;
+	var previewModel : h3d.scene.Object = null;
 	var propsEditor : hide.comp.PropsEditor;
 
 	var blendSpace2D: hrt.animgraph.BlendSpace2D;
@@ -26,6 +31,8 @@ class BlendSpace2DEditor extends hide.view.FileView {
 
 	var startMovePos : h2d.col.Point = null;
 
+	var previewSettings : BlendSpacePreviewSettings;
+
 	static final pointRadius = 8;
 	var subdivs = 5;
 
@@ -44,12 +51,30 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		return inline new h2d.col.Point(x, y);
 	}
 
-	override function onRebuild() {
+	override function onDisplay() {
 		blendSpace2D = Std.downcast(hide.Ide.inst.loadPrefab(state.path, null,  true), hrt.animgraph.BlendSpace2D);
 		if (blendSpace2D == null)
 			throw "Invalid blendSpace2D";
+		super.onDisplay();
+		if (blendSpace2D.animFolder == null) {
+            element.html("
+                <h1>Choose a folder containing the models to animate</h1>
+                <button-2></button-2>
+            ");
+
+            var button = new hide.comp.Button(null, element.find("button-2"), "Choose folder");
+            button.onClick = () -> {
+                ide.chooseDirectory((path) -> {
+                    if (path != null) {
+                        blendSpace2D.animFolder = path;
+                        save();
+                        onDisplay();
+                    }
+                });
+            }
+			return;
+		}
 
-		super.onRebuild();
 		element.html("");
 
 		root = new hide.Element("<blend-space-2d-root></blend-space-2d-root>").appendTo(element);
@@ -176,16 +201,57 @@ class BlendSpace2DEditor extends hide.view.FileView {
 					movedPoint = -1;
 				}
 
+				svg.oncontextmenu = (e:js.html.MouseEvent) -> {
+					e.preventDefault();
+
+					var options : Array<hide.comp.ContextMenu.MenuItem> = [];
+
+					if (hoverPoint > -1) {
+						var toDel = hoverPoint > -1 ? hoverPoint : selectedPoint;
+						options.push({
+							label: "Delete",
+							click: () -> {
+								deletePoint(toDel);
+							}
+						});
+					}
+					else {
+						selectedPoint = -1;
+						var x = e.clientX;
+						var y = e.clientY;
+						var ctrl = e.ctrlKey;
+
+						options.push({
+							label: "Add point",
+							click: () -> {
+								var pt = getPointPos(x, y, !ctrl);
+								addPoint({
+									x: pt.x,
+									y: pt.y,
+									animPath: "",
+								});
+							}
+						});
+
+					}
+
+					hide.comp.ContextMenu.createFromEvent(e, options);
+				}
+
+
 			}
 			panel.onResize = refreshGraph;
 
-			scenePreview = new hide.comp.Scene(config, previewContainer, null);
+			scenePreview = new hide.comp.ScenePreview(config, previewContainer, null, saveDisplayKey + "/preview");
 			scenePreviewReady = false;
-			previewModel = null;
 			scenePreview.element.addClass("scene-preview");
 
 			scenePreview.onReady = onScenePreviewReady;
 			scenePreview.onUpdate = onScenePreviewUpdate;
+			scenePreview.onObjectLoaded = () -> {
+				previewModel = scenePreview.prefab?.find(hrt.prefab.Model, (f) -> StringTools.startsWith(f.source, blendSpace2D.animFolder))?.local3d;
+				refreshPreviewAnimation();
+			}
 		}
 
 		propertiesContainer = new hide.Element("<properties-container></properties-container>").appendTo(root).addClass("hide-properties");
@@ -199,19 +265,9 @@ class BlendSpace2DEditor extends hide.view.FileView {
 		keys.register("delete", deleteSelection);
 	}
 
-	function reloadModel() {
-		if (!scenePreviewReady)
-			return;
-
-		if (previewModel != null) {
-			previewModel.remove();
-		}
-		if (blendSpace2D.refModel != null) {
-			previewModel = scenePreview.loadModel(blendSpace2D.refModel);
-			scenePreview.s3d.addChild(previewModel);
-		}
-
-		refreshPreviewAnimation();
+	override function getDefaultContent():haxe.io.Bytes {
+		var animgraph = (new hrt.animgraph.BlendSpace2D(null, null)).serialize();
+		return haxe.io.Bytes.ofString(ide.toJSON(animgraph));
 	}
 
 	function refreshPreviewAnimation() {
@@ -225,10 +281,14 @@ class BlendSpace2DEditor extends hide.view.FileView {
 			}
 			else @:privateAccess {
 				var root : hrt.animgraph.nodes.BlendSpace2D.BlendSpace2D = cast @:privateAccess animPreview.rootNode;
-				var old = root.points[0].animInfo.anim.frame;
+				var old = root.points[0]?.animInfo?.anim.frame;
 				animPreview.bind(previewModel);
-				for (point in root.points) {
-					point.animInfo.anim.setFrame(old);
+				if (old != null) {
+					for (point in root.points) {
+						if (point.animInfo != null) {
+							point.animInfo.anim.setFrame(old);
+						}
+					}
 				}
 			}
 		}
@@ -244,17 +304,6 @@ class BlendSpace2DEditor extends hide.view.FileView {
 	function refreshPropertiesPannel() {
 		propsEditor.clear();
 
-		propsEditor.add(new hide.Element('
-			<div class="group" name="Blend Space">
-				<dl>
-					<dt>Preview</dt><dd><input type="fileselect" extensions="fbx" field="refModel"/></dd>
-				</dl>
-			</div>
-		'), blendSpace2D, (_) -> {
-			reloadModel();
-		});
-
-
 		if (selectedPoint != -1) {
 			propsEditor.add(new hide.Element('
 				<div class="group" name="Point">
@@ -310,10 +359,12 @@ class BlendSpace2DEditor extends hide.view.FileView {
 	}
 
     function onScenePreviewReady() {
-        previewCamController = new hide.comp.Scene.PreviewCamController(scenePreview.s3d);
-
 		scenePreviewReady = true;
-		reloadModel();
+
+		if (scenePreview.getObjectPath() == null) {
+			var first = AnimGraphEditor.gatherAllPreviewModels(blendSpace2D.animFolder)[0];
+			scenePreview.setObjectPath(first);
+		}
     }
 
 	function deletePoint(index: Int) {

+ 4 - 1
hrt/animgraph/BlendSpace2D.hx

@@ -9,7 +9,7 @@ typedef BlendSpacePoint = {
 class BlendSpace2D extends hrt.prefab.Prefab {
 	@:s var points : Array<BlendSpacePoint> = [];
 	@:s var triangles : Array<Array<Int>> = [];
-	@:s var refModel : String = null;
+	@:s var animFolder : String = null; // The folder to use as a base for the animation selection/loading
 
 	var instance : BlendSpace2DInstance;
 
@@ -26,6 +26,9 @@ class BlendSpace2D extends hrt.prefab.Prefab {
 		}
 
 		var triangulation = h2d.col.Delaunay.triangulate(h2dPoints);
+		if (triangulation == null)
+			return;
+
 		for (triangle in triangulation) {
 			triangles.push([h2dPoints.indexOf(triangle.p1), h2dPoints.indexOf(triangle.p2), h2dPoints.indexOf(triangle.p3)]);
 		}

+ 49 - 38
hrt/animgraph/nodes/BlendSpace2D.hx

@@ -4,7 +4,7 @@ using hrt.tools.MapUtils;
 typedef BlendSpaceInstancePoint = {
 	x: Float,
 	y: Float,
-	?animInfo: AnimInfo,
+	?animInfo: AnimInfo, // can be null if no animation could be loaded
 }
 
 typedef AnimInfo = {
@@ -66,31 +66,34 @@ class BlendSpace2D extends AnimNode {
 
 		for (blendSpacePoint in blendSpace.points) {
 			var point : BlendSpaceInstancePoint = {x: blendSpacePoint.x, y: blendSpacePoint.y};
-			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 proxy = new hrt.animgraph.nodes.Input.AnimProxy(null);
-					var animInstance = animBase.createInstance(proxy);
-
-					var indexRemap = [];
-
-					for (boneId => obj in animInstance.getObjects()) {
-						var ourId = boneMap.getOrPut(obj.objectName, curOurBoneId++);
-						indexRemap[ourId] = boneId;
-					}
+			if (blendSpacePoint.animPath != null && blendSpacePoint.animPath.length > 0) {
+				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 proxy = new hrt.animgraph.nodes.Input.AnimProxy(null);
+						var animInstance = animBase.createInstance(proxy);
+
+						var indexRemap = [];
+
+						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];
-			} catch (e) {
-				trace('Couldn\'t load anim ${blendSpacePoint.animPath} : ${e.toString()}');
+					point.animInfo = animInfos[animIndex];
+				} catch (e) {
+					trace('Couldn\'t load anim ${blendSpacePoint.animPath} : ${e.toString()}');
+				}
 			}
+
 			points.push(point);
 		}
 
@@ -124,6 +127,9 @@ class BlendSpace2D extends AnimNode {
 
 	@:haxe.warning("-WInlineOptimizedField")
 	override function getBoneTransform(boneId:Int, outMatrix:h3d.Matrix, ctx:hrt.animgraph.nodes.AnimNode.GetBoneTransformContext) {
+		if (triangles.length < 1)
+			return;
+
 		if (currentTriangle == -1) {
 			var curPos = inline new h2d.col.Point(bsX, bsY);
 
@@ -178,9 +184,6 @@ class BlendSpace2D extends AnimNode {
 
 			if (currentTriangle == -1)
 				throw "assert";
-
-
-			trace(bsX, bsY, weights, weights[0] + weights[1] + weights[2], currentTriangle, collided, debugk);
 		}
 
 		var blendedPos = inline new h3d.Vector();
@@ -191,22 +194,30 @@ class BlendSpace2D extends AnimNode {
 		var def = ctx.getDefPose();
 		refQuat.set(def._12, def._13, def._21, def._23);
 		for (ptIndex => point in triangle) {
-			@:privateAccess
-			if (!point.animInfo.anim.isSync) {
-				point.animInfo.anim.sync(true);
-				point.animInfo.anim.isSync = true;
-			}
-			var boneIndex = point.animInfo.indexRemap[boneId];
-			var matrix = if (boneIndex == -1 || point.animInfo.anim == null) {
-				def;
-			} else {
-				point.animInfo.anim.getObjects()[boneIndex].targetObject.defaultTransform;
-			}
-
 			var w =  weights[ptIndex];
 			if (w == 0) {
 				continue;
 			}
+
+			var matrix : h3d.Matrix;
+
+			if (point.animInfo == null) {
+				matrix = ctx.getDefPose();
+			}
+			else {
+				@:privateAccess
+				if (!point.animInfo.anim.isSync) {
+					point.animInfo.anim.sync(true);
+					point.animInfo.anim.isSync = true;
+				}
+				var boneIndex = point.animInfo.indexRemap[boneId];
+				matrix = if (boneIndex == -1 || point.animInfo.anim == null) {
+					def;
+				} else {
+					point.animInfo.anim.getObjects()[boneIndex].targetObject.defaultTransform;
+				}
+			}
+
 			blendedPos = inline blendedPos.add(inline new h3d.Vector(matrix.tx * w, matrix.ty * w, matrix.tz * w));
 			blendedScale = inline blendedScale.add(inline new h3d.Vector(matrix._11 * w, matrix._22 * w, matrix._33 * w));
 			workQuats[ptIndex].set(matrix._12, matrix._13, matrix._21, matrix._23);

+ 2 - 2
hrt/prefab/Prefab.hx

@@ -168,8 +168,8 @@ class Prefab {
 		if( !sh.isInstance ) throw "assert";
 		if( sh.currentPath == null ) sh.currentPath = shared.currentPath;
 		#if editor
-		sh.editor = this.shared.editor;
-		sh.scene = this.shared.scene;
+		sh.editor ??= this.shared.editor;
+		sh.scene ??= this.shared.scene;
 		#end
 		return this.clone(sh).make(sh);
 	}