• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2012 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package com.jme3.shadow;
33 
34 import com.jme3.bounding.BoundingBox;
35 import com.jme3.bounding.BoundingVolume;
36 import com.jme3.math.Matrix4f;
37 import com.jme3.math.Transform;
38 import com.jme3.math.Vector2f;
39 import com.jme3.math.Vector3f;
40 import com.jme3.renderer.Camera;
41 import com.jme3.renderer.queue.GeometryList;
42 import com.jme3.scene.Geometry;
43 import static java.lang.Math.max;
44 import static java.lang.Math.min;
45 import java.util.ArrayList;
46 import java.util.List;
47 
48 /**
49  * Includes various useful shadow mapping functions.
50  *
51  * @see
52  * <ul>
53  * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>
54  * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>
55  * </ul>
56  * for more info.
57  */
58 public class ShadowUtil {
59 
60     /**
61      * Updates a points arrays with the frustum corners of the provided camera.
62      * @param viewCam
63      * @param points
64      */
updateFrustumPoints2(Camera viewCam, Vector3f[] points)65     public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
66         int w = viewCam.getWidth();
67         int h = viewCam.getHeight();
68         float n = viewCam.getFrustumNear();
69         float f = viewCam.getFrustumFar();
70 
71         points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));
72         points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));
73         points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));
74         points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));
75 
76         points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));
77         points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));
78         points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));
79         points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));
80     }
81 
82     /**
83      * Updates the points array to contain the frustum corners of the given
84      * camera. The nearOverride and farOverride variables can be used
85      * to override the camera's near/far values with own values.
86      *
87      * TODO: Reduce creation of new vectors
88      *
89      * @param viewCam
90      * @param nearOverride
91      * @param farOverride
92      */
updateFrustumPoints(Camera viewCam, float nearOverride, float farOverride, float scale, Vector3f[] points)93     public static void updateFrustumPoints(Camera viewCam,
94             float nearOverride,
95             float farOverride,
96             float scale,
97             Vector3f[] points) {
98 
99         Vector3f pos = viewCam.getLocation();
100         Vector3f dir = viewCam.getDirection();
101         Vector3f up = viewCam.getUp();
102 
103         float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear();
104         float near = nearOverride;
105         float far = farOverride;
106         float ftop = viewCam.getFrustumTop();
107         float fright = viewCam.getFrustumRight();
108         float ratio = fright / ftop;
109 
110         float near_height;
111         float near_width;
112         float far_height;
113         float far_width;
114 
115         if (viewCam.isParallelProjection()) {
116             near_height = ftop;
117             near_width = near_height * ratio;
118             far_height = ftop;
119             far_width = far_height * ratio;
120         } else {
121             near_height = depthHeightRatio * near;
122             near_width = near_height * ratio;
123             far_height = depthHeightRatio * far;
124             far_width = far_height * ratio;
125         }
126 
127         Vector3f right = dir.cross(up).normalizeLocal();
128 
129         Vector3f temp = new Vector3f();
130         temp.set(dir).multLocal(far).addLocal(pos);
131         Vector3f farCenter = temp.clone();
132         temp.set(dir).multLocal(near).addLocal(pos);
133         Vector3f nearCenter = temp.clone();
134 
135         Vector3f nearUp = temp.set(up).multLocal(near_height).clone();
136         Vector3f farUp = temp.set(up).multLocal(far_height).clone();
137         Vector3f nearRight = temp.set(right).multLocal(near_width).clone();
138         Vector3f farRight = temp.set(right).multLocal(far_width).clone();
139 
140         points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight);
141         points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight);
142         points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight);
143         points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight);
144 
145         points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight);
146         points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight);
147         points[6].set(farCenter).addLocal(farUp).addLocal(farRight);
148         points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight);
149 
150         if (scale != 1.0f) {
151             // find center of frustum
152             Vector3f center = new Vector3f();
153             for (int i = 0; i < 8; i++) {
154                 center.addLocal(points[i]);
155             }
156             center.divideLocal(8f);
157 
158             Vector3f cDir = new Vector3f();
159             for (int i = 0; i < 8; i++) {
160                 cDir.set(points[i]).subtractLocal(center);
161                 cDir.multLocal(scale - 1.0f);
162                 points[i].addLocal(cDir);
163             }
164         }
165     }
166 
167     /**
168      * Compute bounds of a geomList
169      * @param list
170      * @param transform
171      * @return
172      */
computeUnionBound(GeometryList list, Transform transform)173     public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {
174         BoundingBox bbox = new BoundingBox();
175         for (int i = 0; i < list.size(); i++) {
176             BoundingVolume vol = list.get(i).getWorldBound();
177             BoundingVolume newVol = vol.transform(transform);
178             //Nehon : prevent NaN and infinity values to screw the final bounding box
179             if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) {
180                 bbox.mergeLocal(newVol);
181             }
182         }
183         return bbox;
184     }
185 
186     /**
187      * Compute bounds of a geomList
188      * @param list
189      * @param mat
190      * @return
191      */
computeUnionBound(GeometryList list, Matrix4f mat)192     public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {
193         BoundingBox bbox = new BoundingBox();
194         BoundingVolume store = null;
195         for (int i = 0; i < list.size(); i++) {
196             BoundingVolume vol = list.get(i).getWorldBound();
197             store = vol.clone().transform(mat, null);
198             //Nehon : prevent NaN and infinity values to screw the final bounding box
199             if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) {
200                 bbox.mergeLocal(store);
201             }
202         }
203         return bbox;
204     }
205 
206     /**
207      * Computes the bounds of multiple bounding volumes
208      * @param bv
209      * @return
210      */
computeUnionBound(List<BoundingVolume> bv)211     public static BoundingBox computeUnionBound(List<BoundingVolume> bv) {
212         BoundingBox bbox = new BoundingBox();
213         for (int i = 0; i < bv.size(); i++) {
214             BoundingVolume vol = bv.get(i);
215             bbox.mergeLocal(vol);
216         }
217         return bbox;
218     }
219 
220     /**
221      * Compute bounds from an array of points
222      * @param pts
223      * @param transform
224      * @return
225      */
computeBoundForPoints(Vector3f[] pts, Transform transform)226     public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {
227         Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
228         Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
229         Vector3f temp = new Vector3f();
230         for (int i = 0; i < pts.length; i++) {
231             transform.transformVector(pts[i], temp);
232 
233             min.minLocal(temp);
234             max.maxLocal(temp);
235         }
236         Vector3f center = min.add(max).multLocal(0.5f);
237         Vector3f extent = max.subtract(min).multLocal(0.5f);
238         return new BoundingBox(center, extent.x, extent.y, extent.z);
239     }
240 
241     /**
242      * Compute bounds from an array of points
243      * @param pts
244      * @param mat
245      * @return
246      */
computeBoundForPoints(Vector3f[] pts, Matrix4f mat)247     public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {
248         Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
249         Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
250         Vector3f temp = new Vector3f();
251 
252         for (int i = 0; i < pts.length; i++) {
253             float w = mat.multProj(pts[i], temp);
254 
255             temp.x /= w;
256             temp.y /= w;
257             // Why was this commented out?
258             temp.z /= w;
259 
260             min.minLocal(temp);
261             max.maxLocal(temp);
262         }
263 
264         Vector3f center = min.add(max).multLocal(0.5f);
265         Vector3f extent = max.subtract(min).multLocal(0.5f);
266         //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned
267         return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);
268     }
269 
270     /**
271      * Updates the shadow camera to properly contain the given
272      * points (which contain the eye camera frustum corners)
273      *
274      * @param shadowCam
275      * @param points
276      */
updateShadowCamera(Camera shadowCam, Vector3f[] points)277     public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {
278         boolean ortho = shadowCam.isParallelProjection();
279         shadowCam.setProjectionMatrix(null);
280 
281         if (ortho) {
282             shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
283         } else {
284             shadowCam.setFrustumPerspective(45, 1, 1, 150);
285         }
286 
287         Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
288         Matrix4f projMatrix = shadowCam.getProjectionMatrix();
289 
290         BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
291 
292         Vector3f splitMin = splitBB.getMin(null);
293         Vector3f splitMax = splitBB.getMax(null);
294 
295 //        splitMin.z = 0;
296 
297         // Create the crop matrix.
298         float scaleX, scaleY, scaleZ;
299         float offsetX, offsetY, offsetZ;
300 
301         scaleX = 2.0f / (splitMax.x - splitMin.x);
302         scaleY = 2.0f / (splitMax.y - splitMin.y);
303         offsetX = -0.5f * (splitMax.x + splitMin.x) * scaleX;
304         offsetY = -0.5f * (splitMax.y + splitMin.y) * scaleY;
305         scaleZ = 1.0f / (splitMax.z - splitMin.z);
306         offsetZ = -splitMin.z * scaleZ;
307 
308         Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
309                 0f, scaleY, 0f, offsetY,
310                 0f, 0f, scaleZ, offsetZ,
311                 0f, 0f, 0f, 1f);
312 
313 
314         Matrix4f result = new Matrix4f();
315         result.set(cropMatrix);
316         result.multLocal(projMatrix);
317 
318         shadowCam.setProjectionMatrix(result);
319     }
320 
321     /**
322      * Updates the shadow camera to properly contain the given
323      * points (which contain the eye camera frustum corners) and the
324      * shadow occluder objects.
325      *
326      * @param occluders
327      * @param receivers
328      * @param shadowCam
329      * @param points
330      */
updateShadowCamera(GeometryList occluders, GeometryList receivers, Camera shadowCam, Vector3f[] points)331     public static void updateShadowCamera(GeometryList occluders,
332             GeometryList receivers,
333             Camera shadowCam,
334             Vector3f[] points) {
335         updateShadowCamera(occluders, receivers, shadowCam, points, null);
336     }
337 
338     /**
339      * Updates the shadow camera to properly contain the given
340      * points (which contain the eye camera frustum corners) and the
341      * shadow occluder objects.
342      *
343      * @param occluders
344      * @param shadowCam
345      * @param points
346      */
updateShadowCamera(GeometryList occluders, GeometryList receivers, Camera shadowCam, Vector3f[] points, GeometryList splitOccluders)347     public static void updateShadowCamera(GeometryList occluders,
348             GeometryList receivers,
349             Camera shadowCam,
350             Vector3f[] points,
351             GeometryList splitOccluders) {
352 
353         boolean ortho = shadowCam.isParallelProjection();
354 
355         shadowCam.setProjectionMatrix(null);
356 
357         if (ortho) {
358             shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
359         } else {
360             shadowCam.setFrustumPerspective(45, 1, 1, 150);
361         }
362 
363         // create transform to rotate points to viewspace
364         Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
365 
366         BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
367 
368         ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>();
369         for (int i = 0; i < receivers.size(); i++) {
370             // convert bounding box to light's viewproj space
371             Geometry receiver = receivers.get(i);
372             BoundingVolume bv = receiver.getWorldBound();
373             BoundingVolume recvBox = bv.transform(viewProjMatrix, null);
374 
375             if (splitBB.intersects(recvBox)) {
376                 visRecvList.add(recvBox);
377             }
378         }
379 
380         ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>();
381         for (int i = 0; i < occluders.size(); i++) {
382             // convert bounding box to light's viewproj space
383             Geometry occluder = occluders.get(i);
384             BoundingVolume bv = occluder.getWorldBound();
385             BoundingVolume occBox = bv.transform(viewProjMatrix, null);
386 
387             boolean intersects = splitBB.intersects(occBox);
388             if (!intersects && occBox instanceof BoundingBox) {
389                 BoundingBox occBB = (BoundingBox) occBox;
390                 //Kirill 01/10/2011
391                 // Extend the occluder further into the frustum
392                 // This fixes shadow dissapearing issues when
393                 // the caster itself is not in the view camera
394                 // but its shadow is in the camera
395                 //      The number is in world units
396                 occBB.setZExtent(occBB.getZExtent() + 50);
397                 occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));
398                 if (splitBB.intersects(occBB)) {
399                     // To prevent extending the depth range too much
400                     // We return the bound to its former shape
401                     // Before adding it
402                     occBB.setZExtent(occBB.getZExtent() - 50);
403                     occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));
404                     visOccList.add(occBox);
405                     if (splitOccluders != null) {
406                         splitOccluders.add(occluder);
407                     }
408                 }
409             } else if (intersects) {
410                 visOccList.add(occBox);
411                 if (splitOccluders != null) {
412                     splitOccluders.add(occluder);
413                 }
414             }
415         }
416 
417         BoundingBox casterBB = computeUnionBound(visOccList);
418         BoundingBox receiverBB = computeUnionBound(visRecvList);
419 
420         //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows
421         if (visOccList.size() != visRecvList.size()) {
422             casterBB.setXExtent(casterBB.getXExtent() + 2.0f);
423             casterBB.setYExtent(casterBB.getYExtent() + 2.0f);
424             casterBB.setZExtent(casterBB.getZExtent() + 2.0f);
425         }
426 
427         Vector3f casterMin = casterBB.getMin(null);
428         Vector3f casterMax = casterBB.getMax(null);
429 
430         Vector3f receiverMin = receiverBB.getMin(null);
431         Vector3f receiverMax = receiverBB.getMax(null);
432 
433         Vector3f splitMin = splitBB.getMin(null);
434         Vector3f splitMax = splitBB.getMax(null);
435 
436         splitMin.z = 0;
437 
438         if (!ortho) {
439             shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
440         }
441 
442         Matrix4f projMatrix = shadowCam.getProjectionMatrix();
443 
444         Vector3f cropMin = new Vector3f();
445         Vector3f cropMax = new Vector3f();
446 
447         // IMPORTANT: Special handling for Z values
448         cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x);
449         cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x);
450 
451         cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y);
452         cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y);
453 
454         cropMin.z = min(casterMin.z, splitMin.z);
455         cropMax.z = min(receiverMax.z, splitMax.z);
456 
457 
458         // Create the crop matrix.
459         float scaleX, scaleY, scaleZ;
460         float offsetX, offsetY, offsetZ;
461 
462         scaleX = (2.0f) / (cropMax.x - cropMin.x);
463         scaleY = (2.0f) / (cropMax.y - cropMin.y);
464 
465         offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;
466         offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;
467 
468         scaleZ = 1.0f / (cropMax.z - cropMin.z);
469         offsetZ = -cropMin.z * scaleZ;
470 
471 
472 
473         Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
474                 0f, scaleY, 0f, offsetY,
475                 0f, 0f, scaleZ, offsetZ,
476                 0f, 0f, 0f, 1f);
477 
478 
479         Matrix4f result = new Matrix4f();
480         result.set(cropMatrix);
481         result.multLocal(projMatrix);
482 
483         shadowCam.setProjectionMatrix(result);
484 
485     }
486 }
487