Last active
June 29, 2024 13:53
-
-
Save dmitriypereverza/28771c37677cb2c74f4173155878687e to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Title</title> | |
| <style> | |
| body { | |
| position: relative; | |
| background: gray; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| canvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| border: 1px solid rgba(0, 0, 0, 0.5); | |
| /*background: white;*/ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| class Bitmap { | |
| constructor(width, height, scale = 1) { | |
| this.width = width; | |
| this.height = height; | |
| this.scale = scale; | |
| this.data = new Uint8Array(width * scale * height * scale); | |
| this.scaledWidth = width * scale; | |
| this.scaledHeight = height * scale; | |
| } | |
| clean() { | |
| for (let i = 0; i < this.data.length; i++) { | |
| this.data[i] = 0; | |
| } | |
| } | |
| set(x, y, val) { | |
| for (let sy = Math.round(y * this.scale); sy < (y + 1) * this.scale; sy++) { | |
| for (let sx = Math.round(x * this.scale); sx < (x + 1) * this.scale; sx++) { | |
| this.data[sy * this.scaledWidth + sx] = val; | |
| } | |
| } | |
| } | |
| setScaled(x, y, val) { | |
| this.data[Math.round(Math.round(y) * this.scaledWidth + Math.round(x))] = val; | |
| } | |
| getScaled(x, y) { | |
| return this.data[Math.round(Math.round(y) * this.scaledWidth + Math.round(x))]; | |
| } | |
| get(x, y) { | |
| return this.data[Math.round((y * this.scale * this.scaledWidth + x * this.scale))]; | |
| } | |
| /** | |
| * @param {number} px | |
| * @param {number} py | |
| * @return {number} | |
| */ | |
| getInPercents(px, py) { | |
| return this.data[Math.round((Math.round(py * this.scaledHeight) * this.scaledWidth + px * this.scaledWidth))]; | |
| } | |
| } | |
| const NORTH = 0, SOUTH = 1, EAST = 2, WEST = 3; | |
| class LightCasting2D { | |
| /** | |
| * @param {number} radius | |
| * @param {number} quality | |
| */ | |
| constructor(radius, quality = 0.12) { | |
| this.radius = radius; | |
| this.vecEdges = []; | |
| this.vecVisibilityPolygonPoints = []; | |
| this.lightMap = new Bitmap(radius * 2, radius * 2, quality); | |
| } | |
| /** | |
| * @param {boolean[]} world | |
| * @param {number} sx | |
| * @param {number} sy | |
| * @param {number} width | |
| * @param {number} height | |
| * @param {number} fBlockWidth | |
| */ | |
| addTileMapToPolyMap(world, sx, sy, width, height, fBlockWidth) { | |
| this.world = world; | |
| this.worldEdgeInfo = world.map(() => ({ | |
| edge_id: [0, 0, 0, 0], | |
| edge_exist: [false, false, false, false] | |
| })); | |
| this.vecEdges = []; | |
| for (let x = 0; x < width; x++) { | |
| for (let y = 0; y < height; y++) { | |
| for (let j = 0; j < 4; j++) { | |
| this.worldEdgeInfo[(y + sy) * width + (x + sx)].edge_exist[j] = false; | |
| this.worldEdgeInfo[(y + sy) * width + (x + sx)].edge_id[j] = 0; | |
| } | |
| } | |
| } | |
| this.vecEdges.push( | |
| {sx: 0, sy: sy, ex: width * fBlockWidth, ey: sy}, // top | |
| {sx: 0, sy: height * fBlockWidth, ex: width * fBlockWidth, ey: height * fBlockWidth}, // bottom | |
| {sx: 0, sy: 0, ex: 0, ey: height * fBlockWidth}, // left | |
| {sx: width * fBlockWidth, sy: 0, ex: width * fBlockWidth, ey: height * fBlockWidth}, // right | |
| ); | |
| for (let x = 0; x < width; x++) { | |
| for (let y = 0; y < height; y++) { | |
| let i = (y + sy) * width + (x + sx); // This | |
| let n = (y + sy - 1) * width + (x + sx); // Northern Neighbour | |
| let s = (y + sy + 1) * width + (x + sx); // Southern Neighbour | |
| let w = (y + sy) * width + (x + sx - 1); // Western Neighbour | |
| let e = (y + sy) * width + (x + sx + 1); // Eastern Neighbour | |
| if (this.world[i]) { | |
| if (!this.world[w]) { | |
| if (this.worldEdgeInfo[n]?.edge_exist[WEST]) { | |
| this.vecEdges[this.worldEdgeInfo[n].edge_id[WEST]].ey += fBlockWidth; | |
| this.worldEdgeInfo[i].edge_id[WEST] = this.worldEdgeInfo[n].edge_id[WEST]; | |
| this.worldEdgeInfo[i].edge_exist[WEST] = true; | |
| } else { | |
| let edge = {sx: (sx + x) * fBlockWidth, sy: (sy + y) * fBlockWidth}; | |
| edge.ex = edge.sx; | |
| edge.ey = edge.sy + fBlockWidth; | |
| let edge_id = this.vecEdges.length; | |
| this.vecEdges.push(edge); | |
| this.worldEdgeInfo[i].edge_id[WEST] = edge_id; | |
| this.worldEdgeInfo[i].edge_exist[WEST] = true; | |
| } | |
| } | |
| if (!this.world[e]) { | |
| if (this.worldEdgeInfo[n]?.edge_exist[EAST]) { | |
| this.vecEdges[this.worldEdgeInfo[n].edge_id[EAST]].ey += fBlockWidth; | |
| this.worldEdgeInfo[i].edge_id[EAST] = this.worldEdgeInfo[n].edge_id[EAST]; | |
| this.worldEdgeInfo[i].edge_exist[EAST] = true; | |
| } else { | |
| let edge = {sx: (sx + x + 1) * fBlockWidth, sy: (sy + y) * fBlockWidth}; | |
| edge.ex = edge.sx; | |
| edge.ey = edge.sy + fBlockWidth; | |
| let edge_id = this.vecEdges.length; | |
| this.vecEdges.push(edge); | |
| this.worldEdgeInfo[i].edge_id[EAST] = edge_id; | |
| this.worldEdgeInfo[i].edge_exist[EAST] = true; | |
| } | |
| } | |
| if (!this.world[n]) { | |
| if (this.worldEdgeInfo[w]?.edge_exist[NORTH]) { | |
| this.vecEdges[this.worldEdgeInfo[w].edge_id[NORTH]].ex += fBlockWidth; | |
| this.worldEdgeInfo[i].edge_id[NORTH] = this.worldEdgeInfo[w].edge_id[NORTH]; | |
| this.worldEdgeInfo[i].edge_exist[NORTH] = true; | |
| } else { | |
| let edge = {sx: (sx + x) * fBlockWidth, sy: (sy + y) * fBlockWidth}; | |
| edge.ex = edge.sx + fBlockWidth; | |
| edge.ey = edge.sy; | |
| let edge_id = this.vecEdges.length; | |
| this.vecEdges.push(edge); | |
| this.worldEdgeInfo[i].edge_id[NORTH] = edge_id; | |
| this.worldEdgeInfo[i].edge_exist[NORTH] = true; | |
| } | |
| } | |
| if (!this.world[s]) { | |
| if (this.worldEdgeInfo[w]?.edge_exist[SOUTH]) { | |
| this.vecEdges[this.worldEdgeInfo[w].edge_id[SOUTH]].ex += fBlockWidth; | |
| this.worldEdgeInfo[i].edge_id[SOUTH] = this.worldEdgeInfo[w].edge_id[SOUTH]; | |
| this.worldEdgeInfo[i].edge_exist[SOUTH] = true; | |
| } else { | |
| let edge = {sx: (sx + x) * fBlockWidth, sy: (sy + y + 1) * fBlockWidth}; | |
| edge.ex = edge.sx + fBlockWidth; | |
| edge.ey = edge.sy; | |
| let edge_id = this.vecEdges.length; | |
| this.vecEdges.push(edge); | |
| this.worldEdgeInfo[i].edge_id[SOUTH] = edge_id; | |
| this.worldEdgeInfo[i].edge_exist[SOUTH] = true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * @param {number} ox | |
| * @param {number} oy | |
| */ | |
| calculateVisibilityPolygon(ox, oy) { | |
| this.vecVisibilityPolygonPoints = []; | |
| const countRadialRays = 20; | |
| for (let i = 0; i <= countRadialRays; i++) { | |
| const ang = - Math.PI + i * Math.PI * 2 / countRadialRays; | |
| const current = this.castLightRay(ox, oy, ang, this.radius); | |
| const prev2 = this.vecVisibilityPolygonPoints[this.vecVisibilityPolygonPoints.length - 2]; | |
| const prev = this.vecVisibilityPolygonPoints[this.vecVisibilityPolygonPoints.length - 1]; | |
| if (prev2 && prev && getDistanceFrom2DPointToLine(current.x, current.y, prev2.x, prev2.y, prev.x, prev.y) <= 0.05) { | |
| prev.x = current.x; | |
| prev.y = current.y; | |
| } else { | |
| this.vecVisibilityPolygonPoints.push(current); | |
| } | |
| } | |
| for (let e1 of this.vecEdges) { | |
| const bothOfEdgePointsOutOfDistance = distToSegment(ox, oy, e1.sx, e1.sy, e1.ex, e1.ey) > this.radius; | |
| if (bothOfEdgePointsOutOfDistance) continue; | |
| for (let i = 0; i < 2; i++) { | |
| let rdx = (i === 0 ? e1.sx : e1.ex) - ox; | |
| let rdy = (i === 0 ? e1.sy : e1.ey) - oy; | |
| let ang = 0; | |
| const base_ang = Math.atan2(rdy, rdx); | |
| for (let j = 0; j < 3; j++) { | |
| const delta = 0.001; | |
| if (j === 0) ang = base_ang - delta; | |
| if (j === 1) ang = base_ang; | |
| if (j === 2) ang = base_ang + delta; | |
| const vec = this.castLightRay( ox, oy, ang, this.radius); | |
| this.vecVisibilityPolygonPoints.push(vec); | |
| } | |
| } | |
| } | |
| this.vecVisibilityPolygonPoints.sort((p1, p2) => p1.ang < p2.ang ? -1 : 1); | |
| const joinDistance = 0.2; | |
| const uniqVecVisibilityPolygonPoints = []; | |
| for (let i = 0; i < this.vecVisibilityPolygonPoints.length - 1; i++) { | |
| const prev = this.vecVisibilityPolygonPoints[i - 1]; | |
| const current = this.vecVisibilityPolygonPoints[i]; | |
| let next = this.vecVisibilityPolygonPoints[i + 1]; | |
| uniqVecVisibilityPolygonPoints.push(current); | |
| let isClosePoint = Math.abs(current.x - next.x) < joinDistance && Math.abs(current.y - next.y) < joinDistance; | |
| while (next && isClosePoint) { | |
| i++; | |
| next = this.vecVisibilityPolygonPoints[i + 1]; | |
| isClosePoint = next && Math.abs(current.x - next.x) < joinDistance && Math.abs(current.y - next.y) < joinDistance; | |
| } | |
| let nextPointOnCurrentLine = prev && current && next && getDistanceFrom2DPointToLine(next.x, next.y, prev.x, prev.y, current.x, current.y) <= 0.1; | |
| while (nextPointOnCurrentLine) { | |
| current.x = next.x; | |
| current.y = next.y; | |
| i++; | |
| next = this.vecVisibilityPolygonPoints[i + 1]; | |
| nextPointOnCurrentLine = prev && current && next && getDistanceFrom2DPointToLine(next.x, next.y, prev.x, prev.y, current.x, current.y) <= 0.1; | |
| } | |
| } | |
| this.vecVisibilityPolygonPoints = uniqVecVisibilityPolygonPoints; | |
| // add last segment | |
| this.vecVisibilityPolygonPoints.push({ | |
| x: this.vecVisibilityPolygonPoints[0].x, | |
| y: this.vecVisibilityPolygonPoints[0].y, | |
| ang: this.vecVisibilityPolygonPoints[0].ang + Math.PI * 2 | |
| }); | |
| this.lightMap.clean(); | |
| const mapStartX = ox - this.radius; | |
| const mapStartY = oy - this.radius; | |
| const radiusInLightMap = this.lightMap.scaledWidth / 2; | |
| for (let i = 0; i < this.vecVisibilityPolygonPoints.length - 1; i++) { | |
| const current = this.vecVisibilityPolygonPoints[i]; | |
| const next = this.vecVisibilityPolygonPoints[i + 1]; | |
| const minX = Math.floor((Math.min(ox, current.x, next.x) - mapStartX) * this.lightMap.scale); | |
| const minY = Math.floor((Math.min(oy, current.y, next.y) - mapStartY) * this.lightMap.scale); | |
| const maxX = Math.ceil((Math.max(ox, current.x, next.x) - mapStartX) * this.lightMap.scale); | |
| const maxY = Math.ceil((Math.max(oy, current.y, next.y) - mapStartY) * this.lightMap.scale); | |
| for (let x = minX; x <= maxX; x++) { | |
| for (let y = minY; y <= maxY; y++) { | |
| if (this.lightMap.getScaled(x, y) > 0) continue; | |
| if (!is2DPointInTriangle( | |
| x, | |
| y, | |
| radiusInLightMap, | |
| radiusInLightMap, | |
| (current.x - mapStartX ) * this.lightMap.scale, | |
| (current.y - mapStartY) * this.lightMap.scale, | |
| (next.x - mapStartX) * this.lightMap.scale, | |
| (next.y - mapStartY) * this.lightMap.scale, | |
| 0.1 | |
| )) { | |
| continue; | |
| } | |
| this.lightMap.setScaled(x, y, (1 - (getMagnitude(x, y, radiusInLightMap, radiusInLightMap) / radiusInLightMap)) * 255); | |
| } | |
| } | |
| window.lm = this.lightMap; | |
| } | |
| } | |
| castLightRay( ox, oy, ang, distance) { | |
| const rdx = distance * Math.cos(ang); | |
| const rdy = distance * Math.sin(ang); | |
| let minIntersection = {x: ox + rdx, y: oy + rdy}; | |
| let minMagnitude = distance; | |
| let min_ang = ang; | |
| for (let e2 of this.vecEdges) { | |
| const bothOfEdgePointsOutOfDistance = distToSegment( ox, oy, e2.sx, e2.sy, e2.ex, e2.ey) > distance; | |
| if (bothOfEdgePointsOutOfDistance) continue; | |
| let intersection = intersect(e2.sx, e2.sy, e2.ex, e2.ey, ox, oy, ox + rdx, oy + rdy); | |
| if (!intersection) continue; | |
| const magnitude = getMagnitude(intersection.x, intersection.y, ox, oy); | |
| if (magnitude < minMagnitude) { | |
| minIntersection = intersection; | |
| minMagnitude = magnitude; | |
| min_ang = Math.atan2(intersection.y - oy, intersection.x - ox); | |
| } | |
| } | |
| return { ang: min_ang, x: minIntersection.x, y: minIntersection.y }; | |
| } | |
| isPointInLight(ox, oy, x, y) { | |
| const lightMapStartX = ox - this.radius; | |
| const lightMapStartY = oy - this.radius; | |
| const lightMapEndX = ox + this.radius; | |
| const lightMapEndY = oy + this.radius; | |
| if (x < lightMapStartX || x > lightMapEndX || y < lightMapStartY || y > lightMapEndY) return false; | |
| return this.lightMap.getInPercents( | |
| (x - lightMapStartX) / this.lightMap.width, | |
| (y - lightMapStartY) / this.lightMap.height | |
| ) > 0; | |
| } | |
| } | |
| function getMagnitude(sx, sy, ex, ey) { | |
| return Math.sqrt(Math.pow(sx - ex, 2) + Math.pow(sy - ey, 2)); | |
| } | |
| function intersect(x1, y1, x2, y2, x3, y3, x4, y4) { | |
| if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return false; | |
| const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)) | |
| if (denominator === 0) return false; | |
| let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator | |
| let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator | |
| if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return false; | |
| return { x: x1 + ua * (x2 - x1), y: y1 + ua * (y2 - y1) }; | |
| } | |
| function dist2(x, y, x2, y2) { | |
| return Math.pow(x - x2, 2) + Math.pow(y - y2, 2) | |
| } | |
| function distToSegment(x, y, sx, sy, ex, ey) { | |
| const l2 = dist2(sx, sy, ex, ey); | |
| if (l2 === 0) return getMagnitude(x, y, sx, sy); | |
| let t = ((x - sx) * (ex - sx) + (y - sy) * (ey - sy)) / l2; | |
| t = Math.max(0, Math.min(1, t)); | |
| return getMagnitude(x, y, sx + t * (ex - sx), sy + t * (ey - sy)); | |
| } | |
| function getDistanceFrom2DPointToLine(pointX, pointY, pointOnLineX, pointOnLineY, anotherPointOnLineX, anotherPointOnLineY) { | |
| const A = anotherPointOnLineY - pointOnLineY; | |
| const B = pointOnLineX - anotherPointOnLineX; | |
| const C = anotherPointOnLineX * pointOnLineY - pointOnLineX * anotherPointOnLineY; | |
| return Math.abs(A * pointX + B * pointY + C) / Math.sqrt(A * A + B * B); | |
| } | |
| function barycentric(pointX, pointY, triangleP1x, triangleP1y, triangleP2x, triangleP2y, triangleP3x, triangleP3y) { | |
| const v0x = triangleP3x - triangleP1x; | |
| const v0y = triangleP3y - triangleP1y; | |
| const v1x = triangleP2x - triangleP1x; | |
| const v1y = triangleP2y - triangleP1y; | |
| const v2x = pointX - triangleP1x; | |
| const v2y = pointY - triangleP1y; | |
| const dot00 = v0x * v0x + v0y * v0y; | |
| const dot01 = v0x * v1x + v0y * v1y; | |
| const dot02 = v0x * v2x + v0y * v2y; | |
| const dot11 = v1x * v1x + v1y * v1y; | |
| const dot12 = v1x * v2x + v1y * v2y; | |
| const invDenom = 1 / (dot00 * dot11 - dot01 * dot01); | |
| const u = (dot11 * dot02 - dot01 * dot12) * invDenom; | |
| const v = (dot00 * dot12 - dot01 * dot02) * invDenom; | |
| return [u, v, 1 - u - v]; | |
| } | |
| function is2DPointInTriangle(pointX, pointY, triangleP1x, triangleP1y, triangleP2x, triangleP2y, triangleP3x, triangleP3y, threshold = 0.1) { | |
| const a = (triangleP1x - pointX) * (triangleP2y - triangleP1y) - (triangleP2x - triangleP1x) * (triangleP1y - pointY); | |
| const b = (triangleP2x - pointX) * (triangleP3y - triangleP2y) - (triangleP3x - triangleP2x) * (triangleP2y - pointY); | |
| const c = (triangleP3x - pointX) * (triangleP1y - triangleP3y) - (triangleP1x - triangleP3x) * (triangleP3y - pointY); | |
| return (a >= 0 && b >= 0 && c >= 0) || (a <= 0 && b <= 0 && c <= 0); | |
| } | |
| function radiansToDegrees(radial) { | |
| return 180 * radial / Math.PI; | |
| } | |
| function degreesToRadians(angle) { | |
| return angle * Math.PI / 180; | |
| } | |
| </script> | |
| <script> | |
| let _mouseX = 0; | |
| let _mouseY = 0; | |
| let _mouseDown = undefined; | |
| let _mouseReleased = false; | |
| let _mouseHeld = false; | |
| document.addEventListener('mousedown', (event) => { | |
| _mouseReleased = false; | |
| _mouseDown = event.button; | |
| _mouseHeld = true; | |
| }); | |
| function mouseX() { | |
| return _mouseX; | |
| } | |
| function mouseY() { | |
| return _mouseY; | |
| } | |
| function mouseButton() { | |
| if (_mouseDown === undefined) return _mouseDown; | |
| return _mouseDown === 0 ? 'left' : 'right'; | |
| } | |
| function mouseReleased() { | |
| return _mouseReleased; | |
| } | |
| function mouseHeld() { | |
| return _mouseHeld; | |
| } | |
| document.addEventListener('mouseup', () => { | |
| _mouseReleased = true; | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| _mouseX = e.clientX - canvas.offsetLeft; | |
| _mouseY = e.clientY - canvas.offsetTop; | |
| if (mouseButton()) { | |
| _mouseHeld = true; | |
| } | |
| }); | |
| const nWorldWidth = 30; | |
| const nWorldHeight = 15; | |
| const fBlockWidth = 40; | |
| let canvas, buffLightRay, buffLightTex; | |
| const canvasWidth = fBlockWidth * nWorldWidth; | |
| const canvasHeight = fBlockWidth * nWorldHeight; | |
| const convertTileMapToPolyMapTime = getFnTimeBuilder(); | |
| const calculateVisibilityPolygonTime = getFnTimeBuilder(); | |
| const isPointLight = getFnTimeBuilder(); | |
| class Main { | |
| constructor() { | |
| this.world = new Array(nWorldWidth * nWorldHeight).fill(false); | |
| this.shadowCaster = new LightCasting2D(200); | |
| canvas = document.createElement('canvas'); | |
| document.body.appendChild(canvas); | |
| canvas.width = canvasWidth; | |
| canvas.height = canvasHeight; | |
| window.ctx = canvas.getContext('2d'); | |
| buffLightTex = document.createElement('canvas'); | |
| document.body.appendChild(buffLightTex); | |
| buffLightTex.width = canvasWidth; | |
| buffLightTex.height = canvasHeight; | |
| window.ctx2 = buffLightTex.getContext('2d'); | |
| buffLightRay = document.createElement('canvas'); | |
| document.body.appendChild(buffLightRay); | |
| buffLightRay.width = canvasWidth; | |
| buffLightRay.height = canvasHeight; | |
| window.ctx3 = buffLightRay.getContext('2d'); | |
| } | |
| update() { | |
| let fSourceX = mouseX(); | |
| let fSourceY = mouseY(); | |
| if (mouseButton() === 'left' && mouseReleased()) { | |
| let i = ((Math.floor(fSourceY / fBlockWidth)) * nWorldWidth + Math.floor(fSourceX / fBlockWidth)); | |
| this.world[i] = !this.world[i]; | |
| } | |
| convertTileMapToPolyMapTime.measure(() => { | |
| this.shadowCaster.addTileMapToPolyMap(this.world, 0, 0, nWorldWidth, nWorldHeight, fBlockWidth); | |
| }) | |
| if (mouseButton() === 'right' && mouseHeld()) { | |
| calculateVisibilityPolygonTime.measure(() => { | |
| this.shadowCaster.calculateVisibilityPolygon(fSourceX, fSourceY); | |
| }) | |
| } | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = 'black'; | |
| for (let x = 0; x < canvasWidth; x = x + 4) { | |
| for (let y = 0; y < canvasHeight; y = y + 4) { | |
| const isPointInLight = isPointLight.measure(() => this.shadowCaster.isPointInLight(fSourceX, fSourceY, x, y)); | |
| if (isPointInLight) continue; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 1, 0, 2 * Math.PI); | |
| ctx.fill(); | |
| } | |
| } | |
| const vecVisibilityPolygonPoints = this.shadowCaster.vecVisibilityPolygonPoints; | |
| let nRaysCast = vecVisibilityPolygonPoints.length; | |
| ctx.font = "24px Arial"; | |
| ctx.fillText(`Rays Cast: ${nRaysCast}`, 40, 30); | |
| ctx.fillText(`convertTileMapToPolyMapTime: ${convertTileMapToPolyMapTime.avgTime().toFixed(3)}`, 40, 60); | |
| ctx.fillText(`calculateVisibilityPolygonTime: ${calculateVisibilityPolygonTime.avgTime().toFixed(3)}`, 40, 90); | |
| ctx.fillText(`isPointLight: ${isPointLight.avgTime().toFixed(3)}`, 40, 120); | |
| ctx3.clearRect(0, 0, buffLightRay.width, buffLightRay.height); | |
| if (mouseButton() === 'right' && vecVisibilityPolygonPoints.length > 1) { | |
| ctx2.clearRect(0, 0, buffLightTex.width, buffLightTex.height); | |
| // for (let i = 0; i < vecVisibilityPolygonPoints.length - 1; i++) { | |
| // ctx3.beginPath(); | |
| // ctx3.fillStyle = 'rgba(255, 255, 255, 0.2)'; | |
| // ctx3.strokeStyle = 'rgba(0, 0, 0, 0.1)'; | |
| // ctx3.moveTo(fSourceX, fSourceY); | |
| // ctx3.lineTo(vecVisibilityPolygonPoints[i].x, vecVisibilityPolygonPoints[i].y); | |
| // ctx3.lineTo(vecVisibilityPolygonPoints[i + 1].x, vecVisibilityPolygonPoints[i + 1].y); | |
| // ctx3.fill(); | |
| // // ctx3.stroke(); | |
| // } | |
| // ctx3.beginPath(); | |
| // ctx3.moveTo(fSourceX, fSourceY); | |
| // ctx3.lineTo(vecVisibilityPolygonPoints[vecVisibilityPolygonPoints.length - 1].x, vecVisibilityPolygonPoints[vecVisibilityPolygonPoints.length - 1].y); | |
| // ctx3.lineTo(vecVisibilityPolygonPoints[0].x, vecVisibilityPolygonPoints[0].y); | |
| // ctx3.fill(); | |
| } | |
| for (let x = 0; x < nWorldWidth; x++) { | |
| for (let y = 0; y < nWorldHeight; y++) { | |
| if (this.world[y * nWorldWidth + x]) { | |
| ctx.fillStyle = 'green'; | |
| ctx.fillRect(x * fBlockWidth, y * fBlockWidth, fBlockWidth, fBlockWidth); | |
| } | |
| } | |
| } | |
| for (let e of this.shadowCaster.vecEdges) { | |
| ctx.strokeStyle = 'blue'; | |
| ctx.beginPath(); | |
| ctx.moveTo(e.sx, e.sy); | |
| ctx.lineTo(e.ex, e.ey); | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| function getFnTimeBuilder() { | |
| const times = []; | |
| return { | |
| measure: (fn) => { | |
| // const start = performance.now(); | |
| const res = fn(); | |
| // times.push(performance.now() - start); | |
| // if (times.length > 100) times.shift(); | |
| return res; | |
| }, | |
| avgTime: () => { | |
| return 0; | |
| // return times.reduce((a, b) => a + b, 0) / times.length; | |
| } | |
| } | |
| } | |
| const main = new Main(); | |
| const cb = dt => { | |
| main.update(dt); | |
| if (mouseButton() && mouseReleased()) { | |
| _mouseDown = undefined; | |
| _mouseHeld = false; | |
| _mouseReleased = false; | |
| } | |
| requestAnimationFrame(cb); | |
| }; | |
| requestAnimationFrame(cb); | |
| </script> | |
| <script> | |
| const canvasPosition = { x: 1200, y: 0 }; | |
| const demoCanvas = document.createElement('canvas'); | |
| document.body.appendChild(demoCanvas); | |
| demoCanvas.style = `position: absolute; top: ${canvasPosition.y}px; left: ${canvasPosition.x}px; background: white;` | |
| demoCanvas.width = 200; | |
| demoCanvas.height = 200; | |
| window.demoCtx = demoCanvas.getContext('2d'); | |
| function inTriangleDetection(mX, mY) { | |
| demoCtx.beginPath(); | |
| if (is2DPointInTriangle(mX, mY, 40, 40, 200, 100, 300, 20)) { | |
| demoCtx.fillStyle = 'rgba(0, 0, 0, 0.2)'; | |
| } else { | |
| demoCtx.fillStyle = 'rgba(100, 0, 0, 1)'; | |
| } | |
| demoCtx.moveTo(40, 40); | |
| demoCtx.lineTo(200, 100); | |
| demoCtx.lineTo(300, 20); | |
| demoCtx.fill(); | |
| demoCtx.stroke(); | |
| } | |
| function angleTest(mX, mY) { | |
| const px = 200; | |
| const py = 100; | |
| const distance = 100; | |
| const ang = Math.atan2(mX - px, mY - py); | |
| const vec = {x: Math.cos(ang) * distance, y: Math.sin(ang) * distance}; | |
| demoCtx.fillText(`ang: ${ang.toFixed(3)}`, 10, 10); | |
| demoCtx.fillText(`deg: ${radiansToDegrees(ang).toFixed(3)}`, 10, 30); | |
| demoCtx.beginPath(); | |
| demoCtx.moveTo(px, py); | |
| demoCtx.lineTo(px + vec.x, py + vec.y); | |
| demoCtx.stroke(); | |
| } | |
| function setPixel(buffer, x, y, val) { | |
| if (val === undefined) return; | |
| if (val < 0) val = 0; | |
| if (val > 255) val = 255; | |
| const offset = 4 * (Math.floor(x) + Math.floor(y) * buffer.width); | |
| buffer.data[offset] = val; | |
| buffer.data[offset + 1] = val; | |
| buffer.data[offset + 2] = val; | |
| buffer.data[offset + 3] = 255; | |
| } | |
| function bitmapTest(mX, mY) { | |
| demoCtx.clearRect(0, 0, demoCanvas.width, demoCanvas.height); | |
| if (!window.lm) return; | |
| const worldWidth = 20; | |
| const worldHeight = 20; | |
| const bm = new Bitmap(worldWidth, worldHeight, 1); | |
| for (let y = 0; y < worldHeight; y++) { | |
| bm.set(y, y, 255); | |
| } | |
| const buffer = demoCtx.createImageData(demoCanvas.width, demoCanvas.height); | |
| for (let y = 0; y < buffer.height; y++) { | |
| for (let x = 0; x < buffer.width; x++) { | |
| // setPixel(buffer, bm.width, x, y, bm.getInPercents(x / bm.width, y / bm.height)); | |
| const pointExist = window.lm.getInPercents(x / buffer.width , y / buffer.height); | |
| setPixel(buffer, x, y, pointExist); | |
| } | |
| } | |
| demoCtx.putImageData(buffer, 0, 0); | |
| } | |
| // return; | |
| const cb2 = () => { | |
| const mX = mouseX() - canvasPosition.x; | |
| const mY = mouseY() - canvasPosition.y; | |
| demoCtx.clearRect(0, 0, demoCanvas.width, demoCanvas.height); | |
| // inTriangleDetection(mX, mY); | |
| // angleTest(mX, mY); | |
| bitmapTest(mX, mY); | |
| if (mouseButton() && mouseReleased()) { | |
| _mouseDown = undefined; | |
| _mouseHeld = false; | |
| _mouseReleased = false; | |
| } | |
| requestAnimationFrame(cb2); | |
| }; | |
| requestAnimationFrame(cb2); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment