Browse Source

Re-merged ref-overrides2 with fixes for crash

Clément Espeute 4 months ago
parent
commit
4700951358

+ 14 - 5
bin/style.css

@@ -1128,11 +1128,6 @@ input[type=checkbox].indeterminate:after {
   word-break: break-word;
   word-break: break-word;
   margin-top: 4px;
   margin-top: 4px;
 }
 }
-.hide-properties dd span,
-.hide-properties dt span {
-  display: inline-block;
-  width: 20px;
-}
 .hide-properties dt {
 .hide-properties dt {
   width: calc(80px - var(--level) * 2%);
   width: calc(80px - var(--level) * 2%);
   text-align: right;
   text-align: right;
@@ -1166,6 +1161,9 @@ input[type=checkbox].indeterminate:after {
 .hide-properties input[type=checkbox] {
 .hide-properties input[type=checkbox] {
   width: 13px;
   width: 13px;
 }
 }
+.hide-properties .warning {
+  color: red;
+}
 .hide-properties dt input[type=button] {
 .hide-properties dt input[type=button] {
   width: 100%;
   width: 100%;
   text-align: right;
   text-align: right;
@@ -2733,6 +2731,17 @@ body.hide-subview .lm_controls {
 .jstree .inRef {
 .jstree .inRef {
   background: #444444 !important;
   background: #444444 !important;
 }
 }
+.jstree .isOverride {
+  background: #25346d !important;
+}
+.jstree .isOverride.isOverriden > i,
+.jstree .isOverride.isOverriden > a {
+  color: cyan !important;
+}
+.jstree .isOverride.isOverriden.isOverridenNew i,
+.jstree .isOverride.isOverriden.isOverridenNew a {
+  color: #26c726 !important;
+}
 .jstree a.locked {
 .jstree a.locked {
   color: #666 !important;
   color: #666 !important;
   font-style: italic;
   font-style: italic;

+ 20 - 5
bin/style.less

@@ -1231,11 +1231,6 @@ input[type=checkbox] {
 		text-wrap: wrap;
 		text-wrap: wrap;
 		word-break: break-word;
 		word-break: break-word;
 		margin-top:4px;
 		margin-top:4px;
-
-		span {
-			display: inline-block;
-			width: 20px;
-		}
 	}
 	}
 
 
 	dt {
 	dt {
@@ -1278,6 +1273,10 @@ input[type=checkbox] {
 		width:13px;
 		width:13px;
 	}
 	}
 
 
+	.warning {
+		color: red;
+	}
+
 	dt {
 	dt {
 		input[type=button] {
 		input[type=button] {
 			width: 100%;
 			width: 100%;
@@ -3165,6 +3164,22 @@ body.hide-subview {
 		background: rgb(68, 68, 68) !important;
 		background: rgb(68, 68, 68) !important;
 	}
 	}
 
 
+	.isOverride {
+		background: #25346d !important;
+	}
+
+	.isOverride.isOverriden {
+		> i, > a {
+			color: cyan !important;
+		}
+	}
+
+	.isOverride.isOverriden.isOverridenNew {
+		i, a {
+			color: rgb(38, 199, 38) !important;
+		}
+	}
+
 	a.locked {
 	a.locked {
 		color: #666 !important;
 		color: #666 !important;
 		font-style: italic;
 		font-style: italic;

+ 249 - 152
hide/comp/SceneEditor.hx

@@ -238,10 +238,6 @@ class ViewportOverlaysPopup extends hide.comp.Popup {
 			var btn = addButton("Scene Info", "info-circle", "sceneInformationToggle", () -> editor.updateStatusTextVisibility()).appendTo(group);
 			var btn = addButton("Scene Info", "info-circle", "sceneInformationToggle", () -> editor.updateStatusTextVisibility()).appendTo(group);
 			addButton("Wireframe", "connectdevelop", "wireframeToggle", () -> editor.updateWireframe()).appendTo(group);
 			addButton("Wireframe", "connectdevelop", "wireframeToggle", () -> editor.updateWireframe()).appendTo(group);
 			addButton("Disable Scene Render", "eye-slash", "tog-scene-render", () -> {}).appendTo(group);
 			addButton("Disable Scene Render", "eye-slash", "tog-scene-render", () -> {}).appendTo(group);
-
-			//btn.contextmenu(() -> {
-			//
-			//})
 		}
 		}
 
 
 
 
@@ -717,46 +713,46 @@ class ViewModePopup extends hide.comp.Popup {
 }
 }
 
 
 class IconVisibilityPopup extends hide.comp.Popup {
 class IconVisibilityPopup extends hide.comp.Popup {
-    var editor : SceneEditor;
+	 var editor : SceneEditor;
 
 
-    public function new(?parent : Element, editor: SceneEditor) {
-        super(parent);
-        this.editor = editor;
+	 public function new(?parent : Element, editor: SceneEditor) {
+		  super(parent);
+		  this.editor = editor;
 
 
-        element.append(new Element("<p>Icon Visibility</p>"));
-        element.addClass("settings-popup");
-        element.css("max-width", "300px");
+		  element.append(new Element("<p>Icon Visibility</p>"));
+		  element.addClass("settings-popup");
+		  element.css("max-width", "300px");
 
 
-        var form_div = new Element("<div>").addClass("form-grid").appendTo(element);
+		  var form_div = new Element("<div>").addClass("form-grid").appendTo(element);
 
 
-        var editMode : hrt.tools.Gizmo.EditMode = @:privateAccess editor.gizmo.editMode;
+		  var editMode : hrt.tools.Gizmo.EditMode = @:privateAccess editor.gizmo.editMode;
 
 
 		var ide = hide.Ide.inst;
 		var ide = hide.Ide.inst;
-        for (k => v in ide.show3DIconsCategory) {
-            var input = new Element('<input type="checkbox" name="snap" id="$k" value="$k"/>');
-            if (v)
-                input.get(0).toggleAttribute("checked", true);
-            input.change((e) -> {
+		  for (k => v in ide.show3DIconsCategory) {
+				var input = new Element('<input type="checkbox" name="snap" id="$k" value="$k"/>');
+				if (v)
+					 input.get(0).toggleAttribute("checked", true);
+				input.change((e) -> {
 				var val = !ide.show3DIconsCategory.get(k);
 				var val = !ide.show3DIconsCategory.get(k);
 				ide.show3DIconsCategory.set(k, val);
 				ide.show3DIconsCategory.set(k, val);
 				js.Browser.window.localStorage.setItem(hrt.impl.EditorTools.iconVisibilityKey(k), val ? "true" : "false");
 				js.Browser.window.localStorage.setItem(hrt.impl.EditorTools.iconVisibilityKey(k), val ? "true" : "false");
-            });
-            form_div.append(input);
-            form_div.append(new Element('<label for="$k" class="left">$k</label>'));
-        }
-    }
+				});
+				form_div.append(input);
+				form_div.append(new Element('<label for="$k" class="left">$k</label>'));
+		  }
+	 }
 }
 }
 
 
 class HelpPopup extends hide.comp.Popup {
 class HelpPopup extends hide.comp.Popup {
 	var editor : SceneEditor;
 	var editor : SceneEditor;
 
 
 	public function new(?parent : Element, editor: SceneEditor, ?shortcuts: Array<{name:String, shortcut:String}>) {
 	public function new(?parent : Element, editor: SceneEditor, ?shortcuts: Array<{name:String, shortcut:String}>) {
-        super(parent);
-        this.editor = editor;
+		  super(parent);
+		  this.editor = editor;
 
 
-        element.append(new Element("<p>Shortcuts</p>"));
-        element.addClass("settings-popup");
-        element.css("max-width", "300px");
+		  element.append(new Element("<p>Shortcuts</p>"));
+		  element.addClass("settings-popup");
+		  element.css("max-width", "300px");
 
 
 		var form_div = new Element("<div>").addClass("form-grid").appendTo(element);
 		var form_div = new Element("<div>").addClass("form-grid").appendTo(element);
 
 
@@ -916,9 +912,9 @@ class RenderPropsPopup extends Popup {
 @:access(hide.comp.SceneEditor)
 @:access(hide.comp.SceneEditor)
 class CustomEditor {
 class CustomEditor {
 
 
-    var ide(get, never) : hide.Ide;
+	 var ide(get, never) : hide.Ide;
 	function get_ide() { return editor.ide; }
 	function get_ide() { return editor.ide; }
-    var editor : SceneEditor;
+	 var editor : SceneEditor;
 
 
 	var element : hide.Element;
 	var element : hide.Element;
 
 
@@ -926,9 +922,9 @@ class CustomEditor {
 		this.editor = editor;
 		this.editor = editor;
 	}
 	}
 
 
-    public function setElementSelected( p : hrt.prefab.Prefab, b : Bool ) {
+	 public function setElementSelected( p : hrt.prefab.Prefab, b : Bool ) {
 		return true;
 		return true;
-    }
+	 }
 
 
 	public function update( dt : Float ) {
 	public function update( dt : Float ) {
 
 
@@ -991,11 +987,11 @@ class SceneEditor {
 	public var curEdit(default, null) : SceneEditorContext;
 	public var curEdit(default, null) : SceneEditorContext;
 	public var snapToGround = false;
 	public var snapToGround = false;
 
 
-    public var snapToggle = false;
-    public var snapMoveStep = 1.0;
-    public var snapRotateStep = 15.0;
-    public var snapScaleStep = 1.0;
-    public var snapForceOnGrid = false;
+	 public var snapToggle = false;
+	 public var snapMoveStep = 1.0;
+	 public var snapRotateStep = 15.0;
+	 public var snapScaleStep = 1.0;
+	 public var snapForceOnGrid = false;
 
 
 	public var localTransform = true;
 	public var localTransform = true;
 	public var selfOnlyTransform = false;
 	public var selfOnlyTransform = false;
@@ -1082,11 +1078,17 @@ class SceneEditor {
 
 
 	public var lastFocusObjects : Array<Object> = [];
 	public var lastFocusObjects : Array<Object> = [];
 
 
-	public function new(view, data) {
+
+	// Called when the sceneEditor scene has finished loading
+	// Use it to call setPrefab() to set the content of the scene
+	dynamic public function onSceneReady() {
+
+	}
+
+	public function new(view) {
 		ready = false;
 		ready = false;
 		ide = hide.Ide.inst;
 		ide = hide.Ide.inst;
 		this.view = view;
 		this.view = view;
-		this.sceneData = data;
 
 
 		event = new hxd.WaitEvent();
 		event = new hxd.WaitEvent();
 
 
@@ -1106,7 +1108,7 @@ class SceneEditor {
 		var sceneEl = new Element('<div class="heaps-scene"></div>');
 		var sceneEl = new Element('<div class="heaps-scene"></div>');
 		scene = new hide.comp.Scene(view.config, null, sceneEl);
 		scene = new hide.comp.Scene(view.config, null, sceneEl);
 		scene.editor = this;
 		scene.editor = this;
-		scene.onReady = onSceneReady;
+		scene.onReady = onSceneReadyInternal;
 		scene.onResize = function() {
 		scene.onResize = function() {
 			if( cameraController2D != null ) cameraController2D.toTarget();
 			if( cameraController2D != null ) cameraController2D.toTarget();
 			onResize();
 			onResize();
@@ -1307,23 +1309,23 @@ class SceneEditor {
 		grid.scale(1);
 		grid.scale(1);
 		grid.material.mainPass.setPassName("overlay");
 		grid.material.mainPass.setPassName("overlay");
 
 
-        if (snapToggle) {
-    		gridStep = snapMoveStep;
-        }
-        else {
-            gridStep = ide.currentConfig.get("sceneeditor.gridStep");
-        }
+		  if (snapToggle) {
+	 		gridStep = snapMoveStep;
+		  }
+		  else {
+				gridStep = ide.currentConfig.get("sceneeditor.gridStep");
+		  }
 		gridSize = ide.currentConfig.get("sceneeditor.gridSize");
 		gridSize = ide.currentConfig.get("sceneeditor.gridSize");
 
 
 		var col = h3d.Vector.fromColor(scene?.engine?.backgroundColor ?? 0);
 		var col = h3d.Vector.fromColor(scene?.engine?.backgroundColor ?? 0);
 		var hsl = col.toColorHSL();
 		var hsl = col.toColorHSL();
 
 
-        var mov = 0.1;
+		  var mov = 0.1;
 
 
-        if (snapToggle) {
-            mov = 0.2;
-            hsl.y += (1.0-hsl.y) * 0.2;
-        }
+		  if (snapToggle) {
+				mov = 0.2;
+				hsl.y += (1.0-hsl.y) * 0.2;
+		  }
 		if(hsl.z > 0.5) hsl.z -= mov;
 		if(hsl.z > 0.5) hsl.z -= mov;
 		else hsl.z += mov;
 		else hsl.z += mov;
 
 
@@ -1348,12 +1350,12 @@ class SceneEditor {
 
 
 		var hsl = color.toColorHSL();
 		var hsl = color.toColorHSL();
 
 
-        var mov = 0.1;
+		  var mov = 0.1;
 
 
-        if (snapToggle) {
-            mov = 0.2;
-            hsl.y += (1.0-hsl.y) * 0.2;
-        }
+		  if (snapToggle) {
+				mov = 0.2;
+				hsl.y += (1.0-hsl.y) * 0.2;
+		  }
 		if(hsl.z > 0.5) hsl.z -= mov;
 		if(hsl.z > 0.5) hsl.z -= mov;
 		else hsl.z += mov;
 		else hsl.z += mov;
 
 
@@ -1428,16 +1430,16 @@ class SceneEditor {
 		haxe.Timer.delay(function() event.wait(0.5, updateStats), 0);
 		haxe.Timer.delay(function() event.wait(0.5, updateStats), 0);
 	}
 	}
 
 
-    public function getSnapStatus() : Bool {
-        var ctrl = K.isDown(K.CTRL);
-        return (snapToggle && !ctrl) || (!snapToggle && ctrl);
-    };
+	 public function getSnapStatus() : Bool {
+		  var ctrl = K.isDown(K.CTRL);
+		  return (snapToggle && !ctrl) || (!snapToggle && ctrl);
+	 };
 
 
-    public function snap(value: Float, step:Float) : Float {
-        if (step > 0.0 && getSnapStatus())
-            value = hxd.Math.round(value / step) * step;
-        return value;
-    }
+	 public function snap(value: Float, step:Float) : Float {
+		  if (step > 0.0 && getSnapStatus())
+				value = hxd.Math.round(value / step) * step;
+		  return value;
+	 }
 
 
 	public function gizmoSnap(value: Float, mode: hrt.tools.Gizmo.EditMode) : Float {
 	public function gizmoSnap(value: Float, mode: hrt.tools.Gizmo.EditMode) : Float {
 		switch(mode) {
 		switch(mode) {
@@ -1522,7 +1524,7 @@ class SceneEditor {
 		if (tree == null)
 		if (tree == null)
 			tree = this.tree;
 			tree = this.tree;
 
 
-        focusObjects(getSelectedLocal3D());
+		  focusObjects(getSelectedLocal3D());
 		var selected3d = getSelectedLocal3D();
 		var selected3d = getSelectedLocal3D();
 		for(obj in selectedPrefabs)
 		for(obj in selectedPrefabs)
 			tree.revealNode(obj);
 			tree.revealNode(obj);
@@ -1712,9 +1714,9 @@ class SceneEditor {
 			return;
 			return;
 
 
 		var id = Std.parseInt(settings.camTypeIndex) ?? 0;
 		var id = Std.parseInt(settings.camTypeIndex) ?? 0;
-        var newClass = CameraControllerEditor.controllersClasses[id];
-        if (Type.getClass(cameraController) != newClass.cl)
-            switchCamController(newClass.cl);
+		  var newClass = CameraControllerEditor.controllersClasses[id];
+		  if (Type.getClass(cameraController) != newClass.cl)
+				switchCamController(newClass.cl);
 
 
 		scene.s3d.camera.pos.set(settings.x, settings.y, settings.z);
 		scene.s3d.camera.pos.set(settings.x, settings.y, settings.z);
 		scene.s3d.camera.target.set(settings.tx, settings.ty, settings.tz);
 		scene.s3d.camera.target.set(settings.tx, settings.ty, settings.tz);
@@ -1769,53 +1771,45 @@ class SceneEditor {
 		}
 		}
 	}
 	}
 
 
-    function loadSnapSettings() {
-        function sanitize(value:Dynamic, def: Dynamic) {
-            if (value == null || value == 0.0)
-                return def;
-            return value;
-        }
-        @:privateAccess snapMoveStep = sanitize(view.getDisplayState("snapMoveStep"), snapMoveStep);
-        @:privateAccess snapRotateStep = sanitize(view.getDisplayState("snapRotateStep"), snapRotateStep);
-        @:privateAccess snapScaleStep = sanitize(view.getDisplayState("snapScaleStep"), snapScaleStep);
-        @:privateAccess snapForceOnGrid = view.getDisplayState("snapForceOnGrid");
-    }
-
-    public function saveSnapSettings() {
-        @:privateAccess view.saveDisplayState("snapMoveStep", snapMoveStep);
-        @:privateAccess view.saveDisplayState("snapRotateStep", snapRotateStep);
-        @:privateAccess view.saveDisplayState("snapScaleStep", snapScaleStep);
-        @:privateAccess view.saveDisplayState("snapForceOnGrid", snapForceOnGrid);
-    }
-
-    function toggleSnap(?force: Bool) {
-        if (force != null)
-            snapToggle = force;
-        else
-            snapToggle = !snapToggle;
-
-        var snap = new Element("#snap").get(0);
-        if (snap != null) {
-            snap.toggleAttribute("checked", snapToggle);
-        }
-
-        updateGrid();
-    }
-
-	function onSceneReady() {
-		// Load display state
-		{
-			var all = sceneData.flatten(PrefabElement, null);
-			var list = @:privateAccess view.getDisplayState("hideList");
-			if(list != null) {
-				var m = [for(i in (list:Array<Dynamic>)) i => true];
-				for(p in all) {
-					if(m.exists(p.getAbsPath(true, true)))
-						hideList.set(p, true);
-				}
-			}
+	function loadSnapSettings() {
+		function sanitize(value:Dynamic, def: Dynamic) {
+			if (value == null || value == 0.0)
+					return def;
+			return value;
 		}
 		}
+		@:privateAccess snapMoveStep = sanitize(view.getDisplayState("snapMoveStep"), snapMoveStep);
+		@:privateAccess snapRotateStep = sanitize(view.getDisplayState("snapRotateStep"), snapRotateStep);
+		@:privateAccess snapScaleStep = sanitize(view.getDisplayState("snapScaleStep"), snapScaleStep);
+		@:privateAccess snapForceOnGrid = view.getDisplayState("snapForceOnGrid");
+	}
+
+	public function saveSnapSettings() {
+		@:privateAccess view.saveDisplayState("snapMoveStep", snapMoveStep);
+		@:privateAccess view.saveDisplayState("snapRotateStep", snapRotateStep);
+		@:privateAccess view.saveDisplayState("snapScaleStep", snapScaleStep);
+		@:privateAccess view.saveDisplayState("snapForceOnGrid", snapForceOnGrid);
+	}
 
 
+	function toggleSnap(?force: Bool) {
+		if (force != null)
+			snapToggle = force;
+		else
+			snapToggle = !snapToggle;
+
+		var snap = new Element("#snap").get(0);
+		if (snap != null) {
+			snap.toggleAttribute("checked", snapToggle);
+		}
+
+		updateGrid();
+	}
+
+	public function setPrefab(prefab: hrt.prefab.Prefab) {
+		sceneData = prefab;
+		refreshScene();
+	}
+
+	function onSceneReadyInternal() {
 		tree.saveDisplayKey = view.saveDisplayKey + '/tree';
 		tree.saveDisplayKey = view.saveDisplayKey + '/tree';
 		renderPropsTree.saveDisplayKey = view.saveDisplayKey + '/renderPropsTree';
 		renderPropsTree.saveDisplayKey = view.saveDisplayKey + '/renderPropsTree';
 
 
@@ -1900,7 +1894,7 @@ class SceneEditor {
 				value : o,
 				value : o,
 				text : o.name,
 				text : o.name,
 				icon : "ico ico-"+icon,
 				icon : "ico ico-"+icon,
-				children : o.children.length > 0 || (ref != null && @:privateAccess ref.editMode),
+				children : o.children.length > 0 || (ref != null && ref.editMode != None),
 				state: state
 				state: state
 			};
 			};
 			return r;
 			return r;
@@ -1918,7 +1912,7 @@ class SceneEditor {
 				objs = visibleObjs;
 				objs = visibleObjs;
 			}
 			}
 			var ref = o == null ? null : o.to(Reference);
 			var ref = o == null ? null : o.to(Reference);
-			@:privateAccess if( ref != null && ref.editMode && ref.refInstance != null ) {
+			@:privateAccess if( ref != null && ref.editMode != None && ref.refInstance != null ) {
 				for( c in ref.refInstance )
 				for( c in ref.refInstance )
 					objs.push(c);
 					objs.push(c);
 			}
 			}
@@ -1927,6 +1921,8 @@ class SceneEditor {
 		};
 		};
 
 
 		tree.get = function(o:PrefabElement) {
 		tree.get = function(o:PrefabElement) {
+			if (sceneData == null)
+				return [];
 			var objs = o == null ? sceneData.children : Lambda.array(o);
 			var objs = o == null ? sceneData.children : Lambda.array(o);
 			return getFunc(objs, o);
 			return getFunc(objs, o);
 		};
 		};
@@ -2137,17 +2133,21 @@ class SceneEditor {
 		tree.applyStyle = function(p, el) applyTreeStyle(p, el);
 		tree.applyStyle = function(p, el) applyTreeStyle(p, el);
 		renderPropsTree.applyStyle = function(p, el) applyTreeStyle(p, el, renderPropsTree);
 		renderPropsTree.applyStyle = function(p, el) applyTreeStyle(p, el, renderPropsTree);
 
 
-		selectElements([]);
-		refreshScene();
+		ready = true;
+
+		onSceneReady();
+
+		selectElements([], NoHistory);
 		this.camera2D = camera2D;
 		this.camera2D = camera2D;
 
 
 		updateViewportOverlays();
 		updateViewportOverlays();
 
 
-		ready = true;
+
 		for (callback in readyDelayed) {
 		for (callback in readyDelayed) {
 			callback();
 			callback();
 		}
 		}
 		readyDelayed.empty();
 		readyDelayed.empty();
+
 	}
 	}
 
 
 	function checkAllowParent(prefabInf:hrt.prefab.Prefab.PrefabInfo, prefabParent : PrefabElement) : Bool {
 	function checkAllowParent(prefabInf:hrt.prefab.Prefab.PrefabInfo, prefabParent : PrefabElement) : Bool {
@@ -2171,7 +2171,17 @@ class SceneEditor {
 		tree.collapseAll();
 		tree.collapseAll();
 	}
 	}
 
 
+	var treeRefreshing = false;
+	var queueRefresh : Array<() -> Void> = null;
+
 	function refreshTree( ?callb ) {
 	function refreshTree( ?callb ) {
+		if (treeRefreshing) {
+			queueRefresh ??= [];
+			if (callb != null)
+				queueRefresh.push(callb);
+			return;
+		}
+		treeRefreshing = true;
 		tree.refresh(function() {
 		tree.refresh(function() {
 			var all = sceneData.flatten(PrefabElement);
 			var all = sceneData.flatten(PrefabElement);
 			for(elt in all) {
 			for(elt in all) {
@@ -2183,6 +2193,13 @@ class SceneEditor {
 			tree.setSelection(selectedPrefabs);
 			tree.setSelection(selectedPrefabs);
 
 
 			if(callb != null) callb();
 			if(callb != null) callb();
+
+			treeRefreshing = false;
+			if (queueRefresh != null) {
+				var list = queueRefresh;
+				queueRefresh = null;
+				refreshTree(() -> for (cb in list) cb());
+			}
 		});
 		});
 
 
 		renderPropsTree.refresh(function() {
 		renderPropsTree.refresh(function() {
@@ -2359,7 +2376,7 @@ class SceneEditor {
 		if (renderPropsRoot == null && path != null) {
 		if (renderPropsRoot == null && path != null) {
 			renderPropsRoot = new hrt.prefab.Reference(null, new ContextShared());
 			renderPropsRoot = new hrt.prefab.Reference(null, new ContextShared());
 			renderPropsRoot.setEditor(this, this.scene);
 			renderPropsRoot.setEditor(this, this.scene);
-			renderPropsRoot.editMode = Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false);
+			renderPropsRoot.editMode = Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false) ? Edit : None;
 			renderPropsRoot.name = "Render Props";
 			renderPropsRoot.name = "Render Props";
 			renderPropsRoot.source = path;
 			renderPropsRoot.source = path;
 
 
@@ -2398,13 +2415,16 @@ class SceneEditor {
 	}
 	}
 
 
 	public function refreshScene() {
 	public function refreshScene() {
+
 		clearWatches();
 		clearWatches();
 
 
 		if (root2d != null) root2d.remove();
 		if (root2d != null) root2d.remove();
 		if (root3d != null) root3d.remove();
 		if (root3d != null) root3d.remove();
 
 
-		if (sceneData != null)
-			sceneData.dispose();
+		if (sceneData == null)
+			return;
+
+		sceneData.dispose();
 
 
 		hrt.impl.Gradient.purgeEditorCache();
 		hrt.impl.Gradient.purgeEditorCache();
 
 
@@ -2449,6 +2469,19 @@ class SceneEditor {
 		scene.init();
 		scene.init();
 		scene.engine.backgroundColor = bgcol;
 		scene.engine.backgroundColor = bgcol;
 
 
+		// Load display state
+		{
+			var all = sceneData.flatten(PrefabElement, null);
+			var list = @:privateAccess view.getDisplayState("hideList");
+			if(list != null) {
+				var m = [for(i in (list:Array<Dynamic>)) i => true];
+				for(p in all) {
+					if(m.exists(p.getAbsPath(true, true)))
+						hideList.set(p, true);
+				}
+			}
+		}
+
 		rebuild(sceneData);
 		rebuild(sceneData);
 
 
 		var all = sceneData.all();
 		var all = sceneData.all();
@@ -2460,6 +2493,8 @@ class SceneEditor {
 		setRenderProps();
 		setRenderProps();
 
 
 		onRefresh();
 		onRefresh();
+
+
 	}
 	}
 
 
 	function getAllWithRefs<T:PrefabElement>( p : PrefabElement, cl : Class<T>, ?arr : Array<T>, forceLoad: Bool = false ) : Array<T> {
 	function getAllWithRefs<T:PrefabElement>( p : PrefabElement, cl : Class<T>, ?arr : Array<T>, forceLoad: Bool = false ) : Array<T> {
@@ -2490,10 +2525,6 @@ class SceneEditor {
 			if( isLocked(elt) ) toggleInteractive(elt, false);
 			if( isLocked(elt) ) toggleInteractive(elt, false);
 		}
 		}
 		var ref = Std.downcast(elt,Reference);
 		var ref = Std.downcast(elt,Reference);
-		@:privateAccess if( ref != null && ref.editMode && ref.refInstance != null ) {
-			for( p in ref.refInstance.flatten() )
-				makeInteractive(p);
-		}
 	}
 	}
 
 
 	function toggleInteractive( e : PrefabElement, visible : Bool ) {
 	function toggleInteractive( e : PrefabElement, visible : Bool ) {
@@ -2660,8 +2691,10 @@ class SceneEditor {
 	}
 	}
 
 
 	public function refreshInteractive(elt : PrefabElement) {
 	public function refreshInteractive(elt : PrefabElement) {
-		removeInteractive(elt);
-		makeInteractive(elt);
+		for (p in elt.flatten(null, null)) {
+			removeInteractive(p);
+			makeInteractive(p);
+		}
 	}
 	}
 
 
 	public function removeInteractive(elt: PrefabElement) {
 	public function removeInteractive(elt: PrefabElement) {
@@ -2728,7 +2761,7 @@ class SceneEditor {
 				if(rot != null) {
 				if(rot != null) {
 					rot.toMatrix(transf);
 					rot.toMatrix(transf);
 
 
-                }
+					 }
 				if(translate != null)
 				if(translate != null)
 					transf.translate(translate.x, translate.y, translate.z);
 					transf.translate(translate.x, translate.y, translate.z);
 				for(i in 0...sceneObjs.length) {
 				for(i in 0...sceneObjs.length) {
@@ -2762,21 +2795,21 @@ class SceneEditor {
 					var obj3d = objects3d[i];
 					var obj3d = objects3d[i];
 					var obj3dPrevTransform = obj3d.getTransform();
 					var obj3dPrevTransform = obj3d.getTransform();
 					var euler = newMat.getEulerAngles();
 					var euler = newMat.getEulerAngles();
-                    if (translate != null && translate.length() > 0.0001 && snapForceOnGrid) {
-                        obj3d.x = snap(quantize(newMat.tx, posQuant), snapMoveStep);
-                        obj3d.y = snap(quantize(newMat.ty, posQuant), snapMoveStep);
-                        obj3d.z = snap(quantize(newMat.tz, posQuant), snapMoveStep);
-                    }
-                    else { // Don't snap translation if the primary action wasn't a translation (i.e. Rotation around a pivot)
+						  if (translate != null && translate.length() > 0.0001 && snapForceOnGrid) {
+								obj3d.x = snap(quantize(newMat.tx, posQuant), snapMoveStep);
+								obj3d.y = snap(quantize(newMat.ty, posQuant), snapMoveStep);
+								obj3d.z = snap(quantize(newMat.tz, posQuant), snapMoveStep);
+						  }
+						  else { // Don't snap translation if the primary action wasn't a translation (i.e. Rotation around a pivot)
 						obj3d.x = quantize(newMat.tx, posQuant);
 						obj3d.x = quantize(newMat.tx, posQuant);
 						obj3d.y = quantize(newMat.ty, posQuant);
 						obj3d.y = quantize(newMat.ty, posQuant);
 						obj3d.z = quantize(newMat.tz, posQuant);
 						obj3d.z = quantize(newMat.tz, posQuant);
 					}
 					}
 
 
-                    if (rot != null) {
-                        obj3d.rotationX = quantize(M.radToDeg(euler.x), rotQuant);
-                        obj3d.rotationY = quantize(M.radToDeg(euler.y), rotQuant);
-                        obj3d.rotationZ = quantize(M.radToDeg(euler.z), rotQuant);
+						  if (rot != null) {
+								obj3d.rotationX = quantize(M.radToDeg(euler.x), rotQuant);
+								obj3d.rotationY = quantize(M.radToDeg(euler.y), rotQuant);
+								obj3d.rotationZ = quantize(M.radToDeg(euler.z), rotQuant);
 					}
 					}
 
 
 					if(scale != null) {
 					if(scale != null) {
@@ -2941,10 +2974,10 @@ class SceneEditor {
 			var engine = h3d.Engine.getCurrent();
 			var engine = h3d.Engine.getCurrent();
 			var ratio = 150 / engine.height;
 			var ratio = 150 / engine.height;
 
 
-            var scale = ratio * distToCam * Math.tan(cam.fovY * 0.5 * Math.PI / 180.0);
-            if (cam.orthoBounds != null) {
-                scale = ratio *  (cam.orthoBounds.xSize) * 0.5;
-            }
+				var scale = ratio * distToCam * Math.tan(cam.fovY * 0.5 * Math.PI / 180.0);
+				if (cam.orthoBounds != null) {
+					 scale = ratio *  (cam.orthoBounds.xSize) * 0.5;
+				}
 			basis.setScale(scale);
 			basis.setScale(scale);
 
 
 		} else {
 		} else {
@@ -3158,15 +3191,21 @@ class SceneEditor {
 		}
 		}
 
 
 		var modifiedRef = Std.downcast(p.shared.parentPrefab, hrt.prefab.Reference);
 		var modifiedRef = Std.downcast(p.shared.parentPrefab, hrt.prefab.Reference);
-		if (modifiedRef != null) {
+		if (modifiedRef != null && modifiedRef.editMode == Edit) {
 			var path = modifiedRef.source;
 			var path = modifiedRef.source;
 
 
 			var others = sceneData.findAll(Reference, (r) -> r.source == path && r != modifiedRef, true);
 			var others = sceneData.findAll(Reference, (r) -> r.source == path && r != modifiedRef, true);
 			@:privateAccess
 			@:privateAccess
-			for (ref in others) {
-				removeInstance(ref.refInstance, false);
-				ref.refInstance = modifiedRef.refInstance.clone();
-				queueRebuild(ref);
+			if (others.length > 0) {
+				var data = modifiedRef.refInstance.serialize();
+				beginRebuild();
+				for (ref in others) {
+					removeInstance(ref.refInstance, false);
+					@:privateAccess ref.setRef(data);
+					queueRebuild(ref);
+				}
+				endRebuild();
+				refreshTree();
 			}
 			}
 		}
 		}
 
 
@@ -3181,8 +3220,59 @@ class SceneEditor {
 		var obj3d  = p.to(Object3D);
 		var obj3d  = p.to(Object3D);
 		el.toggleClass("disabled", !p.enabled);
 		el.toggleClass("disabled", !p.enabled);
 		var aEl = el.find("a").first();
 		var aEl = el.find("a").first();
-		var root = p.getRoot();
-		el.toggleClass("inRef", root != sceneData);
+
+		// reference
+		var isOverride = false;
+		var isOverriden = false;
+		var isOverridenNew = false;
+		var inRef = false;
+		if (p.shared.parentPrefab != null) {
+			var parentRef = Std.downcast(p.shared.parentPrefab, Reference);
+			if (parentRef != null) {
+				if (parentRef.editMode == Override) {
+					isOverride = true;
+
+					var path = [];
+					var current = p;
+					while (current != null) {
+						path.push(current);
+						current = current.parent;
+					}
+
+					var currentOverride = @:privateAccess parentRef.computeDiffFromSource();
+
+					// skip first item in the path
+					path.pop();
+					while(currentOverride != null && path.length > 0) {
+						var current = path.pop();
+						if (currentOverride.children != null) {
+							currentOverride = Reflect.field(currentOverride.children, current.name);
+						}
+					}
+
+					if (currentOverride != null) {
+						var overridenFields = Reflect.fields(currentOverride);
+						overridenFields.remove("children");
+						if (overridenFields.length > 0) {
+							isOverriden = true;
+							if (currentOverride.type != null) {
+								isOverridenNew = true;
+							}
+						}
+					}
+
+				} else {
+					inRef = true;
+				}
+			}
+		}
+
+
+		el.toggleClass("inRef", inRef);
+		el.toggleClass("isOverride", isOverride);
+		el.toggleClass("isOverriden", isOverriden);
+		el.toggleClass("isOverridenNew", isOverridenNew);
+
 
 
 		var tag = getTag(p);
 		var tag = getTag(p);
 
 
@@ -4049,7 +4139,7 @@ class SceneEditor {
 	function groupSelection() {
 	function groupSelection() {
 		if(!canGroupSelection()) {
 		if(!canGroupSelection()) {
 			return;
 			return;
-        }
+		  }
 
 
 		// Sort the selection to match the scene order
 		// Sort the selection to match the scene order
 		var elts : Array<hrt.prefab.Prefab> = [];
 		var elts : Array<hrt.prefab.Prefab> = [];
@@ -4571,7 +4661,7 @@ class SceneEditor {
 			return;
 			return;
 
 
 		var ref = Std.downcast(to, Reference);
 		var ref = Std.downcast(to, Reference);
-		@:privateAccess if( ref != null && ref.editMode ) to = ref.refInstance;
+		@:privateAccess if( ref != null && ref.editMode != None ) to = ref.refInstance;
 
 
 		// Sort node based on where they appear in the scene tree
 		// Sort node based on where they appear in the scene tree
 		var flat = sceneData.flatten();
 		var flat = sceneData.flatten();
@@ -4756,12 +4846,19 @@ class SceneEditor {
 		}
 		}
 	}
 	}
 
 
+	var beginRebuildStack = 0;
 	function beginRebuild() {
 	function beginRebuild() {
+		beginRebuildStack++;
+		if (beginRebuildStack > 1)
+			return;
 		rebuildQueue = [];
 		rebuildQueue = [];
 		rebuildEndCallbacks = [];
 		rebuildEndCallbacks = [];
 	}
 	}
 
 
 	function endRebuild() {
 	function endRebuild() {
+		beginRebuildStack --;
+		if (beginRebuildStack > 0)
+			return;
 		for (prefab => want in rebuildQueue) {
 		for (prefab => want in rebuildQueue) {
 			switch (want) {
 			switch (want) {
 				case Skip:
 				case Skip:

+ 34 - 41
hide/view/FXEditor.hx

@@ -43,16 +43,11 @@ private class FXSceneEditor extends hide.comp.SceneEditor {
 	public var is2D : Bool = false;
 	public var is2D : Bool = false;
 
 
 
 
-	public function new(view,  data) {
-		super(view, data);
+	public function new(view) {
+		super(view);
 		parent = cast view;
 		parent = cast view;
 	}
 	}
 
 
-	override function onSceneReady() {
-		super.onSceneReady();
-		parent.onSceneReady();
-	}
-
 	override function onPrefabChange(p: PrefabElement, ?pname: String) {
 	override function onPrefabChange(p: PrefabElement, ?pname: String) {
 		super.onPrefabChange(p, pname);
 		super.onPrefabChange(p, pname);
 		parent.onPrefabChange(p, pname);
 		parent.onPrefabChange(p, pname);
@@ -345,6 +340,8 @@ class FXEditor extends hide.view.FileView {
 	var xOffset = 0.;
 	var xOffset = 0.;
 	var tlKeys: Array<{name:String, shortcut:String}> = [];
 	var tlKeys: Array<{name:String, shortcut:String}> = [];
 
 
+	var fxprops : hide.comp.PropsEditor;
+
 	var pauseButton : hide.comp.Toolbar.ToolToggle;
 	var pauseButton : hide.comp.Toolbar.ToolToggle;
 	@:isVar var currentTime(get, set) : Float;
 	@:isVar var currentTime(get, set) : Float;
 	var selectMin : Float;
 	var selectMin : Float;
@@ -355,8 +352,6 @@ class FXEditor extends hide.view.FileView {
 	var afterPanRefreshes : Array<Bool->Void> = [];
 	var afterPanRefreshes : Array<Bool->Void> = [];
 	var statusText : h2d.Text;
 	var statusText : h2d.Text;
 
 
-	var scriptEditor : hide.comp.ScriptEditor;
-	//var fxScriptParser : hrt.prefab.fx.FXScriptParser;
 	var cullingPreview : h3d.scene.Sphere;
 	var cullingPreview : h3d.scene.Sphere;
 
 
     var viewModes : Array<String>;
     var viewModes : Array<String>;
@@ -393,8 +388,6 @@ class FXEditor extends hide.view.FileView {
 		var content = sys.io.File.getContent(getPath());
 		var content = sys.io.File.getContent(getPath());
 		var json = haxe.Json.parse(content);
 		var json = haxe.Json.parse(content);
 
 
-
-		data = cast(PrefabElement.createFromDynamic(json), hrt.prefab.fx.BaseFX);
 		currentSign = ide.makeSignature(content);
 		currentSign = ide.makeSignature(content);
 
 
 		element.html('
 		element.html('
@@ -440,16 +433,13 @@ class FXEditor extends hide.view.FileView {
 						<div class="tab expand" name="Properties" icon="cog">
 						<div class="tab expand" name="Properties" icon="cog">
 							<div class="fx-props"></div>
 							<div class="fx-props"></div>
 						</div>
 						</div>
-						<div class="tab expand" name="Script" icon="cog">
-							<div class="fx-script"></div>
-							<div class="fx-scriptParams"></div>
-						</div>
 					</div>
 					</div>
 				</div>
 				</div>
 			</div>');
 			</div>');
 		tools = new hide.comp.Toolbar(null,element.find(".tools-buttons"));
 		tools = new hide.comp.Toolbar(null,element.find(".tools-buttons"));
 		var tabs = new hide.comp.Tabs(null,element.find(".tabs"));
 		var tabs = new hide.comp.Tabs(null,element.find(".tabs"));
-		sceneEditor = new FXSceneEditor(this, cast(data, hrt.prefab.Prefab));
+		sceneEditor = new FXSceneEditor(this);
+		sceneEditor.onSceneReady = onSceneReady;
 
 
 		for (callback in sceneReadyDelayed) {
 		for (callback in sceneReadyDelayed) {
 			sceneEditor.delayReady(callback);
 			sceneEditor.delayReady(callback);
@@ -515,42 +505,19 @@ class FXEditor extends hide.view.FileView {
 		element.find(".collapse-btn").click(function(e) {
 		element.find(".collapse-btn").click(function(e) {
 			sceneEditor.collapseTree();
 			sceneEditor.collapseTree();
 		});
 		});
-		var fxprops = new hide.comp.PropsEditor(undo,null,element.find(".fx-props"));
-		{
-			var edit = new FXEditContext(this);
-			edit.properties = fxprops;
-			edit.scene = sceneEditor.scene;
-			edit.cleanups = [];
-			cast(data, hrt.prefab.Prefab).edit(edit);
-		}
+		fxprops = new hide.comp.PropsEditor(undo,null,element.find(".fx-props"));
+
 
 
 		if (is2D) {
 		if (is2D) {
 			sceneEditor.camera2D = true;
 			sceneEditor.camera2D = true;
 		}
 		}
 
 
-		var scriptElem = element.find(".fx-script");
-		scriptEditor = new hide.comp.ScriptEditor(data.scriptCode, null, scriptElem, scriptElem);
-		function onSaveScript() {
-			data.scriptCode = scriptEditor.code;
-			save();
-			skipNextChange = true;
-			modified = false;
-		}
-		scriptEditor.onSave = onSaveScript;
-		//fxScriptParser = new hrt.prefab.fx.FXScriptParser();
-		data.scriptCode = scriptEditor.code;
-
 		keys.register("playPause", function() { pauseButton.toggle(!pauseButton.isDown()); });
 		keys.register("playPause", function() { pauseButton.toggle(!pauseButton.isDown()); });
 
 
 		currentVersion = undo.currentID;
 		currentVersion = undo.currentID;
 		sceneEditor.tree.element.addClass("small");
 		sceneEditor.tree.element.addClass("small");
 		sceneEditor.renderPropsTree.element.addClass("small");
 		sceneEditor.renderPropsTree.element.addClass("small");
 
 
-		selectMin = 0.0;
-		selectMax = 0.0;
-		previewMin = 0.0;
-		previewMax = data.duration == 0 ? 5000 : data.duration;
-
 		var rpEditionvisible = Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false);
 		var rpEditionvisible = Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false);
 		setRenderPropsEditionVisibility(rpEditionvisible);
 		setRenderPropsEditionVisibility(rpEditionvisible);
 	}
 	}
@@ -583,6 +550,27 @@ class FXEditor extends hide.view.FileView {
 		setRenderPropsEditionVisibility(Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false));
 		setRenderPropsEditionVisibility(Ide.inst.currentConfig.get("sceneeditor.renderprops.edit", false));
 	}
 	}
 	public function onSceneReady() {
 	public function onSceneReady() {
+		data = cast(hxd.res.Loader.currentInstance.load(state.path).toPrefab().load().clone(), hrt.prefab.fx.BaseFX);
+		if (data == null) {
+			throw "Prefab is not a FX";
+			return;
+		}
+
+		sceneEditor.setPrefab(cast data);
+
+		selectMin = 0.0;
+		selectMax = 0.0;
+		previewMin = 0.0;
+		previewMax = data.duration == 0 ? 5000 : data.duration;
+
+		{
+			var edit = new FXEditContext(this);
+			edit.properties = fxprops;
+			edit.scene = sceneEditor.scene;
+			edit.cleanups = [];
+			cast(data, hrt.prefab.Prefab).edit(edit);
+		}
+
 		var axis = new h3d.scene.Graphics(scene.s3d);
 		var axis = new h3d.scene.Graphics(scene.s3d);
 		axis.z = 0.001;
 		axis.z = 0.001;
 		axis.lineStyle(2,0xFF0000); axis.lineTo(1,0,0);
 		axis.lineStyle(2,0xFF0000); axis.lineTo(1,0,0);
@@ -667,6 +655,8 @@ class FXEditor extends hide.view.FileView {
 
 
 		statusText = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d);
 		statusText = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d);
 		statusText.setPosition(5, 5);
 		statusText.setPosition(5, 5);
+
+		rebuildAnimPanel();
 	}
 	}
 
 
 	function onPrefabChange(p: PrefabElement, ?pname: String) {
 	function onPrefabChange(p: PrefabElement, ?pname: String) {
@@ -1259,6 +1249,9 @@ class FXEditor extends hide.view.FileView {
 	}
 	}
 
 
 	function rebuildAnimPanel() {
 	function rebuildAnimPanel() {
+		if (@:privateAccess !sceneEditor.ready)
+			return;
+
 		if(element == null)
 		if(element == null)
 			return;
 			return;
 
 

+ 8 - 2
hide/view/Model.hx

@@ -80,11 +80,12 @@ class Model extends FileView {
 		tabs = new hide.comp.Tabs(null,element.find(".tabs"));
 		tabs = new hide.comp.Tabs(null,element.find(".tabs"));
 		eventList = element.find(".event-editor");
 		eventList = element.find(".event-editor");
 
 
-		root = new hrt.prefab.Prefab(null, null);
 		var def = new hrt.prefab.Prefab(null, null);
 		var def = new hrt.prefab.Prefab(null, null);
 		new hrt.prefab.RenderProps(def, null).name = "renderer";
 		new hrt.prefab.RenderProps(def, null).name = "renderer";
 		var l = new hrt.prefab.Light(def, null);
 		var l = new hrt.prefab.Light(def, null);
-		sceneEditor = new hide.comp.SceneEditor(this, root);
+		sceneEditor = new hide.comp.SceneEditor(this);
+		sceneEditor.onSceneReady = onSceneReady;
+
 		sceneEditor.editorDisplay = false;
 		sceneEditor.editorDisplay = false;
 		sceneEditor.onRefresh = onRefresh;
 		sceneEditor.onRefresh = onRefresh;
 		sceneEditor.onUpdate = onUpdate;
 		sceneEditor.onUpdate = onUpdate;
@@ -1200,6 +1201,11 @@ class Model extends FileView {
 	// Scene editor bindings
 	// Scene editor bindings
 	inline function get_scene() return sceneEditor.scene;
 	inline function get_scene() return sceneEditor.scene;
 
 
+	function onSceneReady() {
+		root = new hrt.prefab.Prefab(null, null);
+		sceneEditor.setPrefab(root);
+	}
+
 	function onRefresh() {
 	function onRefresh() {
 		this.saveDisplayKey = "Model:" + state.path;
 		this.saveDisplayKey = "Model:" + state.path;
 
 

+ 8 - 10
hide/view/Prefab.hx

@@ -52,8 +52,8 @@ class FiltersPopup extends hide.comp.Popup {
 class PrefabSceneEditor extends hide.comp.SceneEditor {
 class PrefabSceneEditor extends hide.comp.SceneEditor {
 	var parent : Prefab;
 	var parent : Prefab;
 
 
-	public function new(view, data) {
-		super(view, data);
+	public function new(view) {
+		super(view);
 		parent = cast view;
 		parent = cast view;
 		this.localTransform = false; // TODO: Expose option
 		this.localTransform = false; // TODO: Expose option
 	}
 	}
@@ -63,11 +63,6 @@ class PrefabSceneEditor extends hide.comp.SceneEditor {
 		parent.onUpdate(dt);
 		parent.onUpdate(dt);
 	}
 	}
 
 
-	override function onSceneReady() {
-		super.onSceneReady();
-		parent.onSceneReady();
-	}
-
 	override function applyTreeStyle(p: PrefabElement, el: Element, ?pname: String, ?tree: hide.comp.IconTree<PrefabElement>) {
 	override function applyTreeStyle(p: PrefabElement, el: Element, ?pname: String, ?tree: hide.comp.IconTree<PrefabElement>) {
 		super.applyTreeStyle(p, el, pname, tree);
 		super.applyTreeStyle(p, el, pname, tree);
 		parent.applyTreeStyle(p, el, pname);
 		parent.applyTreeStyle(p, el, pname);
@@ -239,7 +234,8 @@ class Prefab extends hide.view.FileView {
 	}
 	}
 
 
 	function createEditor() {
 	function createEditor() {
-		sceneEditor = new PrefabSceneEditor(this, data);
+		sceneEditor = new PrefabSceneEditor(this);
+		sceneEditor.onSceneReady = onSceneReady;
 		for (callback in sceneReadyDelayed) {
 		for (callback in sceneReadyDelayed) {
 			sceneEditor.delayReady(callback);
 			sceneEditor.delayReady(callback);
 		}
 		}
@@ -248,10 +244,8 @@ class Prefab extends hide.view.FileView {
 
 
 	override function onDisplay() {
 	override function onDisplay() {
 		if( sceneEditor != null ) sceneEditor.dispose();
 		if( sceneEditor != null ) sceneEditor.dispose();
-
 		createData();
 		createData();
 		var content = sys.io.File.getContent(getPath());
 		var content = sys.io.File.getContent(getPath());
-		data = hrt.prefab.Prefab.createFromDynamic(haxe.Json.parse(content));
 		currentSign = ide.makeSignature(content);
 		currentSign = ide.makeSignature(content);
 
 
 
 
@@ -439,6 +433,9 @@ class Prefab extends hide.view.FileView {
 	}
 	}
 
 
 	public function onSceneReady() {
 	public function onSceneReady() {
+		data = hxd.res.Loader.currentInstance.load(state.path).toPrefab().load().clone();
+		sceneEditor.setPrefab(cast data);
+
 		refreshSceneFilters();
 		refreshSceneFilters();
 		refreshGraphicsFilters();
 		refreshGraphicsFilters();
 		refreshViewModes();
 		refreshViewModes();
@@ -578,6 +575,7 @@ class Prefab extends hide.view.FileView {
 			initGraphicsFilters();
 			initGraphicsFilters();
 			initSceneFilters();
 			initSceneFilters();
 		}
 		}
+
 	}
 	}
 
 
 	function resetCamera( top : Bool ) {
 	function resetCamera( top : Bool ) {

+ 341 - 0
hrt/prefab/Diff.hx

@@ -0,0 +1,341 @@
+package hrt.prefab;
+
+enum DiffResult {
+	/**The two object are identical, don't save anything**/
+	Skip;
+
+	/**The two objects are different, save the result as diff**/
+	Set(diff: Dynamic);
+}
+
+/**
+	Utility class to get the difference between two dynamics
+
+	There are two main functions : diff and apply, and they are reciprocal
+	diff(A,B) = D
+	apply(A,D) = B
+
+	the diff has a special support for prefab if the diffPrefab function is used :
+	the children array is handled as a special case, and prefabs that change type are
+	fully serialized in the diff instead of just the delta
+
+	Special fields prefixed by an @ can appear in the diff, they are as follow :
+
+	@removed : an array of keys name that are present in A but were removed in B
+	@index : in the prefab children data, indicate that this child has changed index between the A.children and B.children array
+
+**/
+class Diff {
+
+	/**
+		Add or Set a key/value pair to a DiffResult. If "diff" was a Skip, it will become a Set({key: value})
+	**/
+	public static function addToDiff(diff: DiffResult, key: String, value: Dynamic) : DiffResult{
+		var v = switch(diff) {
+			case Skip:
+				var v = {};
+				Reflect.setField(v, key, value);
+				return Set(v);
+			case Set(v):
+				Reflect.setField(v, key, value);
+				return diff;
+		}
+	}
+
+	public static function deepCopy(v:Dynamic) : Dynamic {
+		return haxe.Json.parse(haxe.Json.stringify(v));
+	}
+
+	/**
+		Returns the difference of two values together
+	**/
+	public static function diff(originalValue: Dynamic, modifiedValue: Dynamic) : DiffResult {
+		var originalType = Type.typeof(originalValue);
+		var modifiedType = Type.typeof(modifiedValue);
+
+		if (!originalType.equals(modifiedType)) {
+			return Set(modifiedValue);
+		}
+
+		switch (modifiedType) {
+			case TNull:
+				// The only way we get here is if both types are null, so by definition they are both null and so there is no diff
+				return Skip;
+			case TInt | TFloat | TBool:
+				if (originalValue == modifiedValue) {
+					return Skip;
+				}
+			case TObject:
+				return diffObject(originalValue, modifiedValue);
+			case TClass(subClass): {
+				switch (subClass) {
+					case String:
+						if (originalValue == modifiedValue) {
+							return Skip;
+						}
+					case Array:
+						return diffArray(originalValue, modifiedValue);
+					default:
+						throw "Can't diff class " + subClass;
+				}
+			}
+			default:
+				throw "Unhandled type " + modifiedType;
+		}
+		return Set(modifiedValue);
+	}
+
+
+	/**
+		Same as diffObject, but handles `type` and `children` fields as a special case :
+		children is serialized as an object with prefabName: diffPrefab(prefab)
+		and if original.type != modified.type, the whole modified object is copied as is
+		(because we consider that changing the type of a prefab in a diff means the prefab was destroyed then re-created)
+	**/
+	public static function diffPrefab(original: Dynamic, modified: Dynamic) : DiffResult {
+		if (original == null || modified == null) {
+			if (original == modified)
+				return Skip;
+			return Set(deepCopy(modified));
+		}
+
+		if (original.type != modified.type)
+			return Set(deepCopy(modified));
+
+		var result = diffObject(original, modified, ["children"]); // we could skip "type" but because we are sure that the type are equals they will never be serialised
+
+		var resultChildren = {};
+
+		var originalChildren = original.children ?? [];
+		var modifiedChildren = modified.children ?? [];
+
+		var childrenMap : Map<String, {originals: Array<Dynamic>, modifieds: Array<Dynamic>}> = [];
+
+		for (index => child in originalChildren) {
+			hrt.tools.MapUtils.getOrPut(childrenMap, child.name ?? "", {originals: [], modifieds: []}).originals.push({index: index, child: child});
+		}
+
+		for (index => child in modifiedChildren) {
+			hrt.tools.MapUtils.getOrPut(childrenMap, child.name ?? "", {originals: [], modifieds: []}).modifieds.push({index: index, child: child});
+		}
+
+		for (name => data in childrenMap) {
+			for (index in 0...hxd.Math.imax(data.originals.length, data.modifieds.length)) {
+				var originalChild = data.originals[index];
+				var modifiedChild = data.modifieds[index];
+				var key = name;
+
+				#if editor
+				if ((originalChild?.child != null && originalChild.child.type == null) || (modifiedChild?.child != null && modifiedChild.child.type == null)) {
+					throw "can't diff child that have a missing `type`";
+				}
+				#end
+				if (index > 0)
+					key += '@$index';
+
+				var diff = diffPrefab(originalChild?.child, modifiedChild?.child);
+
+				if (originalChild?.index != modifiedChild?.index) {
+					if (modifiedChild?.index != null) {
+						diff = addToDiff(diff, "@index", modifiedChild.index);
+					}
+				}
+
+				switch(diff) {
+					case Skip:
+					case Set(value):
+						Reflect.setField(resultChildren, key, value);
+				}
+			}
+		}
+
+		if (Reflect.fields(resultChildren).length > 0) {
+			result = addToDiff(result, "children", resultChildren);
+		}
+
+		return result;
+	}
+
+	/**
+		Returns the difference between two dynamic objects
+	**/
+	public static function diffObject(original: Dynamic, modified: Dynamic, skipFields: Array<String> = null) : DiffResult {
+		skipFields ??= [];
+		var result = {};
+		var removedFields : Array<String> = [];
+
+		if (original == null || modified == null) {
+			if (original == modified)
+				return Skip;
+			return Set(deepCopy(modified));
+		}
+
+		// Mark fields as removed
+		for (originalField in Reflect.fields(original)) {
+			if (skipFields.contains(originalField))
+				continue;
+
+			if (!Reflect.hasField(modified, originalField)) {
+				removedFields.push(originalField);
+				continue;
+			}
+		}
+
+		for (modifiedField in Reflect.fields(modified)) {
+			if (skipFields.contains(modifiedField))
+				continue;
+
+			var originalValue = Reflect.getProperty(original, modifiedField);
+			var modifiedValue = Reflect.getProperty(modified, modifiedField);
+
+			switch(diff(originalValue, modifiedValue)) {
+				case Skip:
+				case Set(v):
+					Reflect.setField(result, modifiedField, v);
+			}
+		}
+
+		if (removedFields.length > 0) {
+			Reflect.setField(result, "@removed", removedFields);
+		}
+
+		if (Reflect.fields(result).length == 0)
+			return Skip;
+		return Set(result);
+	}
+
+	/**
+		Returns the difference between two arrays. If the arrays are found to be different, a full copy of
+		modified will be returned as a Set()
+	**/
+	public static function diffArray(original: Array<Dynamic>, modified: Dynamic) : DiffResult {
+		if (original.length != modified.length) {
+			return Set(deepCopy(modified));
+		}
+
+		for (index in 0...original.length) {
+			var originalValue = original[index];
+			var modifiedValue = modified[index];
+
+			switch(diff(originalValue, modifiedValue)) {
+				case Set(_):
+					// return the whole modified object when any field is different than the original
+					return Set(deepCopy(modified));
+				case Skip:
+			}
+		}
+		return Skip;
+	}
+
+	/**
+		Modifies `target` dynamic so `apply(a, diffObject(a, b)) == b`
+	**/
+	public static function apply(target: Dynamic, diff: Dynamic) : Dynamic {
+		if (diff == null)
+			return null;
+
+		if (target == null)
+			target = {};
+
+		if (diff.type != null && diff.type != target.type) {
+			return diff;
+		}
+
+		for (field in Reflect.fields(diff)) {
+			if (field == "children")
+			{
+				var targetChildren = Reflect.field(target, "children") ?? [];
+				var diffChildren = Reflect.field(diff, "children");
+
+				for (fields in Reflect.fields(diffChildren)) {
+					var diffChild = Reflect.field(diffChildren, fields);
+					var name = fields;
+					var split = name.split("@");
+					var nthChild = 0;
+					if (split.length == 2) {
+						name = split[0];
+						nthChild = Std.parseInt(split[1]);
+					}
+
+					var targetChild = null;
+					var originalIndex = targetChildren.length; // if we don't found any children with the right name in the array, this will make sure we add the newly created children at the end of the array
+					for (index => child in targetChildren) {
+						if (name == child.name) {
+							if (nthChild == 0) {
+								targetChild = child;
+								originalIndex = index;
+								break;
+							} else {
+								nthChild --;
+							}
+						}
+					}
+
+					// Remove child if null
+					if (diffChild == null) {
+						targetChildren[originalIndex] = null;
+						continue;
+					}
+
+					// Skip diff children that don't have type if they don't
+					// modify a prefab from target object (because we can't create a prefab without a type)
+					if (targetChild == null && diffChild.type == null) {
+							continue;
+					}
+
+					targetChildren[originalIndex] = apply(targetChild, diffChild);
+				}
+
+				// Reorder the targetChildren array based on @indexes.
+				// if the @index point to a slot already taken, find the next free slot
+				// This should ensure that arrays are somewhat coherent in bad situation like
+				// the target children array has been modified since the last diff
+				var finalChildren : Array<Dynamic> = [];
+				for (index => child in targetChildren) {
+					if (child == null) continue;
+					var changedIndex = Reflect.field(child, "@index");
+					var targetIndex = if (changedIndex != null) {
+						Reflect.deleteField(child, "@index");
+						changedIndex;
+					} else {
+						index;
+					}
+					while (finalChildren[targetIndex] != null) {
+						targetIndex ++;
+					}
+					finalChildren[targetIndex] = child;
+				}
+				// If a prefab has been removed, it get inserted as a null in the childrenArray
+				// we fix that here
+				finalChildren = finalChildren.filter((f) -> f != null);
+
+				Reflect.setField(target, "children", finalChildren);
+				continue;
+			}
+
+			if (field == "@removed") {
+				var removed = Reflect.field(diff, "@removed");
+				for (field in (removed:Array<String>)) {
+					Reflect.deleteField(target, field);
+				}
+				continue;
+			}
+
+			var targetValue = Reflect.getProperty(target, field);
+			var diffValue = Reflect.getProperty(diff, field);
+
+			var targetType = Type.typeof(targetValue);
+			var diffType = Type.typeof(diffValue);
+
+			switch (targetType) {
+				case TNull | TInt | TFloat | TBool | TClass(Array) | TClass(String):
+					Reflect.setField(target, field, diffValue);
+				case TObject:
+					apply(targetValue, diffValue);
+				default:
+					throw "unhandeld type " + targetType;
+			}
+		}
+		return target;
+	}
+}

+ 1 - 1
hrt/prefab/Light.hx

@@ -179,7 +179,7 @@ class Light extends Object3D {
 		}
 		}
 
 
 		#if editor
 		#if editor
-		if (shared.parentPrefab == null || (Std.downcast(shared.parentPrefab, Reference)?.editMode && shared.parentPrefab != shared.editor?.renderPropsRoot)) {
+		if (shared.parentPrefab == null || (Std.downcast(shared.parentPrefab, Reference)?.editMode != None && shared.parentPrefab != shared.editor?.renderPropsRoot)) {
 			icon = hrt.impl.EditorTools.create3DIcon(object, hide.Ide.inst.getHideResPath("icons/PointLight.png"), 0.5, Light);
 			icon = hrt.impl.EditorTools.create3DIcon(object, hide.Ide.inst.getHideResPath("icons/PointLight.png"), 0.5, Light);
 		}
 		}
 		#end
 		#end

+ 51 - 17
hrt/prefab/Prefab.hx

@@ -63,7 +63,11 @@ class Prefab {
 	/**
 	/**
 		The associated source file (an image, a 3D model, etc.) if the prefab type needs it.
 		The associated source file (an image, a 3D model, etc.) if the prefab type needs it.
 	**/
 	**/
-	@:s public var source : String;
+	@:s public var source(default, set) : String;
+
+	public function set_source(newSource: String) {
+		return source = newSource;
+	}
 
 
 	/**
 	/**
 		The parent of the prefab in the tree view
 		The parent of the prefab in the tree view
@@ -516,10 +520,39 @@ class Prefab {
 		return [].iterator();
 		return [].iterator();
 	}
 	}
 
 
+
+	/**
+		Returns a name that will allow to disambiguate this
+		prefabs from siblings with the same name
+		Prefab with more than one sibling with the same name
+		will have their name formated as `name-<index>` unless their index is 0.
+	**/
+	public function getUniqueName() {
+		if (parent == null) {
+			return "";
+		}
+
+		var path = name;
+		var suffix = 0;
+		for(i => c in parent.children) {
+			if(c == this)
+				break;
+			else {
+				var cname = c.name ?? "";
+				if(cname == path)
+					++suffix;
+			}
+		}
+		if(suffix > 0)
+			path += "-" + suffix;
+		return path;
+	}
+
 	/**
 	/**
 		Returns the absolute name path for this prefab
 		Returns the absolute name path for this prefab
 	**/
 	**/
 	public function getAbsPath(unique=false, followRef : Bool = false) {
 	public function getAbsPath(unique=false, followRef : Bool = false) {
+		var origParent = parent;
 		var parent = parent;
 		var parent = parent;
 		if (parent != null && followRef) {
 		if (parent != null && followRef) {
 			var ref = Std.downcast(parent.shared.parentPrefab, Reference);
 			var ref = Std.downcast(parent.shared.parentPrefab, Reference);
@@ -532,25 +565,14 @@ class Prefab {
 		if (path == "")
 		if (path == "")
 			path = hrt.prefab.Prefab.emptyNameReplacement;
 			path = hrt.prefab.Prefab.emptyNameReplacement;
 		if(unique) {
 		if(unique) {
-			var suffix = 0;
-			for(i in 0...parent.children.length) {
-				var c = parent.children[i];
-				if(c == this)
-					break;
-				else {
-					var cname = c.name ?? "";
-					if(cname == path)
-						++suffix;
-				}
-			}
-			if(suffix > 0)
-				path += "-" + suffix;
+			path = getUniqueName();
 		}
 		}
 		if(parent.parent != null)
 		if(parent.parent != null)
 			path = parent.getAbsPath(unique) + "." + path;
 			path = parent.getAbsPath(unique) + "." + path;
 		return path;
 		return path;
 	}
 	}
 
 
+
 	/**
 	/**
 		If the prefab `props` represent CDB data, returns the sheet name of it, or null.
 		If the prefab `props` represent CDB data, returns the sheet name of it, or null.
 	 **/
 	 **/
@@ -727,20 +749,32 @@ class Prefab {
 	/**
 	/**
 		Finds a prefab by folowing a dot separated path like this one : `parent.child.grandchild`.
 		Finds a prefab by folowing a dot separated path like this one : `parent.child.grandchild`.
 		Returns null if the path is invalid or does not match any prefabs in the hierarchy
 		Returns null if the path is invalid or does not match any prefabs in the hierarchy
+		If the path contains many prefabs with the same name, they can be disambiguated in the path with `name-index`
 	**/
 	**/
-	function locatePrefab(path: String) : Null<Prefab> {
+	public function locatePrefab(path: String) : Null<Prefab> {
 		if (path == null)
 		if (path == null)
 			return null;
 			return null;
 		var parts = path.split(".");
 		var parts = path.split(".");
 		var p = this;
 		var p = this;
 		while (parts.length > 0 && p != null) {
 		while (parts.length > 0 && p != null) {
 			var name = parts.shift();
 			var name = parts.shift();
+			var subIndex = name.split("-");
+			var chooseNth = 0;
+			if (subIndex.length > 1) {
+				chooseNth = Std.parseInt(subIndex.pop()) ?? 0;
+				name = subIndex[0];
+			}
 			var found = null;
 			var found = null;
+			var currentNth = 0;
 			for (o in p.children) {
 			for (o in p.children) {
 				if (o.name == name)
 				if (o.name == name)
 				{
 				{
-					found = o;
-					break;
+					if (currentNth == chooseNth) {
+						found = o;
+						break;
+					} else {
+						currentNth ++;
+					}
 				}
 				}
 			}
 			}
 			p = found;
 			p = found;

+ 258 - 71
hrt/prefab/Reference.hx

@@ -1,31 +1,62 @@
 package hrt.prefab;
 package hrt.prefab;
 
 
-class Reference extends Object3D {
-	@:s public var editMode : Bool = false;
+enum EditMode {
+	/** The reference can't be edited in the editor **/
+	None;
+
+	/** The reference can be edited in the editor, and saving it will update the referenced prefab file on disk **/
+	Edit;
 
 
+	/** The reference can be edited, and saving it will save a diff between the original prefab and this in the `overrides` field **/
+	Override;
+}
+class Reference extends Object3D {
+	/**
+		The referenced prefab loaded by this reference
+	**/
 	public var refInstance : Prefab;
 	public var refInstance : Prefab;
 
 
+	/**
+		How the reference can be edited in the editor
+	**/
+	@:s public var editMode : EditMode = None;
+
+	/**
+		List of all the properties that differs between this reference
+		and the original prefab data. Use the format defined by
+		hrt.prefab.Diff.diffPrefab
+	**/
+	@:s public var overrides : Dynamic = null;
+
 	#if editor
 	#if editor
 	var wasMade : Bool = false;
 	var wasMade : Bool = false;
+
+	/**
+		Copy of the original data to use as a reference on save for overrides
+	**/
+	public var originalSource : Dynamic;
 	#end
 	#end
 
 
-	public static function copy_overrides(from:Dynamic) : haxe.ds.StringMap<Dynamic> {
-		if (Std.isOfType(from, haxe.ds.StringMap)) {
-			return from != null ? cast(from, haxe.ds.StringMap<Dynamic>).copy() : new haxe.ds.StringMap<Dynamic>();
-		}
-		else {
-			var m = new haxe.ds.StringMap<Dynamic>();
-			for (f in Reflect.fields(from)) {
-				m.set(f, Reflect.getProperty(from ,f));
-			}
-			return m;
+	override function set_source(newSource:String):String {
+		if (newSource != source) {
+			resetRefInstance();
 		}
 		}
+		return source = newSource;
 	}
 	}
 
 
 	override function save() {
 	override function save() {
+		#if editor
+		if (editMode == Override && refInstance != null) {
+			this.overrides = computeDiffFromSource();
+		} else if (editMode == Edit && refInstance != null) {
+			this.overrides = null;
+		}
+		#end
+
 		var obj : Dynamic = super.save();
 		var obj : Dynamic = super.save();
+
 		#if editor
 		#if editor
-		if( editMode && refInstance != null ) {
+		if( editMode == Edit && refInstance != null ) {
 			var sheditor = Std.downcast(shared, hide.prefab.ContextShared);
 			var sheditor = Std.downcast(shared, hide.prefab.ContextShared);
 			if( sheditor.editor != null ) sheditor.editor.watchIgnoreChanges(source);
 			if( sheditor.editor != null ) sheditor.editor.watchIgnoreChanges(source);
 
 
@@ -33,41 +64,111 @@ class Reference extends Object3D {
 			sys.io.File.saveContent(hide.Ide.inst.getPath(source), hide.Ide.inst.toJSON(s));
 			sys.io.File.saveContent(hide.Ide.inst.getPath(source), hide.Ide.inst.toJSON(s));
 		}
 		}
 		#end
 		#end
+
 		return obj;
 		return obj;
 	}
 	}
 
 
-	#if editor
-	override function setEditorChildren(sceneEditor:hide.comp.SceneEditor, scene: hide.comp.Scene) {
-		super.setEditorChildren(sceneEditor, scene);
+	override function load(obj: Dynamic) {
+		// Backward compatibility between old bool editMode and new enum based editMode
+		if (Type.typeof(obj.editMode) == TBool) {
+			obj.editMode = "Edit";
+		}
 
 
-		if (refInstance != null) {
-			refInstance.setEditor(sceneEditor, scene);
+		super.load(obj);
+
+		if (source != null && shouldBeInstanciated() && hxd.res.Loader.currentInstance.exists(source)) {
+			#if editor
+			// we need the resCache to exist or we'll have an error in Ide.CustomeLoader.loadCache
+			if (@:privateAccess h3d.Engine.getCurrent()?.resCache == null)
+				return;
+
+			if (hasCycle())
+				return;
+			#end
+			initRefInstance();
+		}
+	}
+
+	override function copy(obj: Prefab) {
+		super.copy(obj);
+		var otherRef : Reference = cast obj;
+
+		#if editor
+		originalSource = @:privateAccess hxd.res.Loader.currentInstance.load(source).toPrefab().loadData();
+		#end
+
+		// Clone the refInstance from the original prefab on copy
+		if (source != null && shouldBeInstanciated()) {
+			if (otherRef.refInstance != null) {
+				refInstance = otherRef.refInstance.clone(new ContextShared(source, null, null, true));
+			} else {
+				initRefInstance();
+			}
+			if (refInstance != null) {
+				refInstance.shared.parentPrefab = this;
+			}
+		}
+	}
+
+	#if editor
+	function computeDiffFromSource() : Dynamic {
+		var orig = originalSource;
+		var ref = refInstance?.serialize() ?? null;
+		var diff = hrt.prefab.Diff.diffPrefab(orig, ref);
+		switch (diff) {
+			case Skip:
+				return null;
+			case Set(v):
+				return hrt.prefab.Diff.deepCopy(v);
 		}
 		}
 	}
 	}
 	#end
 	#end
 
 
-	function resolveRef() : Prefab {
-		if(source == null)
-			return null;
-		if (refInstance != null)
-			return refInstance;
+	function initRefInstance() {
+		var refInstanceData = null;
 		#if editor
 		#if editor
 		try {
 		try {
 		#end
 		#end
-			var refInstance = hxd.res.Loader.currentInstance.load(source).to(hrt.prefab.Resource).load().clone();
-			refInstance.shared.parentPrefab = this;
-			this.refInstance = refInstance;
-			return refInstance;
+			refInstanceData = @:privateAccess hxd.res.Loader.currentInstance.load(source).toPrefab().loadData();
 		#if editor
 		#if editor
-		} catch (_) {
-			return null;
+			originalSource = @:privateAccess hxd.res.Loader.currentInstance.load(source).toPrefab().loadData();
+		} catch (e) {
+			return;
+		}
+		#end
+
+		if (overrides != null) {
+			refInstanceData = hrt.prefab.Diff.apply(refInstanceData, overrides);
 		}
 		}
+
+		refInstance = hrt.prefab.Prefab.createFromDynamic(refInstanceData, null, new ContextShared(source, null, null, true));
+		refInstance.shared.parentPrefab = this;
+	}
+
+	function resolveRef() : Prefab {
+		var shouldLoad = refInstance == null && source != null && shouldBeInstanciated();
+
+		#if editor
+		if (hasCycle())
+			shouldLoad = false;
 		#end
 		#end
+
+		if (shouldLoad) {
+			initRefInstance();
+		}
+		return refInstance;
 	}
 	}
 
 
 	override function makeInstance() {
 	override function makeInstance() {
 		if( source == null )
 		if( source == null )
 			return;
 			return;
+
+
+		// in the case source has changed since the last load (can happen when creating references manually)
+		if (refInstance?.shared.currentPath != source) {
+			initRefInstance();
+			refInstance = refInstance.clone();
+		}
 		#if editor
 		#if editor
 		if (hasCycle()) {
 		if (hasCycle()) {
 			hide.Ide.inst.quickError('Reference ${getAbsPath()} to $source is creating a cycle. Please fix the reference.');
 			hide.Ide.inst.quickError('Reference ${getAbsPath()} to $source is creating a cycle. Please fix the reference.');
@@ -76,32 +177,31 @@ class Reference extends Object3D {
 		}
 		}
 		#end
 		#end
 
 
-		var p = resolveRef();
 		var refLocal3d : h3d.scene.Object = null;
 		var refLocal3d : h3d.scene.Object = null;
 
 
-		if (Std.downcast(p, Object3D) != null) {
+		if (Std.downcast(refInstance, Object3D) != null) {
 			refLocal3d = shared.current3d;
 			refLocal3d = shared.current3d;
 		} else {
 		} else {
 			super.makeInstance();
 			super.makeInstance();
 			refLocal3d = local3d;
 			refLocal3d = local3d;
 		}
 		}
 
 
-		if (p == null) {
-			refInstance = null;
+		if (refInstance == null) {
 			return;
 			return;
 		}
 		}
 
 
-		var sh = p.shared;
+		var sh = refInstance.shared;
 		@:privateAccess sh.root3d = sh.current3d = refLocal3d;
 		@:privateAccess sh.root3d = sh.current3d = refLocal3d;
 		@:privateAccess sh.root2d = sh.current2d = findFirstLocal2d();
 		@:privateAccess sh.root2d = sh.current2d = findFirstLocal2d();
 
 
 		#if editor
 		#if editor
 		sh.editor = this.shared.editor;
 		sh.editor = this.shared.editor;
 		sh.scene = this.shared.scene;
 		sh.scene = this.shared.scene;
+		if (sh.isInstance == false)
+			throw "isInstance should be true";
 		#end
 		#end
 		sh.parentPrefab = this;
 		sh.parentPrefab = this;
 		sh.customMake = this.shared.customMake;
 		sh.customMake = this.shared.customMake;
-		refInstance = p;
 
 
 		if (refInstance.to(Object3D) != null) {
 		if (refInstance.to(Object3D) != null) {
 			var obj3d = refInstance.to(Object3D);
 			var obj3d = refInstance.to(Object3D);
@@ -120,7 +220,6 @@ class Reference extends Object3D {
 		#end
 		#end
 	}
 	}
 
 
-
 	override public function findRec<T:Prefab>(?cl: Class<T>, ?filter : T -> Bool, followRefs : Bool = false, includeDisabled: Bool = true) : Null<T> {
 	override public function findRec<T:Prefab>(?cl: Class<T>, ?filter : T -> Bool, followRefs : Bool = false, includeDisabled: Bool = true) : Null<T> {
 		if (!includeDisabled && !enabled)
 		if (!includeDisabled && !enabled)
 			return null;
 			return null;
@@ -143,7 +242,7 @@ class Reference extends Object3D {
 
 
 	override public function flatten<T:Prefab>( ?cl : Class<T>, ?arr: Array<T>) : Array<T> {
 	override public function flatten<T:Prefab>( ?cl : Class<T>, ?arr: Array<T>) : Array<T> {
 		arr = super.flatten(cl, arr);
 		arr = super.flatten(cl, arr);
-		if (editMode && resolveRef() != null) {
+		if (editMode != None && resolveRef() != null) {
 			arr = refInstance.flatten(cl, arr);
 			arr = refInstance.flatten(cl, arr);
 		}
 		}
 		return arr;
 		return arr;
@@ -155,8 +254,24 @@ class Reference extends Object3D {
 			refInstance.dispose();
 			refInstance.dispose();
 	}
 	}
 
 
+	function resetRefInstance() {
+		#if editor
+		editorRemoveObjects();
+		#end
+
+		refInstance = null;
+	}
+
 	#if editor
 	#if editor
 
 
+	override function setEditorChildren(sceneEditor:hide.comp.SceneEditor, scene: hide.comp.Scene) {
+		super.setEditorChildren(sceneEditor, scene);
+
+		if (refInstance != null) {
+			refInstance.setEditor(sceneEditor, scene);
+		}
+	}
+
 	override public function editorRemoveObjects() : Void {
 	override public function editorRemoveObjects() : Void {
 		if (refInstance != null && wasMade) {
 		if (refInstance != null && wasMade) {
 			for (child in refInstance.flatten()) {
 			for (child in refInstance.flatten()) {
@@ -168,42 +283,76 @@ class Reference extends Object3D {
 		super.editorRemoveObjects();
 		super.editorRemoveObjects();
 	}
 	}
 
 
-	public function hasCycle(?seenPaths: Map<String, Bool>) : Bool {
-		if (editorOnly)
-			return false;
-		var oldEditMode = editMode;
-		editMode = false;
-		seenPaths = seenPaths?.copy() ?? [];
-		var curPath = this.shared.currentPath;
-		if (seenPaths.get(curPath) != null) {
-			editMode = oldEditMode;
-			return true;
+	/**
+		Updates the original reference data to be equal to `data`.
+		If the ref is an override, the override will be kept as is
+	**/
+	function setRef(data: Dynamic) {
+		if (data == null)
+			throw "Null data";
+
+		if (refInstance == null)
+			return;
+
+		var currentSerialization = refInstance.serialize();
+		var pristineData = hrt.prefab.Diff.deepCopy(data);
+
+		// we might have unsaved changes
+		if (editMode == Override) {
+			switch(hrt.prefab.Diff.diffPrefab(originalSource, currentSerialization)) {
+				case Skip:
+				case Set(diff):
+					pristineData = hrt.prefab.Diff.apply(pristineData, diff);
+			}
+		}
+		else if (overrides != null) {
+			pristineData = hrt.prefab.Diff.apply(pristineData, overrides);
 		}
 		}
-		seenPaths.set(curPath, true);
-
-		if (source != null) {
-			var ref = resolveRef();
-			if (ref != null) {
-				var root = ref;
-				if (Std.isOfType(root, hrt.prefab.fx.BaseFX)) {
-					root = hrt.prefab.fx.BaseFX.BaseFXTools.getFXRoot(root) ?? root;
-				}
 
 
-				var allRefs = root.flatten(Reference);
-				for (r in allRefs) {
-					if (r.hasCycle(seenPaths)){
-						editMode = oldEditMode;
-						return true;
-					}
+		originalSource = hrt.prefab.Diff.deepCopy(data);
+
+		refInstance = Prefab.createFromDynamic(pristineData, new ContextShared(source, true));
+		refInstance.shared.parentPrefab = this;
+	}
+
+	/**
+		Returns true if this reference has a cycle,
+		meaning that references depends on each other
+	**/
+	public function hasCycle() : Bool {
+		var map : Map<String, Bool> = [];
+		map.set(shared.currentPath, true);
+		return hasCycleDynamic(serialize(), map);
+	}
+
+	static function hasCyclePath(path: String, seenPaths: Map<String, Bool>) : Bool {
+		if (seenPaths.get(path) == true)
+			return true;
+		if (!hxd.res.Loader.currentInstance.exists(path))
+			return false;
+
+		seenPaths.set(path, true);
+		var data = @:privateAccess hxd.res.Loader.currentInstance.load(path).toPrefab().loadData();
+		return hasCycleDynamic(data, seenPaths);
+	}
+
+	static function hasCycleDynamic(data: Dynamic, seenPaths: Map<String, Bool>) : Bool {
+		if (data.source != null && (data.type == "reference" || data.type == "subFX")) {
+			if (hasCyclePath(data.source, seenPaths.copy()))
+				return true;
+		}
+		if (data.children) {
+			for (child in (data.children:Array<Dynamic>)) {
+				if (hasCycleDynamic(child, seenPaths)) {
+					return true;
 				}
 				}
 			}
 			}
 		}
 		}
-		editMode = oldEditMode;
 		return false;
 		return false;
 	}
 	}
 
 
 	override function makeInteractive() {
 	override function makeInteractive() {
-		if( editMode )
+		if( editMode != None )
 			return null;
 			return null;
 		return super.makeInteractive();
 		return super.makeInteractive();
 	}
 	}
@@ -213,28 +362,29 @@ class Reference extends Object3D {
 			<div class="group" name="Reference">
 			<div class="group" name="Reference">
 			<dl>
 			<dl>
 				<dt>Reference</dt><dd><input type="fileselect" extensions="prefab l3d fx" field="source"/></dd>
 				<dt>Reference</dt><dd><input type="fileselect" extensions="prefab l3d fx" field="source"/></dd>
-				<dt>Edit</dt><dd><input type="checkbox" field="editMode"/></dd>
+				<dt>Edit</dt><dd><select field="editMode" class="monSelector"></select></dd>
+				<p class="warning">Warning : Edit mode enabled while there are override on this reference. Saving will cause the overrides to be applied to the original reference !</p>
 			</dl>
 			</dl>
 			</div>');
 			</div>');
 
 
+
+		var warning = element.find(".warning");
+
 		function updateProps() {
 		function updateProps() {
 			var input = element.find("input");
 			var input = element.find("input");
 			var found = resolveRef() != null;
 			var found = resolveRef() != null;
 			input.toggleClass("error", !found);
 			input.toggleClass("error", !found);
+			warning.toggle(overrides != null && editMode == Edit);
 		}
 		}
 		updateProps();
 		updateProps();
 
 
 		var props = ctx.properties.add(element, this, function(pname) {
 		var props = ctx.properties.add(element, this, function(pname) {
 			ctx.onChange(this, pname);
 			ctx.onChange(this, pname);
 			if(pname == "source" || pname == "editMode") {
 			if(pname == "source" || pname == "editMode") {
-				if (pname == "source") {
-					editorRemoveObjects();
-					refInstance = null;
-				}
 				if (hasCycle()) {
 				if (hasCycle()) {
 					hide.Ide.inst.quickError('Reference to $source would create a cycle. The reference change was aborted.');
 					hide.Ide.inst.quickError('Reference to $source would create a cycle. The reference change was aborted.');
-					ctx.properties.undo.undo();
-					@:privateAccess ctx.properties.undo.redoElts.pop();
+					source = null;
+					ctx.rebuildProperties();
 					return;
 					return;
 				}
 				}
 				updateProps();
 				updateProps();
@@ -257,6 +407,43 @@ class Reference extends Object3D {
 		});
 		});
 
 
 		super.edit(ctx);
 		super.edit(ctx);
+
+		var over = new hide.Element('
+			<div class="group">
+				<dl>
+					<dt>Overrides</dt><dd><p class="override-infos"></p><fancy-button><span class="label">Clear Overrides</span></fancy-button></dd>
+				</dl>
+			</div>
+		');
+
+		var overInfos = over.find(".override-infos");
+		function refreshOverrideInfos() {
+			if (computeDiffFromSource() == null) {
+				overInfos.text("No overrides");
+			}
+			else {
+				overInfos.text("This reference has overrides");
+			}
+		}
+		refreshOverrideInfos();
+
+		over.find("fancy-button").click((_) -> {
+			var old = overrides;
+			this.overrides = null;
+			var refresh = () -> {
+				if (originalSource != null) {
+					@:privateAccess shared.editor.removeInstance(refInstance, false);
+					originalSource = null;
+					refInstance = null;
+					ctx.rebuildPrefab(this);
+					refreshOverrideInfos();
+				}
+			};
+			@:privateAccess ctx.properties.undo.change(Field(this, "overrides", old), refresh);
+			refresh();
+			//ctx.rebuildPrefab(this);
+		});
+		ctx.properties.add(over);
 	}
 	}
 
 
 	override function getHideProps() : hide.prefab.HideProps {
 	override function getHideProps() : hide.prefab.HideProps {

+ 4 - 2
hrt/prefab/Shader.hx

@@ -203,8 +203,10 @@ class Shader extends Prefab {
 	#if editor
 	#if editor
 
 
 	override function editorRemoveInstanceObjects() : Void {
 	override function editorRemoveInstanceObjects() : Void {
-		shared.editor.queueRebuild(parent);
-		super.editorRemoveInstanceObjects();
+		if (shared?.editor != null) {
+			shared.editor.queueRebuild(parent);
+			super.editorRemoveInstanceObjects();
+		}
 	}
 	}
 
 
 	function getEditProps(shaderDef: hxsl.SharedShader) : Array<hrt.prefab.Props.PropDef> {
 	function getEditProps(shaderDef: hxsl.SharedShader) : Array<hrt.prefab.Props.PropDef> {

+ 1 - 1
hrt/tools/MapUtils.hx

@@ -3,7 +3,7 @@ import haxe.macro.Expr;
 
 
 class MapUtils {
 class MapUtils {
 	/**
 	/**
-		Returns map[key] if key if present, else execute def and puts it into map[key]
+		Returns `map[key]` if key if present, else evaluate `def` and puts the result into `map[key]`, then retunrs `map[key]`
 	**/
 	**/
 	macro public static function getOrPut<K, V>(map:ExprOf<Map<K, V>>, key:ExprOf<K>, def:ExprOf<V>):Expr {
 	macro public static function getOrPut<K, V>(map:ExprOf<Map<K, V>>, key:ExprOf<K>, def:ExprOf<V>):Expr {
 		return macro {
 		return macro {