diff options
author | mallanmba <[email protected]> | 2017-04-19 17:45:16 -0500 |
---|---|---|
committer | Joshua Slack <[email protected]> | 2017-04-19 17:45:16 -0500 |
commit | c4460a3ef2df44b353bb9164400c6bd0879523a5 (patch) | |
tree | bb0f42b95da48b94dd7bf57d40ce27759c5291e6 /ardor3d-examples/src | |
parent | 8ea0c83909327832f6ec575b752153b5ed6366fb (diff) |
Contributions from mallanmba allowing simpler adding of shadowcasters and resolving some shadow flickering.
Diffstat (limited to 'ardor3d-examples/src')
3 files changed, 3 insertions, 3 deletions
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/effect/ParallelSplitShadowMapExample.java b/ardor3d-examples/src/main/java/com/ardor3d/example/effect/ParallelSplitShadowMapExample.java index 5020f5c..57d703f 100644 --- a/ardor3d-examples/src/main/java/com/ardor3d/example/effect/ParallelSplitShadowMapExample.java +++ b/ardor3d-examples/src/main/java/com/ardor3d/example/effect/ParallelSplitShadowMapExample.java @@ -1 +1 @@ -/**
* 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.example.effect;
import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass;
import com.ardor3d.framework.Canvas;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture2D;
import com.ardor3d.input.Key;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.light.Light;
import com.ardor3d.light.PointLight;
import com.ardor3d.math.Matrix3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.pass.BasicPassManager;
import com.ardor3d.renderer.pass.RenderPass;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.BlendState;
import com.ardor3d.renderer.state.BlendState.TestFunction;
import com.ardor3d.renderer.state.CullState;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.scenegraph.controller.SpatialController;
import com.ardor3d.scenegraph.hint.CullHint;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.hint.TextureCombineMode;
import com.ardor3d.scenegraph.shape.Box;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.scenegraph.shape.Torus;
import com.ardor3d.scenegraph.visitor.UpdateModelBoundVisitor;
import com.ardor3d.ui.text.BasicText;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.TextureManager;
/**
* Example showing the parallel split shadow mapping technique. Requires GLSL support.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.effect.ParallelSplitShadowMapExample", //
thumbnailPath = "com/ardor3d/example/media/thumbnails/effect_ParallelSplitShadowMapExample.jpg", //
maxHeapMemory = 64)
public class ParallelSplitShadowMapExample extends ExampleBase {
/** Pssm shadow map pass. */
private ParallelSplitShadowMapPass _pssmPass;
/** Pass manager. */
private BasicPassManager _passManager;
/** Quads used for debug showing shadowmaps. */
private Quad _orthoQuad[];
/** Flag for turning on/off light movement. */
private boolean _updateLight = false;
/** Temp vec for updating light pos. */
private final Vector3 lightPosition = new Vector3(10000, 5000, 10000);
/** Text fields used to present info about the example. */
private final BasicText _exampleInfo[] = new BasicText[12];
/** Flag to make sure quads are updated on reinitialization of shadow renderer */
private boolean _quadsDirty = true;
/** Console fps output */
private double counter = 0;
private int frames = 0;
/**
* The main method.
*
* @param args
* the arguments
*/
public static void main(final String[] args) {
start(ParallelSplitShadowMapExample.class);
}
/**
* Update the PassManager and light.
*
* @param timer
* the application timer
*/
@Override
protected void updateExample(final ReadOnlyTimer timer) {
_passManager.updatePasses(timer.getTimePerFrame());
if (_updateLight) {
final double time = timer.getTimeInSeconds() * 0.2;
lightPosition.set(Math.sin(time) * 10000.0, 5000.0, Math.cos(time) * 10000.0);
}
counter += timer.getTimePerFrame();
frames++;
if (counter > 1) {
final double fps = (frames / counter);
counter = 0;
frames = 0;
System.out.printf("%7.1f FPS\n", fps);
}
}
/**
* Initialize pssm if needed. Update light position. Render scene.
*
* @param renderer
* the renderer
*/
@Override
protected void renderExample(final Renderer renderer) {
if (!_pssmPass.isInitialised()) {
_pssmPass.init(renderer);
}
updateQuadTextures(renderer);
// Update the shadowpass "light" position. Iow it's camera.
final Light light = _lightState.get(0);
if (light instanceof PointLight) {
((PointLight) light).setLocation(lightPosition);
} else if (light instanceof DirectionalLight) {
((DirectionalLight) light).setDirection(lightPosition.normalize(null).negateLocal());
}
_passManager.renderPasses(renderer);
}
/**
* Initialize pssm pass and scene.
*/
@Override
protected void initExample() {
// Setup main camera.
_canvas.setTitle("Parallel Split Shadow Maps - Example");
_canvas.getCanvasRenderer().getCamera().setLocation(new Vector3(250, 200, -250));
_canvas.getCanvasRenderer()
.getCamera()
.setFrustumPerspective(
45.0,
(float) _canvas.getCanvasRenderer().getCamera().getWidth()
/ (float) _canvas.getCanvasRenderer().getCamera().getHeight(), 1.0, 10000);
_canvas.getCanvasRenderer().getCamera().lookAt(new Vector3(0, 0, 0), Vector3.UNIT_Y);
_controlHandle.setMoveSpeed(200);
// Setup some standard states for the scene.
final CullState cullFrontFace = new CullState();
cullFrontFace.setEnabled(true);
cullFrontFace.setCullFace(CullState.Face.Back);
_root.setRenderState(cullFrontFace);
final TextureState ts = new TextureState();
ts.setEnabled(true);
ts.setTexture(TextureManager.load("images/ardor3d_white_256.jpg", Texture.MinificationFilter.Trilinear, true));
_root.setRenderState(ts);
final MaterialState ms = new MaterialState();
ms.setColorMaterial(ColorMaterial.Diffuse);
_root.setRenderState(ms);
_passManager = new BasicPassManager();
// setup some quads for debug viewing.
final RenderPass renderPass = new RenderPass();
final int quadSize = _canvas.getCanvasRenderer().getCamera().getWidth() / 10;
_orthoQuad = new Quad[ParallelSplitShadowMapPass._MAX_SPLITS];
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
_orthoQuad[i] = new Quad("OrthoQuad", quadSize, quadSize);
_orthoQuad[i].setTranslation(new Vector3((quadSize / 2 + 5) + (quadSize + 5) * i, (quadSize / 2 + 5), 1));
_orthoQuad[i].getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
_orthoQuad[i].getSceneHints().setLightCombineMode(LightCombineMode.Off);
_orthoQuad[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Never);
renderPass.add(_orthoQuad[i]);
}
// Create scene objects.
setupTerrain();
final RenderPass rootPass = new RenderPass();
rootPass.add(_root);
_lightState.detachAll();
final DirectionalLight light = new DirectionalLight();
// final PointLight light = new PointLight();
light.setEnabled(true);
_lightState.attach(light);
// Create pssm pass
_pssmPass = new ParallelSplitShadowMapPass(light, 1024, 3);
_pssmPass.add(_root);
_pssmPass.setUseSceneTexturing(true);
_pssmPass.setUseObjectCullFace(true);
final Node occluders = setupOccluders();
_pssmPass.addOccluder(occluders);
// Populate passmanager with passes.
_passManager.add(rootPass);
_passManager.add(_pssmPass);
_passManager.add(renderPass);
// Setup textfields for presenting example info.
final Node textNodes = new Node("Text");
renderPass.add(textNodes);
textNodes.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
textNodes.getSceneHints().setLightCombineMode(LightCombineMode.Off);
final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight();
for (int i = 0; i < _exampleInfo.length; i++) {
_exampleInfo[i] = BasicText.createDefaultTextLabel("Text", "", 16);
_exampleInfo[i].setTranslation(new Vector3(10, infoStartY - (i + 1) * 20, 0));
textNodes.attachChild(_exampleInfo[i]);
}
textNodes.updateGeometricState(0.0);
updateText();
// Register keyboard triggers for manipulating example
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ZERO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setDrawShaderDebug(!_pssmPass.isDrawShaderDebug());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_updateLight = !_updateLight;
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUpdateMainCamera(!_pssmPass.isUpdateMainCamera());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setDrawDebug(!_pssmPass.isDrawDebug());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getNumOfSplits() > ParallelSplitShadowMapPass._MIN_SPLITS) {
_pssmPass.setNumOfSplits(_pssmPass.getNumOfSplits() - 1);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FIVE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getNumOfSplits() < ParallelSplitShadowMapPass._MAX_SPLITS) {
_pssmPass.setNumOfSplits(_pssmPass.getNumOfSplits() + 1);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SIX), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getShadowMapSize() > 1) {
_pssmPass.setShadowMapSize(_pssmPass.getShadowMapSize() / 2);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SEVEN), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getShadowMapSize() < 2048) {
_pssmPass.setShadowMapSize(_pssmPass.getShadowMapSize() * 2);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.EIGHT), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
final double maxShadowDistance = _pssmPass.getMaxShadowDistance();
if (maxShadowDistance > 200.0) {
_pssmPass.setMaxShadowDistance(maxShadowDistance - 100.0);
updateText();
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.NINE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
final double maxShadowDistance = _pssmPass.getMaxShadowDistance();
_pssmPass.setMaxShadowDistance(maxShadowDistance + 100.0);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.U), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setNumOfSplits(1);
_pssmPass.setShadowMapSize(1024);
updateText();
_quadsDirty = true;
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.I), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setNumOfSplits(3);
_pssmPass.setShadowMapSize(512);
updateText();
_quadsDirty = true;
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.J), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUseSceneTexturing(!_pssmPass.isUseSceneTexturing());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.K), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUseObjectCullFace(!_pssmPass.isUseObjectCullFace());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setEnabled(!_pssmPass.isEnabled());
updateText();
_quadsDirty = true;
}
}));
// Make sure all boundings are updated.
_root.acceptVisitor(new UpdateModelBoundVisitor(), false);
}
/**
* Setup debug quads to render pssm shadowmaps.
*/
private void updateQuadTextures(final Renderer r) {
if (!_quadsDirty) {
return;
}
_quadsDirty = false;
_pssmPass.reinit(r);
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
final TextureState screen = new TextureState();
final Texture2D copy = new Texture2D();
copy.setTextureKey(_pssmPass.getShadowMapTexture(i).getTextureKey());
screen.setTexture(copy);
_orthoQuad[i].setRenderState(screen);
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Never);
_orthoQuad[i].updateGeometricState(0.0);
}
for (int i = _pssmPass.getNumOfSplits(); i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Always);
}
}
/**
* Update text information.
*/
private void updateText() {
_exampleInfo[0].setText("[0] Debug shader draw: " + _pssmPass.isDrawShaderDebug());
_exampleInfo[1].setText("[1] Update light: " + _updateLight);
_exampleInfo[2].setText("[2] Update main camera: " + _pssmPass.isUpdateMainCamera());
_exampleInfo[3].setText("[3] Debug draw: " + _pssmPass.isDrawDebug());
_exampleInfo[4].setText("[4/5] Number of splits: " + _pssmPass.getNumOfSplits());
_exampleInfo[5].setText("[6/7] Shadow map size: " + _pssmPass.getShadowMapSize());
_exampleInfo[6].setText("[8/9] Max shadow distance: " + _pssmPass.getMaxShadowDistance());
_exampleInfo[7].setText("[U] Setup 1 split of size 1024");
_exampleInfo[8].setText("[I] Setup 3 splits of size 512");
_exampleInfo[9].setText("[J] Use scene texturing: " + _pssmPass.isUseSceneTexturing());
_exampleInfo[10].setText("[K] Use object cull face: " + _pssmPass.isUseObjectCullFace());
_exampleInfo[11].setText("[SPACE] toggle PSSM pass: " + (_pssmPass.isEnabled() ? "enabled" : "disabled"));
}
/**
* Setup terrain.
*/
private void setupTerrain() {
final Box box = new Box("box", new Vector3(), 10000, 10, 10000);
box.setModelBound(new BoundingBox());
box.addController(new SpatialController<Box>() {
double timer = 0;
public void update(final double time, final Box caller) {
timer += time;
caller.setTranslation(Math.sin(timer) * 20.0, 0, Math.cos(timer) * 20.0);
}
});
_root.attachChild(box);
}
/**
* Setup occluders.
*/
private Node setupOccluders() {
final Node occluders = new Node("occs");
_root.attachChild(occluders);
for (int i = 0; i < 30; i++) {
final double w = Math.random() * 40 + 10;
final double y = Math.random() * 20 + 10;
final Box b = new Box("box", new Vector3(), w, y, w);
b.setModelBound(new BoundingBox());
final double x = Math.random() * 1000 - 500;
final double z = Math.random() * 1000 - 500;
b.setTranslation(new Vector3(x, y, z));
occluders.attachChild(b);
}
final Torus torusWithoutShadows = new Torus("torus", 32, 10, 15.0f, 20.0f);
torusWithoutShadows.setModelBound(new BoundingBox());
torusWithoutShadows.getSceneHints().setCastsShadows(false);
torusWithoutShadows.setTranslation(0, 50, -100);
occluders.attachChild(torusWithoutShadows);
final Torus torus = new Torus("torus", 64, 12, 10.0f, 15.0f);
torus.setModelBound(new BoundingBox());
occluders.attachChild(torus);
torus.addController(new SpatialController<Torus>() {
double timer = 0;
Matrix3 rotation = new Matrix3();
public void update(final double time, final Torus caller) {
timer += time;
caller.setTranslation(Math.sin(timer) * 40.0, Math.sin(timer) * 50.0 + 20.0, Math.cos(timer) * 40.0);
rotation.fromAngles(timer * 0.4, timer * 0.4, timer * 0.4);
caller.setRotation(rotation);
}
});
// Attach "billboard" with an alpha test.
occluders.attachChild(makeBillBoard());
return occluders;
}
private Spatial makeBillBoard() {
final Node billboard = new Node("bb");
billboard.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
final Quad q1 = new Quad("font block", 150, 200);
q1.setTranslation(0, 80, 0);
q1.setModelBound(new BoundingBox());
final CullState cs = new CullState();
cs.setCullFace(CullState.Face.None);
q1.setRenderState(cs);
billboard.attachChild(q1);
final TextureState ts = new TextureState();
ts.setEnabled(true);
ts.setTexture(TextureManager.load("fonts/OkasaSansSerif-35-medium-regular_00.png",
Texture.MinificationFilter.Trilinear, true));
billboard.setRenderState(ts);
final BlendState bs = new BlendState();
bs.setEnabled(true);
bs.setBlendEnabled(false);
bs.setTestEnabled(true);
bs.setTestFunction(TestFunction.GreaterThan);
bs.setReference(0.7f);
billboard.setRenderState(bs);
return billboard;
}
}
\ No newline at end of file +/**
* 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.example.effect;
import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass;
import com.ardor3d.extension.shadow.map.ShadowCasterManager;
import com.ardor3d.framework.Canvas;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture2D;
import com.ardor3d.input.Key;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.light.Light;
import com.ardor3d.light.PointLight;
import com.ardor3d.math.Matrix3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.pass.BasicPassManager;
import com.ardor3d.renderer.pass.RenderPass;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.BlendState;
import com.ardor3d.renderer.state.BlendState.TestFunction;
import com.ardor3d.renderer.state.CullState;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.scenegraph.controller.SpatialController;
import com.ardor3d.scenegraph.hint.CullHint;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.hint.TextureCombineMode;
import com.ardor3d.scenegraph.shape.Box;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.scenegraph.shape.Torus;
import com.ardor3d.scenegraph.visitor.UpdateModelBoundVisitor;
import com.ardor3d.ui.text.BasicText;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.TextureManager;
/**
* Example showing the parallel split shadow mapping technique. Requires GLSL support.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.effect.ParallelSplitShadowMapExample", //
thumbnailPath = "com/ardor3d/example/media/thumbnails/effect_ParallelSplitShadowMapExample.jpg", //
maxHeapMemory = 64)
public class ParallelSplitShadowMapExample extends ExampleBase {
/** Pssm shadow map pass. */
private ParallelSplitShadowMapPass _pssmPass;
/** Pass manager. */
private BasicPassManager _passManager;
/** Quads used for debug showing shadowmaps. */
private Quad _orthoQuad[];
/** Flag for turning on/off light movement. */
private boolean _updateLight = false;
/** Temp vec for updating light pos. */
private final Vector3 lightPosition = new Vector3(10000, 5000, 10000);
/** Text fields used to present info about the example. */
private final BasicText _exampleInfo[] = new BasicText[12];
/** Flag to make sure quads are updated on reinitialization of shadow renderer */
private boolean _quadsDirty = true;
/** Console fps output */
private double counter = 0;
private int frames = 0;
/**
* The main method.
*
* @param args
* the arguments
*/
public static void main(final String[] args) {
start(ParallelSplitShadowMapExample.class);
}
/**
* Update the PassManager and light.
*
* @param timer
* the application timer
*/
@Override
protected void updateExample(final ReadOnlyTimer timer) {
_passManager.updatePasses(timer.getTimePerFrame());
if (_updateLight) {
final double time = timer.getTimeInSeconds() * 0.2;
lightPosition.set(Math.sin(time) * 10000.0, 5000.0, Math.cos(time) * 10000.0);
}
counter += timer.getTimePerFrame();
frames++;
if (counter > 1) {
final double fps = (frames / counter);
counter = 0;
frames = 0;
System.out.printf("%7.1f FPS\n", fps);
}
}
/**
* Initialize pssm if needed. Update light position. Render scene.
*
* @param renderer
* the renderer
*/
@Override
protected void renderExample(final Renderer renderer) {
if (!_pssmPass.isInitialised()) {
_pssmPass.init(renderer);
}
updateQuadTextures(renderer);
// Update the shadowpass "light" position. Iow it's camera.
final Light light = _lightState.get(0);
if (light instanceof PointLight) {
((PointLight) light).setLocation(lightPosition);
} else if (light instanceof DirectionalLight) {
((DirectionalLight) light).setDirection(lightPosition.normalize(null).negateLocal());
}
_passManager.renderPasses(renderer);
}
/**
* Initialize pssm pass and scene.
*/
@Override
protected void initExample() {
// Setup main camera.
_canvas.setTitle("Parallel Split Shadow Maps - Example");
_canvas.getCanvasRenderer().getCamera().setLocation(new Vector3(250, 200, -250));
_canvas.getCanvasRenderer()
.getCamera()
.setFrustumPerspective(
45.0,
(float) _canvas.getCanvasRenderer().getCamera().getWidth()
/ (float) _canvas.getCanvasRenderer().getCamera().getHeight(), 1.0, 10000);
_canvas.getCanvasRenderer().getCamera().lookAt(new Vector3(0, 0, 0), Vector3.UNIT_Y);
_controlHandle.setMoveSpeed(200);
// Setup some standard states for the scene.
final CullState cullFrontFace = new CullState();
cullFrontFace.setEnabled(true);
cullFrontFace.setCullFace(CullState.Face.Back);
_root.setRenderState(cullFrontFace);
final TextureState ts = new TextureState();
ts.setEnabled(true);
ts.setTexture(TextureManager.load("images/ardor3d_white_256.jpg", Texture.MinificationFilter.Trilinear, true));
_root.setRenderState(ts);
final MaterialState ms = new MaterialState();
ms.setColorMaterial(ColorMaterial.Diffuse);
_root.setRenderState(ms);
_passManager = new BasicPassManager();
// setup some quads for debug viewing.
final RenderPass renderPass = new RenderPass();
final int quadSize = _canvas.getCanvasRenderer().getCamera().getWidth() / 10;
_orthoQuad = new Quad[ParallelSplitShadowMapPass._MAX_SPLITS];
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
_orthoQuad[i] = new Quad("OrthoQuad", quadSize, quadSize);
_orthoQuad[i].setTranslation(new Vector3((quadSize / 2 + 5) + (quadSize + 5) * i, (quadSize / 2 + 5), 1));
_orthoQuad[i].getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
_orthoQuad[i].getSceneHints().setLightCombineMode(LightCombineMode.Off);
_orthoQuad[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Never);
renderPass.add(_orthoQuad[i]);
}
// Create scene objects.
setupTerrain();
final RenderPass rootPass = new RenderPass();
rootPass.add(_root);
_lightState.detachAll();
final DirectionalLight light = new DirectionalLight();
// final PointLight light = new PointLight();
light.setEnabled(true);
_lightState.attach(light);
// Create pssm pass
_pssmPass = new ParallelSplitShadowMapPass(light, 1024, 3);
_pssmPass.add(_root);
_pssmPass.setUseSceneTexturing(true);
_pssmPass.setUseObjectCullFace(true);
final Node occluders = setupOccluders();
ShadowCasterManager.INSTANCE.addSpatial(occluders);
// Populate passmanager with passes.
_passManager.add(rootPass);
_passManager.add(_pssmPass);
_passManager.add(renderPass);
// Setup textfields for presenting example info.
final Node textNodes = new Node("Text");
renderPass.add(textNodes);
textNodes.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
textNodes.getSceneHints().setLightCombineMode(LightCombineMode.Off);
final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight();
for (int i = 0; i < _exampleInfo.length; i++) {
_exampleInfo[i] = BasicText.createDefaultTextLabel("Text", "", 16);
_exampleInfo[i].setTranslation(new Vector3(10, infoStartY - (i + 1) * 20, 0));
textNodes.attachChild(_exampleInfo[i]);
}
textNodes.updateGeometricState(0.0);
updateText();
// Register keyboard triggers for manipulating example
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ZERO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setDrawShaderDebug(!_pssmPass.isDrawShaderDebug());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_updateLight = !_updateLight;
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUpdateMainCamera(!_pssmPass.isUpdateMainCamera());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setDrawDebug(!_pssmPass.isDrawDebug());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getNumOfSplits() > ParallelSplitShadowMapPass._MIN_SPLITS) {
_pssmPass.setNumOfSplits(_pssmPass.getNumOfSplits() - 1);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FIVE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getNumOfSplits() < ParallelSplitShadowMapPass._MAX_SPLITS) {
_pssmPass.setNumOfSplits(_pssmPass.getNumOfSplits() + 1);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SIX), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getShadowMapSize() > 1) {
_pssmPass.setShadowMapSize(_pssmPass.getShadowMapSize() / 2);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SEVEN), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (_pssmPass.getShadowMapSize() < 2048) {
_pssmPass.setShadowMapSize(_pssmPass.getShadowMapSize() * 2);
updateText();
_quadsDirty = true;
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.EIGHT), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
final double maxShadowDistance = _pssmPass.getMaxShadowDistance();
if (maxShadowDistance > 200.0) {
_pssmPass.setMaxShadowDistance(maxShadowDistance - 100.0);
updateText();
}
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.NINE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
final double maxShadowDistance = _pssmPass.getMaxShadowDistance();
_pssmPass.setMaxShadowDistance(maxShadowDistance + 100.0);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.U), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setNumOfSplits(1);
_pssmPass.setShadowMapSize(1024);
updateText();
_quadsDirty = true;
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.I), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setNumOfSplits(3);
_pssmPass.setShadowMapSize(512);
updateText();
_quadsDirty = true;
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.J), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUseSceneTexturing(!_pssmPass.isUseSceneTexturing());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.K), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUseObjectCullFace(!_pssmPass.isUseObjectCullFace());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setEnabled(!_pssmPass.isEnabled());
updateText();
_quadsDirty = true;
}
}));
// Make sure all boundings are updated.
_root.acceptVisitor(new UpdateModelBoundVisitor(), false);
}
/**
* Setup debug quads to render pssm shadowmaps.
*/
private void updateQuadTextures(final Renderer r) {
if (!_quadsDirty) {
return;
}
_quadsDirty = false;
_pssmPass.reinit(r);
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
final TextureState screen = new TextureState();
final Texture2D copy = new Texture2D();
copy.setTextureKey(_pssmPass.getShadowMapTexture(i).getTextureKey());
screen.setTexture(copy);
_orthoQuad[i].setRenderState(screen);
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Never);
_orthoQuad[i].updateGeometricState(0.0);
}
for (int i = _pssmPass.getNumOfSplits(); i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Always);
}
}
/**
* Update text information.
*/
private void updateText() {
_exampleInfo[0].setText("[0] Debug shader draw: " + _pssmPass.isDrawShaderDebug());
_exampleInfo[1].setText("[1] Update light: " + _updateLight);
_exampleInfo[2].setText("[2] Update main camera: " + _pssmPass.isUpdateMainCamera());
_exampleInfo[3].setText("[3] Debug draw: " + _pssmPass.isDrawDebug());
_exampleInfo[4].setText("[4/5] Number of splits: " + _pssmPass.getNumOfSplits());
_exampleInfo[5].setText("[6/7] Shadow map size: " + _pssmPass.getShadowMapSize());
_exampleInfo[6].setText("[8/9] Max shadow distance: " + _pssmPass.getMaxShadowDistance());
_exampleInfo[7].setText("[U] Setup 1 split of size 1024");
_exampleInfo[8].setText("[I] Setup 3 splits of size 512");
_exampleInfo[9].setText("[J] Use scene texturing: " + _pssmPass.isUseSceneTexturing());
_exampleInfo[10].setText("[K] Use object cull face: " + _pssmPass.isUseObjectCullFace());
_exampleInfo[11].setText("[SPACE] toggle PSSM pass: " + (_pssmPass.isEnabled() ? "enabled" : "disabled"));
}
/**
* Setup terrain.
*/
private void setupTerrain() {
final Box box = new Box("box", new Vector3(), 10000, 10, 10000);
box.setModelBound(new BoundingBox());
box.addController(new SpatialController<Box>() {
double timer = 0;
public void update(final double time, final Box caller) {
timer += time;
caller.setTranslation(Math.sin(timer) * 20.0, 0, Math.cos(timer) * 20.0);
}
});
_root.attachChild(box);
}
/**
* Setup occluders.
*/
private Node setupOccluders() {
final Node occluders = new Node("occs");
_root.attachChild(occluders);
for (int i = 0; i < 30; i++) {
final double w = Math.random() * 40 + 10;
final double y = Math.random() * 20 + 10;
final Box b = new Box("box", new Vector3(), w, y, w);
b.setModelBound(new BoundingBox());
final double x = Math.random() * 1000 - 500;
final double z = Math.random() * 1000 - 500;
b.setTranslation(new Vector3(x, y, z));
occluders.attachChild(b);
}
final Torus torusWithoutShadows = new Torus("torus", 32, 10, 15.0f, 20.0f);
torusWithoutShadows.setModelBound(new BoundingBox());
torusWithoutShadows.getSceneHints().setCastsShadows(false);
torusWithoutShadows.setTranslation(0, 50, -100);
occluders.attachChild(torusWithoutShadows);
final Torus torus = new Torus("torus", 64, 12, 10.0f, 15.0f);
torus.setModelBound(new BoundingBox());
occluders.attachChild(torus);
torus.addController(new SpatialController<Torus>() {
double timer = 0;
Matrix3 rotation = new Matrix3();
public void update(final double time, final Torus caller) {
timer += time;
caller.setTranslation(Math.sin(timer) * 40.0, Math.sin(timer) * 50.0 + 20.0, Math.cos(timer) * 40.0);
rotation.fromAngles(timer * 0.4, timer * 0.4, timer * 0.4);
caller.setRotation(rotation);
}
});
// Attach "billboard" with an alpha test.
occluders.attachChild(makeBillBoard());
return occluders;
}
private Spatial makeBillBoard() {
final Node billboard = new Node("bb");
billboard.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
final Quad q1 = new Quad("font block", 150, 200);
q1.setTranslation(0, 80, 0);
q1.setModelBound(new BoundingBox());
final CullState cs = new CullState();
cs.setCullFace(CullState.Face.None);
q1.setRenderState(cs);
billboard.attachChild(q1);
final TextureState ts = new TextureState();
ts.setEnabled(true);
ts.setTexture(TextureManager.load("fonts/OkasaSansSerif-35-medium-regular_00.png",
Texture.MinificationFilter.Trilinear, true));
billboard.setRenderState(ts);
final BlendState bs = new BlendState();
bs.setEnabled(true);
bs.setBlendEnabled(false);
bs.setTestEnabled(true);
bs.setTestFunction(TestFunction.GreaterThan);
bs.setReference(0.7f);
billboard.setRenderState(bs);
return billboard;
}
}
\ No newline at end of file diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/MountainShadowTerrainExample.java b/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/MountainShadowTerrainExample.java index a699d74..d25912d 100644 --- a/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/MountainShadowTerrainExample.java +++ b/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/MountainShadowTerrainExample.java @@ -1 +1 @@ -/**
* 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.example.terrain;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.Callable;
import javax.imageio.ImageIO;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.model.collada.jdom.ColladaImporter;
import com.ardor3d.extension.model.collada.jdom.data.ColladaStorage;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass.Filter;
import com.ardor3d.extension.terrain.client.Terrain;
import com.ardor3d.extension.terrain.client.TerrainBuilder;
import com.ardor3d.extension.terrain.client.UrlInputSupplier;
import com.ardor3d.extension.terrain.heightmap.ImageHeightMap;
import com.ardor3d.extension.terrain.providers.array.ArrayTerrainDataProvider;
import com.ardor3d.extension.ui.Orientation;
import com.ardor3d.extension.ui.UIButton;
import com.ardor3d.extension.ui.UIFrame;
import com.ardor3d.extension.ui.UIFrame.FrameButtons;
import com.ardor3d.extension.ui.UIHud;
import com.ardor3d.extension.ui.UILabel;
import com.ardor3d.extension.ui.UIPanel;
import com.ardor3d.extension.ui.UISlider;
import com.ardor3d.extension.ui.event.ActionEvent;
import com.ardor3d.extension.ui.event.ActionListener;
import com.ardor3d.extension.ui.layout.RowLayout;
import com.ardor3d.extension.ui.text.StyleConstants;
import com.ardor3d.extension.ui.util.Insets;
import com.ardor3d.framework.Canvas;
import com.ardor3d.framework.CanvasRenderer;
import com.ardor3d.image.Image;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture2D;
import com.ardor3d.image.util.awt.AWTImageLoader;
import com.ardor3d.input.Key;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Quaternion;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.FogState;
import com.ardor3d.renderer.state.FogState.DensityFunction;
import com.ardor3d.renderer.state.RenderState.StateType;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.renderer.state.ZBufferState;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.hint.CullHint;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.hint.TextureCombineMode;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.util.GameTaskQueue;
import com.ardor3d.util.GameTaskQueueManager;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.geom.Debugger;
import com.ardor3d.util.resource.ResourceLocatorTool;
/**
* Example showing the Geometry Clipmap Terrain system with 'MegaTextures' where the terrain data is provided from a
* float array populated from a heightmap generated from an Image. Requires GLSL support.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.terrain.ImageMapTerrainExample", //
thumbnailPath = "com/ardor3d/example/media/thumbnails/terrain_ImageMapTerrainExample.jpg", //
maxHeapMemory = 128)
public class MountainShadowTerrainExample extends ExampleBase {
private final float farPlane = 8000.0f;
/** Quads used for debug showing shadowmaps. */
private Quad _orthoQuad[];
private Terrain terrain;
private final Node terrainNode = new Node("terrain");
private boolean groundCamera = false;
private Camera terrainCamera;
/** Text fields used to present info about the example. */
private final UILabel _exampleInfo[] = new UILabel[2];
/** Pssm shadow map pass. */
private ParallelSplitShadowMapPass _pssmPass;
private DirectionalLight light;
private double lightTime;
private boolean moveLight = false;
private UIHud hud;
public static void main(final String[] args) {
ExampleBase._minDepthBits = 24;
ExampleBase.start(MountainShadowTerrainExample.class);
}
@Override
protected void renderExample(final Renderer renderer) {
// Lazy init since it needs the renderer...
if (!_pssmPass.isInitialised()) {
_pssmPass.init(renderer);
_pssmPass.setPssmShader(terrain.getGeometryClipmapShader());
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
terrain.getClipTextureState().setTexture(_pssmPass.getShadowMapTexture(i), i + 1);
}
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
terrain.getGeometryClipmapShader().setUniform("shadowMap" + i, i + 1);
}
}
terrain.getGeometryClipmapShader().setUniform("lightDir", light.getDirection());
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
TextureState screen = (TextureState) _orthoQuad[i].getLocalRenderState(StateType.Texture);
Texture copy;
if (screen == null) {
screen = new TextureState();
_orthoQuad[i].setRenderState(screen);
copy = new Texture2D();
screen.setTexture(copy);
_orthoQuad[i].updateGeometricState(0.0);
} else {
copy = screen.getTexture();
}
copy.setTextureKey(_pssmPass.getShadowMapTexture(i).getTextureKey());
}
// XXX: Use a rougher LOD for shadows - tweak?
terrain.setMinVisibleLevel(4);
// Update shadowmaps - this will update our terrain camera to light pos
_pssmPass.updateShadowMaps(renderer);
// XXX: reset LOD for drawing from view camera
terrain.setMinVisibleLevel(0);
// Render scene and terrain with shadows
terrainNode.onDraw(renderer);
_root.onDraw(renderer);
// Render overlay shadows for all objects except the terrain
renderer.renderBuckets();
_pssmPass.renderShadowedScene(renderer);
renderer.renderBuckets();
// draw ui
renderer.draw(hud);
}
private double counter = 0;
private int frames = 0;
@Override
protected void updateExample(final ReadOnlyTimer timer) {
counter += timer.getTimePerFrame();
frames++;
if (counter > 1) {
final double fps = frames / counter;
counter = 0;
frames = 0;
System.out.printf("%7.1f FPS\n", fps);
}
final Camera camera = _canvas.getCanvasRenderer().getCamera();
// Make sure camera is above terrain
final double height = terrain.getHeightAt(camera.getLocation().getX(), camera.getLocation().getZ());
if (height > -Float.MAX_VALUE && (groundCamera || camera.getLocation().getY() < height + 3)) {
camera.setLocation(new Vector3(camera.getLocation().getX(), height + 3, camera.getLocation().getZ()));
terrainCamera.set(camera);
} else {
terrainCamera.set(_canvas.getCanvasRenderer().getCamera());
}
// move terrain to view pos
terrainNode.updateGeometricState(timer.getTimePerFrame());
hud.updateGeometricState(timer.getTimePerFrame());
if (moveLight) {
lightTime += timer.getTimePerFrame();
light.setDirection(new Vector3(Math.sin(lightTime), -.8, Math.cos(lightTime)).normalizeLocal());
}
}
/**
* Initialize pssm pass and scene.
*/
@Override
protected void initExample() {
// Setup main camera.
_canvas.setTitle("Terrain Example");
final Camera canvasCamera = _canvas.getCanvasRenderer().getCamera();
canvasCamera.setLocation(new Vector3(2176, 790, 688));
canvasCamera.lookAt(new Vector3(canvasCamera.getLocation()).addLocal(-0.87105768019686, -0.4349655341112313,
0.22817427967541867), Vector3.UNIT_Y);
canvasCamera.setFrustumPerspective(45.0, (float) _canvas.getCanvasRenderer().getCamera().getWidth()
/ _canvas.getCanvasRenderer().getCamera().getHeight(), 1.0f, farPlane);
final CanvasRenderer canvasRenderer = _canvas.getCanvasRenderer();
final RenderContext renderContext = canvasRenderer.getRenderContext();
final Renderer renderer = canvasRenderer.getRenderer();
GameTaskQueueManager.getManager(renderContext).getQueue(GameTaskQueue.RENDER).enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
renderer.setBackgroundColor(ColorRGBA.BLUE);
return null;
}
});
_controlHandle.setMoveSpeed(400);
setupDefaultStates();
addRover();
addUI();
// Initialize PSSM shadows
_pssmPass = new ParallelSplitShadowMapPass(light, 2048, 4);
_pssmPass.setFiltering(Filter.None);
_pssmPass.setRenderShadowedScene(false);
_pssmPass.setKeepMainShader(true);
// _pssmPass.setMinimumLightDistance(500); // XXX: Tune this
_pssmPass.setUseSceneTexturing(false);
_pssmPass.setUseObjectCullFace(false);
_pssmPass.getShadowOffsetState().setFactor(1.1f);
_pssmPass.getShadowOffsetState().setUnits(4.0f);
// _pssmPass.setDrawDebug(true);
// TODO: backside lock test
final Quad floor = new Quad("floor", 2048, 2048);
floor.updateModelBound();
floor.setRotation(new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X));
floor.setTranslation(1024, 0, 1024);
terrainNode.attachChild(floor);
_pssmPass.addBoundsReceiver(terrainNode);
// Add objects that will get shadowed through overlay render
_pssmPass.add(_root);
// Add our occluders that will produce shadows
_pssmPass.addOccluder(terrainNode);
_pssmPass.addOccluder(_root);
final int quadSize = _canvas.getCanvasRenderer().getCamera().getWidth() / 10;
_orthoQuad = new Quad[ParallelSplitShadowMapPass._MAX_SPLITS];
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
_orthoQuad[i] = new Quad("OrthoQuad", quadSize, quadSize);
_orthoQuad[i].setTranslation(new Vector3(quadSize / 2 + 5 + (quadSize + 5) * i, quadSize / 2 + 5, 1));
_orthoQuad[i].setScale(1, -1, 1);
_orthoQuad[i].getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
_orthoQuad[i].getSceneHints().setLightCombineMode(LightCombineMode.Off);
_orthoQuad[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Never);
hud.attachChild(_orthoQuad[i]);
}
try {
// Keep a separate camera to be able to freeze terrain update
final Camera camera = _canvas.getCanvasRenderer().getCamera();
terrainCamera = new Camera(camera);
// IMAGE LOADING AND CONVERSION TO HEIGHTMAP DONE HERE
final BufferedImage heightmap = ImageIO.read(ResourceLocatorTool.getClassPathResource(
MountainShadowTerrainExample.class, "com/ardor3d/example/media/images/heightmap.jpg"));
final Image ardorImage = AWTImageLoader.makeArdor3dImage(heightmap, false);
final float[] heightMap = ImageHeightMap.generateHeightMap(ardorImage, 0.05f, .33f);
// END OF IMAGE CONVERSION
final int SIZE = ardorImage.getWidth();
final ArrayTerrainDataProvider terrainDataProvider = new ArrayTerrainDataProvider(heightMap, SIZE,
new Vector3(5, 2048, 5), true);
terrainDataProvider.setHeightMax(0.34f);
final TerrainBuilder builder = new TerrainBuilder(terrainDataProvider, terrainCamera)
.setShowDebugPanels(true);
terrain = builder.build();
terrain.setPixelShader(new UrlInputSupplier(ResourceLocatorTool.getClassPathResource(
ShadowedTerrainExample.class,
"com/ardor3d/extension/terrain/shadowedGeometryClipmapShader_normalMap.frag")));
terrain.reloadShader();
terrain.getGeometryClipmapShader().setUniform("normalMap", 5);
terrainNode.attachChild(terrain);
terrain.setCullingEnabled(false);
} catch (final Exception ex1) {
System.out.println("Problem setting up terrain...");
ex1.printStackTrace();
}
final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight() / 2;
for (int i = 0; i < _exampleInfo.length; i++) {
_exampleInfo[i] = new UILabel("Text");
_exampleInfo[i].setForegroundColor(ColorRGBA.WHITE, true);
_exampleInfo[i].addFontStyle(StyleConstants.KEY_SIZE, 16);
_exampleInfo[i].addFontStyle(StyleConstants.KEY_BOLD, Boolean.TRUE);
_exampleInfo[i].setTranslation(new Vector3(10, infoStartY - i * 20, 0));
hud.add(_exampleInfo[i]);
}
updateText();
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(5);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(50);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(400);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(1000);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
groundCamera = !groundCamera;
updateText();
}
}));
}
private void addRover() {
try {
final ColladaStorage storage = new ColladaImporter().load("collada/sketchup/NASA Mars Rover.dae");
final Node rover = storage.getScene();
rover.setTranslation(440, 102, 160.1);
rover.setScale(3);
rover.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_X));
_root.attachChild(rover);
} catch (final IOException ex) {
ex.printStackTrace();
}
}
private void setupDefaultStates() {
terrainNode.setRenderState(_lightState);
terrainNode.setRenderState(_wireframeState);
terrainNode.setRenderState(new ZBufferState());
_lightState.detachAll();
light = new DirectionalLight();
light.setEnabled(true);
light.setAmbient(new ColorRGBA(0.4f, 0.4f, 0.5f, 1));
light.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.5f, 1));
light.setSpecular(new ColorRGBA(0.3f, 0.3f, 0.2f, 1));
light.setDirection(new Vector3(-1, -1, -1).normalizeLocal());
_lightState.attach(light);
_lightState.setEnabled(true);
final FogState fs = new FogState();
fs.setStart(farPlane / 2.0f);
fs.setEnd(farPlane);
fs.setColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
fs.setDensityFunction(DensityFunction.Linear);
terrainNode.setRenderState(fs);
}
/**
* Update text information.
*/
private void updateText() {
_exampleInfo[0].setText("[1/2/3/4] Moving speed: " + _controlHandle.getMoveSpeed() * 3.6 + " km/h");
_exampleInfo[1].setText("[SPACE] Toggle fly/walk: " + (groundCamera ? "walk" : "fly"));
}
@Override
protected void updateLogicalLayer(final ReadOnlyTimer timer) {
hud.getLogicalLayer().checkTriggers(timer.getTimePerFrame());
}
@Override
protected void renderDebug(final Renderer renderer) {
super.renderDebug(renderer);
if (_showBounds) {
Debugger.drawBounds(terrainNode, renderer, true);
}
}
private void addUI() {
// setup hud
hud = new UIHud();
hud.setupInput(_canvas, _physicalLayer, _logicalLayer);
hud.setMouseManager(_mouseManager);
final UIFrame frame = new UIFrame("Controls", EnumSet.noneOf(FrameButtons.class));
frame.setResizeable(false);
final UILabel distLabel = new UILabel("Max Shadow Distance: 1500");
final UISlider distSlider = new UISlider(Orientation.Horizontal, 0, 2000, 1500);
distSlider.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
_pssmPass.setMaxShadowDistance(distSlider.getValue());
distLabel.setText("Max Shadow Distance: " + distSlider.getValue());
}
});
final UIButton updateCamera = new UIButton("Update Shadow Camera");
updateCamera.setSelectable(true);
updateCamera.setSelected(true);
updateCamera.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
_pssmPass.setUpdateMainCamera(updateCamera.isSelected());
updateText();
}
});
final UIButton rotateLight = new UIButton("Rotate Light");
rotateLight.setSelectable(true);
rotateLight.setSelected(false);
rotateLight.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
moveLight = rotateLight.isSelected();
updateText();
}
});
final UIPanel panel = new UIPanel(new RowLayout(false, true, false));
panel.setPadding(new Insets(10, 20, 10, 20));
panel.add(distLabel);
panel.add(distSlider);
panel.add(updateCamera);
panel.add(rotateLight);
frame.setContentPanel(panel);
frame.pack();
final Camera cam = _canvas.getCanvasRenderer().getCamera();
frame.setLocalXY(cam.getWidth() - frame.getLocalComponentWidth(),
cam.getHeight() - frame.getLocalComponentHeight());
hud.add(frame);
}
}
\ No newline at end of file +/**
* 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.example.terrain;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.Callable;
import javax.imageio.ImageIO;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.model.collada.jdom.ColladaImporter;
import com.ardor3d.extension.model.collada.jdom.data.ColladaStorage;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass.Filter;
import com.ardor3d.extension.shadow.map.ShadowCasterManager;
import com.ardor3d.extension.terrain.client.Terrain;
import com.ardor3d.extension.terrain.client.TerrainBuilder;
import com.ardor3d.extension.terrain.client.UrlInputSupplier;
import com.ardor3d.extension.terrain.heightmap.ImageHeightMap;
import com.ardor3d.extension.terrain.providers.array.ArrayTerrainDataProvider;
import com.ardor3d.extension.ui.Orientation;
import com.ardor3d.extension.ui.UIButton;
import com.ardor3d.extension.ui.UIFrame;
import com.ardor3d.extension.ui.UIFrame.FrameButtons;
import com.ardor3d.extension.ui.UIHud;
import com.ardor3d.extension.ui.UILabel;
import com.ardor3d.extension.ui.UIPanel;
import com.ardor3d.extension.ui.UISlider;
import com.ardor3d.extension.ui.event.ActionEvent;
import com.ardor3d.extension.ui.event.ActionListener;
import com.ardor3d.extension.ui.layout.RowLayout;
import com.ardor3d.extension.ui.text.StyleConstants;
import com.ardor3d.extension.ui.util.Insets;
import com.ardor3d.framework.Canvas;
import com.ardor3d.framework.CanvasRenderer;
import com.ardor3d.image.Image;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture2D;
import com.ardor3d.image.util.awt.AWTImageLoader;
import com.ardor3d.input.Key;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Quaternion;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.FogState;
import com.ardor3d.renderer.state.FogState.DensityFunction;
import com.ardor3d.renderer.state.RenderState.StateType;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.renderer.state.ZBufferState;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.hint.CullHint;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.hint.TextureCombineMode;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.util.GameTaskQueue;
import com.ardor3d.util.GameTaskQueueManager;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.geom.Debugger;
import com.ardor3d.util.resource.ResourceLocatorTool;
/**
* Example showing the Geometry Clipmap Terrain system with 'MegaTextures' where the terrain data is provided from a
* float array populated from a heightmap generated from an Image. Requires GLSL support.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.terrain.ImageMapTerrainExample", //
thumbnailPath = "com/ardor3d/example/media/thumbnails/terrain_ImageMapTerrainExample.jpg", //
maxHeapMemory = 128)
public class MountainShadowTerrainExample extends ExampleBase {
private final float farPlane = 8000.0f;
/** Quads used for debug showing shadowmaps. */
private Quad _orthoQuad[];
private Terrain terrain;
private final Node terrainNode = new Node("terrain");
private boolean groundCamera = false;
private Camera terrainCamera;
/** Text fields used to present info about the example. */
private final UILabel _exampleInfo[] = new UILabel[2];
/** Pssm shadow map pass. */
private ParallelSplitShadowMapPass _pssmPass;
private DirectionalLight light;
private double lightTime;
private boolean moveLight = false;
private UIHud hud;
public static void main(final String[] args) {
ExampleBase._minDepthBits = 24;
ExampleBase.start(MountainShadowTerrainExample.class);
}
@Override
protected void renderExample(final Renderer renderer) {
// Lazy init since it needs the renderer...
if (!_pssmPass.isInitialised()) {
_pssmPass.init(renderer);
_pssmPass.setPssmShader(terrain.getGeometryClipmapShader());
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
terrain.getClipTextureState().setTexture(_pssmPass.getShadowMapTexture(i), i + 1);
}
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
terrain.getGeometryClipmapShader().setUniform("shadowMap" + i, i + 1);
}
}
terrain.getGeometryClipmapShader().setUniform("lightDir", light.getDirection());
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
TextureState screen = (TextureState) _orthoQuad[i].getLocalRenderState(StateType.Texture);
Texture copy;
if (screen == null) {
screen = new TextureState();
_orthoQuad[i].setRenderState(screen);
copy = new Texture2D();
screen.setTexture(copy);
_orthoQuad[i].updateGeometricState(0.0);
} else {
copy = screen.getTexture();
}
copy.setTextureKey(_pssmPass.getShadowMapTexture(i).getTextureKey());
}
// XXX: Use a rougher LOD for shadows - tweak?
terrain.setMinVisibleLevel(4);
// Update shadowmaps - this will update our terrain camera to light pos
_pssmPass.updateShadowMaps(renderer);
// XXX: reset LOD for drawing from view camera
terrain.setMinVisibleLevel(0);
// Render scene and terrain with shadows
terrainNode.onDraw(renderer);
_root.onDraw(renderer);
// Render overlay shadows for all objects except the terrain
renderer.renderBuckets();
_pssmPass.renderShadowedScene(renderer);
renderer.renderBuckets();
// draw ui
renderer.draw(hud);
}
private double counter = 0;
private int frames = 0;
@Override
protected void updateExample(final ReadOnlyTimer timer) {
counter += timer.getTimePerFrame();
frames++;
if (counter > 1) {
final double fps = frames / counter;
counter = 0;
frames = 0;
System.out.printf("%7.1f FPS\n", fps);
}
final Camera camera = _canvas.getCanvasRenderer().getCamera();
// Make sure camera is above terrain
final double height = terrain.getHeightAt(camera.getLocation().getX(), camera.getLocation().getZ());
if (height > -Float.MAX_VALUE && (groundCamera || camera.getLocation().getY() < height + 3)) {
camera.setLocation(new Vector3(camera.getLocation().getX(), height + 3, camera.getLocation().getZ()));
terrainCamera.set(camera);
} else {
terrainCamera.set(_canvas.getCanvasRenderer().getCamera());
}
// move terrain to view pos
terrainNode.updateGeometricState(timer.getTimePerFrame());
hud.updateGeometricState(timer.getTimePerFrame());
if (moveLight) {
lightTime += timer.getTimePerFrame();
light.setDirection(new Vector3(Math.sin(lightTime), -.8, Math.cos(lightTime)).normalizeLocal());
}
}
/**
* Initialize pssm pass and scene.
*/
@Override
protected void initExample() {
// Setup main camera.
_canvas.setTitle("Terrain Example");
final Camera canvasCamera = _canvas.getCanvasRenderer().getCamera();
canvasCamera.setLocation(new Vector3(2176, 790, 688));
canvasCamera.lookAt(new Vector3(canvasCamera.getLocation()).addLocal(-0.87105768019686, -0.4349655341112313,
0.22817427967541867), Vector3.UNIT_Y);
canvasCamera.setFrustumPerspective(45.0, (float) _canvas.getCanvasRenderer().getCamera().getWidth()
/ _canvas.getCanvasRenderer().getCamera().getHeight(), 1.0f, farPlane);
final CanvasRenderer canvasRenderer = _canvas.getCanvasRenderer();
final RenderContext renderContext = canvasRenderer.getRenderContext();
final Renderer renderer = canvasRenderer.getRenderer();
GameTaskQueueManager.getManager(renderContext).getQueue(GameTaskQueue.RENDER).enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
renderer.setBackgroundColor(ColorRGBA.BLUE);
return null;
}
});
_controlHandle.setMoveSpeed(400);
setupDefaultStates();
addRover();
addUI();
// Initialize PSSM shadows
_pssmPass = new ParallelSplitShadowMapPass(light, 2048, 4);
_pssmPass.setFiltering(Filter.None);
_pssmPass.setRenderShadowedScene(false);
_pssmPass.setKeepMainShader(true);
// _pssmPass.setMinimumLightDistance(500); // XXX: Tune this
_pssmPass.setUseSceneTexturing(false);
_pssmPass.setUseObjectCullFace(false);
_pssmPass.getShadowOffsetState().setFactor(1.1f);
_pssmPass.getShadowOffsetState().setUnits(4.0f);
// _pssmPass.setDrawDebug(true);
// TODO: backside lock test
final Quad floor = new Quad("floor", 2048, 2048);
floor.updateModelBound();
floor.setRotation(new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X));
floor.setTranslation(1024, 0, 1024);
terrainNode.attachChild(floor);
_pssmPass.addBoundsReceiver(terrainNode);
// Add objects that will get shadowed through overlay render
_pssmPass.add(_root);
// Add our occluders that will produce shadows
ShadowCasterManager.INSTANCE.addSpatial(terrainNode);
ShadowCasterManager.INSTANCE.addSpatial(_root);
final int quadSize = _canvas.getCanvasRenderer().getCamera().getWidth() / 10;
_orthoQuad = new Quad[ParallelSplitShadowMapPass._MAX_SPLITS];
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
_orthoQuad[i] = new Quad("OrthoQuad", quadSize, quadSize);
_orthoQuad[i].setTranslation(new Vector3(quadSize / 2 + 5 + (quadSize + 5) * i, quadSize / 2 + 5, 1));
_orthoQuad[i].setScale(1, -1, 1);
_orthoQuad[i].getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
_orthoQuad[i].getSceneHints().setLightCombineMode(LightCombineMode.Off);
_orthoQuad[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
_orthoQuad[i].getSceneHints().setCullHint(CullHint.Never);
hud.attachChild(_orthoQuad[i]);
}
try {
// Keep a separate camera to be able to freeze terrain update
final Camera camera = _canvas.getCanvasRenderer().getCamera();
terrainCamera = new Camera(camera);
// IMAGE LOADING AND CONVERSION TO HEIGHTMAP DONE HERE
final BufferedImage heightmap = ImageIO.read(ResourceLocatorTool.getClassPathResource(
MountainShadowTerrainExample.class, "com/ardor3d/example/media/images/heightmap.jpg"));
final Image ardorImage = AWTImageLoader.makeArdor3dImage(heightmap, false);
final float[] heightMap = ImageHeightMap.generateHeightMap(ardorImage, 0.05f, .33f);
// END OF IMAGE CONVERSION
final int SIZE = ardorImage.getWidth();
final ArrayTerrainDataProvider terrainDataProvider = new ArrayTerrainDataProvider(heightMap, SIZE,
new Vector3(5, 2048, 5), true);
terrainDataProvider.setHeightMax(0.34f);
final TerrainBuilder builder = new TerrainBuilder(terrainDataProvider, terrainCamera)
.setShowDebugPanels(true);
terrain = builder.build();
terrain.setPixelShader(new UrlInputSupplier(ResourceLocatorTool.getClassPathResource(
ShadowedTerrainExample.class,
"com/ardor3d/extension/terrain/shadowedGeometryClipmapShader_normalMap.frag")));
terrain.reloadShader();
terrain.getGeometryClipmapShader().setUniform("normalMap", 5);
terrainNode.attachChild(terrain);
terrain.setCullingEnabled(false);
} catch (final Exception ex1) {
System.out.println("Problem setting up terrain...");
ex1.printStackTrace();
}
final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight() / 2;
for (int i = 0; i < _exampleInfo.length; i++) {
_exampleInfo[i] = new UILabel("Text");
_exampleInfo[i].setForegroundColor(ColorRGBA.WHITE, true);
_exampleInfo[i].addFontStyle(StyleConstants.KEY_SIZE, 16);
_exampleInfo[i].addFontStyle(StyleConstants.KEY_BOLD, Boolean.TRUE);
_exampleInfo[i].setTranslation(new Vector3(10, infoStartY - i * 20, 0));
hud.add(_exampleInfo[i]);
}
updateText();
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(5);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(50);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(400);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(1000);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() {
@Override
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
groundCamera = !groundCamera;
updateText();
}
}));
}
private void addRover() {
try {
final ColladaStorage storage = new ColladaImporter().load("collada/sketchup/NASA Mars Rover.dae");
final Node rover = storage.getScene();
rover.setTranslation(440, 102, 160.1);
rover.setScale(3);
rover.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_X));
_root.attachChild(rover);
} catch (final IOException ex) {
ex.printStackTrace();
}
}
private void setupDefaultStates() {
terrainNode.setRenderState(_lightState);
terrainNode.setRenderState(_wireframeState);
terrainNode.setRenderState(new ZBufferState());
_lightState.detachAll();
light = new DirectionalLight();
light.setEnabled(true);
light.setAmbient(new ColorRGBA(0.4f, 0.4f, 0.5f, 1));
light.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.5f, 1));
light.setSpecular(new ColorRGBA(0.3f, 0.3f, 0.2f, 1));
light.setDirection(new Vector3(-1, -1, -1).normalizeLocal());
_lightState.attach(light);
_lightState.setEnabled(true);
final FogState fs = new FogState();
fs.setStart(farPlane / 2.0f);
fs.setEnd(farPlane);
fs.setColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
fs.setDensityFunction(DensityFunction.Linear);
terrainNode.setRenderState(fs);
}
/**
* Update text information.
*/
private void updateText() {
_exampleInfo[0].setText("[1/2/3/4] Moving speed: " + _controlHandle.getMoveSpeed() * 3.6 + " km/h");
_exampleInfo[1].setText("[SPACE] Toggle fly/walk: " + (groundCamera ? "walk" : "fly"));
}
@Override
protected void updateLogicalLayer(final ReadOnlyTimer timer) {
hud.getLogicalLayer().checkTriggers(timer.getTimePerFrame());
}
@Override
protected void renderDebug(final Renderer renderer) {
super.renderDebug(renderer);
if (_showBounds) {
Debugger.drawBounds(terrainNode, renderer, true);
}
}
private void addUI() {
// setup hud
hud = new UIHud();
hud.setupInput(_canvas, _physicalLayer, _logicalLayer);
hud.setMouseManager(_mouseManager);
final UIFrame frame = new UIFrame("Controls", EnumSet.noneOf(FrameButtons.class));
frame.setResizeable(false);
final UILabel distLabel = new UILabel("Max Shadow Distance: 1500");
final UISlider distSlider = new UISlider(Orientation.Horizontal, 0, 2000, 1500);
distSlider.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
_pssmPass.setMaxShadowDistance(distSlider.getValue());
distLabel.setText("Max Shadow Distance: " + distSlider.getValue());
}
});
final UIButton updateCamera = new UIButton("Update Shadow Camera");
updateCamera.setSelectable(true);
updateCamera.setSelected(true);
updateCamera.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
_pssmPass.setUpdateMainCamera(updateCamera.isSelected());
updateText();
}
});
final UIButton rotateLight = new UIButton("Rotate Light");
rotateLight.setSelectable(true);
rotateLight.setSelected(false);
rotateLight.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent event) {
moveLight = rotateLight.isSelected();
updateText();
}
});
final UIPanel panel = new UIPanel(new RowLayout(false, true, false));
panel.setPadding(new Insets(10, 20, 10, 20));
panel.add(distLabel);
panel.add(distSlider);
panel.add(updateCamera);
panel.add(rotateLight);
frame.setContentPanel(panel);
frame.pack();
final Camera cam = _canvas.getCanvasRenderer().getCamera();
frame.setLocalXY(cam.getWidth() - frame.getLocalComponentWidth(),
cam.getHeight() - frame.getLocalComponentHeight());
hud.add(frame);
}
}
\ No newline at end of file diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/ShadowedTerrainExample.java b/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/ShadowedTerrainExample.java index 2a028be..1a895ad 100644 --- a/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/ShadowedTerrainExample.java +++ b/ardor3d-examples/src/main/java/com/ardor3d/example/terrain/ShadowedTerrainExample.java @@ -1 +1 @@ -/**
* 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.example.terrain;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass.Filter;
import com.ardor3d.extension.terrain.client.Terrain;
import com.ardor3d.extension.terrain.client.TerrainBuilder;
import com.ardor3d.extension.terrain.client.TerrainDataProvider;
import com.ardor3d.extension.terrain.client.UrlInputSupplier;
import com.ardor3d.extension.terrain.heightmap.MidPointHeightMapGenerator;
import com.ardor3d.extension.terrain.providers.array.ArrayTerrainDataProvider;
import com.ardor3d.framework.Canvas;
import com.ardor3d.framework.CanvasRenderer;
import com.ardor3d.input.Key;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.intersection.PickingUtil;
import com.ardor3d.intersection.PrimitivePickResults;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.CullState;
import com.ardor3d.renderer.state.FogState;
import com.ardor3d.renderer.state.FogState.DensityFunction;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.hint.CullHint;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.shape.Box;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.ui.text.BasicText;
import com.ardor3d.util.GameTaskQueue;
import com.ardor3d.util.GameTaskQueueManager;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.resource.ResourceLocatorTool;
/**
* Example showing the Geometry Clipmap Terrain system combined with PSSM. (a bit experimental) Requires GLSL support.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.terrain.ShadowedTerrainExample", //
thumbnailPath = "com/ardor3d/example/media/thumbnails/terrain_ShadowedTerrainExample.jpg", //
maxHeapMemory = 128)
public class ShadowedTerrainExample extends ExampleBase {
/** The Constant logger. */
private static final Logger logger = Logger.getLogger(ShadowedTerrainExample.class.getName());
private boolean updateTerrain = true;
private final float farPlane = 2500.0f;
private Terrain terrain;
private final Sphere sphere = new Sphere("sp", 16, 16, 1);
private final Ray3 pickRay = new Ray3();
private boolean groundCamera = false;
private Camera terrainCamera;
/** Pssm shadow map pass. */
private ParallelSplitShadowMapPass _pssmPass;
private DirectionalLight light;
/** Temp vec for updating light pos. */
private final Vector3 lightPosition = new Vector3(10000, 10000, 10000);
/** Text fields used to present info about the example. */
private final BasicText _exampleInfo[] = new BasicText[5];
public static void main(final String[] args) {
ExampleBase.start(ShadowedTerrainExample.class);
}
@Override
protected void updateExample(final ReadOnlyTimer timer) {
final Camera camera = _canvas.getCanvasRenderer().getCamera();
// Make sure camera is above terrain
final double height = terrain.getHeightAt(camera.getLocation().getX(), camera.getLocation().getZ());
if (height > -Float.MAX_VALUE && (groundCamera || camera.getLocation().getY() < height + 3)) {
camera.setLocation(new Vector3(camera.getLocation().getX(), height + 3, camera.getLocation().getZ()));
}
if (updateTerrain) {
terrainCamera.set(camera);
}
// if we're picking...
if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) {
// Set up our pick ray
pickRay.setOrigin(camera.getLocation());
pickRay.setDirection(camera.getDirection());
// do pick and move the sphere
final PrimitivePickResults pickResults = new PrimitivePickResults();
pickResults.setCheckDistance(true);
PickingUtil.findPick(_root, pickRay, pickResults);
if (pickResults.getNumber() != 0) {
final Vector3 intersectionPoint = pickResults.getPickData(0).getIntersectionRecord()
.getIntersectionPoint(0);
sphere.setTranslation(intersectionPoint);
// XXX: maybe change the color of the ball for valid vs. invalid?
}
}
}
@Override
protected void renderExample(final Renderer renderer) {
// Lazy init since it needs the renderer...
if (!_pssmPass.isInitialised()) {
_pssmPass.init(renderer);
_pssmPass.setPssmShader(terrain.getGeometryClipmapShader());
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
terrain.getClipTextureState().setTexture(_pssmPass.getShadowMapTexture(i), i + 1);
}
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
terrain.getGeometryClipmapShader().setUniform("shadowMap" + i, i + 1);
}
}
// Update shadowmaps
_pssmPass.updateShadowMaps(renderer);
// Render scene and terrain with shadows
super.renderExample(renderer);
renderer.renderBuckets();
// Render overlay shadows for all objects except the terrain
_pssmPass.renderShadowedScene(renderer);
// TODO: this results in text etc also being shadowed, since they are drawn in the main render...
}
/**
* Initialize pssm pass and scene.
*/
@Override
protected void initExample() {
_canvas.setTitle("Terrain Example");
final Camera cam = _canvas.getCanvasRenderer().getCamera();
cam.setLocation(new Vector3(440, 215, 275));
cam.lookAt(new Vector3(450, 140, 360), Vector3.UNIT_Y);
cam.setFrustumPerspective(70.0, (float) cam.getWidth() / cam.getHeight(), 1.0f, farPlane);
final CanvasRenderer canvasRenderer = _canvas.getCanvasRenderer();
final RenderContext renderContext = canvasRenderer.getRenderContext();
final Renderer renderer = canvasRenderer.getRenderer();
GameTaskQueueManager.getManager(renderContext).getQueue(GameTaskQueue.RENDER).enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
renderer.setBackgroundColor(ColorRGBA.GRAY);
return null;
}
});
_controlHandle.setMoveSpeed(200);
setupDefaultStates();
sphere.getSceneHints().setAllPickingHints(false);
sphere.getSceneHints().setCullHint(CullHint.Always);
_root.attachChild(sphere);
try {
// Keep a separate camera to be able to freeze terrain update
terrainCamera = new Camera(cam);
final int SIZE = 2048;
final MidPointHeightMapGenerator raw = new MidPointHeightMapGenerator(SIZE, 0.6f);
raw.setHeightRange(0.2f);
final float[] heightMap = raw.getHeightData();
final TerrainDataProvider terrainDataProvider = new ArrayTerrainDataProvider(heightMap, SIZE, new Vector3(
1, 300, 1));
terrain = new TerrainBuilder(terrainDataProvider, terrainCamera).setShowDebugPanels(true).build();
terrain.setPixelShader(new UrlInputSupplier(ResourceLocatorTool
.getClassPathResource(ShadowedTerrainExample.class,
"com/ardor3d/extension/terrain/shadowedGeometryClipmapShaderPCF.frag")));
terrain.reloadShader();
_root.attachChild(terrain);
} catch (final Exception e) {
logger.log(Level.SEVERE, "Problem setting up terrain...", e);
System.exit(1);
}
// Initialize PSSM shadows
_pssmPass = new ParallelSplitShadowMapPass(light, 1024, 4);
_pssmPass.setFiltering(Filter.Pcf);
_pssmPass.setRenderShadowedScene(false);
_pssmPass.setKeepMainShader(true);
_pssmPass.setMaxShadowDistance(750); // XXX: Tune this
// _pssmPass.setMinimumLightDistance(500); // XXX: Tune this
_pssmPass.setUseSceneTexturing(false);
_pssmPass.setUseObjectCullFace(false);
// _pssmPass.setDrawDebug(true);
final Node occluders = setupOccluders();
_root.attachChild(occluders);
// TODO: could we use the shadow variable in scenehints here??
// Add objects that will get shadowed through overlay render
_pssmPass.add(occluders);
// Add terrain in as bounds receiver as well, since it's not in the overlay list
_pssmPass.addBoundsReceiver(terrain);
// Add our occluders that will produce shadows
_pssmPass.addOccluder(occluders);
// Setup labels for presenting example info.
final Node textNodes = new Node("Text");
_root.attachChild(textNodes);
textNodes.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
textNodes.getSceneHints().setLightCombineMode(LightCombineMode.Off);
final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight() / 2;
for (int i = 0; i < _exampleInfo.length; i++) {
_exampleInfo[i] = BasicText.createDefaultTextLabel("Text", "", 16);
_exampleInfo[i].setTranslation(new Vector3(10, infoStartY - i * 20, 0));
textNodes.attachChild(_exampleInfo[i]);
}
textNodes.updateGeometricState(0.0);
updateText();
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.U), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
updateTerrain = !updateTerrain;
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(5);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(50);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(400);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(1000);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
groundCamera = !groundCamera;
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.P), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) {
sphere.getSceneHints().setCullHint(CullHint.Always);
} else if (sphere.getSceneHints().getCullHint() == CullHint.Always) {
sphere.getSceneHints().setCullHint(CullHint.Dynamic);
}
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.R), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.getTextureClipmap().setShowDebug(!terrain.getTextureClipmap().isShowDebug());
terrain.reloadShader();
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.reloadShader();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FIVE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() / 2);
terrain.reloadShader();
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SIX), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() * 2);
terrain.reloadShader();
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.C), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUpdateMainCamera(!_pssmPass.isUpdateMainCamera());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ZERO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
final Camera cam = _canvas.getCanvasRenderer().getCamera();
System.out.println("camera location: " + cam.getLocation());
System.out.println("camera direction: " + cam.getDirection());
}
}));
}
private Node setupOccluders() {
final Node occluders = new Node("Occluders");
final Box box = new Box("Box", new Vector3(), 1, 40, 1);
box.setModelBound(new BoundingBox());
box.setRandomColors();
final Random rand = new Random(1337);
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
final Mesh sm = box.makeCopy(true);
sm.setTranslation(500 + rand.nextDouble() * 300 - 150, 20 + rand.nextDouble() * 5.0,
500 + rand.nextDouble() * 300 - 150);
occluders.attachChild(sm);
}
}
return occluders;
}
private void setupDefaultStates() {
_lightState.detachAll();
light = new DirectionalLight();
light.setEnabled(true);
light.setAmbient(new ColorRGBA(0.4f, 0.4f, 0.5f, 1));
light.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.5f, 1));
light.setSpecular(new ColorRGBA(0.3f, 0.3f, 0.2f, 1));
light.setDirection(lightPosition.normalize(null).negateLocal());
_lightState.attach(light);
_lightState.setEnabled(true);
final CullState cs = new CullState();
cs.setEnabled(true);
cs.setCullFace(CullState.Face.Back);
_root.setRenderState(cs);
final FogState fs = new FogState();
fs.setStart(farPlane / 2.0f);
fs.setEnd(farPlane);
fs.setColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
fs.setDensityFunction(DensityFunction.Linear);
_root.setRenderState(fs);
}
/**
* Update text information.
*/
private void updateText() {
_exampleInfo[0].setText("[1/2/3] Moving speed: " + _controlHandle.getMoveSpeed() * 3.6 + " km/h");
_exampleInfo[1].setText("[P] Do picking: " + (sphere.getSceneHints().getCullHint() == CullHint.Dynamic));
_exampleInfo[2].setText("[SPACE] Toggle fly/walk: " + (groundCamera ? "walk" : "fly"));
_exampleInfo[3].setText("[J] Regenerate heightmap/texture");
_exampleInfo[4].setText("[U] Freeze terrain(debug): " + !updateTerrain);
}
}
\ No newline at end of file +/**
* 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.example.terrain;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.example.ExampleBase;
import com.ardor3d.example.Purpose;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass;
import com.ardor3d.extension.shadow.map.ParallelSplitShadowMapPass.Filter;
import com.ardor3d.extension.shadow.map.ShadowCasterManager;
import com.ardor3d.extension.terrain.client.Terrain;
import com.ardor3d.extension.terrain.client.TerrainBuilder;
import com.ardor3d.extension.terrain.client.TerrainDataProvider;
import com.ardor3d.extension.terrain.client.UrlInputSupplier;
import com.ardor3d.extension.terrain.heightmap.MidPointHeightMapGenerator;
import com.ardor3d.extension.terrain.providers.array.ArrayTerrainDataProvider;
import com.ardor3d.framework.Canvas;
import com.ardor3d.framework.CanvasRenderer;
import com.ardor3d.input.Key;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.intersection.PickingUtil;
import com.ardor3d.intersection.PrimitivePickResults;
import com.ardor3d.light.DirectionalLight;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.queue.RenderBucketType;
import com.ardor3d.renderer.state.CullState;
import com.ardor3d.renderer.state.FogState;
import com.ardor3d.renderer.state.FogState.DensityFunction;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.hint.CullHint;
import com.ardor3d.scenegraph.hint.LightCombineMode;
import com.ardor3d.scenegraph.shape.Box;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.ui.text.BasicText;
import com.ardor3d.util.GameTaskQueue;
import com.ardor3d.util.GameTaskQueueManager;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.resource.ResourceLocatorTool;
/**
* Example showing the Geometry Clipmap Terrain system combined with PSSM. (a bit experimental) Requires GLSL support.
*/
@Purpose(htmlDescriptionKey = "com.ardor3d.example.terrain.ShadowedTerrainExample", //
thumbnailPath = "com/ardor3d/example/media/thumbnails/terrain_ShadowedTerrainExample.jpg", //
maxHeapMemory = 128)
public class ShadowedTerrainExample extends ExampleBase {
/** The Constant logger. */
private static final Logger logger = Logger.getLogger(ShadowedTerrainExample.class.getName());
private boolean updateTerrain = true;
private final float farPlane = 2500.0f;
private Terrain terrain;
private final Sphere sphere = new Sphere("sp", 16, 16, 1);
private final Ray3 pickRay = new Ray3();
private boolean groundCamera = false;
private Camera terrainCamera;
/** Pssm shadow map pass. */
private ParallelSplitShadowMapPass _pssmPass;
private DirectionalLight light;
/** Temp vec for updating light pos. */
private final Vector3 lightPosition = new Vector3(10000, 10000, 10000);
/** Text fields used to present info about the example. */
private final BasicText _exampleInfo[] = new BasicText[5];
public static void main(final String[] args) {
ExampleBase.start(ShadowedTerrainExample.class);
}
@Override
protected void updateExample(final ReadOnlyTimer timer) {
final Camera camera = _canvas.getCanvasRenderer().getCamera();
// Make sure camera is above terrain
final double height = terrain.getHeightAt(camera.getLocation().getX(), camera.getLocation().getZ());
if (height > -Float.MAX_VALUE && (groundCamera || camera.getLocation().getY() < height + 3)) {
camera.setLocation(new Vector3(camera.getLocation().getX(), height + 3, camera.getLocation().getZ()));
}
if (updateTerrain) {
terrainCamera.set(camera);
}
// if we're picking...
if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) {
// Set up our pick ray
pickRay.setOrigin(camera.getLocation());
pickRay.setDirection(camera.getDirection());
// do pick and move the sphere
final PrimitivePickResults pickResults = new PrimitivePickResults();
pickResults.setCheckDistance(true);
PickingUtil.findPick(_root, pickRay, pickResults);
if (pickResults.getNumber() != 0) {
final Vector3 intersectionPoint = pickResults.getPickData(0).getIntersectionRecord()
.getIntersectionPoint(0);
sphere.setTranslation(intersectionPoint);
// XXX: maybe change the color of the ball for valid vs. invalid?
}
}
}
@Override
protected void renderExample(final Renderer renderer) {
// Lazy init since it needs the renderer...
if (!_pssmPass.isInitialised()) {
_pssmPass.init(renderer);
_pssmPass.setPssmShader(terrain.getGeometryClipmapShader());
for (int i = 0; i < _pssmPass.getNumOfSplits(); i++) {
terrain.getClipTextureState().setTexture(_pssmPass.getShadowMapTexture(i), i + 1);
}
for (int i = 0; i < ParallelSplitShadowMapPass._MAX_SPLITS; i++) {
terrain.getGeometryClipmapShader().setUniform("shadowMap" + i, i + 1);
}
}
// Update shadowmaps
_pssmPass.updateShadowMaps(renderer);
// Render scene and terrain with shadows
super.renderExample(renderer);
renderer.renderBuckets();
// Render overlay shadows for all objects except the terrain
_pssmPass.renderShadowedScene(renderer);
// TODO: this results in text etc also being shadowed, since they are drawn in the main render...
}
/**
* Initialize pssm pass and scene.
*/
@Override
protected void initExample() {
_canvas.setTitle("Terrain Example");
final Camera cam = _canvas.getCanvasRenderer().getCamera();
cam.setLocation(new Vector3(440, 215, 275));
cam.lookAt(new Vector3(450, 140, 360), Vector3.UNIT_Y);
cam.setFrustumPerspective(70.0, (float) cam.getWidth() / cam.getHeight(), 1.0f, farPlane);
final CanvasRenderer canvasRenderer = _canvas.getCanvasRenderer();
final RenderContext renderContext = canvasRenderer.getRenderContext();
final Renderer renderer = canvasRenderer.getRenderer();
GameTaskQueueManager.getManager(renderContext).getQueue(GameTaskQueue.RENDER).enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
renderer.setBackgroundColor(ColorRGBA.GRAY);
return null;
}
});
_controlHandle.setMoveSpeed(200);
setupDefaultStates();
sphere.getSceneHints().setAllPickingHints(false);
sphere.getSceneHints().setCullHint(CullHint.Always);
_root.attachChild(sphere);
try {
// Keep a separate camera to be able to freeze terrain update
terrainCamera = new Camera(cam);
final int SIZE = 2048;
final MidPointHeightMapGenerator raw = new MidPointHeightMapGenerator(SIZE, 0.6f);
raw.setHeightRange(0.2f);
final float[] heightMap = raw.getHeightData();
final TerrainDataProvider terrainDataProvider = new ArrayTerrainDataProvider(heightMap, SIZE, new Vector3(
1, 300, 1));
terrain = new TerrainBuilder(terrainDataProvider, terrainCamera).setShowDebugPanels(true).build();
terrain.setPixelShader(new UrlInputSupplier(ResourceLocatorTool
.getClassPathResource(ShadowedTerrainExample.class,
"com/ardor3d/extension/terrain/shadowedGeometryClipmapShaderPCF.frag")));
terrain.reloadShader();
_root.attachChild(terrain);
} catch (final Exception e) {
logger.log(Level.SEVERE, "Problem setting up terrain...", e);
System.exit(1);
}
// Initialize PSSM shadows
_pssmPass = new ParallelSplitShadowMapPass(light, 1024, 4);
_pssmPass.setFiltering(Filter.Pcf);
_pssmPass.setRenderShadowedScene(false);
_pssmPass.setKeepMainShader(true);
_pssmPass.setMaxShadowDistance(750); // XXX: Tune this
// _pssmPass.setMinimumLightDistance(500); // XXX: Tune this
_pssmPass.setUseSceneTexturing(false);
_pssmPass.setUseObjectCullFace(false);
// _pssmPass.setDrawDebug(true);
final Node occluders = setupOccluders();
_root.attachChild(occluders);
// TODO: could we use the shadow variable in scenehints here??
// Add objects that will get shadowed through overlay render
_pssmPass.add(occluders);
// Add terrain in as bounds receiver as well, since it's not in the overlay list
_pssmPass.addBoundsReceiver(terrain);
// Add our occluders that will produce shadows
ShadowCasterManager.INSTANCE.addSpatial(occluders);
// Setup labels for presenting example info.
final Node textNodes = new Node("Text");
_root.attachChild(textNodes);
textNodes.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
textNodes.getSceneHints().setLightCombineMode(LightCombineMode.Off);
final double infoStartY = _canvas.getCanvasRenderer().getCamera().getHeight() / 2;
for (int i = 0; i < _exampleInfo.length; i++) {
_exampleInfo[i] = BasicText.createDefaultTextLabel("Text", "", 16);
_exampleInfo[i].setTranslation(new Vector3(10, infoStartY - i * 20, 0));
textNodes.attachChild(_exampleInfo[i]);
}
textNodes.updateGeometricState(0.0);
updateText();
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.U), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
updateTerrain = !updateTerrain;
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ONE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(5);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.TWO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(50);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.THREE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(400);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FOUR), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_controlHandle.setMoveSpeed(1000);
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SPACE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
groundCamera = !groundCamera;
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.P), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
if (sphere.getSceneHints().getCullHint() == CullHint.Dynamic) {
sphere.getSceneHints().setCullHint(CullHint.Always);
} else if (sphere.getSceneHints().getCullHint() == CullHint.Always) {
sphere.getSceneHints().setCullHint(CullHint.Dynamic);
}
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.R), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.getTextureClipmap().setShowDebug(!terrain.getTextureClipmap().isShowDebug());
terrain.reloadShader();
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.reloadShader();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.FIVE), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() / 2);
terrain.reloadShader();
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.SIX), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
terrain.getTextureClipmap().setScale(terrain.getTextureClipmap().getScale() * 2);
terrain.reloadShader();
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.C), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
_pssmPass.setUpdateMainCamera(!_pssmPass.isUpdateMainCamera());
updateText();
}
}));
_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ZERO), new TriggerAction() {
public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
final Camera cam = _canvas.getCanvasRenderer().getCamera();
System.out.println("camera location: " + cam.getLocation());
System.out.println("camera direction: " + cam.getDirection());
}
}));
}
private Node setupOccluders() {
final Node occluders = new Node("Occluders");
final Box box = new Box("Box", new Vector3(), 1, 40, 1);
box.setModelBound(new BoundingBox());
box.setRandomColors();
final Random rand = new Random(1337);
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
final Mesh sm = box.makeCopy(true);
sm.setTranslation(500 + rand.nextDouble() * 300 - 150, 20 + rand.nextDouble() * 5.0,
500 + rand.nextDouble() * 300 - 150);
occluders.attachChild(sm);
}
}
return occluders;
}
private void setupDefaultStates() {
_lightState.detachAll();
light = new DirectionalLight();
light.setEnabled(true);
light.setAmbient(new ColorRGBA(0.4f, 0.4f, 0.5f, 1));
light.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.5f, 1));
light.setSpecular(new ColorRGBA(0.3f, 0.3f, 0.2f, 1));
light.setDirection(lightPosition.normalize(null).negateLocal());
_lightState.attach(light);
_lightState.setEnabled(true);
final CullState cs = new CullState();
cs.setEnabled(true);
cs.setCullFace(CullState.Face.Back);
_root.setRenderState(cs);
final FogState fs = new FogState();
fs.setStart(farPlane / 2.0f);
fs.setEnd(farPlane);
fs.setColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
fs.setDensityFunction(DensityFunction.Linear);
_root.setRenderState(fs);
}
/**
* Update text information.
*/
private void updateText() {
_exampleInfo[0].setText("[1/2/3] Moving speed: " + _controlHandle.getMoveSpeed() * 3.6 + " km/h");
_exampleInfo[1].setText("[P] Do picking: " + (sphere.getSceneHints().getCullHint() == CullHint.Dynamic));
_exampleInfo[2].setText("[SPACE] Toggle fly/walk: " + (groundCamera ? "walk" : "fly"));
_exampleInfo[3].setText("[J] Regenerate heightmap/texture");
_exampleInfo[4].setText("[U] Freeze terrain(debug): " + !updateTerrain);
}
}
\ No newline at end of file |