Instantly share code, notes, and snippets.

# lukem512/lukecaster.html

Created September 25, 2020 14:27
Show Gist options
• Save lukem512/9a9774195bf8a3246f009595ce679b3f to your computer and use it in GitHub Desktop.
LukeCaster (a simple JavaScript raycaster)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 LukeCaster
Click any two points to draw a boundary.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 class Point { constructor(x, y) { this._x = x this._y = y } get x() { return this._x } get y() { return this._y } set x(x) { this._x = x } set y(y) { this._y = y } // Determine equality betwee two points equals(point) { return this.x === point.x && this.y === point.y } // Find the distance to a specified point distanceTo(point) { let dx = this.x - point.x let dy = this.y - point.y let x2 = dx * dx let y2 = dy * dy return Math.sqrt(x2 + y2) } paint(ctx, { diameter = 5, color = "black" }) { let radius = Math.round(diameter / 2) ctx.fillStyle = color ctx.fillRect(this.x - radius, this.y - radius, diameter, diameter) } } class Ray extends Point { constructor(x, y, theta) { super(x, y) this._theta = theta } get theta() { return this._theta } set theta(theta) { this._theta = theta } // Determine where a ray will strike and illuminate // a specified boundary line. // Returns a point if one exists, null otherwise illuminationPoint(boundary, length) { let rayPoint = this.pointOnRay(length) // Points on ray, (x1, y1) (x2, y2) let x1 = this.x let y1 = this.y let x2 = rayPoint.x let y2 = rayPoint.y // Points on boundary, (x3, y3) (x4, y4) let x3 = boundary.a.x let y3 = boundary.a.y let x4 = boundary.b.x let y4 = boundary.b.y // Check for zero-length lines if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) { return null } // Find intersection let denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) // Are lines parallel? if (denom === 0) { return null } let ua = ((x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3)) / denom let ub = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / denom // Lines do not intersect along specified segments if (ua < 0 || ua > 1 || ub < 0 || ub > 1) { return null } let px = x1 + ua * (x2 - x1) let py = y1 + ua * (y2 - y1) return new Point(px, py) } // Determine whether the ray will strike and illuminate // a specified boundary line willIlluminate(boundary, length) { return this.illuminationPoint(boundary, length) !== null } pointOnRay(distance = 200) { let dx = distance * Math.cos(this.theta) let dy = distance * Math.sin(this.theta) return new Point(this.x + dx, this.y + dy) } paint(ctx, { thickness = 1, color = "LightGoldenRodYellow", length = 100, boundaries = [] }) { let endPoint let illuminationPoints = boundaries.filter(x => this.willIlluminate(x, length)).map(x => this.illuminationPoint(x, length)) // If there are illuminated boundary points, find the closest one and end the ray there // Otherwise, illuminate the full length of the ray if (illuminationPoints.length) { endPoint = illuminationPoints.reduce((closest, cur) => { if (cur.distanceTo(this) < closest.distanceTo(this)) { return cur } return closest }, illuminationPoints[0]) } else { endPoint = this.pointOnRay(length) } ctx.lineWidth = thickness ctx.lineCap = "round" ctx.strokeStyle = color ctx.beginPath() ctx.moveTo(this.x, this.y) ctx.lineTo(endPoint.x, endPoint.y) ctx.closePath() ctx.stroke() } } class Boundary { // Accepts two Points, a start and end position constructor(a, b) { this._a = a this._b = b } get a() { return this._a } get b() { return this._b } set a(point) { if (point instanceof Point) { this._a = point } } set b(point) { if (point instanceof Point) { this._b = point } } get xLength() { return Math.abs(this.a.x - this.b.x) } get yLength() { return Math.abs(this.a.y - this.b.y) } get length() { return this.a.distanceTo(this.b) } paint(ctx, { thickness = 3, color = "black" }) { ctx.lineWidth = thickness ctx.lineCap = "round" ctx.strokeStyle = color ctx.beginPath() ctx.moveTo(this.a.x, this.a.y) ctx.lineTo(this.b.x, this.b.y) ctx.stroke() ctx.closePath() } }
to join this conversation on GitHub. Already have an account? Sign in to comment