瀏覽代碼

First pass on Curve Editor

trethaller 7 年之前
父節點
當前提交
a5cc05ed30
共有 7 個文件被更改,包括 386 次插入1 次删除
  1. 2 1
      bin/package.json
  2. 36 0
      bin/style.css
  3. 37 0
      bin/style.less
  4. 165 0
      hide/comp/CurveEditor.hx
  5. 64 0
      hide/comp/SVG.hx
  6. 46 0
      hide/prefab/Curve.hx
  7. 36 0
      hide/prefab/fx/FXScene.hx

+ 2 - 1
bin/package.json

@@ -7,5 +7,6 @@
 		"height" : 600,
 		"show" : true
 	},
-	"main" : "app.html"
+	"main" : "app.html",
+	"js-flags": "--expose-gc"
 }

+ 36 - 0
bin/style.css

@@ -440,6 +440,42 @@ input[type=checkbox]:checked:after {
 .hide-properties .group > .content {
   margin-bottom: 5px;
 }
+/* Curve editor */
+.hide-curve-editor {
+  border: 1px solid black;
+}
+.hide-curve-editor line {
+  stroke-width: 1px;
+}
+.hide-curve-editor .hgrid line {
+  stroke: #444;
+}
+.hide-curve-editor .vgrid line {
+  stroke: #444;
+}
+.hide-curve-editor .curve line {
+  stroke: #888;
+  stroke-width: 2px;
+}
+.hide-curve-editor line.axis {
+  stroke: #fff;
+}
+.hide-curve-editor .vectors line {
+  stroke: #fff;
+}
+.hide-curve-editor .handles circle,
+.hide-curve-editor .handles rect {
+  stroke: #fff;
+  fill: #282828;
+}
+.hide-curve-editor .handles circle:hover,
+.hide-curve-editor .handles rect:hover {
+  fill: #555;
+}
+/* FX Editor */
+.fx-animpanel {
+  flex-basis: 200px;
+}
 /* Golden Layout Fixes */
 .lm_header .lm_tabs {
   z-index: 1;

+ 37 - 0
bin/style.less

@@ -481,6 +481,43 @@ input[type=checkbox] {
 
 }
 
+/* Curve editor */
+.hide-curve-editor {
+	border: 1px solid black;
+
+	line {
+		stroke-width: 1px;
+	}
+	.hgrid line { stroke: #444; }
+	.vgrid line { stroke: #444; }
+	.curve line {
+		stroke: #888;
+		stroke-width: 2px;
+	}
+	line.axis {
+		stroke: #fff;
+	}
+
+	.vectors line {
+		stroke: #fff;
+	}
+
+	.handles {	
+		circle, rect {
+			stroke: #fff;
+			fill: #282828;
+		}
+		circle:hover, rect:hover {
+			fill: #555;
+		}
+	}
+}
+
+/* FX Editor */
+.fx-animpanel {
+	flex-basis: 200px;
+}
+
 /* Golden Layout Fixes */
 
 .lm_header .lm_tabs {

+ 165 - 0
hide/comp/CurveEditor.hx

@@ -0,0 +1,165 @@
+package hide.comp;
+
+class CurveEditor extends Component {
+
+	public var xScale = 200.;
+	public var yScale = 30.;
+	public var xOffset = 0.;
+	public var yOffset = 0.;
+
+	public var curve : hide.prefab.Curve;
+
+	var svg : hide.comp.SVG;
+	var width = 0;
+	var height = 0;
+
+	public function new(root, curve : hide.prefab.Curve) {
+		super(root);
+		this.curve = curve;
+		svg = new hide.comp.SVG(root);
+		svg.element.resize((e) -> refresh());
+		svg.element.addClass("hide-curve-editor");
+	}
+
+	inline function xt(x: Float) return (x + xOffset) * xScale;
+	inline function yt(y: Float) return (y + yOffset) * yScale + height/2;
+	inline function ixt(px: Float) return px / xScale - xOffset;
+	inline function iyt(py: Float) return (py - height/2) / yScale - yOffset;
+
+	public function refresh(?animKey: hide.prefab.Curve.CurveKey) {
+		width = Math.round(svg.element.width());
+		height = Math.round(svg.element.height());
+		var root = svg.element;
+		svg.clear();
+		if(animKey == null) {
+			untyped window.gc();
+		}
+
+		var graph = svg.group(root, "graph"); //, {transform: 'translate(0, ${height/2})'});
+		var background = svg.group(graph, "background");
+		// svg.line(background, 0, 0, width, 0).addClass("axis");
+
+		// var gridSteps = Math.ceil(curve.duration);
+		// var hgrid = svg.group(root, "hgrid");
+		// for(i in 0...gridSteps) {
+		//     svg.line(hgrid, xt(i), 0, xt(i), height);
+		// }
+
+		var minX = Math.floor(ixt(0));
+		var maxX = Math.ceil(ixt(width));
+		var hgrid = svg.group(root, "hgrid");
+		for(ix in minX...(maxX+1)) {
+			var l = svg.line(hgrid, xt(ix), 0, xt(ix), height).attr({
+				"shape-rendering": "crispEdges"
+			});
+			if(ix == 0)
+				l.addClass("axis");
+		}
+
+		var minY = Math.floor(iyt(0));
+		var maxY = Math.ceil(iyt(height));
+		var vgrid = svg.group(root, "vgrid");
+		for(iy in minY...(maxY+1)) {
+			var l = svg.line(vgrid, 0, yt(iy), width, yt(iy)).attr({
+				"shape-rendering": "crispEdges"
+			});
+			if(iy == 0)
+				l.addClass("axis");
+		}
+
+		var curveGroup = svg.group(root, "curve");
+		var vectorsGroup = svg.group(root, "vectors");
+		var handlesGroup = svg.group(root, "handles");
+		var size = 7;
+
+		for(ik in 0...curve.keys.length-1) {
+			var pt = curve.keys[ik];
+			var nextPt = curve.keys[ik+1];
+			svg.line(curveGroup, xt(pt.time), yt(pt.value), xt(nextPt.time), yt(nextPt.value));
+		}
+
+
+		function addRect(x: Float, y: Float) {
+			return svg.rect(handlesGroup, x - Math.floor(size/2), y - Math.floor(size/2), size, size).attr({
+				"shape-rendering": "crispEdges"
+			});
+		}
+
+		for(key in curve.keys) {
+			if(animKey != null && key != animKey)
+				continue;
+			var kx = xt(key.time);
+			var ky = yt(key.value);
+			function addHandle(handle) {
+				if(handle == null) return null;
+				var px = xt(key.time + handle.dt);
+				var py = yt(key.value + handle.dv);
+				svg.line(vectorsGroup, kx, ky, px, py);
+				var circle = svg.circle(handlesGroup, px, py, size/2);
+				return circle;
+			}
+			var pHandle = addHandle(key.prevHandle);
+			var nHandle = addHandle(key.nextHandle);
+			var keyHandle = addRect(kx, ky);
+
+			if(animKey != null)
+				continue;
+			keyHandle.mousedown(function(e) {
+				var startT = key.time;
+				var startV = key.value;
+				var offx = e.clientX - keyHandle.offset().left;
+				var offy = e.clientY - keyHandle.offset().top;
+				var offset = svg.element.offset();
+				root.mousemove(function(e) {
+					var lx = e.clientX - offset.left - offx;
+					var ly = e.clientY - offset.top - offy;
+					var nkx = ixt(lx);
+					var nky = iyt(ly);
+					key.time = nkx;
+					key.value = nky;
+					refresh(key);
+					// keyHandle.attr({
+					// 	x: lx,
+					// 	y: ly});
+					// pHandle.attr({
+					// 	cx: xt(nkx + key.prevHandle.dt),
+					// 	cy: yt(nky + key.prevHandle.dv)});
+					// nHandle.attr({
+					// 	cx: xt(nkx + key.nextHandle.dt),
+					// 	cy: yt(nky + key.nextHandle.dv)});
+				});
+				root.mouseup(function(e) {
+					root.off("mousemove");
+					root.off("mouseup");
+					refresh();
+				});
+			});
+			pHandle.mousedown(function(e) {
+				var startT = key.prevHandle.dt;
+				var startV = key.prevHandle.dv;
+				var offx = e.clientX - pHandle.offset().left;
+				var offy = e.clientY - pHandle.offset().top;
+				var offset = svg.element.offset();
+				var nhLen = hxd.Math.distance(key.nextHandle.dt * xScale, key.nextHandle.dv * yScale);
+				var phLen = hxd.Math.distance(key.prevHandle.dt * xScale, key.prevHandle.dv * yScale);
+				root.mousemove(function(e) {
+					var lx = e.clientX - offset.left - offx;
+					var ly = e.clientY - offset.top - offy;
+					var ndt = ixt(lx) - key.time;
+					var ndv = iyt(ly) - key.value;
+					key.prevHandle.dt = ndt;
+					key.prevHandle.dv = ndv;
+					var angle = Math.atan2(ly - ky, lx - kx);
+					key.nextHandle.dt = Math.cos(angle + Math.PI) * nhLen / xScale;
+					key.nextHandle.dv = Math.sin(angle + Math.PI) * nhLen / yScale;
+					refresh(key);					
+				});
+				root.mouseup(function(e) {
+					root.off("mousemove");
+					root.off("mouseup");
+					refresh();
+				});
+			});
+		}
+	}
+}

+ 64 - 0
hide/comp/SVG.hx

@@ -0,0 +1,64 @@
+package hide.comp;
+import hide.Element;
+
+class SVG extends Component {
+
+	var document = null;
+	public var element(default, null) : hide.Element = null;
+
+	public function new(root: hide.Element) {
+		super(root);
+		document = root[0].ownerDocument;
+
+		var e = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+		element = new Element(e);
+		element.attr("width", "100%");
+		element.attr("height", "100%");
+		root.append(element);
+	}
+
+	public function clear() {
+		element.empty();
+	}
+
+	public function add(el: Element) {
+		element.append(el);
+	}
+
+	public function make(?parent: Element, name: String, ?attr: Dynamic, ?style: Dynamic) {
+		var e = document.createElementNS('http://www.w3.org/2000/svg', name);
+		var el = new Element(e);
+		if(attr != null)
+			el.attr(attr);
+		if(style != null)
+			el.css(style);
+		if(parent != null)
+			parent.append(el);
+		return el;
+	}
+
+	public function circle(?parent: Element, x:Float, y:Float, radius:Float, ?style:Dynamic) {
+		return make(parent, "circle", {cx:x, cy:y, r:radius}, style);
+	}
+
+	public function rect(?parent: Element, x:Float, y:Float, width:Float, height:Float, ?style:Dynamic) {
+		return make(parent, "rect", {x:x, y:y, width:width, height:height}, style);
+	}
+
+	public function line(?parent: Element, x1:Float, y1:Float, x2:Float, y2:Float, ?style:Dynamic) {
+		return make(parent, "line", {x1:x1, y1:y1, x2:x2, y2:y2}, style);
+	}
+
+	public function group(?parent: Element, ?className: String, ?attr: Dynamic) {
+		var g = make(parent, "g", attr);
+		if(className != null)
+			g.addClass(className);
+		return g;
+	}
+	
+	// public function text(x: Float, y: Float, text: String, ?style: Dynamic) {
+	// 	var e = make("text", {x:x, y:y}, style);
+	// 	e.text(text);
+	// 	return e;
+	// }
+}

+ 46 - 0
hide/prefab/Curve.hx

@@ -0,0 +1,46 @@
+package hide.prefab;
+
+typedef CurveHandle = {
+	dt: Float,
+	dv: Float
+}
+
+enum CurveKeyMode {
+	Aligned;
+	Free;
+	Linear;
+	Constant;
+}
+
+typedef CurveKey = {
+	time: Float,
+	value: Float,
+	?prevHandle: CurveHandle,
+	?nextHandle: CurveHandle,
+	?mode: CurveKeyMode,
+}
+
+typedef CurveKeys = Array<CurveKey>;
+
+class Curve extends Prefab {
+
+	public var duration : Float = 0.;
+	public var keys : CurveKeys = [];
+
+   	public function new(?parent) {
+		super(parent);
+		this.type = "curve";
+	}
+
+	override function load(o:Dynamic) {
+		duration = o.duration;
+		keys = o.keys;
+	}
+
+	override function save() {
+		return {
+			duration: duration,
+			keys: keys
+		};
+	}
+}

+ 36 - 0
hide/prefab/fx/FXScene.hx

@@ -0,0 +1,36 @@
+package hide.prefab.fx;
+
+class FXScene extends Library {
+
+	public function new() {
+		super();
+		type = "fx";
+	}
+
+	override function save() {
+		var obj : Dynamic = super.save();
+		return obj;
+	}
+
+	override function load( obj : Dynamic ) {
+		super.load(obj);
+	}
+
+	override function edit( ctx : EditContext ) {
+		#if editor
+		var props = new hide.Element('
+			<div class="group" name="Level">
+			</div>
+		');
+		ctx.properties.add(props, this, function(pname) {
+			ctx.onChange(this, pname);
+		});
+		#end
+	}
+
+	override function getHideProps() {
+		return { icon : "cube", name : "FX", fileSource : ["fx"] };
+	}
+
+	static var _ = Library.register("fx", FXScene);
+}