aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/shadow/ShadowUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/shadow/ShadowUtil.java')
-rw-r--r--engine/src/core/com/jme3/shadow/ShadowUtil.java486
1 files changed, 486 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/shadow/ShadowUtil.java b/engine/src/core/com/jme3/shadow/ShadowUtil.java
new file mode 100644
index 0000000..5832bda
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowUtil.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shadow;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.scene.Geometry;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Includes various useful shadow mapping functions.
+ *
+ * @see
+ * <ul>
+ * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>
+ * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>
+ * </ul>
+ * for more info.
+ */
+public class ShadowUtil {
+
+ /**
+ * Updates a points arrays with the frustum corners of the provided camera.
+ * @param viewCam
+ * @param points
+ */
+ public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
+ int w = viewCam.getWidth();
+ int h = viewCam.getHeight();
+ float n = viewCam.getFrustumNear();
+ float f = viewCam.getFrustumFar();
+
+ points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));
+ points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));
+ points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));
+ points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));
+
+ points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));
+ points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));
+ points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));
+ points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));
+ }
+
+ /**
+ * Updates the points array to contain the frustum corners of the given
+ * camera. The nearOverride and farOverride variables can be used
+ * to override the camera's near/far values with own values.
+ *
+ * TODO: Reduce creation of new vectors
+ *
+ * @param viewCam
+ * @param nearOverride
+ * @param farOverride
+ */
+ public static void updateFrustumPoints(Camera viewCam,
+ float nearOverride,
+ float farOverride,
+ float scale,
+ Vector3f[] points) {
+
+ Vector3f pos = viewCam.getLocation();
+ Vector3f dir = viewCam.getDirection();
+ Vector3f up = viewCam.getUp();
+
+ float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear();
+ float near = nearOverride;
+ float far = farOverride;
+ float ftop = viewCam.getFrustumTop();
+ float fright = viewCam.getFrustumRight();
+ float ratio = fright / ftop;
+
+ float near_height;
+ float near_width;
+ float far_height;
+ float far_width;
+
+ if (viewCam.isParallelProjection()) {
+ near_height = ftop;
+ near_width = near_height * ratio;
+ far_height = ftop;
+ far_width = far_height * ratio;
+ } else {
+ near_height = depthHeightRatio * near;
+ near_width = near_height * ratio;
+ far_height = depthHeightRatio * far;
+ far_width = far_height * ratio;
+ }
+
+ Vector3f right = dir.cross(up).normalizeLocal();
+
+ Vector3f temp = new Vector3f();
+ temp.set(dir).multLocal(far).addLocal(pos);
+ Vector3f farCenter = temp.clone();
+ temp.set(dir).multLocal(near).addLocal(pos);
+ Vector3f nearCenter = temp.clone();
+
+ Vector3f nearUp = temp.set(up).multLocal(near_height).clone();
+ Vector3f farUp = temp.set(up).multLocal(far_height).clone();
+ Vector3f nearRight = temp.set(right).multLocal(near_width).clone();
+ Vector3f farRight = temp.set(right).multLocal(far_width).clone();
+
+ points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight);
+ points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight);
+ points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight);
+ points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight);
+
+ points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight);
+ points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight);
+ points[6].set(farCenter).addLocal(farUp).addLocal(farRight);
+ points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight);
+
+ if (scale != 1.0f) {
+ // find center of frustum
+ Vector3f center = new Vector3f();
+ for (int i = 0; i < 8; i++) {
+ center.addLocal(points[i]);
+ }
+ center.divideLocal(8f);
+
+ Vector3f cDir = new Vector3f();
+ for (int i = 0; i < 8; i++) {
+ cDir.set(points[i]).subtractLocal(center);
+ cDir.multLocal(scale - 1.0f);
+ points[i].addLocal(cDir);
+ }
+ }
+ }
+
+ /**
+ * Compute bounds of a geomList
+ * @param list
+ * @param transform
+ * @return
+ */
+ public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {
+ BoundingBox bbox = new BoundingBox();
+ for (int i = 0; i < list.size(); i++) {
+ BoundingVolume vol = list.get(i).getWorldBound();
+ BoundingVolume newVol = vol.transform(transform);
+ //Nehon : prevent NaN and infinity values to screw the final bounding box
+ if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) {
+ bbox.mergeLocal(newVol);
+ }
+ }
+ return bbox;
+ }
+
+ /**
+ * Compute bounds of a geomList
+ * @param list
+ * @param mat
+ * @return
+ */
+ public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {
+ BoundingBox bbox = new BoundingBox();
+ BoundingVolume store = null;
+ for (int i = 0; i < list.size(); i++) {
+ BoundingVolume vol = list.get(i).getWorldBound();
+ store = vol.clone().transform(mat, null);
+ //Nehon : prevent NaN and infinity values to screw the final bounding box
+ if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) {
+ bbox.mergeLocal(store);
+ }
+ }
+ return bbox;
+ }
+
+ /**
+ * Computes the bounds of multiple bounding volumes
+ * @param bv
+ * @return
+ */
+ public static BoundingBox computeUnionBound(List<BoundingVolume> bv) {
+ BoundingBox bbox = new BoundingBox();
+ for (int i = 0; i < bv.size(); i++) {
+ BoundingVolume vol = bv.get(i);
+ bbox.mergeLocal(vol);
+ }
+ return bbox;
+ }
+
+ /**
+ * Compute bounds from an array of points
+ * @param pts
+ * @param transform
+ * @return
+ */
+ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {
+ Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+ Vector3f temp = new Vector3f();
+ for (int i = 0; i < pts.length; i++) {
+ transform.transformVector(pts[i], temp);
+
+ min.minLocal(temp);
+ max.maxLocal(temp);
+ }
+ Vector3f center = min.add(max).multLocal(0.5f);
+ Vector3f extent = max.subtract(min).multLocal(0.5f);
+ return new BoundingBox(center, extent.x, extent.y, extent.z);
+ }
+
+ /**
+ * Compute bounds from an array of points
+ * @param pts
+ * @param mat
+ * @return
+ */
+ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {
+ Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+ Vector3f temp = new Vector3f();
+
+ for (int i = 0; i < pts.length; i++) {
+ float w = mat.multProj(pts[i], temp);
+
+ temp.x /= w;
+ temp.y /= w;
+ // Why was this commented out?
+ temp.z /= w;
+
+ min.minLocal(temp);
+ max.maxLocal(temp);
+ }
+
+ Vector3f center = min.add(max).multLocal(0.5f);
+ Vector3f extent = max.subtract(min).multLocal(0.5f);
+ //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned
+ return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners)
+ *
+ * @param shadowCam
+ * @param points
+ */
+ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {
+ boolean ortho = shadowCam.isParallelProjection();
+ shadowCam.setProjectionMatrix(null);
+
+ if (ortho) {
+ shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ shadowCam.setFrustumPerspective(45, 1, 1, 150);
+ }
+
+ Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
+ Matrix4f projMatrix = shadowCam.getProjectionMatrix();
+
+ BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
+
+ Vector3f splitMin = splitBB.getMin(null);
+ Vector3f splitMax = splitBB.getMax(null);
+
+// splitMin.z = 0;
+
+ // Create the crop matrix.
+ float scaleX, scaleY, scaleZ;
+ float offsetX, offsetY, offsetZ;
+
+ scaleX = 2.0f / (splitMax.x - splitMin.x);
+ scaleY = 2.0f / (splitMax.y - splitMin.y);
+ offsetX = -0.5f * (splitMax.x + splitMin.x) * scaleX;
+ offsetY = -0.5f * (splitMax.y + splitMin.y) * scaleY;
+ scaleZ = 1.0f / (splitMax.z - splitMin.z);
+ offsetZ = -splitMin.z * scaleZ;
+
+ Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
+ 0f, scaleY, 0f, offsetY,
+ 0f, 0f, scaleZ, offsetZ,
+ 0f, 0f, 0f, 1f);
+
+
+ Matrix4f result = new Matrix4f();
+ result.set(cropMatrix);
+ result.multLocal(projMatrix);
+
+ shadowCam.setProjectionMatrix(result);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners) and the
+ * shadow occluder objects.
+ *
+ * @param occluders
+ * @param receivers
+ * @param shadowCam
+ * @param points
+ */
+ public static void updateShadowCamera(GeometryList occluders,
+ GeometryList receivers,
+ Camera shadowCam,
+ Vector3f[] points) {
+ updateShadowCamera(occluders, receivers, shadowCam, points, null);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners) and the
+ * shadow occluder objects.
+ *
+ * @param occluders
+ * @param shadowCam
+ * @param points
+ */
+ public static void updateShadowCamera(GeometryList occluders,
+ GeometryList receivers,
+ Camera shadowCam,
+ Vector3f[] points,
+ GeometryList splitOccluders) {
+
+ boolean ortho = shadowCam.isParallelProjection();
+
+ shadowCam.setProjectionMatrix(null);
+
+ if (ortho) {
+ shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ shadowCam.setFrustumPerspective(45, 1, 1, 150);
+ }
+
+ // create transform to rotate points to viewspace
+ Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
+
+ BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
+
+ ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>();
+ for (int i = 0; i < receivers.size(); i++) {
+ // convert bounding box to light's viewproj space
+ Geometry receiver = receivers.get(i);
+ BoundingVolume bv = receiver.getWorldBound();
+ BoundingVolume recvBox = bv.transform(viewProjMatrix, null);
+
+ if (splitBB.intersects(recvBox)) {
+ visRecvList.add(recvBox);
+ }
+ }
+
+ ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>();
+ for (int i = 0; i < occluders.size(); i++) {
+ // convert bounding box to light's viewproj space
+ Geometry occluder = occluders.get(i);
+ BoundingVolume bv = occluder.getWorldBound();
+ BoundingVolume occBox = bv.transform(viewProjMatrix, null);
+
+ boolean intersects = splitBB.intersects(occBox);
+ if (!intersects && occBox instanceof BoundingBox) {
+ BoundingBox occBB = (BoundingBox) occBox;
+ //Kirill 01/10/2011
+ // Extend the occluder further into the frustum
+ // This fixes shadow dissapearing issues when
+ // the caster itself is not in the view camera
+ // but its shadow is in the camera
+ // The number is in world units
+ occBB.setZExtent(occBB.getZExtent() + 50);
+ occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));
+ if (splitBB.intersects(occBB)) {
+ // To prevent extending the depth range too much
+ // We return the bound to its former shape
+ // Before adding it
+ occBB.setZExtent(occBB.getZExtent() - 50);
+ occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));
+ visOccList.add(occBox);
+ if (splitOccluders != null) {
+ splitOccluders.add(occluder);
+ }
+ }
+ } else if (intersects) {
+ visOccList.add(occBox);
+ if (splitOccluders != null) {
+ splitOccluders.add(occluder);
+ }
+ }
+ }
+
+ BoundingBox casterBB = computeUnionBound(visOccList);
+ BoundingBox receiverBB = computeUnionBound(visRecvList);
+
+ //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows
+ if (visOccList.size() != visRecvList.size()) {
+ casterBB.setXExtent(casterBB.getXExtent() + 2.0f);
+ casterBB.setYExtent(casterBB.getYExtent() + 2.0f);
+ casterBB.setZExtent(casterBB.getZExtent() + 2.0f);
+ }
+
+ Vector3f casterMin = casterBB.getMin(null);
+ Vector3f casterMax = casterBB.getMax(null);
+
+ Vector3f receiverMin = receiverBB.getMin(null);
+ Vector3f receiverMax = receiverBB.getMax(null);
+
+ Vector3f splitMin = splitBB.getMin(null);
+ Vector3f splitMax = splitBB.getMax(null);
+
+ splitMin.z = 0;
+
+ if (!ortho) {
+ shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
+ }
+
+ Matrix4f projMatrix = shadowCam.getProjectionMatrix();
+
+ Vector3f cropMin = new Vector3f();
+ Vector3f cropMax = new Vector3f();
+
+ // IMPORTANT: Special handling for Z values
+ cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x);
+ cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x);
+
+ cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y);
+ cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y);
+
+ cropMin.z = min(casterMin.z, splitMin.z);
+ cropMax.z = min(receiverMax.z, splitMax.z);
+
+
+ // Create the crop matrix.
+ float scaleX, scaleY, scaleZ;
+ float offsetX, offsetY, offsetZ;
+
+ scaleX = (2.0f) / (cropMax.x - cropMin.x);
+ scaleY = (2.0f) / (cropMax.y - cropMin.y);
+
+ offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;
+ offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;
+
+ scaleZ = 1.0f / (cropMax.z - cropMin.z);
+ offsetZ = -cropMin.z * scaleZ;
+
+
+
+ Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
+ 0f, scaleY, 0f, offsetY,
+ 0f, 0f, scaleZ, offsetZ,
+ 0f, 0f, 0f, 1f);
+
+
+ Matrix4f result = new Matrix4f();
+ result.set(cropMatrix);
+ result.multLocal(projMatrix);
+
+ shadowCam.setProjectionMatrix(result);
+
+ }
+}