www.spaceplanner.app

Web client to the spaceplanner API
git clone git://jacobedwards.org/www.spaceplanner.app
Log | Files | Refs

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 }