瀏覽代碼

Merge branch 'master' of https://github.com/heapsio/hide

clandrin 4 年之前
父節點
當前提交
ed743d5d2b
共有 8 個文件被更改,包括 245 次插入219 次删除
  1. 5 0
      hide/Ide.hx
  2. 2 2
      hide/comp/Scene.hx
  3. 19 34
      hide/comp/SceneEditor.hx
  4. 1 1
      hide/comp/TileSelector.hx
  5. 3 3
      hide/comp/cdb/Cell.hx
  6. 2 0
      hide/view/l3d/Level3D.hx
  7. 27 69
      hrt/prefab/Reference.hx
  8. 186 110
      hrt/prefab/l3d/MeshSpray.hx

+ 5 - 0
hide/Ide.hx

@@ -823,6 +823,11 @@ class Ide {
 		return path;
 		return path;
 	}
 	}
 
 
+	public function getUnCachedUrl( path : String ) {
+		var timestamp = Std.int(Date.now().getTime() / 1000);
+		return "file://" + getPath(path) + "?t=" + timestamp;
+	}
+
 	public static var IMG_EXTS = ["jpg", "jpeg", "gif", "png", "raw", "dds", "hdr", "tga"];
 	public static var IMG_EXTS = ["jpg", "jpeg", "gif", "png", "raw", "dds", "hdr", "tga"];
 	public function chooseImage( onSelect, allowNull=false ) {
 	public function chooseImage( onSelect, allowNull=false ) {
 		chooseFile(IMG_EXTS, onSelect, allowNull);
 		chooseFile(IMG_EXTS, onSelect, allowNull);

+ 2 - 2
hide/comp/Scene.hx

@@ -258,7 +258,7 @@ class Scene extends Component implements h3d.IDrawable {
 
 
 	function _loadTextureData( img : hxd.res.Image, onReady : Void -> Void, t : h3d.mat.Texture ) {
 	function _loadTextureData( img : hxd.res.Image, onReady : Void -> Void, t : h3d.mat.Texture ) {
 		var path = ide.getPath(img.entry.path);
 		var path = ide.getPath(img.entry.path);
-		var img = new Element('<img src="file://$path" crossorigin="anonymous"/>');
+		var img = new Element('<img src="${ide.getUnCachedUrl(path)}" crossorigin="anonymous"/>');
 		function onLoaded() {
 		function onLoaded() {
 			if( engine.driver == null ) return;
 			if( engine.driver == null ) return;
 			setCurrent();
 			setCurrent();
@@ -280,7 +280,7 @@ class Scene extends Component implements h3d.IDrawable {
 		}
 		}
 		img.on("load", onLoaded);
 		img.on("load", onLoaded);
 		function onChange() {
 		function onChange() {
-			img.attr("src", 'file://$path?t=' + Std.int(Date.now().getTime() / 1000));
+			img.attr("src", ide.getUnCachedUrl(path));
 		}
 		}
 		ide.fileWatcher.register( path, onChange, true, element );
 		ide.fileWatcher.register( path, onChange, true, element );
 		cleanup.push(function() { ide.fileWatcher.unregister( path, onChange ); });
 		cleanup.push(function() { ide.fileWatcher.unregister( path, onChange ); });

+ 19 - 34
hide/comp/SceneEditor.hx

@@ -516,11 +516,10 @@ class SceneEditor {
 				{ label : "Rename", enabled : current != null, click : function() tree.editNode(current) },
 				{ label : "Rename", enabled : current != null, click : function() tree.editNode(current) },
 				{ label : "Delete", enabled : current != null, click : function() deleteElements(curEdit.rootElements) },
 				{ label : "Delete", enabled : current != null, click : function() deleteElements(curEdit.rootElements) },
 				{ label : "Duplicate", enabled : current != null, click : duplicate.bind(false) },
 				{ label : "Duplicate", enabled : current != null, click : duplicate.bind(false) },
-				{ label : "Reference", enabled : current != null, click : function() createRef(current, current.parent) },
 			];
 			];
 
 
 			var isObj = current != null && (current.to(Object3D) != null || current.to(Object2D) != null);
 			var isObj = current != null && (current.to(Object3D) != null || current.to(Object2D) != null);
-			var isRef = current != null && current.to(hrt.prefab.Reference) != null;
+			var isRef = isReference(current);
 
 
 			if( isObj ) {
 			if( isObj ) {
 				var visible = !isHidden(current);
 				var visible = !isHidden(current);
@@ -1828,7 +1827,7 @@ class SceneEditor {
 
 
 	public function setEnabled(elements : Array<PrefabElement>, enable: Bool) {
 	public function setEnabled(elements : Array<PrefabElement>, enable: Bool) {
 		// Don't disable/enable Object3Ds, too confusing with visibility
 		// Don't disable/enable Object3Ds, too confusing with visibility
-		elements = [for(e in elements) if(e.to(Object3D) == null || e.to(hrt.prefab.Reference) != null) e];
+		elements = [for(e in elements) if(e.to(Object3D) == null || isReference(e)) e];
 		var old = [for(e in elements) e.enabled];
 		var old = [for(e in elements) e.enabled];
 		function apply(on) {
 		function apply(on) {
 			for(i in 0...elements.length) {
 			for(i in 0...elements.length) {
@@ -1900,7 +1899,7 @@ class SceneEditor {
 		saveDisplayState();
 		saveDisplayState();
 	}
 	}
 
 
-	function setLock(elements : Array<PrefabElement>, locked: Bool) {
+	public function setLock(elements : Array<PrefabElement>, locked: Bool, enableUndo : Bool = true) {
 		var prev = [for( o in elements ) o.locked];
 		var prev = [for( o in elements ) o.locked];
 		for(o in elements) {
 		for(o in elements) {
 			o.locked = locked;
 			o.locked = locked;
@@ -1911,13 +1910,16 @@ class SceneEditor {
 				toggleInteractive(c,!isLocked(c));
 				toggleInteractive(c,!isLocked(c));
 			}
 			}
 		}
 		}
-		undo.change(Custom(function(redo) {
-			for( i in 0...elements.length )
-				elements[i].locked = redo ? locked : prev[i];
-		}), function() {
-			tree.refresh();
-			refreshScene();
-		});
+		if (enableUndo) {
+			undo.change(Custom(function(redo) {
+				for( i in 0...elements.length )
+					elements[i].locked = redo ? locked : prev[i];
+			}), function() {
+				tree.refresh();
+				refreshScene();
+			});
+		}
+		
 		saveDisplayState();
 		saveDisplayState();
 		showGizmo = !locked;
 		showGizmo = !locked;
 		moveGizmoToSelection();
 		moveGizmoToSelection();
@@ -1944,25 +1946,6 @@ class SceneEditor {
 		setVisible(toHide, false);
 		setVisible(toHide, false);
 	}
 	}
 
 
-	function createRef(elt: PrefabElement, toParent: PrefabElement) {
-		var ref = new hrt.prefab.Reference(toParent);
-		ref.name = elt.name;
-		ref.source = "/"+elt.getAbsPath();
-		var obj3d = Std.downcast(elt, Object3D);
-		if(obj3d != null) {
-			ref.x = obj3d.x;
-			ref.y = obj3d.y;
-			ref.z = obj3d.z;
-			ref.scaleX = obj3d.scaleX;
-			ref.scaleY = obj3d.scaleY;
-			ref.scaleZ = obj3d.scaleZ;
-			ref.rotationX = obj3d.rotationX;
-			ref.rotationY = obj3d.rotationY;
-			ref.rotationZ = obj3d.rotationZ;
-		}
-		addElements([ref]);
-	}
-
 	function duplicate(thenMove: Bool) {
 	function duplicate(thenMove: Bool) {
 		if(curEdit == null) return;
 		if(curEdit == null) return;
 		var elements = curEdit.rootElements;
 		var elements = curEdit.rootElements;
@@ -2078,10 +2061,8 @@ class SceneEditor {
 		if( to == null )
 		if( to == null )
 			to = sceneData;
 			to = sceneData;
 
 
-		{
-			var ref = Std.downcast(to, Reference);
-			@:privateAccess if( ref != null && ref.editMode ) to = ref.ref;
-		}
+		var ref = Std.downcast(to, Reference);
+		@:privateAccess if( ref != null && ref.editMode ) to = ref.ref;
 
 
 		var effectFunc = reparentImpl(e, to, index);
 		var effectFunc = reparentImpl(e, to, index);
 		undo.change(Custom(function(undo) {
 		undo.change(Custom(function(undo) {
@@ -2589,6 +2570,10 @@ class SceneEditor {
 		return bestVertex;
 		return bestVertex;
 	}
 	}
 
 
+	static function isReference( what : PrefabElement ) : Bool {
+		return what != null && what.to(hrt.prefab.Reference) != null;
+	}
+
 	static function getPivot(objects: Array<Object>) {
 	static function getPivot(objects: Array<Object>) {
 		if (customPivot != null) {
 		if (customPivot != null) {
 			return customPivot.mesh.localToGlobal(customPivot.locPos.toPoint());
 			return customPivot.mesh.localToGlobal(customPivot.locPos.toPoint());

+ 1 - 1
hide/comp/TileSelector.hx

@@ -113,7 +113,7 @@ class TileSelector extends Component {
 		if( tool.element.children().length == 0 )
 		if( tool.element.children().length == 0 )
 			tool.remove();
 			tool.remove();
 
 
-		var url = "file://" + ide.getPath(file);
+		var url = ide.getUnCachedUrl(file);
 		var scroll = new Element("<div class='flex-scroll'><div class='scroll'>").appendTo(element).find(".scroll");
 		var scroll = new Element("<div class='flex-scroll'><div class='scroll'>").appendTo(element).find(".scroll");
 		image = new Element('<div class="tile" style="background-image:url(\'$url\')"></div>').appendTo(scroll);
 		image = new Element('<div class="tile" style="background-image:url(\'$url\')"></div>').appendTo(scroll);
 
 

+ 3 - 3
hide/comp/cdb/Cell.hx

@@ -455,12 +455,12 @@ class Cell extends Component {
 		if( tiles.length == 0 ) return;
 		if( tiles.length == 0 ) return;
 		tiles.removeClass("toload");
 		tiles.removeClass("toload");
 		var imap = new Map();
 		var imap = new Map();
-		var timestamp = Std.int(Date.now().getTime() / 1000);
 		for( t in tiles )
 		for( t in tiles )
 			imap.set(t.getAttribute("path"), t);
 			imap.set(t.getAttribute("path"), t);
 		for( path => elt in imap ) {
 		for( path => elt in imap ) {
 			var img = js.Browser.document.createImageElement();
 			var img = js.Browser.document.createImageElement();
-			img.src = "file://"+path;
+			var url = Ide.inst.getUnCachedUrl(path);
+			img.src = url;
 			img.setAttribute("style","display:none");
 			img.setAttribute("style","display:none");
 			img.onload = function() {
 			img.onload = function() {
 				var iwidth = img.width;
 				var iwidth = img.width;
@@ -473,7 +473,7 @@ class Cell extends Component {
 						var zoom = Std.parseFloat(pos[2]);
 						var zoom = Std.parseFloat(pos[2]);
 						var bgw = Std.int(iwidth*zoom);
 						var bgw = Std.int(iwidth*zoom);
 						var bgh = Std.int(iheight*zoom);
 						var bgh = Std.int(iheight*zoom);
-						var bg = 'url("$path?t=$timestamp") -${px}px -${py}px / ${bgw}px ${bgh}px';
+						var bg = 'url("$url") -${px}px -${py}px / ${bgw}px ${bgh}px';
 						if( zoom > 1 )
 						if( zoom > 1 )
 							bg += ";image-rendering:pixelated";
 							bg += ";image-rendering:pixelated";
 						t.setAttribute("style", t.getAttribute("style")+" background : "+bg+"; opacity : 1;");
 						t.setAttribute("style", t.getAttribute("style")+" background : "+bg+"; opacity : 1;");

+ 2 - 0
hide/view/l3d/Level3D.hx

@@ -80,6 +80,8 @@ class CamController extends h3d.scene.CameraController {
 				pushY = e.relY;
 				pushY = e.relY;
 			default:
 			default:
 			}
 			}
+		case EFocus:
+			@:privateAccess scene.window.mouseLock = false;
 		default:
 		default:
 		}
 		}
 	}
 	}

+ 27 - 69
hrt/prefab/Reference.hx

@@ -19,17 +19,10 @@ class Reference extends Object3D {
 		}
 		}
 	}
 	}
 
 
-	public function isFile() {
-		return source != null && source.charCodeAt(0) != "/".code;
-	}
-
 	override function save() {
 	override function save() {
-		// Recalc abs path if ref has been resolved to supprot renaming
-		if( ref != null && !isFile() )
-			source = "/"+ref.getAbsPath();
 		var obj : Dynamic = super.save();
 		var obj : Dynamic = super.save();
 		#if editor
 		#if editor
-		if( editMode && isFile() && ref != null )
+		if( editMode && ref != null )
 			hide.Ide.inst.savePrefab(source, ref);
 			hide.Ide.inst.savePrefab(source, ref);
 		#end
 		#end
 		return obj;
 		return obj;
@@ -40,32 +33,16 @@ class Reference extends Object3D {
 			return ref;
 			return ref;
 		if(source == null)
 		if(source == null)
 			return null;
 			return null;
-		if(isFile()) {
-			if(shared == null) { // Allow resolving ref in Hide prefore makeInstance
-				#if editor
-				ref = hide.Ide.inst.loadPrefab(source, null, true);
-				#else
-				return null;
-				#end
-			}
-			else
-				ref = shared.loadPrefab(source);
-			return ref;
-		}
-		else {
-			var lib = getParent(hrt.prefab.Library);
-			if(lib == null)
-				return null;
-			var all = lib.getAll(Prefab);
-			var path = source.substr(1);
-			for(p in all) {
-				if(!Std.is(p, Reference) && p.getAbsPath() == path) {
-					ref = p;
-					return ref;
-				}
-			}
+		if(shared == null) { // Allow resolving ref in Hide prefore makeInstance
+			#if editor
+			ref = hide.Ide.inst.loadPrefab(source, null, true);
+			#else
+			return null;
+			#end
 		}
 		}
-		return null;
+		else
+			ref = shared.loadPrefab(source);
+		return ref;
 	}
 	}
 
 
 	override function updateInstance( ctx: Context, ?propName : String ) {
 	override function updateInstance( ctx: Context, ?propName : String ) {
@@ -108,33 +85,23 @@ class Reference extends Object3D {
 		if(p == null)
 		if(p == null)
 			return ctx;
 			return ctx;
 
 
-		if(isFile()) {
-			ctx = super.makeInstance(ctx);
-			var prevShared = ctx.shared;
-			ctx.shared = ctx.shared.cloneRef(this, source);
-			makeChildren(ctx, p);
-			ctx.shared = prevShared;
+		ctx = super.makeInstance(ctx);
+		var prevShared = ctx.shared;
+		ctx.shared = ctx.shared.cloneRef(this, source);
+		makeChildren(ctx, p);
+		ctx.shared = prevShared;
 
 
-			#if editor
-			if (ctx.local2d == null) {
-				var path = hide.Ide.inst.appPath + "/res/icons/fileRef.png";
-				var data = sys.io.File.getBytes(path);
-				var tile = hxd.res.Any.fromBytes(path, data).toTile().center();
-				var objFollow = new h2d.ObjectFollower(ctx.local3d, ctx.shared.root2d);
-				objFollow.followVisibility = true;
-				var bmp = new h2d.Bitmap(tile, objFollow);
-				ctx.local2d = objFollow;
-			}
-			#end
-
-		}
-		else {
-			ctx = ctx.clone(this);
-			ctx.isSceneReference = true;
-			var refCtx = p.make(ctx); // no customMake here
-			ctx.local3d = refCtx.local3d;
-			updateInstance(ctx);
+		#if editor
+		if (ctx.local2d == null) {
+			var path = hide.Ide.inst.appPath + "/res/icons/fileRef.png";
+			var data = sys.io.File.getBytes(path);
+			var tile = hxd.res.Any.fromBytes(path, data).toTile().center();
+			var objFollow = new h2d.ObjectFollower(ctx.local3d, ctx.shared.root2d);
+			objFollow.followVisibility = true;
+			var bmp = new h2d.Bitmap(tile, objFollow);
+			ctx.local2d = objFollow;
 		}
 		}
+		#end
 
 
 		return ctx;
 		return ctx;
 	}
 	}
@@ -169,7 +136,7 @@ class Reference extends Object3D {
 		var element = new hide.Element('
 		var element = new hide.Element('
 			<div class="group" name="Reference">
 			<div class="group" name="Reference">
 			<dl>
 			<dl>
-				<dt>Reference</dt><dd><input type="text" field="source"/></dd>
+				<dt>Reference</dt><dd><input type="fileselect" extensions="prefab l3d" field="source"/></dd>
 				<dt>Edit</dt><dd><input type="checkbox" field="editMode"/></dd>
 				<dt>Edit</dt><dd><input type="checkbox" field="editMode"/></dd>
 			</dl>
 			</dl>
 			</div>');
 			</div>');
@@ -181,18 +148,9 @@ class Reference extends Object3D {
 		}
 		}
 		updateProps();
 		updateProps();
 
 
-		element.find("input").contextmenu((e) -> {
-			e.preventDefault();
-			if( isFile() ) {
-				new hide.comp.ContextMenu([
-					{ label : "Open", click : () -> ctx.ide.openFile(ctx.ide.getPath(source)) },
-				]);
-			}
-		});
-
 		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") {
 				ref = null;
 				ref = null;
 				updateProps();
 				updateProps();
 				if(!ctx.properties.isTempChange)
 				if(!ctx.properties.isTempChange)

+ 186 - 110
hrt/prefab/l3d/MeshSpray.hx

@@ -4,7 +4,7 @@ package hrt.prefab.l3d;
 
 
 class MeshSprayObject extends h3d.scene.Object {
 class MeshSprayObject extends h3d.scene.Object {
 	public var batches : Array<h3d.scene.MeshBatch> = [];
 	public var batches : Array<h3d.scene.MeshBatch> = [];
-	public var batchesMap : Map<String,{ batch : h3d.scene.MeshBatch, pivot : h3d.Matrix }> = [];
+	public var batchesMap : Map<Int,Map<String,{ batch : h3d.scene.MeshBatch, pivot : h3d.Matrix }>> = [];
 	override function getMaterials( ?arr : Array<h3d.mat.Material>, recursive=true ) {
 	override function getMaterials( ?arr : Array<h3d.mat.Material>, recursive=true ) {
 		if( !recursive ) {
 		if( !recursive ) {
 			// batches materials are considered local materials
 			// batches materials are considered local materials
@@ -18,18 +18,42 @@ class MeshSprayObject extends h3d.scene.Object {
 
 
 class MeshSpray extends Object3D {
 class MeshSpray extends Object3D {
 
 
+	@:s var split : Int;
+
+	inline function getSplitID( c : Prefab ) {
+		var c = c.to(Object3D);
+		return (Math.floor(c.x/split) * 39119 + Math.floor(c.y/split)) % 0x7FFFFFFF;
+	}
+
 	override function createObject( ctx : Context ) {
 	override function createObject( ctx : Context ) {
 		var mspray = new MeshSprayObject(ctx.local3d);
 		var mspray = new MeshSprayObject(ctx.local3d);
 		// preallocate batches so their materials can be resolved
 		// preallocate batches so their materials can be resolved
+		var curID = 0, curMap = mspray.batchesMap.get(0);
+		if( curMap == null ) {
+			curMap = new Map();
+			mspray.batchesMap.set(0, curMap);
+		}
 		for( c in children ) {
 		for( c in children ) {
 			if( !c.enabled || c.type != "model" ) continue;
 			if( !c.enabled || c.type != "model" ) continue;
-			var batch = mspray.batchesMap.get(c.source);
+			if( split > 0 ) {
+				var sid = getSplitID(c);
+				if( sid != curID ) {
+					curID = sid;
+					curMap = mspray.batchesMap.get(sid);
+					if( curMap == null ) {
+						curMap = new Map();
+						mspray.batchesMap.set(sid, curMap);
+					}
+				}
+			}
+			var batch = curMap.get(c.source);
 			if( batch == null ) {
 			if( batch == null ) {
 				var obj = ctx.loadModel(c.source).toMesh();
 				var obj = ctx.loadModel(c.source).toMesh();
 				var batch = new h3d.scene.MeshBatch(cast(obj.primitive,h3d.prim.MeshPrimitive), obj.material, mspray);
 				var batch = new h3d.scene.MeshBatch(cast(obj.primitive,h3d.prim.MeshPrimitive), obj.material, mspray);
+				batch.cullingCollider = @:privateAccess batch.instanced.bounds;
 				var multi = Std.downcast(obj, h3d.scene.MultiMaterial);
 				var multi = Std.downcast(obj, h3d.scene.MultiMaterial);
 				if( multi != null ) batch.materials = multi.materials;
 				if( multi != null ) batch.materials = multi.materials;
-				mspray.batchesMap.set(c.source, { batch : batch, pivot : obj.defaultTransform.isIdentity() ? null : obj.defaultTransform });
+				curMap.set(c.source, { batch : batch, pivot : obj.defaultTransform.isIdentity() ? null : obj.defaultTransform });
 				mspray.batches.push(batch);
 				mspray.batches.push(batch);
 			}
 			}
 		}
 		}
@@ -47,10 +71,18 @@ class MeshSpray extends Object3D {
 			b.begin();
 			b.begin();
 			b.worldPosition = tmp;
 			b.worldPosition = tmp;
 		}
 		}
+		var curID = 0, curMap = mspray.batchesMap.get(0);
 		for( c in children ) {
 		for( c in children ) {
 			if( !c.enabled || c.type != "model" )
 			if( !c.enabled || c.type != "model" )
 				continue;
 				continue;
-			var inf = mspray.batchesMap.get(c.source);
+			if( split > 0 ) {
+				var sid = getSplitID(c);
+				if( sid != curID ) {
+					curMap = mspray.batchesMap.get(sid);
+					curID = sid;
+				}
+			}
+			var inf = curMap.get(c.source);
 			tmp.multiply3x4(c.to(Object3D).getTransform(), pos);
 			tmp.multiply3x4(c.to(Object3D).getTransform(), pos);
 			if( inf.pivot != null ) tmp.multiply3x4(inf.pivot, tmp);
 			if( inf.pivot != null ) tmp.multiply3x4(inf.pivot, tmp);
 			inf.batch.emitInstance();
 			inf.batch.emitInstance();
@@ -102,6 +134,7 @@ typedef MeshSprayConfig = {
 	var rotationOffset : Float;
 	var rotationOffset : Float;
 	var zOffset: Float;
 	var zOffset: Float;
 	var dontRepeatMesh : Bool;
 	var dontRepeatMesh : Bool;
+	var enableBrush : Bool;
 	var orientTerrain : Float;
 	var orientTerrain : Float;
 	var tiltAmount : Float;
 	var tiltAmount : Float;
 }
 }
@@ -200,6 +233,7 @@ class MeshSpray extends Object3D {
 	@:s var defaultConfig: MeshSprayConfig;
 	@:s var defaultConfig: MeshSprayConfig;
 	@:s var currentPresetName : String = null;
 	@:s var currentPresetName : String = null;
 	@:s var currentSetName : String = null;
 	@:s var currentSetName : String = null;
+	@:s var split : Int = 0;
 
 
 	var sceneEditor : hide.comp.SceneEditor;
 	var sceneEditor : hide.comp.SceneEditor;
 
 
@@ -260,6 +294,7 @@ class MeshSpray extends Object3D {
 			rotationOffset: 0,
 			rotationOffset: 0,
 			zOffset: 0,
 			zOffset: 0,
 			dontRepeatMesh: true,
 			dontRepeatMesh: true,
+			enableBrush: true,
 			orientTerrain : 0,
 			orientTerrain : 0,
 			tiltAmount : 0,
 			tiltAmount : 0,
 		};
 		};
@@ -326,103 +361,6 @@ class MeshSpray extends Object3D {
 		}
 		}
 		sceneEditor = ectx.scene.editor;
 		sceneEditor = ectx.scene.editor;
 
 
-
-		var ctx = ectx.getContext(this);
-		var s2d = @:privateAccess ctx.local2d.getScene();
-		reset();
-		interactive = new h2d.Interactive(10000, 10000, s2d);
-		interactive.propagateEvents = true;
-		interactive.cancelEvents = false;
-
-		interactive.onWheel = function(e) {
-
-		};
-
-		interactive.onKeyUp = function(e) {
-			if (e.keyCode == hxd.Key.R) {
-				lastMeshId = -1;
-				if (lastSpray < Date.now().getTime() - 100) {
-					if( !K.isDown( K.SHIFT) ) {
-						if (previewModels.length > 0) {
-							sceneEditor.deleteElements(previewModels, () -> { }, false);
-							sceneEditor.selectElements([this]);
-							previewModels = [];
-						}
-						var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
-						previewMeshesAround(ectx, ctx, worldPos);
-					}
-					lastSpray = Date.now().getTime();
-				}
-			}
-		}
-
-		interactive.onPush = function(e) {
-			e.propagate = false;
-			sprayEnable = true;
-			var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
-			if( K.isDown( K.SHIFT) )
-				removeMeshesAround(ctx, worldPos);
-			else {
-				addMeshes(ctx);
-			}
-		};
-
-		interactive.onRelease = function(e) {
-			e.propagate = false;
-			sprayEnable = false;
-			var addedModels = sprayedModels.copy();
-			if (sprayedModels.length > 0) {
-				undo.change(Custom(function(undo) {
-					if(undo) {
-						sceneEditor.deleteElements(addedModels, () -> reset(), true, false);
-					}
-					else {
-						sceneEditor.addElements(addedModels, false, true, true);
-					}
-				}));
-				sprayedModels = [];
-			}
-			
-
-			if (previewModels.length > 0) {
-				sceneEditor.deleteElements(previewModels, () -> { }, false);
-				sceneEditor.selectElements([this], Nothing);
-				previewModels = [];
-			}
-		};
-
-		interactive.onMove = function(e) {
-			var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
-
-			var shiftPressed = K.isDown( K.SHIFT);
-
-			drawCircle(ctx, worldPos.x, worldPos.y, worldPos.z, (shiftPressed) ? currentConfig.deleteRadius : currentConfig.radius, 5, (shiftPressed) ? 9830400 : 38400);
-
-			if (lastSpray < Date.now().getTime() - 100) {
-				if (previewModels.length > 0) {
-					sceneEditor.deleteElements(previewModels, () -> { }, false, false);
-					previewModels = [];
-				}
-				if( !shiftPressed ) {
-					previewMeshesAround(ectx, ctx, worldPos);
-				}
-
-				if( K.isDown( K.MOUSE_LEFT) ) {
-					e.propagate = false;
-
-					if (sprayEnable) {
-						if( shiftPressed ) {
-							removeMeshesAround(ctx, worldPos);
-						} else {
-							addMeshes(ctx);
-							if (currentConfig.density == 1) sprayEnable = false;
-						}
-					}
-				}
-				lastSpray = Date.now().getTime();
-			}
-		};
-
 		var props = new hide.Element('<div class="group" name="Meshes"></div>');
 		var props = new hide.Element('<div class="group" name="Meshes"></div>');
 
 
 		var preset = new hide.Element('<div class="btn-list" align="center" ></div>').appendTo(props);
 		var preset = new hide.Element('<div class="btn-list" align="center" ></div>').appendTo(props);
@@ -598,6 +536,10 @@ class MeshSpray extends Object3D {
 				Hold down SHIFT to remove meshes
 				Hold down SHIFT to remove meshes
 				<br/>Push R to randomize preview
 				<br/>Push R to randomize preview
 			</p>
 			</p>
+			<p align="center">
+				<label><input type="checkbox" id="enableBrush" style="margin-right: 5px;"/> Enable Brush</label>
+			</p>
+
 		</div>
 		</div>
 		').appendTo(props);
 		').appendTo(props);
 
 
@@ -606,6 +548,20 @@ class MeshSpray extends Object3D {
 			currentConfig.dontRepeatMesh = repeat.is(":checked");
 			currentConfig.dontRepeatMesh = repeat.is(":checked");
 		}).prop("checked", currentConfig.dontRepeatMesh);
 		}).prop("checked", currentConfig.dontRepeatMesh);
 
 
+		var enableBrush = options.find("#enableBrush");
+		enableBrush.on("change", function() {
+			currentConfig.enableBrush = enableBrush.is(":checked");
+			sceneEditor.setLock([this], currentConfig.enableBrush, false);
+			removeInteractiveBrush();
+			if (currentConfig.enableBrush)
+				createInteractiveBrush(ectx);
+			else {
+				interactive.cancelEvents = true;
+			}
+			
+		}).prop("checked", currentConfig.enableBrush);
+		
+
 		options.find("#select").click(function(_) {
 		options.find("#select").click(function(_) {
 			var options = selectElement.children().elements();
 			var options = selectElement.children().elements();
 			for (opt in options) {
 			for (opt in options) {
@@ -649,7 +605,6 @@ class MeshSpray extends Object3D {
 					}
 					}
 				}
 				}
 				sceneEditor.deleteElements(meshes);
 				sceneEditor.deleteElements(meshes);
-				sceneEditor.selectElements([this], Nothing);
 			}
 			}
 		});
 		});
 
 
@@ -676,7 +631,114 @@ class MeshSpray extends Object3D {
 			saveConfigMeshBatch();
 			saveConfigMeshBatch();
 		});
 		});
 
 
+		sceneEditor.setLock([this], currentConfig.enableBrush, false);
+		removeInteractiveBrush();
+		if (currentConfig.enableBrush)
+			createInteractiveBrush(ectx);
 		super.edit(ectx);
 		super.edit(ectx);
+
+		ectx.properties.add(new Element('
+		<div class="group" name="Extra">
+		<dl>
+			<dt>Split</dt><dd><input type="range" min="0" max="2048" field="split"/></dd>
+		</dl>
+		</div>'), this);
+	}
+
+	function createInteractiveBrush(ectx : EditContext) {
+		var ctx = ectx.getContext(this);
+		var s2d = @:privateAccess ctx.local2d.getScene();
+		interactive = new h2d.Interactive(10000, 10000, s2d);
+		interactive.propagateEvents = true;
+		interactive.cancelEvents = false;
+
+		interactive.onWheel = function(e) {
+
+		};
+
+		interactive.onKeyUp = function(e) {
+			if (e.keyCode == hxd.Key.R) {
+				lastMeshId = -1;
+				if (lastSpray < Date.now().getTime() - 100) {
+					if( !K.isDown( K.SHIFT) ) {
+						if (previewModels.length > 0) {
+							sceneEditor.deleteElements(previewModels, () -> { }, false);
+							previewModels = [];
+						}
+						var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
+						previewMeshesAround(ectx, ctx, worldPos);
+					}
+					lastSpray = Date.now().getTime();
+				}
+			}
+		}
+
+		interactive.onPush = function(e) {
+			e.propagate = false;
+			sprayEnable = true;
+			var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
+			if( K.isDown( K.SHIFT) )
+				removeMeshesAround(ctx, worldPos);
+			else {
+				addMeshes(ctx);
+			}
+		};
+
+		interactive.onRelease = function(e) {
+			e.propagate = false;
+			sprayEnable = false;
+			var addedModels = sprayedModels.copy();
+			if (sprayedModels.length > 0) {
+				undo.change(Custom(function(undo) {
+					if(undo) {
+						sceneEditor.deleteElements(addedModels, () -> removeInteractiveBrush(), true, false);
+					}
+					else {
+						sceneEditor.addElements(addedModels, false, true, true);
+					}
+				}));
+				sprayedModels = [];
+			}
+			
+
+			if (previewModels.length > 0) {
+				sceneEditor.deleteElements(previewModels, () -> { }, false);
+				previewModels = [];
+			}
+		};
+
+		interactive.onMove = function(e) {
+			var worldPos = ectx.screenToGround(s2d.mouseX, s2d.mouseY);
+
+			var shiftPressed = K.isDown( K.SHIFT);
+
+			drawCircle(ctx, worldPos.x, worldPos.y, worldPos.z, (shiftPressed) ? currentConfig.deleteRadius : currentConfig.radius, 5, (shiftPressed) ? 9830400 : 38400);
+
+			if (lastSpray < Date.now().getTime() - 100) {
+				if (previewModels.length > 0) {
+					sceneEditor.deleteElements(previewModels, () -> { }, false, false);
+					previewModels = [];
+				}
+				if( !shiftPressed ) {
+					previewMeshesAround(ectx, ctx, worldPos);
+				}
+
+				if( K.isDown( K.MOUSE_LEFT) ) {
+					e.propagate = false;
+
+					if (sprayEnable) {
+						if( shiftPressed ) {
+							removeMeshesAround(ctx, worldPos);
+						} else {
+							if (currentConfig.density == 1) sprayEnable = false;
+							else addMeshes(ctx);
+						}
+					}
+				}
+				lastSpray = Date.now().getTime();
+			}
+		};
+
 	}
 	}
 
 
 	function updateConfig() {
 	function updateConfig() {
@@ -694,16 +756,16 @@ class MeshSpray extends Object3D {
 			input.change();
 			input.change();
 		}
 		}
 
 
-		sceneEditor.properties.element.find("#repeatMeshBtn").prop("checked", CONFIG.dontRepeatMesh);
+		sceneEditor.properties.element.find("#repeatMesh").prop("checked", CONFIG.dontRepeatMesh);
 	}
 	}
 
 
 	override function removeInstance(ctx : Context):Bool {
 	override function removeInstance(ctx : Context):Bool {
-		reset();
+		removeInteractiveBrush();
 		return super.removeInstance(ctx);
 		return super.removeInstance(ctx);
 	}
 	}
 	override function setSelected( ctx : Context, b : Bool ) {
 	override function setSelected( ctx : Context, b : Bool ) {
 		if( !b ) {
 		if( !b ) {
-			reset();
+			removeInteractiveBrush();
 			if( gBrushes != null ) {
 			if( gBrushes != null ) {
 				for (g in gBrushes) g.remove();
 				for (g in gBrushes) g.remove();
 				gBrushes = null;
 				gBrushes = null;
@@ -712,7 +774,7 @@ class MeshSpray extends Object3D {
 		return false;
 		return false;
 	}
 	}
 
 
-	function reset() {
+	function removeInteractiveBrush() {
 		if( interactive != null ) interactive.remove();
 		if( interactive != null ) interactive.remove();
 		if (previewModels != null && previewModels.length > 0) {
 		if (previewModels != null && previewModels.length > 0) {
 			sceneEditor.deleteElements(previewModels, () -> { }, false, false);
 			sceneEditor.deleteElements(previewModels, () -> { }, false, false);
@@ -777,7 +839,6 @@ class MeshSpray extends Object3D {
 		if (computedDensity == 1)
 		if (computedDensity == 1)
 		if (previewModels.length > 0) {
 		if (previewModels.length > 0) {
 			sceneEditor.deleteElements(previewModels, () -> { }, false);
 			sceneEditor.deleteElements(previewModels, () -> { }, false);
-			sceneEditor.selectElements([this], Nothing);
 			previewModels = [];
 			previewModels = [];
 		}
 		}
 		lastPos = point;
 		lastPos = point;
@@ -879,14 +940,16 @@ class MeshSpray extends Object3D {
 		var fakeRadius = currentConfig.deleteRadius * currentConfig.deleteRadius;
 		var fakeRadius = currentConfig.deleteRadius * currentConfig.deleteRadius;
 		for (child in children) {
 		for (child in children) {
 			var model = child.to(hrt.prefab.Object3D);
 			var model = child.to(hrt.prefab.Object3D);
-			if (distance(point2d.x, point2d.y, model.x, model.y) < fakeRadius) {
-				childToRemove.push(child);
+			if (model != null) {
+				if (distance(point2d.x, point2d.y, model.x, model.y) < fakeRadius) {
+					childToRemove.push(child);
+				}
 			}
 			}
+			
 		}
 		}
 		if (childToRemove.length > 0) {
 		if (childToRemove.length > 0) {
 			wasEdited = true;
 			wasEdited = true;
 			sceneEditor.deleteElements(childToRemove, () -> { }, false);
 			sceneEditor.deleteElements(childToRemove, () -> { }, false);
-			sceneEditor.selectElements([this], Nothing);
 		}
 		}
 	}
 	}
 
 
@@ -995,6 +1058,19 @@ class MeshSpray extends Object3D {
 		return primitive;
 		return primitive;
 	}
 	}
 
 
+	override function flatten<T:Prefab>( ?cl : Class<T>, ?arr: Array<T> ) : Array<T> {
+		if(arr == null)
+			arr = [];
+		if( cl == null )
+			arr.push(cast this);
+		else {
+			var i = to(cl);
+			if(i != null)
+				arr.push(i);
+		}
+		return arr;
+	}
+
 	static var _ = Library.register("meshSpray", MeshSpray);
 	static var _ = Library.register("meshSpray", MeshSpray);
 
 
 }
 }