Arbeiten aus dem URlaub
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.simsilica.builder.BuilderState;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.Renderer;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.debug.WireBox;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Image.Format;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.simsilica.arboreal.mesh.BillboardedLeavesMeshGenerator;
|
||||
import com.simsilica.arboreal.mesh.SkinnedTreeMeshGenerator;
|
||||
import com.simsilica.arboreal.mesh.Vertex;
|
||||
import com.simsilica.builder.Builder;
|
||||
import com.simsilica.builder.BuilderReference;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class AtlasGeneratorState extends BaseAppState {
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(AtlasGeneratorState.class);
|
||||
|
||||
private VersionedReference<TreeParameters> treeParametersRef;
|
||||
private Material treeMaterial;
|
||||
private Material leafMaterial;
|
||||
|
||||
private Builder builder;
|
||||
private AtlasTreeBuilderReference builderRef;
|
||||
|
||||
private Mesh trunkMesh;
|
||||
private Mesh leafMesh;
|
||||
|
||||
private FrameBuffer diffuseFb;
|
||||
private FrameBuffer normalFb;
|
||||
private CellView[] cellViews = new CellView[8];
|
||||
private Image diffuseMap;
|
||||
private Texture2D diffuseTexture;
|
||||
private Image normalMap;
|
||||
private Texture2D normalTexture;
|
||||
private int needTextureUpdate;
|
||||
|
||||
private BitmapFont font;
|
||||
|
||||
private boolean debugTextures = false;
|
||||
|
||||
private boolean useNormalMaps = true;
|
||||
|
||||
public AtlasGeneratorState() {
|
||||
}
|
||||
|
||||
public Image getDiffuseMap() {
|
||||
return diffuseMap;
|
||||
}
|
||||
|
||||
public Image getNormalMap() {
|
||||
return normalMap;
|
||||
}
|
||||
|
||||
protected Image createFrameBufferImage( FrameBuffer fb ) {
|
||||
int width = fb.getWidth();
|
||||
int height = fb.getHeight();
|
||||
int size = width * height * 4;
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer(size);
|
||||
Image.Format format = fb.getColorBuffer().getFormat();
|
||||
|
||||
// I guess readFrameBuffer always writes in the same
|
||||
// format regardless of the frame buffer format
|
||||
format = Format.BGRA8;
|
||||
return new Image(format, width, height, buffer);
|
||||
}
|
||||
|
||||
protected void updateTextures() {
|
||||
|
||||
Renderer renderer = getApplication().getRenderer();
|
||||
if( diffuseMap == null ) {
|
||||
diffuseMap = createFrameBufferImage(diffuseFb);
|
||||
diffuseTexture = new Texture2D(diffuseMap);
|
||||
getState(ForestGridState.class).getImpostorMaterial().setTexture("DiffuseMap", diffuseTexture);
|
||||
}
|
||||
renderer.readFrameBuffer(diffuseFb, diffuseMap.getData(0));
|
||||
diffuseMap.setUpdateNeeded();
|
||||
|
||||
if( normalMap == null ) {
|
||||
normalMap = createFrameBufferImage(normalFb);
|
||||
normalTexture = new Texture2D(normalMap);
|
||||
if( useNormalMaps ) {
|
||||
getState(ForestGridState.class).getImpostorMaterial().setTexture("NormalMap", normalTexture);
|
||||
}
|
||||
}
|
||||
renderer.readFrameBuffer(normalFb, normalMap.getData(0));
|
||||
normalMap.setUpdateNeeded();
|
||||
|
||||
needTextureUpdate = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
this.treeParametersRef = getState(TreeParametersState.class).getTreeParametersRef();
|
||||
this.treeMaterial = getState(ForestGridState.class).getTreeMaterial();
|
||||
this.leafMaterial = getState(ForestGridState.class).getLeafMaterial();
|
||||
|
||||
this.builder = getState(BuilderState.class).getBuilder();
|
||||
this.builderRef = new AtlasTreeBuilderReference();
|
||||
|
||||
|
||||
this.font = GuiGlobals.getInstance().loadFont("Interface/Fonts/Default.fnt");
|
||||
|
||||
Camera camera = app.getCamera().clone();
|
||||
camera.resize(256, 256, true);
|
||||
camera.resize(1024, 256, false);
|
||||
|
||||
|
||||
FrameBuffer fb1 = new FrameBuffer(1024, 256, 1);
|
||||
diffuseFb = fb1;
|
||||
Texture2D fbTex1 = new Texture2D(1024, 256, Format.RGBA8);
|
||||
fb1.setDepthBuffer(Format.Depth);
|
||||
fb1.setColorTexture(fbTex1);
|
||||
|
||||
FrameBuffer fb2 = new FrameBuffer(1024, 256, 1);
|
||||
normalFb = fb2;
|
||||
Texture2D fbTex2 = new Texture2D(1024, 256, Format.RGBA8);
|
||||
fb2.setDepthBuffer(Format.Depth);
|
||||
fb2.setColorTexture(fbTex2);
|
||||
|
||||
|
||||
if( debugTextures ) {
|
||||
Quad testQuad = new Quad(512, 128);
|
||||
Geometry testGeom = new Geometry("test", testQuad);
|
||||
Material mat = GuiGlobals.getInstance().createMaterial(fbTex1, false).getMaterial();
|
||||
testGeom.setMaterial(mat);
|
||||
((TreeEditor)app).getGuiNode().attachChild(testGeom);
|
||||
|
||||
testQuad = new Quad(512, 128);
|
||||
testGeom = new Geometry("test", testQuad);
|
||||
testGeom.setLocalTranslation(0, 128, 0);
|
||||
mat = GuiGlobals.getInstance().createMaterial(fbTex2, false).getMaterial();
|
||||
testGeom.setMaterial(mat);
|
||||
((TreeEditor)app).getGuiNode().attachChild(testGeom);
|
||||
|
||||
updateTextures();
|
||||
|
||||
testQuad = new Quad(512, 128);
|
||||
testGeom = new Geometry("test", testQuad);
|
||||
testGeom.setLocalTranslation(0, 256, 0);
|
||||
mat = GuiGlobals.getInstance().createMaterial(diffuseTexture, false).getMaterial();
|
||||
testGeom.setMaterial(mat);
|
||||
((TreeEditor)app).getGuiNode().attachChild(testGeom);
|
||||
|
||||
testQuad = new Quad(512, 128);
|
||||
testGeom = new Geometry("test", testQuad);
|
||||
testGeom.setLocalTranslation(0, 384, 0);
|
||||
mat = GuiGlobals.getInstance().createMaterial(normalTexture, false).getMaterial();
|
||||
testGeom.setMaterial(mat);
|
||||
((TreeEditor)app).getGuiNode().attachChild(testGeom);
|
||||
}
|
||||
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
//sun.setDirection(new Vector3f(0, -1f, -1).normalizeLocal());
|
||||
sun.setDirection(new Vector3f(0, 0, -1).normalizeLocal());
|
||||
|
||||
AmbientLight ambient = new AmbientLight();
|
||||
|
||||
if( useNormalMaps ) {
|
||||
sun.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1));
|
||||
ambient.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1));
|
||||
} else {
|
||||
sun.setColor(new ColorRGBA(1, 1, 1, 1));
|
||||
ambient.setColor(new ColorRGBA(0.25f, 0.25f, 0.25f, 1));
|
||||
}
|
||||
|
||||
//-x * FastMath.TWO_PI - FastMath.QUARTER_PI
|
||||
// The texture quads actually run a, c, d, b starting with
|
||||
// the +, + quadrant
|
||||
cellViews[0] = new CellView(fb1, camera, sun, ambient, FastMath.QUARTER_PI, 0.25f * 0);
|
||||
cellViews[1] = new CellView(fb1, camera, sun, ambient, -FastMath.QUARTER_PI, 0.25f * 1);
|
||||
cellViews[2] = new CellView(fb1, camera, sun, ambient, FastMath.PI - FastMath.QUARTER_PI, 0.25f * 2);
|
||||
cellViews[3] = new CellView(fb1, camera, sun, ambient, FastMath.PI + FastMath.QUARTER_PI, 0.25f * 3);
|
||||
|
||||
cellViews[4] = new NormalMapCellView(fb2, camera, sun, ambient, FastMath.QUARTER_PI, 0.25f * 0);
|
||||
cellViews[5] = new NormalMapCellView(fb2, camera, sun, ambient, -FastMath.QUARTER_PI, 0.25f * 1);
|
||||
cellViews[6] = new NormalMapCellView(fb2, camera, sun, ambient, FastMath.PI - FastMath.QUARTER_PI, 0.25f * 2);
|
||||
cellViews[7] = new NormalMapCellView(fb2, camera, sun, ambient, FastMath.PI + FastMath.QUARTER_PI, 0.25f * 3);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
for( CellView view : cellViews ) {
|
||||
app.getRenderManager().removeMainView(view.getViewPort());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
}
|
||||
|
||||
protected void updateTree( Mesh trunkMesh, Mesh leafMesh ) {
|
||||
if( this.trunkMesh == trunkMesh ) {
|
||||
return;
|
||||
}
|
||||
|
||||
releaseMesh(this.trunkMesh);
|
||||
releaseMesh(this.leafMesh);
|
||||
this.trunkMesh = trunkMesh;
|
||||
this.leafMesh = leafMesh;
|
||||
|
||||
for( CellView view : cellViews ) {
|
||||
if( view != null ) {
|
||||
view.updateMesh(trunkMesh, leafMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Texture updates need to happen one frame late...
|
||||
// but we get the notification that we need the check
|
||||
// early in _this_ frame. ie: updateTree() is called
|
||||
// before our update(), render(). If we want to render
|
||||
// a frame later then we need to skip this frame before
|
||||
// updating textures.
|
||||
needTextureUpdate = 2;
|
||||
}
|
||||
|
||||
protected void releaseMesh( Mesh mesh ) {
|
||||
if( mesh == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the old buffers
|
||||
for( VertexBuffer vb : mesh.getBufferList() ) {
|
||||
if( log.isTraceEnabled() ) {
|
||||
log.trace("--destroying buffer:" + vb);
|
||||
}
|
||||
BufferUtils.destroyDirectBuffer( vb.getData() );
|
||||
}
|
||||
}
|
||||
|
||||
private float nextUpdateCheck = 0.1f;
|
||||
private float lastTpf;
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
lastTpf = tpf;
|
||||
|
||||
nextUpdateCheck += tpf;
|
||||
if( nextUpdateCheck <= 0.1f ) {
|
||||
return;
|
||||
}
|
||||
nextUpdateCheck = 0;
|
||||
|
||||
boolean changed = treeParametersRef.update();
|
||||
if( changed ) {
|
||||
builder.build(builderRef);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render( RenderManager rm ) {
|
||||
if( cellViews != null ) {
|
||||
// We update the logical state here because it is
|
||||
// done after the other updates. So if another app
|
||||
// state or control has modified our root then we
|
||||
// are guaranteed to run after.
|
||||
for( CellView view : cellViews ) {
|
||||
if( view != null ) {
|
||||
view.update(lastTpf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Texture updates need to happen one frame later...
|
||||
// but we get the notification that we need the check
|
||||
// early in _this_ frame.
|
||||
if( needTextureUpdate > 0 ) {
|
||||
needTextureUpdate--;
|
||||
if( needTextureUpdate == 0 ) {
|
||||
updateTextures();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CellView {
|
||||
private ViewPort viewport;
|
||||
private Camera camera;
|
||||
private Node root;
|
||||
private Mesh leafMesh;
|
||||
private Mesh trunkMesh;
|
||||
private Geometry trunkGeom;
|
||||
private Geometry leafGeom;
|
||||
private Geometry wireBounds;
|
||||
private boolean debugBounds = false;
|
||||
private boolean debugCell = false;
|
||||
|
||||
public CellView( FrameBuffer fb, Camera templateCamera, DirectionalLight sun, AmbientLight ambient, float angle, float x ) {
|
||||
|
||||
this.camera = templateCamera.clone();
|
||||
camera.setViewPort(x, x + 0.25f, 0, 1);
|
||||
|
||||
this.root = new Node("CellRoot:" + x );
|
||||
this.viewport = getApplication().getRenderManager().createMainView("AtlasCell[" + x + "]", camera);
|
||||
this.viewport.setOutputFrameBuffer(fb);
|
||||
this.root.rotate(0, -angle, 0);
|
||||
|
||||
if( debugCell ) {
|
||||
BitmapText label = new BitmapText(font);
|
||||
label.setText("u:" + x + "\na:" + angle);
|
||||
label.setLocalScale(0.01f);
|
||||
Quaternion labelRot = root.getLocalRotation().inverse();
|
||||
label.setLocalRotation(labelRot);
|
||||
label.setLocalTranslation(labelRot.mult(new Vector3f(0, 1, 2)));
|
||||
root.attachChild(label);
|
||||
}
|
||||
|
||||
viewport.attachScene(root);
|
||||
root.addLight(sun);
|
||||
root.addLight(ambient);
|
||||
|
||||
viewport.setClearFlags(true, true, true);
|
||||
viewport.setBackgroundColor(new ColorRGBA(0, 0, 0, 0));
|
||||
this.camera.lookAtDirection(new Vector3f(0, 0, -1), Vector3f.UNIT_Y);
|
||||
}
|
||||
|
||||
public ViewPort getViewPort() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void update( float tpf ) {
|
||||
root.updateLogicalState(tpf);
|
||||
root.updateGeometricState();
|
||||
}
|
||||
|
||||
protected Material getTreeMaterial() {
|
||||
return treeMaterial;
|
||||
}
|
||||
|
||||
protected Material getLeafMaterial() {
|
||||
return leafMaterial;
|
||||
}
|
||||
|
||||
public void updateMesh( Mesh trunkMesh, Mesh leafMesh ) {
|
||||
if( trunkGeom == null ) {
|
||||
// Create it
|
||||
trunkGeom = new Geometry("Trunk", trunkMesh);
|
||||
trunkGeom.setMaterial(getTreeMaterial());
|
||||
root.attachChild(trunkGeom);
|
||||
} else {
|
||||
// Just swap out the mesh
|
||||
trunkGeom.setMesh(trunkMesh);
|
||||
}
|
||||
this.trunkMesh = trunkMesh;
|
||||
this.leafMesh = leafMesh;
|
||||
if( leafMesh == null ) {
|
||||
if( leafGeom != null ) {
|
||||
leafGeom.removeFromParent();
|
||||
leafGeom = null;
|
||||
}
|
||||
} else {
|
||||
if( leafGeom == null ) {
|
||||
// Create it
|
||||
leafGeom = new Geometry("Leaves", leafMesh);
|
||||
leafGeom.setMaterial(getLeafMaterial());
|
||||
leafGeom.setQueueBucket(Bucket.Transparent);
|
||||
root.attachChild(leafGeom);
|
||||
} else {
|
||||
// Just swap out the mesh
|
||||
leafGeom.setMesh(leafMesh);
|
||||
}
|
||||
}
|
||||
updateCamera();
|
||||
}
|
||||
|
||||
protected void updateCamera() {
|
||||
|
||||
BoundingBox bb = (BoundingBox)trunkMesh.getBound();
|
||||
if( leafGeom != null ) {
|
||||
BoundingBox bb2 = (BoundingBox)leafMesh.getBound();
|
||||
bb = (BoundingBox)bb.merge(bb2);
|
||||
}
|
||||
|
||||
Vector3f min = bb.getMin(null);
|
||||
Vector3f max = bb.getMax(null);
|
||||
|
||||
float xSize = Math.max(Math.abs(min.x), Math.abs(max.x));
|
||||
float ySize = max.y - min.y;
|
||||
float zSize = Math.max(Math.abs(min.z), Math.abs(max.z));
|
||||
|
||||
float size = ySize * 0.5f;
|
||||
size = Math.max(size, xSize);
|
||||
size = Math.max(size, zSize);
|
||||
|
||||
// In the projection matrix, [1][1] should be:
|
||||
// (2 * Zn) / camHeight
|
||||
// where Zn is distance to near plane.
|
||||
float m11 = camera.getViewProjectionMatrix().m11;
|
||||
|
||||
// We want our position to be such that
|
||||
// 'size' is otherwise = cameraHeight when rendered.
|
||||
float z = m11 * size;
|
||||
|
||||
// Add the z extents so that we adjust for the near plane
|
||||
// of the bounding box... well we will be rotating so
|
||||
// let's just be sure and take the max of x and z
|
||||
//float offset = Math.max(bb.getXExtent(), bb.getZExtent());
|
||||
//z += offset;
|
||||
// This creates problems because it makes way too much
|
||||
// space around the tree. A proper solution would require
|
||||
// a bunch of math and in the end would also have to be duplicated
|
||||
// on the quad generation side or somehow stored with the atlas.
|
||||
|
||||
Vector3f center = bb.getCenter();
|
||||
|
||||
float sizeOffset = size - (ySize*0.5f);
|
||||
|
||||
Vector3f camLoc = new Vector3f(0, center.y + sizeOffset, z);
|
||||
camera.setLocation(camLoc);
|
||||
|
||||
if( debugBounds ) {
|
||||
WireBox box;
|
||||
if( wireBounds == null ) {
|
||||
box = new WireBox();
|
||||
wireBounds = new Geometry("wire box", box);
|
||||
Material mat = GuiGlobals.getInstance().createMaterial(ColorRGBA.Yellow, false).getMaterial();
|
||||
wireBounds.setMaterial(mat);
|
||||
root.attachChild(wireBounds);
|
||||
} else {
|
||||
box = (WireBox)wireBounds.getMesh();
|
||||
}
|
||||
box.updatePositions(bb.getXExtent(), bb.getYExtent(), bb.getZExtent());
|
||||
box.setBound(new BoundingBox(new Vector3f(0,0,0), 0, 0, 0));
|
||||
wireBounds.setLocalTranslation(bb.getCenter());
|
||||
wireBounds.setLocalRotation(leafGeom.getLocalRotation());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class NormalMapCellView extends CellView {
|
||||
public NormalMapCellView( FrameBuffer fb, Camera templateCamera, DirectionalLight sun, AmbientLight ambient, float angle, float x ) {
|
||||
super(fb, templateCamera, sun, ambient, angle, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Material getTreeMaterial() {
|
||||
Material normalMaterial = treeMaterial.clone();
|
||||
normalMaterial.selectTechnique("PreNormalPass", getApplication().getRenderManager());
|
||||
return normalMaterial;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Material getLeafMaterial() {
|
||||
Material normalMaterial = leafMaterial.clone();
|
||||
normalMaterial.selectTechnique("PreNormalPass", getApplication().getRenderManager());
|
||||
return normalMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AtlasTreeBuilderReference implements BuilderReference {
|
||||
|
||||
private Mesh trunkMesh;
|
||||
private Mesh leafMesh;
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
// A relatively low priority
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build() {
|
||||
|
||||
TreeParameters treeParameters = treeParametersRef.get();
|
||||
|
||||
TreeGenerator treeGen = new TreeGenerator();
|
||||
Tree tree = treeGen.generateTree(treeParameters);
|
||||
|
||||
SkinnedTreeMeshGenerator meshGen = new SkinnedTreeMeshGenerator();
|
||||
|
||||
List<Vertex> tips = new ArrayList<Vertex>();
|
||||
trunkMesh = meshGen.generateMesh(tree,
|
||||
treeParameters.getLod(0),
|
||||
treeParameters.getYOffset(),
|
||||
treeParameters.getTextureURepeat(),
|
||||
treeParameters.getTextureVScale(),
|
||||
tips);
|
||||
|
||||
if( treeParameters.getGenerateLeaves() ) {
|
||||
BillboardedLeavesMeshGenerator leafGen = new BillboardedLeavesMeshGenerator();
|
||||
leafMesh = leafGen.generateMesh(tips, treeParameters.getLeafScale());
|
||||
} else {
|
||||
leafMesh = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
// Set the new trunk
|
||||
updateTree(trunkMesh, leafMesh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.Spatial.CullHint;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
|
||||
|
||||
/**
|
||||
* Shows some sample people for scale.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class AvatarState extends BaseAppState {
|
||||
|
||||
private Node avatars;
|
||||
private Spatial male;
|
||||
private Spatial female;
|
||||
|
||||
public AvatarState() {
|
||||
}
|
||||
|
||||
public void setShowAvatars( boolean b ) {
|
||||
if( b ) {
|
||||
avatars.setCullHint(CullHint.Inherit);
|
||||
} else {
|
||||
avatars.setCullHint(CullHint.Always);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
AssetManager assets = app.getAssetManager();
|
||||
|
||||
// Add an avatar for scale
|
||||
avatars = new Node("Avatars");
|
||||
avatars.move(2, 0, 0);
|
||||
|
||||
female = (Node)assets.loadModel("Models/female-parts.j3o");
|
||||
BoundingBox bb = (BoundingBox)female.getWorldBound();
|
||||
float height = bb.getYExtent() * 2;
|
||||
float femaleScale = 1.62f / height;
|
||||
female.move(0, bb.getYExtent(), 0);
|
||||
female.setLocalScale(femaleScale);
|
||||
Material mat = GuiGlobals.getInstance().createMaterial(ColorRGBA.Gray, true).getMaterial();
|
||||
mat.setColor("Ambient", ColorRGBA.Gray);
|
||||
female.setMaterial(mat);
|
||||
female.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
avatars.attachChild(female);
|
||||
|
||||
// Add an avatar for scale
|
||||
male = (Node)assets.loadModel("Models/male-parts-no-bones.j3o");
|
||||
bb = (BoundingBox)male.getWorldBound();
|
||||
height = bb.getYExtent() * 2;
|
||||
float maleScale = 1.77f / height;
|
||||
male.move(bb.getCenter().negate());
|
||||
male.move(1, bb.getYExtent(), 0);
|
||||
male.setLocalScale(maleScale);
|
||||
male.setMaterial(mat);
|
||||
male.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
avatars.attachChild(male);
|
||||
|
||||
// For testing
|
||||
//Spatial tree = assets.loadModel("Models/test1.j3o");
|
||||
//tree.setLocalTranslation(-20, 0, -20);
|
||||
//avatars.attachChild(tree);
|
||||
|
||||
|
||||
TreeOptionsState options = getState(TreeOptionsState.class);
|
||||
options.addOptionToggle("Avatars", this, "setShowAvatars").setChecked(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
Node rootNode = ((SimpleApplication)getApplication()).getRootNode();
|
||||
rootNode.attachChild(avatars);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
avatars.removeFromParent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.MemoryUtils;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.HAlignment;
|
||||
import com.simsilica.lemur.Label;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.input.InputMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class DebugHudState extends BaseAppState {
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(DebugHudState.class);
|
||||
|
||||
private VersionedReference<Vector3f> worldLoc;
|
||||
private Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
private Label location;
|
||||
private Label memory;
|
||||
private Label directMem;
|
||||
|
||||
private long lastUsedMem;
|
||||
private long lastMeg100;
|
||||
private long lastDirectMem;
|
||||
private long lastDirectMeg100;
|
||||
private long nextUpdate = System.currentTimeMillis() + 16; // 60 FPS max
|
||||
private long nextMemTime = System.currentTimeMillis() + 1000;
|
||||
|
||||
private long frameCounter;
|
||||
private long lastFrameCheck;
|
||||
private double lastFps;
|
||||
|
||||
private Container debugHud;
|
||||
|
||||
public DebugHudState() {
|
||||
}
|
||||
|
||||
public void toggleHud() {
|
||||
setEnabled( !isEnabled() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
// Always register for our hot key as long as
|
||||
// we are attached.
|
||||
InputMapper inputMapper = GuiGlobals.getInstance().getInputMapper();
|
||||
inputMapper.addDelegate( MainFunctions.F_HUD, this, "toggleHud" );
|
||||
|
||||
worldLoc = getState( MovementState.class ).getWorldPosition().createReference();
|
||||
|
||||
debugHud = new Container();
|
||||
|
||||
location = debugHud.addChild(new Label( "000.00 000.00 00.00" ));
|
||||
location.setTextHAlignment( HAlignment.Right );
|
||||
resetLocation();
|
||||
|
||||
memory = debugHud.addChild(new Label( "Mem: 0.0 meg (0.0 %)" ));
|
||||
memory.setTextHAlignment( HAlignment.Right );
|
||||
|
||||
directMem = debugHud.addChild(new Label( "DMem: 0.0 meg / 0" ));
|
||||
directMem.setTextHAlignment( HAlignment.Right );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
InputMapper inputMapper = GuiGlobals.getInstance().getInputMapper();
|
||||
inputMapper.removeDelegate( MainFunctions.F_HUD, this, "toggleHud" );
|
||||
}
|
||||
|
||||
protected void resetLocation() {
|
||||
Vector3f v = worldLoc.get();
|
||||
String loc = String.format( "%.2f, %.2f, %.2f", v.x, v.y, v.z );
|
||||
location.setText(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
|
||||
frameCounter++;
|
||||
long time = System.currentTimeMillis();
|
||||
if( time < nextUpdate )
|
||||
return;
|
||||
nextUpdate = time + 16; // 60 FPS max
|
||||
|
||||
if( worldLoc.update() ) {
|
||||
resetLocation();
|
||||
}
|
||||
|
||||
/*if( time > lastFrameCheck + 1000 )
|
||||
{
|
||||
long delta = time - lastFrameCheck;
|
||||
lastFrameCheck = time;
|
||||
|
||||
double fps = frameCounter / (delta / 1000.0);
|
||||
frameCounter = 0;
|
||||
if( fps != lastFps )
|
||||
{
|
||||
lastFps = fps;
|
||||
String s = String.format( "FPS: %.2f", fps );
|
||||
fpsText.setText(s);
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
// Refresh memory and other things less often----------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
if( time < nextMemTime )
|
||||
return;
|
||||
nextMemTime = time + 1000;
|
||||
|
||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||
if( lastUsedMem != usedMemory ) {
|
||||
lastUsedMem = usedMemory;
|
||||
|
||||
long maxMemory = runtime.maxMemory();
|
||||
long meg100 = (usedMemory * 100) / (1024 * 1024);
|
||||
if( lastMeg100 != meg100 ) {
|
||||
lastMeg100 = meg100;
|
||||
double meg = meg100 / 100.0;
|
||||
double percent = (usedMemory * 100.0 / maxMemory);
|
||||
String mem = String.format( "Mem: %.2f meg (%.1f %%)", meg, percent );
|
||||
memory.setText( mem );
|
||||
}
|
||||
}
|
||||
|
||||
long directUsage = MemoryUtils.getDirectMemoryUsage();
|
||||
if( directUsage != lastDirectMem ) {
|
||||
lastDirectMem = directUsage;
|
||||
|
||||
long meg100 = (directUsage * 100) / (1024 * 1024);
|
||||
if( lastDirectMeg100 != meg100 ) {
|
||||
long directCount = MemoryUtils.getDirectMemoryCount();
|
||||
double meg = meg100 / 100.0;
|
||||
String mem = String.format( "DMem: %.2f meg / %d", meg, directCount );
|
||||
directMem.setText( mem );
|
||||
}
|
||||
}
|
||||
|
||||
Camera cam = getApplication().getCamera();
|
||||
Vector3f pref = debugHud.getPreferredSize();
|
||||
debugHud.setLocalTranslation(cam.getWidth() - pref.x - 10, cam.getHeight() - 10, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
((SimpleApplication)getApplication()).getGuiNode().attachChild(debugHud);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
debugHud.removeFromParent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2013 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState.BlendMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.Filter;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.Camera.FrustumIntersect;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.queue.GeometryComparator;
|
||||
import com.jme3.renderer.queue.GeometryList;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.shadow.ShadowUtil;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class DropShadowFilter extends Filter {
|
||||
|
||||
private static final int VERTS_PER_SHADOW = 8; // one per box corner
|
||||
private static final int TRIS_PER_SHADOW = 12; // two per face
|
||||
private static final int INDEXES_PER_SHADOW = TRIS_PER_SHADOW * 3;
|
||||
|
||||
private static final Vector3f[] BASE_CORNERS = new Vector3f[] {
|
||||
new Vector3f(-1, -1, 1), // 0
|
||||
new Vector3f( 1, -1, 1), // 1
|
||||
new Vector3f( 1, -1, -1), // 2
|
||||
new Vector3f(-1, -1, -1), // 3
|
||||
new Vector3f(-1, 1, 1), // 4
|
||||
new Vector3f( 1, 1, 1), // 5
|
||||
new Vector3f( 1, 1, -1), // 6
|
||||
new Vector3f(-1, 1, -1) // 7
|
||||
};
|
||||
|
||||
private static final short[] BASE_INDEXES = new short[] {
|
||||
// top
|
||||
4, 5, 6, 4, 6, 7,
|
||||
// bottom
|
||||
3, 2, 1, 3, 1, 0,
|
||||
// +z
|
||||
0, 1, 5, 0, 5, 4,
|
||||
// -z
|
||||
2, 3, 7, 2, 7, 6,
|
||||
// -x
|
||||
3, 0, 4, 3, 4, 7,
|
||||
// +x
|
||||
1, 2, 6, 1, 6, 5
|
||||
};
|
||||
|
||||
private Geometry shadowGeom;
|
||||
private Material shadowMaterial;
|
||||
private Mesh mesh;
|
||||
private int maxShadows;
|
||||
|
||||
private ColorRGBA shadowColor = new ColorRGBA(0, 0, 0, 0.75f);
|
||||
|
||||
private VertexBuffer vbPos;
|
||||
private VertexBuffer vbNormal;
|
||||
private VertexBuffer vbTexCoord;
|
||||
private VertexBuffer vbTexCoord2;
|
||||
private VertexBuffer vbIndex;
|
||||
|
||||
private GeometryList casters;
|
||||
|
||||
public DropShadowFilter() {
|
||||
this(500);
|
||||
}
|
||||
|
||||
public DropShadowFilter( int maxShadows ) {
|
||||
this.maxShadows = maxShadows;
|
||||
}
|
||||
|
||||
public void setShadowIntensity( float f ) {
|
||||
shadowColor.a = f;
|
||||
}
|
||||
|
||||
public float getShadowIntensity() {
|
||||
return shadowColor.a;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRequiresDepthTexture() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initFilter(AssetManager assets, RenderManager rm, ViewPort vp, int w, int h) {
|
||||
|
||||
// Cheating... side effect of being lazy and using a filter
|
||||
// without actually needing to filter anything.
|
||||
material = new Material( assets, "MatDefs/Null.j3md" );
|
||||
|
||||
mesh = new Mesh();
|
||||
|
||||
// Setup the mesh for the max shadows size
|
||||
mesh.setBuffer( Type.Position, 3, BufferUtils.createVector3Buffer(maxShadows * VERTS_PER_SHADOW) );
|
||||
mesh.setBuffer( Type.Normal, 3, BufferUtils.createVector3Buffer(maxShadows * VERTS_PER_SHADOW) );
|
||||
mesh.setBuffer( Type.TexCoord, 3, BufferUtils.createVector3Buffer(maxShadows * VERTS_PER_SHADOW) );
|
||||
mesh.setBuffer( Type.TexCoord2, 3, BufferUtils.createVector3Buffer(maxShadows * VERTS_PER_SHADOW) );
|
||||
mesh.setBuffer( Type.Index, 3, BufferUtils.createShortBuffer(maxShadows * INDEXES_PER_SHADOW) );
|
||||
|
||||
vbPos = mesh.getBuffer(Type.Position);
|
||||
vbNormal = mesh.getBuffer(Type.Normal);
|
||||
vbTexCoord = mesh.getBuffer(Type.TexCoord);
|
||||
vbTexCoord2 = mesh.getBuffer(Type.TexCoord2);
|
||||
vbIndex = mesh.getBuffer(Type.Index);
|
||||
|
||||
|
||||
shadowGeom = new Geometry("shadowVolumes", mesh);
|
||||
Material m = shadowMaterial = new Material( assets, "MatDefs/Shadows.j3md" );
|
||||
m.setColor( "ShadowColor", shadowColor );
|
||||
m.getAdditionalRenderState().setDepthWrite(false);
|
||||
m.getAdditionalRenderState().setDepthTest(false);
|
||||
m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
shadowGeom.setMaterial(m);
|
||||
shadowGeom.setLocalTranslation(0, 100, 0);
|
||||
|
||||
shadowGeom.updateLogicalState(0.1f);
|
||||
shadowGeom.updateGeometricState();
|
||||
|
||||
// Set our custom comparator for shadow casters
|
||||
casters = new GeometryList(new CasterComparator());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Material getMaterial() {
|
||||
return material;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postFrame( RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer ) {
|
||||
|
||||
RenderQueue rq = viewPort.getQueue();
|
||||
for (Spatial scene : viewPort.getScenes()) {
|
||||
//ShadowUtil.getGeometriesInCamFrustum(scene, viewPort.getCamera(), ShadowMode.Cast, casters);
|
||||
}
|
||||
if( casters.size() == 0 )
|
||||
return;
|
||||
|
||||
Camera cam = viewPort.getCamera();
|
||||
BoundingSphere cullCheck = new BoundingSphere();
|
||||
Vector3f pos = new Vector3f();
|
||||
|
||||
Texture frameTex = prevFilterBuffer.getColorBuffer().getTexture();
|
||||
Texture depthTex = prevFilterBuffer.getDepthBuffer().getTexture();
|
||||
shadowMaterial.setTexture("FrameTexture", frameTex);
|
||||
if( frameTex.getImage().getMultiSamples() > 1 ) {
|
||||
shadowMaterial.setInt("NumSamples", frameTex.getImage().getMultiSamples());
|
||||
} else {
|
||||
shadowMaterial.clearParam("NumSamples");
|
||||
}
|
||||
|
||||
shadowMaterial.setTexture("DepthTexture", depthTex);
|
||||
if( depthTex.getImage().getMultiSamples() > 1 ) {
|
||||
shadowMaterial.setInt("NumSamplesDepth", depthTex.getImage().getMultiSamples());
|
||||
} else {
|
||||
shadowMaterial.clearParam("NumSamplesDepth");
|
||||
}
|
||||
|
||||
int size = casters.size();
|
||||
if( size > maxShadows ) {
|
||||
// Give the shadows their best chance by sorting them.
|
||||
casters.setCamera(cam);
|
||||
casters.sort();
|
||||
}
|
||||
|
||||
FloatBuffer bPos = (FloatBuffer)vbPos.getData().rewind();
|
||||
FloatBuffer bNormal = (FloatBuffer)vbNormal.getData().rewind();
|
||||
FloatBuffer bTexCoord = (FloatBuffer)vbTexCoord.getData().rewind();
|
||||
FloatBuffer bTexCoord2 = (FloatBuffer)vbTexCoord2.getData().rewind();
|
||||
ShortBuffer bIndex = (ShortBuffer)vbIndex.getData().rewind();
|
||||
|
||||
|
||||
Matrix4f viewMatrix = cam.getViewMatrix();
|
||||
Matrix4f worldMatrix = new Matrix4f();
|
||||
Matrix4f worldViewMatrix = new Matrix4f();
|
||||
float[] angles = new float[3];
|
||||
Vector3f vTemp = new Vector3f();
|
||||
Vector3f vert = new Vector3f();
|
||||
Vector3f viewDir = new Vector3f();
|
||||
Vector3f boxScale = new Vector3f();
|
||||
|
||||
int rendered = 0;
|
||||
for( int i = 0; i < size; i++ ) {
|
||||
Geometry g = casters.get(i);
|
||||
|
||||
// Use the geometry bounds. We assumg it is still y-up
|
||||
// and merely rotated. It's a decent enough approximiation
|
||||
// in many cases and will produce better shadows for oblong
|
||||
// objects than a simple round radius would.
|
||||
BoundingBox bounds = (BoundingBox)g.getModelBound();
|
||||
|
||||
float scale = g.getWorldScale().x;
|
||||
float xEx = bounds.getXExtent() * scale;
|
||||
float yEx = bounds.getYExtent() * scale;
|
||||
float zEx = bounds.getZExtent() * scale;
|
||||
float volumeHeight = Math.max(yEx, Math.min(xEx,zEx));
|
||||
|
||||
float xOffset = bounds.getCenter().x * scale;
|
||||
float yOffset = bounds.getCenter().y * scale;
|
||||
float zOffset = bounds.getCenter().z * scale;
|
||||
|
||||
yOffset -= yEx;
|
||||
yOffset -= volumeHeight * 0.5f;
|
||||
yOffset += 0.01f;
|
||||
|
||||
pos.set(g.getWorldTranslation());
|
||||
pos.addLocal(xOffset, yOffset, zOffset);
|
||||
|
||||
// A conservative approximation that works because our shadow volume
|
||||
// is really just a round blob
|
||||
float radius = Math.max(xEx, Math.max(yEx, zEx));
|
||||
cullCheck.setCenter(pos);
|
||||
cullCheck.setRadius(radius);
|
||||
|
||||
int save = cam.getPlaneState();
|
||||
cam.setPlaneState(0);
|
||||
FrustumIntersect intersect = cam.contains(cullCheck);
|
||||
cam.setPlaneState(save);
|
||||
|
||||
if( intersect == FrustumIntersect.Outside ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boxScale.set(0.5f/xEx, 0.5f/volumeHeight, 0.5f/zEx);
|
||||
|
||||
Quaternion quat = g.getWorldRotation();
|
||||
angles = quat.toAngles(angles);
|
||||
|
||||
Quaternion rotation = new Quaternion().fromAngles(0, angles[1], 0);
|
||||
Quaternion invRotation = rotation.inverse();
|
||||
worldMatrix.setTranslation(pos);
|
||||
worldMatrix.setRotationQuaternion(rotation);
|
||||
|
||||
worldViewMatrix.set(viewMatrix);
|
||||
worldViewMatrix.multLocal(worldMatrix);
|
||||
|
||||
// Setup the vertexes for each corner
|
||||
for( int j = 0; j < VERTS_PER_SHADOW; j++ ) {
|
||||
vTemp.set(BASE_CORNERS[j].x * xEx,
|
||||
BASE_CORNERS[j].y * volumeHeight,
|
||||
BASE_CORNERS[j].z * zEx);
|
||||
|
||||
// Get the transformed coordinate in world space
|
||||
vert = worldMatrix.mult(vTemp, vert);
|
||||
bPos.put(vert.x).put(vert.y).put(vert.z);
|
||||
|
||||
// Now calculate the view direction
|
||||
vert = vert.subtractLocal(cam.getLocation());
|
||||
vert.normalizeLocal();
|
||||
viewDir = invRotation.mult(vert, viewDir);
|
||||
bNormal.put(viewDir.x).put(viewDir.y).put(viewDir.z);
|
||||
|
||||
// Model space is easy to calculate
|
||||
bTexCoord.put(BASE_CORNERS[j].x * xEx + xEx);
|
||||
bTexCoord.put(BASE_CORNERS[j].y * volumeHeight + volumeHeight);
|
||||
bTexCoord.put(BASE_CORNERS[j].z * zEx + zEx);
|
||||
|
||||
// And so is the scale... since it's always the same
|
||||
bTexCoord2.put(boxScale.x).put(boxScale.y).put(boxScale.z);
|
||||
}
|
||||
|
||||
// Fill in the index buffer
|
||||
for( int j = 0; j < INDEXES_PER_SHADOW; j++ ) {
|
||||
bIndex.put( (short)(BASE_INDEXES[j] + rendered * VERTS_PER_SHADOW) );
|
||||
}
|
||||
|
||||
rendered++;
|
||||
if( rendered >= maxShadows ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( rendered > 0 ) {
|
||||
// Need to zero out the left-overs
|
||||
for( int i = rendered; i < maxShadows; i++ ) {
|
||||
for( int j = 0; j < INDEXES_PER_SHADOW; j++ ) {
|
||||
bIndex.put((short)0);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the buffers
|
||||
bPos.rewind();
|
||||
bNormal.rewind();
|
||||
bTexCoord.rewind();
|
||||
bTexCoord2.rewind();
|
||||
bIndex.rewind();
|
||||
|
||||
vbPos.updateData(bPos);
|
||||
vbNormal.updateData(bNormal);
|
||||
vbTexCoord.updateData(bTexCoord);
|
||||
vbTexCoord2.updateData(bTexCoord2);
|
||||
vbIndex.updateData(bIndex);
|
||||
|
||||
shadowGeom.updateGeometricState();
|
||||
renderManager.renderGeometry(shadowGeom);
|
||||
}
|
||||
|
||||
casters.clear();
|
||||
}
|
||||
|
||||
private class CasterComparator implements GeometryComparator {
|
||||
|
||||
private Camera cam;
|
||||
private final Vector3f tempVec = new Vector3f();
|
||||
private final Vector3f tempVec2 = new Vector3f();
|
||||
|
||||
public void setCamera( Camera cam ) {
|
||||
this.cam = cam;
|
||||
}
|
||||
|
||||
public float distanceToCam( Geometry spat ) {
|
||||
if( spat == null ) {
|
||||
return Float.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
if( spat.queueDistance != Float.NEGATIVE_INFINITY ) {
|
||||
return spat.queueDistance;
|
||||
}
|
||||
|
||||
Vector3f camPosition = cam.getLocation();
|
||||
Vector3f viewVector = cam.getDirection(tempVec2);
|
||||
Vector3f spatPosition;
|
||||
|
||||
if( spat.getWorldBound() != null ) {
|
||||
spatPosition = spat.getWorldBound().getCenter();
|
||||
} else {
|
||||
spatPosition = spat.getWorldTranslation();
|
||||
}
|
||||
|
||||
spatPosition.subtract(camPosition, tempVec);
|
||||
spat.queueDistance = tempVec.dot(viewVector);
|
||||
|
||||
return spat.queueDistance;
|
||||
}
|
||||
|
||||
public int compare( Geometry o1, Geometry o2 ) {
|
||||
// Front to back sort
|
||||
float d1 = distanceToCam(o1);
|
||||
float d2 = distanceToCam(o2);
|
||||
|
||||
if( d1 == d2 ) {
|
||||
return 0;
|
||||
} else if( d1 < d2 ) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.export.binary.BinaryExporter;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.SceneGraphVisitorAdapter;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.system.JmeSystem;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.simsilica.lemur.Button;
|
||||
import com.simsilica.lemur.Command;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.HAlignment;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import org.progeeks.json.JsonParser;
|
||||
import org.progeeks.json.JsonPrinter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Manages the file-related actions.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class FileActionsState extends BaseAppState {
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(FileActionsState.class);
|
||||
|
||||
private Container buttons;
|
||||
|
||||
public FileActionsState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
buttons = new Container();
|
||||
|
||||
Button saveParms = buttons.addChild(new Button("Save Parms", "glass"));
|
||||
saveParms.addClickCommands(new SaveTreeParameters());
|
||||
saveParms.setTextHAlignment(HAlignment.Center);
|
||||
|
||||
Button loadParms = buttons.addChild(new Button("Load Parms", "glass"), 1);
|
||||
loadParms.addClickCommands(new LoadTreeParameters());
|
||||
loadParms.setTextHAlignment(HAlignment.Center);
|
||||
|
||||
Button saveJ3o = buttons.addChild(new Button("Export j3o", "glass"), 2);
|
||||
saveJ3o.addClickCommands(new SaveJ3o());
|
||||
saveJ3o.setTextHAlignment(HAlignment.Center);
|
||||
|
||||
Button saveTreeAtlas = buttons.addChild(new Button("Save Tree Atlas", "glass"));
|
||||
saveTreeAtlas.addClickCommands(new SaveTreeAtlas());
|
||||
saveTreeAtlas.setTextHAlignment(HAlignment.Center);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
getState(TreeOptionsState.class).getContents().addChild(buttons);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
getState(TreeOptionsState.class).getContents().removeChild(buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out the stuff that we probably don't want to... or
|
||||
* really shouldn't be saving in a j3o. We should also remove
|
||||
* the Impostor LODs at least until there is a way to save and/or
|
||||
* embed the impostor image... but I don't right now.
|
||||
*/
|
||||
protected Spatial filterClone( Spatial tree ) {
|
||||
Spatial result = tree.deepClone();
|
||||
result.depthFirstTraversal(new SceneGraphVisitorAdapter() {
|
||||
@Override
|
||||
public void visit( Geometry g ) {
|
||||
if( g.getName().startsWith("wire:") ) {
|
||||
g.removeFromParent();
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, File> lastRoots = new HashMap<String, File>();
|
||||
protected File chooseFile( final String description, final boolean save, String... extensions ) {
|
||||
//final String ext = (!extension.startsWith(".") ? "." : "") + extension.toLowerCase();
|
||||
final String[] exts = new String[extensions.length];
|
||||
for( int i = 0; i < exts.length; i++ ) {
|
||||
exts[i] = (!extensions[i].startsWith(".") ? "." : "") + extensions[i].toLowerCase();
|
||||
}
|
||||
|
||||
File lastRoot = lastRoots.get(exts[0]);
|
||||
if( lastRoot == null ) {
|
||||
lastRoot = new File(".");
|
||||
}
|
||||
|
||||
log.info("Creating file chooser dialog...");
|
||||
final JFileChooser openDialog = new JFileChooser();
|
||||
|
||||
openDialog.setDialogTitle("Choose Location");
|
||||
if( save ) {
|
||||
openDialog.setDialogType(JFileChooser.SAVE_DIALOG);
|
||||
} else {
|
||||
openDialog.setDialogType(JFileChooser.OPEN_DIALOG);
|
||||
}
|
||||
openDialog.setFileFilter(new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept( File file ) {
|
||||
if( file.isDirectory() ) {
|
||||
return true;
|
||||
}
|
||||
String s = file.getName().toLowerCase();
|
||||
for( String e : exts ) {
|
||||
if( s.endsWith(e) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
});
|
||||
openDialog.setCurrentDirectory(lastRoot);
|
||||
|
||||
log.info("Opening file chooser dialog...");
|
||||
|
||||
final int[] dialogResult = new int[1]; //JFileChooser.CANCEL_OPTION ;
|
||||
if( !SwingUtilities.isEventDispatchThread() ) {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if( save ) {
|
||||
dialogResult[0] = openDialog.showSaveDialog(null);
|
||||
} else {
|
||||
dialogResult[0] = openDialog.showOpenDialog(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch( Exception e ) {
|
||||
throw new RuntimeException("Error invoking", e);
|
||||
}
|
||||
} else {
|
||||
if( save ) {
|
||||
dialogResult[0] = openDialog.showSaveDialog(null);
|
||||
} else {
|
||||
dialogResult[0] = openDialog.showOpenDialog(null);
|
||||
}
|
||||
}
|
||||
|
||||
if( dialogResult[0] != JFileChooser.APPROVE_OPTION ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File result = openDialog.getSelectedFile();
|
||||
lastRoots.put(exts[0], result.getParentFile());
|
||||
|
||||
if( save && !result.getName().toLowerCase().endsWith(exts[0]) ) {
|
||||
result = new File(result.getParent(), result.getName() + exts[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void writeJson( File f, Map<String, Object> map ) throws IOException {
|
||||
|
||||
FileWriter out = new FileWriter(f);
|
||||
try {
|
||||
JsonPrinter json = new JsonPrinter();
|
||||
json.write(map, out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, Object> readJson( File f ) throws IOException {
|
||||
FileReader in = new FileReader(f);
|
||||
try {
|
||||
JsonParser json = new JsonParser();
|
||||
return (Map<String, Object>)json.parse(in);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveJ3o( File f ) throws IOException {
|
||||
BinaryExporter exporter = BinaryExporter.getInstance();
|
||||
log.info("Writing:" + f);
|
||||
exporter.save(filterClone(getState(ForestGridState.class).getMainTreeNode()), f);
|
||||
}
|
||||
|
||||
public void saveTreeParameters( File f ) throws IOException {
|
||||
TreeParameters treeParameters = getState(TreeParametersState.class).getTreeParameters();
|
||||
Map<String, Object> map = treeParameters.toMap();
|
||||
log.info("Writing:" + f);
|
||||
writeJson(f, map);
|
||||
}
|
||||
|
||||
public void loadTreeParameters( File f ) throws IOException {
|
||||
Map<String, Object> map = readJson(f);
|
||||
TreeParameters treeParameters = getState(TreeParametersState.class).getTreeParameters();
|
||||
treeParameters.fromMap(map);
|
||||
getState(TreeParametersState.class).refreshTreePanels();
|
||||
getState(ForestGridState.class).rebuild();
|
||||
}
|
||||
|
||||
public void savePng( File f, Image img ) throws IOException {
|
||||
OutputStream out = new FileOutputStream(f);
|
||||
try {
|
||||
JmeSystem.writeImageFile(out, "png", img.getData(0), img.getWidth(), img.getHeight());
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveTreeAtlas( File f ) throws IOException {
|
||||
|
||||
Image diffuse = getState(AtlasGeneratorState.class).getDiffuseMap();
|
||||
savePng(f, diffuse);
|
||||
|
||||
String normalName = f.getName();
|
||||
if( normalName.toLowerCase().endsWith(".png") ) {
|
||||
normalName = normalName.substring(0, normalName.length() - ".png".length());
|
||||
}
|
||||
f = new File(f.getParentFile(), normalName + "-normals.png");
|
||||
|
||||
Image normal = getState(AtlasGeneratorState.class).getNormalMap();
|
||||
savePng(f, normal);
|
||||
}
|
||||
|
||||
private class SaveJ3o implements Command<Button> {
|
||||
|
||||
@Override
|
||||
public void execute( Button source ) {
|
||||
System.out.println( "Saving j3o..." );
|
||||
File f = chooseFile("JME object file", true, "j3o");
|
||||
System.out.println( "File:" + f );
|
||||
if( f == null ) {
|
||||
return;
|
||||
}
|
||||
if( f.exists() ) {
|
||||
int result = JOptionPane.showConfirmDialog(null, "Overwrite file?\n" + f,
|
||||
"File Already Exists",
|
||||
JOptionPane.YES_NO_CANCEL_OPTION);
|
||||
if( result != JOptionPane.YES_OPTION ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
saveJ3o(f);
|
||||
} catch( IOException e ) {
|
||||
log.error( "Error saving tree to:" + f, e );
|
||||
JmeSystem.showErrorDialog("Error writing file:" + f + "\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SaveTreeParameters implements Command<Button> {
|
||||
|
||||
@Override
|
||||
public void execute( Button source ) {
|
||||
System.out.println( "Saving stparms.json..." );
|
||||
File f = chooseFile("Tree Parameters File", true, "simap");
|
||||
System.out.println( "File:" + f );
|
||||
if( f == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( f.exists() ) {
|
||||
int result = JOptionPane.showConfirmDialog(null, "Overwrite file?\n" + f,
|
||||
"File Already Exists",
|
||||
JOptionPane.YES_NO_CANCEL_OPTION);
|
||||
if( result != JOptionPane.YES_OPTION ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
saveTreeParameters(f);
|
||||
} catch( IOException e ) {
|
||||
log.error("Error writing file:" + f, e);
|
||||
JmeSystem.showErrorDialog("Error writing file:" + f + "\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadTreeParameters implements Command<Button> {
|
||||
|
||||
@Override
|
||||
public void execute( Button source ) {
|
||||
System.out.println( "Loading stparms.json..." );
|
||||
File f = chooseFile("Tree Parameters File", false, "simap", "simap.json");
|
||||
System.out.println( "File:" + f );
|
||||
if( f == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loadTreeParameters(f);
|
||||
} catch( IOException e ) {
|
||||
log.error("Error reading file:" + f, e);
|
||||
JmeSystem.showErrorDialog("Error reading file:" + f + "\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SaveTreeAtlas implements Command<Button> {
|
||||
|
||||
@Override
|
||||
public void execute( Button source ) {
|
||||
|
||||
File f = chooseFile("Tree Atlas Images", true, "png");
|
||||
if( f == null ) {
|
||||
return;
|
||||
}
|
||||
if( f.exists() ) {
|
||||
int result = JOptionPane.showConfirmDialog(null, "Overwrite file?\n" + f,
|
||||
"File Already Exists",
|
||||
JOptionPane.YES_NO_CANCEL_OPTION);
|
||||
if( result != JOptionPane.YES_OPTION ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
saveTreeAtlas(f);
|
||||
} catch( IOException e ) {
|
||||
log.error("Error writing file:" + f, e);
|
||||
JmeSystem.showErrorDialog("Error writing file:" + f + "\n" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
369
simarboreal/src/main/java/com/simsilica/arboreal/ForestGrid.java
Normal file
369
simarboreal/src/main/java/com/simsilica/arboreal/ForestGrid.java
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.scene.Node;
|
||||
import com.simsilica.builder.Builder;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class ForestGrid {
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private float spacing;
|
||||
private int rootSeed;
|
||||
private int seedRange;
|
||||
|
||||
private float rotationVariation = 1f;
|
||||
private float leanVariation = 0.1f;
|
||||
private float scaleVariation = 0.3f;
|
||||
private float positionVariation = 0;
|
||||
|
||||
private Node root;
|
||||
|
||||
private Builder builder;
|
||||
private TreeBuilderReference[][] trees;
|
||||
|
||||
private TreeParameters treeParameters;
|
||||
private Material treeMaterial;
|
||||
private Material wireMaterial;
|
||||
private Material leafMaterial;
|
||||
private Material flatMaterial;
|
||||
private Material impostorMaterial;
|
||||
|
||||
private boolean showWireframe;
|
||||
|
||||
public ForestGrid( TreeParameters treeParameters,
|
||||
Material treeMaterial,
|
||||
Material wireMaterial,
|
||||
Material leafMaterial,
|
||||
Material flatMaterial,
|
||||
Material impostorMaterial,
|
||||
Builder builder ) {
|
||||
this.treeParameters = treeParameters;
|
||||
this.treeMaterial = treeMaterial;
|
||||
this.wireMaterial = wireMaterial;
|
||||
this.leafMaterial = leafMaterial;
|
||||
this.flatMaterial = flatMaterial;
|
||||
this.impostorMaterial = impostorMaterial;
|
||||
this.builder = builder;
|
||||
this.root = new Node("Forest");
|
||||
this.spacing = 5;
|
||||
this.seedRange = 9;
|
||||
setSize(1, 1);
|
||||
}
|
||||
|
||||
public Node getRootNode() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public TreeBuilderReference getTree( int i, int j ) {
|
||||
return trees[i][j];
|
||||
}
|
||||
|
||||
public void setShowWireframe( boolean b ) {
|
||||
if( this.showWireframe == b ) {
|
||||
return;
|
||||
}
|
||||
this.showWireframe = b;
|
||||
refreshWireframe();
|
||||
}
|
||||
|
||||
public void setSeedRange( int range ) {
|
||||
if( this.seedRange == range ) {
|
||||
return;
|
||||
}
|
||||
this.seedRange = range;
|
||||
refreshSeed();
|
||||
}
|
||||
|
||||
public int getSeedRange() {
|
||||
return seedRange;
|
||||
}
|
||||
|
||||
public void setSpacing( float f ) {
|
||||
if( this.spacing == f ) {
|
||||
return;
|
||||
}
|
||||
this.spacing = f;
|
||||
refreshVariation();
|
||||
}
|
||||
|
||||
public float getSpacing() {
|
||||
return spacing;
|
||||
}
|
||||
|
||||
public void setRotationVariation( float f ) {
|
||||
if( this.rotationVariation == f ) {
|
||||
return;
|
||||
}
|
||||
this.rotationVariation = f;
|
||||
refreshVariation();
|
||||
}
|
||||
|
||||
public float getRotationVariation() {
|
||||
return rotationVariation;
|
||||
}
|
||||
|
||||
public void setLeanVariation( float f ) {
|
||||
if( this.leanVariation == f ) {
|
||||
return;
|
||||
}
|
||||
this.leanVariation = f;
|
||||
refreshVariation();
|
||||
}
|
||||
|
||||
public float getLeanVariation() {
|
||||
return leanVariation;
|
||||
}
|
||||
|
||||
public void setScaleVariation( float f ) {
|
||||
if( this.scaleVariation == f ) {
|
||||
return;
|
||||
}
|
||||
this.scaleVariation = f;
|
||||
refreshVariation();
|
||||
}
|
||||
|
||||
public float getScaleVariation() {
|
||||
return scaleVariation;
|
||||
}
|
||||
|
||||
public void setPositionVariation( float f ) {
|
||||
if( this.positionVariation == f ) {
|
||||
return;
|
||||
}
|
||||
this.positionVariation = f;
|
||||
refreshVariation();
|
||||
}
|
||||
|
||||
public float getPositionVariation() {
|
||||
return positionVariation;
|
||||
}
|
||||
|
||||
|
||||
public void setWidth( int width ) {
|
||||
setSize(width, height);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setHeight( int height ) {
|
||||
setSize(width, height);
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setSize( int width, int height ) {
|
||||
|
||||
if( this.width == width && this.height == height ) {
|
||||
return;
|
||||
}
|
||||
|
||||
builder.pause();
|
||||
|
||||
if( trees != null ) {
|
||||
// Cancel and remove the ones that will go away
|
||||
if( this.width > width ) {
|
||||
for( int i = width; i < this.width; i++ ) {
|
||||
for( int j = 0; j < this.height; j++ ) {
|
||||
trees[i][j].getTreeNode().removeFromParent();
|
||||
builder.release(trees[i][j]);
|
||||
trees[i][j] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( this.height > height ) {
|
||||
for( int i = 0; i < this.width; i++ ) {
|
||||
for( int j = height; j < this.height; j++ ) {
|
||||
trees[i][j].getTreeNode().removeFromParent();
|
||||
builder.release(trees[i][j]);
|
||||
trees[i][j] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy what we can into a new structure
|
||||
TreeBuilderReference[][] newTrees = new TreeBuilderReference[width][height];
|
||||
int w = Math.min(width, this.width);
|
||||
int h = Math.min(height, this.height);
|
||||
for( int i = 0; i < w; i++ ) {
|
||||
for( int j = 0; j < h; j++ ) {
|
||||
newTrees[i][j] = trees[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
trees = newTrees;
|
||||
|
||||
// Fill in any missing cells
|
||||
for( int i = 0; i < width; i++ ) {
|
||||
for( int j = 0; j < height; j++ ) {
|
||||
if( trees[i][j] == null ) {
|
||||
trees[i][j] = new TreeBuilderReference(treeParameters,
|
||||
treeMaterial,
|
||||
wireMaterial,
|
||||
leafMaterial,
|
||||
flatMaterial,
|
||||
impostorMaterial);
|
||||
Node tree = trees[i][j].getTreeNode();
|
||||
tree.setLocalTranslation(i * spacing, 0, j * spacing);
|
||||
tree.setLocalScale(treeParameters.getBaseScale());
|
||||
root.attachChild(tree);
|
||||
builder.build(trees[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshSeed();
|
||||
refreshWireframe();
|
||||
refreshVariation();
|
||||
|
||||
builder.resume();
|
||||
}
|
||||
|
||||
public void markChanged() {
|
||||
for( int i = 0; i < width; i++ ) {
|
||||
for( int j = 0; j < height; j++ ) {
|
||||
if( trees[i][j] == null ) {
|
||||
continue;
|
||||
}
|
||||
trees[i][j].markChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Because it's a value we sort of cache and there
|
||||
// is no external way to detect it's changed.
|
||||
refreshSeed();
|
||||
}
|
||||
|
||||
public void rebuild() {
|
||||
for( int i = 0; i < width; i++ ) {
|
||||
for( int j = 0; j < height; j++ ) {
|
||||
if( trees[i][j] == null ) {
|
||||
continue;
|
||||
}
|
||||
builder.build(trees[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshWireframe() {
|
||||
for( int i = 0; i < width; i++ ) {
|
||||
for( int j = 0; j < height; j++ ) {
|
||||
if( trees[i][j] == null ) {
|
||||
continue;
|
||||
}
|
||||
trees[i][j].setWireFrame(showWireframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshSeed() {
|
||||
int index = 0;
|
||||
int rootSeed = treeParameters.getSeed();
|
||||
for( int i = 0; i < width; i++ ) {
|
||||
for( int j = 0; j < height; j++ ) {
|
||||
if( trees[i][j] == null ) {
|
||||
continue;
|
||||
}
|
||||
trees[i][j].setSeed(rootSeed + (index % seedRange));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshVariation() {
|
||||
|
||||
// If there is only one tree then we won't vary it
|
||||
// at all... we could also have made sure 0,0 was always
|
||||
// 0 variation but I think this will be ok
|
||||
if( width == 1 && height == 1 ) {
|
||||
// Just make sure it doesn't have anything weird
|
||||
Node tree = trees[0][0].getTreeNode();
|
||||
tree.setLocalScale(treeParameters.getBaseScale());
|
||||
tree.setLocalRotation(new Quaternion());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Random rand = new Random(0);
|
||||
for( int i = 0; i < width; i++ ) {
|
||||
for( int j = 0; j < height; j++ ) {
|
||||
if( trees[i][j] == null ) {
|
||||
continue;
|
||||
}
|
||||
Node tree = trees[i][j].getTreeNode();
|
||||
float x = leanVariation * ((rand.nextFloat() * 2) - 1) * FastMath.QUARTER_PI;
|
||||
float y = leanVariation * ((rand.nextFloat() * 2) - 1) * FastMath.QUARTER_PI;
|
||||
float scale = scaleVariation * ((rand.nextFloat() * 2) - 1);
|
||||
float angle = rotationVariation * ((rand.nextFloat() * 2) - 1) * FastMath.TWO_PI;
|
||||
|
||||
Quaternion rot = new Quaternion();
|
||||
rot = rot.mult(new Quaternion().fromAngles(0, angle, 0));
|
||||
rot = rot.mult(new Quaternion().fromAngles(x, 0, 0));
|
||||
rot = rot.mult(new Quaternion().fromAngles(0, 0, y));
|
||||
|
||||
tree.setLocalRotation(rot);
|
||||
|
||||
if( scale < 0 )
|
||||
scale *= 0.5f;
|
||||
tree.setLocalScale((1 + scale) * treeParameters.getBaseScale());
|
||||
|
||||
float xOffset = positionVariation * (rand.nextFloat() - 0.5f) * spacing;
|
||||
float yOffset = positionVariation * (rand.nextFloat() - 0.5f) * spacing;
|
||||
tree.setLocalTranslation(i * spacing + xOffset, 0, j * spacing + yOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.simsilica.builder.BuilderState;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState.BlendMode;
|
||||
import com.jme3.material.RenderState.FaceCullMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector4f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.simsilica.fx.sky.SkyState;
|
||||
import com.simsilica.lemur.Checkbox;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.Label;
|
||||
import com.simsilica.lemur.component.SpringGridLayout;
|
||||
import com.simsilica.lemur.core.VersionedHolder;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.props.PropertyPanel;
|
||||
import com.simsilica.lemur.style.ElementId;
|
||||
|
||||
|
||||
/**
|
||||
* Manages a ForestGrid instance as well as hooking up
|
||||
* the UI and materials necessary for it to work.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class ForestGridState extends BaseAppState {
|
||||
|
||||
private ForestGrid forestGrid;
|
||||
private TreeBuilderReference mainTree;
|
||||
|
||||
private Label vertsLabel;
|
||||
private Label trisLabel;
|
||||
private VersionedHolder<Boolean> building = new VersionedHolder<Boolean>(true);
|
||||
|
||||
private VersionedReference<TreeParameters> treeParameters;
|
||||
private VersionedReference<PropertyPanel> gridParameters;
|
||||
|
||||
private Texture bark;
|
||||
private Texture barkNormals;
|
||||
private Texture barkBumps;
|
||||
private Texture leafAtlas;
|
||||
private Texture testPattern;
|
||||
private Texture noise;
|
||||
|
||||
private Material treeMaterial;
|
||||
private Material wireMaterial;
|
||||
private Material flatMaterial;
|
||||
private Material impostorMaterial;
|
||||
private Material leafMaterial;
|
||||
|
||||
private boolean showTestPattern = false;
|
||||
private boolean showTrunkBumps = true;
|
||||
private boolean useWind = false;
|
||||
private boolean useScattering = false;
|
||||
|
||||
private Vector4f windCurve = new Vector4f();
|
||||
|
||||
public ForestGridState() {
|
||||
}
|
||||
|
||||
public VersionedReference<Boolean> getBuildingRef() {
|
||||
return building.createReference();
|
||||
}
|
||||
|
||||
public TreeBuilderReference getMainTree() {
|
||||
return mainTree;
|
||||
}
|
||||
|
||||
public Node getMainTreeNode() {
|
||||
return mainTree.getTreeNode();
|
||||
}
|
||||
|
||||
public void rebuild() {
|
||||
building.setObject(true);
|
||||
forestGrid.markChanged();
|
||||
forestGrid.rebuild();
|
||||
}
|
||||
|
||||
public void setShowTestPattern( boolean b ) {
|
||||
if( this.showTestPattern == b ) {
|
||||
return;
|
||||
}
|
||||
this.showTestPattern = b;
|
||||
if( showTestPattern ) {
|
||||
treeMaterial.setTexture("DiffuseMap", testPattern);
|
||||
flatMaterial.setTexture("DiffuseMap", testPattern);
|
||||
} else {
|
||||
treeMaterial.setTexture("DiffuseMap", bark);
|
||||
flatMaterial.setTexture("DiffuseMap", bark);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getShowTestPattern() {
|
||||
return showTestPattern;
|
||||
}
|
||||
|
||||
public void setShowTrunkBumps( boolean b ) {
|
||||
if( this.showTrunkBumps == b ) {
|
||||
return;
|
||||
}
|
||||
this.showTrunkBumps = b;
|
||||
if( showTrunkBumps ) {
|
||||
treeMaterial.setTexture("NormalMap", barkNormals);
|
||||
treeMaterial.setTexture("ParallaxMap", barkBumps);
|
||||
} else {
|
||||
treeMaterial.clearParam("NormalMap");
|
||||
treeMaterial.clearParam("ParallaxMap");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getShowTrunkBumps() {
|
||||
return showTrunkBumps;
|
||||
}
|
||||
|
||||
public void setUseWind( boolean b ) {
|
||||
if( this.useWind == b ) {
|
||||
return;
|
||||
}
|
||||
this.useWind = b;
|
||||
treeMaterial.setBoolean("UseWind", useWind);
|
||||
leafMaterial.setBoolean("UseWind", useWind);
|
||||
flatMaterial.setBoolean("UseWind", useWind);
|
||||
impostorMaterial.setBoolean("UseWind", useWind);
|
||||
}
|
||||
|
||||
public boolean getUseWind() {
|
||||
return useWind;
|
||||
}
|
||||
|
||||
public void setUseScattering( boolean b ) {
|
||||
if( this.useScattering == b ) {
|
||||
return;
|
||||
}
|
||||
this.useScattering = b;
|
||||
if( treeMaterial != null ) {
|
||||
treeMaterial.setBoolean("UseScattering", useScattering);
|
||||
leafMaterial.setBoolean("UseScattering", useScattering);
|
||||
flatMaterial.setBoolean("UseScattering", useScattering);
|
||||
impostorMaterial.setBoolean("UseScattering", useScattering);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
AssetManager assets = app.getAssetManager();
|
||||
|
||||
bark = assets.loadTexture("Textures/bark128.jpg");
|
||||
bark.setWrap(Texture.WrapMode.Repeat);
|
||||
barkNormals = assets.loadTexture("Textures/bark128-norm.jpg");
|
||||
barkNormals.setWrap(Texture.WrapMode.Repeat);
|
||||
barkBumps = assets.loadTexture("Textures/bark128-bump.png");
|
||||
barkBumps.setWrap(Texture.WrapMode.Repeat);
|
||||
|
||||
testPattern = assets.loadTexture("Textures/test-pattern.png");
|
||||
testPattern.setWrap(Texture.WrapMode.Repeat);
|
||||
|
||||
leafAtlas = assets.loadTexture("Textures/leaf-atlas.png");
|
||||
leafAtlas.setWrap(Texture.WrapMode.Repeat);
|
||||
|
||||
noise = assets.loadTexture("Textures/noise-x3-512.png");
|
||||
noise.setWrap(Texture.WrapMode.Repeat);
|
||||
|
||||
treeParameters = getState(TreeParametersState.class).getTreeParametersRef();
|
||||
|
||||
forestGrid = new ForestGrid(treeParameters.get(),
|
||||
getTreeMaterial(),
|
||||
getWireMaterial(),
|
||||
getLeafMaterial(),
|
||||
getFlatMaterial(),
|
||||
getImpostorMaterial(),
|
||||
getState(BuilderState.class).getBuilder());
|
||||
|
||||
mainTree = forestGrid.getTree(0, 0);
|
||||
|
||||
|
||||
// Add some options check boxes for rendering
|
||||
TreeOptionsState options = getState(TreeOptionsState.class);
|
||||
options.addOptionToggle("Wireframe", forestGrid, "setShowWireframe");
|
||||
options.addOptionToggle("Test Pattern", this, "setShowTestPattern");
|
||||
Checkbox cb = options.addOptionToggle("Bump-map", this, "setShowTrunkBumps");
|
||||
cb.setChecked(true);
|
||||
|
||||
options.addOptionToggle("Wind", this, "setUseWind");
|
||||
|
||||
PropertyPanel properties = new PropertyPanel("glass");
|
||||
gridParameters = properties.createReference();
|
||||
options.getParameterTabs().addTab("Grid", properties);
|
||||
|
||||
properties.addIntProperty("Width", forestGrid, "width", 1, 10, 1);
|
||||
properties.addIntProperty("Height", forestGrid, "height", 1, 10, 1);
|
||||
properties.addFloatProperty("Spacing (m)", forestGrid, "spacing", 0.3f, 40, 0.1f);
|
||||
properties.addIntProperty("Seed Range", forestGrid, "seedRange", 1, 100, 1);
|
||||
properties.addFloatProperty("Rotation Variation (*)", forestGrid, "rotationVariation", 0, 1, 0.01f);
|
||||
properties.addFloatProperty("Lean Variation (*)", forestGrid, "leanVariation", 0, 1, 0.01f);
|
||||
properties.addFloatProperty("Scale Variation (*)", forestGrid, "scaleVariation", 0, 1, 0.01f);
|
||||
properties.addFloatProperty("Position Variation (*)", forestGrid, "positionVariation", 0, 1, 0.01f);
|
||||
|
||||
|
||||
// Add a stats panel to the bottom... could have done it as another
|
||||
// state but we already manage all of the geometry here
|
||||
Container stats = options.getContents().addChild(new Container(new SpringGridLayout(), new ElementId("stats"), "glass"));
|
||||
vertsLabel = stats.addChild(new Label("verts:", "glass"));
|
||||
trisLabel = stats.addChild(new Label("tris:", "glass"), 1);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
Node rootNode = ((SimpleApplication)getApplication()).getRootNode();
|
||||
rootNode.attachChild(forestGrid.getRootNode());
|
||||
}
|
||||
|
||||
private float nextUpdateCheck = 0.1f;
|
||||
private float time;
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
|
||||
|
||||
// Calculate the wind curves
|
||||
time += tpf;
|
||||
windCurve.x = time;
|
||||
|
||||
nextUpdateCheck += tpf;
|
||||
if( nextUpdateCheck <= 0.1f ) {
|
||||
return;
|
||||
}
|
||||
nextUpdateCheck = 0;
|
||||
|
||||
boolean changed = treeParameters.update();
|
||||
if( gridParameters.update() ) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if( changed ) {
|
||||
building.setObject(true);
|
||||
forestGrid.markChanged();
|
||||
forestGrid.rebuild();
|
||||
refreshStats();
|
||||
refreshWindParms();
|
||||
}
|
||||
|
||||
if( building.getObject() && getState(BuilderState.class).getBuilder().getPending() == 0 ) {
|
||||
building.setObject(false);
|
||||
refreshStats();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
forestGrid.getRootNode().removeFromParent();
|
||||
}
|
||||
|
||||
protected void refreshStats() {
|
||||
if( building.getObject() ) {
|
||||
vertsLabel.setText("verts: ???");
|
||||
trisLabel.setText("tris: ???");
|
||||
} else {
|
||||
vertsLabel.setText("verts: " + mainTree.getVertexCount());
|
||||
trisLabel.setText("tris: " + mainTree.getTriangleCount());
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshWindParms() {
|
||||
if( treeMaterial == null ) {
|
||||
// too soon
|
||||
return;
|
||||
}
|
||||
TreeParameters tp = treeParameters.get();
|
||||
|
||||
treeMaterial.setFloat("FlexHeight", tp.getFlexHeight());
|
||||
treeMaterial.setFloat("TrunkFlexibility", tp.getTrunkFlexibility());
|
||||
treeMaterial.setFloat("BranchFlexibility", tp.getBranchFlexibility());
|
||||
|
||||
leafMaterial.setFloat("FlexHeight", tp.getFlexHeight());
|
||||
leafMaterial.setFloat("TrunkFlexibility", tp.getTrunkFlexibility());
|
||||
leafMaterial.setFloat("BranchFlexibility", tp.getBranchFlexibility());
|
||||
|
||||
flatMaterial.setFloat("FlexHeight", tp.getFlexHeight());
|
||||
flatMaterial.setFloat("TrunkFlexibility", tp.getTrunkFlexibility());
|
||||
flatMaterial.setFloat("BranchFlexibility", tp.getBranchFlexibility());
|
||||
|
||||
impostorMaterial.setFloat("TrunkFlexibility", tp.getTrunkFlexibility());
|
||||
|
||||
}
|
||||
|
||||
public Material getTreeMaterial() {
|
||||
if( treeMaterial != null ) {
|
||||
return treeMaterial;
|
||||
}
|
||||
|
||||
treeMaterial = GuiGlobals.getInstance().createMaterial(ColorRGBA.Yellow, true).getMaterial();
|
||||
treeMaterial = new Material(getApplication().getAssetManager(), "MatDefs/TreeLighting.j3md");
|
||||
treeMaterial.setColor("Diffuse", ColorRGBA.White);
|
||||
treeMaterial.setColor("Ambient", ColorRGBA.White);
|
||||
treeMaterial.setBoolean("UseMaterialColors", true);
|
||||
treeMaterial.setBoolean("UseWind", false);
|
||||
treeMaterial.setTexture("WindNoise", noise);
|
||||
treeMaterial.setTexture("DiffuseMap", bark);
|
||||
treeMaterial.setTexture("NormalMap", barkNormals);
|
||||
treeMaterial.setTexture("ParallaxMap", barkBumps);
|
||||
//treeMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
|
||||
|
||||
// Hook it up to the atmospherics
|
||||
getState(SkyState.class).getAtmosphericParameters().applyGroundParameters(treeMaterial, true);
|
||||
|
||||
return treeMaterial;
|
||||
}
|
||||
|
||||
public Material getFlatMaterial() {
|
||||
if( flatMaterial != null ) {
|
||||
return flatMaterial;
|
||||
}
|
||||
|
||||
flatMaterial = new Material(getApplication().getAssetManager(), "MatDefs/AxisBillboardLighting.j3md");
|
||||
flatMaterial.setColor("Diffuse", ColorRGBA.White);
|
||||
flatMaterial.setColor("Ambient", ColorRGBA.White);
|
||||
flatMaterial.setBoolean("UseMaterialColors", true);
|
||||
flatMaterial.setTexture("DiffuseMap", bark);
|
||||
flatMaterial.setBoolean("UseWind", false);
|
||||
flatMaterial.setTexture("WindNoise", noise);
|
||||
flatMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
|
||||
|
||||
// Hook it up to the atmospherics
|
||||
getState(SkyState.class).getAtmosphericParameters().applyGroundParameters(flatMaterial, true);
|
||||
|
||||
return flatMaterial;
|
||||
}
|
||||
|
||||
public Material getImpostorMaterial() {
|
||||
if( impostorMaterial != null ) {
|
||||
return impostorMaterial;
|
||||
}
|
||||
|
||||
impostorMaterial = new Material(getApplication().getAssetManager(), "MatDefs/IndexedBillboardLighting.j3md");
|
||||
impostorMaterial.setColor("Diffuse", ColorRGBA.White);
|
||||
impostorMaterial.setColor("Ambient", ColorRGBA.White);
|
||||
impostorMaterial.setBoolean("UseMaterialColors", true);
|
||||
impostorMaterial.setFloat("AlphaDiscardThreshold", 0.5f);
|
||||
|
||||
impostorMaterial.setBoolean("UseWind", false);
|
||||
impostorMaterial.setTexture("WindNoise", noise);
|
||||
|
||||
impostorMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
|
||||
impostorMaterial.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
|
||||
// Hook it up to the atmospherics
|
||||
getState(SkyState.class).getAtmosphericParameters().applyGroundParameters(impostorMaterial, true);
|
||||
|
||||
return impostorMaterial;
|
||||
}
|
||||
|
||||
public Material getWireMaterial() {
|
||||
if( wireMaterial != null ) {
|
||||
return wireMaterial;
|
||||
}
|
||||
Material mat = GuiGlobals.getInstance().createMaterial(ColorRGBA.Yellow, false).getMaterial();
|
||||
wireMaterial = mat;
|
||||
mat.getAdditionalRenderState().setWireframe(true);
|
||||
return mat;
|
||||
}
|
||||
|
||||
public Material getLeafMaterial() {
|
||||
if( leafMaterial != null ) {
|
||||
return leafMaterial;
|
||||
}
|
||||
|
||||
AssetManager assets = getApplication().getAssetManager();
|
||||
leafMaterial = new Material(assets, "MatDefs/LeafLighting.j3md");
|
||||
leafMaterial.setColor("Diffuse", ColorRGBA.White);
|
||||
leafMaterial.setColor("Ambient", ColorRGBA.White);
|
||||
leafMaterial.setBoolean("UseMaterialColors", true);
|
||||
leafMaterial.setTexture("DiffuseMap", leafAtlas);
|
||||
|
||||
leafMaterial.setBoolean("UseWind", false);
|
||||
leafMaterial.setTexture("WindNoise", noise);
|
||||
|
||||
leafMaterial.setFloat("AlphaDiscardThreshold", 0.5f);
|
||||
leafMaterial.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
|
||||
// Hook it up to the atmospherics
|
||||
getState(SkyState.class).getAtmosphericParameters().applyGroundParameters(leafMaterial, true);
|
||||
|
||||
return leafMaterial;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.texture.Texture.WrapMode;
|
||||
import com.jme3.util.TangentBinormalGenerator;
|
||||
import com.simsilica.fx.sky.SkyState;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.geom.MBox;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class GroundState extends BaseAppState {
|
||||
|
||||
private Material greenMaterial;
|
||||
private Material groundMaterial;
|
||||
private Geometry ground;
|
||||
|
||||
private boolean showGrass = false;
|
||||
private boolean useScattering = false;
|
||||
|
||||
public GroundState() {
|
||||
}
|
||||
|
||||
public void setUseScattering( boolean b ) {
|
||||
if( this.useScattering == b ) {
|
||||
return;
|
||||
}
|
||||
this.useScattering = b;
|
||||
resetScattering();
|
||||
}
|
||||
|
||||
public boolean getUseScattering() {
|
||||
return useScattering;
|
||||
}
|
||||
|
||||
public void setShowGrass( boolean b ) {
|
||||
this.showGrass = b;
|
||||
resetGrass();
|
||||
}
|
||||
|
||||
public boolean getShowGrass() {
|
||||
return showGrass;
|
||||
}
|
||||
|
||||
protected void resetGrass() {
|
||||
if( ground == null ) {
|
||||
return;
|
||||
}
|
||||
if( showGrass ) {
|
||||
ground.setMaterial(groundMaterial);
|
||||
} else {
|
||||
ground.setMaterial(greenMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetScattering() {
|
||||
if( groundMaterial != null ) {
|
||||
groundMaterial.setBoolean("UseScattering", useScattering);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
AssetManager assets = app.getAssetManager();
|
||||
|
||||
groundMaterial = GuiGlobals.getInstance().createMaterial(ColorRGBA.Green, true).getMaterial();
|
||||
|
||||
MBox b = new MBox(500, 0, 500, 50, 0, 50, MBox.TOP_MASK);
|
||||
TangentBinormalGenerator.generate(b);
|
||||
b.scaleTextureCoordinates(new Vector2f(1000, 1000));
|
||||
ground = new Geometry("Box", b);
|
||||
|
||||
greenMaterial = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
|
||||
greenMaterial.setColor("Diffuse", ColorRGBA.Green);
|
||||
greenMaterial.setColor("Ambient", ColorRGBA.Green);
|
||||
greenMaterial.setBoolean("UseMaterialColors", true);
|
||||
ground.setMaterial(greenMaterial);
|
||||
|
||||
|
||||
groundMaterial = new Material(assets, "MatDefs/MultiResolution.j3md");
|
||||
groundMaterial.setColor("Diffuse", ColorRGBA.White);
|
||||
groundMaterial.setColor("Specular", ColorRGBA.White);
|
||||
groundMaterial.setColor("Ambient", ColorRGBA.White);
|
||||
groundMaterial.setFloat("Shininess", 0);
|
||||
groundMaterial.setBoolean("UseMaterialColors", true);
|
||||
|
||||
// Hook up the scattering parameters
|
||||
getState(SkyState.class).getAtmosphericParameters().applyGroundParameters(groundMaterial, true);
|
||||
|
||||
Texture texture;
|
||||
|
||||
//texture = assets.loadTexture("Textures/test-pattern.png");
|
||||
texture = assets.loadTexture("Textures/grass.jpg");
|
||||
texture.setWrap(WrapMode.Repeat);
|
||||
groundMaterial.setTexture("DiffuseMap", texture);
|
||||
|
||||
texture = assets.loadTexture("Textures/grass-flat.jpg");
|
||||
texture.setWrap(WrapMode.Repeat);
|
||||
groundMaterial.setTexture("BackgroundDiffuseMap", texture);
|
||||
|
||||
texture = assets.loadTexture("Textures/brown-dirt-norm.jpg");
|
||||
//texture = assets.loadTexture("Textures/bark128-norm.jpg");
|
||||
texture.setWrap(WrapMode.Repeat);
|
||||
groundMaterial.setTexture("NormalMap", texture);
|
||||
|
||||
texture = assets.loadTexture("Textures/noise-x3-512.png");
|
||||
texture.setWrap(WrapMode.Repeat);
|
||||
groundMaterial.setTexture("NoiseMap", texture);
|
||||
|
||||
resetGrass();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
Node rootNode = ((SimpleApplication)getApplication()).getRootNode();
|
||||
rootNode.attachChild(ground);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
ground.removeFromParent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.simsilica.lemur.core.VersionedHolder;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class LegacyLightingState extends BaseAppState {
|
||||
|
||||
private VersionedHolder<Vector3f> lightDir = new VersionedHolder<Vector3f>();
|
||||
|
||||
private ColorRGBA sunColor;
|
||||
private DirectionalLight sun;
|
||||
private ColorRGBA ambientColor;
|
||||
private AmbientLight ambient;
|
||||
private float timeOfDay = FastMath.atan2(1, 0.3f) / FastMath.PI;
|
||||
private float inclination = FastMath.HALF_PI - FastMath.atan2(1, 0.4f);
|
||||
|
||||
private Node rootNode; // the one we added the lights to
|
||||
|
||||
public LegacyLightingState() {
|
||||
lightDir.setObject(new Vector3f(-0.2f, -1, -0.3f).normalizeLocal());
|
||||
this.sunColor = ColorRGBA.White.mult(2);
|
||||
this.ambientColor = new ColorRGBA(0.25f, 0.25f, 0.25f, 1);
|
||||
}
|
||||
|
||||
public DirectionalLight getSun() {
|
||||
return sun;
|
||||
}
|
||||
|
||||
public VersionedReference<Vector3f> getLightDirRef() {
|
||||
return lightDir.createReference();
|
||||
}
|
||||
|
||||
public void setTimeOfDay( float f ) {
|
||||
if( this.timeOfDay == f ) {
|
||||
return;
|
||||
}
|
||||
this.timeOfDay = f;
|
||||
resetLightDir();
|
||||
}
|
||||
|
||||
public float getTimeOfDay() {
|
||||
return timeOfDay;
|
||||
}
|
||||
|
||||
protected void resetLightDir() {
|
||||
float angle = timeOfDay * FastMath.PI;
|
||||
|
||||
Quaternion q1 = new Quaternion().fromAngles(0, 0, (angle - FastMath.HALF_PI));
|
||||
Quaternion q2 = new Quaternion().fromAngles(inclination, 0, 0);
|
||||
Vector3f dir = q2.mult(q1).mult(Vector3f.UNIT_Y.negate());
|
||||
lightDir.setObject(dir);
|
||||
sun.setDirection(lightDir.getObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
sun = new DirectionalLight();
|
||||
sun.setColor(sunColor);
|
||||
sun.setDirection(lightDir.getObject());
|
||||
|
||||
ambient = new AmbientLight();
|
||||
ambient.setColor(ambientColor);
|
||||
resetLightDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
rootNode = ((SimpleApplication)getApplication()).getRootNode();
|
||||
rootNode.addLight(sun);
|
||||
rootNode.addLight(ambient);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
rootNode.removeLight(sun);
|
||||
rootNode.removeLight(ambient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial.CullHint;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class LegacySkyState extends BaseAppState {
|
||||
|
||||
private ColorRGBA skyColor;
|
||||
private ColorRGBA sunColor;
|
||||
private Geometry sky;
|
||||
private Geometry sun;
|
||||
private boolean showSky;
|
||||
|
||||
private VersionedReference<Vector3f> lightDir;
|
||||
|
||||
public LegacySkyState() {
|
||||
this.sunColor = new ColorRGBA(1, 1, 0.9f, 1);
|
||||
this.skyColor = new ColorRGBA(0.5f, 0.5f, 1f, 1);
|
||||
}
|
||||
|
||||
public void setShowSky( boolean b ) {
|
||||
this.showSky = b;
|
||||
resetShowSky();
|
||||
}
|
||||
|
||||
protected void resetShowSky() {
|
||||
if( sky == null ) {
|
||||
return;
|
||||
}
|
||||
if( showSky ) {
|
||||
sky.setCullHint(CullHint.Inherit);
|
||||
} else {
|
||||
sky.setCullHint(CullHint.Always);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getShowSky() {
|
||||
return sky.getCullHint() == CullHint.Inherit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
lightDir = getState(LegacyLightingState.class).getLightDirRef();
|
||||
|
||||
|
||||
// Add a sun sphere
|
||||
Sphere orb = new Sphere(6, 12, 50);
|
||||
sun = new Geometry("Sun", orb);
|
||||
sun.setMaterial(GuiGlobals.getInstance().createMaterial(sunColor, false).getMaterial());
|
||||
sun.move(lightDir.get().mult(-900));
|
||||
|
||||
|
||||
// Add a sky sphere
|
||||
orb = new Sphere(6, 12, 800, true, true);
|
||||
sky = new Geometry("Sky", orb);
|
||||
sky.setMaterial(GuiGlobals.getInstance().createMaterial(skyColor, false).getMaterial());
|
||||
sky.setQueueBucket(Bucket.Sky);
|
||||
sky.setIgnoreTransform(true);
|
||||
|
||||
resetShowSky();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
Node rootNode = ((SimpleApplication)getApplication()).getRootNode();
|
||||
rootNode.attachChild(sun);
|
||||
rootNode.attachChild(sky);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
if( lightDir.update() ) {
|
||||
sun.setLocalTranslation(lightDir.get().mult(-900));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
sun.removeFromParent();
|
||||
sky.removeFromParent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.simsilica.lemur.input.FunctionId;
|
||||
import com.simsilica.lemur.input.InputMapper;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @version $Revision$
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class MainFunctions
|
||||
{
|
||||
public static final String GROUP = "Main";
|
||||
|
||||
public static final FunctionId F_TOGGLE_MOVEMENT = new FunctionId(GROUP, "Toggle Movement");
|
||||
public static final FunctionId F_HUD = new FunctionId(GROUP, "HUD Toggle");
|
||||
public static final FunctionId F_RECORD_VIDEO = new FunctionId(GROUP, "Record Video");
|
||||
|
||||
public static void initializeDefaultMappings( InputMapper inputMapper )
|
||||
{
|
||||
inputMapper.map( F_TOGGLE_MOVEMENT, KeyInput.KEY_SPACE );
|
||||
inputMapper.map( F_HUD, KeyInput.KEY_F3 );
|
||||
inputMapper.map( F_RECORD_VIDEO, KeyInput.KEY_F12 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.simsilica.lemur.input.Axis;
|
||||
import com.simsilica.lemur.input.Button;
|
||||
import com.simsilica.lemur.input.FunctionId;
|
||||
import com.simsilica.lemur.input.InputMapper;
|
||||
import com.simsilica.lemur.input.InputState;
|
||||
|
||||
|
||||
/**
|
||||
* The shared FunctionIds for camera movement input and
|
||||
* some default input mappings.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class MovementFunctions {
|
||||
|
||||
/**
|
||||
* Camera movement functions will all be in the "Movement" group
|
||||
* to make it easy to toggle camera input on and off.
|
||||
*/
|
||||
public static final String GROUP_MOVEMENT = "Movement";
|
||||
|
||||
public static final FunctionId F_Y_LOOK = new FunctionId(GROUP_MOVEMENT, "Y Look");
|
||||
public static final FunctionId F_X_LOOK = new FunctionId(GROUP_MOVEMENT, "X Look");
|
||||
|
||||
public static final FunctionId F_MOVE = new FunctionId(GROUP_MOVEMENT, "Move");
|
||||
public static final FunctionId F_STRAFE = new FunctionId(GROUP_MOVEMENT, "Strafe");
|
||||
|
||||
public static final FunctionId F_ELEVATE = new FunctionId(GROUP_MOVEMENT, "Elevate");
|
||||
|
||||
public static final FunctionId F_RUN = new FunctionId(GROUP_MOVEMENT, "Run");
|
||||
public static final FunctionId F_SUPER_RUN = new FunctionId(GROUP_MOVEMENT, "Super Run");
|
||||
|
||||
/**
|
||||
* We capture some input mappings in case they need
|
||||
* to be scaled or flipped later. Mouse and joystick input are
|
||||
* often configured with sensitivity settings and this is one
|
||||
* way to do that built into InputMapper.
|
||||
*/
|
||||
public static InputMapper.Mapping MOUSE_X_LOOK;
|
||||
public static InputMapper.Mapping MOUSE_Y_LOOK;
|
||||
public static InputMapper.Mapping JOY_X_LOOK;
|
||||
public static InputMapper.Mapping JOY_Y_LOOK;
|
||||
|
||||
public static void initializeDefaultMappings( InputMapper inputMapper )
|
||||
{
|
||||
// The joystick Y axes are backwards on game pads... forward
|
||||
// is negative. So we'll flip it over in the mapping.
|
||||
inputMapper.map( F_MOVE, InputState.Negative, Axis.JOYSTICK_LEFT_Y );
|
||||
|
||||
// Here a similar approach is used to map the W and S keys with
|
||||
// 'S' being negative. In this way, W and S now act like a joystick
|
||||
// axis.
|
||||
inputMapper.map( F_MOVE, KeyInput.KEY_W );
|
||||
inputMapper.map( F_MOVE, InputState.Negative, KeyInput.KEY_S );
|
||||
|
||||
// Strafing is setup similar to move.
|
||||
inputMapper.map( F_STRAFE, Axis.JOYSTICK_LEFT_X );
|
||||
inputMapper.map( F_STRAFE, KeyInput.KEY_D );
|
||||
inputMapper.map( F_STRAFE, InputState.Negative, KeyInput.KEY_A );
|
||||
|
||||
// Elevation only has key mappings but we still treat it like
|
||||
// one "axis".
|
||||
inputMapper.map( F_ELEVATE, KeyInput.KEY_Q );
|
||||
inputMapper.map( F_ELEVATE, InputState.Negative, KeyInput.KEY_Z );
|
||||
|
||||
// For the mouse and joystick mappings, we remember the mapping object
|
||||
// in case we want to flip it or adjust its sensitivity later. This
|
||||
// is better than trying to do it in the function handler because it's
|
||||
// quite often input specific. The user may want to flip the joystick
|
||||
// but not the mouse or adjust sensitivity on one and not the other.
|
||||
MOUSE_X_LOOK = inputMapper.map( F_X_LOOK, Axis.MOUSE_X );
|
||||
JOY_X_LOOK = inputMapper.map( F_X_LOOK, Axis.JOYSTICK_RIGHT_X );
|
||||
inputMapper.map( F_X_LOOK, KeyInput.KEY_RIGHT );
|
||||
inputMapper.map( F_X_LOOK, InputState.Negative, KeyInput.KEY_LEFT );
|
||||
|
||||
MOUSE_Y_LOOK = inputMapper.map( F_Y_LOOK, Axis.MOUSE_Y );
|
||||
JOY_Y_LOOK = inputMapper.map( F_Y_LOOK, Axis.JOYSTICK_RIGHT_Y );
|
||||
inputMapper.map( F_Y_LOOK, KeyInput.KEY_UP );
|
||||
inputMapper.map( F_Y_LOOK, InputState.Negative, KeyInput.KEY_DOWN );
|
||||
|
||||
// Here we give run its own function. Note that it was also possible
|
||||
// to treat running like an axis used instead of MOVE by combining
|
||||
// keys. For example:
|
||||
// map( F_RUN, KeyInput.KEY_LSHIFT, KeyInput.KEY_W )
|
||||
// Another approach would have been to set the 'scale' of the non-run
|
||||
// mapping to 0.5 or something and simply map SHIFT+W without the
|
||||
// scaling. That would have done two things:
|
||||
// 1) the camera code simply needs to look for MOVE and not worry
|
||||
// about a separate 'run mode' because walking will be half-speed
|
||||
// move anyway.
|
||||
// 2) the joystick would always be running which means the player simply
|
||||
// runs by using full stick.
|
||||
//
|
||||
// Instead, I've opted for a run mode toggle. Mostly because it demonstrates
|
||||
// state mapping where as all of the other inputs so far are going to
|
||||
// be treated as analog. That means our joystick needs a run button also.
|
||||
inputMapper.map( F_RUN, KeyInput.KEY_LSHIFT );
|
||||
inputMapper.map( F_RUN, Button.JOYSTICK_RIGHT1 );
|
||||
|
||||
inputMapper.map( F_SUPER_RUN, KeyInput.KEY_LSHIFT, KeyInput.KEY_LCONTROL );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.math.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public interface MovementHandler
|
||||
{
|
||||
public void setLocation( Vector3f loc );
|
||||
public Vector3f getLocation();
|
||||
|
||||
public void setFacing( Quaternion facing );
|
||||
public Quaternion getFacing();
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.core.VersionedHolder;
|
||||
import com.simsilica.lemur.core.VersionedObject;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.input.AnalogFunctionListener;
|
||||
import com.simsilica.lemur.input.FunctionId;
|
||||
import com.simsilica.lemur.input.InputMapper;
|
||||
import com.simsilica.lemur.input.InputState;
|
||||
import com.simsilica.lemur.input.StateFunctionListener;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author PSpeed
|
||||
*/
|
||||
public class MovementState extends BaseAppState
|
||||
implements AnalogFunctionListener, StateFunctionListener {
|
||||
|
||||
private InputMapper inputMapper;
|
||||
private MovementHandler mover;
|
||||
private double turnSpeed = 2.5; // one half complete revolution in 2.5 seconds
|
||||
private double yaw = FastMath.PI;
|
||||
private double pitch;
|
||||
private double maxPitch = FastMath.HALF_PI;
|
||||
private double minPitch = -FastMath.HALF_PI;
|
||||
private Quaternion facing = new Quaternion().fromAngles((float)pitch, (float)yaw, 0);
|
||||
private double forward;
|
||||
private double side;
|
||||
private double elevation;
|
||||
private double speed = 3.0;
|
||||
private VersionedHolder<Vector3f> worldPos = new VersionedHolder<Vector3f>(new Vector3f());
|
||||
|
||||
public MovementState() {
|
||||
}
|
||||
|
||||
public void toggleEnabled() {
|
||||
setEnabled(!isEnabled());
|
||||
}
|
||||
|
||||
public VersionedObject<Vector3f> getWorldPosition() {
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
public void setMovementHandler( MovementHandler mover ) {
|
||||
this.mover = mover;
|
||||
updateFacing();
|
||||
}
|
||||
|
||||
public MovementHandler getMovementHandler() {
|
||||
return mover;
|
||||
}
|
||||
|
||||
public void setPitch( double pitch ) {
|
||||
this.pitch = pitch;
|
||||
updateFacing();
|
||||
}
|
||||
|
||||
public double getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
public void setYaw( double yaw ) {
|
||||
this.yaw = yaw;
|
||||
updateFacing();
|
||||
}
|
||||
|
||||
public double getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
public void setRotation( Quaternion rotation ) {
|
||||
// Do our best
|
||||
float[] angle = rotation.toAngles(null);
|
||||
this.pitch = angle[0];
|
||||
this.yaw = angle[1];
|
||||
updateFacing();
|
||||
}
|
||||
|
||||
public Quaternion getRotation() {
|
||||
return mover.getFacing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
if( this.mover == null ) {
|
||||
this.mover = new CameraMovementHandler(app.getCamera());
|
||||
}
|
||||
|
||||
if( inputMapper == null )
|
||||
inputMapper = GuiGlobals.getInstance().getInputMapper();
|
||||
|
||||
inputMapper.addDelegate( MainFunctions.F_TOGGLE_MOVEMENT, this, "toggleEnabled" );
|
||||
|
||||
// Most of the movement functions are treated as analog.
|
||||
inputMapper.addAnalogListener(this,
|
||||
MovementFunctions.F_Y_LOOK,
|
||||
MovementFunctions.F_X_LOOK,
|
||||
MovementFunctions.F_MOVE,
|
||||
MovementFunctions.F_ELEVATE,
|
||||
MovementFunctions.F_STRAFE);
|
||||
|
||||
// Only run mode is treated as a 'state' or a trinary value.
|
||||
// (Positive, Off, Negative) and in this case we only care about
|
||||
// Positive and Off. See MovementFunctions for a description
|
||||
// of alternate ways this could have been done.
|
||||
inputMapper.addStateListener(this,
|
||||
MovementFunctions.F_RUN,
|
||||
MovementFunctions.F_SUPER_RUN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
|
||||
inputMapper.removeDelegate( MainFunctions.F_TOGGLE_MOVEMENT, this, "toggleEnabled" );
|
||||
|
||||
inputMapper.removeAnalogListener( this,
|
||||
MovementFunctions.F_Y_LOOK,
|
||||
MovementFunctions.F_X_LOOK,
|
||||
MovementFunctions.F_MOVE,
|
||||
MovementFunctions.F_ELEVATE,
|
||||
MovementFunctions.F_STRAFE);
|
||||
inputMapper.removeStateListener( this,
|
||||
MovementFunctions.F_RUN,
|
||||
MovementFunctions.F_SUPER_RUN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
// Make sure our input group is enabled
|
||||
inputMapper.activateGroup( MovementFunctions.GROUP_MOVEMENT );
|
||||
|
||||
// And kill the cursor
|
||||
GuiGlobals.getInstance().setCursorEventsEnabled(false);
|
||||
|
||||
// A 'bug' in Lemur causes it to miss turning the cursor off if
|
||||
// we are enabled before the MouseAppState is initialized.
|
||||
getApplication().getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
inputMapper.deactivateGroup( MovementFunctions.GROUP_MOVEMENT );
|
||||
GuiGlobals.getInstance().setCursorEventsEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
|
||||
// 'integrate' camera position based on the current move, strafe,
|
||||
// and elevation speeds.
|
||||
if( forward != 0 || side != 0 || elevation != 0 ) {
|
||||
Vector3f loc = mover.getLocation();
|
||||
|
||||
Quaternion rot = mover.getFacing();
|
||||
Vector3f move = rot.mult(Vector3f.UNIT_Z).multLocal((float)(forward * speed * tpf));
|
||||
Vector3f strafe = rot.mult(Vector3f.UNIT_X).multLocal((float)(side * speed * tpf));
|
||||
|
||||
// Note: this camera moves 'elevation' along the camera's current up
|
||||
// vector because I find it more intuitive in free flight.
|
||||
Vector3f elev = rot.mult(Vector3f.UNIT_Y).multLocal((float)(elevation * speed * tpf));
|
||||
|
||||
loc = loc.add(move).add(strafe).add(elev);
|
||||
mover.setLocation(loc);
|
||||
worldPos.setObject(loc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the StateFunctionListener interface.
|
||||
*/
|
||||
@Override
|
||||
public void valueChanged( FunctionId func, InputState value, double tpf ) {
|
||||
|
||||
// Change the speed based on the current run mode
|
||||
// Another option would have been to use the value
|
||||
// directly:
|
||||
// speed = 3 + value.asNumber() * 5
|
||||
//...but I felt it was slightly less clear here.
|
||||
boolean b = value == InputState.Positive;
|
||||
if( func == MovementFunctions.F_RUN ) {
|
||||
if( b ) {
|
||||
speed = 10;
|
||||
} else {
|
||||
speed = 3;
|
||||
}
|
||||
} else if( func == MovementFunctions.F_SUPER_RUN ) {
|
||||
if( b ) {
|
||||
speed = 20;
|
||||
} else {
|
||||
speed = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the AnalogFunctionListener interface.
|
||||
*/
|
||||
@Override
|
||||
public void valueActive( FunctionId func, double value, double tpf ) {
|
||||
|
||||
// Setup rotations and movements speeds based on current
|
||||
// axes states.
|
||||
if( func == MovementFunctions.F_Y_LOOK ) {
|
||||
pitch += -value * tpf * turnSpeed;
|
||||
if( pitch < minPitch )
|
||||
pitch = minPitch;
|
||||
if( pitch > maxPitch )
|
||||
pitch = maxPitch;
|
||||
} else if( func == MovementFunctions.F_X_LOOK ) {
|
||||
double yawDelta = -value * tpf * turnSpeed;
|
||||
yaw += yawDelta;
|
||||
if( yaw < 0 )
|
||||
yaw += Math.PI * 2;
|
||||
if( yaw > Math.PI * 2 )
|
||||
yaw -= Math.PI * 2;
|
||||
} else if( func == MovementFunctions.F_MOVE ) {
|
||||
this.forward = value;
|
||||
return;
|
||||
} else if( func == MovementFunctions.F_STRAFE ) {
|
||||
this.side = -value;
|
||||
return;
|
||||
} else if( func == MovementFunctions.F_ELEVATE ) {
|
||||
this.elevation = value;
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
updateFacing();
|
||||
}
|
||||
|
||||
protected void updateFacing() {
|
||||
facing.fromAngles( (float)pitch, (float)yaw, 0 );
|
||||
mover.setFacing(facing);
|
||||
}
|
||||
|
||||
public static class CameraMovementHandler implements MovementHandler {
|
||||
private Camera camera;
|
||||
|
||||
public CameraMovementHandler( Camera camera ) {
|
||||
this.camera = camera;
|
||||
}
|
||||
|
||||
public void setLocation( Vector3f loc ) {
|
||||
camera.setLocation(loc);
|
||||
}
|
||||
|
||||
public Vector3f getLocation() {
|
||||
return camera.getLocation();
|
||||
}
|
||||
|
||||
public void setFacing( Quaternion facing ) {
|
||||
camera.setRotation(facing);
|
||||
}
|
||||
|
||||
public Quaternion getFacing() {
|
||||
return camera.getRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.shadow.DirectionalLightShadowFilter;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.simsilica.fx.LightingState;
|
||||
import com.simsilica.lemur.Checkbox;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.props.PropertyPanel;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class PostProcessorState extends BaseAppState {
|
||||
|
||||
private FilterPostProcessor fpp;
|
||||
private DirectionalLightShadowFilter shadows1;
|
||||
private DropShadowFilter shadows2;
|
||||
private VersionedReference<Vector3f> lightDir;
|
||||
private float shadowStrength = 0.3f;
|
||||
|
||||
private Checkbox shadowToggle;
|
||||
private Checkbox dropShadowToggle;
|
||||
|
||||
public PostProcessorState() {
|
||||
}
|
||||
|
||||
public void setEnableShadows( boolean b ) {
|
||||
shadows1.setEnabled(b);
|
||||
if( b && shadows2.isEnabled() ) {
|
||||
shadows2.setEnabled(false);
|
||||
dropShadowToggle.setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnableDropShadows( boolean b ) {
|
||||
shadows2.setEnabled(b);
|
||||
if( b && shadows1.isEnabled() ) {
|
||||
shadows1.setEnabled(false);
|
||||
shadowToggle.setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShadowStrength( float f ) {
|
||||
if( this.shadowStrength == f ) {
|
||||
return;
|
||||
}
|
||||
this.shadowStrength = f;
|
||||
resetShadowStrength();
|
||||
}
|
||||
|
||||
public float getShadowStrength() {
|
||||
return shadowStrength;
|
||||
}
|
||||
|
||||
protected void resetShadowStrength() {
|
||||
shadows1.setShadowIntensity(shadowStrength);
|
||||
shadows2.setShadowIntensity(shadowStrength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
lightDir = getState(LightingState.class).getLightDirRef();
|
||||
|
||||
AssetManager assets = app.getAssetManager();
|
||||
|
||||
fpp = new FilterPostProcessor(assets);
|
||||
AppSettings settings = app.getContext().getSettings();
|
||||
if( settings.getSamples() != 0 )
|
||||
{
|
||||
fpp.setNumSamples(settings.getSamples());
|
||||
}
|
||||
|
||||
shadows2 = new DropShadowFilter();
|
||||
shadows2.setEnabled(false);
|
||||
fpp.addFilter(shadows2);
|
||||
|
||||
shadows1 = new DirectionalLightShadowFilter(assets, 4096, 4);
|
||||
shadows1.setShadowIntensity(0.3f);
|
||||
shadows1.setLight(getState(LightingState.class).getSun());
|
||||
shadows1.setEnabled(false);
|
||||
fpp.addFilter(shadows1);
|
||||
|
||||
// Go ahead and add some UI stuff here... normally I'd
|
||||
// put it in another state but it doesn't seem worth it.
|
||||
TreeOptionsState options = getState(TreeOptionsState.class);
|
||||
shadowToggle = options.addOptionToggle("Shadows", this, "setEnableShadows");
|
||||
dropShadowToggle = options.addOptionToggle("Drop Shadows", this, "setEnableDropShadows");
|
||||
|
||||
|
||||
// Add some parameters to the main options window
|
||||
PropertyPanel properties = new PropertyPanel("glass");
|
||||
getState(TreeOptionsState.class).getViewSettings().addChild(properties);
|
||||
|
||||
properties.addFloatProperty("Time of day (*)", getState(LightingState.class),
|
||||
"timeOfDay", 0, 1, 0.05f);
|
||||
properties.addFloatProperty("Shadow Strength (*)", this, "shadowStrength", 0, 1, 0.05f);
|
||||
|
||||
resetShadowStrength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
getApplication().getViewPort().addProcessor(fpp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
if( lightDir.update() ) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
getApplication().getViewPort().removeProcessor(fpp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* ${Id}
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial.CullHint;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.simsilica.arboreal.mesh.BillboardedLeavesMeshGenerator;
|
||||
import com.simsilica.arboreal.mesh.FlatPolyTreeMeshGenerator;
|
||||
import com.simsilica.arboreal.mesh.LodSwitchControl;
|
||||
import com.simsilica.arboreal.mesh.SkinnedTreeMeshGenerator;
|
||||
import com.simsilica.arboreal.mesh.Vertex;
|
||||
import com.simsilica.builder.Builder;
|
||||
import com.simsilica.builder.BuilderReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class TreeBuilderReference implements BuilderReference
|
||||
{
|
||||
static Logger log = LoggerFactory.getLogger(TreeBuilderReference.class);
|
||||
|
||||
private int priority;
|
||||
|
||||
private int seed;
|
||||
private Material treeMaterial;
|
||||
private Material wireMaterial;
|
||||
private Material leafMaterial;
|
||||
private Material flatMaterial;
|
||||
private Material flatWireMaterial;
|
||||
private Material impostorMaterial;
|
||||
private Material impostorWireMaterial;
|
||||
private TreeParameters treeParameters;
|
||||
|
||||
private Node treeNode;
|
||||
|
||||
private LevelGeometry[] lods;
|
||||
private volatile LevelGeometry[] newLods;
|
||||
|
||||
private boolean showWire;
|
||||
|
||||
private AtomicInteger needsUpdate = new AtomicInteger(1);
|
||||
|
||||
// For debugging
|
||||
private volatile boolean check = false;
|
||||
|
||||
public TreeBuilderReference( TreeParameters treeParameters,
|
||||
Material treeMaterial,
|
||||
Material wireMaterial,
|
||||
Material leafMaterial,
|
||||
Material flatMaterial,
|
||||
Material impostorMaterial ) {
|
||||
this.treeParameters = treeParameters;
|
||||
this.treeMaterial = treeMaterial;
|
||||
this.wireMaterial = wireMaterial;
|
||||
this.leafMaterial = leafMaterial;
|
||||
this.flatMaterial = flatMaterial;
|
||||
this.impostorMaterial = impostorMaterial;
|
||||
|
||||
lods = new LevelGeometry[treeParameters.getLodCount()];
|
||||
treeNode = new Node("Tree");
|
||||
treeNode.addControl(new LodSwitchControl());
|
||||
|
||||
// We'll derive the flat wire material from the flat material
|
||||
flatWireMaterial = flatMaterial.clone();
|
||||
flatWireMaterial.clearParam("DiffuseMap");
|
||||
flatWireMaterial.setColor("Diffuse", ColorRGBA.Yellow.mult(10));
|
||||
flatWireMaterial.setColor("Ambient", ColorRGBA.Yellow.mult(10));
|
||||
flatWireMaterial.getAdditionalRenderState().setWireframe(true);
|
||||
|
||||
impostorWireMaterial = impostorMaterial.clone();
|
||||
impostorWireMaterial.clearParam("DiffuseMap");
|
||||
impostorWireMaterial.setColor("Diffuse", ColorRGBA.Yellow.mult(10));
|
||||
impostorWireMaterial.setColor("Ambient", ColorRGBA.Yellow.mult(10));
|
||||
impostorWireMaterial.getAdditionalRenderState().setWireframe(true);
|
||||
}
|
||||
|
||||
public void setSeed( int seed ) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
public void setWireFrame( boolean b ) {
|
||||
if( showWire == b ) {
|
||||
return;
|
||||
}
|
||||
this.showWire = b;
|
||||
for( LevelGeometry level : lods ) {
|
||||
if( level == null ) {
|
||||
continue;
|
||||
}
|
||||
if( showWire ) {
|
||||
level.wireGeom.setCullHint(CullHint.Inherit);
|
||||
} else {
|
||||
level.wireGeom.setCullHint(CullHint.Always);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void markChanged() {
|
||||
needsUpdate.incrementAndGet();
|
||||
}
|
||||
|
||||
public Node getTreeNode() {
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
public int getVertexCount( int lod ) {
|
||||
LevelGeometry level = lods[lod];
|
||||
if( level == null ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
if( level.treeGeom != null ) {
|
||||
Mesh mesh = level.treeGeom.getMesh();
|
||||
result += mesh.getVertexCount();
|
||||
}
|
||||
if( level.leafGeom != null ) {
|
||||
Mesh mesh = level.leafGeom.getMesh();
|
||||
result += mesh.getVertexCount();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getTriangleCount( int lod ) {
|
||||
LevelGeometry level = lods[lod];
|
||||
if( level == null ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
if( level.treeGeom != null ) {
|
||||
Mesh mesh = level.treeGeom.getMesh();
|
||||
result += mesh.getTriangleCount();
|
||||
}
|
||||
if( level.leafGeom != null ) {
|
||||
Mesh mesh = level.leafGeom.getMesh();
|
||||
result += mesh.getTriangleCount();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getVertexCount() {
|
||||
return getVertexCount(0);
|
||||
}
|
||||
|
||||
public int getTriangleCount() {
|
||||
return getTriangleCount(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build() {
|
||||
if( needsUpdate.get() == 0 ) {
|
||||
return;
|
||||
}
|
||||
needsUpdate.set(0);
|
||||
|
||||
check = true;
|
||||
try {
|
||||
log.trace("******* rebuilding tree ********");
|
||||
regenerateTree();
|
||||
log.trace("******* tree built ********" );
|
||||
} finally {
|
||||
check = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
if( newLods == null ) {
|
||||
// Nothing was built
|
||||
return;
|
||||
}
|
||||
|
||||
if( check ) {
|
||||
log.error( "Ships have passed in the night 1." );
|
||||
}
|
||||
|
||||
log.trace("******* applying tree ********" );
|
||||
|
||||
LodSwitchControl lodControl = treeNode.getControl(LodSwitchControl.class);
|
||||
lodControl.clearLevels();
|
||||
|
||||
// Release the old ones if we had them.
|
||||
for( LevelGeometry g : lods ) {
|
||||
if( g != null ) {
|
||||
g.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Add in the new ones
|
||||
for( int i = 0; i < lods.length; i++ ) {
|
||||
lods[i] = newLods[i];
|
||||
lods[i].attach(lodControl);
|
||||
if( !showWire && lods[i] != null && lods[i].wireGeom != null ) {
|
||||
lods[i].wireGeom.setCullHint(CullHint.Always);
|
||||
}
|
||||
}
|
||||
newLods = null;
|
||||
|
||||
log.trace("******* tree applied ********" );
|
||||
if( check ) {
|
||||
log.error( "Ships have passed in the night 2." );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
for( LevelGeometry g : lods ) {
|
||||
if( g != null ) {
|
||||
g.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void releaseGeometry( Geometry geom ) {
|
||||
if( geom == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
geom.removeFromParent();
|
||||
|
||||
Mesh mesh = geom.getMesh();
|
||||
releaseMesh(mesh);
|
||||
}
|
||||
|
||||
protected void releaseMesh( Mesh mesh ) {
|
||||
// Delete the old buffers
|
||||
for( VertexBuffer vb : mesh.getBufferList() ) {
|
||||
if( log.isTraceEnabled() ) {
|
||||
log.trace("--destroying buffer:" + vb);
|
||||
}
|
||||
BufferUtils.destroyDirectBuffer( vb.getData() );
|
||||
}
|
||||
}
|
||||
|
||||
protected void regenerateTree() {
|
||||
|
||||
LevelGeometry[] levels = new LevelGeometry[treeParameters.getLodCount()];
|
||||
|
||||
TreeGenerator treeGen = new TreeGenerator();
|
||||
Tree tree = treeGen.generateTree(seed, treeParameters);
|
||||
|
||||
BoundingBox trunkBounds = null;
|
||||
BoundingBox leafBounds = null;
|
||||
|
||||
List<Vertex> baseTips = null;
|
||||
|
||||
for( int i = 0; i < levels.length; i++ ) {
|
||||
|
||||
LevelOfDetailParameters lodParms = treeParameters.getLod(i);
|
||||
LevelGeometry level = new LevelGeometry(lodParms.distance);
|
||||
levels[i] = level;
|
||||
|
||||
Mesh treeMesh;
|
||||
List<Vertex> tips = null;
|
||||
boolean generateLeaves = false;
|
||||
|
||||
switch( lodParms.reduction ) {
|
||||
case Normal:
|
||||
SkinnedTreeMeshGenerator meshGen = new SkinnedTreeMeshGenerator();
|
||||
|
||||
if( baseTips == null ) {
|
||||
baseTips = tips = new ArrayList<Vertex>();
|
||||
}
|
||||
treeMesh = meshGen.generateMesh(tree,
|
||||
treeParameters.getLod(i),
|
||||
treeParameters.getYOffset(),
|
||||
treeParameters.getTextureURepeat(),
|
||||
treeParameters.getTextureVScale(),
|
||||
tips);
|
||||
trunkBounds = (BoundingBox)treeMesh.getBound();
|
||||
|
||||
level.treeGeom = new Geometry("tree:" + lodParms.reduction, treeMesh);
|
||||
level.treeGeom.setMaterial(treeMaterial);
|
||||
level.treeGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
level.treeGeom.setLocalTranslation(0, treeParameters.getRootHeight(), 0);
|
||||
|
||||
level.wireGeom = new Geometry("wire:" + lodParms.reduction, treeMesh);
|
||||
level.wireGeom.setMaterial(wireMaterial);
|
||||
level.wireGeom.setLocalTranslation(0, treeParameters.getRootHeight(), 0);
|
||||
|
||||
generateLeaves = true;
|
||||
break;
|
||||
case FlatPoly:
|
||||
FlatPolyTreeMeshGenerator polyGen = new FlatPolyTreeMeshGenerator();
|
||||
if( baseTips == null ) {
|
||||
baseTips = tips = new ArrayList<Vertex>();
|
||||
}
|
||||
treeMesh = polyGen.generateMesh(tree,
|
||||
treeParameters.getLod(i),
|
||||
treeParameters.getYOffset(),
|
||||
treeParameters.getTextureURepeat(),
|
||||
treeParameters.getTextureVScale(),
|
||||
tips);
|
||||
|
||||
level.treeGeom = new Geometry("tree:" + lodParms.reduction, treeMesh);
|
||||
level.treeGeom.setMaterial(flatMaterial);
|
||||
level.treeGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
level.treeGeom.setLocalTranslation(0, treeParameters.getRootHeight(), 0);
|
||||
|
||||
level.wireGeom = new Geometry("wire:" + lodParms.reduction, treeMesh);
|
||||
level.wireGeom.setMaterial(flatWireMaterial);
|
||||
level.wireGeom.setLocalTranslation(0, treeParameters.getRootHeight(), 0);
|
||||
|
||||
generateLeaves = true;
|
||||
break;
|
||||
case Impostor:
|
||||
|
||||
if( trunkBounds == null ) {
|
||||
// Generate the mesh just to throw it away
|
||||
meshGen = new SkinnedTreeMeshGenerator();
|
||||
|
||||
if( baseTips == null ) {
|
||||
baseTips = tips = new ArrayList<Vertex>();
|
||||
}
|
||||
treeMesh = meshGen.generateMesh(tree,
|
||||
treeParameters.getLod(0),
|
||||
treeParameters.getYOffset(),
|
||||
treeParameters.getTextureURepeat(),
|
||||
treeParameters.getTextureVScale(),
|
||||
tips);
|
||||
trunkBounds = (BoundingBox)treeMesh.getBound();
|
||||
releaseMesh(treeMesh);
|
||||
}
|
||||
BoundingBox impostorBounds = (BoundingBox)trunkBounds.clone();
|
||||
|
||||
if( leafBounds == null && treeParameters.getGenerateLeaves() ) {
|
||||
BillboardedLeavesMeshGenerator leafGen = new BillboardedLeavesMeshGenerator();
|
||||
Mesh leafMesh = leafGen.generateMesh(baseTips, treeParameters.getLeafScale());
|
||||
leafBounds = (BoundingBox)leafMesh.getBound();
|
||||
releaseMesh(leafMesh);
|
||||
} else if( treeParameters.getGenerateLeaves() ) {
|
||||
impostorBounds.mergeLocal(leafBounds);
|
||||
}
|
||||
|
||||
float rootHeight = treeParameters.getRootHeight();
|
||||
Vector3f min = trunkBounds.getMin(null);
|
||||
Vector3f max = trunkBounds.getMax(null);
|
||||
if( leafBounds != null ) {
|
||||
min.minLocal(leafBounds.getMin(null));
|
||||
max.maxLocal(leafBounds.getMax(null));
|
||||
}
|
||||
//float radius = (max.y - min.y) * 0.5f;
|
||||
|
||||
float xSize = Math.max(Math.abs(min.x), Math.abs(max.x));
|
||||
float ySize = max.y - min.y;
|
||||
float zSize = Math.max(Math.abs(min.z), Math.abs(max.z));
|
||||
|
||||
float size = ySize * 0.5f;
|
||||
size = Math.max(size, xSize);
|
||||
size = Math.max(size, zSize);
|
||||
float radius = size;
|
||||
|
||||
// Just do it here raw for now
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(Type.Position, 3, new float[] {
|
||||
0, min.y + rootHeight, 0,
|
||||
0, min.y + rootHeight, 0,
|
||||
0, min.y + (size*2) + rootHeight, 0,
|
||||
0, min.y + (size*2) + rootHeight, 0
|
||||
//0, max.y + rootHeight, 0,
|
||||
//0, max.y + rootHeight, 0
|
||||
});
|
||||
mesh.setBuffer(Type.Size, 1, new float[] {
|
||||
-radius,
|
||||
radius,
|
||||
-radius,
|
||||
radius
|
||||
});
|
||||
mesh.setBuffer(Type.TexCoord, 2, new float[] {
|
||||
0, 0,
|
||||
1, 0,
|
||||
0, 1f,
|
||||
1, 1f
|
||||
});
|
||||
mesh.setBuffer(Type.Index, 3, new short[] {
|
||||
0, 1, 3,
|
||||
0, 3, 2
|
||||
});
|
||||
//mesh.updateBound();
|
||||
|
||||
// Give the mesh the same bound that the real tree would have
|
||||
// had.
|
||||
impostorBounds.getCenter().addLocal(0, treeParameters.getRootHeight(), 0);
|
||||
mesh.setBound(impostorBounds);
|
||||
|
||||
level.treeGeom = new Geometry("tree:" + lodParms.reduction, mesh);
|
||||
level.treeGeom.setMaterial(impostorMaterial);
|
||||
level.treeGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
level.treeGeom.setLocalTranslation(0, 0, 0);
|
||||
level.treeGeom.setQueueBucket(Bucket.Transparent);
|
||||
|
||||
level.wireGeom = new Geometry("wire:" + lodParms.reduction, mesh);
|
||||
level.wireGeom.setMaterial(impostorWireMaterial);
|
||||
level.wireGeom.setLocalTranslation(0, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if( generateLeaves && treeParameters.getGenerateLeaves() && baseTips != null ) {
|
||||
BillboardedLeavesMeshGenerator leafGen = new BillboardedLeavesMeshGenerator();
|
||||
Mesh leafMesh = leafGen.generateMesh(baseTips, treeParameters.getLeafScale());
|
||||
leafBounds = (BoundingBox)leafMesh.getBound();
|
||||
level.leafGeom = new Geometry("leaves:" + lodParms.reduction, leafMesh);
|
||||
level.leafGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
level.leafGeom.setQueueBucket(Bucket.Transparent);
|
||||
level.leafGeom.setMaterial(leafMaterial);
|
||||
|
||||
level.leafGeom.setLocalTranslation(0, treeParameters.getRootHeight(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
newLods = levels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates all of the tree geometry for a
|
||||
* particular level of detail.
|
||||
*/
|
||||
private class LevelGeometry {
|
||||
|
||||
float distance;
|
||||
Node levelNode;
|
||||
Geometry treeGeom;
|
||||
Geometry wireGeom;
|
||||
Geometry leafGeom;
|
||||
|
||||
public LevelGeometry( float distance ) {
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public void attach( LodSwitchControl control ) {
|
||||
levelNode = new Node("level:" + distance);
|
||||
if( treeGeom != null ) {
|
||||
levelNode.attachChild(treeGeom);
|
||||
levelNode.attachChild(wireGeom);
|
||||
}
|
||||
if( leafGeom != null ) {
|
||||
levelNode.attachChild(leafGeom);
|
||||
}
|
||||
control.addLevel(distance, levelNode);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
levelNode.removeFromParent();
|
||||
releaseGeometry(treeGeom);
|
||||
releaseGeometry(wireGeom);
|
||||
releaseGeometry(leafGeom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
159
simarboreal/src/main/java/com/simsilica/arboreal/TreeEditor.java
Normal file
159
simarboreal/src/main/java/com/simsilica/arboreal/TreeEditor.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.simsilica.builder.BuilderState;
|
||||
import com.jme3.app.DebugKeysAppState;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.StatsAppState;
|
||||
import com.jme3.app.state.ScreenshotAppState;
|
||||
import com.jme3.app.state.VideoRecorderAppState;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.simsilica.fx.LightingState;
|
||||
import com.simsilica.fx.sky.SkyState;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.input.InputMapper;
|
||||
import com.simsilica.lemur.style.BaseStyles;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Main class for the TreeEditor.
|
||||
*
|
||||
* @author PSpeed
|
||||
*/
|
||||
public class TreeEditor extends SimpleApplication {
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(TreeEditor.class);
|
||||
|
||||
public static final String GLASS_STYLES = "/com/simsilica/arboreal/ui/glass-styles.groovy";
|
||||
|
||||
public static void main( String... args ) {
|
||||
TreeEditor main = new TreeEditor();
|
||||
|
||||
AppSettings settings = new AppSettings(false);
|
||||
settings.setTitle("SimArboreal Tree Editor");
|
||||
settings.setSettingsDialogImage("/com/simsilica/arboreal/images/TreeEditor-Splash.png");
|
||||
|
||||
try {
|
||||
BufferedImage[] icons = new BufferedImage[] {
|
||||
ImageIO.read( TreeEditor.class.getResource( "/com/simsilica/arboreal/images/TreeEditor-icon-128.png" ) ),
|
||||
ImageIO.read( TreeEditor.class.getResource( "/com/simsilica/arboreal/images/TreeEditor-icon-32.png" ) ),
|
||||
ImageIO.read( TreeEditor.class.getResource( "/com/simsilica/arboreal/images/TreeEditor-icon-16.png" ) )
|
||||
};
|
||||
settings.setIcons(icons);
|
||||
} catch( IOException e ) {
|
||||
log.warn( "Error loading globe icons", e );
|
||||
}
|
||||
|
||||
main.setSettings(settings);
|
||||
|
||||
main.start();
|
||||
}
|
||||
|
||||
public TreeEditor() {
|
||||
super(new StatsAppState(), new DebugKeysAppState(),
|
||||
new BuilderState(1, 1),
|
||||
new MovementState(),
|
||||
new DebugHudState(),
|
||||
new TreeOptionsState(),
|
||||
new LightingState(),
|
||||
new GroundState(),
|
||||
new SkyState(),
|
||||
new AvatarState(),
|
||||
new TreeParametersState(),
|
||||
new ForestGridState(),
|
||||
new AtlasGeneratorState(),
|
||||
new FileActionsState(),
|
||||
new PostProcessorState(),
|
||||
new ScreenshotAppState("", System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public void toggleRecordVideo() {
|
||||
VideoRecorderAppState recorder = stateManager.getState(VideoRecorderAppState.class);
|
||||
if( recorder == null ) {
|
||||
recorder = new VideoRecorderAppState(new File("trees-" + System.currentTimeMillis() + ".avi"));
|
||||
stateManager.attach(recorder);
|
||||
} else {
|
||||
stateManager.detach(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
|
||||
GuiGlobals.initialize(this);
|
||||
|
||||
cam.setLocation(new Vector3f(0, 1.8f, 10));
|
||||
|
||||
InputMapper inputMapper = GuiGlobals.getInstance().getInputMapper();
|
||||
MainFunctions.initializeDefaultMappings(inputMapper);
|
||||
inputMapper.activateGroup( MainFunctions.GROUP );
|
||||
MovementFunctions.initializeDefaultMappings(inputMapper);
|
||||
|
||||
inputMapper.addDelegate(MainFunctions.F_RECORD_VIDEO, this, "toggleRecordVideo");
|
||||
|
||||
/*
|
||||
// Now create the normal simple test scene
|
||||
Box b = new Box(1, 1, 1);
|
||||
Geometry geom = new Geometry("Box", b);
|
||||
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setColor("Diffuse", ColorRGBA.Blue);
|
||||
mat.setColor("Ambient", ColorRGBA.Green);
|
||||
mat.setBoolean("UseMaterialColors", true);
|
||||
geom.setMaterial(mat);
|
||||
|
||||
rootNode.attachChild(geom);
|
||||
*/
|
||||
|
||||
BaseStyles.loadGlassStyle();
|
||||
|
||||
TreeOptionsState treeOptions = stateManager.getState(TreeOptionsState.class);
|
||||
treeOptions.addOptionToggle("Grass", stateManager.getState(GroundState.class), "setShowGrass");
|
||||
treeOptions.addOptionToggle("Ground Atm.", stateManager.getState(GroundState.class), "setUseScattering");
|
||||
treeOptions.addOptionToggle("Tree Atm.", stateManager.getState(ForestGridState.class), "setUseScattering");
|
||||
treeOptions.addOptionToggle("Sky", stateManager.getState(SkyState.class), "setEnabled");
|
||||
stateManager.getState(SkyState.class).setEnabled(false);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.simsilica.lemur.Button;
|
||||
import com.simsilica.lemur.Checkbox;
|
||||
import com.simsilica.lemur.Command;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.GuiGlobals;
|
||||
import com.simsilica.lemur.RollupPanel;
|
||||
import com.simsilica.lemur.TabbedPanel;
|
||||
import com.simsilica.lemur.component.BorderLayout;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.input.InputMapper;
|
||||
import com.simsilica.lemur.style.ElementId;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class TreeOptionsState extends BaseAppState {
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(TreeOptionsState.class);
|
||||
|
||||
private Container mainWindow;
|
||||
private Container mainContents;
|
||||
|
||||
private Container viewSettingsPanel;
|
||||
private Container actionsPanel;
|
||||
private Container checkboxPanel;
|
||||
private TabbedPanel tabs;
|
||||
|
||||
private List<Checkbox> optionToggles = new ArrayList<Checkbox>();
|
||||
private Map<String, Checkbox> optionToggleMap = new HashMap<String, Checkbox>();
|
||||
|
||||
private int columns = 3;
|
||||
|
||||
public TreeOptionsState() {
|
||||
}
|
||||
|
||||
public Container getContents() {
|
||||
return mainContents;
|
||||
}
|
||||
|
||||
public Container getViewSettings() {
|
||||
return viewSettingsPanel;
|
||||
}
|
||||
|
||||
public TabbedPanel getParameterTabs() {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
public Checkbox addOptionToggle( String name, Object target, String method ) {
|
||||
Checkbox cb = new Checkbox(name, "glass");
|
||||
cb.addClickCommands(new ToggleHandler(target, method));
|
||||
|
||||
int column = optionToggles.size() % columns;
|
||||
if( checkboxPanel != null ) {
|
||||
if( column == 0 ) {
|
||||
checkboxPanel.addChild(cb);
|
||||
} else {
|
||||
checkboxPanel.addChild(cb, column);
|
||||
}
|
||||
}
|
||||
optionToggles.add(cb);
|
||||
optionToggleMap.put(name, cb);
|
||||
return cb;
|
||||
}
|
||||
|
||||
public void toggleHud() {
|
||||
setEnabled( !isEnabled() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
// Always register for our hot key as long as
|
||||
// we are attached.
|
||||
InputMapper inputMapper = GuiGlobals.getInstance().getInputMapper();
|
||||
inputMapper.addDelegate( MainFunctions.F_HUD, this, "toggleHud" );
|
||||
|
||||
mainWindow = new Container(new BorderLayout(), new ElementId("window"), "glass");
|
||||
//mainWindow.addChild(new Label("Tree Options", mainWindow.getElementId().child("title.label"), "glass"),
|
||||
// BorderLayout.Position.North);
|
||||
mainWindow.setLocalTranslation(10, app.getCamera().getHeight() - 10, 0);
|
||||
|
||||
mainContents = mainWindow.addChild(new Container(mainWindow.getElementId().child("contents.container"), "glass"),
|
||||
BorderLayout.Position.Center);
|
||||
|
||||
//mainContents.addChild(new Label("Visualization Options:", "glass"));
|
||||
actionsPanel = mainContents.addChild(new Container());
|
||||
|
||||
Container visOptions = new Container();
|
||||
RollupPanel visRollup = new RollupPanel("Visualization Options", visOptions,
|
||||
new ElementId("root.rollup"), "glass");
|
||||
mainContents.addChild(visRollup);
|
||||
viewSettingsPanel = visOptions.addChild(new Container());
|
||||
checkboxPanel = visOptions.addChild(new Container());
|
||||
|
||||
// Add any toggles that were added before init
|
||||
int i = 0;
|
||||
for( Checkbox cb : optionToggles ) {
|
||||
int column = (i++) % columns;
|
||||
if( column == 0 ) {
|
||||
checkboxPanel.addChild(cb);
|
||||
} else {
|
||||
checkboxPanel.addChild(cb, column);
|
||||
}
|
||||
}
|
||||
|
||||
//mainContents.addChild(new Label("Tree Parameters:", "glass"));
|
||||
//tabs = mainContents.addChild(new TabbedPanel("glass"));
|
||||
tabs = new TabbedPanel("glass");
|
||||
mainContents.addChild(new RollupPanel("Tree Parameters", tabs,
|
||||
new ElementId("root.rollup"), "glass"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
InputMapper inputMapper = GuiGlobals.getInstance().getInputMapper();
|
||||
inputMapper.removeDelegate( MainFunctions.F_HUD, this, "toggleHud" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
((SimpleApplication)getApplication()).getGuiNode().attachChild(mainWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
mainWindow.removeFromParent();
|
||||
}
|
||||
|
||||
private class ToggleHandler implements Command<Button> {
|
||||
private Object object;
|
||||
private Method method;
|
||||
|
||||
public ToggleHandler( Object object, String methodName ) {
|
||||
this.object = object;
|
||||
try {
|
||||
this.method = object.getClass().getMethod(methodName, Boolean.TYPE);
|
||||
} catch( Exception e ) {
|
||||
throw new RuntimeException("Error retrieving method for:" + methodName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( Button source ) {
|
||||
try {
|
||||
method.invoke(object, ((Checkbox)source).isChecked());
|
||||
} catch( Exception e ) {
|
||||
throw new RuntimeException("Error sending state for:" + object + "->" + method, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.simsilica.arboreal.ui.CheckboxModelGroup;
|
||||
import com.simsilica.lemur.Axis;
|
||||
import com.simsilica.lemur.Checkbox;
|
||||
import com.simsilica.lemur.Container;
|
||||
import com.simsilica.lemur.FillMode;
|
||||
import com.simsilica.lemur.Label;
|
||||
import com.simsilica.lemur.RollupPanel;
|
||||
import com.simsilica.lemur.TabbedPanel;
|
||||
import com.simsilica.lemur.component.BorderLayout;
|
||||
import com.simsilica.lemur.component.SpringGridLayout;
|
||||
import com.simsilica.lemur.core.VersionedHolder;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import com.simsilica.lemur.event.BaseAppState;
|
||||
import com.simsilica.lemur.props.PropertyPanel;
|
||||
import com.simsilica.lemur.style.ElementId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Manages the main TreeParameters objects and the
|
||||
* editor panels for modifying its values.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class TreeParametersState extends BaseAppState {
|
||||
|
||||
static Logger log = LoggerFactory.getLogger(TreeParametersState.class);
|
||||
|
||||
private TreeParameters treeParameters;
|
||||
private VersionedHolder<TreeParameters> treeParametersHolder = new VersionedHolder<TreeParameters>();
|
||||
|
||||
// Keep track of the panels so that we can easily refresh them
|
||||
// when loading new files or otherwise changing parameters
|
||||
// outside of the UI.
|
||||
private List<PropertyPanel> treePanels;
|
||||
|
||||
// Keep the array of version refs for easy tracking
|
||||
// of changes across all parameters.
|
||||
private VersionedReference[] versions;
|
||||
|
||||
private VersionedReference<Boolean> building;
|
||||
private LevelStats[] levelStats;
|
||||
|
||||
public TreeParametersState() {
|
||||
this.treeParameters = new TreeParameters(5);
|
||||
this.treeParametersHolder.setObject(treeParameters);
|
||||
}
|
||||
|
||||
public TreeParameters getTreeParameters() {
|
||||
return treeParameters;
|
||||
}
|
||||
|
||||
public VersionedReference<TreeParameters> getTreeParametersRef() {
|
||||
return treeParametersHolder.createReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize( Application app ) {
|
||||
|
||||
List<VersionedReference> versionsList = new ArrayList<VersionedReference>();
|
||||
|
||||
TabbedPanel tabs = getState(TreeOptionsState.class).getParameterTabs();
|
||||
|
||||
treePanels = new ArrayList<PropertyPanel>();
|
||||
PropertyPanel properties;
|
||||
|
||||
// Biggest real life tree is 25 feet in diameter... or 7.62 meters. We'll
|
||||
// let things get more ridiculous
|
||||
properties = new PropertyPanel("glass");
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
tabs.addTab("Tree", properties);
|
||||
|
||||
properties.addIntProperty("Seed", treeParameters, "seed", 0, 100, 1);
|
||||
properties.addFloatProperty("Radius (m)", treeParameters, "trunkRadius", 0.05f, 10f, 0.05f);
|
||||
properties.addFloatProperty("Height (m)", treeParameters, "trunkHeight", 0.1f, 50f, 0.1f);
|
||||
properties.addFloatProperty("Root Height (m)", treeParameters, "rootHeight", 0f, 10f, 0.1f);
|
||||
properties.addFloatProperty("Y Offset (m)", treeParameters, "YOffset", 0f, 10f, 0.01f);
|
||||
properties.addIntProperty("Texture U Repeat", treeParameters, "textureURepeat", 1, 12, 1);
|
||||
properties.addFloatProperty("Texture V Scale", treeParameters, "textureVScale", 0.01f, 10f, 0.1f);
|
||||
properties.addFloatProperty("Wind Flex Height (m)", treeParameters, "flexHeight", 0.0f, 50, 0.01f);
|
||||
properties.addFloatProperty("Trunk Flex.", treeParameters, "trunkFlexibility", 0.0f, 5.0f, 0.001f);
|
||||
properties.addFloatProperty("Branch Flex.", treeParameters, "branchFlexibility", 0.0f, 5.0f, 0.001f);
|
||||
|
||||
Container branchPanels = new Container("glass");
|
||||
tabs.addTab("Branches", branchPanels);
|
||||
|
||||
// Now the sub-panels for each branch
|
||||
RollupPanel first = null;
|
||||
CheckboxModelGroup rollupGroup = new CheckboxModelGroup();
|
||||
for( int i = 0; i < treeParameters.getDepth(); i++ ) {
|
||||
BranchParameters branch = treeParameters.getBranch(i);
|
||||
|
||||
Container nested = new Container("glass");
|
||||
properties = new PropertyPanel(new ElementId("nestedProperties"), "glass");
|
||||
nested.addChild(properties);
|
||||
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
RollupPanel rollup = branchPanels.addChild(new RollupPanel("Level " + i, nested, "glass"));
|
||||
rollup.setOpenModel(rollupGroup.addChild(rollup.getOpenModel()));
|
||||
if( i > 0 ) {
|
||||
// The first panel cannot be disabled so we only
|
||||
// apply the enabled/disabled checkbox to the others
|
||||
properties.setEnabledProperty(branch, "enabled");
|
||||
rollup.getTitleContainer().addChild(new Checkbox("", properties.getEnabledModel(), "glass"));
|
||||
properties.addBooleanField("Inherit", branch, "inherit");
|
||||
rollup.setOpen(false);
|
||||
} else {
|
||||
first = rollup;
|
||||
}
|
||||
|
||||
properties.addIntField("Radial Segments", branch, "radialSegments", 3, 24, 1);
|
||||
properties.addIntField("Length Segments", branch, "lengthSegments", 1, 10, 1);
|
||||
properties.addFloatField("Segment Variation (*)", branch, "segmentVariation", 0, 1, 0.01f);
|
||||
properties.addFloatField("Taper (*)", branch, "taper", 0.1f, 1f, 0.01f);
|
||||
properties.addFloatField("Twist (rads)", branch, "twist", 0f, FastMath.PI, 0.01f);
|
||||
properties.addFloatField("Gravity (*)", branch, "gravity", -1f, 1f, 0.1f);
|
||||
|
||||
|
||||
properties.addIntField("Side Joints", branch, "sideJointCount", 0, 8, 1);
|
||||
|
||||
properties = new PropertyPanel(new ElementId("nestedProperties"), "glass");
|
||||
nested.addChild(properties);
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
|
||||
properties.addFloatField("Angle (rads)", branch, "sideJointStartAngle", 0, FastMath.PI, 0.01f);
|
||||
properties.addFloatField("Inclination (rads)", branch, "inclination", 0f, FastMath.HALF_PI, 0.01f);
|
||||
properties.addFloatField("Radius Scale (*)", branch, "radiusScale", 0.01f, 2f, 0.01f);
|
||||
properties.addFloatField("Length Scale (*)", branch, "lengthScale", 0.01f, 5f, 0.01f);
|
||||
|
||||
properties.addBooleanField("Tip Joint", branch, "hasEndJoint");
|
||||
properties.addFloatField("Tip Rotation (rads)", branch, "tipRotation", 0f, FastMath.PI, 0.01f);
|
||||
}
|
||||
first.setOpen(true);
|
||||
|
||||
|
||||
Container rootPanels = new Container("glass");
|
||||
tabs.addTab("Roots", rootPanels);
|
||||
|
||||
// Now the sub-panels for each root
|
||||
first = null;
|
||||
rollupGroup = new CheckboxModelGroup();
|
||||
for( int i = 0; i < treeParameters.getDepth(); i++ ) {
|
||||
BranchParameters branch = treeParameters.getRoot(i);
|
||||
|
||||
Container nested = new Container("glass");
|
||||
properties = new PropertyPanel(new ElementId("nestedProperties"), "glass");
|
||||
nested.addChild(properties);
|
||||
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
RollupPanel rollup = rootPanels.addChild(new RollupPanel("Level " + i, nested, "glass"));
|
||||
rollup.setOpenModel(rollupGroup.addChild(rollup.getOpenModel()));
|
||||
if( i > 0 ) {
|
||||
// The first panel cannot be disabled so we only
|
||||
// apply the enabled/disabled checkbox to the others
|
||||
properties.setEnabledProperty(branch, "enabled");
|
||||
rollup.getTitleContainer().addChild(new Checkbox("", properties.getEnabledModel(), "glass"));
|
||||
properties.addBooleanField("Inherit", branch, "inherit");
|
||||
rollup.setOpen(false);
|
||||
} else {
|
||||
first = rollup;
|
||||
}
|
||||
|
||||
properties.addIntField("Radial Segments", branch, "radialSegments", 3, 24, 1);
|
||||
properties.addIntField("Length Segments", branch, "lengthSegments", 1, 10, 1);
|
||||
properties.addFloatField("Segment Variation (*)", branch, "segmentVariation", 0, 1, 0.01f);
|
||||
properties.addFloatField("Taper (*)", branch, "taper", 0.1f, 1f, 0.01f);
|
||||
properties.addFloatField("Twist (rads)", branch, "twist", 0f, FastMath.PI, 0.01f);
|
||||
properties.addFloatField("Gravity (*)", branch, "gravity", -1f, 1f, 0.1f);
|
||||
|
||||
properties.addIntField("Side Joints", branch, "sideJointCount", 0, 8, 1);
|
||||
|
||||
properties = new PropertyPanel(new ElementId("nestedProperties"), "glass");
|
||||
nested.addChild(properties);
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
|
||||
properties.addFloatField("Angle (rads)", branch, "sideJointStartAngle", 0, FastMath.PI, 0.01f);
|
||||
properties.addFloatField("Inclination (rads)", branch, "inclination", 0f, FastMath.HALF_PI, 0.01f);
|
||||
properties.addFloatField("Radius Scale (*)", branch, "radiusScale", 0.01f, 2f, 0.01f);
|
||||
properties.addFloatField("Length Scale (*)", branch, "lengthScale", 0.01f, 4f, 0.01f);
|
||||
|
||||
properties.addBooleanField("Tip Joint", branch, "hasEndJoint");
|
||||
properties.addFloatField("Tip Rotation (rads)", branch, "tipRotation", 0f, FastMath.PI, 0.01f);
|
||||
}
|
||||
first.setOpen(true);
|
||||
|
||||
// And now the leaves panel
|
||||
properties = new PropertyPanel("glass");
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
tabs.addTab("Leaves", properties);
|
||||
properties.addBooleanProperty("Enabled", treeParameters, "generateLeaves");
|
||||
properties.addFloatProperty("Size (m)", treeParameters, "leafScale", 0.1f, 10f, 0.1f);
|
||||
|
||||
|
||||
|
||||
// LOD tab
|
||||
Container lodPanels = new Container("glass");
|
||||
tabs.addTab("LOD", lodPanels);
|
||||
|
||||
levelStats = new LevelStats[treeParameters.getLodCount()];
|
||||
|
||||
first = null;
|
||||
rollupGroup = new CheckboxModelGroup();
|
||||
for( int i = 0; i < treeParameters.getLodCount(); i++ ) {
|
||||
LevelOfDetailParameters lod = treeParameters.getLod(i);
|
||||
|
||||
String name = (i == 0) ? "Highest" : ("Level " + i);
|
||||
|
||||
Container nested = new Container(new BorderLayout());
|
||||
levelStats[i] = nested.addChild(new LevelStats(i), BorderLayout.Position.South);
|
||||
properties = new PropertyPanel("glass");
|
||||
nested.addChild(properties, BorderLayout.Position.Center);
|
||||
treePanels.add(properties);
|
||||
versionsList.add(properties.createReference());
|
||||
RollupPanel rollup = lodPanels.addChild(new RollupPanel(name, nested, "glass"));
|
||||
rollup.setOpenModel(rollupGroup.addChild(rollup.getOpenModel()));
|
||||
if( i == 0 ) {
|
||||
first = rollup;
|
||||
} else {
|
||||
rollup.setOpen(false);
|
||||
}
|
||||
|
||||
properties.addFloatField("Distance (m)", lod, "distance", 0, 1000, 1);
|
||||
properties.addIntField("Branch Depth", lod, "branchDepth", 1, treeParameters.getDepth(), 1);
|
||||
properties.addIntField("Root Depth", lod, "rootDepth", 1, treeParameters.getDepth(), 1);
|
||||
properties.addIntField("Max Radial Segments", lod, "maxRadialSegments", 3, 24, 1);
|
||||
properties.addEnumField("Mesh Type", lod, "reduction");
|
||||
}
|
||||
first.setOpen(true);
|
||||
|
||||
|
||||
versions = new VersionedReference[versionsList.size()];
|
||||
versions = versionsList.toArray(versions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup( Application app ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
}
|
||||
|
||||
float nextUpdateCheck = 0.1f;
|
||||
@Override
|
||||
public void update( float tpf ) {
|
||||
|
||||
nextUpdateCheck += tpf;
|
||||
if( nextUpdateCheck > 0.1f ) {
|
||||
boolean changed = false;
|
||||
for( VersionedReference ref : versions ) {
|
||||
if( ref.update() ) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if( changed ) {
|
||||
treeParametersHolder.incrementVersion();
|
||||
}
|
||||
}
|
||||
|
||||
// See if we need to update the LOD stats
|
||||
if( building == null ) {
|
||||
// We haven't retrieved it yet. We get attached before
|
||||
// ForedGridState so we have to grab this lazily.
|
||||
building = getState(ForestGridState.class).getBuildingRef();
|
||||
refreshStats();
|
||||
} else if( building.update() ) {
|
||||
refreshStats();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
}
|
||||
|
||||
public void refreshTreePanels() {
|
||||
for( PropertyPanel p : treePanels ) {
|
||||
p.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshStats() {
|
||||
boolean ready = !building.get();
|
||||
for( LevelStats stats : levelStats ) {
|
||||
stats.update(ready);
|
||||
}
|
||||
}
|
||||
|
||||
private class LevelStats extends Container {
|
||||
|
||||
int level;
|
||||
Label verts;
|
||||
Label tris;
|
||||
|
||||
public LevelStats( int level ) {
|
||||
super(new SpringGridLayout(Axis.X, Axis.Y, FillMode.Even, FillMode.Even));
|
||||
this.level = level;
|
||||
this.verts = addChild(new Label("LOD verts: ???", "glass"));
|
||||
this.tris = addChild(new Label("tris: ???", "glass"));
|
||||
}
|
||||
|
||||
protected void update( boolean ready ) {
|
||||
if( ready ) {
|
||||
TreeBuilderReference tree = getState(ForestGridState.class).getMainTree();
|
||||
verts.setText("LOD verts: " + tree.getVertexCount(level));
|
||||
tris.setText("tris: " + tree.getTriangleCount(level));
|
||||
} else {
|
||||
verts.setText("LOD verts: ???");
|
||||
tris.setText("tris: ???");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 238 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 750 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.simsilica.arboreal.ui;
|
||||
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import com.simsilica.lemur.CheckboxModel;
|
||||
import com.simsilica.lemur.DefaultCheckboxModel;
|
||||
import com.simsilica.lemur.core.VersionedReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Provides checkbox models that are linked so that
|
||||
* only one is checked at a time.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class CheckboxModelGroup {
|
||||
|
||||
private List<CheckboxModel> models = new SafeArrayList<CheckboxModel>(CheckboxModel.class);
|
||||
private List<VersionedReference<Boolean>> refs = new ArrayList<VersionedReference<Boolean>>();
|
||||
|
||||
public CheckboxModelGroup() {
|
||||
}
|
||||
|
||||
public CheckboxModel createChild( boolean checked ) {
|
||||
return addChild(new DefaultCheckboxModel(checked));
|
||||
}
|
||||
|
||||
public CheckboxModel addChild( CheckboxModel model ) {
|
||||
int index = models.size();
|
||||
models.add(model);
|
||||
refs.add(model.createReference());
|
||||
if( model.isChecked() ) {
|
||||
updateChecked(index);
|
||||
}
|
||||
return new CheckboxModelWrapper(model);
|
||||
}
|
||||
|
||||
public void removeChild( CheckboxModel model ) {
|
||||
int index = models.indexOf(model);
|
||||
if( index < 0 ) {
|
||||
return;
|
||||
}
|
||||
models.remove(index);
|
||||
refs.remove(index);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
for( int i = 0; i < refs.size(); i++ ) {
|
||||
VersionedReference<Boolean> ref = refs.get(i);
|
||||
if( ref.update() && ref.get() ) {
|
||||
updateChecked(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateChecked( int index ) {
|
||||
|
||||
for( int i = 0; i < models.size(); i++ ) {
|
||||
if( i == index ) {
|
||||
continue;
|
||||
}
|
||||
models.get(i).setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected class CheckboxModelWrapper implements CheckboxModel {
|
||||
private CheckboxModel delegate;
|
||||
|
||||
public CheckboxModelWrapper( CheckboxModel delegate ) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChecked( boolean b ) {
|
||||
delegate.setChecked(b);
|
||||
if( b ) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return delegate.isChecked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return delegate.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getObject() {
|
||||
return delegate.getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionedReference<Boolean> createReference() {
|
||||
return new VersionedReference<Boolean>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CheckboxModelWrapper[" + delegate + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
|
||||
import com.simsilica.lemur.*;
|
||||
import com.simsilica.lemur.Button.ButtonAction;
|
||||
import com.simsilica.lemur.component.*;
|
||||
|
||||
def gradient = TbtQuadBackgroundComponent.create(
|
||||
texture( name:"/com/simsilica/lemur/icons/bordered-gradient.png",
|
||||
generateMips:false ),
|
||||
1, 1, 1, 126, 126,
|
||||
1f, false );
|
||||
|
||||
def bevel = TbtQuadBackgroundComponent.create(
|
||||
texture( name:"/com/simsilica/lemur/icons/bevel-quad.png",
|
||||
generateMips:false ),
|
||||
0.125f, 8, 8, 119, 119,
|
||||
1f, false );
|
||||
|
||||
def border = TbtQuadBackgroundComponent.create(
|
||||
texture( name:"/com/simsilica/lemur/icons/border.png",
|
||||
generateMips:false ),
|
||||
1, 1, 1, 6, 6,
|
||||
1f, false );
|
||||
def border2 = TbtQuadBackgroundComponent.create(
|
||||
texture( name:"/com/simsilica/lemur/icons/border.png",
|
||||
generateMips:false ),
|
||||
1, 2, 2, 6, 6,
|
||||
1f, false );
|
||||
|
||||
def doubleGradient = new QuadBackgroundComponent( color(0.5, 0.75, 0.85, 0.5) );
|
||||
doubleGradient.texture = texture( name:"/com/simsilica/lemur/icons/double-gradient-128.png",
|
||||
generateMips:false )
|
||||
|
||||
selector( "glass" ) {
|
||||
fontSize = 14
|
||||
}
|
||||
|
||||
selector( "label", "glass" ) {
|
||||
//textVAlignment = VAlignment.Center
|
||||
//intsets = new Insets3f(2, 0, 0, 0);
|
||||
//background = bevel;
|
||||
insets = new Insets3f( 2, 2, 0, 2 );
|
||||
color = color(0.5, 0.75, 0.75, 0.85)
|
||||
//background = border;
|
||||
}
|
||||
|
||||
selector( "container", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
selector( "nestedProperties.container", "glass" ) {
|
||||
background = border2.clone();
|
||||
background.setColor(color(0.0, 0.0, 0.0, 0.5))
|
||||
}
|
||||
|
||||
selector( "stats", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
selector( "slider", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
def pressedCommand = new Command<Button>() {
|
||||
public void execute( Button source ) {
|
||||
if( source.isPressed() ) {
|
||||
source.move(1, -1, 0);
|
||||
} else {
|
||||
source.move(-1, 1, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
def stdButtonCommands = [
|
||||
(ButtonAction.Down):[pressedCommand],
|
||||
(ButtonAction.Up):[pressedCommand]
|
||||
];
|
||||
|
||||
selector( "title", "glass" ) {
|
||||
color = color(0.8, 0.9, 1, 0.85f)
|
||||
highlightColor = color(1, 0.8, 1, 0.85f)
|
||||
shadowColor = color(0, 0, 0, 0.75f)
|
||||
shadowOffset = new com.jme3.math.Vector3f(2, -2, 1);
|
||||
background = new QuadBackgroundComponent( color(0.5, 0.75, 0.85, 0.5) );
|
||||
background.texture = texture( name:"/com/simsilica/lemur/icons/double-gradient-128.png",
|
||||
generateMips:false )
|
||||
//background.setMargin(2, 2);
|
||||
insets = new Insets3f( 2, 2, 2, 2 );
|
||||
|
||||
buttonCommands = stdButtonCommands;
|
||||
}
|
||||
|
||||
|
||||
selector( "button", "glass" ) {
|
||||
background = gradient.clone()
|
||||
color = color(0.8, 0.9, 1, 0.85f)
|
||||
//color = color(0, 196f/255, 196f/255, 0.75f)
|
||||
background.setColor(color(0, 0.75, 0.75, 0.5))
|
||||
insets = new Insets3f( 2, 2, 2, 2 );
|
||||
|
||||
buttonCommands = stdButtonCommands;
|
||||
}
|
||||
|
||||
selector( "slider", "glass" ) {
|
||||
insets = new Insets3f( 1, 3, 1, 2 );
|
||||
}
|
||||
|
||||
selector( "slider", "button", "glass" ) {
|
||||
background = doubleGradient.clone()
|
||||
background.setColor(color(0.5, 0.75, 0.75, 0.5))
|
||||
insets = new Insets3f( 0, 0, 0, 0 );
|
||||
}
|
||||
|
||||
selector( "slider.thumb.button", "glass" ) {
|
||||
text = "[]"
|
||||
color = color(0.6, 0.8, 0.8, 0.85)
|
||||
}
|
||||
|
||||
selector( "slider.left.button", "glass" ) {
|
||||
text = "-"
|
||||
background = doubleGradient.clone()
|
||||
background.setColor(color(0.5, 0.75, 0.75, 0.5))
|
||||
background.setMargin(5, 0);
|
||||
color = color(0.6, 0.8, 0.8, 0.85)
|
||||
}
|
||||
|
||||
selector( "slider.right.button", "glass" ) {
|
||||
text = "+"
|
||||
background = doubleGradient.clone()
|
||||
background.setColor(color(0.5, 0.75, 0.75, 0.5))
|
||||
background.setMargin(4, 0);
|
||||
color = color(0.6, 0.8, 0.8, 0.85)
|
||||
}
|
||||
|
||||
selector( "checkbox", "glass" ) {
|
||||
def on = new IconComponent( "/com/simsilica/lemur/icons/Glass-check-on.png", 1f,
|
||||
0, 0, 1f, false );
|
||||
on.setColor(color(0.5, 0.9, 0.9, 0.9))
|
||||
on.setMargin(5, 0);
|
||||
def off = new IconComponent( "/com/simsilica/lemur/icons/Glass-check-off.png", 1f,
|
||||
0, 0, 1f, false );
|
||||
off.setColor(color(0.6, 0.8, 0.8, 0.8))
|
||||
off.setMargin(5, 0);
|
||||
|
||||
onView = on;
|
||||
offView = off;
|
||||
|
||||
//background = border.clone();
|
||||
//background.color = color(0, 0, 0, 1.0);
|
||||
|
||||
color = color(0.8, 0.9, 1, 0.85f)
|
||||
//insetsComponent = new DynamicInsetsComponent(1, 1, 1, 1);
|
||||
//insets = new Insets3f(0, 5, 0, 5);
|
||||
}
|
||||
|
||||
selector( "value", "label", "glass" ) {
|
||||
insets = new Insets3f( 1, 2, 0, 2 );
|
||||
textHAlignment = HAlignment.Right;
|
||||
background = border.clone();
|
||||
background.color = color(0.5, 0.75, 0.75, 0.25)
|
||||
color = color(0.6, 0.8, 0.8, 0.85)
|
||||
}
|
||||
|
||||
/*selector( "properties", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}*/
|
||||
|
||||
selector( "rollup", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
selector( "window", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
selector( "tabbedPanel", "glass" ) {
|
||||
activationColor = color(0.8, 0.9, 1, 0.85f)
|
||||
}
|
||||
|
||||
selector( "tabbedPanel.container", "glass" ) {
|
||||
background = null
|
||||
}
|
||||
|
||||
selector( "tab.button", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
//color = color(0.8, 0.9, 1, 0.85f)
|
||||
color = color(0.4, 0.45, 0.5, 0.85f)
|
||||
insets = new Insets3f( 4, 2, 0, 2 );
|
||||
|
||||
buttonCommands = stdButtonCommands;
|
||||
}
|
||||
|
||||
selector( "root.rollup.title", "glass" ) {
|
||||
background = new QuadBackgroundComponent( color(0.25, 0.5, 0.85, 0.5) );
|
||||
background.texture = texture( name:"/com/simsilica/lemur/icons/double-gradient-128.png",
|
||||
generateMips:false )
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.simsilica.fx;
|
||||
|
||||
import com.simsilica.arboreal.LegacyLightingState;
|
||||
|
||||
public class LightingState extends LegacyLightingState {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.simsilica.fx.sky;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
|
||||
public class AtmosphericParameters {
|
||||
public void applyGroundParameters(Material mat, boolean b) {
|
||||
// stub — sim-fx atmospheric effects not available
|
||||
}
|
||||
}
|
||||
12
simarboreal/src/main/java/com/simsilica/fx/sky/SkyState.java
Normal file
12
simarboreal/src/main/java/com/simsilica/fx/sky/SkyState.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.simsilica.fx.sky;
|
||||
|
||||
import com.simsilica.arboreal.LegacySkyState;
|
||||
|
||||
public class SkyState extends LegacySkyState {
|
||||
|
||||
private final AtmosphericParameters atmosphere = new AtmosphericParameters();
|
||||
|
||||
public AtmosphericParameters getAtmosphericParameters() {
|
||||
return atmosphere;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
import com.simsilica.lemur.*;
|
||||
import com.simsilica.lemur.Button.ButtonAction;
|
||||
import com.simsilica.lemur.component.*;
|
||||
|
||||
def gradient = TbtQuadBackgroundComponent.create(
|
||||
texture( name:"/com/simsilica/lemur/icons/bordered-gradient.png",
|
||||
generateMips:false ),
|
||||
1, 1, 1, 126, 126,
|
||||
1f, false );
|
||||
|
||||
def border = TbtQuadBackgroundComponent.create(
|
||||
texture( name:"/com/simsilica/lemur/icons/border.png",
|
||||
generateMips:false ),
|
||||
1, 2, 2, 6, 6,
|
||||
1f, false );
|
||||
|
||||
|
||||
selector( "nestedProperties.container", "glass" ) {
|
||||
background = border.clone();
|
||||
background.setColor(color(0.0, 0.0, 0.0, 0.5))
|
||||
}
|
||||
|
||||
selector( "stats", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
selector( "window", "glass" ) {
|
||||
background = gradient.clone()
|
||||
background.setColor(color(0.25, 0.5, 0.5, 0.5))
|
||||
}
|
||||
|
||||
selector( "root.rollup.title", "glass" ) {
|
||||
background = new QuadBackgroundComponent( color(0.25, 0.5, 0.85, 0.5) );
|
||||
background.texture = texture( name:"/com/simsilica/lemur/icons/double-gradient-128.png",
|
||||
generateMips:false )
|
||||
}
|
||||
34
simarboreal/src/main/resources/license.txt
Normal file
34
simarboreal/src/main/resources/license.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
34
simarboreal/src/main/resources/log4j.xml
Normal file
34
simarboreal/src/main/resources/log4j.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
|
||||
|
||||
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
|
||||
|
||||
<!-- ============================== -->
|
||||
<!-- Append messages to the console -->
|
||||
<!-- ============================== -->
|
||||
|
||||
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
|
||||
<param name="Target" value="System.out"/>
|
||||
<param name="Threshold" value="TRACE"/>
|
||||
|
||||
<layout class="org.apache.log4j.PatternLayout">
|
||||
<!-- The default pattern: Date Priority [Category] Message\n -->
|
||||
<param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
|
||||
<!-- ======================= -->
|
||||
<!-- Setup the Root category -->
|
||||
<!-- ======================= -->
|
||||
|
||||
<category name="com.simsilica.lemur.style">
|
||||
<priority value="INFO"/>
|
||||
</category>
|
||||
|
||||
<root>
|
||||
<priority value="INFO"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
||||
Reference in New Issue
Block a user