From 4a37685a4e9338a83e090b01ec2c0a8c49467a36 Mon Sep 17 00:00:00 2001 From: Renanse Date: Tue, 18 Dec 2012 15:32:47 -0600 Subject: Added "asType(Type)" method to BoundingVolume. --- .../java/com/ardor3d/bounding/BoundingBox.java | 27 ++++++++++++ .../java/com/ardor3d/bounding/BoundingSphere.java | 48 +++++++++++++++++----- .../java/com/ardor3d/bounding/BoundingVolume.java | 9 ++++ .../com/ardor3d/bounding/OrientedBoundingBox.java | 26 ++++++++++++ 4 files changed, 100 insertions(+), 10 deletions(-) (limited to 'ardor3d-core/src/main/java') diff --git a/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java b/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java index d488d81..c7de5dc 100644 --- a/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java +++ b/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java @@ -426,6 +426,33 @@ public class BoundingBox extends BoundingVolume { } } + @Override + public BoundingVolume asType(final Type newType) { + if (newType == null) { + return null; + } + + switch (newType) { + case AABB: { + return this.clone(null); + } + + case Sphere: { + final BoundingSphere sphere = new BoundingSphere(0, _center); + return sphere.merge(this); + } + + case OBB: { + final OrientedBoundingBox obb = new OrientedBoundingBox(); + obb.setCenter(_center); + return obb.merge(this); + } + + default: + return null; + } + } + /** * Merges this AABB with the given OBB. * diff --git a/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java b/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java index 9bf81b8..675a53c 100644 --- a/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java +++ b/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java @@ -20,10 +20,10 @@ import com.ardor3d.math.MathUtils; import com.ardor3d.math.Plane; import com.ardor3d.math.Vector3; import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyPlane.Side; import com.ardor3d.math.type.ReadOnlyRay3; import com.ardor3d.math.type.ReadOnlyTransform; import com.ardor3d.math.type.ReadOnlyVector3; -import com.ardor3d.math.type.ReadOnlyPlane.Side; import com.ardor3d.scenegraph.MeshData; import com.ardor3d.util.export.InputCapsule; import com.ardor3d.util.export.OutputCapsule; @@ -103,6 +103,7 @@ public class BoundingSphere extends BoundingVolume { * * @return the radius of the bounding sphere. */ + @Override public double getRadius() { return _radius; } @@ -269,9 +270,9 @@ public class BoundingSphere extends BoundingVolume { _center.set(0, 0, 0); setRadius(0); } else { - final Vector3 o = a.cross(b, null).multiplyLocal(c.lengthSquared()).addLocal( - c.cross(a, null).multiplyLocal(b.lengthSquared())).addLocal( - b.cross(c, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator); + final Vector3 o = a.cross(b, null).multiplyLocal(c.lengthSquared()) + .addLocal(c.cross(a, null).multiplyLocal(b.lengthSquared())) + .addLocal(b.cross(c, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator); setRadius(o.length() * radiusEpsilon); O.add(o, _center); @@ -301,8 +302,8 @@ public class BoundingSphere extends BoundingVolume { setRadius(0); } else { - final Vector3 o = acrossB.cross(a, null).multiplyLocal(b.lengthSquared()).addLocal( - b.cross(acrossB, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator); + final Vector3 o = acrossB.cross(a, null).multiplyLocal(b.lengthSquared()) + .addLocal(b.cross(acrossB, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator); setRadius(o.length() * radiusEpsilon); O.add(o, _center); } @@ -562,6 +563,33 @@ public class BoundingSphere extends BoundingVolume { return store; } + @Override + public BoundingVolume asType(final Type newType) { + if (newType == null) { + return null; + } + + switch (newType) { + case AABB: { + final BoundingBox box = new BoundingBox(_center, 0, 0, 0); + return box.merge(this); + } + + case Sphere: { + return this.clone(null); + } + + case OBB: { + final OrientedBoundingBox obb = new OrientedBoundingBox(); + obb.setCenter(_center); + return obb.merge(this); + } + + default: + return null; + } + } + /** * clone creates a new BoundingSphere object containing the same data as this one. * @@ -662,8 +690,8 @@ public class BoundingSphere extends BoundingVolume { discr = (a1 * a1) - a; root = Math.sqrt(discr); final double[] distances = new double[] { root - a1 }; - final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()).addLocal( - ray.getOrigin()) }; + final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()) + .addLocal(ray.getOrigin()) }; return new IntersectionRecord(distances, points); } @@ -687,8 +715,8 @@ public class BoundingSphere extends BoundingVolume { } final double[] distances = new double[] { -a1 }; - final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()).addLocal( - ray.getOrigin()) }; + final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()) + .addLocal(ray.getOrigin()) }; return new IntersectionRecord(distances, points); } diff --git a/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java b/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java index 5c0ba3d..19a9822 100644 --- a/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java +++ b/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java @@ -241,6 +241,15 @@ public abstract class BoundingVolume implements Serializable, Savable { */ public abstract boolean contains(ReadOnlyVector3 point); + /** + * Convert this bounding volume to another, given bounding type. + * + * @param newType + * the type of bounding volume to convert to. + * @return a new bounding volume of the given type, containing this bounding volume. + */ + public abstract BoundingVolume asType(Type newType); + public void write(final OutputCapsule capsule) throws IOException { capsule.write(_center, "center", new Vector3(Vector3.ZERO)); } diff --git a/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java b/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java index e3e77b0..20dd515 100644 --- a/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java +++ b/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java @@ -191,6 +191,32 @@ public class OrientedBoundingBox extends BoundingVolume { } } + @Override + public BoundingVolume asType(final Type newType) { + if (newType == null) { + return null; + } + + switch (newType) { + case AABB: { + final BoundingBox box = new BoundingBox(_center, 0, 0, 0); + return box.merge(this); + } + + case Sphere: { + final BoundingSphere sphere = new BoundingSphere(0, _center); + return sphere.merge(this); + } + + case OBB: { + return this.clone(null); + } + + default: + return null; + } + } + private BoundingVolume mergeSphere(final BoundingSphere volume) { // check for infinite bounds to prevent NaN values if (Vector3.isInfinite(getExtent()) || Double.isInfinite(volume.getRadius())) { -- cgit v1.2.3 From 76c6a1ff4ce5d8bf5394e7851744a598982a47a1 Mon Sep 17 00:00:00 2001 From: Renanse Date: Tue, 18 Dec 2012 15:38:40 -0600 Subject: Small tweaks, spelling, unnecessary checks, etc. --- .../java/com/ardor3d/extension/animation/skeletal/SkeletonPose.java | 1 - .../extension/animation/skeletal/blendtree/BinaryLERPSource.java | 3 ++- .../extension/animation/skeletal/blendtree/ManagedTransformSource.java | 2 +- ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java | 2 -- .../java/com/ardor3d/example/effect/ParallelSplitShadowMapExample.java | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) (limited to 'ardor3d-core/src/main/java') diff --git a/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/SkeletonPose.java b/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/SkeletonPose.java index cb1ea7a..a0fa4c1 100644 --- a/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/SkeletonPose.java +++ b/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/SkeletonPose.java @@ -196,7 +196,6 @@ public class SkeletonPose implements Savable { } Transform.releaseTempInstance(temp); updateTransforms(); - firePoseUpdated(); } /** diff --git a/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/BinaryLERPSource.java b/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/BinaryLERPSource.java index fec63a8..5edb64d 100644 --- a/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/BinaryLERPSource.java +++ b/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/BinaryLERPSource.java @@ -95,7 +95,8 @@ public class BinaryLERPSource extends AbstractTwoPartSource { */ public static Map combineSourceData(final Map sourceAData, final Map sourceBData, final Double blendWeight) { - return BinaryLERPSource.combineSourceData(sourceAData, sourceBData, blendWeight, null); + return BinaryLERPSource.combineSourceData(sourceAData, sourceBData, + blendWeight != null ? blendWeight.doubleValue() : 0.0, null); } public static Map combineSourceData(final Map sourceAData, diff --git a/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/ManagedTransformSource.java b/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/ManagedTransformSource.java index 6530783..1806e6a 100644 --- a/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/ManagedTransformSource.java +++ b/ardor3d-animation/src/main/java/com/ardor3d/extension/animation/skeletal/blendtree/ManagedTransformSource.java @@ -24,7 +24,7 @@ import com.google.common.collect.Maps; /** * This tree source maintains its own source data, which can be modified directly using setJointXXX. This source is - * meant to be used for controlling a particular joint or set of joints programmatically. + * meant to be used for controlling a particular joint or set of joints programatically. */ public class ManagedTransformSource implements BlendTreeSource { diff --git a/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java b/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java index f281735..3b72a92 100644 --- a/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java +++ b/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java @@ -1386,8 +1386,6 @@ public class Camera implements Savable, Externalizable { */ private void checkModelViewProjection() { if (_updateMVPMatrix) { - checkModelView(); - checkProjection(); _modelViewProjection.set(getModelViewMatrix()).multiplyLocal(getProjectionMatrix()); _updateMVPMatrix = false; } 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 6c7f15e..5020f5c 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 . */ 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); // Setyp 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() { 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() { 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 . */ 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() { 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() { 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 -- cgit v1.2.3