Răsfoiți Sursa

Add Polygon editor

ShiroSmith 7 ani în urmă
părinte
comite
352494b3e9
5 a modificat fișierele cu 869 adăugiri și 61 ștergeri
  1. 24 2
      bin/style.less
  2. 108 0
      hide/prefab/Polygon.hx
  3. 593 0
      hide/prefab/PolygonEditor.hx
  4. 133 46
      hide/prefab/l3d/Polygon.hx
  5. 11 13
      hide/view/l3d/Level3D.hx

+ 24 - 2
bin/style.less

@@ -871,14 +871,36 @@ input[type=checkbox] {
 		}
 	}
 }
+/* Poly editor */
+.poly-editor{
+	.description{
+		margin: 5px;
+		background-color: black;
+		outline: 4px solid #4a4a4a;
+		padding : 2px;
+	}
+	.point-list {
+		input {
+			width: 70px;
 
-/* Terrain editor */
+		}
+		.deletePoint {
+			width: 20px;
+			margin-left: 8px;
+		}
+	}
+	.poly-vector2{
+		display: block;
+		padding : 5px;
+	}
+}
 
+/* Terrain editor */
 .terrain-brushModeDescription{
 	margin: 5px;
 	background-color: black;
 	outline: 4px solid #4a4a4a;
-	padding : 1px;
+	padding : 2px;
 }
 
 .terrain-brushModeContainer{

+ 108 - 0
hide/prefab/Polygon.hx

@@ -0,0 +1,108 @@
+package hide.prefab;
+
+/*class Polygon extends Object3D {
+
+	#if editor
+	public var editor : PolygonEditor;
+	var debugColor : Int;
+	var debugMesh : h3d.scene.Mesh;
+	#end
+
+	public var points : h2d.col.Polygon = [];
+	public var primitive : h3d.prim.Polygon;
+	public var mesh : h3d.scene.Mesh;
+
+	override function load( obj : Dynamic ) {
+		super.load(obj);
+		points = obj.points != null ? obj.points : [];
+		debugColor = obj.debugColor != null ? obj.debugColor : 0xFFFFFF;
+	}
+
+	override function save() {
+		var obj : Dynamic = super.save();
+		obj.points = points;
+		obj.debugColor = debugColor;
+		return obj;
+	}
+
+	override function makeInstance( ctx : Context ) : Context {
+		ctx = ctx.clone(this);
+		mesh = new h3d.scene.Mesh(null, null, ctx.local3d);
+		ctx.local3d = mesh;
+		ctx.local3d.name = name;
+		#if editor
+		debugMesh = new h3d.scene.Mesh(null, null, ctx.local3d);
+		debugMesh.name = "debugMesh";
+		debugMesh.material.props = h3d.mat.MaterialSetup.current.getDefaults("ui");
+		debugMesh.material.blendMode = Alpha;
+		debugMesh.material.mainPass.culling = None;
+		debugMesh.material.color = h3d.Vector.fromColor(debugColor);
+		debugMesh.material.color.a = 0.7;
+		#end
+		generatePolygon();
+		mesh.primitive = primitive;
+		updateInstance(ctx);
+		return ctx;
+	}
+
+	override function updateInstance( ctx : Context, ?propName : String ) {
+		super.updateInstance(ctx, null);
+		#if editor
+		if(editor != null)
+			editor.update(propName, ctx);
+		if(propName == "debugColor") {
+			debugMesh.material.color = h3d.Vector.fromColor(debugColor);
+			debugMesh.material.color.a = 0.7;
+		}
+		#end
+	}
+
+	public function generatePolygon(){
+		if(primitive != null) primitive.dispose();
+		if(points != null){
+			var indexes = points.fastTriangulate();
+			var idx : hxd.IndexBuffer = new hxd.IndexBuffer();
+			for( i in indexes ) if(i != null) idx.push(i);
+			var pts = [for( p in points) new h3d.col.Point(p.x, p.y, 0)];
+			primitive = new h3d.prim.Polygon(pts, idx);
+			primitive.addNormals();
+			primitive.addUVs();
+			primitive.addTangents() ;
+			primitive.alloc(h3d.Engine.getCurrent());
+		}
+		mesh.primitive = primitive;
+		#if editor
+		debugMesh.primitive = primitive;
+		#end
+	}
+
+
+	#if editor
+
+	override function setSelected( ctx : Context, b : Bool ) {
+		if( editor != null ) editor.setSelected(ctx, b);
+	}
+
+	override function getHideProps() : HideProps {
+		return { icon : "object-ungroup", name : "polyditor" };
+	}
+
+	override function edit( ctx : EditContext ) {
+		super.edit(ctx);
+		var props = new hide.Element('<div></div>');
+		//if( editor == null ) editor = new PolygonEditor(this, ctx.properties.undo);
+		//editor.editContext = ctx;
+		//editor.setupUI(props, ctx);
+		props.append('
+		<div class="group" name="Polygon">
+				<dt>Color</dt><dd><input type="color" field="debugColor"/></dd>
+		</div>');
+
+		ctx.properties.add(props, this, function(pname) {
+			ctx.onChange(this, pname);
+		});
+	}
+	#end
+
+	static var _ = hxd.prefab.Library.register("polyditor", hide.prefab.Polygon);
+}*/

+ 593 - 0
hide/prefab/PolygonEditor.hx

@@ -0,0 +1,593 @@
+package hide.prefab;
+import hxd.Key as K;
+
+enum ColorState{
+	None;
+	Overlapped;
+	OverlappedForDelete;
+	Selected;
+}
+
+class Edge{
+	public var p1 : h2d.col.Point;
+	public var p2 : h2d.col.Point;
+	public function new(p1, p2){
+		this.p1 = p1;
+		this.p2 = p2;
+	}
+}
+
+class MovablePoint {
+
+	public var showDebug : Bool;
+	public var point : h2d.col.Point;
+	var mesh: h3d.scene.Mesh;
+	public var colorState = None;
+	var localPosText : h2d.ObjectFollower;
+	var worldPosText : h2d.ObjectFollower;
+
+	public function new(point : h2d.col.Point, ctx : Context){
+		this.point = point;
+		mesh = new h3d.scene.Mesh(h3d.prim.Sphere.defaultUnitSphere(), null, ctx.local3d);
+		mesh.name = "_movablePoint";
+		mesh.material.setDefaultProps("ui");
+		mesh.scale(0.1);
+		mesh.setPosition(point.x, point.y, 0);
+		localPosText = createText(ctx);
+		worldPosText = createText(ctx);
+		worldPosText.offsetZ = (0.3);
+		localPosText.offsetZ = (0.6);
+		updateText(ctx);
+	}
+
+	function createText(ctx : Context){
+		var o = new h2d.ObjectFollower(mesh,  @:privateAccess ctx.local2d.getScene());
+		var t = new h2d.Text(hxd.res.DefaultFont.get(), o);
+		t.textColor = 0xFFFFFF;
+		t.textAlign = Center;
+		t.dropShadow = { dx : 1.5, dy : 1.5, color : 0x202020, alpha : 1.0 };
+		return o;
+	}
+
+	public function dispose(){
+		mesh.remove();
+		worldPosText.remove();
+		localPosText.remove();
+	}
+
+	function worldToScreen(wx: Float, wy: Float, wz: Float, ctx : Context) {
+		var s2d = @:privateAccess ctx.local2d.getScene();
+		var camera = @:privateAccess ctx.local3d.getScene().camera;
+		camera.update();
+		var pt = camera.project(wx, wy, wz, s2d.width, s2d.height);
+		return new h2d.col.Point( pt.x, pt.y);
+	}
+
+	public function updateText(ctx : Context){
+		inline function getText(o) : h2d.Text{
+			return Std.instance(o.getChildAt(0), h2d.Text);
+		}
+		getText(localPosText).visible = showDebug;
+		getText(worldPosText).visible = showDebug;
+		var pointWorldPos = new h3d.Vector(point.x, point.y, 0.);
+		ctx.local3d.localToGlobal(pointWorldPos);
+		getText(localPosText).text = "Local : " + untyped point.x.toFixed(3) + " / " + untyped point.y.toFixed(3);
+		getText(worldPosText).text = "World : " + untyped pointWorldPos.x.toFixed(3) + " / " + untyped pointWorldPos.y.toFixed(3) + " / " + untyped pointWorldPos.z.toFixed(3);
+	}
+
+	public function updateColor(){
+		switch(colorState){
+			case None : mesh.material.color.set(1,1,1);
+			case Overlapped : mesh.material.color.set(1,1,0);
+			case OverlappedForDelete : mesh.material.color.set(1,0,0);
+			case Selected : mesh.material.color.set(0,0,1);
+		}
+	}
+
+	public function interset(ray : h3d.col.Ray) : Bool{
+		return mesh.getCollider().rayIntersection(ray, false) != -1;
+	}
+
+	public function setColorState(s : ColorState){
+		colorState = s;
+	}
+}
+
+class PolygonEditor {
+
+	var polygonPrefab : hide.prefab.l3d.Polygon;
+	var undo : hide.ui.UndoHistory;
+	var interactive : h2d.Interactive;
+	var lineGraphics : h3d.scene.Graphics;
+	var triangleGraphics : h3d.scene.Graphics;
+	var movablePoints : Array<MovablePoint> = [];
+	var selectedPoints : Array<h2d.col.Point> = [];
+	var lastPointSelected : h2d.col.Point;
+	var lastPos : h3d.Vector;
+	var selectedEdge : Edge;
+	var selectedEdgeGraphic : h3d.scene.Graphics;
+	public var editContext : EditContext;
+	public var showDebug : Bool;
+	public var gridSize = 1;
+	public var showTriangles : Bool = false;
+	var lastClickStamp = 0.0;
+
+	var beforeMoveList : Array<h2d.col.Point> = [];
+	var afterMoveList : Array<h2d.col.Point> = [];
+
+	public function new( polygonPrefab : hide.prefab.l3d.Polygon, undo : hide.ui.UndoHistory ){
+		this.polygonPrefab = polygonPrefab;
+		this.undo = undo;
+	}
+
+	public function dispose(){
+		reset();
+	}
+
+	function removeGraphics(g : h3d.scene.Graphics){
+		if(g != null){
+			g.clear();
+			g.remove();
+			g.dispose();
+		}
+	}
+
+	public function reset(){
+		clearMovablePoints();
+		clearSelectedPoint();
+		if(interactive != null) interactive.remove();
+		removeGraphics(lineGraphics);
+		removeGraphics(selectedEdgeGraphic);
+		removeGraphics(triangleGraphics);
+	}
+
+	inline function getContext(){
+		return editContext.getContext(polygonPrefab);
+	}
+
+	public function update( ?propName : String) {
+		if(propName == "showDebug"){
+			for(mp in movablePoints){
+				mp.showDebug = showDebug;
+				mp.updateText(getContext());
+			}
+		}
+		else if(propName == "showTriangles") {
+			drawTriangles(showTriangles);
+		}
+	}
+
+	function copyArray(array : Array<h2d.col.Point>){
+		var copy : Array<h2d.col.Point> = [];
+		for(p in array)
+		copy.push(p.clone());
+		return copy;
+	}
+
+	function addUndo( prev : Array<h2d.col.Point>, next : Array<h2d.col.Point>){
+		undo.change(Custom(function(undo) {
+			var prevList = prev;
+			var newList = next;
+			if(undo)
+				polygonPrefab.points = prevList;
+			else
+				polygonPrefab.points = newList;
+			refreshPolygon();
+		}));
+	}
+
+	function refreshPolygon(){
+		var polyPrim = polygonPrefab.generateCustomPolygon();
+		var mesh : h3d.scene.Mesh = cast getContext().local3d;
+		mesh.primitive = polyPrim;
+		refreshEditorDisplay();
+	}
+
+	function refreshDebugDisplay(){
+		for(mp in movablePoints)
+			mp.updateText(getContext());
+	}
+
+	function clearSelectedPoint(){
+		selectedPoints.splice(0, selectedPoints.length);
+		lastPointSelected = null;
+	}
+
+	function isAlreadySelected( p : h2d.col.Point ) : Bool {
+		if( p == null) return false;
+		for( point in selectedPoints )
+			if( point == p ) return true;
+		return false;
+	}
+
+	function addSelectedPoint( p : h2d.col.Point ){
+		if( p == null) return;
+		for( point in selectedPoints )
+			if( point == p ) return;
+		selectedPoints.push(p);
+	}
+
+	function removePoint( p : h2d.col.Point) {
+		polygonPrefab.points.remove(p);
+		refreshPolygon();
+	}
+
+	function addPointOnEdge( pos: h2d.col.Point, e : Edge) {
+		if(e == null){
+			polygonPrefab.points.points.push(pos);
+			return;
+		}
+		function findIndex(p) : Int {
+			for(i in 0 ... polygonPrefab.points.length)
+				if( p == polygonPrefab.points[i])
+					return i;
+			return -1;
+		}
+		var i1 = findIndex(e.p1);
+		var i2 = findIndex(e.p2);
+		if( hxd.Math.abs(i1 - i2) > 1 )
+			polygonPrefab.points.points.push(pos);
+		else
+			polygonPrefab.points.points.insert(Std.int(hxd.Math.max(i1,i2)), pos);
+		refreshPolygon();
+	}
+
+	function projectToGround( ray: h3d.col.Ray) {
+		var minDist = -1.;
+		var normal = getContext().local3d.getAbsPos().up();
+		var plane = h3d.col.Plane.fromNormalPoint(normal.toPoint(), new h3d.col.Point(getContext().local3d.getAbsPos().tx, getContext().local3d.getAbsPos().ty, getContext().local3d.getAbsPos().tz));
+		var pt = ray.intersect(plane);
+		if(pt != null) { minDist = pt.sub(ray.getPos()).length();}
+		return minDist;
+	}
+
+	function screenToWorld( u : Float, v : Float ) {
+		var camera = @:privateAccess getContext().local3d.getScene().camera;
+		var ray = camera.rayFromScreen(u, v);
+		var dist = projectToGround(ray);
+		return dist >= 0 ? ray.getPoint(dist) : null;
+	}
+
+	function trySelectPoint( ray: h3d.col.Ray ) : MovablePoint {
+		for(mp in movablePoints)
+			if(mp.interset(ray))
+				return mp;
+		return null;
+	}
+
+	function trySelectEdge( pos : h2d.col.Point ) : Edge {
+		inline function crossProduct( a : h2d.col.Point, b : h2d.col.Point ){
+			return a.x * b.y - a.y * b.x;
+		}
+		inline function dist(s1 : h2d.col.Point, s2 : h2d.col.Point, p : h2d.col.Point){
+			var l = s2.distance(s1);
+			l = l * l;
+			if(l == 0) return p.distance(s1);
+			var t = hxd.Math.max(0, hxd.Math.min(1, p.sub(s1).dot(s2.sub(s1)) / l));
+			var proj = s1.add((s2.sub(s1).scale(t)));
+			return p.distance(proj);
+		}
+		if(polygonPrefab.points.length < 2) return null;
+		var minDist = dist(polygonPrefab.points[0], polygonPrefab.points[polygonPrefab.points.length - 1], pos);
+		var edge : Edge = new Edge(polygonPrefab.points[0],polygonPrefab.points[polygonPrefab.points.length - 1]);
+		for(i in 1 ... polygonPrefab.points.length){
+			var p1 = polygonPrefab.points[i-1];
+			var p2 = polygonPrefab.points[i];
+			var dist = dist(p1, p2, pos);
+			if(dist < minDist){
+				edge.p1 = p1;
+				edge.p2 = p2;
+				minDist = dist;
+			}
+		}
+		return edge;
+	}
+
+	function getFinalPos(mouseX, mouseY){
+		var worldPos = screenToWorld(mouseX, mouseY).toVector();
+		var localPos = getContext().local3d.globalToLocal(worldPos);
+		if( K.isDown( K.CTRL ) ){ // Snap To Grid with Ctrl
+			localPos.x = hxd.Math.round(worldPos.x / gridSize) * gridSize;
+			localPos.y = hxd.Math.round(worldPos.y / gridSize) * gridSize;
+			localPos.z = hxd.Math.round(worldPos.z / gridSize) * gridSize;
+		}
+		return localPos;
+	}
+
+	public function setSelected( ctx : Context, b : Bool ) {
+		reset();
+		if(b){
+			var s2d = @:privateAccess ctx.local2d.getScene();
+			interactive = new h2d.Interactive(10000, 10000, s2d);
+			interactive.propagateEvents = true;
+			interactive.cancelEvents = false;
+			lineGraphics = new h3d.scene.Graphics(ctx.local3d);
+			lineGraphics.lineStyle(2, 0xFFFFFF);
+			lineGraphics.material.mainPass.setPassName("overlay");
+			lineGraphics.material.mainPass.depth(false, LessEqual);
+			selectedEdgeGraphic = new h3d.scene.Graphics(ctx.local3d);
+			selectedEdgeGraphic.lineStyle(6, 0xFFFF00, 0.5);
+			selectedEdgeGraphic.material.mainPass.setPassName("overlay");
+			selectedEdgeGraphic.material.mainPass.depth(false, LessEqual);
+			triangleGraphics = new h3d.scene.Graphics(ctx.local3d);
+			triangleGraphics.lineStyle(2, 0xFF0000);
+			triangleGraphics.material.mainPass.setPassName("overlay");
+			triangleGraphics.material.mainPass.depth(false, LessEqual);
+
+			refreshEditorDisplay();
+			drawTriangles(showTriangles);
+
+			interactive.onWheel = function(e) {
+				refreshDebugDisplay();
+			};
+			interactive.onKeyDown =
+			function(e) {
+				e.propagate = false;
+				if( K.isDown( K.SHIFT ) ){
+					clearSelectedPoint();
+					var ray = @:privateAccess ctx.local3d.getScene().camera.rayFromScreen(s2d.mouseX, s2d.mouseY);
+					refreshMovablePoints(ray);
+					if(lastPos == null) lastPos = getFinalPos(s2d.mouseX, s2d.mouseY);
+					refreshSelectedEdge(new h2d.col.Point(lastPos.x, lastPos.y));
+				}
+			}
+			interactive.onKeyUp =
+			function(e) {
+				e.propagate = false;
+				var ray = @:privateAccess ctx.local3d.getScene().camera.rayFromScreen(s2d.mouseX, s2d.mouseY);
+				refreshMovablePoints(ray);
+				if(lastPos == null) lastPos = getFinalPos(s2d.mouseX, s2d.mouseY);
+				refreshSelectedEdge(new h2d.col.Point(lastPos.x, lastPos.y));
+			}
+			interactive.onPush =
+			function(e) {
+				var finalPos = getFinalPos(s2d.mouseX, s2d.mouseY);
+				var ray = @:privateAccess ctx.local3d.getScene().camera.rayFromScreen(s2d.mouseX, s2d.mouseY);
+				if( K.isDown( K.MOUSE_LEFT ) ){
+					e.propagate = false;
+					// Shift + Left Click : Remove Point
+					if( K.isDown( K.SHIFT ) ){
+						var mp = trySelectPoint(ray);
+						if(mp != null){
+							var prevList = copyArray(polygonPrefab.points.points);
+							removePoint(mp.point);
+							var newList = copyArray(polygonPrefab.points.points);
+							addUndo(prevList, newList);
+						}
+					}
+					else {
+						// Left Click : Add/Set selected point / Clear selection
+						lastPos = finalPos.clone();
+						var mp = trySelectPoint(ray);
+						if(mp != null){
+							if( K.isDown(K.ALT) && !isAlreadySelected(mp.point))
+									addSelectedPoint(mp.point);
+							lastPointSelected = mp.point;
+							beforeMoveList = copyArray(polygonPrefab.points.points);
+						}
+						// Double Left Click : Create point
+						else{
+							clearSelectedPoint();
+							var curStamp = haxe.Timer.stamp();
+							var diff = curStamp - lastClickStamp;
+							if(diff < 0.2){
+								var prevList = copyArray(polygonPrefab.points.points);
+								addPointOnEdge(new h2d.col.Point(finalPos.x, finalPos.y), selectedEdge);
+								var newList = copyArray(polygonPrefab.points.points);
+								addUndo(prevList, newList);
+								refreshSelectedEdge(new h2d.col.Point(finalPos.x, finalPos.y));
+							}
+							lastClickStamp = curStamp;
+						}
+						refreshMovablePoints();
+					}
+				}
+			};
+			interactive.onRelease =
+			function(e) {
+				//lastPos = null;
+				lastPointSelected = null;
+				refreshDebugDisplay();
+				if( beforeMoveList != null ){
+					afterMoveList = copyArray(polygonPrefab.points.points);
+					addUndo(beforeMoveList, afterMoveList);
+					beforeMoveList = null;
+					afterMoveList = null;
+				}
+			};
+			interactive.onMove =
+			function(e) {
+				var ray = @:privateAccess ctx.local3d.getScene().camera.rayFromScreen(s2d.mouseX, s2d.mouseY);
+				var finalPos = getFinalPos(s2d.mouseX, s2d.mouseY);
+				refreshMovablePoints(ray);
+				refreshSelectedEdge(new h2d.col.Point(finalPos.x, finalPos.y));
+				if( K.isDown( K.MOUSE_LEFT )){
+					var move : h2d.col.Point = null;
+					var pos = new h2d.col.Point(finalPos.x, finalPos.y);
+					if(lastPointSelected != null){
+						move = pos.sub(lastPointSelected);
+						lastPointSelected.load(pos);
+						for(p in selectedPoints){
+							if(lastPointSelected == p) continue;
+							p.x += move.x; p.y += move.y;
+						}
+					}
+					refreshMovablePoints();
+					refreshSelectedEdge(new h2d.col.Point(finalPos.x, finalPos.y));
+					refreshPolygon();
+					lastPos = finalPos.clone();
+				}
+				else
+					refreshDebugDisplay();
+			};
+		}
+	}
+
+	function refreshSelectedEdge( pos : h2d.col.Point ){
+		selectedEdge = trySelectEdge(pos);
+		selectedEdgeGraphic.clear();
+		if(K.isDown( K.SHIFT ) )
+			return;
+		if(selectedEdge != null){
+			selectedEdgeGraphic.moveTo(selectedEdge.p1.x, selectedEdge.p1.y, 0);
+			selectedEdgeGraphic.lineTo(selectedEdge.p2.x, selectedEdge.p2.y, 0);
+		}
+	}
+
+	function drawTriangles( b : Bool ){
+		triangleGraphics.clear();
+		if(b && polygonPrefab.getPrimitive(getContext()) != null){
+			var i = 0;
+			var prim = polygonPrefab.getPrimitive(getContext());
+			while(i < prim.idx.length){
+				triangleGraphics.moveTo(prim.points[prim.idx[i]].x, prim.points[prim.idx[i]].y, 0);
+				triangleGraphics.lineTo(prim.points[prim.idx[i + 1]].x, prim.points[prim.idx[i + 1]].y, 0);
+				triangleGraphics.lineTo(prim.points[prim.idx[i + 2]].x, prim.points[prim.idx[i + 2]].y, 0);
+				triangleGraphics.lineTo(prim.points[prim.idx[i]].x, prim.points[prim.idx[i]].y, 0);
+				i += 3;
+			}
+		}
+	}
+
+	function clearMovablePoints(){
+		for(mp in movablePoints)
+			mp.dispose();
+		movablePoints.splice(0, movablePoints.length);
+	}
+
+	function createMovablePoints(){
+		for(p in polygonPrefab.points){
+			var mp = new MovablePoint(p, getContext());
+			movablePoints.push(mp);
+		}
+	}
+
+	function refreshMovablePoints( ?ray ){
+		for(mp in movablePoints)
+			mp.setColorState(None);
+		if(ray != null){
+			var mp = trySelectPoint(ray);
+			if( mp != null && mp.colorState != Selected)
+				K.isDown( K.SHIFT ) ? mp.setColorState(OverlappedForDelete) : mp.setColorState(Overlapped);
+		}
+		for(p in selectedPoints)
+			for(mp in movablePoints)
+				if(mp.point == p){
+					mp.setColorState(Selected);
+					break;
+				}
+		for(mp in movablePoints){
+			if( mp.point == lastPointSelected) mp.setColorState(Selected);
+			mp.updateColor();
+			mp.showDebug = showDebug;
+			mp.updateText(getContext());
+		}
+	}
+
+	function refreshEditorDisplay(){
+		lineGraphics.clear();
+		clearMovablePoints();
+		refreshPointList(editContext.getCurrentProps(polygonPrefab));
+		if(polygonPrefab.points == null || polygonPrefab.points.length == 0) return;
+		lineGraphics.moveTo(polygonPrefab.points[polygonPrefab.points.length - 1].x, polygonPrefab.points[polygonPrefab.points.length - 1].y, 0);
+		for(p in polygonPrefab.points)
+			lineGraphics.lineTo(p.x, p.y, 0);
+		createMovablePoints();
+		refreshMovablePoints();
+	}
+
+	public function addProps( ctx : EditContext ){
+		var props = new hide.Element('
+		<div class="poly-editor">
+			<div class="group" name="Tool">
+				<div class="description">
+					<i>Double Left Click</i> : Add point on edge <br>
+					<i>Shift + Left Click</i> : Delete selected point <br>
+					Drag with <i>Left Click</i> : Move selected points <br>
+					Drag with <i>Left Click + Ctrl</i> : Move selected points on grid <br>
+					<i>Alt + Left Click</i> : Add point to selection
+				</div>
+				<dt>Show Debug</dt><dd><input type="checkbox" field="showDebug"/></dd>
+				<dt>Show Triangles</dt><dd><input type="checkbox" field="showTriangles"/></dd>
+				<dt>Grid Size</dt><dd><input type="range" min="0" max="10" field="gridSize"/></dd>
+			</div>
+			<div align="center">
+				<div class="group" name="Points">
+					<div class="point-list"> </div>
+					<input type="button" value="Reset" class="reset" />
+				</div>
+			</div>
+		</div>');
+
+		props.find(".reset").click(function(_) {
+			var prevList = copyArray(polygonPrefab.points.points);
+			polygonPrefab.points.points.splice(0, polygonPrefab.points.points.length);
+			var nextList = copyArray(polygonPrefab.points.points);
+			addUndo(prevList, nextList);
+			refreshPolygon();
+		});
+
+		refreshPointList(props);
+
+		ctx.properties.add(props, this, function(pname) {ctx.onChange(polygonPrefab, pname); });
+		return props;
+	}
+
+	function refreshPointList( props : hide.Element){
+		var container = props.find(".point-list");
+		container.empty();
+
+		function createVector(p : h2d.col.Point){
+			var v = new Element('<div class="poly-vector2" >');
+			var deleteButton = new Element('<input type="button" value="-" class="deletePoint" />');
+			var fieldX = new Element('<input class="inputX" type="text" name="xfield" minlength="1" maxlength="4">');
+			var fieldY = new Element('<input type="text" name="yfield" minlength="1" maxlength="4">');
+
+			fieldX.val(p.x);
+			fieldY.val(p.y);
+
+			fieldX.on("input", function(_) {
+				var prevValue = p.x;
+				p.x = Std.parseFloat(fieldX.val());
+				var nextValue = p.x;
+				undo.change(Custom(function(undo) {
+					p.x = undo ? prevValue : nextValue;
+					refreshPolygon();
+				}));
+				refreshPolygon();
+			});
+
+			fieldY.on("input", function(_) {
+				var prevValue = p.y;
+				p.y = Std.parseFloat(fieldY.val());
+				var nextValue = p.y;
+				undo.change(Custom(function(undo) {
+					p.y = undo ? prevValue : nextValue;
+					refreshPolygon();
+				}));
+				refreshPolygon();
+			});
+
+			deleteButton.on("click", function(_) {
+				var prevList = copyArray(polygonPrefab.points.points);
+				polygonPrefab.points.points.remove(p);
+				var nextList = copyArray(polygonPrefab.points.points);
+				addUndo(prevList, nextList);
+				refreshPolygon();
+				refreshPointList(props);
+			});
+
+			v.append('<label>X </label>');
+			v.append(fieldX);
+			v.append('<label> Y </label>');
+			v.append(fieldY);
+			v.append(deleteButton);
+			v.append('</div>');
+			container.append(v);
+		}
+
+		for(p in polygonPrefab.points){
+			createVector(p);
+		}
+	}
+}

+ 133 - 46
hide/prefab/l3d/Polygon.hx

@@ -4,6 +4,7 @@ import h2d.col.Point;
 enum Shape {
 	Quad;
 	Disc(segments: Int, angle: Float, inner: Float, rings:Int);
+	Custom;
 }
 
 typedef PrimCache = Map<Shape, h3d.prim.Polygon>;
@@ -11,37 +12,55 @@ typedef PrimCache = Map<Shape, h3d.prim.Polygon>;
 class Polygon extends Object3D {
 
 	public var shape(default, null) : Shape = Quad;
+	public var debugColor : Int;
+	public var points : h2d.col.Polygon = [];
+	#if editor
+	public var editor : PolygonEditor;
+	#end
 
 	override function save() {
 		var obj : Dynamic = super.save();
-		if(shape != Quad) {
-			obj.kind = shape.getIndex();
-			obj.args = shape.getParameters();
+		switch(shape){
+			case Quad:
+			case Disc(segments, angle, inner, rings):
+				obj.kind = shape.getIndex();
+				obj.args = shape.getParameters();
+			case Custom:
+				obj.kind = 2;
+				obj.points = points;
 		}
+		obj.debugColor = debugColor;
 		return obj;
 	}
 
 	override function load( obj : Dynamic ) {
 		super.load(obj);
-		if(obj.kind > 0)
-			shape = Type.createEnumIndex(Shape, obj.kind, obj.args);
-		else
-			shape = Quad;
+		switch(obj.kind){
+			case 0: shape = Quad;
+			case 1: shape = Type.createEnumIndex(Shape, obj.kind, obj.args);
+			case 2:
+				shape = Custom;
+				points = obj.points;
+		}
+		debugColor = obj.debugColor != null ? obj.debugColor : 0xFFFFFF;
 	}
 
 	override function updateInstance( ctx : Context, ?propName : String) {
 		super.updateInstance(ctx, propName);
-		if(ctx.local3d == null)
-			return;
-
+		//if(ctx.local3d == null) return;
 		var mesh : h3d.scene.Mesh = cast ctx.local3d;
 		mesh.primitive = makePrimitive();
-
-		if(hide.prefab.Material.hasOverride(this))
-			return;
-
-		var mat = mesh.material;
-		mat.mainPass.culling = None;
+		//if(!hide.prefab.Material.hasOverride(this)){
+			var mat = mesh.material;
+			mat.mainPass.culling = None;
+			mat.castShadows = false;
+			mat.color = h3d.Vector.fromColor(debugColor);
+			mat.color.a = 0.7;
+		//}
+		#if editor
+		if(editor != null)
+			editor.update(propName);
+		#end
 		// var layer = getParent(Layer);
 		// if(layer != null) {
 		// 	setColor(ctx, layer != null ? (layer.color | 0x40000000) : 0x40ff00ff);
@@ -50,8 +69,6 @@ class Polygon extends Object3D {
 		// 	mat.props = h3d.mat.MaterialSetup.current.getDefaults("opaque");
 		// 	mat.color.setColor(0xffffffff);
 		// }
-
-		mat.castShadows = false;
 	}
 
 	function getPrimCache() {
@@ -65,6 +82,10 @@ class Polygon extends Object3D {
 	}
 
 	function makePrimitive() {
+
+		if(shape == Custom)
+			return generateCustomPolygon();
+
 		var cache = getPrimCache();
 		var primitive : h3d.prim.Polygon = cache.get(shape);
 		if(primitive != null)
@@ -152,19 +173,53 @@ class Polygon extends Object3D {
 		if(ctx.local3d == null)
 			return;
 		var mesh = Std.instance(ctx.local3d, h3d.scene.Mesh);
-		if(mesh != null) {
+		if(mesh != null)
 			hide.prefab.Box.setDebugColor(color, mesh.material);
-		}
 		#end
 	}
 
+	public function generateCustomPolygon(){
+		var polyPrim : h3d.prim.Polygon = null;
+		if( points != null ){
+			var indexes = points.fastTriangulate();
+			var idx : hxd.IndexBuffer = new hxd.IndexBuffer();
+			for( i in indexes ) if( i != null ) idx.push(i);
+			var pts = [for( p in points ) new h3d.col.Point(p.x, p.y, 0)];
+			polyPrim = new h3d.prim.Polygon(pts, idx);
+			polyPrim.addNormals();
+			polyPrim.addUVs();
+			polyPrim.addTangents() ;
+			polyPrim.alloc(h3d.Engine.getCurrent());
+		}
+		return polyPrim;
+	}
+
+	public function getPrimitive( ctx : Context ) : h3d.prim.Polygon {
+		var mesh = Std.instance(ctx.local3d, h3d.scene.Mesh);
+		return Std.instance(mesh.primitive, h3d.prim.Polygon);
+	}
+
 	#if editor
 
+	override function setSelected( ctx : Context, b : Bool ) {
+		super.setSelected(ctx, b);
+		if( editor != null && shape == Custom)
+			editor.setSelected(ctx, b);
+	}
+
+	function createEditor( ctx : EditContext ){
+		if( editor == null )
+			editor = new PolygonEditor(this, ctx.properties.undo);
+		editor.editContext = ctx;
+	}
+
 	override function edit( ctx : EditContext ) {
 		super.edit(ctx);
+		createEditor(ctx);
 
+		var prevKind : Shape = this.shape;
 		var viewModel = {
-			kind: shape.getIndex(),
+			kind: shape.getName(),
 			segments: 24,
 			rings: 4,
 			innerRadius: 0.0,
@@ -172,23 +227,27 @@ class Polygon extends Object3D {
 		};
 
 		switch(shape) {
+			case Quad:
 			case Disc(seg, angle, inner, rings):
 				viewModel.segments = seg;
 				viewModel.angle = angle;
 				viewModel.innerRadius = inner;
+			case Custom:
 			default:
 		}
 
-		var group = new hide.Element('<div class="group" name="Polygon">
-				<dl>
-					<dt>Kind</dt><dd>
-						<select field="kind">
-							<option value="0">Quad</option>
-							<option value="1">Disc</option>
-						</select>
-					</dd>
-				</dl>
-			</div>
+		var group = new hide.Element('
+		<div class="group" name="Shape">
+			<dl>
+				<dt>Kind</dt><dd>
+					<select field="kind">
+						<option value="Quad">Quad</option>
+						<option value="Disc">Disc</option>
+						<option value="Custom">Custom</option>
+					</select>
+				</dd>
+			</dl>
+		</div>
 		');
 
 		var discProps = new hide.Element('
@@ -198,31 +257,59 @@ class Polygon extends Object3D {
 			<dt>Angle</dt><dd><input field="angle" type="range" min="0" max="360" /></dd>');
 
 		group.append(discProps);
+		var editorProps = editor.addProps(ctx);
 
 		function updateProps() {
-			if(viewModel.kind == 1)
-				discProps.show();
-			else
-				discProps.hide();
+			discProps.hide();
+			editorProps.hide();
+			switch(viewModel.kind){
+				case "Quad":
+				case "Disc": discProps.show();
+				case "Custom":
+					editorProps.show();
+					setSelected(ctx.getContext(this), true);
+				default:
+			}
 		}
-		updateProps();
+
+		ctx.properties.add( new hide.Element('
+			<div class="group" name="Params">
+				<dl><dt>Color</dt><dd><input type="color" field="debugColor"/></dd> </dl>
+			</div>'), this, function(pname) { ctx.onChange(this, pname); });
+
 		ctx.properties.add(group, viewModel, function(pname) {
-			if(pname == "kind") {
-				var cache = getPrimCache();
-				var prim = cache.get(shape);
-				prim.dispose();
-				cache.remove(shape);
+			if( pname == "kind" ) {
+				editor.reset();
+
+				if( prevKind != Custom ){
+					var cache = getPrimCache();
+					var prim = cache.get(shape);
+					if(prim != null){
+						prim.dispose();
+						cache.remove(shape);
+					}
+				}
+				else if( prevKind == Custom ){
+					var mesh = Std.instance(ctx.getContext(this).local3d, h3d.scene.Mesh);
+					if( mesh.primitive != null ) mesh.primitive.dispose(); // Dispose custom prim
+				}
+
+				prevKind = this.shape;
 			}
 
-			switch(viewModel.kind) {
-				case 1:
-					shape = Disc(viewModel.segments, viewModel.angle, viewModel.innerRadius, viewModel.rings);
-				default:
-					shape = Quad;
+			switch( viewModel.kind ) {
+				case "Quad": shape = Quad;
+				case "Disc": shape = Disc(viewModel.segments, viewModel.angle, viewModel.innerRadius, viewModel.rings);
+				case "Custom": shape = Custom;
+				default: shape = Quad;
 			}
+
 			updateProps();
 			ctx.onChange(this, pname);
 		});
+
+		updateProps();
+
 	}
 
 	override function getHideProps() : HideProps {

+ 11 - 13
hide/view/l3d/Level3D.hx

@@ -619,20 +619,18 @@ class Level3D extends FileView {
 		}
 
 		var color = getDisplayColor(p);
-		if(color == null)
-			color = 0x80ffffff;
-		else
+		if(color != null){
 			color = (color & 0xffffff) | 0xa0000000;
-
-		var box = p.to(hide.prefab.Box);
-		if(box != null) {
-			var ctx = sceneEditor.getContext(box);
-			box.setColor(ctx, color);
-		}
-		var poly = p.to(hide.prefab.l3d.Polygon);
-		if(poly != null) {
-			var ctx = sceneEditor.getContext(poly);
-			poly.setColor(ctx, color);
+			var box = p.to(hide.prefab.Box);
+			if(box != null) {
+				var ctx = sceneEditor.getContext(box);
+				box.setColor(ctx, color);
+			}
+			var poly = p.to(hide.prefab.l3d.Polygon);
+			if(poly != null) {
+				var ctx = sceneEditor.getContext(poly);
+				poly.setColor(ctx, color);
+			}
 		}
 	}