Browse Source

Support other modes, display fixes

trethaller 7 years ago
parent
commit
f054c2399c
4 changed files with 194 additions and 55 deletions
  1. 24 12
      bin/style.css
  2. 29 15
      bin/style.less
  3. 129 24
      hide/comp/CurveEditor.hx
  4. 12 4
      hide/prefab/Curve.hx

+ 24 - 12
bin/style.css

@@ -447,6 +447,12 @@ input[type=checkbox]:checked:after {
 .hide-curve-editor line {
   stroke-width: 1px;
 }
+.hide-curve-editor rect {
+  shape-rendering: crispEdges;
+}
+.hide-curve-editor .grid line {
+  shape-rendering: crispEdges;
+}
 .hide-curve-editor .grid .hgrid line {
   stroke: #444;
 }
@@ -454,38 +460,44 @@ input[type=checkbox]:checked:after {
   stroke: #444;
 }
 .hide-curve-editor .grid line.axis {
-  stroke: #fff;
+  stroke: #747474;
+  shape-rendering: crispEdges;
 }
 .hide-curve-editor .graph .selection rect {
-  fill: #0077ff;
-  fill-opacity: 0.5;
+  fill: #ffffff;
+  fill-opacity: 0.1;
   stroke: #ffffff;
+  opacity: 0.3;
   mix-blend-mode: screen;
 }
 .hide-curve-editor .graph .curve line,
 .hide-curve-editor .graph path {
-  stroke: #f00;
+  stroke: #ff1f1f;
   stroke-width: 1px;
   fill: none;
 }
 .hide-curve-editor .graph .vectors line {
-  stroke: #fff;
+  stroke: rgba(207, 207, 207, 0.603);
+}
+.hide-curve-editor .graph .vectors line.selected {
+  stroke: #ffffff;
 }
 .hide-curve-editor .graph .handles circle,
 .hide-curve-editor .graph .handles rect {
-  stroke: #fff;
-  fill: #282828;
-}
-.hide-curve-editor .graph .handles rect.selected,
-.hide-curve-editor .graph .handles rect:hover.selected {
-  fill: #f00;
+  fill: rgba(187, 187, 187, 0.644);
 }
 .hide-curve-editor .graph .handles circle:hover,
 .hide-curve-editor .graph .handles rect:hover {
-  fill: #555;
+  fill: #fff;
   stroke: #fff;
   stroke-width: 2px;
 }
+.hide-curve-editor .graph .handles circle.selected,
+.hide-curve-editor .graph .handles circle:hover.selected,
+.hide-curve-editor .graph .handles rect.selected,
+.hide-curve-editor .graph .handles rect:hover.selected {
+  fill: #ffffff;
+}
 .hide-curve-editor .selection-overlay rect {
   mix-blend-mode: multiply;
   fill: #888;

+ 29 - 15
bin/style.less

@@ -483,54 +483,68 @@ input[type=checkbox] {
 
 /* Curve editor */
 .hide-curve-editor {
+	@selectCol: rgb(255, 255, 255);
+	@lineCol: rgb(255, 31, 31);
+
 	border: 1px solid black;
 	line {
 		stroke-width: 1px;
 	}
 
+	rect {
+		shape-rendering: crispEdges;
+	}
+
 	.grid {
+		line {
+			shape-rendering: crispEdges;
+		}
 		.hgrid line { stroke: #444; }
 		.vgrid line { stroke: #444; }
 		line.axis {
-			stroke: #fff;
+			stroke: rgb(116, 116, 116);
+			shape-rendering: crispEdges;
 		}
 	}
 
 	.graph {
 
 		.selection rect {
-			fill: rgb(0, 119, 255);
-			fill-opacity: 0.5;
+			fill: @selectCol;
+			fill-opacity: 0.1;
 			stroke: rgb(255, 255, 255);
-			//opacity: 0.5;
+			opacity: 0.3;
 			mix-blend-mode: screen;
 		}
 
 		.curve line, path {
-			stroke: #f00;
+			stroke: @lineCol;
 			stroke-width: 1px;
 			fill: none;
 		}
 
-		.vectors line {
-			stroke: #fff;
+		.vectors {
+			line {
+				stroke: rgba(207, 207, 207, 0.603);
+			}
+
+			line.selected {
+				stroke: @selectCol;
+			}
 		}
 
 		.handles {	
 			circle, rect {
-				stroke: #fff;
-				fill: #282828;
-			}
-
-			rect.selected, rect:hover.selected {
-				fill: #f00;
+				fill: rgba(187, 187, 187, 0.644);
 			}
-
 			circle:hover, rect:hover {
-				fill: #555;
+				fill: #fff;
 				stroke: #fff;
 				stroke-width: 2px;
 			}
+			circle.selected, circle:hover.selected, rect.selected, rect:hover.selected {
+				fill: @selectCol;
+			}
 		}
 	}
 

+ 129 - 24
hide/comp/CurveEditor.hx

@@ -63,6 +63,10 @@ class CurveEditor extends Component {
 				startPan(e);
 			}
 		});
+		root.contextmenu(function(e) {
+			e.preventDefault();
+			return false;
+		});
 		root.on("mousewheel", function(e) {
 			var step = e.originalEvent.wheelDelta > 0 ? 1.0 : -1.0;
 			if(hxd.Key.isDown(hxd.Key.SHIFT))
@@ -73,8 +77,6 @@ class CurveEditor extends Component {
 		});
 		div.keydown(function(e) {
 			if(e.keyCode == 46) {
-				var backup = curve.keys.copy();
-				var selBackup = selectedKeys.copy();
 				var newVal = [for(k in curve.keys) if(selectedKeys.indexOf(k) < 0) k];
 				curve.keys = newVal;
 				selectedKeys = [];
@@ -96,19 +98,75 @@ class CurveEditor extends Component {
 		if(val == null)
 			val = curve.getVal(time);
 
-		var prev = curve.keys[index-1];
-		var next = curve.keys[index];
-
 		var key : hide.prefab.Curve.CurveKey = {
 			time: time,
 			value: val,
-			prevHandle: { dt : prev != null ? (prev.time - time) / 4 : -0.5, dv : 0. },
-			nextHandle: { dt : next != null ? (next.time - time) / 4 : 0.5, dv : 0. },
+			mode: Linear
 		};
 		curve.keys.insert(index, key);
 		afterChange();
 	}
 
+	function fixKey(key : hide.prefab.Curve.CurveKey) {
+		var index = curve.keys.indexOf(key);
+		var prev = curve.keys[index-1];
+		var next = curve.keys[index+1];
+
+		inline function addPrevH() {
+			if(key.prevHandle == null)
+				key.prevHandle = { dt: prev != null ? (prev.time - key.time) / 3 : -0.5, dv: 0};
+		}
+		inline function addNextH() {
+			if(key.nextHandle == null)
+				key.nextHandle = { dt: next != null ? (next.time - key.time) / 3 : -0.5, dv: 0};
+		}
+		switch(key.mode) {
+			case Aligned:
+				addPrevH();
+				addNextH();
+				var pa = hxd.Math.atan2(key.prevHandle.dv, key.prevHandle.dt);
+				var na = hxd.Math.atan2(key.nextHandle.dv, key.nextHandle.dt);
+				if(hxd.Math.abs(hxd.Math.angle(pa - na)) < Math.PI - (1./180.)) {
+					key.nextHandle.dt = -key.prevHandle.dt;
+					key.nextHandle.dv = -key.prevHandle.dv;
+				}
+			case Free:
+				addPrevH();
+				addNextH();
+			case Linear:
+				key.nextHandle = null;
+				key.prevHandle = null;
+			case Constant:
+				key.nextHandle = null;
+				key.prevHandle = null;
+		}
+
+		if(key.time < 0)
+			key.time = 0;
+
+		if(prev != null && key.time < prev.time)
+			key.time = prev.time + 0.01;
+		if(next != null && key.time > next.time)
+			key.time = next.time - 0.01;
+
+		if(next != null && key.nextHandle != null) {
+			var slope = key.nextHandle.dv / key.nextHandle.dt;
+			slope = hxd.Math.clamp(slope, -1000, 1000);
+			if(key.nextHandle.dt + key.time > next.time) {
+				key.nextHandle.dt = next.time - key.time;
+				key.nextHandle.dv = slope * key.nextHandle.dt;
+			}
+		}
+		if(prev != null && key.prevHandle != null) {
+			var slope = key.prevHandle.dv / key.prevHandle.dt;
+			slope = hxd.Math.clamp(slope, -1000, 1000);
+			if(key.prevHandle.dt + key.time < prev.time) {
+				key.prevHandle.dt = prev.time - key.time;
+				key.prevHandle.dv = slope * key.prevHandle.dt;
+			}
+		}
+	}
+
 	function startSelectRect(p1x: Float, p1y: Float) {
 		var offset = root.offset();
 		var selX = p1x;
@@ -152,12 +210,12 @@ class CurveEditor extends Component {
 		});
 	}
 
-	inline function xt(x: Float) return (x + xOffset) * xScale;
-	inline function yt(y: Float) return (y + yOffset) * yScale + height/2;
+	inline function xt(x: Float) return Math.round((x + xOffset) * xScale);
+	inline function yt(y: Float) return Math.round((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 startDrag(el: Element, onMove, onStop) {
+	function startDrag(el: Element, onMove, onStop) {
 		el.mousemove(onMove);
 		el.mouseup(function(e) {
 			el.off("mousemove");
@@ -190,6 +248,17 @@ class CurveEditor extends Component {
 		refresh();
 	}
 
+	public function resetView() {
+		// var margin = 20;
+		// var minT = ixt(-margin);
+		// var maxT = ixt(width + margin);
+		// var minV = iyt(0);
+		// var maxV = iyt(height);
+		// xOffset = minT;
+		// xScale = (maxT - minT)
+		// TODO
+	}
+
 	public function refresh(?anim: Bool = false, ?animKey: hide.prefab.Curve.CurveKey) {
 		width = Math.round(svg.element.width());
 		height = Math.round(svg.element.height());
@@ -228,10 +297,11 @@ class CurveEditor extends Component {
 				l.addClass("axis");
 		}
 
-
 		var curveGroup = svg.group(graphGroup, "curve");
 		var vectorsGroup = svg.group(graphGroup, "vectors");
 		var handlesGroup = svg.group(graphGroup, "handles");
+		var tangentsHandles = svg.group(handlesGroup, "tangents");
+		var keyHandles = svg.group(handlesGroup, "keys");
 		var selection = svg.group(graphGroup, "selection");
 		var size = 7;
 
@@ -242,16 +312,25 @@ class CurveEditor extends Component {
 			for(ik in 1...keys.length) {
 				var prev = keys[ik-1];
 				var cur = keys[ik];
-				lines.push('C ${xt(prev.time + prev.nextHandle.dt)}, ${yt(prev.value + prev.nextHandle.dv)}
-					${xt(cur.time + cur.prevHandle.dt)}, ${yt(cur.value + cur.prevHandle.dv)}
+				lines.push('C
+					${xt(prev.time + (prev.nextHandle != null ? prev.nextHandle.dt : 0.))}, ${yt(prev.value + (prev.nextHandle != null ? prev.nextHandle.dv : 0.))}
+					${xt(cur.time + (cur.prevHandle != null ? cur.prevHandle.dt : 0.))}, ${yt(cur.value + (cur.prevHandle != null ? cur.prevHandle.dv : 0.))}
 					${xt(cur.time)}, ${yt(cur.value)} ');
 			}
 			svg.make(curveGroup, "path", {d: lines.join("")});
+			// var pts = curve.sample(200);
+			// var poly = [];
+			// for(i in 0...pts.length) {
+			// 	var x = xt(curve.duration * i / (pts.length - 1));
+			// 	var y = yt(pts[i]);
+			// 	poly.push(new h2d.col.Point(x, y));
+			// }
+			// svg.polygon(curveGroup, poly);
 		}
 
 
-		function addRect(x: Float, y: Float) {
-			return svg.rect(handlesGroup, x - Math.floor(size/2), y - Math.floor(size/2), size, size).attr({
+		function addRect(group, x: Float, y: Float) {
+			return svg.rect(group, x - Math.floor(size/2), y - Math.floor(size/2), size, size).attr({
 				"shape-rendering": "crispEdges"
 			});
 		}
@@ -259,10 +338,10 @@ class CurveEditor extends Component {
 		for(key in curve.keys) {
 			var kx = xt(key.time);
 			var ky = yt(key.value);
-			var keyHandle = addRect(kx, ky);
-			if(selectedKeys.indexOf(key) >= 0) {
+			var keyHandle = addRect(keyHandles, kx, ky);
+			var selected = selectedKeys.indexOf(key) >= 0;
+			if(selected)
 				keyHandle.addClass("selected");
-			}
 			if(!anim) {
 				keyHandle.mousedown(function(e) {
 					if(e.which != 1) return;
@@ -278,14 +357,33 @@ class CurveEditor extends Component {
 						var nky = iyt(ly);
 						key.time = nkx;
 						key.value = nky;
+						fixKey(key);
 						refresh(true, key);
 					}, function(e) {
 						selectedKeys = [key];
+						fixKey(key);
 						afterChange();
 					});
 					selectedKeys = [key];
 					refresh();
 				});
+				keyHandle.contextmenu(function(e) {
+					e.preventDefault();
+					function setMode(m: hide.prefab.Curve.CurveKeyMode) {
+						key.mode = m;
+						fixKey(key);
+						refresh();
+					}
+					new ContextMenu([
+						{ label : "Mode", menu :[
+							{ label : "Aligned", checked: key.mode == Aligned, click : setMode.bind(Aligned) },
+							{ label : "Free", checked: key.mode == Free, click : setMode.bind(Free) },
+							{ label : "Linear", checked: key.mode == Linear, click : setMode.bind(Linear) },
+							{ label : "Constant", checked: key.mode == Constant, click : setMode.bind(Constant) },
+						] }
+					]);
+					return false;
+				});
 			}
 			function addHandle(next: Bool) {
 				var handle = next ? key.nextHandle : key.prevHandle;
@@ -293,8 +391,12 @@ class CurveEditor extends Component {
 				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);
+				var line = svg.line(vectorsGroup, kx, ky, px, py);
+				var circle = svg.circle(tangentsHandles, px, py, size/2);
+				if(selected) {
+					line.addClass("selected");
+					circle.addClass("selected");
+				}
 				if(anim)
 					return circle;
 				circle.mousedown(function(e) {
@@ -314,9 +416,12 @@ class CurveEditor extends Component {
 						var ndv = iyt(ly) - key.value;
 						handle.dt = ndt;
 						handle.dv = ndv;
-						var angle = Math.atan2(ly - ky, lx - kx);
-						other.dt = Math.cos(angle + Math.PI) * otherLen / xScale;
-						other.dv = Math.sin(angle + Math.PI) * otherLen / yScale;
+						if(key.mode == Aligned) {
+							var angle = Math.atan2(ly - ky, lx - kx);
+							other.dt = Math.cos(angle + Math.PI) * otherLen / xScale;
+							other.dv = Math.sin(angle + Math.PI) * otherLen / yScale;
+						}
+						fixKey(key);
 						refresh(true, key);
 					}, function(e) {
 						afterChange();
@@ -330,7 +435,7 @@ class CurveEditor extends Component {
 			}
 		}
 
-		if(selectedKeys.length > 0) {
+		if(selectedKeys.length > 1) {
 			var bounds = new h2d.col.Bounds();
 			for(key in selectedKeys)
 				bounds.addPoint(new h2d.col.Point(xt(key.time), yt(key.value)));

+ 12 - 4
hide/prefab/Curve.hx

@@ -15,9 +15,9 @@ typedef CurveHandle = {
 typedef CurveKey = {
 	time: Float,
 	value: Float,
+	mode: CurveKeyMode,	
 	?prevHandle: CurveHandle,
 	?nextHandle: CurveHandle,
-	?mode: CurveKeyMode,
 }
 
 typedef CurveKeys = Array<CurveKey>;
@@ -68,7 +68,7 @@ class Curve extends Prefab {
 
 		var cur = keys[idx];
 		var next = keys[idx + 1];
-		if(next == null)
+		if(next == null || cur.mode == Constant)
 			return cur.value;
 		
 		var minT = 0.;
@@ -76,11 +76,19 @@ class Curve extends Prefab {
 		var maxDelta = 1./ 25.;
 
 		inline function sampleTime(t) {
-			return bezier(cur.time, cur.time + cur.nextHandle.dt, next.time + next.prevHandle.dt, next.time, t);
+			return bezier(
+				cur.time,
+				cur.time + (cur.nextHandle != null ? cur.nextHandle.dt : 0.),
+				next.time + (next.prevHandle != null ? next.prevHandle.dt : 0.),
+				next.time, t);
 		}
 
 		inline function sampleVal(t) {
-			return bezier(cur.value, cur.value + cur.nextHandle.dv, next.value + next.prevHandle.dv, next.value, t);
+			return bezier(
+				cur.value,
+				cur.value + (cur.nextHandle != null ? cur.nextHandle.dv : 0.),
+				next.value + (next.prevHandle != null ? next.prevHandle.dv : 0.),
+				next.value, t);
 		}
 
 		while( maxT - minT > maxDelta ) {