package hide.comp; import hrt.prefab.Reference; import h3d.scene.Mesh; import h3d.col.FPoint; import h3d.col.Ray; import h3d.col.PolygonBuffer; import h3d.prim.HMDModel; import h3d.col.Collider.OptimizedCollider; import h3d.Vector; import hxd.Key as K; import hxd.Math as M; import hrt.prefab.Prefab as PrefabElement; import hrt.prefab.Object2D; import hrt.prefab.Object3D; import h3d.scene.Object; import hide.comp.cdb.DataFiles; import hide.view.CameraController.CamController as CameraController; enum SelectMode { /** Update tree, add undo command **/ Default; /** Update tree only **/ NoHistory; /** Add undo but don't update tree **/ NoTree; /** Don't refresh tree and don't undo command **/ Nothing; } @:access(hide.comp.SceneEditor) class SceneEditorContext extends hide.prefab.EditContext { public var editor(default, null) : SceneEditor; public var elements : Array; public var rootObjects(default, null): Array; public var rootObjects2D(default, null): Array; public var rootElements(default, null): Array; public function new(ctx, elts, editor) { super(ctx); this.editor = editor; this.updates = editor.updates; this.elements = elts; rootObjects = []; rootObjects2D = []; rootElements = []; cleanups = []; for(elt in elements) { // var obj3d = elt.to(Object3D); // if(obj3d == null) continue; if(!SceneEditor.hasParent(elt, elements)) { rootElements.push(elt); var ctx = getContext(elt); if(ctx != null) { var pobj = elt.parent == editor.sceneData ? ctx.shared.root3d : getContextRec(elt.parent).local3d; var pobj2d = elt.parent == editor.sceneData ? ctx.shared.root2d : getContextRec(elt.parent).local2d; if( ctx.local3d != pobj ) rootObjects.push(ctx.local3d); if( ctx.local2d != pobj2d ) rootObjects2D.push(ctx.local2d); } } } } override function screenToGround(x:Float, y:Float, ?forPrefab:hrt.prefab.Prefab) { return editor.screenToGround(x, y, forPrefab); } override function positionToGroundZ(x:Float, y:Float, ?forPrefab:hrt.prefab.Prefab):Float { return editor.getZ(x, y, forPrefab); } override function getCurrentProps( p : hrt.prefab.Prefab ) { var cur = editor.curEdit; return cur != null && cur.elements[0] == p ? editor.properties.element : new Element(); } function getContextRec( p : hrt.prefab.Prefab ) { if( p == null ) return editor.context; var c = editor.context.shared.contexts.get(p); if( c == null ) return getContextRec(p.parent); return c; } override function rebuildProperties() { editor.scene.setCurrent(); editor.selectElements(elements, NoHistory); } override function rebuildPrefab( p : hrt.prefab.Prefab ) { // refresh all for now editor.refresh(); } public function cleanup() { for( c in cleanups.copy() ) c(); cleanups = []; } override function onChange(p : PrefabElement, pname: String) { super.onChange(p, pname); editor.onPrefabChange(p, pname); } } enum RefreshMode { Partial; Full; } typedef CustomPivot = { elt : PrefabElement, mesh : Mesh, locPos : Vector }; class SceneEditor { public var tree : hide.comp.IconTree; public var scene : hide.comp.Scene; public var properties : hide.comp.PropsEditor; public var context(default,null) : hrt.prefab.Context; public var curEdit(default, null) : SceneEditorContext; public var snapToGround = false; public var localTransform = true; public var cameraController : h3d.scene.CameraController; public var cameraController2D : hide.view.l3d.CameraController2D; public var editorDisplay(default,set) : Bool; public var camera2D(default,set) : Bool = false; public var objectAreSelectable = true; // Windows default is 0.5 public var dblClickDuration = 0.2; var updates : Array Void> = []; var showGizmo = true; var gizmo : hide.view.l3d.Gizmo; var gizmo2d : hide.view.l3d.Gizmo2D; static var customPivot : CustomPivot; var interactives : Map; var ide : hide.Ide; public var event(default, null) : hxd.WaitEvent; var hideList : Map = new Map(); var undo(get, null):hide.ui.UndoHistory; function get_undo() { return view.undo; } public var view(default, null) : hide.view.FileView; var sceneData : PrefabElement; var lastRenderProps : hrt.prefab.RenderProps; var focusedSinceSelect = false; public function new(view, data) { ide = hide.Ide.inst; this.view = view; this.sceneData = data; event = new hxd.WaitEvent(); var propsEl = new Element('
'); properties = new hide.comp.PropsEditor(undo,null,propsEl); properties.saveDisplayKey = view.saveDisplayKey + "/properties"; tree = new hide.comp.IconTree(); tree.async = false; tree.autoOpenNodes = false; var sceneEl = new Element('
'); scene = new hide.comp.Scene(view.config, null, sceneEl); scene.editor = this; scene.onReady = onSceneReady; scene.onResize = function() { if( cameraController2D != null ) cameraController2D.toTarget(); onResize(); }; context = new hrt.prefab.Context(); context.shared = new hide.prefab.ContextShared(scene); context.shared.currentPath = view.state.path; context.init(); editorDisplay = true; view.keys.register("copy", onCopy); view.keys.register("paste", onPaste); view.keys.register("cancel", deselect); view.keys.register("selectAll", selectAll); view.keys.register("duplicate", duplicate.bind(true)); view.keys.register("duplicateInPlace", duplicate.bind(false)); view.keys.register("group", groupSelection); view.keys.register("delete", () -> deleteElements(curEdit.rootElements)); view.keys.register("search", function() tree.openFilter()); view.keys.register("rename", function () { if(curEdit.rootElements.length > 0) tree.editNode(curEdit.rootElements[0]); }); view.keys.register("sceneeditor.focus", focusSelection); view.keys.register("sceneeditor.lasso", startLassoSelect); view.keys.register("sceneeditor.hide", function() { var isHidden = isHidden(curEdit.rootElements[0]); setVisible(curEdit.elements, isHidden); }); view.keys.register("sceneeditor.isolate", function() { isolate(curEdit.elements); }); view.keys.register("sceneeditor.showAll", function() { setVisible(context.shared.elements(), true); }); view.keys.register("sceneeditor.selectParent", function() { if(curEdit.rootElements.length > 0) { var p = curEdit.rootElements[0].parent; if( p != null && p != sceneData ) selectElements([p]); } }); view.keys.register("sceneeditor.reparent", function() { if(curEdit.rootElements.length > 1) { var children = curEdit.rootElements.copy(); var parent = children.pop(); reparentElement(children, parent, 0); } }); view.keys.register("sceneeditor.editPivot", editPivot); view.keys.register("sceneeditor.gatherToMouse", gatherToMouse); // Load display state { var all = sceneData.flatten(hrt.prefab.Prefab); var list = @:privateAccess view.getDisplayState("hideList"); if(list != null) { var m = [for(i in (list:Array)) i => true]; for(p in all) { if(m.exists(p.getAbsPath(true))) hideList.set(p, true); } } } } public function dispose() { scene.dispose(); tree.dispose(); } function set_camera2D(b) { if( cameraController != null ) cameraController.visible = !b; if( cameraController2D != null ) cameraController2D.visible = b; return camera2D = b; } public function onResourceChanged(lib : hxd.fmt.hmd.Library) { var models = sceneData.findAll(p -> Std.downcast(p, PrefabElement)); var toRebuild : Array = []; for(m in models) { @:privateAccess if(m.source == lib.resource.entry.path) { if (toRebuild.indexOf(m) < 0) { toRebuild.push(m); } } } for(m in toRebuild) { removeInstance(m); makeInstance(m); } } public dynamic function onResize() { } function set_editorDisplay(v) { context.shared.editorDisplay = v; return editorDisplay = v; } public function getSelection() { return curEdit != null ? curEdit.elements : []; } function makeCamController() : h3d.scene.CameraController { var c = new CameraController(scene.s3d, this); c.friction = 0.9; c.panSpeed = 0.6; c.zoomAmount = 1.05; c.smooth = 0.7; c.minDistance = 1; return c; } public function setFullScreen( b : Bool ) { view.fullScreen = b; if( b ) { view.element.find(".tabs").hide(); } else { view.element.find(".tabs").show(); } } function makeCamController2D() { return new hide.view.l3d.CameraController2D(context.shared.root2d); } function focusSelection() { if(curEdit.rootObjects.length > 0) { var bnds = new h3d.col.Bounds(); var centroid = new h3d.Vector(); for(obj in curEdit.rootObjects) { centroid = centroid.add(obj.getAbsPos().getPosition()); bnds.add(obj.getBounds()); } if(!bnds.isEmpty()) { var s = bnds.toSphere(); var r = focusedSinceSelect ? s.r * 4.0 : null; cameraController.set(r, null, null, s.getCenter()); } else { centroid.scale3(1.0 / curEdit.rootObjects.length); cameraController.set(centroid.toPoint()); } } for(obj in curEdit.rootElements) tree.revealNode(obj); focusedSinceSelect = true; } function getAvailableTags(p: PrefabElement) : Array<{id: String, color: String}>{ return null; } public function getTag(p: PrefabElement) { if(p.props != null) { var tagId = Reflect.field(p.props, "tag"); if(tagId != null) { var tags = getAvailableTags(p); if(tags != null) return Lambda.find(tags, t -> t.id == tagId); } } return null; } public function setTag(p: PrefabElement, tag: String) { if(p.props == null) p.props = {}; var prevVal = getTag(p); Reflect.setField(p.props, "tag", tag); onPrefabChange(p, "tag"); undo.change(Field(p.props, "tag", prevVal), function() { onPrefabChange(p, "tag"); }); } function getTagMenu(p: PrefabElement) : Array { var tags = getAvailableTags(p); if(tags == null) return null; var ret = []; for(tag in tags) { var style = 'background-color: ${tag.color};'; ret.push({ label: '${tag.id}', click: function () { if(getTag(p) == tag) setTag(p, null); else setTag(p, tag.id); }, checked: getTag(p) == tag, stayOpen: true, }); } return ret; } function onSceneReady() { tree.saveDisplayKey = view.saveDisplayKey + '/tree'; scene.s2d.addChild(context.shared.root2d); scene.s3d.addChild(context.shared.root3d); gizmo = new hide.view.l3d.Gizmo(scene); gizmo.moveStep = view.config.get("sceneeditor.gridSnapStep"); gizmo2d = new hide.view.l3d.Gizmo2D(); scene.s2d.add(gizmo2d, 1); // over local3d cameraController = makeCamController(); cameraController.onClick = function(e) { switch( e.button ) { case K.MOUSE_RIGHT: selectNewObject(); case K.MOUSE_LEFT: selectElements([]); } }; if (!camera2D) resetCamera(); var cam = @:privateAccess view.getDisplayState("Camera"); if( cam != null ) { scene.s3d.camera.pos.set(cam.x, cam.y, cam.z); scene.s3d.camera.target.set(cam.tx, cam.ty, cam.tz); } cameraController.loadFromCamera(); scene.s2d.defaultSmooth = true; context.shared.root2d.x = scene.s2d.width >> 1; context.shared.root2d.y = scene.s2d.height >> 1; cameraController2D = makeCamController2D(); cameraController2D.onClick = cameraController.onClick; var cam2d = @:privateAccess view.getDisplayState("Camera2D"); if( cam2d != null ) { context.shared.root2d.x = scene.s2d.width*0.5 + cam2d.x; context.shared.root2d.y = scene.s2d.height*0.5 + cam2d.y; context.shared.root2d.setScale(cam2d.z); } cameraController2D.loadFromScene(); if (camera2D) resetCamera(); scene.onUpdate = update; // BUILD scene tree var icons = new Map(); var iconsConfig = view.config.get("sceneeditor.icons"); for( f in Reflect.fields(iconsConfig) ) icons.set(f, Reflect.field(iconsConfig,f)); function makeItem(o:PrefabElement, ?state) : hide.comp.IconTree.IconTreeItem { var p = o.getHideProps(); var ref = o.to(Reference); var icon = p.icon; var ct = o.getCdbType(); if( ct != null && icons.exists(ct) ) icon = icons.get(ct); var r : hide.comp.IconTree.IconTreeItem = { value : o, text : o.name, icon : "ico ico-"+icon, children : o.children.length > 0 || (ref != null && @:privateAccess ref.editMode), state: state }; return r; } tree.get = function(o:PrefabElement) { var objs = o == null ? sceneData.children : Lambda.array(o); if( o != null && o.getHideProps().hideChildren != null ) { var hideChildren = o.getHideProps().hideChildren; var visibleObjs = []; for( o in objs ) { if( hideChildren(o) ) continue; visibleObjs.push(o); } objs = visibleObjs; } var ref = o == null ? null : o.to(Reference); @:privateAccess if( ref != null && ref.editMode && ref.ref != null ) { for( c in ref.ref ) objs.push(c); } var out = [for( o in objs ) makeItem(o)]; return out; }; function ctxMenu(tree, e) { e.preventDefault(); var current = tree.getCurrentOver(); if(current != null && (curEdit == null || curEdit.elements.indexOf(current) < 0)) { selectElements([current]); } var newItems = getNewContextMenu(current); var menuItems : Array = [ { label : "New...", menu : newItems }, ]; var actionItems : Array = [ { label : "Rename", enabled : current != null, click : function() tree.editNode(current), keys : view.config.get("key.rename") }, { label : "Delete", enabled : current != null, click : function() deleteElements(curEdit.rootElements), keys : view.config.get("key.delete") }, { label : "Duplicate", enabled : current != null, click : duplicate.bind(false), keys : view.config.get("key.duplicateInPlace") }, ]; var isObj = current != null && (current.to(Object3D) != null || current.to(Object2D) != null); var isRef = isReference(current); if( current != null ) { menuItems.push({ label : "Enable", checked : current.enabled, stayOpen : true, click : function() setEnabled(curEdit.elements, !current.enabled) }); menuItems.push({ label : "Editor only", checked : current.editorOnly, stayOpen : true, click : function() setEditorOnly(curEdit.elements, !current.editorOnly) }); } if( isObj ) { menuItems = menuItems.concat([ { label : "Show in editor" , checked : !isHidden(current), stayOpen : true, click : function() setVisible(curEdit.elements, isHidden(current)), keys : view.config.get("key.sceneeditor.hide") }, { label : "Locked", checked : current.locked, stayOpen : true, click : function() { current.locked = !current.locked; setLock(curEdit.elements, current.locked); } }, { label : "Select all", click : selectAll, keys : view.config.get("key.selectAll") }, { label : "Select children", enabled : current != null, click : function() selectElements(current.flatten()) }, ]); if( !isRef ) actionItems = actionItems.concat([ { label : "Isolate", click : function() isolate(curEdit.elements), keys : view.config.get("key.sceneeditor.isolate") }, { label : "Group", enabled : curEdit != null && canGroupSelection(), click : groupSelection, keys : view.config.get("key.group") }, ]); } if( current != null ) { var menu = getTagMenu(current); if(menu != null) menuItems.push({ label : "Tag", menu: menu }); } menuItems.push({ isSeparator : true, label : "" }); new hide.comp.ContextMenu(menuItems.concat(actionItems)); }; tree.element.parent().contextmenu(ctxMenu.bind(tree)); tree.allowRename = true; tree.init(); tree.onClick = function(e, _) { selectElements(tree.getSelection(), NoTree); } tree.onDblClick = function(e) { focusSelection(); return true; } tree.onRename = function(e, name) { var oldName = e.name; e.name = name; undo.change(Field(e, "name", oldName), function() { tree.refresh(); refreshScene(); }); refreshScene(); return true; }; tree.onAllowMove = function(e, to) return checkAllowParent({cl : e.type, inf : e.getHideProps()}, to); // Batch tree.onMove, which is called for every node moved, causing problems with undo and refresh { var movetimer : haxe.Timer = null; var moved = []; tree.onMove = function(e, to, idx) { if(movetimer != null) { movetimer.stop(); } moved.push(e); movetimer = haxe.Timer.delay(function() { reparentElement(moved, to, idx); movetimer = null; moved = []; }, 50); } } tree.applyStyle = function(p, el) applyTreeStyle(p, el); selectElements([]); refresh(); this.camera2D = camera2D; } function checkAllowParent(prefabInf, prefabParent : PrefabElement) : Bool { if (prefabInf.inf.allowParent == null) if (prefabParent == null || prefabParent.getHideProps().allowChildren == null || (prefabParent.getHideProps().allowChildren != null && prefabParent.getHideProps().allowChildren(prefabInf.cl))) return true; else return false; if (prefabParent == null) if (prefabInf.inf.allowParent(sceneData)) return true; else return false; if ((prefabParent.getHideProps().allowChildren == null || prefabParent.getHideProps().allowChildren != null && prefabParent.getHideProps().allowChildren(prefabInf.cl)) && prefabInf.inf.allowParent(prefabParent)) return true; return false; }; public function refresh( ?mode: RefreshMode, ?callb: Void->Void) { if(mode == null || mode == Full) refreshScene(); refreshTree(callb); } public function collapseTree() { tree.collapseAll(); } function refreshTree( ?callb ) { tree.refresh(function() { var all = sceneData.flatten(hrt.prefab.Prefab); for(elt in all) { var el = tree.getElement(elt); if(el == null) continue; applyTreeStyle(elt, el); } if(callb != null) callb(); }); } function refreshProps() { selectElements(curEdit.elements, Nothing); } public function refreshScene() { var sh = context.shared; sh.root3d.remove(); sh.root2d.remove(); for( c in sh.contexts ) if( c != null && c.cleanup != null ) c.cleanup(); context.shared = sh = new hide.prefab.ContextShared(scene); sh.editorDisplay = editorDisplay; sh.currentPath = view.state.path; scene.s3d.addChild(sh.root3d); scene.s2d.addChild(sh.root2d); sh.root2d.addChild(cameraController2D); scene.setCurrent(); scene.onResize(); context.init(); sceneData.make(context); var bgcol = scene.engine.backgroundColor; scene.init(); scene.engine.backgroundColor = bgcol; refreshInteractives(); var all = sceneData.flatten(hrt.prefab.Prefab); for(elt in all) applySceneStyle(elt); if( lastRenderProps == null ) { var renderProps = getAllWithRefs(sceneData,hrt.prefab.RenderProps); for( r in renderProps ) if( @:privateAccess r.isDefault ) { lastRenderProps = r; break; } if( lastRenderProps == null ) lastRenderProps = renderProps[0]; } if( lastRenderProps != null ) lastRenderProps.applyProps(scene.s3d.renderer); else { var refPrefab = new Reference(); refPrefab.source = view.config.getLocal("scene.renderProps"); refPrefab.makeInstance(context); if( @:privateAccess refPrefab.ref != null ) { var renderProps = @:privateAccess refPrefab.ref.get(hrt.prefab.RenderProps); if( renderProps != null ) renderProps.applyProps(scene.s3d.renderer); } } onRefresh(); } function getAllWithRefs( p : hrt.prefab.Prefab, cl : Class, ?arr : Array ) : Array { if( arr == null ) arr = []; var v = p.to(cl); if( v != null ) arr.push(v); for( c in p.children ) getAllWithRefs(c, cl, arr); var ref = p.to(Reference); @:privateAccess if( ref != null && ref.ref != null ) getAllWithRefs(ref.ref, cl, arr); return arr; } public dynamic function onRefresh() { } function makeInteractive( elt : PrefabElement, ?shared : hrt.prefab.ContextShared ) { if( shared == null ) shared = context.shared; var ctx = shared.contexts[elt]; if( ctx == null ) return; var int = elt.makeInteractive(ctx); if( int != null ) { initInteractive(elt,cast int); if( isLocked(elt) ) toggleInteractive(elt, false); } var ref = Std.downcast(elt,Reference); @:privateAccess if( ref != null && ref.editMode ) { var ctx = shared.getRef(elt); for( p in ref.ref.flatten() ) makeInteractive(p, ctx); } } function toggleInteractive( e : PrefabElement, visible : Bool ) { var int = getInteractive(e); if( int == null ) return; var i2d = Std.downcast(int,h2d.Interactive); var i3d = Std.downcast(int,h3d.scene.Interactive); if( i2d != null ) i2d.visible = visible; if( i3d != null ) i3d.visible = visible; } function initInteractive( elt : PrefabElement, int : { dynamic function onPush(e:hxd.Event) : Void; dynamic function onMove(e:hxd.Event) : Void; dynamic function onRelease(e:hxd.Event) : Void; dynamic function onClick(e:hxd.Event) : Void; function handleEvent(e:hxd.Event) : Void; function preventClick() : Void; } ) { if( int == null ) return; var startDrag = null; var curDrag = null; var dragBtn = -1; var lastPush : Array = null; var i3d = Std.downcast(int, h3d.scene.Interactive); var i2d = Std.downcast(int, h2d.Interactive); var prevClickTime : Float = -1e20; int.onClick = function(e) { if(e.button == K.MOUSE_RIGHT) { var dist = hxd.Math.distance(scene.s2d.mouseX - lastPush[0], scene.s2d.mouseY - lastPush[1]); if( dist > 5 ) return; selectNewObject(); e.propagate = false; return; } } int.onPush = function(e) { if( e.button == K.MOUSE_MIDDLE ) return; startDrag = [scene.s2d.mouseX, scene.s2d.mouseY]; if( e.button == K.MOUSE_RIGHT ) lastPush = startDrag; dragBtn = e.button; if( e.button == K.MOUSE_LEFT ) { var elts = null; if(K.isDown(K.SHIFT)) { if(Type.getClass(elt.parent) == hrt.prefab.Object3D) elts = [elt.parent]; else elts = elt.parent.children; } else elts = [elt]; if(K.isDown(K.CTRL)) { var current = curEdit.elements.copy(); if(current.indexOf(elt) < 0) { for(e in elts) { if(current.indexOf(e) < 0) current.push(e); } } else { for(e in elts) current.remove(e); } selectElements(current); } else selectElements(elts); } // ensure we get onMove even if outside our interactive, allow fast click'n'drag if( e.button == K.MOUSE_LEFT ) { scene.sevents.startCapture(int.handleEvent); e.propagate = false; } }; int.onRelease = function(e) { if( e.button == K.MOUSE_MIDDLE ) return; startDrag = null; curDrag = null; dragBtn = -1; if( e.button == K.MOUSE_LEFT ) { scene.sevents.stopCapture(); e.propagate = false; var curTime = haxe.Timer.stamp(); if( curTime - prevClickTime < dblClickDuration && !(elt.getHideProps().isGround)) { focusSelection(); prevClickTime = -1e20; } else prevClickTime = curTime; } } int.onMove = function(e) { if(startDrag != null && hxd.Math.distance(startDrag[0] - scene.s2d.mouseX, startDrag[1] - scene.s2d.mouseY) > 5 ) { if(dragBtn == K.MOUSE_LEFT ) { if( i3d != null ) { moveGizmoToSelection(); gizmo.startMove(MoveXY); } if( i2d != null ) { moveGizmoToSelection(); gizmo2d.startMove(Pan); } } int.preventClick(); startDrag = null; } } interactives.set(elt,cast int); } function selectNewObject() { if( !objectAreSelectable ) return; var parentEl = sceneData; // for now always create at scene root, not `curEdit.rootElements[0];` var group = getParentGroup(parentEl); if( group != null ) parentEl = group; var originPt = getPickTransform(parentEl).getPosition(); var newItems = getNewContextMenu(parentEl, function(newElt) { var newObj3d = Std.downcast(newElt, Object3D); if(newObj3d != null) { var newPos = new h3d.Matrix(); newPos.identity(); newPos.setPosition(originPt); var invParent = getObject(parentEl).getAbsPos().clone(); invParent.invert(); newPos.multiply(newPos, invParent); newObj3d.setTransform(newPos); } var newObj2d = Std.downcast(newElt, Object2D); if( newObj2d != null ) { var pt = new h2d.col.Point(scene.s2d.mouseX, scene.s2d.mouseY); var l2d = getContext(parentEl).local2d; l2d.globalToLocal(pt); newObj2d.x = pt.x; newObj2d.y = pt.y; } }); var menuItems : Array = [ { label : "New...", menu : newItems }, { isSeparator : true, label : "" }, { label : "Gather here", click : gatherToMouse, enabled : (curEdit.rootElements.length > 0), keys : view.config.get("key.sceneeditor.gatherToMouse"), }, ]; new hide.comp.ContextMenu(menuItems); } public function refreshInteractive(elt : PrefabElement) { var int = interactives.get(elt); if(int != null) { var i3d = Std.downcast(int, h3d.scene.Interactive); if( i3d != null ) i3d.remove() else cast(int,h2d.Interactive).remove(); interactives.remove(elt); } makeInteractive(elt); } function refreshInteractives() { var contexts = context.shared.contexts; interactives = new Map(); var all = contexts.keys(); for(elt in all) { makeInteractive(elt); } } function setupGizmo() { if(curEdit == null) return; var posQuant = view.config.get("sceneeditor.xyzPrecision"); var scaleQuant = view.config.get("sceneeditor.scalePrecision"); var rotQuant = view.config.get("sceneeditor.rotatePrecision"); inline function quantize(x: Float, step: Float) { if(step > 0) { x = Math.round(x / step) * step; x = untyped parseFloat(x.toFixed(5)); // Snap to closest nicely displayed float :cold_sweat: } return x; } gizmo.onStartMove = function(mode) { var objects3d = [for(o in curEdit.rootElements) { var obj3d = o.to(hrt.prefab.Object3D); if(obj3d != null) obj3d; }]; var sceneObjs = [for(o in objects3d) getContext(o).local3d]; var pivotPt = getPivot(sceneObjs); var pivot = new h3d.Matrix(); pivot.initTranslation(pivotPt.x, pivotPt.y, pivotPt.z); var invPivot = pivot.clone(); invPivot.invert(); var localMats = [for(o in sceneObjs) { var m = worldMat(o); m.multiply(m, invPivot); m; }]; var prevState = [for(o in objects3d) o.saveTransform()]; gizmo.onMove = function(translate: h3d.Vector, rot: h3d.Quat, scale: h3d.Vector) { var transf = new h3d.Matrix(); transf.identity(); if(rot != null) rot.toMatrix(transf); if(translate != null) transf.translate(translate.x, translate.y, translate.z); for(i in 0...sceneObjs.length) { var newMat = localMats[i].clone(); newMat.multiply(newMat, transf); newMat.multiply(newMat, pivot); if(snapToGround && mode == MoveXY) { newMat.tz = getZ(newMat.tx, newMat.ty); } var invParent = sceneObjs[i].parent.getAbsPos().clone(); invParent.invert(); newMat.multiply(newMat, invParent); if(scale != null) { newMat.prependScale(scale.x, scale.y, scale.z); } var obj3d = objects3d[i]; var rot = newMat.getEulerAngles(); obj3d.x = quantize(newMat.tx, posQuant); obj3d.y = quantize(newMat.ty, posQuant); obj3d.z = quantize(newMat.tz, posQuant); obj3d.rotationX = quantize(M.radToDeg(rot.x), rotQuant); obj3d.rotationY = quantize(M.radToDeg(rot.y), rotQuant); obj3d.rotationZ = quantize(M.radToDeg(rot.z), rotQuant); if(scale != null) { inline function scaleSnap(x: Float) { if(K.isDown(K.CTRL)) { var step = K.isDown(K.SHIFT) ? 0.5 : 1.0; x = Math.round(x / step) * step; } return x; } var s = newMat.getScale(); obj3d.scaleX = quantize(scaleSnap(s.x), scaleQuant); obj3d.scaleY = quantize(scaleSnap(s.y), scaleQuant); obj3d.scaleZ = quantize(scaleSnap(s.z), scaleQuant); } obj3d.applyTransform(sceneObjs[i]); } } gizmo.onFinishMove = function() { var newState = [for(o in objects3d) o.saveTransform()]; refreshProps(); undo.change(Custom(function(undo) { if( undo ) { for(i in 0...objects3d.length) { objects3d[i].loadTransform(prevState[i]); objects3d[i].applyTransform(sceneObjs[i]); } refreshProps(); } else { for(i in 0...objects3d.length) { objects3d[i].loadTransform(newState[i]); objects3d[i].applyTransform(sceneObjs[i]); } refreshProps(); } for(o in objects3d) o.updateInstance(getContext(o)); })); for(o in objects3d) o.updateInstance(getContext(o)); } } gizmo2d.onStartMove = function(mode) { var objects2d = [for(o in curEdit.rootElements) { var obj = o.to(hrt.prefab.Object2D); if(obj != null) obj; }]; var sceneObjs = [for(o in objects2d) getContext(o).local2d]; var pivot = getPivot2D(sceneObjs); var center = pivot.getCenter(); var prevState = [for(o in objects2d) o.saveTransform()]; var startPos = [for(o in sceneObjs) o.getAbsPos()]; gizmo2d.onMove = function(t) { t.x = Math.round(t.x); t.y = Math.round(t.y); for(i in 0...sceneObjs.length) { var pos = startPos[i].clone(); var obj = objects2d[i]; switch( mode ) { case Pan: pos.x += t.x; pos.y += t.y; case ScaleX, ScaleY, Scale: // no inherited rotation if( pos.b == 0 && pos.c == 0 ) { pos.x -= center.x; pos.y -= center.y; pos.x *= t.scaleX; pos.y *= t.scaleY; pos.x += center.x; pos.y += center.y; obj.scaleX = quantize(t.scaleX * prevState[i].scaleX, scaleQuant); obj.scaleY = quantize(t.scaleY * prevState[i].scaleY, scaleQuant); } else { var m2 = new h2d.col.Matrix(); m2.initScale(t.scaleX, t.scaleY); pos.x -= center.x; pos.y -= center.y; pos.multiply(pos,m2); pos.x += center.x; pos.y += center.y; var s = pos.getScale(); obj.scaleX = quantize(s.x, scaleQuant); obj.scaleY = quantize(s.y, scaleQuant); } case Rotation: pos.x -= center.x; pos.y -= center.y; var ca = Math.cos(t.rotation); var sa = Math.sin(t.rotation); var px = pos.x * ca - pos.y * sa; var py = pos.x * sa + pos.y * ca; pos.x = px + center.x; pos.y = py + center.y; var r = M.degToRad(prevState[i].rotation) + t.rotation; r = quantize(M.radToDeg(r), rotQuant); obj.rotation = r; } var pt = pos.getPosition(); sceneObjs[i].parent.globalToLocal(pt); obj.x = quantize(pt.x, posQuant); obj.y = quantize(pt.y, posQuant); obj.applyTransform(sceneObjs[i]); } }; gizmo2d.onFinishMove = function() { var newState = [for(o in objects2d) o.saveTransform()]; refreshProps(); undo.change(Custom(function(undo) { if( undo ) { for(i in 0...objects2d.length) { objects2d[i].loadTransform(prevState[i]); objects2d[i].applyTransform(sceneObjs[i]); } refreshProps(); } else { for(i in 0...objects2d.length) { objects2d[i].loadTransform(newState[i]); objects2d[i].applyTransform(sceneObjs[i]); } refreshProps(); } for(o in objects2d) o.updateInstance(getContext(o)); })); for(o in objects2d) o.updateInstance(getContext(o)); }; }; } function moveGizmoToSelection() { // Snap Gizmo at center of objects gizmo.getRotationQuat().identity(); if(curEdit != null && curEdit.rootObjects.length > 0) { var pos = getPivot(curEdit.rootObjects); gizmo.visible = showGizmo; gizmo.setPosition(pos.x, pos.y, pos.z); if(curEdit.rootObjects.length == 1 && (localTransform || K.isDown(K.ALT))) { var obj = curEdit.rootObjects[0]; var mat = worldMat(obj); var s = mat.getScale(); if(s.x != 0 && s.y != 0 && s.z != 0) { mat.prependScale(1.0 / s.x, 1.0 / s.y, 1.0 / s.z); gizmo.getRotationQuat().initRotateMatrix(mat); } } } else { gizmo.visible = false; } if( curEdit != null && curEdit.rootObjects2D.length > 0 && !gizmo.visible ) { var pos = getPivot2D(curEdit.rootObjects2D); gizmo2d.visible = showGizmo; gizmo2d.setPosition(pos.getCenter().x, pos.getCenter().y); gizmo2d.setSize(pos.width, pos.height); } else { gizmo2d.visible = false; } } var inLassoMode = false; function startLassoSelect() { if(inLassoMode) { inLassoMode = false; return; } scene.setCurrent(); inLassoMode = true; var g = new h2d.Object(scene.s2d); var overlay = new h2d.Bitmap(h2d.Tile.fromColor(0xffffff, 10000, 10000, 0.1), g); var intOverlay = new h2d.Interactive(10000, 10000, scene.s2d); var lastPt = new h2d.col.Point(scene.s2d.mouseX, scene.s2d.mouseY); var points : h2d.col.Polygon = [lastPt]; var polyG = new h2d.Graphics(g); event.waitUntil(function(dt) { var curPt = new h2d.col.Point(scene.s2d.mouseX, scene.s2d.mouseY); if(curPt.distance(lastPt) > 3.0) { points.push(curPt); polyG.clear(); polyG.beginFill(0xff0000, 0.5); polyG.lineStyle(1.0, 0, 1.0); polyG.moveTo(points[0].x, points[0].y); for(i in 1...points.length) { polyG.lineTo(points[i].x, points[i].y); } polyG.endFill(); lastPt = curPt; } var finish = false; if(!inLassoMode || K.isDown(K.ESCAPE) || K.isDown(K.MOUSE_RIGHT)) { finish = true; } if(K.isDown(K.MOUSE_LEFT)) { var contexts = context.shared.contexts; var all = getAllSelectable3D(); var inside = []; for(elt in all) { if(elt.to(Object3D) == null) continue; var ctx = contexts[elt]; var o = ctx.local3d; if(o == null || !o.visible) continue; var absPos = o.getAbsPos(); var screenPos = worldToScreen(absPos.tx, absPos.ty, absPos.tz); if(points.contains(screenPos, false)) { inside.push(elt); } } selectElements(inside); finish = true; } if(finish) { intOverlay.remove(); g.remove(); inLassoMode = false; return true; } return false; }); } public function onPrefabChange(p: PrefabElement, ?pname: String) { var model = p.to(hrt.prefab.Model); if(model != null && pname == "source") { refreshScene(); return; } if(p != sceneData) { var el = tree.getElement(p); if( el != null && el.toggleClass != null ) applyTreeStyle(p, el, pname); } applySceneStyle(p); } public function applyTreeStyle(p: PrefabElement, el: Element, ?pname: String) { var obj3d = p.to(Object3D); el.toggleClass("disabled", !p.enabled); var aEl = el.find("a").first(); var tag = getTag(p); if(tag != null) { aEl.css("background", tag.color); el.find("ul").first().css("background", tag.color + "80"); } else if(pname == "tag") { aEl.css("background", ""); el.find("ul").first().css("background", ""); } if(obj3d != null) { el.toggleClass("disabled", !p.enabled || !obj3d.visible); el.toggleClass("hidden", isHidden(obj3d)); el.toggleClass("locked", p.locked); el.toggleClass("editorOnly", p.editorOnly); var visTog = el.find(".visibility-toggle").first(); if(visTog.length == 0) { visTog = new Element('').insertAfter(el.find("a.jstree-anchor").first()); visTog.click(function(e) { if(curEdit.elements.indexOf(obj3d) >= 0) setVisible(curEdit.elements, isHidden(obj3d)); else setVisible([obj3d], isHidden(obj3d)); e.preventDefault(); e.stopPropagation(); }); visTog.dblclick(function(e) { e.preventDefault(); e.stopPropagation(); }); } var lockTog = el.find(".lock-toggle").first(); if(lockTog.length == 0) { lockTog = new Element('').insertAfter(el.find("a.jstree-anchor").first()); lockTog.click(function(e) { if(curEdit.elements.indexOf(obj3d) >= 0) setLock(curEdit.elements, !obj3d.locked); else setLock([obj3d], !obj3d.locked); e.preventDefault(); e.stopPropagation(); }); lockTog.dblclick(function(e) { e.preventDefault(); e.stopPropagation(); }); } lockTog.css({visibility: p.locked ? "visible" : "hidden"}); } } public function applySceneStyle(p: PrefabElement) { var obj3d = p.to(Object3D); if(obj3d != null) { var visible = obj3d.visible && !isHidden(obj3d); for(ctx in getContexts(obj3d)) { ctx.local3d.visible = visible; } } } public function getInteractives(elt : PrefabElement) { var r = [getInteractive(elt)]; for(c in elt.children) { r = r.concat(getInteractives(c)); } return r; } public function getInteractive(elt: PrefabElement) { return interactives.get(elt); } public function getContext(elt : PrefabElement, ?shared : hrt.prefab.ContextShared) { if(elt == null) return null; if( shared == null ) shared = context.shared; var ctx = shared.contexts.get(elt); if( ctx == null ) { for( r in @:privateAccess shared.refsContexts ) { ctx = getContext(elt, r); if( ctx != null ) break; } } if( ctx == null && elt == sceneData ) ctx = context; return ctx; } public function getContexts(elt: PrefabElement) { if(elt == null) return null; return context.shared.getContexts(elt); } public function getObject(elt: PrefabElement) { var ctx = getContext(elt); if(ctx != null) return ctx.local3d; return context.shared.root3d; } public function getSelfObject(elt: PrefabElement) { var ctx = getContext(elt); var parentCtx = getContext(elt.parent); if(ctx == null || parentCtx == null) return null; if(ctx.local3d != parentCtx.local3d) return ctx.local3d; return null; } function removeInstance(elt : PrefabElement) { var result = true; var contexts = context.shared.contexts; function recRemove(e: PrefabElement) { for(c in e.children) recRemove(c); var int = interactives.get(e); if(int != null) { var i3d = Std.downcast(int, h3d.scene.Interactive); if( i3d != null ) i3d.remove() else cast(int,h2d.Interactive).remove(); interactives.remove(e); } for(ctx in getContexts(e)) { if(!e.removeInstance(ctx)) result = false; contexts.remove(e); } } recRemove(elt); return result; } function makeInstance(elt: PrefabElement) { scene.setCurrent(); var p = elt.parent; var parentCtx = null; while( p != null ) { parentCtx = getContext(p); if( parentCtx != null ) break; p = p.parent; } var ctx = elt.make(parentCtx); for( p in elt.flatten() ) makeInteractive(p); scene.init(ctx.local3d); } function refreshParents( elts : Array ) { var parents = new Map(); for( e in elts ) { if( e.parent == null ) throw e+" is missing parent"; parents.set(e.parent, true); } for( p in parents.keys() ) { var h = p.getHideProps(); if( h.onChildListChanged != null ) h.onChildListChanged(); } if( lastRenderProps != null && parents.exists(lastRenderProps) ) lastRenderProps.applyProps(scene.s3d.renderer); } public function addElements(elts : Array, selectObj : Bool = true, doRefresh : Bool = true, enableUndo = true) { for (e in elts) { makeInstance(e); } if (doRefresh) { refresh(Partial, if (selectObj) () -> selectElements(elts, NoHistory) else null); refreshParents(elts); } if( !enableUndo ) return; undo.change(Custom(function(undo) { var fullRefresh = false; if(undo) { selectElements([], NoHistory); for (e in elts) { if(!removeInstance(e)) fullRefresh = true; e.parent.children.remove(e); } refresh(fullRefresh ? Full : Partial); } else { for (e in elts) { e.parent.children.push(e); makeInstance(e); } refresh(Partial, () -> selectElements(elts,NoHistory)); refreshParents(elts); } })); } function makeCdbProps( e : PrefabElement, type : cdb.Sheet ) { var props = type.getDefaults(); Reflect.setField(props, "$cdbtype", DataFiles.getTypeName(type)); if( type.idCol != null && !type.idCol.opt ) { var id = new haxe.io.Path(view.state.path).file; id = id.charAt(0).toUpperCase() + id.substr(1); id += "_"+e.name; Reflect.setField(props, type.idCol.name, id); } return props; } function fillProps( edit, e : PrefabElement ) { e.edit(edit); var typeName = e.getCdbType(); if( typeName == null && e.props != null ) return; // don't allow CDB data with props already used ! var types = DataFiles.getAvailableTypes(); if( types.length == 0 ) return; var group = new hide.Element('
Type
'); var cdbLarge = @:privateAccess view.getDisplayState("cdbLarge"); var detachable = new DetachablePanel(); detachable.saveDisplayKey = "detachedCdb"; group.find(".btn-cdb-large").click((_) -> { cdbLarge = !cdbLarge; @:privateAccess view.saveDisplayState("cdbLarge", cdbLarge); group.toggleClass("cdb-large", cdbLarge); detachable.setDetached(cdbLarge); }); group.toggleClass("cdb-large", cdbLarge == true); detachable.setDetached(cdbLarge == true); var select = group.find("select"); for(t in types) { var id = DataFiles.getTypeName(t); new hide.Element("