Skip to content

Commit 59d8a5f

Browse files
committed
Allow directional lights to automatically follow shadows
1 parent cafb63d commit 59d8a5f

File tree

4 files changed

+76
-2
lines changed

4 files changed

+76
-2
lines changed

docs/components/light.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ creating a child entity it targets. For example, pointing down its -Z axis:
9191
</a-light>
9292
```
9393

94-
Directional lights are the most efficient type for adding realtime shadows to a scene.
94+
Directional lights are the most efficient type for adding realtime shadows to a scene. You can use shadows like so:
95+
96+
```html
97+
<a-light type="directional" light="castShadow:true;" position="1 1 1" intensity="0.5" auto-shadow-cam="#objects"></a-light>
98+
```
99+
100+
The `auto-shadow-cam` configuration maps to `light.shadowCameraAutoTarget` which tells the light to automatically update the shadow camera to be the minimum size and position to encompass the target elements.
95101

96102
### Hemisphere
97103

examples/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ <h2>Examples</h2>
148148
<li><a href="showcase/spheres-and-fog/">Spheres and Fog</a></li>
149149
<li><a href="showcase/wikipedia/">Wikipedia</a></li>
150150
<li><a href="boilerplate/hello-world/">Hello World</a></li>
151+
<li><a href="boilerplate/ar-hello-world/">AR Hello World</a></li>
151152
<li><a href="boilerplate/panorama/">360&deg; Image</a></li>
152153
<li><a href="boilerplate/360-video/">360&deg; Video</a></li>
153154
<li><a href="boilerplate/3d-model/">3D Model (glTF)</a></li>

src/components/light.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,23 @@ var CubeLoader = new THREE.CubeTextureLoader();
1111

1212
var probeCache = {};
1313

14+
function distanceOfPointFromPlane (positionOnPlane, planeNormal, p1) {
15+
// the d value in the plane equation a*x + b*y + c*z=d
16+
var d = planeNormal.dot(positionOnPlane);
17+
18+
// distance of point from plane
19+
return (d - planeNormal.dot(p1)) / planeNormal.length();
20+
}
21+
22+
function nearestPointInPlane (positionOnPlane, planeNormal, p1, out) {
23+
var t = distanceOfPointFromPlane(positionOnPlane, planeNormal, p1);
24+
// closest point on the plane
25+
out.copy(planeNormal);
26+
out.multiplyScalar(t);
27+
out.add(p1);
28+
return out;
29+
}
30+
1431
/**
1532
* Light component.
1633
*/
@@ -42,6 +59,7 @@ module.exports.Component = registerComponent('light', {
4259
shadowCameraBottom: {default: -5, if: {castShadow: true}},
4360
shadowCameraLeft: {default: -5, if: {castShadow: true}},
4461
shadowCameraVisible: {default: false, if: {castShadow: true}},
62+
shadowCameraAutoTarget: {default: '', if: {type: ['spot', 'directional']}},
4563
shadowMapHeight: {default: 512, if: {castShadow: true}},
4664
shadowMapWidth: {default: 512, if: {castShadow: true}},
4765
shadowRadius: {default: 1, if: {castShadow: true}}
@@ -141,11 +159,59 @@ module.exports.Component = registerComponent('light', {
141159
return;
142160
}
143161

162+
if (data.shadowCameraAutoTarget) {
163+
this.shadowCameraAutoTargetEls = Array.from(document.querySelectorAll(data.shadowCameraAutoTarget));
164+
}
165+
144166
// No light yet or light type has changed. Create and add light.
145167
this.setLight(this.data);
146168
this.updateShadow();
147169
},
148170

171+
tick: (function tickSetup () {
172+
var bbox = new THREE.Box3();
173+
var normal = new THREE.Vector3();
174+
var cameraWorldPosition = new THREE.Vector3();
175+
var tempMat = new THREE.Matrix4();
176+
var sphere = new THREE.Sphere();
177+
var tempVector = new THREE.Vector3();
178+
179+
return function tick () {
180+
if (
181+
this.data.type === 'directional' &&
182+
this.light.shadow &&
183+
this.light.shadow.camera instanceof THREE.OrthographicCamera &&
184+
this.shadowCameraAutoTargetEls.length
185+
) {
186+
var camera = this.light.shadow.camera;
187+
camera.getWorldDirection(normal);
188+
camera.getWorldPosition(cameraWorldPosition);
189+
tempMat.copy(camera.matrixWorld);
190+
tempMat.invert();
191+
192+
camera.near = 1;
193+
camera.left = 100000;
194+
camera.right = -100000;
195+
camera.top = -100000;
196+
camera.bottom = 100000;
197+
this.shadowCameraAutoTargetEls.forEach(function (el) {
198+
bbox.setFromObject(el.object3D);
199+
bbox.getBoundingSphere(sphere);
200+
var distanceToPlane = distanceOfPointFromPlane(cameraWorldPosition, normal, sphere.center);
201+
var pointOnCameraPlane = nearestPointInPlane(cameraWorldPosition, normal, sphere.center, tempVector);
202+
203+
var pointInXYPlane = pointOnCameraPlane.applyMatrix4(tempMat);
204+
camera.near = Math.min(-distanceToPlane - sphere.radius - 1, camera.near);
205+
camera.left = Math.min(-sphere.radius + pointInXYPlane.x, camera.left);
206+
camera.right = Math.max(sphere.radius + pointInXYPlane.x, camera.right);
207+
camera.top = Math.max(sphere.radius + pointInXYPlane.y, camera.top);
208+
camera.bottom = Math.min(-sphere.radius + pointInXYPlane.y, camera.bottom);
209+
});
210+
camera.updateProjectionMatrix();
211+
}
212+
};
213+
}()),
214+
149215
setLight: function (data) {
150216
var el = this.el;
151217
var newLight = this.getLight(data);

src/extras/primitives/primitives/a-light.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ registerPrimitive('a-light', {
1515
penumbra: 'light.penumbra',
1616
type: 'light.type',
1717
target: 'light.target',
18-
envmap: 'light.envMap'
18+
envmap: 'light.envMap',
19+
'auto-shadow-cam': 'light.shadowCameraAutoTarget'
1920
}
2021
});

0 commit comments

Comments
 (0)