├── .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 |
--------------------------------------------------------------------------------