Skip to content

Commit

Permalink
Merge branch 'release/0.5.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
caewok committed Aug 4, 2023
2 parents e74c1a6 + 23eb1db commit 12f46f6
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 321 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 0.5.6
Add back WebGL as choice of default algorithm for new scenes. Closes issue #66.
Fix error when moving tokens with Levels module enabled. Closes issues #64 and #61.
Set the elevation in the toolbar to the nearest elevation step.

# 0.5.5
GM can set a "light size" in the ambient light configuration. This controls the amount of penumbra in the shadow. Lights are modeled physically as 3d spheres, with penumbra appearing for the left and right edges of walls and top/bottom edges of limited height walls when the light source is above the wall.

Expand Down
98 changes: 85 additions & 13 deletions scripts/DirectionalLightSource.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/* globals
AmbientLight,
canvas,
CONST,
LightSource,
PIXI
PIXI,
PreciseText
*/
"use strict";

import { MODULE_ID, FLAGS } from "./const.js";
import { Plane } from "./geometry/3d/Plane.js";
import { DirectionalSourceShadowWallGeometry } from "./glsl/SourceShadowWallGeometry.js";
import { DirectionalShadowWallShader, ShadowMesh } from "./glsl/ShadowWallShader.js";
import { ShadowVisionMaskTokenLOSShader } from "./glsl/ShadowVisionMaskShader.js";
Expand Down Expand Up @@ -66,6 +68,8 @@ export class DirectionalLightSource extends LightSource {
return canvas.dimensions.rect.toPolygon();
}

get elevationE() { return Number.MAX_SAFE_INTEGER; }

/**
* Holds a grid delineating elevation angle breaks that can be displayed when hovering over or
* dragging the source.
Expand Down Expand Up @@ -475,18 +479,16 @@ export class DirectionalLightSource extends LightSource {

// Project a point out beyond the canvas to stand in for the light position.
const { azimuth, elevationAngle, solarAngle } = this;
const midCollision = directionalCollision(this, testPt, azimuth, elevationAngle);

this.hasWallCollision(origin, testPt);
const midCollision = this.hasWallCollision(testPt, azimuth, elevationAngle);

/* Draw.point(origin, { color: Draw.COLORS.yellow }) */
if ( !solarAngle ) return Number(midCollision);

// Test the top/bottom/left/right points of the light for penumbra shadow.
const topCollision = directionalCollision(this, testPt, azimuth, elevationAngle + solarAngle);
const bottomCollision = directionalCollision(this, testPt, azimuth, elevationAngle - solarAngle);
const side0Collision = directionalCollision(this, testPt, azimuth + solarAngle, elevationAngle);
const side1Collision = directionalCollision(this, testPt, azimuth - solarAngle, elevationAngle);
const topCollision = this.hasWallCollision(testPt, azimuth, elevationAngle + solarAngle);
const bottomCollision = this.hasWallCollision(testPt, testPt, azimuth, elevationAngle - solarAngle);
const side0Collision = this.hasWallCollision(testPt, testPt, azimuth + solarAngle, elevationAngle);
const side1Collision = this.hasWallCollision(testPt, testPt, azimuth - solarAngle, elevationAngle);

// Shadows: side0/mid/side1 = 100%; side0/mid = 50%; mid/side1 = 50%; any one = 25%
const sideSum = side0Collision + side1Collision + midCollision;
Expand All @@ -509,12 +511,82 @@ export class DirectionalLightSource extends LightSource {

return heightShadowPercentage * sideShadowPercentage;
}
}

function directionalCollision(source, testPt, azimuth, elevationAngle) {
const dir = DirectionalLightSource.lightDirection(azimuth, elevationAngle);
const origin = testPt.add(dir.multiplyScalar(canvas.dimensions.maxR));
return source.hasWallCollision(origin, testPt);
/**
* Comparable to PointSourcePolygon.prototype._testWallInclusion
* Test for whether a given wall interacts with this source.
* Used to filter walls in the quadtree in _getWalls
* @param {Wall} wall
* @param {number} azimuth
* @param {number} elevationAngle
* @returns {boolean}
*/
_testWallInclusion(wall, azimuth, elevationAngle) {
// Ignore reverse proximity walls (b/c this is a near-infinite light source)
const type = this.constructor.sourceType;
if ( wall.document[type] === CONST.WALL_SENSE_TYPES.DISTANCE ) return false;

// Ignore walls that are non-blocking for this type.
if ( !wall.document[type] || wall.isOpen ) return false;

// Ignore collinear walls
// Create a fake source origin point to test orientation\
azimuth ??= this.azimuth;
elevationAngle ??= this.elevationAngle;
const dir = DirectionalLightSource.lightDirection(azimuth, elevationAngle);
const origin = PIXI.Point.fromObject(wall.B).add(dir.multiplyScalar(canvas.dimensions.maxR));
const side = wall.orientPoint(origin);
if ( !side ) return false;

// Ignore one-directional walls facing away from the origin.
if ( side === wall.document.dir ) return false;

// If wall is entirely below the canvas, do not keep.
const minCanvasE = canvas.elevation?.minElevation ?? canvas.scene.getFlag(MODULE_ID, "elevationmin") ?? 0;
if ( wall.topZ <= minCanvasE ) return false;

return true;
}

_getWalls(bounds, azimuth, elevationAngle) {
bounds ??= this.bounds;
const collisionTest = o => this._testWallInclusion(o.t, azimuth, elevationAngle);
return canvas.walls.quadtree.getObjects(bounds, { collisionTest });
}

/**
* Use a fake origin to test for collision.
* Allow azimuth and elevationAngle to be adjusted.
*/
hasWallCollision(testPt, azimuth, elevationAngle) {
azimuth ??= this.azimuth;
elevationAngle ??= this.elevationAngle;
const dir = DirectionalLightSource.lightDirection(azimuth, elevationAngle);
const origin = testPt.add(dir.multiplyScalar(canvas.dimensions.maxR));

const xMinMax = Math.minMax(origin.x, testPt.x);
const yMinMax = Math.minMax(origin.y, testPt.y);
const lineBounds = new PIXI.Rectangle(
xMinMax.min,
yMinMax.min,
xMinMax.max - xMinMax.min,
yMinMax.max - yMinMax.min);
const walls = this._getWalls(lineBounds, azimuth, elevationAngle);
if ( !walls.size ) return false;

// Test the intersection of the ray with each wall.
const rayDir = testPt.subtract(origin);
return walls.some(w => {
if ( !isFinite(w.topE) && !isFinite(w.bottomE) ) return true;

const wallPts = Point3d.fromWall(w);
const v0 = wallPts.A.top;
const v1 = wallPts.A.bottom;
const v2 = wallPts.B.bottom;
const v3 = wallPts.B.top;
return Plane.rayIntersectionQuad3dLD(origin, rayDir, v0, v1, v2, v3); // Null or t value
});
}
}

/**
Expand Down
4 changes: 2 additions & 2 deletions scripts/ElevationLayerToolBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export class ElevationLayerToolBar extends Application {
constructor() {
super(...arguments);

// As the elevation default to 0, it makes sense to start at 1 unit of elevation.
// Start the elevation at the current step size or 1 grid unit
this.elevation = canvas.elevation;
this.currentElevation = canvas.scene.dimensions.distance;
this.currentElevation = canvas.elevation.elevationStep || canvas.scene.dimensions.distance;
}

get elevationStep() {
Expand Down
Loading

0 comments on commit 12f46f6

Please sign in to comment.