Private
Public Access
219 lines
8.3 KiB
JavaScript
Executable File
219 lines
8.3 KiB
JavaScript
Executable File
(function (root, factory) {
|
|
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
var exports_obj = factory();
|
|
for (var key in exports_obj) {
|
|
if (exports_obj.hasOwnProperty(key)) {
|
|
exports[key] = exports_obj[key];
|
|
}
|
|
}
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
define([], factory);
|
|
} else {
|
|
var exports_obj = factory();
|
|
for (var key in exports_obj) {
|
|
if (exports_obj.hasOwnProperty(key)) {
|
|
root[key] = exports_obj[key];
|
|
}
|
|
}
|
|
}
|
|
}(typeof self !== 'undefined' ? self : this, function () {
|
|
|
|
function vis_ray(ox, oy, angle, maxDist = 1000) {
|
|
var dx = Math.cos(angle), dy = Math.sin(angle);
|
|
return({ ox, oy, dx, dy, ex: ox + dx * maxDist, ey: oy + dy * maxDist, dist: maxDist });
|
|
}
|
|
|
|
function vis_intersect_rect(ray, rx, ry, rw, rh) {
|
|
var tmin = (rx - ray.ox) / ray.dx, tmax = (rx + rw - ray.ox) / ray.dx;
|
|
if(tmin > tmax) [tmin, tmax] = [tmax, tmin];
|
|
|
|
var tymin = (ry - ray.oy) / ray.dy, tymax = (ry + rh - ray.oy) / ray.dy;
|
|
if(tymin > tymax) [tymin, tymax] = [tymax, tymin];
|
|
|
|
if(tmin > tymax || tymin > tmax) return false;
|
|
|
|
var t = Math.max(tmin, tymin);
|
|
if(t < 0 || t > ray.dist) return false;
|
|
|
|
return({
|
|
x: ray.ox + ray.dx * t, y: ray.oy + ray.dy * t, dist: t,
|
|
side: tmin > tymin ? (ray.dx > 0 ? 'left' : 'right') : (ray.dy > 0 ? 'top' : 'bottom')
|
|
});
|
|
}
|
|
|
|
function vis_intersect_circle(ray, cx, cy, r) {
|
|
var fx = ray.ox - cx, fy = ray.oy - cy;
|
|
var a = ray.dx * ray.dx + ray.dy * ray.dy;
|
|
var b = 2 * (fx * ray.dx + fy * ray.dy);
|
|
var c = fx * fx + fy * fy - r * r;
|
|
|
|
var discriminant = b * b - 4 * a * c;
|
|
if(discriminant < 0) return false;
|
|
|
|
var sqrt_disc = Math.sqrt(discriminant);
|
|
var t = ((-b - sqrt_disc) / (2 * a)) >= 0 ? (-b - sqrt_disc) / (2 * a) : (-b + sqrt_disc) / (2 * a);
|
|
if(t < 0 || t > ray.dist) return false;
|
|
|
|
return({ x: ray.ox + ray.dx * t, y: ray.oy + ray.dy * t, dist: t, side: 'surface' });
|
|
}
|
|
|
|
function vis_cast_ray(ox, oy, angle, blockers, maxDist = 1000) {
|
|
var ray = vis_ray(ox, oy, angle, maxDist);
|
|
var closest = false, minDist = maxDist;
|
|
|
|
blockers.forEach(b => {
|
|
var hit = b.type == 'rect' ? vis_intersect_rect(ray, b.x, b.y, b.w, b.h) :
|
|
b.type == 'circle' ? vis_intersect_circle(ray, b.x, b.y, b.r) : false;
|
|
if(hit && hit.dist < minDist) { closest = hit; minDist = hit.dist; }
|
|
});
|
|
|
|
return closest ? { x: closest.x, y: closest.y, dist: closest.dist, blocked: true, side: closest.side } :
|
|
{ x: ray.ex, y: ray.ey, dist: maxDist, blocked: false };
|
|
}
|
|
|
|
function vis_radial(ox, oy, rayCount = 360, blockers = [], maxDist = 1000) {
|
|
var rays = [], step = (Math.PI * 2) / rayCount;
|
|
for(var i = 0; i < rayCount; i++) {
|
|
var hit = vis_cast_ray(ox, oy, i * step, blockers, maxDist);
|
|
rays.push({ angle: i * step, x: hit.x, y: hit.y, dist: hit.dist, blocked: hit.blocked });
|
|
}
|
|
return rays;
|
|
}
|
|
|
|
function vis_radial_adaptive(ox, oy, rayCount = 360, blockers = [], maxDist = 1000, refinements = 2) {
|
|
var rays = vis_radial(ox, oy, rayCount, blockers, maxDist);
|
|
|
|
for(var r = 0; r < refinements; r++) {
|
|
var newRays = [];
|
|
for(var i = 0; i < rays.length; i++) {
|
|
var curr = rays[i], next = rays[(i + 1) % rays.length];
|
|
newRays.push(curr);
|
|
|
|
if(curr.blocked != next.blocked || Math.abs(curr.dist - next.dist) > maxDist * 0.1) {
|
|
var midAngle = curr.angle + (next.angle - curr.angle) * 0.5;
|
|
if(next.angle < curr.angle) midAngle += Math.PI;
|
|
|
|
var midHit = vis_cast_ray(ox, oy, midAngle, blockers, maxDist);
|
|
newRays.push({ angle: midAngle, x: midHit.x, y: midHit.y, dist: midHit.dist, blocked: midHit.blocked });
|
|
}
|
|
}
|
|
rays = newRays.sort((a, b) => a.angle - b.angle);
|
|
}
|
|
return rays;
|
|
}
|
|
|
|
function vis_polygon(rays) { return rays.map(ray => [ray.x, ray.y]); }
|
|
|
|
function vis_shadow_polygon(blocker, lightX, lightY, maxDist = 1000) {
|
|
if(blocker.type == 'rect') {
|
|
var corners = [[blocker.x, blocker.y], [blocker.x + blocker.w, blocker.y],
|
|
[blocker.x + blocker.w, blocker.y + blocker.h], [blocker.x, blocker.y + blocker.h]];
|
|
var shadow = [];
|
|
corners.forEach(corner => {
|
|
var dx = corner[0] - lightX, dy = corner[1] - lightY, len = Math.sqrt(dx*dx + dy*dy);
|
|
if(len > 0) {
|
|
shadow.push(corner, [corner[0] + dx/len * maxDist, corner[1] + dy/len * maxDist]);
|
|
}
|
|
});
|
|
return shadow;
|
|
} else if(blocker.type == 'circle') {
|
|
var dx = blocker.x - lightX, dy = blocker.y - lightY, dist = Math.sqrt(dx*dx + dy*dy);
|
|
if(dist <= blocker.r) return [];
|
|
|
|
var angle = Math.atan2(dy, dx), tangentAngle = Math.asin(blocker.r / dist);
|
|
var angle1 = angle - tangentAngle, angle2 = angle + tangentAngle;
|
|
|
|
var t1x = blocker.x + Math.cos(angle1 + Math.PI/2) * blocker.r;
|
|
var t1y = blocker.y + Math.sin(angle1 + Math.PI/2) * blocker.r;
|
|
var t2x = blocker.x + Math.cos(angle2 - Math.PI/2) * blocker.r;
|
|
var t2y = blocker.y + Math.sin(angle2 - Math.PI/2) * blocker.r;
|
|
|
|
return [[t1x, t1y], [t1x + Math.cos(angle1) * maxDist, t1y + Math.sin(angle1) * maxDist],
|
|
[t2x + Math.cos(angle2) * maxDist, t2y + Math.sin(angle2) * maxDist], [t2x, t2y]];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function vis_create_mask(rays, ox, oy, blockers, maxDist) {
|
|
return { light: vis_polygon(rays), shadows: blockers.map(b => vis_shadow_polygon(b, ox, oy, maxDist)), rays };
|
|
}
|
|
|
|
function vis_light_mask(ox, oy, rayCount, blockers, maxDist) {
|
|
return vis_create_mask(vis_radial(ox, oy, rayCount, blockers, maxDist), ox, oy, blockers, maxDist);
|
|
}
|
|
|
|
function vis_light_mask_adaptive(ox, oy, rayCount, blockers, maxDist, refinements = 2) {
|
|
return vis_create_mask(vis_radial_adaptive(ox, oy, rayCount, blockers, maxDist, refinements), ox, oy, blockers, maxDist);
|
|
}
|
|
|
|
function vis_corner_adaptive(ox, oy, rayCount, blockers, maxDist, cornerSamples = 3) {
|
|
var allRays = vis_radial(ox, oy, rayCount, blockers, maxDist).slice();
|
|
|
|
blockers.forEach(b => {
|
|
if(b.type == 'rect') {
|
|
[[b.x, b.y], [b.x + b.w, b.y], [b.x + b.w, b.y + b.h], [b.x, b.y + b.h]].forEach(corner => {
|
|
var cornerAngle = Math.atan2(corner[1] - oy, corner[0] - ox);
|
|
for(var s = -cornerSamples; s <= cornerSamples; s++) {
|
|
var hit = vis_cast_ray(ox, oy, cornerAngle + s * 0.02, blockers, maxDist);
|
|
allRays.push({ angle: cornerAngle + s * 0.02, x: hit.x, y: hit.y, dist: hit.dist, blocked: hit.blocked });
|
|
}
|
|
});
|
|
} else if(b.type == 'circle') {
|
|
var dx = b.x - ox, dy = b.y - oy, dist = Math.sqrt(dx*dx + dy*dy);
|
|
if(dist > b.r) {
|
|
var baseAngle = Math.atan2(dy, dx), tangentAngle = Math.asin(b.r / dist);
|
|
[baseAngle - tangentAngle, baseAngle + tangentAngle].forEach(angle => {
|
|
for(var s = -cornerSamples; s <= cornerSamples; s++) {
|
|
var hit = vis_cast_ray(ox, oy, angle + s * 0.01, blockers, maxDist);
|
|
allRays.push({ angle: angle + s * 0.01, x: hit.x, y: hit.y, dist: hit.dist, blocked: hit.blocked });
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
allRays.sort((a, b) => a.angle - b.angle);
|
|
var filteredRays = [], lastAngle = -999;
|
|
allRays.forEach(ray => { if(ray.angle - lastAngle > 0.01) { filteredRays.push(ray); lastAngle = ray.angle; } });
|
|
return filteredRays;
|
|
}
|
|
|
|
function vis_light_mask_corner_adaptive(ox, oy, rayCount, blockers, maxDist, cornerSamples = 3) {
|
|
return vis_create_mask(vis_corner_adaptive(ox, oy, rayCount, blockers, maxDist, cornerSamples), ox, oy, blockers, maxDist);
|
|
}
|
|
|
|
function vis_multi_light_mask(lights, rayCount, blockers, maxDist, adaptive = false) {
|
|
return lights.map(light => {
|
|
var result = adaptive ? vis_light_mask_adaptive(light.x, light.y, rayCount, blockers, maxDist, 2) :
|
|
vis_light_mask(light.x, light.y, rayCount, blockers, maxDist);
|
|
result.color = light.color || 0xFFFFAA;
|
|
result.lightSource = light;
|
|
return result;
|
|
});
|
|
}
|
|
|
|
function vis_distance_fade(dist, maxDist, fadeStart = 0.7) {
|
|
var fadePoint = maxDist * fadeStart;
|
|
return dist < fadePoint ? 1 : Math.max(0, 1 - (dist - fadePoint) / (maxDist - fadePoint));
|
|
}
|
|
|
|
return {
|
|
vis_ray: vis_ray,
|
|
vis_intersect_rect: vis_intersect_rect,
|
|
vis_intersect_circle: vis_intersect_circle,
|
|
vis_cast_ray: vis_cast_ray,
|
|
vis_radial: vis_radial,
|
|
vis_radial_adaptive: vis_radial_adaptive,
|
|
vis_polygon: vis_polygon,
|
|
vis_shadow_polygon: vis_shadow_polygon,
|
|
vis_create_mask: vis_create_mask,
|
|
vis_light_mask: vis_light_mask,
|
|
vis_light_mask_adaptive: vis_light_mask_adaptive,
|
|
vis_corner_adaptive: vis_corner_adaptive,
|
|
vis_light_mask_corner_adaptive: vis_light_mask_corner_adaptive,
|
|
vis_multi_light_mask: vis_multi_light_mask,
|
|
vis_distance_fade: vis_distance_fade
|
|
};
|
|
|
|
}));
|