diff --git a/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java b/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java index 388310a920..b1e49f9d65 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java +++ b/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,85 +71,83 @@ private ShadowUtil() { * Updates a points arrays with the frustum corners of the provided camera. * * @param viewCam the viewing Camera (not null, unaffected) - * @param points storage for the corner coordinates (not null, length≥8, - * modified) + * @param points storage for the corner coordinates (not null, length ≥8, modified) */ public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) { int w = viewCam.getWidth(); int h = viewCam.getHeight(); - points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 0)); - points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 0)); - points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 0)); - points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 0)); + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + + viewCam.getWorldCoordinates(tempVec2.set(0, 0), 0, points[0]); + viewCam.getWorldCoordinates(tempVec2.set(0, h), 0, points[1]); + viewCam.getWorldCoordinates(tempVec2.set(w, h), 0, points[2]); + viewCam.getWorldCoordinates(tempVec2.set(w, 0), 0, points[3]); + + viewCam.getWorldCoordinates(tempVec2.set(0, 0), 1, points[4]); + viewCam.getWorldCoordinates(tempVec2.set(0, h), 1, points[5]); + viewCam.getWorldCoordinates(tempVec2.set(w, h), 1, points[6]); + viewCam.getWorldCoordinates(tempVec2.set(w, 0), 1, points[7]); - points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 1)); - points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 1)); - points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 1)); - points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 1)); + vars.release(); } /** - * 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 + * Updates the provided array of {@code Vector3f} to contain the frustum corners + * of the given camera, with optional overrides for near/far distances and a scale factor. + * The array must have a length of at least 8. * * @param viewCam the viewing Camera (not null, unaffected) - * @param nearOverride distance to the near plane (in world units) - * @param farOverride distance to the far plane (in world units) - * @param scale scale factor - * @param points storage for the corner coordinates (not null, length≥8, - * modified) + * @param near distance to the near plane (in world units) + * @param far distance to the far plane (in world units) + * @param scale a factor to scale the frustum points around their center (1.0 for no scaling) + * @param points storage for the corner coordinates (not null, length ≥ 8, modified) */ - public static void updateFrustumPoints(Camera viewCam, - float nearOverride, - float farOverride, - float scale, - Vector3f[] points) { + public static void updateFrustumPoints(Camera viewCam, float near, float far, float scale, Vector3f[] points) { - Vector3f pos = viewCam.getLocation(); - Vector3f dir = viewCam.getDirection(); - Vector3f up = viewCam.getUp(); + TempVars vars = TempVars.get(); + + Vector3f camPos = viewCam.getLocation(); + Vector3f camDir = viewCam.getDirection(vars.vect1); + Vector3f camUp = viewCam.getUp(vars.vect2); + Vector3f camRight = vars.vect3.set(camDir).crossLocal(camUp).normalizeLocal(); float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear(); - float near = nearOverride; - float far = farOverride; - float ftop = viewCam.getFrustumTop(); + float ftop = viewCam.getFrustumTop(); float fright = viewCam.getFrustumRight(); - float ratio = fright / ftop; + float ratio = fright / ftop; - float near_height; - float near_width; - float far_height; - float far_width; + float nearHeight; + float nearWidth; + float farHeight; + float farWidth; if (viewCam.isParallelProjection()) { - near_height = ftop; - near_width = near_height * ratio; - far_height = ftop; - far_width = far_height * ratio; + nearHeight = ftop; + nearWidth = nearHeight * ratio; + farHeight = ftop; + farWidth = farHeight * ratio; } else { - near_height = depthHeightRatio * near; - near_width = near_height * ratio; - far_height = depthHeightRatio * far; - far_width = far_height * ratio; + nearHeight = depthHeightRatio * near; + nearWidth = nearHeight * ratio; + farHeight = depthHeightRatio * far; + farWidth = farHeight * ratio; } - Vector3f right = dir.cross(up).normalizeLocal(); + Vector3f nearCenter = vars.vect4; + Vector3f farCenter = vars.vect5; - 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(); + // Calculate near and far center points + nearCenter.set(camDir).multLocal(near).addLocal(camPos); + farCenter.set(camDir).multLocal(far).addLocal(camPos); - 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(); + Vector3f nearUp = vars.vect6.set(camUp).multLocal(nearHeight); + Vector3f farUp = vars.vect7.set(camUp).multLocal(farHeight); + Vector3f nearRight = vars.vect8.set(camRight).multLocal(nearWidth); + Vector3f farRight = vars.vect9.set(camRight).multLocal(farWidth); + // Populate frustum points points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight); points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight); points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight); @@ -168,133 +166,147 @@ public static void updateFrustumPoints(Camera viewCam, } center.divideLocal(8f); - Vector3f cDir = new Vector3f(); + Vector3f tempVec = vars.vect10; // Reusing tempVec for calculations for (int i = 0; i < 8; i++) { - cDir.set(points[i]).subtractLocal(center); - cDir.multLocal(scale - 1.0f); - points[i].addLocal(cDir); + tempVec.set(points[i]).subtractLocal(center); + tempVec.multLocal(scale - 1.0f); + points[i].addLocal(tempVec); } } + + vars.release(); } /** - * Compute bounds of a geomList + * Computes the union bounding box of all geometries in the given list, + * after transforming their world bounds by the provided {@link Transform}. * - * @param list a list of geometries (not null) - * @param transform a coordinate transform - * @return a new instance + * @param list a list of geometries (not null) + * @param transform a coordinate transform to apply to each geometry's world bound (not null, unaffected) + * @return a new {@link BoundingBox} representing the union of transformed bounds. */ public static BoundingBox computeUnionBound(GeometryList list, Transform transform) { BoundingBox bbox = new BoundingBox(); - TempVars tempVars = TempVars.get(); + TempVars vars = TempVars.get(); for (int i = 0; i < list.size(); i++) { BoundingVolume vol = list.get(i).getWorldBound(); - BoundingVolume newVol = vol.transform(transform, tempVars.bbox); + BoundingVolume newVol = vol.transform(transform, vars.bbox); //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); } } - tempVars.release(); + vars.release(); return bbox; } /** - * Compute bounds of a geomList + * Computes the union bounding box of all geometries in the given list, + * after transforming their world bounds by the provided {@link Matrix4f}. * * @param list a list of geometries (not null) - * @param mat a coordinate-transform matrix - * @return a new instance + * @param mat a coordinate-transform matrix to apply to each geometry's world bound (not null, unaffected) + * @return a new {@link BoundingBox} representing the union of transformed bounds. */ public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) { BoundingBox bbox = new BoundingBox(); - TempVars tempv = TempVars.get(); + TempVars vars = TempVars.get(); for (int i = 0; i < list.size(); i++) { BoundingVolume vol = list.get(i).getWorldBound(); - BoundingVolume store = vol.transform(mat, tempv.bbox); + BoundingVolume store = vol.transform(mat, vars.bbox); //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); } } - tempv.release(); + vars.release(); return bbox; } /** - * Computes the bounds of multiple bounding volumes + * Computes the union bounding box of a list of {@link BoundingVolume}s. * - * @param bv a list of bounding volumes (not null) - * @return a new instance + * @param bv a list of bounding volumes (not null, elements can be null) + * @return a new {@link BoundingBox} representing the union of the provided volumes. */ public static BoundingBox computeUnionBound(List bv) { BoundingBox bbox = new BoundingBox(); - for (int i = 0; i < bv.size(); i++) { - BoundingVolume vol = bv.get(i); + for (BoundingVolume vol : bv) { bbox.mergeLocal(vol); } return bbox; } /** - * Compute bounds from an array of points + * Computes a {@link BoundingBox} that encloses an array of 3D points, + * after transforming them by the provided {@link Transform}. * - * @param pts an array of location vectors (not null, unaffected) - * @param transform a coordinate transform - * @return a new instance + * @param pts an array of location vectors (not null, unaffected) + * @param transform a coordinate transform to apply to each point (not null, unaffected) + * @return a new {@link BoundingBox} enclosing the transformed points. */ 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); + TempVars vars = TempVars.get(); + Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY); + Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY); + Vector3f tempVec = vars.vect3; + + for (Vector3f pt : pts) { + transform.transformVector(pt, tempVec); + min.minLocal(tempVec); + max.maxLocal(tempVec); } - 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); + Vector3f center = vars.vect4.set(min).addLocal(max).multLocal(0.5f); + Vector3f extent = vars.vect5.set(max).subtractLocal(min).multLocal(0.5f); + + BoundingBox bbox = new BoundingBox(center, extent.x, extent.y, extent.z); + vars.release(); + return bbox; } /** - * Compute bounds from an array of points + * Computes a {@link BoundingBox} that encloses an array of 3D points, + * after transforming them by the provided {@link Matrix4f}. + *

+ * Note: An offset is added to the extent to help avoid banding artifacts + * when frustums are aligned. * * @param pts an array of location vectors (not null, unaffected) * @param mat a coordinate-transform matrix (not null, unaffected) - * @return a new BoundingBox + * @return a new {@link BoundingBox} enclosing the transformed points. */ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) { - Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); - Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); TempVars vars = TempVars.get(); - Vector3f temp = vars.vect1; - - 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 min = vars.vect1.set(Vector3f.POSITIVE_INFINITY); + Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY); + Vector3f tempVec = vars.vect3; + + for (Vector3f pt : pts) { + float w = mat.multProj(pt, tempVec); + tempVec.x /= w; + tempVec.y /= w; + tempVec.z /= w; // Z component correction + + min.minLocal(tempVec); + max.maxLocal(tempVec); } + Vector3f center = vars.vect4.set(min).addLocal(max).multLocal(0.5f); + Vector3f extent = vars.vect5.set(max).subtractLocal(min).multLocal(0.5f); + + //Nehon 08/18/2010 : Added an offset to the extent, to avoid banding artifacts when the frustums are aligned. + BoundingBox bbox = new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f); vars.release(); - 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 frustums are aligned. - return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f); + return bbox; } /** - * Updates the shadow camera to properly contain the given points (which - * contain the eye camera frustum corners) + * Updates the projection matrix of the shadow camera to properly contain + * the given points (which typically represent the eye camera frustum corners). + * This method is suitable for simple shadow camera setups where only the + * frustum points determine the shadow camera's projection. * * @param shadowCam the shadow camera (not null, modified) - * @param points an array of location vectors (not null, unaffected) + * @param points an array of location vectors representing the bounds to contain (not null, unaffected) */ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { boolean ortho = shadowCam.isParallelProjection(); @@ -316,9 +328,7 @@ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { Vector3f splitMin = splitBB.getMin(vars.vect1); Vector3f splitMax = splitBB.getMax(vars.vect2); -// splitMin.z = 0; - - // Create the crop matrix. + // Create the crop matrix based on the contained bounding box. float scaleX, scaleY, scaleZ; float offsetX, offsetY, offsetZ; @@ -335,13 +345,12 @@ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { 0f, 0f, scaleZ, offsetZ, 0f, 0f, 0f, 1f); - - Matrix4f result = new Matrix4f(); + Matrix4f result = vars.tempMat42; result.set(cropMatrix); result.multLocal(projMatrix); - vars.release(); shadowCam.setProjectionMatrix(result); + vars.release(); } /** @@ -353,68 +362,92 @@ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { */ public static class OccludersExtractor { // global variables set in order not to have recursive process method with too many parameters - Matrix4f viewProjMatrix; - public Integer casterCount; - BoundingBox splitBB, casterBB; - GeometryList splitOccluders; - TempVars vars; - - public OccludersExtractor() {} - - // initialize the global OccludersExtractor variables - public OccludersExtractor(Matrix4f vpm, int cc, BoundingBox sBB, BoundingBox cBB, - GeometryList sOCC, TempVars v) { - viewProjMatrix = vpm; - casterCount = cc; - splitBB = sBB; - casterBB = cBB; - splitOccluders = sOCC; - vars = v; + private final Matrix4f viewProjMatrix; + private int casterCount; + private final BoundingBox splitBB, casterBB; + private final GeometryList splitOccluders; + private final TempVars vars; + + /** + * Creates a new {@code OccludersExtractor}. + * + * @param viewProjMatrix the view-projection matrix of the shadow camera (not null, unaffected) + * @param splitBB the bounding box of the viewer camera's frustum in shadow camera's view-projection space (not null, unaffected) + * @param casterBB a bounding box to merge found caster bounds into (not null, modified) + * @param splitOccluders a list to add found caster geometries to (may be null if only counting casters) + * @param vars a {@link TempVars} instance for temporary object pooling (not null, managed by caller) + */ + public OccludersExtractor(Matrix4f viewProjMatrix, BoundingBox splitBB, BoundingBox casterBB, + GeometryList splitOccluders, TempVars vars) { + this.viewProjMatrix = viewProjMatrix; + this.casterCount = 0; + this.splitBB = splitBB; + this.casterBB = casterBB; + this.splitOccluders = splitOccluders; + this.vars = vars; } /** - * Check the rootScene against camera frustum and if intersects process it recursively. - * The global OccludersExtractor variables need to be initialized first. - * Variables are updated and used in {@link ShadowUtil#updateShadowCamera} at last. + * Recursively checks the provided scene graph for shadow casters (occluders) + * and adds them to the internal list if they intersect the specified frustum. + * The {@code casterCount} and {@code casterBB} will be updated during this process. * * @param scene the root of the scene to check (may be null) - * @return the number of shadow casters found + * @return this {@code OccludersExtractor} instance for chaining. */ - public int addOccluders(Spatial scene) { - if (scene != null) process(scene); + public OccludersExtractor addOccluders(Spatial scene) { + if (scene != null) { + process(scene); + } + return this; + } + + /** + * Returns the number of shadow casters found by this extractor. + * + * @return the current count of shadow casters. + */ + public int getCasterCount() { return casterCount; } + /** + * Internal recursive method to process the scene graph and identify shadow casters. + * + * @param scene the current spatial to process (not null) + */ private void process(Spatial scene) { - if (scene.getCullHint() == Spatial.CullHint.Always) return; + if (scene.getCullHint() == Spatial.CullHint.Always) { + return; + } RenderQueue.ShadowMode shadowMode = scene.getShadowMode(); if (scene instanceof Geometry) { // convert bounding box to light's viewproj space - Geometry occluder = (Geometry)scene; - if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive - && !occluder.isGrouped() && occluder.getWorldBound()!=null) { + Geometry occluder = (Geometry) scene; + if (shadowMode != RenderQueue.ShadowMode.Off + && shadowMode != RenderQueue.ShadowMode.Receive + && !occluder.isGrouped() + && occluder.getWorldBound() != null) { + BoundingVolume bv = occluder.getWorldBound(); BoundingVolume occBox = bv.transform(viewProjMatrix, vars.bbox); 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 disappearing issues when - // the caster itself is not in the view camera - // but its shadow is in the camera + float originalZExtent = occBB.getZExtent(); + + // Attempt to extend the occluder further into the frustum for better shadow coverage. + // This fixes issues where the caster itself is outside the view camera, but its shadow is visible. // The number is in world units - occBB.setZExtent(occBB.getZExtent() + 50); + occBB.setZExtent(originalZExtent + 50); occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25)); if (splitBB.intersects(occBB)) { - //Nehon : prevent NaN and infinity values to screw the final bounding box + // Prevent NaN and infinity values from screwing the final bounding box if (!Float.isNaN(occBox.getCenter().x) && !Float.isInfinite(occBox.getCenter().x)) { - // To prevent extending the depth range too much - // We return the bound to its former shape - // Before adding it - occBB.setZExtent(occBB.getZExtent() - 50); + // Restore original bound before merging to prevent depth range extension + occBB.setZExtent(originalZExtent - 50); occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25)); casterBB.mergeLocal(occBox); casterCount++; @@ -431,30 +464,27 @@ private void process(Spatial scene) { } } } - } else if (scene instanceof Node && ((Node)scene).getWorldBound() != null) { - Node nodeOcc = (Node)scene; - boolean intersects = false; - // some + } else if (scene instanceof Node && scene.getWorldBound() != null) { + Node nodeOcc = (Node) scene; BoundingVolume bv = nodeOcc.getWorldBound(); BoundingVolume occBox = bv.transform(viewProjMatrix, vars.bbox); - intersects = splitBB.intersects(occBox); + 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 disappearing issues when - // the caster itself is not in the view camera - // but its shadow is in the camera + float originalZExtent = occBB.getZExtent(); + + // Attempt to extend the occluder further into the frustum for better shadow coverage. + // This fixes issues where the caster itself is outside the view camera, but its shadow is visible. // The number is in world units - occBB.setZExtent(occBB.getZExtent() + 50); + occBB.setZExtent(originalZExtent + 50); occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25)); intersects = splitBB.intersects(occBB); } if (intersects) { - for (Spatial child : ((Node)scene).getChildren()) { - process(child); + for (Spatial child : ((Node) scene).getChildren()) { + process(child); // Recursively process children } } } @@ -462,23 +492,24 @@ private void process(Spatial scene) { } /** - * Updates the shadow camera to properly contain the given points (which - * contain the eye camera frustum corners) and the shadow occluder objects - * collected through the traverse of the scene hierarchy + * Updates the shadow camera's projection matrix to encompass the viewer camera's + * frustum corners and the identified shadow occluder and receiver objects. + * This method calculates a tight bounding box in the shadow camera's view-projection + * space and uses it to derive a suitable orthographic projection. * - * @param viewPort the ViewPort - * @param receivers a list of receiving geometries - * @param shadowCam the shadow camera (not null, modified) - * @param points an array of location vectors (not null, unaffected) - * @param splitOccluders a list of occluding geometries - * @param shadowMapSize the size of each edge of the shadow map (in pixels) + * @param viewPort the current view port (not null) + * @param receivers a list of geometries acting as shadow receivers (not null, unaffected) + * @param shadowCam the shadow camera to be updated (not null, modified) + * @param points an array of 8 {@code Vector3f} representing the viewer camera's frustum corners in world space (not null, unaffected) + * @param splitOccluders a {@link GeometryList} to populate with geometries that act as shadow casters (may be empty, modified) + * @param shadowMapSize the size of each edge of the shadow map texture (in pixels), used for stabilization */ public static void updateShadowCamera(ViewPort viewPort, - GeometryList receivers, - Camera shadowCam, - Vector3f[] points, - GeometryList splitOccluders, - float shadowMapSize) { + GeometryList receivers, + Camera shadowCam, + Vector3f[] points, + GeometryList splitOccluders, + float shadowMapSize) { boolean ortho = shadowCam.isParallelProjection(); @@ -498,7 +529,7 @@ public static void updateShadowCamera(ViewPort viewPort, BoundingBox casterBB = new BoundingBox(); BoundingBox receiverBB = new BoundingBox(); - int casterCount = 0, receiverCount = 0; + int receiverCount = 0; for (int i = 0; i < receivers.size(); i++) { // convert bounding box to light's viewproj space @@ -516,11 +547,11 @@ public static void updateShadowCamera(ViewPort viewPort, } // collect splitOccluders through scene recursive traverse - OccludersExtractor occExt = new OccludersExtractor(viewProjMatrix, casterCount, splitBB, casterBB, splitOccluders, vars); + OccludersExtractor occExt = new OccludersExtractor(viewProjMatrix, splitBB, casterBB, splitOccluders, vars); for (Spatial scene : viewPort.getScenes()) { occExt.addOccluders(scene); } - casterCount = occExt.casterCount; + int casterCount = occExt.getCasterCount(); if (casterCount == 0) { vars.release(); @@ -564,7 +595,6 @@ public static void updateShadowCamera(ViewPort viewPort, 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; @@ -574,12 +604,11 @@ public static void updateShadowCamera(ViewPort viewPort, scaleX = deltaCropX == 0 ? 0 : 2.0f / deltaCropX; scaleY = deltaCropY == 0 ? 0 : 2.0f / deltaCropY; - //Shadow map stabilization approximation from shaderX 7 - //from Practical Cascaded Shadow maps adapted to PSSM - //scale stabilization + // Shadow map stabilization approximation from ShaderX 7 (Practical Cascaded Shadow Maps adapted to PSSM) + // Scale stabilization: Quantizes the scale to prevent shimmering artifacts during camera movement. float halfTextureSize = shadowMapSize * 0.5f; - if (halfTextureSize != 0 && scaleX >0 && scaleY>0) { + if (halfTextureSize != 0 && scaleX > 0 && scaleY > 0) { float scaleQuantizer = 0.1f; scaleX = 1.0f / FastMath.ceil(1.0f / scaleX * scaleQuantizer) * scaleQuantizer; scaleY = 1.0f / FastMath.ceil(1.0f / scaleY * scaleQuantizer) * scaleQuantizer; @@ -588,11 +617,9 @@ public static void updateShadowCamera(ViewPort viewPort, offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX; offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY; - - //Shadow map stabilization approximation from shaderX 7 - //from Practical Cascaded Shadow maps adapted to PSSM - //offset stabilization - if (halfTextureSize != 0 && scaleX >0 && scaleY>0) { + // Shadow map stabilization approximation from ShaderX 7 (Practical Cascaded Shadow Maps adapted to PSSM) + // Offset stabilization: Quantizes the offset to align pixel boundaries + if (halfTextureSize != 0 && scaleX > 0 && scaleY > 0) { offsetX = FastMath.ceil(offsetX * halfTextureSize) / halfTextureSize; offsetY = FastMath.ceil(offsetY * halfTextureSize) / halfTextureSize; } @@ -601,111 +628,114 @@ public static void updateShadowCamera(ViewPort viewPort, scaleZ = deltaCropZ == 0 ? 0 : 1.0f / deltaCropZ; offsetZ = -cropMin.z * scaleZ; - - - Matrix4f cropMatrix = vars.tempMat4; cropMatrix.set(scaleX, 0f, 0f, offsetX, 0f, scaleY, 0f, offsetY, 0f, 0f, scaleZ, offsetZ, 0f, 0f, 0f, 1f); - - Matrix4f result = new Matrix4f(); + Matrix4f result = vars.tempMat42; result.set(cropMatrix); result.multLocal(projMatrix); - vars.release(); shadowCam.setProjectionMatrix(result); + vars.release(); } /** - * Populates the outputGeometryList with the geometry of the - * inputGeometryList that are in the frustum of the given camera + * Populates the {@code outputGeometryList} with geometries from the + * {@code inputGeometryList} that are within the frustum of the given camera. + * This method iterates through each geometry and checks its world bound + * against the camera's frustum. * - * @param inputGeometryList The list containing all geometries to check - * against the camera frustum - * @param camera the camera to check geometries against - * @param outputGeometryList the list of all geometries that are in the - * camera frustum + * @param inputGeometryList The list containing all geometries to check against the camera frustum (not null, unaffected) + * @param camera The camera to check geometries against (not null, unaffected) + * @param outputGeometryList The list to which geometries within the camera frustum will be added (not null, modified) */ public static void getGeometriesInCamFrustum(GeometryList inputGeometryList, - Camera camera, - GeometryList outputGeometryList) { + Camera camera, GeometryList outputGeometryList) { + int planeState = camera.getPlaneState(); for (int i = 0; i < inputGeometryList.size(); i++) { Geometry g = inputGeometryList.get(i); - int planeState = camera.getPlaneState(); camera.setPlaneState(0); if (camera.contains(g.getWorldBound()) != Camera.FrustumIntersect.Outside) { outputGeometryList.add(g); } - camera.setPlaneState(planeState); } - + camera.setPlaneState(planeState); } /** - * Populates the outputGeometryList with the rootScene children geometries - * that are in the frustum of the given camera + * Populates the {@code outputGeometryList} with geometries from the + * provided scene graph (starting from {@code rootScene}) that are + * within the frustum of the given camera and match the specified shadow mode. + * This method traverses the scene hierarchy recursively. * - * @param rootScene the rootNode of the scene to traverse - * @param camera the camera to check geometries against - * @param mode the ShadowMode to test for - * @param outputGeometryList the list of all geometries that are in the - * camera frustum + * @param rootScene The root of the scene to traverse (may be null) + * @param camera The camera to check geometries against (not null, unaffected) + * @param mode The {@link RenderQueue.ShadowMode} to filter geometries by + * @param outputGeometryList The list to which matching geometries within the camera frustum will be added (not null, modified) */ - public static void getGeometriesInCamFrustum(Spatial rootScene, Camera camera, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { - if (rootScene != null && rootScene instanceof Node) { + public static void getGeometriesInCamFrustum(Spatial rootScene, Camera camera, + RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { + if (rootScene instanceof Node) { int planeState = camera.getPlaneState(); - addGeometriesInCamFrustumFromNode(camera, (Node)rootScene, mode, outputGeometryList); + addGeometriesInCamFrustumFromNode(camera, (Node) rootScene, mode, outputGeometryList); camera.setPlaneState(planeState); } } /** - * Helper function to distinguish between Occluders and Receivers + * Helper function to determine if a spatial's shadow mode matches the desired mode. + * This is useful for distinguishing between shadow casters and receivers. * - * @param shadowMode the ShadowMode tested - * @param desired the desired ShadowMode - * @return true if tested ShadowMode matches the desired one + * @param shadowMode The actual {@link RenderQueue.ShadowMode} of a spatial. + * @param desired The desired {@link RenderQueue.ShadowMode} to check against. + * @return true if the {@code shadowMode} allows for the {@code desired} shadow behavior, false otherwise. */ - static private boolean checkShadowMode(RenderQueue.ShadowMode shadowMode, RenderQueue.ShadowMode desired) - { - if (shadowMode != RenderQueue.ShadowMode.Off) - { - switch (desired) { - case Cast : - return shadowMode==RenderQueue.ShadowMode.Cast || shadowMode==RenderQueue.ShadowMode.CastAndReceive; - case Receive: - return shadowMode==RenderQueue.ShadowMode.Receive || shadowMode==RenderQueue.ShadowMode.CastAndReceive; - case CastAndReceive: - return true; - } + private static boolean checkShadowMode(RenderQueue.ShadowMode shadowMode, RenderQueue.ShadowMode desired) { + if (shadowMode == RenderQueue.ShadowMode.Off) { + return false; + } + switch (desired) { + case Cast: + return shadowMode == RenderQueue.ShadowMode.Cast || shadowMode == RenderQueue.ShadowMode.CastAndReceive; + case Receive: + return shadowMode == RenderQueue.ShadowMode.Receive || shadowMode == RenderQueue.ShadowMode.CastAndReceive; + case CastAndReceive: + return true; // Any non-Off mode implies CastAndReceive if that's desired + default: + return false; } - return false; } /** - * Helper function used to recursively populate the outputGeometryList - * with geometry children of scene node + * Recursive helper function to populate the {@code outputGeometryList} with geometries + * from a given node and its children that are within the camera frustum and match the + * specified shadow mode. * - * @param camera - * @param scene the root of the scene to traverse (may be null) - * @param mode the ShadowMode to test for - * @param outputGeometryList + * @param camera The camera to check against (not null, unaffected) + * @param scene The current node in the scene graph to traverse (not null) + * @param mode The {@link RenderQueue.ShadowMode} to filter geometries by. + * @param outputGeometryList The list to add matching geometries to (not null, modified) */ - private static void addGeometriesInCamFrustumFromNode(Camera camera, Node scene, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { - if (scene.getCullHint() == Spatial.CullHint.Always) return; + private static void addGeometriesInCamFrustumFromNode(Camera camera, Node scene, + RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { + if (scene.getCullHint() == Spatial.CullHint.Always) { + return; + } + camera.setPlaneState(0); if (camera.contains(scene.getWorldBound()) != Camera.FrustumIntersect.Outside) { - for (Spatial child: scene.getChildren()) { - if (child instanceof Node) addGeometriesInCamFrustumFromNode(camera, (Node)child, mode, outputGeometryList); + for (Spatial child : scene.getChildren()) { + if (child instanceof Node) + addGeometriesInCamFrustumFromNode(camera, (Node) child, mode, outputGeometryList); else if (child instanceof Geometry && child.getCullHint() != Spatial.CullHint.Always) { camera.setPlaneState(0); if (checkShadowMode(child.getShadowMode(), mode) && - !((Geometry)child).isGrouped() && + !((Geometry) child).isGrouped() && camera.contains(child.getWorldBound()) != Camera.FrustumIntersect.Outside) { - outputGeometryList.add((Geometry)child); + outputGeometryList.add((Geometry) child); } } } @@ -713,23 +743,24 @@ else if (child instanceof Geometry && child.getCullHint() != Spatial.CullHint.Al } /** - * Populates the outputGeometryList with the geometry of the - * inputGeometryList that are in the radius of a light. - * The array must contain 6 cameras, initialized to represent the viewspace of a point light. + * Populates the {@code outputGeometryList} with geometries from the + * {@code inputGeometryList} that are within the light's effective radius, + * represented by an array of cameras (e.g., 6 cameras for a point light's cubemap faces). + * A geometry is considered "in light radius" if its world bound intersects + * the frustum of at least one of the provided cameras. * - * @param inputGeometryList The list containing all geometries to check - * against the camera frustum - * @param cameras the camera array to check geometries against - * @param outputGeometryList the list of all geometries that are in the - * camera frustum + * @param inputGeometryList The list containing all geometries to check (not null, unaffected) + * @param cameras An array of cameras representing the light's view frustums (not null, unaffected) + * @param outputGeometryList The list to which geometries within the light's radius will be added (not null, modified) */ public static void getGeometriesInLightRadius(GeometryList inputGeometryList, - Camera[] cameras, - GeometryList outputGeometryList) { + Camera[] cameras, GeometryList outputGeometryList) { for (int i = 0; i < inputGeometryList.size(); i++) { Geometry g = inputGeometryList.get(i); boolean inFrustum = false; - for (int j = 0; j < cameras.length && inFrustum == false; j++) { + + // Iterate through all light cameras, stop if found in any + for (int j = 0; j < cameras.length && !inFrustum; j++) { Camera camera = cameras[j]; int planeState = camera.getPlaneState(); camera.setPlaneState(0); @@ -740,38 +771,47 @@ public static void getGeometriesInLightRadius(GeometryList inputGeometryList, outputGeometryList.add(g); } } - } /** - * Populates the outputGeometryList with the geometries of the children - * of OccludersExtractor.rootScene node that are both in the frustum of the given vpCamera and some camera inside cameras array. - * The array of cameras must be initialized to represent the light viewspace of some light like pointLight or spotLight + * Populates the {@code outputGeometryList} with geometries from the children + * of {@code rootScene} that are both in the frustum of the main viewport camera + * ({@code vpCamera}) and within the view frustum of at least one camera + * from the provided {@code cameras} array (representing a light's view space, + * like for point or spot lights). The geometries are also filtered by their + * {@link RenderQueue.ShadowMode}. * - * @param rootScene the root of the scene to traverse (may be null) - * @param vpCamera the viewPort camera - * @param cameras the camera array to check geometries against, representing the light viewspace - * @param mode the ShadowMode to test for - * @param outputGeometryList the output list of all geometries that are in the camera frustum + * @param rootScene The root of the scene to traverse (may be null) + * @param vpCamera The main viewport camera (not null, unaffected) + * @param cameras An array of cameras representing the light's view frustums (not null, unaffected) + * @param mode The {@link RenderQueue.ShadowMode} to filter geometries by. + * @param outputGeometryList The list to which matching geometries will be added (not null, modified) */ - public static void getLitGeometriesInViewPort(Spatial rootScene, Camera vpCamera, Camera[] cameras, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { - if (rootScene != null && rootScene instanceof Node) { + public static void getLitGeometriesInViewPort(Spatial rootScene, Camera vpCamera, Camera[] cameras, + RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { + if (rootScene instanceof Node) { addGeometriesInCamFrustumAndViewPortFromNode(vpCamera, cameras, rootScene, mode, outputGeometryList); } } + /** - * Helper function to recursively collect the geometries for getLitGeometriesInViewPort function. + * Recursive helper function to collect geometries that are visible in both + * the main viewport camera and at least one light view camera. * - * @param vpCamera the viewPort camera - * @param cameras the camera array to check geometries against, representing the light viewspace - * @param scene the Node to traverse or geometry to possibly add - * @param outputGeometryList the output list of all geometries that are in the camera frustum + * @param vpCamera The main viewport camera (not null, unaffected) + * @param cameras An array of cameras representing the light's view frustums (not null, unaffected) + * @param scene The current spatial (Node or Geometry) to process (not null) + * @param mode The {@link RenderQueue.ShadowMode} to filter geometries by. + * @param outputGeometryList The list to add matching geometries to (not null, modified) */ - private static void addGeometriesInCamFrustumAndViewPortFromNode(Camera vpCamera, Camera[] cameras, Spatial scene, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { - if (scene.getCullHint() == Spatial.CullHint.Always) return; + private static void addGeometriesInCamFrustumAndViewPortFromNode(Camera vpCamera, Camera[] cameras, + Spatial scene, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { + if (scene.getCullHint() == Spatial.CullHint.Always) { + return; + } boolean inFrustum = false; - for (int j = 0; j < cameras.length && inFrustum == false; j++) { + for (int j = 0; j < cameras.length && !inFrustum; j++) { Camera camera = cameras[j]; int planeState = camera.getPlaneState(); camera.setPlaneState(0); @@ -779,16 +819,14 @@ private static void addGeometriesInCamFrustumAndViewPortFromNode(Camera vpCamera camera.setPlaneState(planeState); } if (inFrustum) { - if (scene instanceof Node) - { - Node node = (Node)scene; - for (Spatial child: node.getChildren()) { + if (scene instanceof Node) { + Node node = (Node) scene; + for (Spatial child : node.getChildren()) { addGeometriesInCamFrustumAndViewPortFromNode(vpCamera, cameras, child, mode, outputGeometryList); } - } - else if (scene instanceof Geometry) { - if (checkShadowMode(scene.getShadowMode(), mode) && !((Geometry)scene).isGrouped()) { - outputGeometryList.add((Geometry)scene); + } else if (scene instanceof Geometry) { + if (checkShadowMode(scene.getShadowMode(), mode) && !((Geometry) scene).isGrouped()) { + outputGeometryList.add((Geometry) scene); } } }