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

1
simarboreal/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.gradle/

View File

@@ -0,0 +1,332 @@
#import "Common/ShaderLib/Parallax.glsllib"
#import "Common/ShaderLib/Optics.glsllib"
#define ATTENUATION
//#define HQ_ATTENUATION
#import "MatDefs/FragScattering.glsllib"
varying vec2 texCoord;
#ifdef SEPARATE_TEXCOORD
varying vec2 texCoord2;
#endif
varying vec3 AmbientSum;
varying vec4 DiffuseSum;
varying vec3 SpecularSum;
varying float z;
#ifndef VERTEX_LIGHTING
uniform vec4 g_LightDirection;
//varying vec3 vPosition;
varying vec3 vViewDir;
varying vec4 vLightDir;
varying vec3 lightVec;
#else
varying vec2 vertexLightValues;
#endif
#ifdef DIFFUSEMAP
uniform sampler2D m_DiffuseMap;
uniform sampler2D m_BackgroundDiffuseMap;
uniform sampler2D m_NoiseMap;
#endif
#ifdef SPECULARMAP
uniform sampler2D m_SpecularMap;
#endif
#ifdef PARALLAXMAP
uniform sampler2D m_ParallaxMap;
#endif
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING)
uniform float m_ParallaxHeight;
#endif
#ifdef LIGHTMAP
uniform sampler2D m_LightMap;
#endif
#ifdef NORMALMAP
uniform sampler2D m_NormalMap;
#else
varying vec3 vNormal;
#endif
#ifdef ALPHAMAP
uniform sampler2D m_AlphaMap;
#endif
#ifdef COLORRAMP
uniform sampler2D m_ColorRamp;
#endif
uniform float m_AlphaDiscardThreshold;
#ifndef VERTEX_LIGHTING
uniform float m_Shininess;
#ifdef HQ_ATTENUATION
uniform vec4 g_LightPosition;
#endif
#ifdef USE_REFLECTION
uniform float m_ReflectionPower;
uniform float m_ReflectionIntensity;
varying vec4 refVec;
uniform ENVMAP m_EnvMap;
#endif
float tangDot(in vec3 v1, in vec3 v2){
float d = dot(v1,v2);
#ifdef V_TANGENT
d = 1.0 - d*d;
return step(0.0, d) * sqrt(d);
#else
return d;
#endif
}
float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){
#ifdef MINNAERT
float NdotL = max(0.0, dot(norm, lightdir));
float NdotV = max(0.0, dot(norm, viewdir));
return NdotL * pow(max(NdotL * NdotV, 0.1), -1.0) * 0.5;
#else
return max(0.0, dot(norm, lightdir));
#endif
}
float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){
// NOTE: check for shiny <= 1 removed since shininess is now
// 1.0 by default (uses matdefs default vals)
#ifdef LOW_QUALITY
// Blinn-Phong
// Note: preferably, H should be computed in the vertex shader
vec3 H = (viewdir + lightdir) * vec3(0.5);
return pow(max(tangDot(H, norm), 0.0), shiny);
#elif defined(WARDISO)
// Isotropic Ward
vec3 halfVec = normalize(viewdir + lightdir);
float NdotH = max(0.001, tangDot(norm, halfVec));
float NdotV = max(0.001, tangDot(norm, viewdir));
float NdotL = max(0.001, tangDot(norm, lightdir));
float a = tan(acos(NdotH));
float p = max(shiny/128.0, 0.001);
return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL)));
#else
// Standard Phong
vec3 R = reflect(-lightdir, norm);
return pow(max(tangDot(R, viewdir), 0.0), shiny);
#endif
}
vec2 computeLighting(in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){
float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir);
float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess);
#ifdef HQ_ATTENUATION
float att = clamp(1.0 - g_LightPosition.w * length(lightVec), 0.0, 1.0);
#else
float att = vLightDir.w;
#endif
if (m_Shininess <= 1.0) {
specularFactor = 0.0; // should be one instruction on most cards ..
}
specularFactor *= diffuseFactor;
return vec2(diffuseFactor, specularFactor) * vec2(att);
}
#endif
vec4 getColor( in sampler2D diffuseMap, in sampler2D diffuseMap2,
in sampler2D normalMap, in vec2 tc, in float distMix,
out vec3 normal ) {
vec2 tcOffset;
tcOffset = texture2D(m_NoiseMap, tc * 0.01).xy * 6.0 - 3.0;
vec4 diffuseColor = texture2D(diffuseMap, (tc + tcOffset) * 0.75);
tcOffset = (texture2D(m_NoiseMap, tc * 0.01).xy * 6.0) - 3.0;
vec4 subColor = texture2D(diffuseMap2, ((tc + tcOffset) * 1.0) * 0.1 );
diffuseColor = mix(diffuseColor, subColor, distMix);
#ifdef NORMALMAP
vec4 normalHeight = texture2D(normalMap, tc);
normal = normalize((normalHeight.xyz * vec3(2.0) - vec3(1.0)));
#else
normal = vec3(0.0, 1.0, 0.0);
#endif
return diffuseColor;
}
void main(){
vec2 newTexCoord;
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING)
#ifdef STEEP_PARALLAX
#ifdef NORMALMAP_PARALLAX
//parallax map is stored in the alpha channel of the normal map
newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
#else
//parallax map is a texture
newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
#endif
#else
#ifdef NORMALMAP_PARALLAX
//parallax map is stored in the alpha channel of the normal map
newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
#else
//parallax map is a texture
newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
#endif
#endif
#else
newTexCoord = texCoord;
#endif
float distMix = z / 32.0;
distMix = clamp(distMix, 0.4, 1.0);
#ifdef DIFFUSEMAP
vec3 newNormal;
#ifdef NORMALMAP
vec4 diffuseColor = getColor(m_DiffuseMap, m_BackgroundDiffuseMap,
m_NormalMap, texCoord, distMix, newNormal);
#else
vec4 diffuseColor = getColor(m_DiffuseMap, m_BackgroundDiffuseMap,
m_DiffuseMap, texCoord, distMix, newNormal);
#endif
#else
vec4 diffuseColor = vec4(1.0);
vec3 newNormal = vec3(0.0, 1.0, 0.0);
#endif
float alpha = DiffuseSum.a * diffuseColor.a;
#ifdef ALPHAMAP
alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r;
#endif
if(alpha < m_AlphaDiscardThreshold){
discard;
}
#ifndef VERTEX_LIGHTING
float spotFallOff = 1.0;
#if __VERSION__ >= 110
// allow use of control flow
if(g_LightDirection.w != 0.0){
#endif
vec3 L = normalize(lightVec.xyz);
vec3 spotdir = normalize(g_LightDirection.xyz);
float curAngleCos = dot(-L, spotdir);
float innerAngleCos = floor(g_LightDirection.w) * 0.001;
float outerAngleCos = fract(g_LightDirection.w);
float innerMinusOuter = innerAngleCos - outerAngleCos;
spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter;
#if __VERSION__ >= 110
if(spotFallOff <= 0.0){
gl_FragColor.rgb = AmbientSum * diffuseColor.rgb;
gl_FragColor.a = alpha;
return;
}else{
spotFallOff = clamp(spotFallOff, 0.0, 1.0);
}
}
#else
spotFallOff = clamp(spotFallOff, step(g_LightDirection.w, 0.001), 1.0);
#endif
#endif
// ***********************
// Read from textures
// ***********************
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
vec3 normal = newNormal;
#elif !defined(VERTEX_LIGHTING)
vec3 normal = vNormal;
#if !defined(LOW_QUALITY) && !defined(V_TANGENT)
normal = normalize(normal);
#endif
#endif
#ifdef SPECULARMAP
vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
#else
vec4 specularColor = vec4(1.0);
#endif
#ifdef LIGHTMAP
vec3 lightMapColor;
#ifdef SEPARATE_TEXCOORD
lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
#else
lightMapColor = texture2D(m_LightMap, texCoord).rgb;
#endif
specularColor.rgb *= lightMapColor;
diffuseColor.rgb *= lightMapColor;
#endif
#ifdef VERTEX_LIGHTING
vec2 light = vertexLightValues.xy;
#ifdef COLORRAMP
light.x = texture2D(m_ColorRamp, vec2(light.x, 0.0)).r;
light.y = texture2D(m_ColorRamp, vec2(light.y, 0.0)).r;
#endif
#ifndef USE_SCATTERING
gl_FragColor.rgb = AmbientSum * diffuseColor.rgb +
DiffuseSum.rgb * diffuseColor.rgb * vec3(light.x) +
SpecularSum * specularColor.rgb * vec3(light.y);
#else
vec3 color = AmbientSum * diffuseColor.rgb +
DiffuseSum.rgb * diffuseColor.rgb * vec3(light.x) +
SpecularSum * specularColor.rgb * vec3(light.y);
gl_FragColor.rgb = calculateGroundColor(vec4(color, 1.0)).rgb;
#endif
#else
vec4 lightDir = vLightDir;
lightDir.xyz = normalize(lightDir.xyz);
vec3 viewDir = normalize(vViewDir);
vec2 light = computeLighting(normal, viewDir, lightDir.xyz) * spotFallOff;
#ifdef COLORRAMP
diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb;
specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb;
#endif
// Workaround, since it is not possible to modify varying variables
vec4 SpecularSum2 = vec4(SpecularSum, 1.0);
#ifdef USE_REFLECTION
vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz);
// Interpolate light specularity toward reflection color
// Multiply result by specular map
specularColor = mix(SpecularSum2 * light.y, refColor, refVec.w) * specularColor;
SpecularSum2 = vec4(1.0);
light.y = 1.0;
#endif
#ifndef USE_SCATTERING
gl_FragColor.rgb = AmbientSum * diffuseColor.rgb +
DiffuseSum.rgb * diffuseColor.rgb * vec3(light.x) +
SpecularSum * specularColor.rgb * vec3(light.y);
#else
vec3 color = AmbientSum * diffuseColor.rgb +
DiffuseSum.rgb * diffuseColor.rgb * vec3(light.x) +
SpecularSum * specularColor.rgb * vec3(light.y);
gl_FragColor.rgb = calculateGroundColor(vec4(color, 1.0)).rgb;
#endif
#endif
gl_FragColor.a = alpha;
}

View File

@@ -0,0 +1,352 @@
MaterialDef Phong Lighting {
MaterialParameters {
// Compute vertex lighting in the shader
// For better performance
Boolean VertexLighting
// Use more efficent algorithms to improve performance
Boolean LowQuality
// Improve quality at the cost of performance
Boolean HighQuality
// Output alpha from the diffuse map
Boolean UseAlpha
// Alpha threshold for fragment discarding
Float AlphaDiscardThreshold (AlphaTestFallOff)
// Normal map is in BC5/ATI2n/LATC/3Dc compression format
Boolean LATC
// Use the provided ambient, diffuse, and specular colors
Boolean UseMaterialColors
// Activate shading along the tangent, instead of the normal
// Requires tangent data to be available on the model.
Boolean VTangent
// Use minnaert diffuse instead of lambert
Boolean Minnaert
// Use ward specular instead of phong
Boolean WardIso
// Use vertex color as an additional diffuse color.
Boolean UseVertexColor
// Ambient color
Color Ambient (MaterialAmbient)
// Diffuse color
Color Diffuse (MaterialDiffuse)
// Specular color
Color Specular (MaterialSpecular)
// Specular power/shininess
Float Shininess (MaterialShininess) : 1
// Diffuse map
Texture2D DiffuseMap
// Diffuse map
Texture2D BackgroundDiffuseMap
// Diffuse map
Texture2D NoiseMap
// Normal map
Texture2D NormalMap
// Specular/gloss map
Texture2D SpecularMap
// Parallax/height map
Texture2D ParallaxMap
//Set to true is parallax map is stored in the alpha channel of the normal map
Boolean PackedNormalParallax
//Sets the relief height for parallax mapping
Float ParallaxHeight : 0.05
//Set to true to activate Steep Parallax mapping
Boolean SteepParallax
// Texture that specifies alpha values
Texture2D AlphaMap
// Color ramp, will map diffuse and specular values through it.
Texture2D ColorRamp
// Texture of the glowing parts of the material
Texture2D GlowMap
// Set to Use Lightmap
Texture2D LightMap
// Set to use TexCoord2 for the lightmap sampling
Boolean SeparateTexCoord
// The glow color of the object
Color GlowColor
// Parameters for fresnel
// X = bias
// Y = scale
// Z = power
Vector3 FresnelParams
// Env Map for reflection
TextureCubeMap EnvMap
// the env map is a spheremap and not a cube map
Boolean EnvMapAsSphereMap
//shadows
Int FilterMode
Boolean HardwareShadows
Texture2D ShadowMap0
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
Vector2 FadeInfo
Matrix4 LightViewProjectionMatrix0
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Float PCFEdge
Float ShadowMapSize
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
// Ground scattering parameters
Boolean UseScattering
Vector3 SunPosition
Float Exposure
Float KmESun
Float InnerRadius
Float RadiusScale
Float PlanetScale : 1
Vector3 InvWavelengthsKrESun
Float AverageDensityScale
Float InvAverageDensityHeight;
Vector3 KWavelengths4PI;
}
Technique {
LightMode MultiPass
VertexShader GLSL110: MatDefs/MultiResolution.vert
FragmentShader GLSL110: MatDefs/MultiResolution.frag
WorldParameters {
WorldViewProjectionMatrix
NormalMatrix
WorldViewMatrix
ViewMatrix
CameraPosition
WorldMatrix
}
Defines {
LATC : LATC
VERTEX_COLOR : UseVertexColor
VERTEX_LIGHTING : VertexLighting
ATTENUATION : Attenuation
MATERIAL_COLORS : UseMaterialColors
V_TANGENT : VTangent
MINNAERT : Minnaert
WARDISO : WardIso
LOW_QUALITY : LowQuality
HQ_ATTENUATION : HighQuality
DIFFUSEMAP : DiffuseMap
NORMALMAP : NormalMap
SPECULARMAP : SpecularMap
PARALLAXMAP : ParallaxMap
NORMALMAP_PARALLAX : PackedNormalParallax
STEEP_PARALLAX : SteepParallax
ALPHAMAP : AlphaMap
COLORRAMP : ColorRamp
LIGHTMAP : LightMap
SEPARATE_TEXCOORD : SeparateTexCoord
USE_REFLECTION : EnvMap
SPHERE_MAP : SphereMap
NUM_BONES : NumberOfBones
USE_SCATTERING : UseScattering
}
}
Technique PreShadow {
VertexShader GLSL110 : Common/MatDefs/Shadow/PreShadow.vert
FragmentShader GLSL110 : Common/MatDefs/Shadow/PreShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
}
Defines {
COLOR_MAP : ColorMap
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
}
ForcedRenderState {
FaceCull Off
DepthTest On
DepthWrite On
PolyOffset 5 3
ColorWrite Off
}
}
Technique PostShadow15{
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
}
ForcedRenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique PostShadow{
VertexShader GLSL110: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL110: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
DISCARD_ALPHA : AlphaDiscardThreshold
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
}
ForcedRenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique PreNormalPass {
VertexShader GLSL110 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL110 : Common/MatDefs/SSAO/normal.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
}
Defines {
DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones
}
}
Technique GBuf {
VertexShader GLSL110: Common/MatDefs/Light/GBuf.vert
FragmentShader GLSL110: Common/MatDefs/Light/GBuf.frag
WorldParameters {
WorldViewProjectionMatrix
NormalMatrix
WorldViewMatrix
WorldMatrix
}
Defines {
VERTEX_COLOR : UseVertexColor
MATERIAL_COLORS : UseMaterialColors
V_TANGENT : VTangent
MINNAERT : Minnaert
WARDISO : WardIso
DIFFUSEMAP : DiffuseMap
NORMALMAP : NormalMap
SPECULARMAP : SpecularMap
PARALLAXMAP : ParallaxMap
}
}
Technique Glow {
VertexShader GLSL110: Common/MatDefs/Misc/Unshaded.vert
FragmentShader GLSL110: Common/MatDefs/Light/Glow.frag
WorldParameters {
WorldViewProjectionMatrix
}
Defines {
NEED_TEXCOORD1
HAS_GLOWMAP : GlowMap
HAS_GLOWCOLOR : GlowColor
NUM_BONES : NumberOfBones
}
}
}

View File

@@ -0,0 +1,237 @@
#define ATTENUATION
//#define HQ_ATTENUATION
#import "Common/ShaderLib/Skinning.glsllib"
#import "MatDefs/VertScattering.glsllib"
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_WorldMatrix;
uniform mat3 g_NormalMatrix;
uniform mat4 g_ViewMatrix;
uniform vec3 g_CameraPosition;
uniform vec4 m_Ambient;
uniform vec4 m_Diffuse;
uniform vec4 m_Specular;
uniform float m_Shininess;
uniform vec4 g_LightColor;
uniform vec4 g_LightPosition;
uniform vec4 g_AmbientLightColor;
varying vec2 texCoord;
#ifdef SEPARATE_TEXCOORD
varying vec2 texCoord2;
attribute vec2 inTexCoord2;
#endif
varying vec3 AmbientSum;
varying vec4 DiffuseSum;
varying vec3 SpecularSum;
varying float z;
attribute vec3 inPosition;
attribute vec2 inTexCoord;
attribute vec3 inNormal;
varying vec3 lightVec;
//varying vec4 spotVec;
#ifdef VERTEX_COLOR
attribute vec4 inColor;
#endif
#ifndef VERTEX_LIGHTING
attribute vec4 inTangent;
#ifndef NORMALMAP
varying vec3 vNormal;
#endif
//varying vec3 vPosition;
varying vec3 vViewDir;
varying vec4 vLightDir;
#else
varying vec2 vertexLightValues;
uniform vec4 g_LightDirection;
#endif
#ifdef USE_REFLECTION
uniform vec3 g_CameraPosition;
uniform mat4 g_WorldMatrix;
uniform vec3 m_FresnelParams;
varying vec4 refVec;
/**
* Input:
* attribute inPosition
* attribute inNormal
* uniform g_WorldMatrix
* uniform g_CameraPosition
*
* Output:
* varying refVec
*/
void computeRef(in vec4 modelSpacePos){
vec3 worldPos = (g_WorldMatrix * modelSpacePos).xyz;
vec3 I = normalize( g_CameraPosition - worldPos ).xyz;
vec3 N = normalize( (g_WorldMatrix * vec4(inNormal, 0.0)).xyz );
refVec.xyz = reflect(I, N);
refVec.w = m_FresnelParams.x + m_FresnelParams.y * pow(1.0 + dot(I, N), m_FresnelParams.z);
}
#endif
// JME3 lights in world space
void lightComputeDir(in vec3 worldPos, in vec4 color, in vec4 position, out vec4 lightDir){
float posLight = step(0.5, color.w);
vec3 tempVec = position.xyz * sign(posLight - 0.5) - (worldPos * posLight);
lightVec = tempVec;
#ifdef ATTENUATION
float dist = length(tempVec);
lightDir.w = clamp(1.0 - position.w * dist * posLight, 0.0, 1.0);
lightDir.xyz = tempVec / vec3(dist);
#else
lightDir = vec4(normalize(tempVec), 1.0);
#endif
}
#ifdef VERTEX_LIGHTING
float lightComputeDiffuse(in vec3 norm, in vec3 lightdir){
return max(0.0, dot(norm, lightdir));
}
float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){
if (shiny <= 1.0){
return 0.0;
}
#ifndef LOW_QUALITY
vec3 H = (viewdir + lightdir) * vec3(0.5);
return pow(max(dot(H, norm), 0.0), shiny);
#else
return 0.0;
#endif
}
vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec4 wvLightPos){
vec4 lightDir;
lightComputeDir(wvPos, g_LightColor, wvLightPos, lightDir);
float spotFallOff = 1.0;
if(g_LightDirection.w != 0.0){
vec3 L=normalize(lightVec.xyz);
vec3 spotdir = normalize(g_LightDirection.xyz);
float curAngleCos = dot(-L, spotdir);
float innerAngleCos = floor(g_LightDirection.w) * 0.001;
float outerAngleCos = fract(g_LightDirection.w);
float innerMinusOuter = innerAngleCos - outerAngleCos;
spotFallOff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0);
}
float diffuseFactor = lightComputeDiffuse(wvNorm, lightDir.xyz);
float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, lightDir.xyz, m_Shininess);
//specularFactor *= step(0.01, diffuseFactor);
return vec2(diffuseFactor, specularFactor) * vec2(lightDir.w)*spotFallOff;
}
#endif
void main(){
vec4 modelSpacePos = vec4(inPosition, 1.0);
vec3 modelSpaceNorm = inNormal;
#ifndef VERTEX_LIGHTING
vec3 modelSpaceTan = inTangent.xyz;
#endif
#ifdef NUM_BONES
#ifndef VERTEX_LIGHTING
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Skinning_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef USE_SCATTERING
vec4 wPos = g_WorldMatrix * modelSpacePos;
calculateVertexGroundScattering(wPos.xyz, g_CameraPosition);
#endif
gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
texCoord = inTexCoord;
#ifdef SEPARATE_TEXCOORD
texCoord2 = inTexCoord2;
#endif
vec3 wvPosition = (g_WorldViewMatrix * modelSpacePos).xyz;
z = length(wvPosition);
vec3 wvNormal = normalize(g_NormalMatrix * modelSpaceNorm);
vec3 viewDir = normalize(-wvPosition);
//vec4 lightColor = g_LightColor[gl_InstanceID];
//vec4 lightPos = g_LightPosition[gl_InstanceID];
//vec4 wvLightPos = (g_ViewMatrix * vec4(lightPos.xyz, lightColor.w));
//wvLightPos.w = lightPos.w;
vec4 wvLightPos = (g_ViewMatrix * vec4(g_LightPosition.xyz,clamp(g_LightColor.w,0.0,1.0)));
wvLightPos.w = g_LightPosition.w;
vec4 lightColor = g_LightColor;
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
vec3 wvTangent = normalize(g_NormalMatrix * modelSpaceTan);
vec3 wvBinormal = cross(wvNormal, wvTangent);
mat3 tbnMat = mat3(wvTangent, wvBinormal * -inTangent.w,wvNormal);
//vPosition = wvPosition * tbnMat;
//vViewDir = viewDir * tbnMat;
vViewDir = -wvPosition * tbnMat;
lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);
vLightDir.xyz = (vLightDir.xyz * tbnMat).xyz;
#elif !defined(VERTEX_LIGHTING)
vNormal = wvNormal;
//vPosition = wvPosition;
vViewDir = viewDir;
lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);
#ifdef V_TANGENT
vNormal = normalize(g_NormalMatrix * inTangent.xyz);
vNormal = -cross(cross(vLightDir.xyz, vNormal), vNormal);
#endif
#endif
//computing spot direction in view space and unpacking spotlight cos
// spotVec = (g_ViewMatrix * vec4(g_LightDirection.xyz, 0.0) );
// spotVec.w = floor(g_LightDirection.w) * 0.001;
// lightVec.w = fract(g_LightDirection.w);
lightColor.w = 1.0;
#ifdef MATERIAL_COLORS
AmbientSum = (m_Ambient * g_AmbientLightColor).rgb;
DiffuseSum = m_Diffuse * lightColor;
SpecularSum = (m_Specular * lightColor).rgb;
#else
AmbientSum = vec3(0.2, 0.2, 0.2) * g_AmbientLightColor.rgb; // Default: ambient color is dark gray
DiffuseSum = lightColor;
SpecularSum = vec3(0.0);
#endif
#ifdef VERTEX_COLOR
AmbientSum *= inColor.rgb;
DiffuseSum *= inColor;
#endif
#ifdef VERTEX_LIGHTING
vertexLightValues = computeLighting(wvPosition, wvNormal, viewDir, wvLightPos);
#endif
#ifdef USE_REFLECTION
computeRef(modelSpacePos);
#endif
}

View File

@@ -0,0 +1,10 @@
#import "Common/ShaderLib/MultiSample.glsllib"
uniform COLORTEXTURE m_Texture;
varying vec2 texCoord;
void main() {
vec4 texVal = getColor(m_Texture, texCoord);
gl_FragColor = texVal;
}

View File

@@ -0,0 +1,24 @@
MaterialDef Depth Blur {
MaterialParameters {
Int NumSamples
Int NumSamplesDepth
Texture2D Texture
Texture2D DepthTexture
}
Technique {
VertexShader GLSL100: Common/MatDefs/Post/Post.vert
FragmentShader GLSL100: MatDefs/Null.frag
WorldParameters {
WorldViewProjectionMatrix
}
Defines {
RESOLVE_MS : NumSamples
RESOLVE_DEPTH_MS : NumSamplesDepth
}
}
}

View File

@@ -0,0 +1,72 @@
#import "Common/ShaderLib/MultiSample.glsllib"
//#define SHOW_BOX
//#define SHOW_DELTA
uniform vec2 g_FrustumNearFar;
uniform vec4 g_ViewPort;
uniform vec4 m_ShadowColor;
uniform COLORTEXTURE m_FrameTexture;
uniform DEPTHTEXTURE m_DepthTexture;
varying vec3 texCoord;
varying vec3 vViewDir;
varying vec3 boxScale;
void main(){
vec4 color = vec4(1.0);
vec2 uv = vec2(gl_FragCoord.x/g_ViewPort.z, gl_FragCoord.y/g_ViewPort.w);
float zBuffer = getDepth( m_DepthTexture, uv ).r;
//
// z_buffer_value = a + b / z;
//
// Where:
// a = zFar / ( zFar - zNear )
// b = zFar * zNear / ( zNear - zFar )
// z = distance from the eye to the object
//
// Which means:
// zb - a = b / z;
// z * (zb - a) = b
// z = b / (zb - a)
//
float a = g_FrustumNearFar.y / (g_FrustumNearFar.y - g_FrustumNearFar.x);
float b = g_FrustumNearFar.y * g_FrustumNearFar.x / (g_FrustumNearFar.x - g_FrustumNearFar.y);
float z = b / (zBuffer - a);
float us = b / (gl_FragCoord.z - a);
float modelScale = 1.0;
float delta = (z-us) * modelScale;
#if defined(SHOW_DELTA)
color = vec4(delta, 0.0, 0.0, 1.0);
#elif defined(SHOW_BOX)
color = vec4(texCoord * boxScale,1.0);
#else
vec3 view = normalize(vViewDir);
vec3 scene = texCoord + view * delta;
vec3 stu = scene * boxScale;
float xTex = (0.5 - stu.x) * 2.0;
float zTex = (0.5 - stu.z) * 2.0;
float t = stu.y;
float low = (t - 0.75) * 1.33333;
float hi = (t - 0.75) * 4.0;
float yTex = low * step(t, 0.75) + hi * step(0.75, t);
float col = sqrt((xTex * xTex) + (zTex * zTex) + (yTex * yTex));
float shadow = (1.0 - col);
color = vec4(m_ShadowColor);
color.a *= clamp(shadow, 0.0, 0.8);
#endif
gl_FragColor = color;
}

View File

@@ -0,0 +1,29 @@
MaterialDef Simple Shadows {
MaterialParameters {
Int NumSamples
Int NumSamplesDepth
Color ShadowColor
Texture2D FrameTexture
Texture2D DepthTexture
}
Technique {
VertexShader GLSL120: MatDefs/Shadows.vert
FragmentShader GLSL130: MatDefs/Shadows.frag
WorldParameters {
ViewProjectionMatrix
FrustumNearFar
ViewPort
}
Defines {
RESOLVE_MS : NumSamples
RESOLVE_DEPTH_MS : NumSamplesDepth
}
}
}

View File

@@ -0,0 +1,22 @@
uniform mat4 g_ViewProjectionMatrix;
attribute vec3 inPosition; // the world position
attribute vec3 inTexCoord; // the model space position, relative to a corner
attribute vec3 inTexCoord2; // the x,y,z scale to get from model space to 0->1 space
attribute vec3 inNormal; // the view direction in model-space
varying vec3 texCoord;
varying vec3 vViewDir;
varying vec3 boxScale;
void main(){
vec4 modelSpacePos = vec4(inPosition, 1.0);
gl_Position = g_ViewProjectionMatrix * modelSpacePos;
vViewDir = inNormal;
texCoord = inTexCoord;
boxScale = inTexCoord2;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

31
simarboreal/build.gradle Normal file
View File

@@ -0,0 +1,31 @@
plugins {
id 'application'
}
application {
mainClass = 'com.simsilica.arboreal.TreeEditor'
applicationDefaultJvmArgs = ['-Xmx512m', '-XX:MaxDirectMemorySize=512m']
}
sourceSets.main.resources {
srcDirs += 'src/main/java'
exclude '**/*.java'
exclude '**/*.tmp'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
runtimeOnly files('assets')
}
tasks.register('extractNatives', Copy) {
from zipTree(file('libs/jME3-lwjgl-natives.jar'))
into "${buildDir}/natives"
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
run {
dependsOn extractNatives
workingDir = rootDir
jvmArgs "-Djava.library.path=${buildDir}/natives"
}

BIN
simarboreal/libs/Lemur.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
simarboreal/libs/Pager.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
simarboreal/libs/assets.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
simarboreal/libs/jinput.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
simarboreal/libs/lwjgl.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--assets-impl.xml v1.0-->
<project name="assets-impl" basedir="..">
<target name="-init-assets">
<jar jarfile="${build.dir}/${assets.jar.name}" excludes="${assets.excludes}" basedir="${assets.folder.name}" compress="${assets.compress}"/>
<property location="${assets.folder.name}" name="assets.dir.resolved"/>
<property location="${build.dir}/${assets.jar.name}" name="assets.jar.resolved"/>
<property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
<pathconvert property="run.classpath.without.build.classes.dir">
<path path="${run.classpath}"/>
<map from="${build.classes.dir.resolved}" to=""/>
<map from="${assets.dir.resolved}" to="${assets.jar.resolved}"/>
</pathconvert>
</target>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
build.xml.data.CRC32=94bf7c61
build.xml.script.CRC32=79a29eb7
build.xml.stylesheet.CRC32=958a1d3e@1.32.1.45
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=0f2662fc
nbproject/build-impl.xml.script.CRC32=bb94fa75
nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48

View File

@@ -0,0 +1,124 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
ant.customtasks.libs=launch4j
application.desc=Editor for the tree parameters necessary for generating trees.
application.homepage=https://code.google.com/p/simsilica-tools/
application.splash=C:\\Development\\google\\simsilica-tools\\trunk\\SimArboreal-Editor\\TreeEditor-Splash.png
application.title=SimArboreal-Editor
application.vendor=Simsilica, LLC
assets.jar.name=assets.jar
assets.excludes=**/*.j3odata,**/*.mesh,**/*.skeleton,**/*.mesh.xml,**/*.skeleton.xml,**/*.scene,**/*.material,**/*.obj,**/*.mtl,**/*.3ds,**/*.dae,**/*.blend,**/*.blend*[0-9],**/.backups/**,**/*.psd
assets.folder.name=assets
assets.compress=true
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form,**/.backups/**
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
compile.on.save=true
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.test.classpath=\
${run.test.classpath}
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=${dist.dir}/${application.title}.jar
dist.javadoc.dir=${dist.dir}/javadoc
endorsed.classpath=
excludes=
file.reference.arboreal-assets.jar=..\\SimArboreal\\dist\\lib\\arboreal-assets.jar
file.reference.groovy-all-2.1.9.jar=lib\\groovy-all-2.1.9.jar
file.reference.guava-12.0.jar=lib\\guava-12.0.jar
file.reference.log4j-1.2.12.jar=lib\\log4j-1.2.12.jar
file.reference.meta-jb-json-1.0.1.jar=lib\\meta-jb-json-1.0.1.jar
file.reference.simfx-assets.jar=..\\SimFX\\dist\\lib\\simfx-assets.jar
file.reference.slf4j-api-1.7.5.jar=lib\\slf4j-api-1.7.5.jar
file.reference.slf4j-log4j12-1.7.5.jar=lib\\slf4j-log4j12-1.7.5.jar
includes=**
jar.compress=false
javac.classpath=\
${reference.SimArboreal.jar}:\
${libs.jme3-lwjgl.classpath}:\
${libs.jme3-effects.classpath}:\
${libs.jme3-desktop.classpath}:\
${file.reference.arboreal-assets.jar}:\
${reference.Pager.jar}:\
${reference.SimFX.jar}:\
${libs.jme3-core.classpath}:\
${reference.Lemur.jar}:\
${reference.LemurProps.jar}:\
${file.reference.groovy-all-2.1.9.jar}:\
${file.reference.guava-12.0.jar}:\
${file.reference.log4j-1.2.12.jar}:\
${file.reference.meta-jb-json-1.0.1.jar}:\
${file.reference.slf4j-api-1.7.5.jar}:\
${file.reference.slf4j-log4j12-1.7.5.jar}:\
${file.reference.simfx-assets.jar}
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.processorpath=\
${javac.classpath}
javac.source=1.6
javac.target=1.6
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
jnlp.codebase.type=local
jnlp.descriptor=application
jnlp.enabled=false
jnlp.offline-allowed=false
jnlp.signed=false
launch4j.exe.enabled=true
linux.launcher.enabled=true
mac.app.enabled=true
main.class=com.simsilica.arboreal.TreeEditor
meta.inf.dir=${src.dir}/META-INF
manifest.file=MANIFEST.MF
mkdist.disabled=false
platform.active=JDK_1.7
project.Lemur=../../Lemur
project.LemurProps=../../Lemur/extensions/LemurProps
project.Pager=../Pager
project.SimArboreal=../SimArboreal
project.SimFX=../SimFX
reference.Lemur.jar=${project.Lemur}/dist/Lemur.jar
reference.LemurProps.jar=${project.LemurProps}/dist/LemurProps.jar
reference.Pager.jar=${project.Pager}/dist/Pager.jar
reference.SimArboreal.jar=${project.SimArboreal}/dist/SimArboreal.jar
reference.SimFX.jar=${project.SimFX}/dist/SimFX.jar
run.classpath=\
${javac.classpath}:\
${build.classes.dir}:\
${assets.folder.name}
# Space-separated list of JVM arguments used when running the project
# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
# or test-sys-prop.name=value to set system properties for unit tests):
run.jvmargs=-Xmx512m -XX:MaxDirectMemorySize=512m
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
source.encoding=UTF-8
src.java.dir=src\\main\\java
src.resources.dir=src\\main\\resources

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<buildExtensions xmlns="http://www.netbeans.org/ns/ant-build-extender/1">
<extension file="assets-impl.xml" id="assets">
<dependency dependsOn="-init-assets" target="-do-init"/>
</extension>
<extension file="launch4j-impl.xml" id="launch4j">
<dependency dependsOn="-launch4j-exe" target="jar"/>
</extension>
<extension file="macapp-impl.xml" id="macapp">
<dependency dependsOn="-mac-app" target="jar"/>
</extension>
<extension file="linuxlauncher-impl.xml" id="linuxlauncher">
<dependency dependsOn="-linux-launcher" target="jar"/>
</extension>
</buildExtensions>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>SimArboreal-Editor</name>
<explicit-platform explicit-source-supported="true"/>
<source-roots>
<root id="src.resources.dir"/>
<root id="src.java.dir"/>
</source-roots>
<test-roots/>
</data>
<references xmlns="http://www.netbeans.org/ns/ant-project-references/1">
<reference>
<foreign-project>Lemur</foreign-project>
<artifact-type>jar</artifact-type>
<script>build.xml</script>
<target>jar</target>
<clean-target>clean</clean-target>
<id>jar</id>
</reference>
<reference>
<foreign-project>LemurProps</foreign-project>
<artifact-type>jar</artifact-type>
<script>build.xml</script>
<target>jar</target>
<clean-target>clean</clean-target>
<id>jar</id>
</reference>
<reference>
<foreign-project>Pager</foreign-project>
<artifact-type>jar</artifact-type>
<script>build.xml</script>
<target>jar</target>
<clean-target>clean</clean-target>
<id>jar</id>
</reference>
<reference>
<foreign-project>SimArboreal</foreign-project>
<artifact-type>jar</artifact-type>
<script>build.xml</script>
<target>jar</target>
<clean-target>clean</clean-target>
<id>jar</id>
</reference>
<reference>
<foreign-project>SimFX</foreign-project>
<artifact-type>jar</artifact-type>
<script>build.xml</script>
<target>jar</target>
<clean-target>clean</clean-target>
<id>jar</id>
</reference>
</references>
</configuration>
</project>

Binary file not shown.

View File

@@ -0,0 +1,3 @@
#!/bin/sh
java -Xmx512m -XX:MaxDirectMemorySize=512m -jar SimArboreal-Editor.jar

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,71 @@
Revision ???
-------------
- Added a dependency to the SimFX package and converted to use
its LightingState and SkyState (with scattering)
- Grass plane supports atmospherics and a toggle was added to
the visualization options panel.
- Added atmospheric support for trees along with a toggle.
Revision 143
-------------
- Added an action to save the tree atlas images
as PNG files.
- Fixed how the atlas textures are generated so that they
save as embedded textures in the j3o.
- Fixed impostor meshes to use short buffers instead of int.
- Added toggleable noise-based wind
- Added a video recodring option F12
- Added editors for the tree wind-related parameters
- Changed the tree parameters file extension to just plain
.simap The old simap.json extension is still supported for
loading and the file format itself hasn't changed.
Revision 126
-------------
- Reworked the FileActionsState to better allow embedding
in external applications. Save and load methods were added
and the buttons are now not added to the UI unless the state
is enabled. This should help facilitate a parallel JME SDK
plug-in effort.
Revision 94
------------
- Moved RollupPanel and TabbedPanel out into Lemur core.
- Moved the builder classes out into the new simsilica-tools Pager
library.
- Moved PropertyPanel into its own Lemur extension project LemurProps.
- Converted to use the now-standard Lemur 'glass' style with just a few
local custom extensions.
- Moved the Builderstate out to the builder project.
Revision 69
-------------
- Added Y offset parameter that is separate from trunk
height.
- Added LOD support including two mesh reduction strategies:
Flat-Poly : renders the tree branches as a set of axis-aligned
billboarded flat quads.
Impostor : renders a single quad with a view-direction indexed texture.
(Note: impostors currently don't save properly to the j3o)
- Better visual separate of child properies in the UI.
- Reorganized UI to include outer rollup panels to separate vis
settings from tree parameters.
- Added an avatar toggle to the UI.
- Added shadow intensity and lighting direction settings to the
UI.
- Added a simplified 'drop shadow' filter that can be enabled instead
of regular shadows.
- File write operations now warn before overwriting existing files.
- Cleaned out the wire frame meshes from the exported j3o during save.
- Renamed the tree geometry elements to make more sense when viewing
the tree object in something like Scene Composer.
Revision 33
-------------
- Initial release

Binary file not shown.

View File

@@ -0,0 +1 @@
rootProject.name = 'sim-arboreal-editor'

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

Some files were not shown because too many files have changed in this diff Show More