geometry.js (5120B)
1 import { default as SVG } from "/lib/github.com/svgdotjs/svg.js/svg.js" 2 import { Vector2 } from "/lib/github.com/mrdoob/three.js/math/Vector2.js" 3 4 SVG.extend(SVG.Point, { 5 vec: function() { 6 return new Vector2(this.x, this.y) 7 } 8 }) 9 10 SVG.extend(SVG.Circle, { 11 vec: function() { 12 return new Vector2(this.cx(), this.cy()) 13 } 14 }) 15 16 SVG.extend(SVG.Shape, { 17 vec: function() { 18 return new Vector2(this.x(), this.y()) 19 }, 20 21 distanceTo: function(x, y) { 22 return this.bbox().distanceTo(x, y) 23 }, 24 25 insideT: function(x, y) { 26 const box = this.rbox(this.root()) 27 28 return ( 29 x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height 30 ) 31 }, 32 33 touching: function(x, y, minsize) { 34 let b = this.rbox(this.root()) 35 if (b.width < minsize) { 36 b.x -= (minsize - b.width) / 2 37 b.width = minsize 38 } 39 if (b.height < minsize) { 40 b.y -= (minsize - b.height) / 2 41 b.height = minsize 42 } 43 return x >= b.x && x <= b.x + b.width && 44 y >= b.y && y <= b.y + b.height 45 } 46 }) 47 48 SVG.extend(SVG.Line, { 49 vecs: function() { 50 let a = this.array() 51 let vecs = [] 52 for (let i in a) { 53 vecs.push(new Vector2(a[i][0], a[i][1])) 54 } 55 return vecs 56 }, 57 58 // See https://math.stackexchange.com/questions/274712/calculate-on-which-side-of-a-straight-line-is-a-given-po 59 vecs_top_first: function() { 60 let v = this.vecs() 61 return v[1].y > v[0].y ? v : [v[1], v[0]]; 62 }, 63 64 point_offset: function(point) { 65 const cross = function(a, b, o) { 66 return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) 67 } 68 const t = this.vecs_top_first() 69 return cross(point, t[1], t[0]) 70 }, 71 72 /* 73 * Most of this copied from the svg.js math library, 74 * but I didn't particularly like the look of the 75 * library itself so I wrote equivilents here. 76 */ 77 segmentLengthSquared: function() { 78 let vecs = this.vecs() 79 return vecs[0].distanceToSquared(vecs[1]) 80 }, 81 82 closestLinearInterpolation: function(p) { 83 let vecs = this.vecs() 84 let d = vecs[1].clone().sub(vecs[0]) 85 let x = p.clone().sub(vecs[0]).multiply(d) 86 return (x.x + x.y) / this.segmentLengthSquared() 87 }, 88 89 interpolatedPoint: function(t) { 90 let vecs = this.vecs() 91 return vecs[0].lerp(vecs[1], t) 92 }, 93 94 closestPoint: function(p) { 95 return this.interpolatedPoint( 96 Math.min(1, Math.max(0, this.closestLinearInterpolation(p))) 97 ) 98 }, 99 100 intersection: function(line2) { 101 let d = this.a * line2.b - line2.a * this.b; 102 103 return { 104 parallel: (d === 0), 105 x: (line2.b * this.c - this.b * line2.c) / d, 106 y: (this.a * line2.c - line2.a * this.c) / d 107 } 108 }, 109 110 whereIsPoint: function(x, y, width) { 111 let p = new Vector2(x, y) 112 if (width == null) { 113 width = this.attr("stroke-width") ?? 1 114 } 115 let closest = this.closestPoint(p) 116 117 /* 118 * Note that this doesn't work very accurately for 119 * lines that aren't at 90 degree angles. Check out 120 * Harry Stevens's geometric library with the lineOnPoint 121 * function and the epsilon number 122 */ 123 let h = width / 2 124 if (p.x > closest.x - h && p.x < closest.x + h && 125 p.y > closest.y - h && p.y < closest.y + h) { 126 return closest 127 } 128 return null 129 }, 130 131 // This must use x and y to be compatible with Shape's inside() 132 inside: function(x, y) { 133 return this.whereIsPoint(x, y) != null 134 }, 135 136 insideT: function(x, y) { 137 return this.inside(x, y) 138 }, 139 140 touching: function(x, y, width) { 141 return this.whereIsPoint(x, y, width) != null 142 }, 143 144 closestEdge: function(x, y) { 145 let p = new Vector2(x, y) 146 let w = this.attr("stroke-width") ?? 1 147 let c = this.closestPoint(p) 148 149 let b = new SVG.Box(c.x - w / 2, c.y - w / 2, w, w) 150 return b.closestEdge(p.x, p.y) 151 }, 152 153 distanceTo: function(x, y) { 154 return this.closestEdge(x, y).distanceTo(new Vector2(x, y)) 155 } 156 }) 157 158 SVG.extend(SVG.Box, { 159 closestEdge: function(x, y) { 160 let ex 161 if (x < this.x) { 162 ex = this.x 163 } else if (x > this.x + this.width) { 164 ex = this.x + this.width 165 } else { 166 ex = x 167 } 168 let ey 169 if (y < this.y) { 170 ey = this.y 171 } else if (y > this.y + this.height) { 172 ey = this.y + this.height 173 } else { 174 ey = y 175 } 176 177 let ev = new Vector2(ex, ey) 178 return ev 179 }, 180 181 distanceTo: function(x, y) { 182 return this.closestEdge(x, y).distanceTo(new Vector2(x, y)) 183 } 184 }) 185 186 export function rad(deg) { 187 return deg * Math.PI / 180 188 } 189 190 export function deg(rad) { 191 return rad * (180 / Math.PI) 192 } 193 194 export function length(a, b, length) { 195 if (!length) { 196 return a.distanceTo(b) 197 } 198 199 /* 200 * Not sure if a zero length line is worth supporting, it doesn't 201 * really work naturally. To support it you would need another 202 * store of information in addition to the vector 203 */ 204 if (length <= 0) { 205 throw new Error("Zero length line wouldn't be able to be lengthened again") 206 } 207 /* 208 * Basically make it's origin zero, normalize it to be from 209 * 0-1, multiply it by length, then add the origin back to it. 210 */ 211 return b.sub(a).normalize().multiplyScalar(length).add(a) 212 } 213 214 export function lineAngle(p1, p2) { 215 return Math.atan2(p2.y - p1.y, p2.x - p1.x) 216 } 217 218 export function compareVecs(a, b) { 219 if (a.y !== b.y) { 220 return a.y < b.y 221 } 222 if (a.x < b.x) { 223 return true 224 } 225 return false 226 } 227 228 export function sortVecs(vecs) { 229 return vecs.sort(compareVecs) 230 }