Arbeiten aus dem URlaub

This commit is contained in:
2026-05-19 12:55:05 +02:00
parent b8a0234ad2
commit 4f48834e2c
403 changed files with 23402 additions and 6389 deletions

View File

@@ -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() {
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}
}

View 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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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 );
}
}

View File

@@ -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 );
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View 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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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

View File

@@ -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 + "]";
}
}
}

View File

@@ -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 )
}

View File

@@ -0,0 +1,6 @@
package com.simsilica.fx;
import com.simsilica.arboreal.LegacyLightingState;
public class LightingState extends LegacyLightingState {
}

View File

@@ -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
}
}

View 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;
}
}

View File

@@ -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 )
}

View 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.
*/

View 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>