├── .gitignore ├── src ├── node │ ├── index.js │ ├── group.js │ ├── primitives.js │ ├── node.js │ └── bezier.js ├── render.js ├── math.js ├── global.js ├── scene.js └── main.js └── docs ├── index.html ├── app.js └── app.js.map /.gitignore: -------------------------------------------------------------------------------- 1 | /.build 2 | /_* 3 | node_modules 4 | .DS_Store 5 | .vscode 6 | *~ 7 | *.sublime* 8 | -------------------------------------------------------------------------------- /src/node/index.js: -------------------------------------------------------------------------------- 1 | export { Node, DrawableNode, Stroke } from "./node" 2 | export { GroupNode } from "./group" 3 | export { LineNode, DiscNode } from "./primitives" 4 | export { CubicBezierNode } from "./bezier" 5 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | rays 11 | 12 | 34 | 35 | 36 | 37 | Source 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/node/group.js: -------------------------------------------------------------------------------- 1 | import { Node } from "./index" 2 | 3 | export class GroupNode extends Node { 4 | constructor(position, children) { 5 | super(position) 6 | this.children = new Set(children) 7 | } 8 | 9 | add(n) { 10 | if (n.parent) { 11 | n.parent.remove(n) 12 | } 13 | this.children.add(n) 14 | n.parent = this 15 | return n 16 | } 17 | 18 | remove(n) { 19 | if (this.children.delete(n)) { 20 | n.parent = null 21 | } 22 | } 23 | 24 | updateAndDraw(g, time) { 25 | if (!this.visible) { 26 | return 27 | } 28 | if (this.needsRecompute) { 29 | this.recompute() 30 | } 31 | let prevtr = g.getTransform() 32 | g.translate(this.position.x, this.position.y) 33 | this.update(time, g) 34 | for (let n of this.children) { 35 | n.updateAndDraw(g, time) 36 | } 37 | g.setTransform(prevtr) 38 | } 39 | 40 | pointFromSceneSpace(p) { 41 | return p.sub(this.position) 42 | } 43 | 44 | hitTest(p) { 45 | p = p.sub(this.position) 46 | for (let n of this.children) { 47 | let ev = n.hitTest(p) 48 | if (ev) { 49 | return ev 50 | } 51 | } 52 | return null 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/render.js: -------------------------------------------------------------------------------- 1 | export class Renderer extends EventEmitter { 2 | constructor() { 3 | super() 4 | this.canvas = $('canvas') 5 | this.g = this.canvas.getContext('2d') 6 | this.scale = 1 7 | this.updateScale() 8 | this.resizeCanvasToWindow() 9 | this._onWindowResize = () => { 10 | this.updateScale() 11 | this.resizeCanvasToWindow() 12 | this.dispatchEvent("resize") 13 | } 14 | window.addEventListener("resize", this._onWindowResize) 15 | } 16 | 17 | finalize() { 18 | window.removeEventListener("resize", this._onWindowResize) 19 | } 20 | 21 | updateScale() { 22 | this.scale = (window.devicePixelRatio || 1) / ( 23 | this.g.backingStorePixelRatio || 24 | this.g.webkitBackingStorePixelRatio || 25 | this.g.mozBackingStorePixelRatio || 26 | this.g.msBackingStorePixelRatio || 27 | this.g.oBackingStorePixelRatio || 28 | 1 29 | ) 30 | } 31 | 32 | resizeCanvasToWindow() { 33 | // this.resizeCanvas(window.innerWidth, window.innerWidth * (3/4)) 34 | this.resizeCanvas(window.innerWidth, window.innerWidth) 35 | } 36 | 37 | resizeCanvas(width, height) { 38 | this.canvas.style.zoom = String(1 / this.scale) 39 | this.canvas.width = width * this.scale 40 | this.canvas.height = height * this.scale 41 | } 42 | 43 | render(scene, time) { 44 | const g = this.g 45 | g.setTransform(this.scale, 0, 0, this.scale, 0, 0) 46 | if (scene.transform) { 47 | g.transform(scene.transform) 48 | } 49 | scene.updateAndDraw(g, time) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/node/primitives.js: -------------------------------------------------------------------------------- 1 | import { vec } from "../math" 2 | import { DrawableNode } from "./node" 3 | 4 | export class LineNode extends DrawableNode { 5 | constructor(start, end, stroke) { 6 | super(start, null, stroke || Stroke.default) 7 | this.end = end || vec(0, 0) 8 | } 9 | 10 | draw(g) { 11 | super.draw(g) 12 | g.beginPath() 13 | g.moveTo(this.position.x, this.position.y) 14 | g.lineTo(this.end.x, this.end.y) 15 | g.stroke() 16 | } 17 | 18 | get length() { 19 | return this.position.distanceTo(this.end) 20 | } 21 | 22 | set length(len) { 23 | let angle = this.position.angleTo(this.end) 24 | this.end = vec( 25 | this.position.x + len * Math.cos(angle), 26 | this.position.y + len * Math.sin(angle) 27 | ) 28 | } 29 | 30 | rotate(degrees) { 31 | let radians = (Math.PI / 180) * degrees 32 | let cos = Math.cos(radians) 33 | let sin = Math.sin(radians) 34 | let p1 = this.position 35 | let p2 = this.end 36 | this.end = vec( 37 | (cos * (p2.x - p1.x)) + (sin * (p2.y - p1.y)) + p1.x, 38 | (cos * (p2.y - p1.y)) - (sin * (p2.x - p1.x)) + p1.y 39 | ) 40 | } 41 | 42 | hitTest(p) { return null } 43 | } 44 | 45 | 46 | export class DiscNode extends DrawableNode { 47 | constructor(position, radius, fill, stroke) { 48 | super(position, fill, stroke) 49 | this.radius = radius || 10 50 | this.size = this.radius * 2 51 | } 52 | 53 | recompute() { 54 | super.recompute() 55 | this.bounds.x = this.position.x - this.radius 56 | this.bounds.y = this.position.y - this.radius 57 | this.bounds.width = this.radius * 2 58 | this.bounds.height = this.radius * 2 59 | } 60 | 61 | draw(g) { 62 | super.draw(g) 63 | g.beginPath() 64 | g.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false) 65 | g.closePath() 66 | this.fill && g.fill() 67 | this.stroke && g.stroke() 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/math.js: -------------------------------------------------------------------------------- 1 | export function rad2deg(radians) { 2 | return radians * (180 / Math.PI) 3 | } 4 | 5 | export function deg2rad(degrees) { 6 | return degrees * (Math.PI / 180) 7 | } 8 | 9 | 10 | export class Rect { 11 | constructor(x, y, width, height) { 12 | this.x = x 13 | this.y = y 14 | this.width = width 15 | this.height = height 16 | } 17 | 18 | containsPoint(v) { 19 | return ( 20 | v.x >= this.x && v.x <= this.x + this.width && 21 | v.y >= this.y && v.y <= this.y + this.width 22 | ) 23 | } 24 | 25 | translate(v) { 26 | return new Rect(this.x + v.x, this.y + v.y, this.width, this.height) 27 | } 28 | 29 | toString() { 30 | return `(${this.x}, ${this.y}, ${this.width}, ${this.height})` 31 | } 32 | } 33 | 34 | 35 | export class Vec /*extends Array*/ { 36 | constructor(x, y) { 37 | // super(x, y) 38 | this.x = x 39 | this.y = y 40 | } 41 | 42 | distanceTo(v) { // euclidean distance between this and v 43 | return Math.sqrt(this.squaredDistanceTo(v)) 44 | } 45 | 46 | squaredDistanceTo(v){ 47 | let x = this.x - v.x 48 | let y = this.y - v.y 49 | return x * x + y * y 50 | } 51 | 52 | angleTo(v) { // angle from this to v in radians 53 | return Math.atan2(this.y - v.y, this.x - v.x) + Math.PI 54 | } 55 | 56 | // LERP - Linear intERPolation between this and v 57 | lerp(v, t) { 58 | let a = this, ax = a.x, ay = a.y 59 | return vec(ax + t * (v.x - ax), ay + t * (v.y - ay)) 60 | } 61 | 62 | isInside(vmin, vmax) { 63 | return v.x >= vmin.x && v.x <= vmax.x && v.y >= vmin.y && v.y <= vmax.y 64 | } 65 | 66 | copy() { 67 | return new Vec(this.x, this.y) 68 | } 69 | 70 | sub(v) { 71 | return (typeof v == "number" ? 72 | new Vec(this.x - v, this.y - v) : 73 | new Vec(this.x - v.x, this.y - v.y) ) 74 | } 75 | add(v) { 76 | return (typeof v == "number" ? 77 | new Vec(this.x + v, this.y + v) : 78 | new Vec(this.x + v.x, this.y + v.y) ) 79 | } 80 | mul(v) { 81 | return (typeof v == "number" ? 82 | new Vec(this.x * v, this.y * v) : 83 | new Vec(this.x * v.x, this.y * v.y) ) 84 | } 85 | div(v) { 86 | return (typeof v == "number" ? 87 | new Vec(this.x / v, this.y / v) : 88 | new Vec(this.x / v.x, this.y / v.y) ) 89 | } 90 | 91 | toString() { 92 | return `(${this.x}, ${this.y})` 93 | } 94 | } 95 | 96 | export function vec(x, y) { 97 | return new Vec(x, y) 98 | } 99 | -------------------------------------------------------------------------------- /src/node/node.js: -------------------------------------------------------------------------------- 1 | import { vec, Rect } from "../math" 2 | import { HitEvent } from "../scene" 3 | 4 | 5 | export class Stroke { 6 | constructor(color, weight) { 7 | this.color = color 8 | this.weight = weight 9 | } 10 | } 11 | Stroke.default = new Stroke("black", 1) 12 | 13 | 14 | export class Node extends EventEmitter { 15 | constructor(position) { 16 | super() 17 | this.parent = null 18 | this.position = position || vec(0,0) 19 | this._bounds = new Rect(0,0,0,0) 20 | this.visible = true 21 | this.transform = null // optional matrix 22 | this.needsRecompute = true // true when recompute() needs to be called on next update 23 | } 24 | 25 | dirty() { 26 | this.needsRecompute = true 27 | } 28 | 29 | recompute() { 30 | // Called when first-level properties has changed. Recompute derived data like bounds. 31 | this.needsRecompute = false 32 | this._bounds.x = this.position.x 33 | this._bounds.y = this.position.y 34 | } 35 | 36 | get bounds() { 37 | if (this.needsRecompute) { 38 | this.recompute() 39 | } 40 | return this._bounds 41 | } 42 | 43 | updateAndDraw(g, time) { 44 | // Called by renderer each frame. Don't override this; instead override update() and draw(). 45 | if (this.visible) { 46 | if (this.needsRecompute) { 47 | this.recompute() 48 | } 49 | this.update(time, g) 50 | this.draw(g, time) 51 | } 52 | } 53 | 54 | update(time, g) {} // Update state that depends on time 55 | draw(g, time) {} // Draw to graphics context g 56 | 57 | remove() { 58 | if (this.parent) { 59 | this.parent.remove(this) 60 | } 61 | } 62 | pointFromSceneSpace(p) { 63 | if (this.parent) { 64 | return this.parent.pointFromSceneSpace(p) 65 | } 66 | return p 67 | } 68 | hitTest(p) { 69 | return null 70 | } 71 | } 72 | 73 | 74 | export class DrawableNode extends Node { 75 | constructor(position, fill, stroke) { 76 | super(position) 77 | this.interactive = true // participates in hit testing 78 | this.position = position || vec(0,0) 79 | this.fill = fill === undefined ? "white" : fill 80 | if (typeof stroke == "string") { 81 | stroke = new Stroke(stroke, 1) 82 | } 83 | this.stroke = stroke || null 84 | } 85 | 86 | draw(g) { 87 | g.fillStyle = this.fill 88 | if (this.stroke) { 89 | g.strokeStyle = this.stroke.color 90 | g.lineWidth = this.stroke.weight 91 | } 92 | } 93 | 94 | hitTest(p) { 95 | // log(`${this.constructor.name}.hitTest p=${p} bounds=${this.bounds}`) 96 | if (this.interactive && this.bounds.containsPoint(p)) { 97 | return new HitEvent(this, p) 98 | } 99 | return null 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/node/bezier.js: -------------------------------------------------------------------------------- 1 | import { vec } from "../math" 2 | import { DrawableNode } from "./node" 3 | 4 | export class CubicBezierNode extends DrawableNode { 5 | constructor(start, c1, c2, end, stroke) { 6 | super(start, null, stroke || Stroke.default) 7 | this.c1 = c1 8 | this.c2 = c2 9 | this.end = end 10 | this.interpolationData = {x1:0,x2:0,x3:0,x4:0, y1:0,y2:0,y3:0,y4:0} 11 | } 12 | 13 | recompute() { 14 | super.recompute() 15 | this.updateInterpolationData() 16 | } 17 | 18 | updateInterpolationData() { 19 | const D = this.interpolationData 20 | let a = this.position.x, b = this.c1.x, c = this.c2.x, d = this.end.x 21 | D.x1 = d - (3.0 * c) + (3.0 * b) - a 22 | D.x2 = (3.0 * c) - (6.0 * b) + (3.0 * a) 23 | D.x3 = (3.0 * b) - (3.0 * a) 24 | D.x4 = a 25 | a = this.position.y, b = this.c1.y, c = this.c2.y, d = this.end.y 26 | D.y1 = d - (3.0 * c) + (3.0 * b) - a 27 | D.y2 = (3.0 * c) - (6.0 * b) + (3.0 * a) 28 | D.y3 = (3.0 * b) - (3.0 * a) 29 | D.y4 = a 30 | } 31 | 32 | pointAt(t) { // t [0-1] 33 | const D = this.interpolationData 34 | return vec( 35 | ( D.x1*t*t*t + D.x2*t*t + D.x3*t + D.x4 ), 36 | ( D.y1*t*t*t + D.y2*t*t + D.y3*t + D.y4 ) 37 | ) 38 | } 39 | 40 | normalAt(t) { 41 | return this.tangentAt(t).rotate(90) 42 | } 43 | 44 | tangentAt(t) { 45 | const D = this.interpolationData 46 | return vec( 47 | ( ( 3.0 * D.x1 * t* t ) + ( 2.0 * D.x2 * t ) + D.x3 ), 48 | ( ( 3.0 * D.y1 * t* t ) + ( 2.0 * D.y2 * t ) + D.y3 ) 49 | ) 50 | } 51 | 52 | // Find the closest point on a Bézier curve to p 53 | // Returns [p :Vec, t :number] 54 | closestPoint(p) { 55 | // More samples increases the chance of being correct 56 | let mindex = 0, samples = 25 57 | for (let min = Infinity, i = samples + 1; i-- ;) { 58 | let d2 = p.squaredDistanceTo(this.pointAt(i / samples)) 59 | if (d2 < min) { 60 | min = d2 61 | mindex = i 62 | } 63 | } 64 | 65 | // Find a minimum point for a bounded function. May be a local minimum. 66 | let minX = Math.max((mindex - 1) / samples, 0) 67 | let maxX = Math.min((mindex + 1) / samples, 1) 68 | let p2, t2 = 0 69 | let f = t => { 70 | t2 = t 71 | p2 = this.pointAt(t) 72 | return p.squaredDistanceTo(p2) 73 | } 74 | let e = 1e-4 75 | let k = 0.0 76 | while ((maxX - minX) > e) { 77 | k = (maxX + minX) / 2 78 | if (f(k - e) < f(k + e)) { 79 | maxX = k 80 | } else { 81 | minX = k 82 | } 83 | } 84 | return [p2, t2] 85 | } 86 | 87 | draw(g) { 88 | super.draw(g) 89 | g.beginPath() 90 | g.moveTo(this.position.x, this.position.y) 91 | g.bezierCurveTo(this.c1.x, this.c1.y, this.c2.x, this.c2.y, this.end.x, this.end.y) 92 | g.strokeStyle = this.stroke.color 93 | g.lineWidth = this.stroke.weight 94 | g.stroke() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | const GlobalContext = ( 2 | typeof global != 'undefined' ? global : 3 | typeof window != 'undefined' ? window : 4 | this || {} 5 | ) 6 | 7 | const log = console.log.bind(console) 8 | const dlog = DEBUG ? console.log.bind(console) : function(){} 9 | 10 | const $$ = (q, e) => Array.prototype.slice.call((e || document).querySelectorAll(q)) 11 | const $ = (q, e) => (e || document).querySelector(q) 12 | 13 | 14 | class EventEmitter { 15 | constructor() { 16 | this._events = new Map() // > 17 | } 18 | 19 | on(event, handler) { return this.addEventListener(event, handler) } 20 | off(event, handler) { return this.removeEventListener(event, handler) } 21 | 22 | addEventListener(event, handler) { 23 | // Returns the number of handlers registered for event, after adding handler. 24 | let s = this._events.get(event) 25 | if (s) { 26 | s.add(handler) 27 | } else { 28 | s = new Set([handler]) 29 | this._events.set(event, s) 30 | } 31 | return s.size 32 | } 33 | 34 | removeEventListener(event, handler) { 35 | // Returns the number of handlers registered for event, after removing handler. 36 | // Returns -1 if handler was not registered for the event. 37 | let s = this._events.get(event) 38 | if (!s || !s.delete(handler)) { 39 | return -1 40 | } 41 | if (s.size == 0) { 42 | this._events.delete(event) 43 | } 44 | return s.size 45 | } 46 | 47 | dispatchEvent(event, data) { 48 | let s = this._events.get(event) 49 | if (s) for (let handler of s) { 50 | handler(data) 51 | } 52 | } 53 | } 54 | 55 | 56 | function _stackTrace(cons) { 57 | const x = {stack:''} 58 | if (Error.captureStackTrace) { 59 | Error.captureStackTrace(x, cons) 60 | const p = x.stack.indexOf('\n') 61 | if (p != -1) { 62 | return x.stack.substr(p+1) 63 | } 64 | } 65 | return x.stack 66 | } 67 | 68 | // _parseStackFrame(sf :string) : StackFrameInfo | null 69 | // interface StackFrameInfo { 70 | // func :string 71 | // file :string 72 | // line :int 73 | // col :int 74 | // } 75 | // 76 | function _parseStackFrame(sf) { 77 | let m = /\s*at\s+(?:[^\s]+\.|)([^\s\.]+)\s+(?:\[as ([^\]]+)\]\s+|)\((?:.+[\/ ](src\/[^\:]+)|([^\:]*))(?:\:(\d+)\:(\d+)|.*)\)/.exec(sf) 78 | // 1: name 79 | // 2: as-name | undefined 80 | // 3: src-filename 81 | // 4: filename 82 | // 5: line 83 | // 6: column 84 | // 85 | if (m) { 86 | return { 87 | func: m[2] || m[1], 88 | file: m[3] || m[4], 89 | line: m[5] ? parseInt(m[5]) : 0, 90 | col: m[6] ? parseInt(m[6]) : 0, 91 | } 92 | } else { 93 | console.log("failed to parse stack frame", JSON.stringify(sf)) 94 | } 95 | return null 96 | } 97 | 98 | function assert() { 99 | if (DEBUG) { 100 | let cond = arguments[0], msg = arguments[1], cons = (arguments[2] || assert) 101 | if (!cond) { 102 | let stack = _stackTrace(cons) 103 | let message = 'assertion failure: ' + (msg || cond) 104 | if (!assert.throws && typeof process != 'undefined') { 105 | console.error(message + "\n" + stack) 106 | process.exit(3) 107 | } else { 108 | let e = new Error(message) 109 | e.name = 'AssertionError' 110 | e.stack = stack 111 | throw e 112 | } 113 | } 114 | } 115 | } 116 | 117 | 118 | // export as globals since local names are mangled by esbuild 119 | GlobalContext["GlobalContext"] = GlobalContext 120 | GlobalContext["log"] = log 121 | GlobalContext["dlog"] = dlog 122 | GlobalContext["$"] = $ 123 | GlobalContext["$$"] = $$ 124 | GlobalContext["EventEmitter"] = EventEmitter 125 | GlobalContext["assert"] = assert 126 | -------------------------------------------------------------------------------- /src/scene.js: -------------------------------------------------------------------------------- 1 | import { Vec, vec } from "./math" 2 | import { GroupNode } from "./node" 3 | 4 | 5 | export class HitEvent { 6 | constructor(node, point, scenePoint) { 7 | this.node = node 8 | this.point = point // in node's coordinate system 9 | this.scenePoint = scenePoint // updated by Scene in case of scene origin 10 | } 11 | } 12 | 13 | 14 | export class Scene extends EventEmitter { 15 | constructor(renderer) { 16 | super() 17 | this.renderer = renderer 18 | this.width = renderer.canvas.width 19 | this.height = renderer.canvas.height 20 | this.pointer = vec(0,0) 21 | this.pointerDownAt = vec(0,0) // pointer value of last pointerdown event 22 | this.root = new GroupNode() 23 | this._eventHandlers = {} 24 | } 25 | 26 | enableHitTesting() { 27 | let pointerDownEvent = null 28 | this.addEventListener("pointerdown", () => { 29 | if (pointerDownEvent = this.hitTest(this.pointer)) { 30 | this.pointerDownAt.x = this.pointer.x 31 | this.pointerDownAt.y = this.pointer.y 32 | pointerDownEvent.node.dispatchEvent("pointerdown", pointerDownEvent) 33 | } 34 | }) 35 | this.addEventListener("pointerup", () => { 36 | let hitevent = this.hitTest(this.pointer) 37 | if (hitevent) { 38 | hitevent.node.dispatchEvent("pointerup", hitevent) 39 | if (pointerDownEvent && hitevent.node !== pointerDownEvent.node) { 40 | pointerDownEvent.node.dispatchEvent("pointerup", hitevent) 41 | } 42 | } else if (pointerDownEvent) { 43 | pointerDownEvent.node.dispatchEvent("pointerup", new HitEvent(null, null)) 44 | } 45 | pointerDownEvent = null 46 | }) 47 | } 48 | 49 | hitTest(p) { 50 | return this.root.hitTest(p) 51 | } 52 | 53 | addEventListener(event, handler) { 54 | let n = super.addEventListener(event, handler) 55 | if (n == 1) { 56 | // first handler added 57 | this._addEventHandler(event) 58 | } 59 | return n 60 | } 61 | 62 | removeEventListener(event, handler) { 63 | let n = super.removeEventListener(event, handler) 64 | if (n == 0) { 65 | // last handler removed 66 | this._removeEventHandler(event) 67 | } 68 | return n 69 | } 70 | 71 | _addEventHandler(event) { 72 | const events1 = { 73 | "pointermove":1, 74 | "pointerleave":1, 75 | "pointerenter":1, 76 | "pointerdown":1, 77 | "pointerup":1, 78 | } 79 | if (!(event in events1)) { 80 | throw new Error(`invalid event ${event}`) 81 | } 82 | 83 | let handler = ( 84 | event == "pointermove" ? ev => { 85 | this.pointer.x = ev.x 86 | this.pointer.y = ev.y 87 | let delta = this.pointer.sub(this.pointerDownAt) 88 | this.dispatchEvent(event, delta) 89 | } : ev => { 90 | this.pointer.x = ev.x 91 | this.pointer.y = ev.y 92 | this.dispatchEvent(event, this.pointer) 93 | } 94 | ) 95 | this.renderer.canvas.addEventListener(event, handler) 96 | this._eventHandlers[event] = handler 97 | } 98 | 99 | _removeEventHandler(event) { 100 | let handler = this._eventHandlers[event] 101 | if (handler) { 102 | delete this._eventHandlers[event] 103 | this.renderer.canvas.removeEventListener(event, handler) 104 | } 105 | } 106 | 107 | // localPointerPosition() { 108 | // let p = this.pointer 109 | // let m = this.renderer.g.getTransform() 110 | // if (m.e != 0 || m.f != 0) { 111 | // p = vec(p.x - (m.e / m.a), p.y - (m.f / m.d)) 112 | // } 113 | // return p 114 | // } 115 | 116 | add(n) { return this.root.add(n) } 117 | remove(n) { this.root.remove(n) } 118 | 119 | updateAndDraw(g, time) { 120 | g.clearRect(0, 0, this.width, this.height) 121 | this.root.updateAndDraw(g, time) 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import "./global" 2 | import { Scene } from "./scene" 3 | import { Renderer } from "./render" 4 | import { vec } from "./math" 5 | import { GroupNode, LineNode, DiscNode, CubicBezierNode, Stroke } from "./node" 6 | 7 | function setupScene(scene, renderer) { 8 | let g = new GroupNode(vec(100,200)) 9 | 10 | let curve1 = g.add(new CubicBezierNode( 11 | vec(250, 120), // start 12 | vec(290, -40), // control 1 13 | vec(300, 200), // control 2 14 | vec(400, 150), // end 15 | new Stroke("rgba(0,0,0,0.4)", 1.5) 16 | )) 17 | 18 | // show bezier control points 19 | let handleSize = 4 20 | let c1Line = g.add(new LineNode(curve1.c1, curve1.position, "rgba(0,0,0,0.1)")) 21 | let c2Line = g.add(new LineNode(curve1.c2, curve1.end, "rgba(0,0,0,0.1)")) 22 | let curveStart = g.add(new DiscNode(curve1.position, handleSize, "white", "rgba(0,30,200,0.5)")) 23 | let curveEnd = g.add(new DiscNode(curve1.end, handleSize, "white", "rgba(0,30,200,0.5)")) 24 | let c1 = g.add(new DiscNode(curve1.c1, handleSize, "white", "rgba(0,180,20,0.8)")) 25 | let c2 = g.add(new DiscNode(curve1.c2, handleSize, "white", "rgba(0,180,20,0.8)")) 26 | function makeDraggable(n) { 27 | let origFill = n.fill 28 | const movePoint = movementDelta => { 29 | let p = pointerDot.pointFromSceneSpace(scene.pointer) 30 | n.position.x = p.x 31 | n.position.y = p.y 32 | n.dirty() 33 | c1Line.dirty() 34 | c2Line.dirty() 35 | curve1.dirty() 36 | } 37 | n.on("pointerdown", ev => { 38 | n.fill = "rgba(0,180,20,0.8)" 39 | scene.on("pointermove", movePoint) 40 | }) 41 | n.on("pointerup", ev => { 42 | n.fill = origFill 43 | scene.off("pointermove", movePoint) 44 | }) 45 | } 46 | makeDraggable(curveStart) 47 | makeDraggable(curveEnd) 48 | makeDraggable(c1) 49 | makeDraggable(c2) 50 | 51 | // pointer 52 | let pointerp = vec(-1000,-1000) 53 | let pointerPerimeter = g.add(new DiscNode(pointerp, 8, null, "rgba(0,200,255,0.2)")) 54 | let pointerDot = g.add(new DiscNode(pointerp, 8, "rgba(0,200,255,0.3)")) 55 | let pointerTangentLine = g.add(new LineNode(null, null, "rgba(255,50,0,0.9)")) 56 | let pointerNormalLine = g.add(new LineNode(null, null, "rgba(0,100,255,0.9)")) 57 | let pointerDotRay = g.add(new DiscNode(pointerp, 3, "rgba(0,200,255,1)")) 58 | 59 | const pointerUpdate = movementDelta => { 60 | pointerDot.position = pointerDot.pointFromSceneSpace(scene.pointer) 61 | 62 | let [p1, t] = curve1.closestPoint(pointerDot.position) 63 | pointerDotRay.position = p1 64 | 65 | let d = pointerDot.position.distanceTo(pointerDotRay.position) 66 | 67 | let p2 = p1.add(curve1.tangentAt(t)) 68 | pointerTangentLine.position = p1 69 | pointerTangentLine.end = p2 70 | pointerTangentLine.length = Math.max(40, d) // adjust line to be fixed size 71 | 72 | pointerNormalLine.position = p1 73 | pointerNormalLine.end = p2 74 | pointerNormalLine.rotate(90) 75 | pointerNormalLine.length = Math.max(40, d) 76 | 77 | pointerPerimeter.position = pointerDot.position 78 | pointerPerimeter.radius = Math.max(pointerDot.radius, d) 79 | pointerPerimeter.stroke.color = `rgba(0,200,255,${Math.max(0.1,20/d)})` 80 | } 81 | scene.on("pointermove", pointerUpdate) 82 | scene.on("pointerleave", () => { 83 | scene.removeEventListener("pointermove", pointerUpdate) 84 | pointerDotRay.visible = false 85 | pointerDot.visible = false 86 | pointerPerimeter.visible = false 87 | pointerTangentLine.visible = false 88 | pointerNormalLine.visible = false 89 | }) 90 | scene.on("pointerenter", () => { 91 | scene.addEventListener("pointermove", pointerUpdate) 92 | pointerDotRay.visible = true 93 | pointerDot.visible = true 94 | pointerPerimeter.visible = true 95 | pointerTangentLine.visible = true 96 | pointerNormalLine.visible = true 97 | }) 98 | 99 | // animated tangent & normal lines 100 | let tangentLine = g.add(new LineNode(null, null, "rgba(255,50,0,0.9)")) 101 | let normalLine = g.add(new LineNode(null, null, "rgba(0,100,255,0.9)")) 102 | let dot = g.add(new DiscNode(null, 2, "black")) 103 | tangentLine.update = time => { 104 | let t = Math.abs(1 - (time % 4000) / 2000) 105 | dot.position = curve1.pointAt(t) 106 | 107 | let p1 = curve1.pointAt(t) 108 | 109 | let vel = curve1.tangentAt(t) 110 | let p2 = p1.add(vel.mul(/* reduce length */0.3)) 111 | tangentLine.position = p1 112 | tangentLine.end = p2 113 | 114 | normalLine.position = p1 115 | normalLine.end = p2 116 | normalLine.rotate(90) 117 | } 118 | 119 | scene.add(g) 120 | scene.enableHitTesting() 121 | 122 | // center group in scene 123 | // scene. 124 | } 125 | 126 | 127 | function main() { 128 | log("start") 129 | let renderer = new Renderer() 130 | let scene = new Scene(renderer) 131 | setupScene(scene) 132 | 133 | let animate = true 134 | 135 | function tick(time) { 136 | renderer.render(scene, time) 137 | if (animate) { 138 | requestAnimationFrame(tick) 139 | } 140 | } 141 | 142 | if (animate) { 143 | requestAnimationFrame(tick) 144 | } else { 145 | tick(1200) 146 | } 147 | } 148 | 149 | // window.addEventListener('DOMContentLoaded', main) 150 | main() 151 | -------------------------------------------------------------------------------- /docs/app.js: -------------------------------------------------------------------------------- 1 | ((g,h)=>{let i=function(){return this}(),e={},f=(c,d)=>{if(typeof c==="number"){let a=e[c],b;return a||(a=e[c]={exports:{}},g[c].call(i,f,a.exports,a)),b=a.exports,d&&(!b||!b.__esModule)&&((!b||typeof b!=="object")&&(b={}),"default"in b||Object.defineProperty(b,"default",{get:()=>a.exports,enumerable:!0})),b}d.__esModule=()=>!0;for(let a in d)Object.defineProperty(c,a,{get:d[a],enumerable:!0})};return f(h)})({1(){const m=typeof global!="undefined"?global:typeof window!="undefined"?window:this||{},M=console.log.bind(console),N=function(){},O=(a,b)=>Array.prototype.slice.call((b||document).querySelectorAll(a)),P=(a,b)=>(b||document).querySelector(a);class Q{constructor(){this._events=new Map()}on(a,b){return this.addEventListener(a,b)}off(a,b){return this.removeEventListener(a,b)}addEventListener(a,b){let c=this._events.get(a);return c?c.add(b):(c=new Set([b]),this._events.set(a,c)),c.size}removeEventListener(a,b){let c=this._events.get(a);return!c||!c.delete(b)?-1:(c.size==0&&this._events.delete(a),c.size)}dispatchEvent(a,b){let c=this._events.get(a);if(c)for(let d of c)d(b)}}function ba(a){const b={stack:""};if(Error.captureStackTrace){Error.captureStackTrace(b,a);const c=b.stack.indexOf(` 2 | `);if(c!=-1)return b.stack.substr(c+1)}return b.stack}function ca(a){let b=/\s*at\s+(?:[^\s]+\.|)([^\s\.]+)\s+(?:\[as ([^\]]+)\]\s+|)\((?:.+[\/ ](src\/[^\:]+)|([^\:]*))(?:\:(\d+)\:(\d+)|.*)\)/.exec(a);return b?{func:b[2]||b[1],file:b[3]||b[4],line:b[5]?parseInt(b[5]):0,col:b[6]?parseInt(b[6]):0}:(console.log("failed to parse stack frame",JSON.stringify(a)),null)}function R(){}m.GlobalContext=m,m.log=M,m.dlog=N,m.$=P,m.$$=O,m.EventEmitter=Q,m.assert=R; 3 | function oa(a){return a*(180/Math.PI)}function pa(a){return a*(Math.PI/180)}class J{constructor(a,b,c,d){this.x=a,this.y=b,this.width=c,this.height=d}containsPoint(a){return a.x>=this.x&&a.x<=this.x+this.width&&a.y>=this.y&&a.y<=this.y+this.width}translate(a){return new J(this.x+a.x,this.y+a.y,this.width,this.height)}toString(){return`(${this.x}, ${this.y}, ${this.width}, ${this.height})`}}class i{constructor(a,b){this.x=a,this.y=b}distanceTo(a){return Math.sqrt(this.squaredDistanceTo(a))}squaredDistanceTo(a){let b=this.x-a.x,c=this.y-a.y;return b*b+c*c}angleTo(a){return Math.atan2(this.y-a.y,this.x-a.x)+Math.PI}lerp(a,b){let c=this,d=c.x,e=c.y;return f(d+b*(a.x-d),e+b*(a.y-e))}isInside(a,b){return v.x>=a.x&&v.x<=b.x&&v.y>=a.y&&v.y<=b.y}copy(){return new i(this.x,this.y)}sub(a){return typeof a=="number"?new i(this.x-a,this.y-a):new i(this.x-a.x,this.y-a.y)}add(a){return typeof a=="number"?new i(this.x+a,this.y+a):new i(this.x+a.x,this.y+a.y)}mul(a){return typeof a=="number"?new i(this.x*a,this.y*a):new i(this.x*a.x,this.y*a.y)}div(a){return typeof a=="number"?new i(this.x/a,this.y/a):new i(this.x/a.x,this.y/a.y)}toString(){return`(${this.x}, ${this.y})`}}function f(a,b){return new i(a,b)} 4 | class A{constructor(a,b){this.color=a,this.weight=b}}A.default=new A("black",1);class K extends EventEmitter{constructor(a){super(),this.parent=null,this.position=a||f(0,0),this._bounds=new J(0,0,0,0),this.visible=!0,this.transform=null,this.needsRecompute=!0}dirty(){this.needsRecompute=!0}recompute(){this.needsRecompute=!1,this._bounds.x=this.position.x,this._bounds.y=this.position.y}get bounds(){return this.needsRecompute&&this.recompute(),this._bounds}updateAndDraw(a,b){this.visible&&(this.needsRecompute&&this.recompute(),this.update(b,a),this.draw(a,b))}update(a,b){}draw(a,b){}remove(){this.parent&&this.parent.remove(this)}pointFromSceneSpace(a){return this.parent?this.parent.pointFromSceneSpace(a):a}hitTest(a){return null}}class E extends K{constructor(a,b,c){super(a),this.interactive=!0,this.position=a||f(0,0),this.fill=b===void 0?"white":b,typeof c=="string"&&(c=new A(c,1)),this.stroke=c||null}draw(a){a.fillStyle=this.fill,this.stroke&&(a.strokeStyle=this.stroke.color,a.lineWidth=this.stroke.weight)}hitTest(a){return this.interactive&&this.bounds.containsPoint(a)?new L(this,a):null}} 5 | class I extends K{constructor(a,b){super(a),this.children=new Set(b)}add(a){return a.parent&&a.parent.remove(a),this.children.add(a),a.parent=this,a}remove(a){this.children.delete(a)&&(a.parent=null)}updateAndDraw(a,b){if(!this.visible)return;this.needsRecompute&&this.recompute();let c=a.getTransform();a.translate(this.position.x,this.position.y),this.update(b,a);for(let d of this.children)d.updateAndDraw(a,b);a.setTransform(c)}pointFromSceneSpace(a){return a.sub(this.position)}hitTest(a){a=a.sub(this.position);for(let b of this.children){let c=b.hitTest(a);if(c)return c}return null}} 6 | class t extends E{constructor(a,b,c){super(a,null,c||Stroke.default),this.end=b||f(0,0)}draw(a){super.draw(a),a.beginPath(),a.moveTo(this.position.x,this.position.y),a.lineTo(this.end.x,this.end.y),a.stroke()}get length(){return this.position.distanceTo(this.end)}set length(a){let b=this.position.angleTo(this.end);this.end=f(this.position.x+a*Math.cos(b),this.position.y+a*Math.sin(b))}rotate(a){let b=Math.PI/180*a,c=Math.cos(b),d=Math.sin(b),e=this.position,g=this.end;this.end=f(c*(g.x-e.x)+d*(g.y-e.y)+e.x,c*(g.y-e.y)-d*(g.x-e.x)+e.y)}hitTest(a){return null}}class n extends E{constructor(a,b,c,d){super(a,c,d),this.radius=b||10,this.size=this.radius*2}recompute(){super.recompute(),this.bounds.x=this.position.x-this.radius,this.bounds.y=this.position.y-this.radius,this.bounds.width=this.radius*2,this.bounds.height=this.radius*2}draw(a){super.draw(a),a.beginPath(),a.arc(this.position.x,this.position.y,this.radius,0,Math.PI*2,!1),a.closePath(),this.fill&&a.fill(),this.stroke&&a.stroke()}} 7 | class U extends E{constructor(a,b,c,d,e){super(a,null,e||Stroke.default),this.c1=b,this.c2=c,this.end=d,this.interpolationData={x1:0,x2:0,x3:0,x4:0,y1:0,y2:0,y3:0,y4:0}}recompute(){super.recompute(),this.updateInterpolationData()}updateInterpolationData(){const a=this.interpolationData;let b=this.position.x,c=this.c1.x,d=this.c2.x,e=this.end.x;a.x1=e-3*d+3*c-b,a.x2=3*d-6*c+3*b,a.x3=3*c-3*b,a.x4=b,b=this.position.y,c=this.c1.y,d=this.c2.y,e=this.end.y,a.y1=e-3*d+3*c-b,a.y2=3*d-6*c+3*b,a.y3=3*c-3*b,a.y4=b}pointAt(a){const b=this.interpolationData;return f(b.x1*a*a*a+b.x2*a*a+b.x3*a+b.x4,b.y1*a*a*a+b.y2*a*a+b.y3*a+b.y4)}normalAt(a){return this.tangentAt(a).rotate(90)}tangentAt(a){const b=this.interpolationData;return f(3*b.x1*a*a+2*b.x2*a+b.x3,3*b.y1*a*a+2*b.y2*a+b.y3)}closestPoint(a){let b=0,c=25;for(let s=Infinity,o=c+1;o--;){let u=a.squaredDistanceTo(this.pointAt(o/c));u(B=s,g=this.pointAt(s),a.squaredDistanceTo(g)),x=.0001,r=0;for(;e-d>x;)r=(e+d)/2,C(r-x){(a=this.hitTest(this.pointer))&&(this.pointerDownAt.x=this.pointer.x,this.pointerDownAt.y=this.pointer.y,a.node.dispatchEvent("pointerdown",a))}),this.addEventListener("pointerup",()=>{let b=this.hitTest(this.pointer);b?(b.node.dispatchEvent("pointerup",b),a&&b.node!==a.node&&a.node.dispatchEvent("pointerup",b)):a&&a.node.dispatchEvent("pointerup",new L(null,null)),a=null})}hitTest(a){return this.root.hitTest(a)}addEventListener(a,b){let c=super.addEventListener(a,b);return c==1&&this._addEventHandler(a),c}removeEventListener(a,b){let c=super.removeEventListener(a,b);return c==0&&this._removeEventHandler(a),c}_addEventHandler(a){const b={pointermove:1,pointerleave:1,pointerenter:1,pointerdown:1,pointerup:1};if(!(a in b))throw new Error(`invalid event ${a}`);let c=a=="pointermove"?d=>{this.pointer.x=d.x,this.pointer.y=d.y;let e=this.pointer.sub(this.pointerDownAt);this.dispatchEvent(a,e)}:d=>{this.pointer.x=d.x,this.pointer.y=d.y,this.dispatchEvent(a,this.pointer)};this.renderer.canvas.addEventListener(a,c),this._eventHandlers[a]=c}_removeEventHandler(a){let b=this._eventHandlers[a];b&&(delete this._eventHandlers[a],this.renderer.canvas.removeEventListener(a,b))}add(a){return this.root.add(a)}remove(a){this.root.remove(a)}updateAndDraw(a,b){a.clearRect(0,0,this.width,this.height),this.root.updateAndDraw(a,b)}} 9 | class T extends EventEmitter{constructor(){super(),this.canvas=$("canvas"),this.g=this.canvas.getContext("2d"),this.scale=1,this.updateScale(),this.resizeCanvasToWindow(),this._onWindowResize=()=>{this.updateScale(),this.resizeCanvasToWindow(),this.dispatchEvent("resize")},window.addEventListener("resize",this._onWindowResize)}finalize(){window.removeEventListener("resize",this._onWindowResize)}updateScale(){this.scale=(window.devicePixelRatio||1)/(this.g.backingStorePixelRatio||this.g.webkitBackingStorePixelRatio||this.g.mozBackingStorePixelRatio||this.g.msBackingStorePixelRatio||this.g.oBackingStorePixelRatio||1)}resizeCanvasToWindow(){this.resizeCanvas(window.innerWidth,window.innerWidth)}resizeCanvas(a,b){this.canvas.style.zoom=String(1/this.scale),this.canvas.width=a*this.scale,this.canvas.height=b*this.scale}render(a,b){const c=this.g;c.setTransform(this.scale,0,0,this.scale,0,0),a.transform&&c.transform(a.transform),a.updateAndDraw(c,b)}} 10 | function V(a,b){let c=new I(f(100,200)),d=c.add(new U(f(250,120),f(290,-40),f(300,200),f(400,150),new A("rgba(0,0,0,0.4)",1.5))),e=4,g=c.add(new t(d.c1,d.position,"rgba(0,0,0,0.1)")),B=c.add(new t(d.c2,d.end,"rgba(0,0,0,0.1)")),C=c.add(new n(d.position,e,"white","rgba(0,30,200,0.5)")),x=c.add(new n(d.end,e,"white","rgba(0,30,200,0.5)")),r=c.add(new n(d.c1,e,"white","rgba(0,180,20,0.8)")),s=c.add(new n(d.c2,e,"white","rgba(0,180,20,0.8)"));function o(h){let j=h.fill;const p=l=>{let q=k.pointFromSceneSpace(a.pointer);h.position.x=q.x,h.position.y=q.y,h.dirty(),g.dirty(),B.dirty(),d.dirty()};h.on("pointerdown",l=>{h.fill="rgba(0,180,20,0.8)",a.on("pointermove",p)}),h.on("pointerup",l=>{h.fill=j,a.off("pointermove",p)})}o(C),o(x),o(r),o(s);let u=f(-1000,-1000),y=c.add(new n(u,8,null,"rgba(0,200,255,0.2)")),k=c.add(new n(u,8,"rgba(0,200,255,0.3)")),z=c.add(new t(null,null,"rgba(255,50,0,0.9)")),w=c.add(new t(null,null,"rgba(0,100,255,0.9)")),D=c.add(new n(u,3,"rgba(0,200,255,1)"));const F=h=>{k.position=k.pointFromSceneSpace(a.pointer);let[j,p]=d.closestPoint(k.position);D.position=j;let l=k.position.distanceTo(D.position),q=j.add(d.tangentAt(p));z.position=j,z.end=q,z.length=Math.max(40,l),w.position=j,w.end=q,w.rotate(90),w.length=Math.max(40,l),y.position=k.position,y.radius=Math.max(k.radius,l),y.stroke.color=`rgba(0,200,255,${Math.max(.1,20/l)})`};a.on("pointermove",F),a.on("pointerleave",()=>{a.removeEventListener("pointermove",F),D.visible=!1,k.visible=!1,y.visible=!1,z.visible=!1,w.visible=!1}),a.on("pointerenter",()=>{a.addEventListener("pointermove",F),D.visible=!0,k.visible=!0,y.visible=!0,z.visible=!0,w.visible=!0});let G=c.add(new t(null,null,"rgba(255,50,0,0.9)")),H=c.add(new t(null,null,"rgba(0,100,255,0.9)")),X=c.add(new n(null,2,"black"));G.update=h=>{let j=Math.abs(1-h%4000/2000);X.position=d.pointAt(j);let p=d.pointAt(j),l=d.tangentAt(j),q=p.add(l.mul(.3));G.position=p,G.end=q,H.position=p,H.end=q,H.rotate(90)},a.add(c),a.enableHitTesting()}function W(){log("start");let a=new T(),b=new S(a);V(b);let c=!0;function d(e){a.render(b,e),c&&requestAnimationFrame(d)}c?requestAnimationFrame(d):d(1200)}W(); 11 | }},1); 12 | //# sourceMappingURL=app.js.map 13 | -------------------------------------------------------------------------------- /docs/app.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["src/global.js", "src/math.js", "src/node/node.js", "src/node/group.js", "src/node/primitives.js", "src/node/bezier.js", "src/node/index.js", "src/scene.js", "src/render.js", "src/main.js"], 4 | "sourcesContent": ["const GlobalContext = (\n typeof global != 'undefined' ? global :\n typeof window != 'undefined' ? window :\n this || {}\n)\n\nconst log = console.log.bind(console)\nconst dlog = DEBUG ? console.log.bind(console) : function(){}\n\nconst $$ = (q, e) => Array.prototype.slice.call((e || document).querySelectorAll(q))\nconst $ = (q, e) => (e || document).querySelector(q)\n\n\nclass EventEmitter {\n constructor() {\n this._events = new Map() // >\n }\n\n on(event, handler) { return this.addEventListener(event, handler) }\n off(event, handler) { return this.removeEventListener(event, handler) }\n\n addEventListener(event, handler) {\n // Returns the number of handlers registered for event, after adding handler.\n let s = this._events.get(event)\n if (s) {\n s.add(handler)\n } else {\n s = new Set([handler])\n this._events.set(event, s)\n }\n return s.size\n }\n\n removeEventListener(event, handler) {\n // Returns the number of handlers registered for event, after removing handler.\n // Returns -1 if handler was not registered for the event.\n let s = this._events.get(event)\n if (!s || !s.delete(handler)) {\n return -1\n }\n if (s.size == 0) {\n this._events.delete(event)\n }\n return s.size\n }\n\n dispatchEvent(event, data) {\n let s = this._events.get(event)\n if (s) for (let handler of s) {\n handler(data)\n }\n }\n}\n\n\nfunction _stackTrace(cons) {\n const x = {stack:''}\n if (Error.captureStackTrace) {\n Error.captureStackTrace(x, cons)\n const p = x.stack.indexOf('\\n')\n if (p != -1) {\n return x.stack.substr(p+1)\n }\n }\n return x.stack\n}\n\n// _parseStackFrame(sf :string) : StackFrameInfo | null\n// interface StackFrameInfo {\n// func :string\n// file :string\n// line :int\n// col :int\n// }\n//\nfunction _parseStackFrame(sf) {\n let m = /\\s*at\\s+(?:[^\\s]+\\.|)([^\\s\\.]+)\\s+(?:\\[as ([^\\]]+)\\]\\s+|)\\((?:.+[\\/ ](src\\/[^\\:]+)|([^\\:]*))(?:\\:(\\d+)\\:(\\d+)|.*)\\)/.exec(sf)\n // 1: name\n // 2: as-name | undefined\n // 3: src-filename\n // 4: filename\n // 5: line\n // 6: column\n //\n if (m) {\n return {\n func: m[2] || m[1],\n file: m[3] || m[4],\n line: m[5] ? parseInt(m[5]) : 0,\n col: m[6] ? parseInt(m[6]) : 0,\n }\n } else {\n console.log(\"failed to parse stack frame\", JSON.stringify(sf))\n }\n return null\n}\n\nfunction assert() {\n if (DEBUG) {\n let cond = arguments[0], msg = arguments[1], cons = (arguments[2] || assert)\n if (!cond) {\n let stack = _stackTrace(cons)\n let message = 'assertion failure: ' + (msg || cond)\n if (!assert.throws && typeof process != 'undefined') {\n console.error(message + \"\\n\" + stack)\n process.exit(3)\n } else {\n let e = new Error(message)\n e.name = 'AssertionError'\n e.stack = stack\n throw e\n }\n }\n }\n}\n\n\n// export as globals since local names are mangled by esbuild\nGlobalContext[\"GlobalContext\"] = GlobalContext\nGlobalContext[\"log\"] = log\nGlobalContext[\"dlog\"] = dlog\nGlobalContext[\"$\"] = $\nGlobalContext[\"$$\"] = $$\nGlobalContext[\"EventEmitter\"] = EventEmitter\nGlobalContext[\"assert\"] = assert\n", "export function rad2deg(radians) {\n return radians * (180 / Math.PI)\n}\n\nexport function deg2rad(degrees) {\n return degrees * (Math.PI / 180)\n}\n\n\nexport class Rect {\n constructor(x, y, width, height) {\n this.x = x\n this.y = y\n this.width = width\n this.height = height\n }\n\n containsPoint(v) {\n return (\n v.x >= this.x && v.x <= this.x + this.width &&\n v.y >= this.y && v.y <= this.y + this.width\n )\n }\n\n translate(v) {\n return new Rect(this.x + v.x, this.y + v.y, this.width, this.height)\n }\n\n toString() {\n return `(${this.x}, ${this.y}, ${this.width}, ${this.height})`\n }\n}\n\n\nexport class Vec /*extends Array*/ {\n constructor(x, y) {\n // super(x, y)\n this.x = x\n this.y = y\n }\n\n distanceTo(v) { // euclidean distance between this and v\n return Math.sqrt(this.squaredDistanceTo(v))\n }\n\n squaredDistanceTo(v){\n let x = this.x - v.x\n let y = this.y - v.y\n return x * x + y * y\n }\n\n angleTo(v) { // angle from this to v in radians\n return Math.atan2(this.y - v.y, this.x - v.x) + Math.PI\n }\n\n // LERP - Linear intERPolation between this and v\n lerp(v, t) {\n let a = this, ax = a.x, ay = a.y\n return vec(ax + t * (v.x - ax), ay + t * (v.y - ay))\n }\n\n isInside(vmin, vmax) {\n return v.x >= vmin.x && v.x <= vmax.x && v.y >= vmin.y && v.y <= vmax.y\n }\n\n copy() {\n return new Vec(this.x, this.y)\n }\n\n sub(v) {\n return (typeof v == \"number\" ?\n new Vec(this.x - v, this.y - v) :\n new Vec(this.x - v.x, this.y - v.y) )\n }\n add(v) {\n return (typeof v == \"number\" ?\n new Vec(this.x + v, this.y + v) :\n new Vec(this.x + v.x, this.y + v.y) )\n }\n mul(v) {\n return (typeof v == \"number\" ?\n new Vec(this.x * v, this.y * v) :\n new Vec(this.x * v.x, this.y * v.y) )\n }\n div(v) {\n return (typeof v == \"number\" ?\n new Vec(this.x / v, this.y / v) :\n new Vec(this.x / v.x, this.y / v.y) )\n }\n\n toString() {\n return `(${this.x}, ${this.y})`\n }\n}\n\nexport function vec(x, y) {\n return new Vec(x, y)\n}\n", "import { vec, Rect } from \"../math\"\nimport { HitEvent } from \"../scene\"\n\n\nexport class Stroke {\n constructor(color, weight) {\n this.color = color\n this.weight = weight\n }\n}\nStroke.default = new Stroke(\"black\", 1)\n\n\nexport class Node extends EventEmitter {\n constructor(position) {\n super()\n this.parent = null\n this.position = position || vec(0,0)\n this._bounds = new Rect(0,0,0,0)\n this.visible = true\n this.transform = null // optional matrix\n this.needsRecompute = true // true when recompute() needs to be called on next update\n }\n\n dirty() {\n this.needsRecompute = true\n }\n\n recompute() {\n // Called when first-level properties has changed. Recompute derived data like bounds.\n this.needsRecompute = false\n this._bounds.x = this.position.x\n this._bounds.y = this.position.y\n }\n\n get bounds() {\n if (this.needsRecompute) {\n this.recompute()\n }\n return this._bounds\n }\n\n updateAndDraw(g, time) {\n // Called by renderer each frame. Don't override this; instead override update() and draw().\n if (this.visible) {\n if (this.needsRecompute) {\n this.recompute()\n }\n this.update(time, g)\n this.draw(g, time)\n }\n }\n\n update(time, g) {} // Update state that depends on time\n draw(g, time) {} // Draw to graphics context g\n\n remove() {\n if (this.parent) {\n this.parent.remove(this)\n }\n }\n pointFromSceneSpace(p) {\n if (this.parent) {\n return this.parent.pointFromSceneSpace(p)\n }\n return p\n }\n hitTest(p) {\n return null\n }\n}\n\n\nexport class DrawableNode extends Node {\n constructor(position, fill, stroke) {\n super(position)\n this.interactive = true // participates in hit testing\n this.position = position || vec(0,0)\n this.fill = fill === undefined ? \"white\" : fill\n if (typeof stroke == \"string\") {\n stroke = new Stroke(stroke, 1)\n }\n this.stroke = stroke || null\n }\n\n draw(g) {\n g.fillStyle = this.fill\n if (this.stroke) {\n g.strokeStyle = this.stroke.color\n g.lineWidth = this.stroke.weight\n }\n }\n\n hitTest(p) {\n // log(`${this.constructor.name}.hitTest p=${p} bounds=${this.bounds}`)\n if (this.interactive && this.bounds.containsPoint(p)) {\n return new HitEvent(this, p)\n }\n return null\n }\n}\n", "import { Node } from \"./index\"\n\nexport class GroupNode extends Node {\n constructor(position, children) {\n super(position)\n this.children = new Set(children)\n }\n\n add(n) {\n if (n.parent) {\n n.parent.remove(n)\n }\n this.children.add(n)\n n.parent = this\n return n\n }\n\n remove(n) {\n if (this.children.delete(n)) {\n n.parent = null\n }\n }\n\n updateAndDraw(g, time) {\n if (!this.visible) {\n return\n }\n if (this.needsRecompute) {\n this.recompute()\n }\n let prevtr = g.getTransform()\n g.translate(this.position.x, this.position.y)\n this.update(time, g)\n for (let n of this.children) {\n n.updateAndDraw(g, time)\n }\n g.setTransform(prevtr)\n }\n\n pointFromSceneSpace(p) {\n return p.sub(this.position)\n }\n\n hitTest(p) {\n p = p.sub(this.position)\n for (let n of this.children) {\n let ev = n.hitTest(p)\n if (ev) {\n return ev\n }\n }\n return null\n }\n}\n", "import { vec } from \"../math\"\nimport { DrawableNode } from \"./node\"\n\nexport class LineNode extends DrawableNode {\n constructor(start, end, stroke) {\n super(start, null, stroke || Stroke.default)\n this.end = end || vec(0, 0)\n }\n\n draw(g) {\n super.draw(g)\n g.beginPath()\n g.moveTo(this.position.x, this.position.y)\n g.lineTo(this.end.x, this.end.y)\n g.stroke()\n }\n\n get length() {\n return this.position.distanceTo(this.end)\n }\n\n set length(len) {\n let angle = this.position.angleTo(this.end)\n this.end = vec(\n this.position.x + len * Math.cos(angle),\n this.position.y + len * Math.sin(angle)\n )\n }\n\n rotate(degrees) {\n let radians = (Math.PI / 180) * degrees\n let cos = Math.cos(radians)\n let sin = Math.sin(radians)\n let p1 = this.position\n let p2 = this.end\n this.end = vec(\n (cos * (p2.x - p1.x)) + (sin * (p2.y - p1.y)) + p1.x,\n (cos * (p2.y - p1.y)) - (sin * (p2.x - p1.x)) + p1.y\n )\n }\n\n hitTest(p) { return null }\n}\n\n\nexport class DiscNode extends DrawableNode {\n constructor(position, radius, fill, stroke) {\n super(position, fill, stroke)\n this.radius = radius || 10\n this.size = this.radius * 2\n }\n\n recompute() {\n super.recompute()\n this.bounds.x = this.position.x - this.radius\n this.bounds.y = this.position.y - this.radius\n this.bounds.width = this.radius * 2\n this.bounds.height = this.radius * 2\n }\n\n draw(g) {\n super.draw(g)\n g.beginPath()\n g.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2, false)\n g.closePath()\n this.fill && g.fill()\n this.stroke && g.stroke()\n }\n}\n\n", "import { vec } from \"../math\"\nimport { DrawableNode } from \"./node\"\n\nexport class CubicBezierNode extends DrawableNode {\n constructor(start, c1, c2, end, stroke) {\n super(start, null, stroke || Stroke.default)\n this.c1 = c1\n this.c2 = c2\n this.end = end\n this.interpolationData = {x1:0,x2:0,x3:0,x4:0, y1:0,y2:0,y3:0,y4:0}\n }\n\n recompute() {\n super.recompute()\n this.updateInterpolationData()\n }\n\n updateInterpolationData() {\n const D = this.interpolationData\n let a = this.position.x, b = this.c1.x, c = this.c2.x, d = this.end.x\n D.x1 = d - (3.0 * c) + (3.0 * b) - a\n D.x2 = (3.0 * c) - (6.0 * b) + (3.0 * a)\n D.x3 = (3.0 * b) - (3.0 * a)\n D.x4 = a\n a = this.position.y, b = this.c1.y, c = this.c2.y, d = this.end.y\n D.y1 = d - (3.0 * c) + (3.0 * b) - a\n D.y2 = (3.0 * c) - (6.0 * b) + (3.0 * a)\n D.y3 = (3.0 * b) - (3.0 * a)\n D.y4 = a\n }\n\n pointAt(t) { // t [0-1]\n const D = this.interpolationData\n return vec(\n ( D.x1*t*t*t + D.x2*t*t + D.x3*t + D.x4 ),\n ( D.y1*t*t*t + D.y2*t*t + D.y3*t + D.y4 )\n )\n }\n\n normalAt(t) {\n return this.tangentAt(t).rotate(90)\n }\n\n tangentAt(t) {\n const D = this.interpolationData\n return vec(\n ( ( 3.0 * D.x1 * t* t ) + ( 2.0 * D.x2 * t ) + D.x3 ),\n ( ( 3.0 * D.y1 * t* t ) + ( 2.0 * D.y2 * t ) + D.y3 )\n )\n }\n\n // Find the closest point on a B\u00E9zier curve to p\n // Returns [p :Vec, t :number]\n closestPoint(p) {\n // More samples increases the chance of being correct\n let mindex = 0, samples = 25\n for (let min = Infinity, i = samples + 1; i-- ;) {\n let d2 = p.squaredDistanceTo(this.pointAt(i / samples))\n if (d2 < min) {\n min = d2\n mindex = i\n }\n }\n\n // Find a minimum point for a bounded function. May be a local minimum.\n let minX = Math.max((mindex - 1) / samples, 0)\n let maxX = Math.min((mindex + 1) / samples, 1)\n let p2, t2 = 0\n let f = t => {\n t2 = t\n p2 = this.pointAt(t)\n return p.squaredDistanceTo(p2)\n }\n let e = 1e-4\n let k = 0.0\n while ((maxX - minX) > e) {\n k = (maxX + minX) / 2\n if (f(k - e) < f(k + e)) {\n maxX = k\n } else {\n minX = k\n }\n }\n return [p2, t2]\n }\n\n draw(g) {\n super.draw(g)\n g.beginPath()\n g.moveTo(this.position.x, this.position.y)\n g.bezierCurveTo(this.c1.x, this.c1.y, this.c2.x, this.c2.y, this.end.x, this.end.y)\n g.strokeStyle = this.stroke.color\n g.lineWidth = this.stroke.weight\n g.stroke()\n }\n}\n", "export { Node, DrawableNode, Stroke } from \"./node\"\nexport { GroupNode } from \"./group\"\nexport { LineNode, DiscNode } from \"./primitives\"\nexport { CubicBezierNode } from \"./bezier\"\n", "import { Vec, vec } from \"./math\"\nimport { GroupNode } from \"./node\"\n\n\nexport class HitEvent {\n constructor(node, point, scenePoint) {\n this.node = node\n this.point = point // in node's coordinate system\n this.scenePoint = scenePoint // updated by Scene in case of scene origin\n }\n}\n\n\nexport class Scene extends EventEmitter {\n constructor(renderer) {\n super()\n this.renderer = renderer\n this.width = renderer.canvas.width\n this.height = renderer.canvas.height\n this.pointer = vec(0,0)\n this.pointerDownAt = vec(0,0) // pointer value of last pointerdown event\n this.root = new GroupNode()\n this._eventHandlers = {}\n }\n\n enableHitTesting() {\n let pointerDownEvent = null\n this.addEventListener(\"pointerdown\", () => {\n if (pointerDownEvent = this.hitTest(this.pointer)) {\n this.pointerDownAt.x = this.pointer.x\n this.pointerDownAt.y = this.pointer.y\n pointerDownEvent.node.dispatchEvent(\"pointerdown\", pointerDownEvent)\n }\n })\n this.addEventListener(\"pointerup\", () => {\n let hitevent = this.hitTest(this.pointer)\n if (hitevent) {\n hitevent.node.dispatchEvent(\"pointerup\", hitevent)\n if (pointerDownEvent && hitevent.node !== pointerDownEvent.node) {\n pointerDownEvent.node.dispatchEvent(\"pointerup\", hitevent)\n }\n } else if (pointerDownEvent) {\n pointerDownEvent.node.dispatchEvent(\"pointerup\", new HitEvent(null, null))\n }\n pointerDownEvent = null\n })\n }\n\n hitTest(p) {\n return this.root.hitTest(p)\n }\n\n addEventListener(event, handler) {\n let n = super.addEventListener(event, handler)\n if (n == 1) {\n // first handler added\n this._addEventHandler(event)\n }\n return n\n }\n\n removeEventListener(event, handler) {\n let n = super.removeEventListener(event, handler)\n if (n == 0) {\n // last handler removed\n this._removeEventHandler(event)\n }\n return n\n }\n\n _addEventHandler(event) {\n const events1 = {\n \"pointermove\":1,\n \"pointerleave\":1,\n \"pointerenter\":1,\n \"pointerdown\":1,\n \"pointerup\":1,\n }\n if (!(event in events1)) {\n throw new Error(`invalid event ${event}`)\n }\n\n let handler = (\n event == \"pointermove\" ? ev => {\n this.pointer.x = ev.x\n this.pointer.y = ev.y\n let delta = this.pointer.sub(this.pointerDownAt)\n this.dispatchEvent(event, delta)\n } : ev => {\n this.pointer.x = ev.x\n this.pointer.y = ev.y\n this.dispatchEvent(event, this.pointer)\n }\n )\n this.renderer.canvas.addEventListener(event, handler)\n this._eventHandlers[event] = handler\n }\n\n _removeEventHandler(event) {\n let handler = this._eventHandlers[event]\n if (handler) {\n delete this._eventHandlers[event]\n this.renderer.canvas.removeEventListener(event, handler)\n }\n }\n\n // localPointerPosition() {\n // let p = this.pointer\n // let m = this.renderer.g.getTransform()\n // if (m.e != 0 || m.f != 0) {\n // p = vec(p.x - (m.e / m.a), p.y - (m.f / m.d))\n // }\n // return p\n // }\n\n add(n) { return this.root.add(n) }\n remove(n) { this.root.remove(n) }\n\n updateAndDraw(g, time) {\n g.clearRect(0, 0, this.width, this.height)\n this.root.updateAndDraw(g, time)\n }\n}\n\n", "export class Renderer extends EventEmitter {\n constructor() {\n super()\n this.canvas = $('canvas')\n this.g = this.canvas.getContext('2d')\n this.scale = 1\n this.updateScale()\n this.resizeCanvasToWindow()\n this._onWindowResize = () => {\n this.updateScale()\n this.resizeCanvasToWindow()\n this.dispatchEvent(\"resize\")\n }\n window.addEventListener(\"resize\", this._onWindowResize)\n }\n\n finalize() {\n window.removeEventListener(\"resize\", this._onWindowResize)\n }\n\n updateScale() {\n this.scale = (window.devicePixelRatio || 1) / (\n this.g.backingStorePixelRatio ||\n this.g.webkitBackingStorePixelRatio ||\n this.g.mozBackingStorePixelRatio ||\n this.g.msBackingStorePixelRatio ||\n this.g.oBackingStorePixelRatio ||\n 1\n )\n }\n\n resizeCanvasToWindow() {\n // this.resizeCanvas(window.innerWidth, window.innerWidth * (3/4))\n this.resizeCanvas(window.innerWidth, window.innerWidth)\n }\n\n resizeCanvas(width, height) {\n this.canvas.style.zoom = String(1 / this.scale)\n this.canvas.width = width * this.scale\n this.canvas.height = height * this.scale\n }\n\n render(scene, time) {\n const g = this.g\n g.setTransform(this.scale, 0, 0, this.scale, 0, 0)\n if (scene.transform) {\n g.transform(scene.transform)\n }\n scene.updateAndDraw(g, time)\n }\n}\n", "import \"./global\"\nimport { Scene } from \"./scene\"\nimport { Renderer } from \"./render\"\nimport { vec } from \"./math\"\nimport { GroupNode, LineNode, DiscNode, CubicBezierNode, Stroke } from \"./node\"\n\nfunction setupScene(scene, renderer) {\n let g = new GroupNode(vec(100,200))\n\n let curve1 = g.add(new CubicBezierNode(\n vec(250, 120), // start\n vec(290, -40), // control 1\n vec(300, 200), // control 2\n vec(400, 150), // end\n new Stroke(\"rgba(0,0,0,0.4)\", 1.5)\n ))\n\n // show bezier control points\n let handleSize = 4\n let c1Line = g.add(new LineNode(curve1.c1, curve1.position, \"rgba(0,0,0,0.1)\"))\n let c2Line = g.add(new LineNode(curve1.c2, curve1.end, \"rgba(0,0,0,0.1)\"))\n let curveStart = g.add(new DiscNode(curve1.position, handleSize, \"white\", \"rgba(0,30,200,0.5)\"))\n let curveEnd = g.add(new DiscNode(curve1.end, handleSize, \"white\", \"rgba(0,30,200,0.5)\"))\n let c1 = g.add(new DiscNode(curve1.c1, handleSize, \"white\", \"rgba(0,180,20,0.8)\"))\n let c2 = g.add(new DiscNode(curve1.c2, handleSize, \"white\", \"rgba(0,180,20,0.8)\"))\n function makeDraggable(n) {\n let origFill = n.fill\n const movePoint = movementDelta => {\n let p = pointerDot.pointFromSceneSpace(scene.pointer)\n n.position.x = p.x\n n.position.y = p.y\n n.dirty()\n c1Line.dirty()\n c2Line.dirty()\n curve1.dirty()\n }\n n.on(\"pointerdown\", ev => {\n n.fill = \"rgba(0,180,20,0.8)\"\n scene.on(\"pointermove\", movePoint)\n })\n n.on(\"pointerup\", ev => {\n n.fill = origFill\n scene.off(\"pointermove\", movePoint)\n })\n }\n makeDraggable(curveStart)\n makeDraggable(curveEnd)\n makeDraggable(c1)\n makeDraggable(c2)\n\n // pointer\n let pointerp = vec(-1000,-1000)\n let pointerPerimeter = g.add(new DiscNode(pointerp, 8, null, \"rgba(0,200,255,0.2)\"))\n let pointerDot = g.add(new DiscNode(pointerp, 8, \"rgba(0,200,255,0.3)\"))\n let pointerTangentLine = g.add(new LineNode(null, null, \"rgba(255,50,0,0.9)\"))\n let pointerNormalLine = g.add(new LineNode(null, null, \"rgba(0,100,255,0.9)\"))\n let pointerDotRay = g.add(new DiscNode(pointerp, 3, \"rgba(0,200,255,1)\"))\n\n const pointerUpdate = movementDelta => {\n pointerDot.position = pointerDot.pointFromSceneSpace(scene.pointer)\n\n let [p1, t] = curve1.closestPoint(pointerDot.position)\n pointerDotRay.position = p1\n\n let d = pointerDot.position.distanceTo(pointerDotRay.position)\n\n let p2 = p1.add(curve1.tangentAt(t))\n pointerTangentLine.position = p1\n pointerTangentLine.end = p2\n pointerTangentLine.length = Math.max(40, d) // adjust line to be fixed size\n\n pointerNormalLine.position = p1\n pointerNormalLine.end = p2\n pointerNormalLine.rotate(90)\n pointerNormalLine.length = Math.max(40, d)\n\n pointerPerimeter.position = pointerDot.position\n pointerPerimeter.radius = Math.max(pointerDot.radius, d)\n pointerPerimeter.stroke.color = `rgba(0,200,255,${Math.max(0.1,20/d)})`\n }\n scene.on(\"pointermove\", pointerUpdate)\n scene.on(\"pointerleave\", () => {\n scene.removeEventListener(\"pointermove\", pointerUpdate)\n pointerDotRay.visible = false\n pointerDot.visible = false\n pointerPerimeter.visible = false\n pointerTangentLine.visible = false\n pointerNormalLine.visible = false\n })\n scene.on(\"pointerenter\", () => {\n scene.addEventListener(\"pointermove\", pointerUpdate)\n pointerDotRay.visible = true\n pointerDot.visible = true\n pointerPerimeter.visible = true\n pointerTangentLine.visible = true\n pointerNormalLine.visible = true\n })\n\n // animated tangent & normal lines\n let tangentLine = g.add(new LineNode(null, null, \"rgba(255,50,0,0.9)\"))\n let normalLine = g.add(new LineNode(null, null, \"rgba(0,100,255,0.9)\"))\n let dot = g.add(new DiscNode(null, 2, \"black\"))\n tangentLine.update = time => {\n let t = Math.abs(1 - (time % 4000) / 2000)\n dot.position = curve1.pointAt(t)\n\n let p1 = curve1.pointAt(t)\n\n let vel = curve1.tangentAt(t)\n let p2 = p1.add(vel.mul(/* reduce length */0.3))\n tangentLine.position = p1\n tangentLine.end = p2\n\n normalLine.position = p1\n normalLine.end = p2\n normalLine.rotate(90)\n }\n\n scene.add(g)\n scene.enableHitTesting()\n\n // center group in scene\n // scene.\n}\n\n\nfunction main() {\n log(\"start\")\n let renderer = new Renderer()\n let scene = new Scene(renderer)\n setupScene(scene)\n\n let animate = true\n\n function tick(time) {\n renderer.render(scene, time)\n if (animate) {\n requestAnimationFrame(tick)\n }\n }\n\n if (animate) {\n requestAnimationFrame(tick)\n } else {\n tick(1200)\n }\n}\n\n// window.addEventListener('DOMContentLoaded', main)\nmain()\n"], 5 | "mappings": "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", 6 | "names": [] 7 | } 8 | --------------------------------------------------------------------------------