Package com.ardor3d.extension.shadow.map

Source Code of com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass

/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/

package com.ardor3d.extension.shadow.map;

import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.bounding.BoundingSphere;
import com.ardor3d.bounding.BoundingVolume;
import com.ardor3d.bounding.OrientedBoundingBox;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture.DepthTextureCompareFunc;
import com.ardor3d.image.Texture.DepthTextureCompareMode;
import com.ardor3d.image.Texture.DepthTextureMode;
import com.ardor3d.image.Texture2D;
import com.ardor3d.image.TextureStoreFormat;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.light.Light;
import com.ardor3d.light.PointLight;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.Matrix4;
import com.ardor3d.math.Quaternion;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.Vector4;
import com.ardor3d.math.type.ReadOnlyColorRGBA;
import com.ardor3d.math.type.ReadOnlyMatrix4;
import com.ardor3d.math.type.ReadOnlyVector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.Camera.ProjectionMode;
import com.ardor3d.renderer.ContextCapabilities;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.IndexMode;
import com.ardor3d.renderer.RenderLogic;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.TextureRenderer;
import com.ardor3d.renderer.TextureRendererFactory;
import com.ardor3d.renderer.pass.Pass;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.BlendState;
import com.ardor3d.renderer.state.ClipState;
import com.ardor3d.renderer.state.ColorMaskState;
import com.ardor3d.renderer.state.CullState;
import com.ardor3d.renderer.state.CullState.Face;
import com.ardor3d.renderer.state.GLSLShaderObjectsState;
import com.ardor3d.renderer.state.LightState;
import com.ardor3d.renderer.state.OffsetState;
import com.ardor3d.renderer.state.OffsetState.OffsetType;
import com.ardor3d.renderer.state.RenderState;
import com.ardor3d.renderer.state.RenderState.StateType;
import com.ardor3d.renderer.state.ShadingState;
import com.ardor3d.renderer.state.ShadingState.ShadingMode;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.renderer.state.WireframeState;
import com.ardor3d.renderer.state.ZBufferState;
import com.ardor3d.scenegraph.Line;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Renderable;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.util.geom.BufferUtils;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.google.common.collect.Lists;

/**
* A pass providing a parallel split shadow mapping (PSSM) layer across the top of an existing scene.
*/
public class ParallelSplitShadowMapPass extends Pass {

    /** The Constant logger. */
    private static final Logger logger = Logger.getLogger(ParallelSplitShadowMapPass.class.getName());

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 1L;

    /** Bias matrix from [-1, 1] to [0, 1]. */
    public static final ReadOnlyMatrix4 SCALE_BIAS_MATRIX = new Matrix4( //
            0.5, 0.0, 0.0, 0.0, //
            0.0, 0.5, 0.0, 0.0, //
            0.0, 0.0, 0.5, 0.0, //
            0.5, 0.5, 0.5, 1.0);//

    /** The renderer used to produce the shadow map. */
    private TextureRenderer _shadowMapRenderer;

    /** The textures storing the shadow maps. */
    private Texture2D _shadowMapTexture[];

    /** The list of occluding nodes. */
    private final List<Spatial> _occluderNodes = Lists.newArrayList();

    /** Extra bounds receivers, when rendering shadows other ways than through overlay */
    private final List<Spatial> _boundsReceiver = Lists.newArrayList();

    // Various optimizations for rendering shadow maps...
    /** Culling front faces when rendering shadow maps. */
    private final CullState _cullFrontFace;

    /** Turn off textures when rendering shadow maps. */
    private final TextureState _noTexture;

    /** Turn off lighting when rendering shadow maps. */
    private final LightState _noLights;

    /** set flat shading when rendering shadow maps. */
    private final ShadingState _flat;

    // Important pieces for rendering shadow maps
    /** Turn off colors when rendering shadow maps. */
    private final ColorMaskState _colorDisabled;

    /** The state applying the depth offset for the shadow. */
    private final OffsetState _shadowOffsetState;

    /**
     * The blending to both discard the fragments that have been determined to be free of shadows and to blend into the
     * background scene.
     */
    private final BlendState _discardShadowFragments;

    /** The state applying the shadow map. */
    private final TextureState _shadowTextureState;

    /** Don't perform any plane clipping when rendering the shadowed scene. */
    private final ClipState _noClip;

    /** True once the pass has been initialized. */
    protected boolean _initialised = false;

    /** Flag for updating texture splits when number of splits changed. */
    protected boolean _reinitSplitsDirty = true;

    /** Flag for updating texture renderer when texture size changed. */
    protected boolean _reinitTextureSizeDirty = true;

    /** The size of the shadow map texture. */
    private int _shadowMapSize;

    /** Minimum number of splits allowed */
    public static final int _MIN_SPLITS = 1;

    /** Maximum number of splits allowed */
    public static final int _MAX_SPLITS = 4;

    /** Number of splits used. */
    protected int _numOfSplits;

    /** Minimum light z distance from target. */
    private double _minimumLightDistance = 1000.0;

    /** Shadow color and transparency. */
    private final ColorRGBA _shadowColor = new ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f);

    /** Light -> Camera transformation matrix. */
    private final Matrix4 _shadowMatrix = new Matrix4();

    /** Bounding of scene for packing frustum. */
    protected BoundingBox _receiverBounds = new BoundingBox();

    /** Indicates if we have any valid reciever bounds to use for frustum packing */
    protected boolean hasValidBounds = false;

    /** Special camera with functionality for packing frustum etc. */
    protected PSSMCamera _pssmCam;

    /** Light that casts the shadow. */
    protected Light _light;

    /** Shader for rendering pssm shadows in one pass. */
    private GLSLShaderObjectsState _pssmShader;

    /** Might need to keep main shader when doing both multitexturing shadows and overlays */
    private GLSLShaderObjectsState _mainShader;

    /** Shader for debugging pssm shadows drawing splits in different colors. */
    private GLSLShaderObjectsState _pssmDebugShader;

    /** Debug for stopping camera update. */
    private boolean _updateMainCamera = true;

    /** Debug drawing frustums. */
    private boolean _drawDebug = false;

    /** Debug drawing shader. */
    private boolean _drawShaderDebug = false;

    /** Do we want to keep the main shader to do both multitexturing shadows and overlays? */
    private boolean _keepMainShader = false;

    /**
     * True if we want to factor in texturing to shadows; useful for casting shadows against alpha-tested textures.
     * Default is false.
     */
    private boolean _useSceneTexturing = false;

    /**
     * True if we want to use the culling set on the objects instead of always culling front face (which is done for
     * shadow precision). Default is false.
     */
    private boolean _useObjectCullFace = false;

    /**
     * When true (the default) the generated shadow map textures are drawn over the scene in a separate blend pass. If
     * false, the shadows are generated, but not applied.
     */
    private boolean _renderShadowedScene = true;

    /** Store format to use for the shadow textures. */
    protected TextureStoreFormat _shadowTextureStoreFormat = TextureStoreFormat.Depth16;

    /** Shadow filter techniques */
    public enum Filter {
        None, Pcf
    }

    private Filter filter = Filter.None;

    private ShadowRenderCallback shadowRenderCallback;

    /**
     * Create a pssm shadow map pass casting shadows from a light with the direction given.
     *
     * @param shadowMapSize
     *            The size of the shadow map texture
     * @param numOfSplits
     *            the num of splits
     */
    public ParallelSplitShadowMapPass(final Light light, final int shadowMapSize, final int numOfSplits) {
        _light = light;
        _shadowMapSize = shadowMapSize;
        setNumOfSplits(numOfSplits);
        _pssmCam = new PSSMCamera();

        _noClip = new ClipState();
        _noClip.setEnabled(false);
        _noTexture = new TextureState();
        _noTexture.setEnabled(false);
        _colorDisabled = new ColorMaskState();
        _colorDisabled.setAll(false);
        _cullFrontFace = new CullState();
        _cullFrontFace.setEnabled(true);
        _cullFrontFace.setCullFace(CullState.Face.Front);
        _noLights = new LightState();
        _noLights.setEnabled(false);

        _shadowOffsetState = new OffsetState();
        _shadowOffsetState.setEnabled(true);
        _shadowOffsetState.setTypeEnabled(OffsetType.Fill, true);
        _shadowOffsetState.setFactor(1.1f);
        _shadowOffsetState.setUnits(4.0f);

        _flat = new ShadingState();
        _flat.setShadingMode(ShadingMode.Flat);

        // When rendering and comparing the shadow map with the current depth, the result will be set to alpha 1 if in
        // shadow and to 0 if not in shadow.
        _discardShadowFragments = new BlendState();
        _discardShadowFragments.setEnabled(true);
        _discardShadowFragments.setBlendEnabled(true);
        _discardShadowFragments.setTestEnabled(true);
        _discardShadowFragments.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
        _discardShadowFragments.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);

        _shadowTextureState = new TextureState();
    }

    /**
     * Add a spatial that will occlude light and hence cast a shadow.
     *
     * @param occluder
     *            The spatial to add as an occluder
     */
    public void addOccluder(final Spatial occluder) {
        if (!_occluderNodes.contains(occluder)) {
            _occluderNodes.add(occluder);
        }
    }

    /**
     * Remove a spatial from the list of occluders.
     *
     * @param occluder
     *            The spatial to remove from the occluderlist
     */
    public void removeOccluder(final Spatial occluder) {
        _occluderNodes.remove(occluder);
    }

    /**
     * Initialize the pass render states.
     *
     * @param r
     *            the r
     */
    public void init(final Renderer r) {
        if (_initialised) {
            return;
        }

        _initialised = true; // now it's initialized

        // render states to use when rendering into the shadow map, no textures or colors are required since we're only
        // interested in recording depth. Also only need back faces when rendering the shadow maps

        // Load PSSM shader.
        final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
        if (caps.isGLSLSupported()) {
            _pssmShader = new GLSLShaderObjectsState();
            try {
                _pssmShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
                        ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssm.vert"));
                if (filter == Filter.None) {
                    _pssmShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
                            ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssm.frag"));
                } else if (filter == Filter.Pcf) {
                    _pssmShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
                            ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmPCF.frag"));
                }
            } catch (final IOException ex) {
                logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
            }
            _mainShader = _pssmShader;

            _pssmDebugShader = new GLSLShaderObjectsState();
            try {
                _pssmDebugShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
                        ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmDebug.vert"));
                _pssmDebugShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
                        ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmDebug.frag"));
            } catch (final IOException ex) {
                logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
            }
        }

        // Setup texture renderer.
        reinitTextureSize(r);

        // Setup textures and shader for all splits
        reinitSplits();
    }

    public void reinit(final Renderer r) {
        reinitTextureSize(r);
        reinitSplits();
    }

    /**
     * Reinit texture size. Sets up texture renderer.
     *
     * @param r
     *            the Renderer
     */
    private void reinitTextureSize(final Renderer r) {
        if (!_reinitTextureSizeDirty) {
            return;
        }

        _reinitTextureSizeDirty = false;

        final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();

        // Create texture renderer
        _shadowMapRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(_shadowMapSize, _shadowMapSize, r,
                caps);

        // Enforce performance enhancing states on the renderer.
        _shadowMapRenderer.enforceState(_noClip);
        _shadowMapRenderer.enforceState(_colorDisabled);
        if (!_useObjectCullFace) {
            _shadowMapRenderer.enforceState(_cullFrontFace);
        } else {
            _shadowMapRenderer.clearEnforcedState(StateType.Cull);
        }
        _shadowMapRenderer.enforceState(_noLights);
        _shadowMapRenderer.enforceState(_flat);
        _shadowMapRenderer.enforceState(_shadowOffsetState);
        if (!_useSceneTexturing) {
            _shadowMapRenderer.enforceState(_noTexture);
        } else {
            _shadowMapRenderer.clearEnforcedState(RenderState.StateType.Texture);
        }

        if (_light instanceof DirectionalLight) {
            _shadowMapRenderer.getCamera().setProjectionMode(ProjectionMode.Parallel);
        }
    }

    /**
     * Reinit splits. Setup textures and shader for all splits
     */
    private void reinitSplits() {
        if (!_reinitSplitsDirty) {
            return;
        }

        _reinitSplitsDirty = false;

        // render state to apply the shadow map texture
        _shadowMapTexture = new Texture2D[_numOfSplits];
        for (int i = 0; i < _numOfSplits; i++) {
            _shadowMapTexture[i] = new Texture2D();
            _shadowMapTexture[i].setWrap(Texture.WrapMode.BorderClamp);
            _shadowMapTexture[i].setMinificationFilter(Texture.MinificationFilter.BilinearNoMipMaps);
            _shadowMapTexture[i].setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
            _shadowMapTexture[i].setBorderColor(ColorRGBA.WHITE);

            _shadowMapTexture[i].setEnvironmentalMapMode(Texture.EnvironmentalMapMode.EyeLinear);

            _shadowMapTexture[i].setTextureStoreFormat(_shadowTextureStoreFormat);
            _shadowMapTexture[i].setDepthCompareMode(DepthTextureCompareMode.RtoTexture);
            _shadowMapTexture[i].setDepthCompareFunc(DepthTextureCompareFunc.GreaterThanEqual);
            _shadowMapTexture[i].setDepthMode(DepthTextureMode.Intensity);

            _shadowMapRenderer.setupTexture(_shadowMapTexture[i]);
            _shadowTextureState.setTexture(_shadowMapTexture[i], i);
        }
        if (_pssmShader != null) {
            for (int i = 0; i < _MAX_SPLITS; i++) {
                _pssmShader.setUniform("shadowMap" + i, i);
                _pssmDebugShader.setUniform("shadowMap" + i, i);
                _mainShader.setUniform("shadowMap" + i, i);
            }
        }
    }

    /**
     * Render the pass.
     *
     * @param r
     *            the Renderer
     */
    @Override
    protected void doRender(final Renderer r) {
        updateShadowMaps(r);

        if (_renderShadowedScene) {
            // Render overlay scene
            renderShadowedScene(r);
        }
    }

    public void updateShadowMaps(final Renderer r) {
        init(r);
        reinitTextureSize(r);
        reinitSplits();

        // Update receiving scene bounds to prepare for frustum packing
        updateReceiverBounds();

        // If updating camera is true (can be turned off for debugging), copy from main
        // camera and pack frustum.
        if (_updateMainCamera) {
            // Copy our active camera to our working pssm camera.
            final Camera cam = ContextManager.getCurrentContext().getCurrentCamera();
            _pssmCam.set(cam);

            // Calculate the closest fitting near and far planes.
            if (hasValidBounds) {
                _pssmCam.pack(_receiverBounds);
            } else {
                _pssmCam.updateMaxCameraFar();
            }
        }

        // Calculate the split distances
        _pssmCam.calculateSplitDistances(_numOfSplits);

        // Render our scene in sections.
        // For each part...
        for (int iSplit = 0; iSplit < _numOfSplits; iSplit++) {
            // Figure out the appropriate frustum.
            final double fNear = _pssmCam.getSplitDistances()[iSplit];
            final double fFar = _pssmCam.getSplitDistances()[iSplit + 1];

            // Calculate the frustum corners for the current split
            _pssmCam.calculateFrustum(fNear, fFar);

            // Debug draw main camera frustum for current split
            if (_drawDebug) {
                drawFrustum(r, _pssmCam, fNear, fFar, new ColorRGBA(0, 1, (float) (iSplit + 1) / _numOfSplits, 1),
                        (short) 0xF000, iSplit == 0);
            }

            // Calculate the appropriate lightview and projection matrices for the current split
            if (_light instanceof DirectionalLight) {
                calculateOptimalLightFrustum(_pssmCam._corners, _pssmCam._center);

                if (_drawDebug) {
                    boundingSphere
                            .setData(frustumBoundingSphere.getCenter(), 10, 10, frustumBoundingSphere.getRadius());
                    boundingSphere.draw(r);
                }
            } else if (_light instanceof PointLight) {
                calculateOptimalLightFrustumOld(_pssmCam._corners, _pssmCam._center);
            }

            // Debug draw light frustum for current split
            if (_drawDebug) {
                drawFrustum(r, _shadowMapRenderer.getCamera(), new ColorRGBA(1, 1, (iSplit + 1) / (float) _numOfSplits,
                        1), (short) 0xFFFF, true);
            }

            // Render shadowmap from light view for current split
            updateShadowMap(iSplit, r);

            // Update texture matrix for shadowmap
            updateTextureMatrix(iSplit);
        }

        updateShaderVariables();
    }

    private void updateShaderVariables() {
        if (_pssmShader != null && ContextManager.getCurrentContext().getCapabilities().isGLSLSupported()) {
            final float split1 = (float) _pssmCam.getSplitDistances()[1];
            final float split2 = (float) (_pssmCam.getSplitDistances().length > 2 ? _pssmCam.getSplitDistances()[2]
                    : 0f);
            final float split3 = (float) (_pssmCam.getSplitDistances().length > 3 ? _pssmCam.getSplitDistances()[3]
                    : 0f);
            final float split4 = (float) (_pssmCam.getSplitDistances().length > 4 ? _pssmCam.getSplitDistances()[4]
                    : 0f);

            GLSLShaderObjectsState currentShader = _drawShaderDebug ? _pssmDebugShader : _pssmShader;
            if (_drawShaderDebug) {
                currentShader = _pssmDebugShader;
            }

            currentShader.setUniform("sampleDist", split1, split2, split3, split4);
            if (filter == Filter.Pcf) {
                // TODO
                // currentShader.setUniform("_shadowSize", 1f / _shadowMapSize);
            }

            if (!_drawShaderDebug) {
                currentShader.setUniform("shadowColor", _shadowColor);
            }

            if (_keepMainShader) {
                _mainShader.setUniform("sampleDist", split1, split2, split3, split4);
                if (filter == Filter.Pcf) {
                    // TODO
                    // _mainShader.setUniform("_shadowSize", 1f / _shadowMapSize);
                }

                if (!_drawShaderDebug) {
                    _mainShader.setUniform("shadowColor", _shadowColor);
                }
            }
        }
    }

    // TODO
    final FloatBuffer frustumBoundingBuffer = BufferUtils.createVector3Buffer(8);
    final BoundingSphere frustumBoundingSphere = new BoundingSphere();

    /**
     * Calculate optimal light frustum perspective.
     *
     * @param frustumCorners
     *            the frustum corners
     * @param center
     *            the center
     */
    private void calculateOptimalLightFrustum(final Vector3[] frustumCorners, final ReadOnlyVector3 centerFrustum) {
        for (int i = 0; i < 8; i++) {
            BufferUtils.setInBuffer(frustumCorners[i], frustumBoundingBuffer, i);
        }
        frustumBoundingSphere.computeFromPoints(frustumBoundingBuffer);

        final Camera shadowCam = _shadowMapRenderer.getCamera();

        final ReadOnlyVector3 center = frustumBoundingSphere.getCenter();
        final double radius = frustumBoundingSphere.getRadius();

        Vector3 direction = new Vector3();
        final DirectionalLight dl = (DirectionalLight) _light;
        direction = direction.set(dl.getDirection());
        final double distance = Math.max(radius, _minimumLightDistance);

        final Vector3 tmpVec = Vector3.fetchTempInstance();
        tmpVec.set(direction);
        tmpVec.negateLocal();
        tmpVec.multiplyLocal(distance);
        tmpVec.addLocal(center);

        // temporary location
        shadowCam.setLocation(tmpVec);
        shadowCam.lookAt(center, Vector3.UNIT_Y);

        {
            // determine
            final double texelSizeW = (2 * radius) / _shadowMapRenderer.getWidth();
            final double texelSizeH = (2 * radius) / _shadowMapRenderer.getHeight();

            // build a Quaternion from camera axes to move
            final Quaternion q = Quaternion.fetchTempInstance();
            q.fromAxes(shadowCam.getLeft(), shadowCam.getUp(), shadowCam.getDirection());

            // invert to calculate in light space
            final Vector3 lightSpace = q.invert(null).apply(tmpVec, Vector3.fetchTempInstance());

            // snap to nearest texel
            lightSpace.setX(lightSpace.getX() - (lightSpace.getX() % texelSizeW));
            lightSpace.setY(lightSpace.getY() - (lightSpace.getY() % texelSizeH));

            // convert back
            q.apply(lightSpace, tmpVec);
            Vector3.releaseTempInstance(lightSpace);

            Quaternion.releaseTempInstance(q);
        }

        // updated location
        final double x = tmpVec.getX();
        final double y = tmpVec.getY();
        final double z = tmpVec.getZ();
        final double farZ = tmpVec.subtractLocal(center).length() + radius;
        Vector3.releaseTempInstance(tmpVec);

        // set frustum, then location
        shadowCam.setFrustum(1, farZ, -radius, radius, radius, -radius);
        shadowCam.setLocation(x, y, z);
    }

    /**
     * Saving this around until we fully support a good solution for non-directional lights. Like dual paraboloid shadow
     * maps...
     *
     * @param frustumCorners
     * @param center
     */
    private void calculateOptimalLightFrustumOld(final Vector3[] frustumCorners, final ReadOnlyVector3 center) {
        final Camera shadowCam = _shadowMapRenderer.getCamera();

        double distance = _minimumLightDistance;

        final Vector3 tmpVec = Vector3.fetchTempInstance();

        // Update shadow camera from light
        final PointLight pl = (PointLight) _light;

        shadowCam.setLocation(pl.getLocation());

        // Point light at split center
        shadowCam.lookAt(center, Vector3.UNIT_Y);

        // Reset frustum
        distance = center.subtract(shadowCam.getLocation(), tmpVec).length();
        shadowCam.setFrustum(1, distance, -1, 1, 1, -1);

        double fMinX = Double.POSITIVE_INFINITY;
        double fMaxX = Double.NEGATIVE_INFINITY;
        double fMinY = Double.POSITIVE_INFINITY;
        double fMaxY = Double.NEGATIVE_INFINITY;
        double fMinZ = Double.POSITIVE_INFINITY;
        double fMaxZ = Double.NEGATIVE_INFINITY;

        final ReadOnlyMatrix4 lightviewproj = shadowCam.getModelViewProjectionMatrix();

        final Vector4 position = Vector4.fetchTempInstance();
        for (final Vector3 frustumCorner : frustumCorners) {
            position.set(frustumCorner.getX(), frustumCorner.getY(), frustumCorner.getZ(), 1);
            lightviewproj.applyPre(position, position);

            position.setX(position.getX() / position.getW());
            position.setY(position.getY() / position.getW());
            position.setZ(position.getZ());

            fMinX = Math.min(position.getX(), fMinX);
            fMaxX = Math.max(position.getX(), fMaxX);

            fMinY = Math.min(position.getY(), fMinY);
            fMaxY = Math.max(position.getY(), fMaxY);

            fMinZ = Math.min(position.getZ(), fMinZ);
            fMaxZ = Math.max(position.getZ(), fMaxZ);
        }

        double width = 0;
        double height = 0;
        fMinX = clamp(fMinX, -1.0, 1.0);
        fMaxX = clamp(fMaxX, -1.0, 1.0);
        fMinY = clamp(fMinY, -1.0, 1.0);
        fMaxY = clamp(fMaxY, -1.0, 1.0);

        // Make sure the minimum z is at least a specified distance from
        // the target.
        fMinZ = Math.min(fMinZ, distance - _minimumLightDistance);
        fMinZ = Math.max(10.0, fMinZ);

        width = fMinZ * (fMaxX - fMinX) * 0.5;
        height = fMinZ * (fMaxY - fMinY) * 0.5;

        final Vector3 newCenter = Vector3.fetchTempInstance();
        position.set((fMinX + fMaxX) * 0.5, (fMinY + fMaxY) * 0.5, 1.0, 1);
        shadowCam.getModelViewProjectionInverseMatrix().applyPre(position, position);
        position.divideLocal(position.getW());
        newCenter.set(position.getX(), position.getY(), position.getZ());

        shadowCam.lookAt(newCenter, Vector3.UNIT_Y);

        Vector3.releaseTempInstance(newCenter);
        Vector4.releaseTempInstance(position);

        shadowCam.setFrustum(fMinZ, fMaxZ, -width, width, height, -height);
        shadowCam.update();
    }

    /**
     * Render the overlay scene with shadows.
     *
     * @param r
     *            The renderer to use
     */
    public void renderShadowedScene(final Renderer r) {
        boolean reset = false;
        if (_context == null) {
            _context = ContextManager.getCurrentContext();
            reset = true;
        }

        _context.pushEnforcedStates();
        _context.enforceState(_shadowTextureState);
        _context.enforceState(_discardShadowFragments);

        if (_pssmShader != null && _context.getCapabilities().isGLSLSupported()) {
            GLSLShaderObjectsState currentShader = _drawShaderDebug ? _pssmDebugShader : _pssmShader;
            if (_drawShaderDebug) {
                currentShader = _pssmDebugShader;
            }
            if (_keepMainShader) {
                currentShader = _mainShader;
            }
            _context.enforceState(currentShader);
        }

        for (final Spatial spat : _spatials) {
            spat.onDraw(r);
        }
        r.renderBuckets();

        _context.popEnforcedStates();

        if (reset) {
            // _context = null;
        }
    }

    private static RenderLogic logic = new RenderLogic() {
        private CullState cullState;
        private Face cullFace;
        private boolean isVisible;

        public void apply(final Renderable renderable) {
            if (renderable instanceof Mesh) {
                final Mesh mesh = (Mesh) renderable;

                isVisible = mesh.isVisible();
                if (!mesh.getSceneHints().isCastsShadows()) {
                    mesh.setVisible(false);
                }

                cullState = (CullState) mesh.getWorldRenderState(StateType.Cull);
                if (cullState != null) {
                    cullFace = cullState.getCullFace();
                    if (cullFace != Face.None) {
                        cullState.setCullFace(Face.Front);
                    }
                }
            }
        }

        public void restore(final Renderable renderable) {
            if (renderable instanceof Mesh) {
                final Mesh mesh = (Mesh) renderable;

                mesh.setVisible(isVisible);

                if (cullState != null) {
                    cullState.setCullFace(cullFace);
                }
            }
        }
    };

    /**
     * Update the shadow map.
     *
     * @param index
     *            shadow map texture index to update
     */
    private void updateShadowMap(final int index, final Renderer r) {

        if (shadowRenderCallback != null) {
            shadowRenderCallback.onRender(index, r, this, _shadowMapRenderer.getCamera());
        }

        r.setRenderLogic(logic);
        if (!_useSceneTexturing) {
            Mesh.RENDER_VERTEX_ONLY = true;
        }
        _shadowMapRenderer.render(_occluderNodes, _shadowMapTexture[index], Renderer.BUFFER_COLOR_AND_DEPTH);
        if (!_useSceneTexturing) {
            Mesh.RENDER_VERTEX_ONLY = false;
        }
        r.setRenderLogic(null);
    }

    /**
     * Update texture matrix.
     *
     * @param index
     *            the index
     */
    private void updateTextureMatrix(final int index) {
        // Create a matrix going from light to camera space
        final Camera cam = ContextManager.getCurrentContext().getCurrentCamera();
        _shadowMatrix.set(cam.getModelViewMatrix()).invertLocal();
        _shadowMatrix.multiplyLocal(_shadowMapRenderer.getCamera().getModelViewProjectionMatrix()).multiplyLocal(
                SCALE_BIAS_MATRIX);
        _shadowMapTexture[index].setTextureMatrix(_shadowMatrix);
    }

    /**
     * Checks if this pass is initialized.
     *
     * @return true, if is initialized
     */
    public boolean isInitialised() {
        return _initialised;
    }

    /**
     *
     * @return the offset state used for drawing the shadow textures.
     */
    public OffsetState getShadowOffsetState() {
        return _shadowOffsetState;
    }

    /**
     * Update receiver bounds.
     */
    private void updateReceiverBounds() {
        hasValidBounds = false;
        boolean firstRun = true;

        for (int i = 0, cSize = _boundsReceiver.size(); i < cSize; i++) {
            final Spatial child = _boundsReceiver.get(i);
            if (child != null && child.getWorldBound() != null && boundIsValid(child.getWorldBound())) {
                if (firstRun) {
                    _receiverBounds.setCenter(child.getWorldBound().getCenter());
                    _receiverBounds.setXExtent(0);
                    _receiverBounds.setYExtent(0);
                    _receiverBounds.setZExtent(0);
                    firstRun = false;
                }
                _receiverBounds.mergeLocal(child.getWorldBound());
                hasValidBounds = true;
            }
        }

        for (int i = 0, cSize = _spatials.size(); i < cSize; i++) {
            final Spatial child = _spatials.get(i);
            if (child != null && child.getWorldBound() != null && boundIsValid(child.getWorldBound())) {
                if (firstRun) {
                    _receiverBounds.setCenter(child.getWorldBound().getCenter());
                    _receiverBounds.setXExtent(0);
                    _receiverBounds.setYExtent(0);
                    _receiverBounds.setZExtent(0);
                    firstRun = false;
                }
                _receiverBounds.mergeLocal(child.getWorldBound());
                hasValidBounds = true;
            }
        }
    }

    /**
     * Checks if a bounding volume is valid.
     *
     * @param volume
     * @return
     */
    private boolean boundIsValid(final BoundingVolume volume) {
        if (!Vector3.isValid(volume.getCenter())) {
            return false;
        }
        switch (volume.getType()) {
            case AABB: {
                final BoundingBox vBox = (BoundingBox) volume;
                return !(Double.isInfinite(vBox.getXExtent()) || Double.isInfinite(vBox.getYExtent())
                        || Double.isInfinite(vBox.getZExtent()) || Double.isNaN(vBox.getXExtent())
                        || Double.isNaN(vBox.getYExtent()) || Double.isNaN(vBox.getZExtent()));
            }

            case Sphere: {
                final BoundingSphere vSphere = (BoundingSphere) volume;
                return !(Double.isInfinite(vSphere.getRadius()) || Double.isNaN(vSphere.getRadius()));
            }

            case OBB: {
                final OrientedBoundingBox obb = (OrientedBoundingBox) volume;
                return Vector3.isValid(obb.getExtent());
            }

            default:
                return true;
        }
    }

    /**
     * Gets the shadow map texture.
     *
     * @param index
     *            the index
     *
     * @return the shadow map texture
     */
    public Texture2D getShadowMapTexture(final int index) {
        return _shadowMapTexture[index];
    }

    /**
     * Gets the number of splits.
     *
     * @return the number of splits
     */
    public int getNumOfSplits() {
        return _numOfSplits;
    }

    /**
     * Sets the number of frustum splits and thus the number of shadow textures created by this pass. More splits
     * creates crisper shadows at the cost of increased texture memory.
     *
     * @param numOfSplits
     *            the new number of splits
     */
    public void setNumOfSplits(final int numOfSplits) {
        _numOfSplits = Math.min(Math.max(_MIN_SPLITS, numOfSplits), _MAX_SPLITS);
        _reinitSplitsDirty = true;

        if (_numOfSplits != numOfSplits) {
            logger.warning("Valid range for number of splits is " + _MIN_SPLITS + " to " + _MAX_SPLITS
                    + ". Tried to set it to " + numOfSplits);
        }
    }

    /**
     * Gets the shadow map size.
     *
     * @return the shadow map size
     */
    public int getShadowMapSize() {
        return _shadowMapSize;
    }

    /**
     * Sets the shadow map size.
     *
     * @param shadowMapSize
     *            the new shadow map size
     */
    public void setShadowMapSize(final int shadowMapSize) {
        _shadowMapSize = shadowMapSize;
        _reinitTextureSizeDirty = true;
        _reinitSplitsDirty = true;
    }

    /**
     * Gets the maximum distance for shadowing.
     *
     * @return max distance
     * @see com.ardor3d.extension.shadow.map.PSSMCamera#getMaxFarPlaneDistance()
     */
    public double getMaxShadowDistance() {
        return _pssmCam.getMaxFarPlaneDistance();
    }

    /**
     * Sets the maximum distance for shadowing.
     *
     * @param maxShadowDistance
     *            distance to set
     * @see com.ardor3d.extension.shadow.map.PSSMCamera#setMaxFarPlaneDistance(double)
     */
    public void setMaxShadowDistance(final double maxShadowDistance) {
        _pssmCam.setMaxFarPlaneDistance(maxShadowDistance);
    }

    /**
     * Gets the minimum z distance for the light.
     *
     * @return the minimumLightDistance
     */
    public double getMinimumLightDistance() {
        return _minimumLightDistance;
    }

    /**
     * Sets the minimum z distance for the light.
     *
     * @param minimumLightDistance
     *            the minimumLightDistance to set
     */
    public void setMinimumLightDistance(final double minimumLightDistance) {
        _minimumLightDistance = minimumLightDistance;
    }

    /**
     * Gets shadow color and transparency.
     *
     * @return the shadowColor
     */
    public ReadOnlyColorRGBA getShadowColor() {
        return _shadowColor;
    }

    /**
     * Sets shadow color and transparency.
     *
     * @param shadowColor
     *            the shadowColor to set
     */
    public void setShadowColor(final ReadOnlyColorRGBA shadowColor) {
        _shadowColor.set(shadowColor);
    }

    public ShadowRenderCallback getShadowRenderCallback() {
        return shadowRenderCallback;
    }

    public void setShadowRenderCallback(final ShadowRenderCallback callback) {
        shadowRenderCallback = callback;
    }

    /**
     * Clean up.
     *
     * @see com.ardor3d.renderer.pass.Pass#cleanUp()
     */
    @Override
    public void cleanUp() {
        super.cleanUp();

        if (_shadowMapRenderer != null) {
            _shadowMapRenderer.cleanup();
        }
    }

    /**
     * Remove the contents of the pass.
     */
    public void clear() {
        _occluderNodes.clear();
        _spatials.clear();
    }

    /**
     * Simple clamp.
     *
     * @param val
     *            value to clamp
     * @param from
     *            minimum value after clamping
     * @param to
     *            maximum value after clamping
     * @return Math.min(to, Math.max(from, val))
     */
    private double clamp(final double val, final double from, final double to) {
        return Math.min(to, Math.max(from, val));
    }

    /**
     * @return the updateMainCamera
     */
    public boolean isUpdateMainCamera() {
        return _updateMainCamera;
    }

    /**
     * @param updateMainCamera
     *            True (the default) if we want to copy the current rendering camera into this pass for use in shadow
     *            generation. False if we will manage our shadow camera elsewhere.
     * @see #getPssmCam()
     */
    public void setUpdateMainCamera(final boolean updateMainCamera) {
        _updateMainCamera = updateMainCamera;
    }

    /**
     * @return the drawDebug
     */
    public boolean isDrawDebug() {
        return _drawDebug;
    }

    /**
     * @param drawDebug
     *            True if we want to draw camera and light frustums for debugging purposes. Default is false.
     */
    public void setDrawDebug(final boolean drawDebug) {
        _drawDebug = drawDebug;
    }

    /**
     * @return the drawShaderDebug
     */
    public boolean isDrawShaderDebug() {
        return _drawShaderDebug;
    }

    /**
     * @param drawShaderDebug
     *            True if we want to draw debug colors over the shadows, showing which level they come from.
     */
    public void setDrawShaderDebug(final boolean drawShaderDebug) {
        _drawShaderDebug = drawShaderDebug;
    }

    /**
     * @return the useSceneTexturing
     */
    public boolean isUseSceneTexturing() {
        return _useSceneTexturing;
    }

    /**
     * @param useSceneTexturing
     *            True if we want to factor in texturing to shadows; useful for casting shadows against alpha-tested
     *            textures. Default is false.
     */
    public void setUseSceneTexturing(final boolean useSceneTexturing) {
        _useSceneTexturing = useSceneTexturing;
        if (_shadowMapRenderer != null) {
            if (!_useSceneTexturing) {
                _shadowMapRenderer.enforceState(_noTexture);
            } else {
                _shadowMapRenderer.clearEnforcedState(RenderState.StateType.Texture);
            }
        }

    }

    public boolean isUseObjectCullFace() {
        return _useObjectCullFace;
    }

    /**
     * @param useObjectCullFace
     *            True if we want to use the culling set on the objects instead of always culling front face (which is
     *            done for shadow precision). Default is false.
     */
    public void setUseObjectCullFace(final boolean useObjectCullFace) {
        _useObjectCullFace = useObjectCullFace;
        if (_shadowMapRenderer != null) {
            if (!_useObjectCullFace) {
                _shadowMapRenderer.enforceState(_cullFrontFace);
            } else {
                _shadowMapRenderer.clearEnforcedState(StateType.Cull);
            }
        }
    }

    public boolean isRenderShadowedScene() {
        return _renderShadowedScene;
    }

    /**
     * @param renderShadowedScene
     *            When true (the default) the generated shadow map textures are drawn over the scene in a separate blend
     *            pass. If false, the shadows are generated, but not applied.
     */
    public void setRenderShadowedScene(final boolean renderShadowedScene) {
        _renderShadowedScene = renderShadowedScene;
    }

    /**
     * @return the camera used internally to generate shadows.
     */
    public PSSMCamera getPssmCam() {
        return _pssmCam;
    }

    /**
     * @return the texture store format to use for the shadow textures on the next call to init/setNumOfSplits.
     */
    public TextureStoreFormat getShadowTextureStoreFormat() {
        return _shadowTextureStoreFormat;
    }

    /**
     * @param shadowTextureStoreFormat
     *            - the texture store format to use for the shadow textures. Only has an affect if called prior to
     *            calling init on this pass (or if called before calling {@link #setNumOfSplits(int)}).
     */
    public void setShadowTextureStoreFormat(final TextureStoreFormat shadowTextureStoreFormat) {
        _shadowTextureStoreFormat = shadowTextureStoreFormat;
    }

    /** The debug line frustum. */
    private static Line lineFrustum;

    /** The debug test cam. */
    private static final PSSMCamera testCam = new PSSMCamera();

    /** Debug bounding sphere */
    private static final Sphere boundingSphere = new Sphere("bsphere", 10, 10, 1);
    static {
        boundingSphere.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
        boundingSphere.setRenderState(new WireframeState());
        boundingSphere.setRenderState(new ZBufferState());
        boundingSphere.updateWorldRenderStates(false);
    }

    /**
     * Draw debug frustum.
     *
     * @param r
     *            the r
     * @param cam
     *            the cam
     * @param color
     *            the color
     * @param drawOriginConnector
     *            whether or not to draw a connector
     */
    private static void drawFrustum(final Renderer r, final Camera cam, final ReadOnlyColorRGBA color,
            final short pattern, final boolean drawOriginConnector) {
        drawFrustum(r, cam, cam.getFrustumNear(), cam.getFrustumFar(), color, pattern, drawOriginConnector);
    }

    /**
     * Draw debug frustum.
     *
     * @param r
     *            the r
     * @param cam
     *            the cam
     * @param fNear
     *            the f near
     * @param fFar
     *            the f far
     * @param color
     *            the color
     * @param drawOriginConnector
     *            whether or not to draw a connector
     */
    private static void drawFrustum(final Renderer r, final Camera cam, final double fNear, final double fFar,
            final ReadOnlyColorRGBA color, final short pattern, final boolean drawOriginConnector) {
        if (lineFrustum == null) {
            final FloatBuffer verts = BufferUtils.createVector3Buffer(24);
            final FloatBuffer colors = BufferUtils.createColorBuffer(24);

            lineFrustum = new Line("Lines", verts, null, colors, null);
            lineFrustum.getMeshData().setIndexModes(
                    new IndexMode[] { IndexMode.LineLoop, IndexMode.LineLoop, IndexMode.Lines, IndexMode.Lines });
            lineFrustum.getMeshData().setIndexLengths(new int[] { 4, 4, 8, 8 });
            lineFrustum.setLineWidth(2);
            lineFrustum.getSceneHints().setLightCombineMode(LightCombineMode.Off);

            final BlendState lineBlendState = new BlendState();
            lineBlendState.setEnabled(true);
            lineBlendState.setBlendEnabled(true);
            lineBlendState.setTestEnabled(true);
            lineBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
            lineBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
            lineFrustum.setRenderState(lineBlendState);

            final ZBufferState zstate = new ZBufferState();
            lineFrustum.setRenderState(zstate);
            lineFrustum.updateGeometricState(0.0);

            lineFrustum.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
        }

        lineFrustum.setDefaultColor(color);
        lineFrustum.setStipplePattern(pattern);

        testCam.set(cam);
        testCam.update();
        testCam.calculateFrustum(fNear, fFar);

        final FloatBuffer colors = lineFrustum.getMeshData().getColorBuffer();
        for (int i = 0; i < 16; i++) {
            BufferUtils.setInBuffer(color, colors, i);
        }
        final float alpha = drawOriginConnector ? 0.4f : 0.0f;
        for (int i = 16; i < 24; i++) {
            colors.position(i * 4);
            colors.put(color.getRed());
            colors.put(color.getGreen());
            colors.put(color.getBlue());
            colors.put(alpha);
        }

        final FloatBuffer verts = lineFrustum.getMeshData().getVertexBuffer();
        BufferUtils.setInBuffer(testCam._corners[0], verts, 0);
        BufferUtils.setInBuffer(testCam._corners[1], verts, 1);
        BufferUtils.setInBuffer(testCam._corners[2], verts, 2);
        BufferUtils.setInBuffer(testCam._corners[3], verts, 3);

        BufferUtils.setInBuffer(testCam._corners[4], verts, 4);
        BufferUtils.setInBuffer(testCam._corners[5], verts, 5);
        BufferUtils.setInBuffer(testCam._corners[6], verts, 6);
        BufferUtils.setInBuffer(testCam._corners[7], verts, 7);

        BufferUtils.setInBuffer(testCam._corners[0], verts, 8);
        BufferUtils.setInBuffer(testCam._corners[4], verts, 9);
        BufferUtils.setInBuffer(testCam._corners[1], verts, 10);
        BufferUtils.setInBuffer(testCam._corners[5], verts, 11);
        BufferUtils.setInBuffer(testCam._corners[2], verts, 12);
        BufferUtils.setInBuffer(testCam._corners[6], verts, 13);
        BufferUtils.setInBuffer(testCam._corners[3], verts, 14);
        BufferUtils.setInBuffer(testCam._corners[7], verts, 15);

        BufferUtils.setInBuffer(testCam.getLocation(), verts, 16);
        BufferUtils.setInBuffer(testCam._corners[0], verts, 17);
        BufferUtils.setInBuffer(testCam.getLocation(), verts, 18);
        BufferUtils.setInBuffer(testCam._corners[1], verts, 19);
        BufferUtils.setInBuffer(testCam.getLocation(), verts, 20);
        BufferUtils.setInBuffer(testCam._corners[2], verts, 21);
        BufferUtils.setInBuffer(testCam.getLocation(), verts, 22);
        BufferUtils.setInBuffer(testCam._corners[3], verts, 23);

        lineFrustum.draw(r);
    }

    public void setPssmShader(final GLSLShaderObjectsState pssmShader) {
        _pssmShader = pssmShader;
    }

    public boolean isKeepMainShader() {
        return _keepMainShader;
    }

    public void setKeepMainShader(final boolean keepMainShader) {
        _keepMainShader = keepMainShader;
    }

    public void addBoundsReceiver(final Spatial spatial) {
        _boundsReceiver.add(spatial);
    }

    public void setFiltering(final Filter filter) {
        this.filter = filter;
    }

    public BlendState getDiscardShadowFragmentsBlendState() {
        return _discardShadowFragments;
    }
}
TOP

Related Classes of com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.