Arbeiten aus dem URlaub
21
ez-tree-jme/assets/MatDefs/Tree.j3md
Normal file
@@ -0,0 +1,21 @@
|
||||
MaterialDef Tree {
|
||||
|
||||
MaterialParameters {
|
||||
Color Diffuse (Color) : 0.42 0.26 0.10 1.0
|
||||
Float WindStrength : 0.15
|
||||
Float WindSpeed : 0.5
|
||||
Texture2D BarkMap
|
||||
Boolean HasBarkMap : false
|
||||
}
|
||||
|
||||
Technique {
|
||||
VertexShader GLSL150 : Shaders/Tree.vert
|
||||
FragmentShader GLSL150 : Shaders/Tree.frag
|
||||
|
||||
WorldParameters {
|
||||
WorldViewProjectionMatrix
|
||||
WorldMatrix
|
||||
Time
|
||||
}
|
||||
}
|
||||
}
|
||||
83
ez-tree-jme/assets/MatDefs/TreeLeaf.j3md
Normal file
@@ -0,0 +1,83 @@
|
||||
MaterialDef TreeLeaf {
|
||||
|
||||
MaterialParameters {
|
||||
Color Diffuse (Color) : 0.18 0.60 0.10 1.0
|
||||
Float WindStrength : 0.30
|
||||
Float WindSpeed : 0.7
|
||||
Texture2D LeafMap
|
||||
Boolean HasLeafMap : false
|
||||
|
||||
// Vom Shadow-Renderer befüllt (PostShadow-Pass) — vollständige Liste aus PostShadow.j3md
|
||||
Int BoundDrawBuffer
|
||||
Int FilterMode
|
||||
Boolean HardwareShadows
|
||||
Texture2D ShadowMap0
|
||||
Texture2D ShadowMap1
|
||||
Texture2D ShadowMap2
|
||||
Texture2D ShadowMap3
|
||||
Texture2D ShadowMap4
|
||||
Texture2D ShadowMap5
|
||||
Float ShadowIntensity : 1.0
|
||||
Vector4 Splits
|
||||
Vector2 FadeInfo
|
||||
Matrix4 LightViewProjectionMatrix0
|
||||
Matrix4 LightViewProjectionMatrix1
|
||||
Matrix4 LightViewProjectionMatrix2
|
||||
Matrix4 LightViewProjectionMatrix3
|
||||
Matrix4 LightViewProjectionMatrix4
|
||||
Matrix4 LightViewProjectionMatrix5
|
||||
Vector3 LightPos
|
||||
Vector3 LightDir
|
||||
Float PCFEdge
|
||||
Float ShadowMapSize
|
||||
Boolean BackfaceShadows : false
|
||||
}
|
||||
|
||||
Technique {
|
||||
VertexShader GLSL150 : Shaders/Tree.vert
|
||||
FragmentShader GLSL150 : Shaders/TreeLeaf.frag
|
||||
|
||||
WorldParameters {
|
||||
WorldViewProjectionMatrix
|
||||
WorldMatrix
|
||||
Time
|
||||
}
|
||||
|
||||
RenderState {
|
||||
FaceCull Off
|
||||
}
|
||||
}
|
||||
|
||||
Technique PostShadow {
|
||||
VertexShader GLSL150 : Shaders/LeafPostShadow.vert
|
||||
FragmentShader GLSL150 : Shaders/LeafPostShadow.frag
|
||||
|
||||
WorldParameters {
|
||||
WorldViewProjectionMatrix
|
||||
WorldMatrix
|
||||
}
|
||||
|
||||
ForcedRenderState {
|
||||
Blend Modulate
|
||||
FaceCull Off
|
||||
DepthWrite Off
|
||||
}
|
||||
}
|
||||
|
||||
Technique PreShadow {
|
||||
VertexShader GLSL150 : Shaders/LeafPreShadow.vert
|
||||
FragmentShader GLSL150 : Shaders/LeafPreShadow.frag
|
||||
|
||||
WorldParameters {
|
||||
WorldViewProjectionMatrix
|
||||
}
|
||||
|
||||
ForcedRenderState {
|
||||
FaceCull Off
|
||||
DepthTest On
|
||||
DepthWrite On
|
||||
PolyOffset 5 3
|
||||
ColorWrite Off
|
||||
}
|
||||
}
|
||||
}
|
||||
20
ez-tree-jme/assets/Shaders/LeafPostShadow.frag
Normal file
@@ -0,0 +1,20 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform sampler2D m_ShadowMap0;
|
||||
uniform float m_ShadowIntensity;
|
||||
uniform sampler2D m_LeafMap;
|
||||
uniform bool m_HasLeafMap;
|
||||
|
||||
in vec4 shadowCoord;
|
||||
in vec2 texCoord;
|
||||
|
||||
void main() {
|
||||
// Transparente Blattbereiche empfangen keinen Schatten
|
||||
if (m_HasLeafMap && texture2D(m_LeafMap, texCoord).a < 0.5) discard;
|
||||
|
||||
vec3 coord = shadowCoord.xyz / shadowCoord.w;
|
||||
float mapDepth = texture2D(m_ShadowMap0, coord.xy).r;
|
||||
float lit = (coord.z > mapDepth + 0.001) ? (1.0 - m_ShadowIntensity) : 1.0;
|
||||
|
||||
gl_FragColor = vec4(lit, lit, lit, 1.0);
|
||||
}
|
||||
18
ez-tree-jme/assets/Shaders/LeafPostShadow.vert
Normal file
@@ -0,0 +1,18 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform mat4 g_WorldViewProjectionMatrix;
|
||||
uniform mat4 g_WorldMatrix;
|
||||
uniform mat4 m_LightViewProjectionMatrix0;
|
||||
|
||||
in vec3 inPosition;
|
||||
in vec2 inTexCoord;
|
||||
|
||||
out vec4 shadowCoord;
|
||||
out vec2 texCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
|
||||
vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
|
||||
shadowCoord = m_LightViewProjectionMatrix0 * worldPos;
|
||||
texCoord = inTexCoord;
|
||||
}
|
||||
15
ez-tree-jme/assets/Shaders/LeafPreShadow.frag
Normal file
@@ -0,0 +1,15 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform sampler2D m_LeafMap;
|
||||
uniform bool m_HasLeafMap;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main() {
|
||||
if (m_HasLeafMap) {
|
||||
vec4 tex = texture2D(m_LeafMap, texCoord);
|
||||
if (tex.a < 0.5) discard;
|
||||
}
|
||||
// Nur Tiefe schreiben — ColorWrite ist per ForcedRenderState deaktiviert
|
||||
gl_FragColor = vec4(1.0);
|
||||
}
|
||||
13
ez-tree-jme/assets/Shaders/LeafPreShadow.vert
Normal file
@@ -0,0 +1,13 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform mat4 g_WorldViewProjectionMatrix;
|
||||
|
||||
in vec3 inPosition;
|
||||
in vec2 inTexCoord;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
|
||||
texCoord = inTexCoord;
|
||||
}
|
||||
29
ez-tree-jme/assets/Shaders/Tree.frag
Normal file
@@ -0,0 +1,29 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform vec4 m_Diffuse;
|
||||
uniform sampler2D m_BarkMap;
|
||||
uniform bool m_HasBarkMap;
|
||||
|
||||
in vec2 texCoord;
|
||||
in vec3 worldNormal;
|
||||
|
||||
void main() {
|
||||
vec3 n = normalize(worldNormal);
|
||||
|
||||
// Sun at ~45° elevation from SE — good contrast on vertical cylinders
|
||||
vec3 sunDir = normalize(vec3(0.6, 0.7, 0.4));
|
||||
vec3 fillDir = normalize(vec3(-0.5, 0.3, -0.4));
|
||||
vec3 rimDir = normalize(vec3(-0.3, 0.5, 0.7));
|
||||
|
||||
float sun = max(dot(n, sunDir), 0.0);
|
||||
float fill = max(dot(n, fillDir), 0.0) * 0.22;
|
||||
float rim = max(dot(n, rimDir), 0.0) * 0.14;
|
||||
float sky = dot(n, vec3(0.0, 1.0, 0.0)) * 0.4 + 0.4; // [0.0, 0.8]
|
||||
float light = sun * 0.75 + fill + rim + sky * 0.09 + 0.05;
|
||||
|
||||
vec3 baseColor = m_HasBarkMap
|
||||
? texture2D(m_BarkMap, texCoord).rgb * m_Diffuse.rgb
|
||||
: m_Diffuse.rgb;
|
||||
|
||||
gl_FragColor = vec4(baseColor * clamp(light, 0.0, 1.0), m_Diffuse.a);
|
||||
}
|
||||
33
ez-tree-jme/assets/Shaders/Tree.vert
Normal file
@@ -0,0 +1,33 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform mat4 g_WorldViewProjectionMatrix;
|
||||
uniform mat4 g_WorldMatrix;
|
||||
uniform float g_Time;
|
||||
uniform float m_WindStrength;
|
||||
uniform float m_WindSpeed;
|
||||
|
||||
in vec3 inPosition;
|
||||
in vec3 inNormal;
|
||||
in vec2 inTexCoord;
|
||||
in vec4 inColor; // R = Wind-Gewicht (0 = Wurzel, 1 = Spitze)
|
||||
|
||||
out vec2 texCoord;
|
||||
out vec3 worldNormal;
|
||||
|
||||
void main() {
|
||||
float windW = inColor.r;
|
||||
float t = g_Time * m_WindSpeed;
|
||||
|
||||
// Welt-Position für orts-abhängige Phase (verhindert synchrones Schwingen)
|
||||
vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
|
||||
float phase = worldPos.x * 0.08 + worldPos.z * 0.06;
|
||||
|
||||
float swayX = sin(t + phase) * windW * m_WindStrength;
|
||||
float swayZ = cos(t * 0.73 + phase) * windW * m_WindStrength * 0.55;
|
||||
|
||||
vec3 animPos = inPosition + vec3(swayX, 0.0, swayZ);
|
||||
|
||||
gl_Position = g_WorldViewProjectionMatrix * vec4(animPos, 1.0);
|
||||
texCoord = inTexCoord;
|
||||
worldNormal = normalize((g_WorldMatrix * vec4(inNormal, 0.0)).xyz);
|
||||
}
|
||||
27
ez-tree-jme/assets/Shaders/TreeLeaf.frag
Normal file
@@ -0,0 +1,27 @@
|
||||
#import "Common/ShaderLib/GLSLCompat.glsllib"
|
||||
|
||||
uniform vec4 m_Diffuse;
|
||||
uniform sampler2D m_LeafMap;
|
||||
uniform bool m_HasLeafMap;
|
||||
|
||||
in vec2 texCoord;
|
||||
in vec3 worldNormal;
|
||||
|
||||
void main() {
|
||||
vec3 baseColor;
|
||||
|
||||
if (m_HasLeafMap) {
|
||||
vec4 tex = texture2D(m_LeafMap, texCoord);
|
||||
if (tex.a < 0.5) discard;
|
||||
baseColor = tex.rgb * m_Diffuse.rgb;
|
||||
} else {
|
||||
// Fallback: kreisförmiger Clip
|
||||
vec2 uv = texCoord * 2.0 - 1.0;
|
||||
if (dot(uv, uv) > 0.95) discard;
|
||||
float edge = 1.0 - dot(uv, uv);
|
||||
baseColor = m_Diffuse.rgb * (0.7 + 0.3 * edge);
|
||||
}
|
||||
|
||||
// Leaves transmit light — no directional shading, uniform brightness
|
||||
gl_FragColor = vec4(baseColor, 1.0);
|
||||
}
|
||||
BIN
ez-tree-jme/assets/Textures/bark/Bark001_Color.jpg
Normal file
|
After Width: | Height: | Size: 812 KiB |
BIN
ez-tree-jme/assets/Textures/bark/Bark002_Color.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
ez-tree-jme/assets/Textures/bark/Bark003_Color.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
ez-tree-jme/assets/Textures/bark/Bark008_Color.jpg
Normal file
|
After Width: | Height: | Size: 769 KiB |
BIN
ez-tree-jme/assets/Textures/leaves/ash.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
ez-tree-jme/assets/Textures/leaves/aspen.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
ez-tree-jme/assets/Textures/leaves/oak.png
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
ez-tree-jme/assets/Textures/leaves/palm.png
Normal file
|
After Width: | Height: | Size: 834 KiB |
BIN
ez-tree-jme/assets/Textures/leaves/pine.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
15
ez-tree-jme/build.gradle
Normal file
@@ -0,0 +1,15 @@
|
||||
ext {
|
||||
jmeVersion = '3.9.0-stable'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDirs = ['src/main/resources', 'assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/BarkOptions.class
Normal file
23
ez-tree-jme/src/main/java/de/blight/eztree/BarkOptions.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
public final class BarkOptions {
|
||||
|
||||
/** Optional asset path for a bark diffuse texture. */
|
||||
public String textureFile = null;
|
||||
public boolean flatShading = false;
|
||||
public float textureScaleX = 1f;
|
||||
public float textureScaleY = 1f;
|
||||
|
||||
/** Base bark color (used when no texture is assigned). */
|
||||
public float r = 0.45f, g = 0.30f, b = 0.18f;
|
||||
|
||||
public BarkOptions copy() {
|
||||
BarkOptions c = new BarkOptions();
|
||||
c.textureFile = textureFile;
|
||||
c.flatShading = flatShading;
|
||||
c.textureScaleX = textureScaleX;
|
||||
c.textureScaleY = textureScaleY;
|
||||
c.r = r; c.g = g; c.b = b;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/Billboard.class
Normal file
@@ -0,0 +1,5 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
public enum Billboard {
|
||||
NONE, CROSS, ROTATE_X
|
||||
}
|
||||
18
ez-tree-jme/src/main/java/de/blight/eztree/Branch.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
public final class Branch {
|
||||
|
||||
/** World-space start position. */
|
||||
public Vector3f origin = new Vector3f();
|
||||
|
||||
/** Rotation that maps Vector3f.UNIT_Y to the initial branch direction. */
|
||||
public Quaternion orientation = new Quaternion();
|
||||
|
||||
public float length;
|
||||
public float radius;
|
||||
public int level;
|
||||
public Branch parent;
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/BranchOptions.class
Normal file
@@ -0,0 +1,81 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* All branch parameters, stored as per-level maps (key = branch level).
|
||||
* When a level is not present the highest defined level at or below is used.
|
||||
*/
|
||||
public final class BranchOptions {
|
||||
|
||||
/** Maximum branch depth (0 = trunk only). */
|
||||
public int levels = 3;
|
||||
|
||||
/** Spread angle in degrees from parent direction for children at each level. */
|
||||
public Map<Integer, Float> angle = new HashMap<>();
|
||||
/** Number of child branches produced by a branch at each level. */
|
||||
public Map<Integer, Integer> children = new HashMap<>();
|
||||
/** Random direction perturbation magnitude (radians) per section step. */
|
||||
public Map<Integer, Float> gnarliness = new HashMap<>();
|
||||
/** Gravitational / wind force applied to branch growth direction. */
|
||||
public ForceOptions force = new ForceOptions();
|
||||
/** Length multiplier for children (relative to their parent). Level 0 = absolute trunk length. */
|
||||
public Map<Integer, Float> length = new HashMap<>();
|
||||
/** Radius multiplier for children (relative to parent section radius). Level 0 = absolute trunk radius. */
|
||||
public Map<Integer, Float> radius = new HashMap<>();
|
||||
/** Number of cylinder sections along a branch at each level. */
|
||||
public Map<Integer, Integer> sections = new HashMap<>();
|
||||
/** Number of polygon sides per cylinder cross-section at each level. */
|
||||
public Map<Integer, Integer> segments = new HashMap<>();
|
||||
/** Fraction along parent at which children of this level begin to appear. */
|
||||
public Map<Integer, Float> start = new HashMap<>();
|
||||
/** Tip-radius / base-radius ratio for each level (0=needle, 1=no taper). */
|
||||
public Map<Integer, Float> taper = new HashMap<>();
|
||||
/** Degrees per section by which the ring frame is rotated around the branch axis. */
|
||||
public Map<Integer, Float> twist = new HashMap<>();
|
||||
|
||||
// ── Accessors with fallback ──────────────────────────────────────────────
|
||||
|
||||
public float getAngle(int lv) { return floatAt(angle, lv, 45f); }
|
||||
public int getChildren(int lv) { return intAt (children, lv, 0); }
|
||||
public float getGnarliness(int lv){ return floatAt(gnarliness,lv, 0f); }
|
||||
public float getLength(int lv) { return floatAt(length, lv, 1f); }
|
||||
public float getRadius(int lv) { return floatAt(radius, lv, 0.5f);}
|
||||
public int getSections(int lv) { return intAt (sections, lv, 4); }
|
||||
public int getSegments(int lv) { return intAt (segments, lv, 6); }
|
||||
public float getStart(int lv) { return floatAt(start, lv, 0f); }
|
||||
public float getTaper(int lv) { return floatAt(taper, lv, 0.6f);}
|
||||
public float getTwist(int lv) { return floatAt(twist, lv, 0f); }
|
||||
|
||||
private float floatAt(Map<Integer, Float> m, int lv, float def) {
|
||||
if (m.containsKey(lv)) return m.get(lv);
|
||||
int best = -1;
|
||||
for (int k : m.keySet()) if (k <= lv && k > best) best = k;
|
||||
return best >= 0 ? m.get(best) : def;
|
||||
}
|
||||
|
||||
private int intAt(Map<Integer, Integer> m, int lv, int def) {
|
||||
if (m.containsKey(lv)) return m.get(lv);
|
||||
int best = -1;
|
||||
for (int k : m.keySet()) if (k <= lv && k > best) best = k;
|
||||
return best >= 0 ? m.get(best) : def;
|
||||
}
|
||||
|
||||
public BranchOptions copy() {
|
||||
BranchOptions c = new BranchOptions();
|
||||
c.levels = levels;
|
||||
c.angle = new HashMap<>(angle);
|
||||
c.children = new HashMap<>(children);
|
||||
c.gnarliness = new HashMap<>(gnarliness);
|
||||
c.force = force.copy();
|
||||
c.length = new HashMap<>(length);
|
||||
c.radius = new HashMap<>(radius);
|
||||
c.sections = new HashMap<>(sections);
|
||||
c.segments = new HashMap<>(segments);
|
||||
c.start = new HashMap<>(start);
|
||||
c.taper = new HashMap<>(taper);
|
||||
c.twist = new HashMap<>(twist);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
/** Immutable snapshot of one section along a branch. */
|
||||
record BranchSection(Vector3f pos, float radius, Vector3f dir, float t) {}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/ForceOptions.class
Normal file
20
ez-tree-jme/src/main/java/de/blight/eztree/ForceOptions.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
public final class ForceOptions {
|
||||
|
||||
public Vector3f direction = new Vector3f(0f, 0.5f, 0f);
|
||||
public float strength = 0.02f;
|
||||
|
||||
public ForceOptions() {}
|
||||
|
||||
public ForceOptions(float dx, float dy, float dz, float strength) {
|
||||
this.direction.set(dx, dy, dz);
|
||||
this.strength = strength;
|
||||
}
|
||||
|
||||
public ForceOptions copy() {
|
||||
return new ForceOptions(direction.x, direction.y, direction.z, strength);
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/LeavesOptions.class
Normal file
@@ -0,0 +1,30 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
public final class LeavesOptions {
|
||||
|
||||
/** Optional asset path for a leaf alpha texture. */
|
||||
public String textureFile = null;
|
||||
public Billboard billboard = Billboard.CROSS;
|
||||
public int count = 10;
|
||||
/** Fraction along the last-level branch at which leaf placement begins. */
|
||||
public float start = 0.4f;
|
||||
public float size = 0.5f;
|
||||
public float sizeVariance = 0.25f;
|
||||
public float alphaTest = 0.5f;
|
||||
|
||||
/** Base leaf color. */
|
||||
public float r = 0.13f, g = 0.53f, b = 0.17f;
|
||||
|
||||
public LeavesOptions copy() {
|
||||
LeavesOptions c = new LeavesOptions();
|
||||
c.textureFile = textureFile;
|
||||
c.billboard = billboard;
|
||||
c.count = count;
|
||||
c.start = start;
|
||||
c.size = size;
|
||||
c.sizeVariance = sizeVariance;
|
||||
c.alphaTest = alphaTest;
|
||||
c.r = r; c.g = g; c.b = b;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
30
ez-tree-jme/src/main/java/de/blight/eztree/Rng.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
/** Marsaglia's Multiply-With-Carry RNG — matches the ez-tree JS implementation. */
|
||||
public final class Rng {
|
||||
|
||||
private int mz = 987654321;
|
||||
private int mw;
|
||||
|
||||
public Rng(int seed) {
|
||||
this.mw = (seed == 0) ? 12345 : seed;
|
||||
}
|
||||
|
||||
/** Returns a value in [0, 1). */
|
||||
public float next() {
|
||||
mz = 36969 * (mz & 0xFFFF) + (mz >>> 16);
|
||||
mw = 18000 * (mw & 0xFFFF) + (mw >>> 16);
|
||||
long result = (((long) mz << 16) + (mw & 0xFFFFL)) & 0xFFFFFFFFL;
|
||||
return (float) (result / 4294967296.0);
|
||||
}
|
||||
|
||||
/** Returns a value in [min, max). */
|
||||
public float range(float min, float max) {
|
||||
return min + next() * (max - min);
|
||||
}
|
||||
|
||||
/** Returns an integer in [min, max). */
|
||||
public int integer(int min, int max) {
|
||||
return min + (int) (next() * (max - min));
|
||||
}
|
||||
}
|
||||
398
ez-tree-jme/src/main/java/de/blight/eztree/Tree.java
Normal file
@@ -0,0 +1,398 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* JME3 port of the ez-tree procedural tree generator.
|
||||
*
|
||||
* After construction call {@link #generate()} (or re-call it to regenerate).
|
||||
* The resulting scene graph has two child {@link Geometry} nodes:
|
||||
* <ul>
|
||||
* <li>{@code "bark"} — cylinder mesh with VertexBuffer.Color.R = wind factor</li>
|
||||
* <li>{@code "leaves"} — quad mesh with VertexBuffer.Color.R = wind factor</li>
|
||||
* </ul>
|
||||
* Materials must be assigned by the caller.
|
||||
*/
|
||||
public class Tree extends Node {
|
||||
|
||||
private final TreeOptions opts;
|
||||
|
||||
// Accumulated geometry data, reset on each generate() call
|
||||
private final List<Float> barkPos = new ArrayList<>();
|
||||
private final List<Float> barkNorm = new ArrayList<>();
|
||||
private final List<Float> barkUV = new ArrayList<>();
|
||||
private final List<Integer> barkIdx = new ArrayList<>();
|
||||
private final List<Float> barkWind = new ArrayList<>();
|
||||
|
||||
private final List<Float> leafPos = new ArrayList<>();
|
||||
private final List<Float> leafNorm = new ArrayList<>();
|
||||
private final List<Float> leafUV = new ArrayList<>();
|
||||
private final List<Integer> leafIdx = new ArrayList<>();
|
||||
private final List<Float> leafWind = new ArrayList<>();
|
||||
|
||||
public Tree(TreeOptions opts) {
|
||||
super("EzTree");
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
public TreeOptions getOptions() { return opts; }
|
||||
|
||||
// ── Public API ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Clears previous geometry and rebuilds the tree mesh. */
|
||||
public void generate() {
|
||||
detachAllChildren();
|
||||
barkPos.clear(); barkNorm.clear(); barkUV.clear(); barkIdx.clear(); barkWind.clear();
|
||||
leafPos.clear(); leafNorm.clear(); leafUV.clear(); leafIdx.clear(); leafWind.clear();
|
||||
|
||||
Rng rng = new Rng(opts.seed);
|
||||
|
||||
Branch trunk = new Branch();
|
||||
trunk.level = 0;
|
||||
trunk.length = opts.branch.getLength(0);
|
||||
trunk.radius = opts.branch.getRadius(0);
|
||||
|
||||
Queue<Branch> queue = new ArrayDeque<>();
|
||||
queue.add(trunk);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
processBranch(queue.poll(), rng, queue);
|
||||
}
|
||||
|
||||
if (!barkPos.isEmpty()) {
|
||||
attachChild(buildGeometry("bark", barkPos, barkNorm, barkUV, barkIdx, barkWind));
|
||||
}
|
||||
if (!leafPos.isEmpty()) {
|
||||
attachChild(buildGeometry("leaves", leafPos, leafNorm, leafUV, leafIdx, leafWind));
|
||||
}
|
||||
|
||||
if (opts.trellis.enabled) {
|
||||
Node trellisNode = Trellis.build(opts.trellis);
|
||||
attachChild(trellisNode);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Branch processing ────────────────────────────────────────────────────
|
||||
|
||||
private void processBranch(Branch branch, Rng rng, Queue<Branch> queue) {
|
||||
BranchOptions bo = opts.branch;
|
||||
int level = branch.level;
|
||||
int sections = bo.getSections(level);
|
||||
int segments = bo.getSegments(level);
|
||||
float gnarliness = bo.getGnarliness(level);
|
||||
float taper = bo.getTaper(level);
|
||||
float twistStep = bo.getTwist(level) * FastMath.DEG_TO_RAD;
|
||||
float sectionLen = branch.length / sections;
|
||||
|
||||
float startRad = branch.radius;
|
||||
float endRad = startRad * taper;
|
||||
|
||||
// Initial direction = orientation applied to Y+
|
||||
Vector3f dir = branch.orientation.mult(Vector3f.UNIT_Y).normalizeLocal();
|
||||
Vector3f pos = branch.origin.clone();
|
||||
|
||||
// Parallel-transport frame for smooth cylinder rings
|
||||
Vector3f frameRight = findPerp(dir);
|
||||
Vector3f frameUp = dir.cross(frameRight).normalizeLocal();
|
||||
|
||||
List<BranchSection> pts = new ArrayList<>(sections + 1);
|
||||
pts.add(new BranchSection(pos.clone(), startRad, dir.clone(), 0f));
|
||||
|
||||
for (int i = 0; i < sections; i++) {
|
||||
float t = (i + 1f) / sections;
|
||||
|
||||
// Gnarliness: random XZ perturbation of direction
|
||||
if (gnarliness > 0f) {
|
||||
dir.x += rng.range(-gnarliness, gnarliness);
|
||||
dir.z += rng.range(-gnarliness, gnarliness);
|
||||
dir.normalizeLocal();
|
||||
}
|
||||
|
||||
// External force (gravity / wind bias)
|
||||
Vector3f fd = bo.force.direction;
|
||||
float fs = bo.force.strength;
|
||||
if (fs != 0f) {
|
||||
dir.x += fd.x * fs;
|
||||
dir.y += fd.y * fs;
|
||||
dir.z += fd.z * fs;
|
||||
dir.normalizeLocal();
|
||||
}
|
||||
|
||||
// Twist: spin the transport frame around the branch axis
|
||||
if (twistStep != 0f) {
|
||||
Quaternion twistQ = new Quaternion().fromAngleAxis(twistStep, dir);
|
||||
frameRight = twistQ.mult(frameRight).normalizeLocal();
|
||||
frameUp = dir.cross(frameRight).normalizeLocal();
|
||||
}
|
||||
|
||||
pos.addLocal(dir.mult(sectionLen));
|
||||
float radius = startRad + (endRad - startRad) * t;
|
||||
pts.add(new BranchSection(pos.clone(), radius, dir.clone(), t));
|
||||
}
|
||||
|
||||
addCylinderGeometry(pts, segments, taper);
|
||||
|
||||
if (level < bo.levels) {
|
||||
generateChildren(branch, pts, rng, queue);
|
||||
}
|
||||
|
||||
// Leaves on the last (finest) level branches
|
||||
if (level == bo.levels) {
|
||||
generateLeaves(pts, rng);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Cylinder mesh contribution ───────────────────────────────────────────
|
||||
|
||||
private void addCylinderGeometry(List<BranchSection> pts, int segments, float taper) {
|
||||
int vertsPerRing = segments + 1;
|
||||
int baseVertex = barkPos.size() / 3;
|
||||
|
||||
// Parallel-transport frame
|
||||
Vector3f frameRight = findPerp(pts.get(0).dir());
|
||||
Vector3f frameUp = pts.get(0).dir().cross(frameRight).normalizeLocal();
|
||||
|
||||
for (BranchSection sp : pts) {
|
||||
Vector3f d = sp.dir().normalize();
|
||||
|
||||
// Transport the frame to the new direction
|
||||
float dot = frameRight.dot(d);
|
||||
frameRight = frameRight.subtract(d.mult(dot));
|
||||
if (frameRight.lengthSquared() < 1e-6f) {
|
||||
frameRight = findPerp(d);
|
||||
} else {
|
||||
frameRight.normalizeLocal();
|
||||
}
|
||||
frameUp = d.cross(frameRight).normalizeLocal();
|
||||
|
||||
float uv_v = (1f - sp.t()) * opts.bark.textureScaleY;
|
||||
|
||||
for (int seg = 0; seg <= segments; seg++) {
|
||||
float angle = (float) seg / segments * FastMath.TWO_PI;
|
||||
float cosA = FastMath.cos(angle);
|
||||
float sinA = FastMath.sin(angle);
|
||||
|
||||
// Vertex = center + radial offset along (right, up)
|
||||
float ox = frameRight.x * cosA + frameUp.x * sinA;
|
||||
float oy = frameRight.y * cosA + frameUp.y * sinA;
|
||||
float oz = frameRight.z * cosA + frameUp.z * sinA;
|
||||
float r = sp.radius();
|
||||
|
||||
barkPos.add(sp.pos().x + ox * r);
|
||||
barkPos.add(sp.pos().y + oy * r);
|
||||
barkPos.add(sp.pos().z + oz * r);
|
||||
|
||||
// Normal = outward radial direction
|
||||
float nlen = FastMath.sqrt(ox * ox + oy * oy + oz * oz);
|
||||
barkNorm.add(ox / nlen);
|
||||
barkNorm.add(oy / nlen);
|
||||
barkNorm.add(oz / nlen);
|
||||
|
||||
barkUV.add((float) seg / segments * opts.bark.textureScaleX);
|
||||
barkUV.add(uv_v);
|
||||
barkWind.add(sp.t());
|
||||
}
|
||||
}
|
||||
|
||||
// Connect adjacent rings with quads
|
||||
int numRings = pts.size();
|
||||
for (int si = 0; si < numRings - 1; si++) {
|
||||
int r0 = baseVertex + si * vertsPerRing;
|
||||
int r1 = baseVertex + (si + 1) * vertsPerRing;
|
||||
for (int seg = 0; seg < segments; seg++) {
|
||||
int a = r0 + seg, b = r0 + seg + 1;
|
||||
int c = r1 + seg, d = r1 + seg + 1;
|
||||
barkIdx.add(a); barkIdx.add(b); barkIdx.add(c);
|
||||
barkIdx.add(b); barkIdx.add(d); barkIdx.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Child branch placement ───────────────────────────────────────────────
|
||||
|
||||
private void generateChildren(Branch parent, List<BranchSection> pts,
|
||||
Rng rng, Queue<Branch> queue) {
|
||||
BranchOptions bo = opts.branch;
|
||||
int level = parent.level;
|
||||
int childLevel = level + 1;
|
||||
int childCount = bo.getChildren(level);
|
||||
float childStart = bo.getStart(childLevel);
|
||||
float childLenFactor = bo.getLength(childLevel);
|
||||
float childRadFactor = bo.getRadius(childLevel);
|
||||
float childAngle = bo.getAngle(childLevel) * FastMath.DEG_TO_RAD;
|
||||
float childTwistBase = bo.getTwist(childLevel) * FastMath.DEG_TO_RAD;
|
||||
int sections = pts.size() - 1;
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
// Stratified placement along parent with small jitter
|
||||
float t = childStart + ((float) i / childCount) * (1f - childStart);
|
||||
t = FastMath.clamp(t + rng.range(-0.02f, 0.02f), childStart, 1f);
|
||||
|
||||
int sectionIdx = Math.min(Math.max(0, (int) (t * sections)), sections);
|
||||
BranchSection sp = pts.get(sectionIdx);
|
||||
|
||||
// Azimuth: evenly distribute around parent axis, with optional twist accumulation
|
||||
float phi = ((float) i / childCount) * FastMath.TWO_PI
|
||||
+ childTwistBase * i
|
||||
+ rng.range(-0.15f, 0.15f);
|
||||
|
||||
Vector3f parentDir = sp.dir().normalize();
|
||||
Vector3f perp = findPerp(parentDir);
|
||||
|
||||
// Rotate perp around parentDir by phi to obtain the swing axis
|
||||
Quaternion phiQ = new Quaternion().fromAngleAxis(phi, parentDir);
|
||||
Vector3f swingAxis = phiQ.mult(perp).normalizeLocal();
|
||||
|
||||
// Rotate parentDir by childAngle around swingAxis
|
||||
Quaternion thetaQ = new Quaternion().fromAngleAxis(childAngle, swingAxis);
|
||||
Vector3f childDir = thetaQ.mult(parentDir).normalizeLocal();
|
||||
|
||||
Branch child = new Branch();
|
||||
child.origin = sp.pos().clone();
|
||||
child.orientation = dirToQuat(childDir);
|
||||
child.level = childLevel;
|
||||
child.parent = parent;
|
||||
child.length = parent.length * childLenFactor;
|
||||
child.radius = sp.radius() * childRadFactor;
|
||||
|
||||
queue.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Leaf placement ───────────────────────────────────────────────────────
|
||||
|
||||
private void generateLeaves(List<BranchSection> pts, Rng rng) {
|
||||
LeavesOptions lo = opts.leaves;
|
||||
int count = lo.count;
|
||||
int nSections = pts.size() - 1;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
float t = lo.start + rng.next() * (1f - lo.start);
|
||||
int si = Math.min(Math.max(0, (int) (t * nSections)), nSections);
|
||||
BranchSection sp = pts.get(si);
|
||||
|
||||
float size = lo.size * (1f + rng.range(-lo.sizeVariance, lo.sizeVariance));
|
||||
float yaw = rng.range(0f, FastMath.TWO_PI);
|
||||
float pitch = rng.range(-FastMath.QUARTER_PI * 0.4f, FastMath.QUARTER_PI * 0.4f);
|
||||
float wind = sp.t();
|
||||
|
||||
switch (lo.billboard) {
|
||||
case CROSS -> {
|
||||
addLeafQuad(sp.pos(), yaw, pitch, size, wind);
|
||||
addLeafQuad(sp.pos(), yaw + FastMath.HALF_PI, pitch, size, wind);
|
||||
}
|
||||
case ROTATE_X -> addLeafQuad(sp.pos(), yaw, FastMath.HALF_PI, size, wind);
|
||||
default -> addLeafQuad(sp.pos(), yaw, pitch, size, wind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addLeafQuad(Vector3f center, float yaw, float pitch, float size, float wind) {
|
||||
float h = size * 0.5f;
|
||||
float cy = FastMath.cos(yaw), sy = FastMath.sin(yaw);
|
||||
float cp = FastMath.cos(pitch), sp = FastMath.sin(pitch);
|
||||
|
||||
// Local frame: right = rotate X by yaw, up = rotated Y by yaw+pitch
|
||||
Vector3f right = new Vector3f(cy, 0f, -sy);
|
||||
Vector3f up = new Vector3f(sy * sp, cp, cy * sp);
|
||||
Vector3f norm = right.cross(up).normalizeLocal();
|
||||
|
||||
// 4 corners: BL, BR, TR, TL
|
||||
float[] cornerX = { center.x - right.x*h - up.x*h,
|
||||
center.x + right.x*h - up.x*h,
|
||||
center.x + right.x*h + up.x*h,
|
||||
center.x - right.x*h + up.x*h };
|
||||
float[] cornerY = { center.y - right.y*h - up.y*h,
|
||||
center.y + right.y*h - up.y*h,
|
||||
center.y + right.y*h + up.y*h,
|
||||
center.y - right.y*h + up.y*h };
|
||||
float[] cornerZ = { center.z - right.z*h - up.z*h,
|
||||
center.z + right.z*h - up.z*h,
|
||||
center.z + right.z*h + up.z*h,
|
||||
center.z - right.z*h + up.z*h };
|
||||
|
||||
int base = leafPos.size() / 3;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
leafPos.add(cornerX[i]); leafPos.add(cornerY[i]); leafPos.add(cornerZ[i]);
|
||||
leafNorm.add(norm.x); leafNorm.add(norm.y); leafNorm.add(norm.z);
|
||||
leafWind.add(wind);
|
||||
}
|
||||
leafUV.add(0f); leafUV.add(0f);
|
||||
leafUV.add(1f); leafUV.add(0f);
|
||||
leafUV.add(1f); leafUV.add(1f);
|
||||
leafUV.add(0f); leafUV.add(1f);
|
||||
|
||||
leafIdx.add(base); leafIdx.add(base+1); leafIdx.add(base+2);
|
||||
leafIdx.add(base); leafIdx.add(base+2); leafIdx.add(base+3);
|
||||
}
|
||||
|
||||
// ── Mesh assembly ────────────────────────────────────────────────────────
|
||||
|
||||
private static Geometry buildGeometry(String name,
|
||||
List<Float> pos, List<Float> norm,
|
||||
List<Float> uv, List<Integer> idx,
|
||||
List<Float> wind) {
|
||||
int nVerts = pos.size() / 3;
|
||||
|
||||
FloatBuffer pb = BufferUtils.createFloatBuffer(nVerts * 3);
|
||||
FloatBuffer nb = BufferUtils.createFloatBuffer(nVerts * 3);
|
||||
FloatBuffer ub = BufferUtils.createFloatBuffer(nVerts * 2);
|
||||
FloatBuffer cb = BufferUtils.createFloatBuffer(nVerts * 4); // RGBA — R = wind
|
||||
IntBuffer ib = BufferUtils.createIntBuffer(idx.size());
|
||||
|
||||
for (float f : pos) pb.put(f);
|
||||
for (float f : norm) nb.put(f);
|
||||
for (float f : uv) ub.put(f);
|
||||
for (float f : wind) { cb.put(f); cb.put(1f); cb.put(1f); cb.put(1f); }
|
||||
for (int i : idx) ib.put(i);
|
||||
|
||||
pb.flip(); nb.flip(); ub.flip(); cb.flip(); ib.flip();
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(Type.Position, 3, pb);
|
||||
mesh.setBuffer(Type.Normal, 3, nb);
|
||||
mesh.setBuffer(Type.TexCoord, 2, ub);
|
||||
mesh.setBuffer(Type.Color, 4, cb);
|
||||
mesh.setBuffer(Type.Index, 3, ib);
|
||||
mesh.updateBound();
|
||||
|
||||
return new Geometry(name, mesh);
|
||||
}
|
||||
|
||||
// ── Math helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Returns a unit vector perpendicular to v. */
|
||||
private static Vector3f findPerp(Vector3f v) {
|
||||
Vector3f p = v.cross(Vector3f.UNIT_X);
|
||||
if (p.lengthSquared() < 0.01f) p = v.cross(Vector3f.UNIT_Z);
|
||||
return p.normalizeLocal();
|
||||
}
|
||||
|
||||
/** Creates a quaternion that rotates UNIT_Y onto dir. */
|
||||
private static Quaternion dirToQuat(Vector3f dir) {
|
||||
Vector3f d = dir.normalize();
|
||||
Vector3f axis = Vector3f.UNIT_Y.cross(d);
|
||||
float len = axis.length();
|
||||
if (len < 1e-6f) {
|
||||
if (Vector3f.UNIT_Y.dot(d) > 0f) return new Quaternion();
|
||||
return new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_X);
|
||||
}
|
||||
axis.divideLocal(len);
|
||||
float angle = FastMath.acos(FastMath.clamp(Vector3f.UNIT_Y.dot(d), -1f, 1f));
|
||||
return new Quaternion().fromAngleAxis(angle, axis);
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/TreeOptions.class
Normal file
22
ez-tree-jme/src/main/java/de/blight/eztree/TreeOptions.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
public final class TreeOptions {
|
||||
|
||||
public int seed = 0;
|
||||
public TreeType type = TreeType.DECIDUOUS;
|
||||
public BarkOptions bark = new BarkOptions();
|
||||
public BranchOptions branch = new BranchOptions();
|
||||
public LeavesOptions leaves = new LeavesOptions();
|
||||
public TrellisOptions trellis = new TrellisOptions();
|
||||
|
||||
public TreeOptions copy() {
|
||||
TreeOptions c = new TreeOptions();
|
||||
c.seed = seed;
|
||||
c.type = type;
|
||||
c.bark = bark.copy();
|
||||
c.branch = branch.copy();
|
||||
c.leaves = leaves.copy();
|
||||
c.trellis = trellis.copy();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/TreePresets.class
Normal file
537
ez-tree-jme/src/main/java/de/blight/eztree/TreePresets.java
Normal file
@@ -0,0 +1,537 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
/**
|
||||
* Presets from https://github.com/dgreenheck/ez-tree/tree/main/src/lib/presets
|
||||
*
|
||||
* Scaling applied vs. raw JSON values to match JME3 meter units:
|
||||
* trunk length[0] ÷ 4
|
||||
* trunk radius[0] ÷ 4
|
||||
* leaf size ÷ 5
|
||||
* Relative factors (length/radius level 1+) are JSON[n]/JSON[n-1] — unchanged.
|
||||
* Twist: JSON radians × 57.2958 → degrees.
|
||||
* Pine children capped at 20-22 (JS uses 91-100 for needle density, our generator
|
||||
* creates full branch geometry per child).
|
||||
*/
|
||||
public final class TreePresets {
|
||||
|
||||
private TreePresets() {}
|
||||
|
||||
private static final String BARK1 = "Textures/bark/Bark001_Color.jpg";
|
||||
private static final String BARK2 = "Textures/bark/Bark002_Color.jpg";
|
||||
private static final String BARK3 = "Textures/bark/Bark003_Color.jpg";
|
||||
|
||||
private static final String LEAF_OAK = "Textures/leaves/oak.png";
|
||||
private static final String LEAF_ASH = "Textures/leaves/ash.png";
|
||||
private static final String LEAF_ASPEN = "Textures/leaves/aspen.png";
|
||||
private static final String LEAF_PINE = "Textures/leaves/pine.png";
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// OAK
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions oakSmall() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 30895;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 1.000f; o.bark.g = 0.953f; o.bark.b = 0.820f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 10f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 54f); b.angle.put(2, 58f); b.angle.put(3, 32f);
|
||||
b.children.put(0, 4); b.children.put(1, 2); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.01f;
|
||||
b.gnarliness.put(0, 0.07f); b.gnarliness.put(1, 0.08f);
|
||||
b.gnarliness.put(2, 0.11f); b.gnarliness.put(3, 0.09f);
|
||||
b.length.put(0, 7.02f); b.length.put(1, 0.162f); b.length.put(2, 2.149f); b.length.put(3, 0.732f);
|
||||
b.radius.put(0, 0.25f); b.radius.put(1, 0.60f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 16); b.sections.put(1, 9); b.sections.put(2, 8);
|
||||
b.segments.put(0, 7); b.segments.put(1, 5); b.segments.put(2, 3); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.49f); b.start.put(2, 0.06f); b.start.put(3, 0.12f);
|
||||
b.taper.put(0, 0.73f); b.taper.put(1, 0.42f); b.taper.put(2, 0.69f); b.taper.put(3, 0.75f);
|
||||
b.twist.put(0, -13.18f); b.twist.put(1, 24.06f);
|
||||
|
||||
o.leaves = leaves(LEAF_OAK, 0.835f, 0.835f, 0.804f, Billboard.CROSS, 14, 0.16f, 0.28f, 0.70f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions oakMedium() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 35729;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 1.000f; o.bark.g = 0.953f; o.bark.b = 0.820f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 10f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 54f); b.angle.put(2, 58f); b.angle.put(3, 32f);
|
||||
b.children.put(0, 6); b.children.put(1, 4); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.02f;
|
||||
b.gnarliness.put(0, 0.00f); b.gnarliness.put(1, 0.10f);
|
||||
b.gnarliness.put(2, 0.15f); b.gnarliness.put(3, 0.09f);
|
||||
b.length.put(0, 9.31f); b.length.put(1, 0.298f); b.length.put(2, 1.118f); b.length.put(3, 0.578f);
|
||||
b.radius.put(0, 0.35f); b.radius.put(1, 0.60f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 8); b.sections.put(1, 6); b.sections.put(2, 4);
|
||||
b.segments.put(0, 7); b.segments.put(1, 5); b.segments.put(2, 3); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.49f); b.start.put(2, 0.06f); b.start.put(3, 0.12f);
|
||||
b.taper.put(0, 0.73f); b.taper.put(1, 0.42f); b.taper.put(2, 0.69f); b.taper.put(3, 0.75f);
|
||||
b.twist.put(0, -13.18f); b.twist.put(1, 24.06f);
|
||||
|
||||
o.leaves = leaves(LEAF_OAK, 0.835f, 0.835f, 0.804f, Billboard.CROSS, 18, 0.16f, 0.50f, 0.70f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions oakLarge() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 23399;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 1.000f; o.bark.g = 0.953f; o.bark.b = 0.820f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 10f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 54f); b.angle.put(2, 43f); b.angle.put(3, 32f);
|
||||
b.children.put(0, 9); b.children.put(1, 5); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.02f;
|
||||
b.gnarliness.put(0, 0.04f); b.gnarliness.put(1, 0.16f);
|
||||
b.gnarliness.put(2, 0.06f); b.gnarliness.put(3, 0.09f);
|
||||
b.length.put(0, 11.93f); b.length.put(1, 0.616f); b.length.put(2, 0.600f); b.length.put(3, 0.406f);
|
||||
b.radius.put(0, 0.75f); b.radius.put(1, 0.58f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 16); b.sections.put(1, 9); b.sections.put(2, 8); b.sections.put(3, 3);
|
||||
b.segments.put(0, 12); b.segments.put(1, 5); b.segments.put(2, 3); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.35f); b.start.put(2, 0.10f); b.start.put(3, 0.00f);
|
||||
b.taper.put(0, 0.73f); b.taper.put(1, 0.42f); b.taper.put(2, 0.69f); b.taper.put(3, 0.75f);
|
||||
b.twist.put(0, -13.18f); b.twist.put(1, 24.06f);
|
||||
|
||||
o.leaves = leaves(LEAF_OAK, 0.835f, 0.835f, 0.804f, Billboard.CROSS, 10, 0.16f, 0.90f, 0.70f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// ASH
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions ashSmall() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 26867;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 0.808f; o.bark.g = 0.800f; o.bark.b = 0.745f;
|
||||
o.bark.textureScaleX = 0.5f; o.bark.textureScaleY = 5f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 2;
|
||||
b.angle.put(1, 48f); b.angle.put(2, 75f); b.angle.put(3, 60f);
|
||||
b.children.put(0, 10); b.children.put(1, 3); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.01f;
|
||||
b.gnarliness.put(0, 0.11f); b.gnarliness.put(1, 0.09f);
|
||||
b.gnarliness.put(2, 0.05f); b.gnarliness.put(3, 0.09f);
|
||||
b.length.put(0, 5.97f); b.length.put(1, 0.754f); b.length.put(2, 0.311f); b.length.put(3, 0.823f);
|
||||
b.radius.put(0, 0.20f); b.radius.put(1, 0.62f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 10); b.sections.put(3, 10);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.53f); b.start.put(2, 0.33f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
b.twist.put(0, 17.19f); b.twist.put(1, -4.01f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASH, 1.0f, 1.0f, 1.0f, Billboard.CROSS, 30, 0.00f, 0.41f, 0.717f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions ashMedium() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 36330;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 0.808f; o.bark.g = 0.800f; o.bark.b = 0.745f;
|
||||
o.bark.textureScaleX = 0.5f; o.bark.textureScaleY = 5f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 48f); b.angle.put(2, 75f); b.angle.put(3, 60f);
|
||||
b.children.put(0, 7); b.children.put(1, 4); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.01f;
|
||||
b.gnarliness.put(0, 0.03f); b.gnarliness.put(1, 0.25f);
|
||||
b.gnarliness.put(2, 0.20f); b.gnarliness.put(3, 0.09f);
|
||||
b.length.put(0, 10.87f); b.length.put(1, 0.624f); b.length.put(2, 0.350f); b.length.put(3, 0.484f);
|
||||
b.radius.put(0, 0.50f); b.radius.put(1, 0.60f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 8); b.sections.put(2, 6); b.sections.put(3, 4);
|
||||
b.segments.put(0, 12); b.segments.put(1, 6); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.23f); b.start.put(2, 0.33f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
b.twist.put(0, 5.16f); b.twist.put(1, -4.01f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASH, 1.0f, 1.0f, 1.0f, Billboard.CROSS, 16, 0.00f, 0.53f, 0.720f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions ashLarge() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 29919;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 0.808f; o.bark.g = 0.800f; o.bark.b = 0.745f;
|
||||
o.bark.textureScaleX = 0.5f; o.bark.textureScaleY = 5f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 39f); b.angle.put(2, 39f); b.angle.put(3, 51f);
|
||||
b.children.put(0, 10); b.children.put(1, 4); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.01f;
|
||||
b.gnarliness.put(0, 0.05f); b.gnarliness.put(1, 0.20f);
|
||||
b.gnarliness.put(2, 0.16f); b.gnarliness.put(3, 0.05f);
|
||||
b.length.put(0, 11.25f); b.length.put(1, 0.654f); b.length.put(2, 0.520f); b.length.put(3, 0.301f);
|
||||
b.radius.put(0, 0.76f); b.radius.put(1, 0.58f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 8); b.sections.put(2, 6); b.sections.put(3, 4);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.32f); b.start.put(2, 0.34f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.62f); b.taper.put(2, 0.76f); b.taper.put(3, 0.00f);
|
||||
b.twist.put(0, 5.16f); b.twist.put(1, -4.01f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASH, 1.0f, 1.0f, 1.0f, Billboard.CROSS, 10, 0.01f, 0.92f, 0.720f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// ASPEN
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions aspenSmall() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 36330;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK2;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 1f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 2;
|
||||
b.angle.put(1, 70f); b.angle.put(2, 35f); b.angle.put(3, 7f);
|
||||
b.children.put(0, 4); b.children.put(1, 3); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.011f;
|
||||
b.gnarliness.put(0, 0.04f); b.gnarliness.put(1, 0.01f);
|
||||
b.gnarliness.put(2, 0.12f); b.gnarliness.put(3, 0.02f);
|
||||
b.length.put(0, 6.00f); b.length.put(1, 0.140f); b.length.put(2, 2.292f); b.length.put(3, 0.130f);
|
||||
b.radius.put(0, 0.093f); b.radius.put(1, 0.55f); b.radius.put(2, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 8); b.sections.put(3, 6);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.45f); b.start.put(2, 0.33f);
|
||||
b.taper.put(0, 0.37f); b.taper.put(1, 0.13f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASPEN, 1.000f, 0.980f, 0.384f, Billboard.CROSS, 13, 0.20f, 0.50f, 0.70f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions aspenMedium() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 18020;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK2;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 1f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 2;
|
||||
b.angle.put(1, 75f); b.angle.put(2, 32f); b.angle.put(3, 7f);
|
||||
b.children.put(0, 10); b.children.put(1, 3); b.children.put(2, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.015f;
|
||||
b.gnarliness.put(0, 0.05f); b.gnarliness.put(1, 0.12f);
|
||||
b.gnarliness.put(2, 0.12f); b.gnarliness.put(3, 0.02f);
|
||||
b.length.put(0, 12.50f); b.length.put(1, 0.121f); b.length.put(2, 1.843f); b.length.put(3, 0.089f);
|
||||
b.radius.put(0, 0.18f); b.radius.put(1, 0.55f); b.radius.put(2, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 8); b.sections.put(3, 6);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.59f); b.start.put(2, 0.35f);
|
||||
b.taper.put(0, 0.37f); b.taper.put(1, 0.13f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASPEN, 1.000f, 0.980f, 0.384f, Billboard.CROSS, 11, 0.124f, 0.50f, 0.70f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions aspenLarge() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 30631;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK2;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 1f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 2;
|
||||
b.angle.put(1, 47f); b.angle.put(2, 63f); b.angle.put(3, 7f);
|
||||
b.children.put(0, 10); b.children.put(1, 6); b.children.put(2, 0);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.022f;
|
||||
b.gnarliness.put(0, 0.05f); b.gnarliness.put(1, 0.03f);
|
||||
b.gnarliness.put(2, 0.12f); b.gnarliness.put(3, 0.02f);
|
||||
b.length.put(0, 17.40f); b.length.put(1, 0.267f); b.length.put(2, 0.603f); b.length.put(3, 0.089f);
|
||||
b.radius.put(0, 0.28f); b.radius.put(1, 0.55f); b.radius.put(2, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 8); b.sections.put(3, 6);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.62f); b.start.put(2, 0.05f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.13f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASPEN, 0.988f, 1.000f, 0.149f, Billboard.CROSS, 20, 0.152f, 0.70f, 0.70f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// PINE
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions pineSmall() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 11744;
|
||||
o.type = TreeType.EVERGREEN;
|
||||
o.bark.textureFile = BARK3;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 1f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 1;
|
||||
b.angle.put(1, 117f); b.angle.put(2, 60f);
|
||||
b.children.put(0, 20); b.children.put(1, 7);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0f;
|
||||
b.gnarliness.put(0, 0.05f); b.gnarliness.put(1, 0.08f);
|
||||
b.length.put(0, 9.89f); b.length.put(1, 0.307f); b.length.put(2, 0.825f);
|
||||
b.radius.put(0, 0.14f); b.radius.put(1, 0.55f); b.radius.put(2, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 8);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4);
|
||||
b.start.put(1, 0.16f); b.start.put(2, 0.30f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f);
|
||||
|
||||
o.leaves = leaves(LEAF_PINE, 1.0f, 1.0f, 1.0f, Billboard.CROSS, 21, 0.00f, 0.19f, 0.70f, 0.3f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions pineMedium() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 13977;
|
||||
o.type = TreeType.EVERGREEN;
|
||||
o.bark.textureFile = BARK3;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 1f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 1;
|
||||
b.angle.put(1, 110f); b.angle.put(2, 16f);
|
||||
b.children.put(0, 18); b.children.put(1, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = -0.003f;
|
||||
b.gnarliness.put(0, 0.05f); b.gnarliness.put(1, 0.08f);
|
||||
b.length.put(0, 12.50f); b.length.put(1, 0.477f); b.length.put(2, 0.590f);
|
||||
b.radius.put(0, 0.26f); b.radius.put(1, 0.55f); b.radius.put(2, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 8);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4);
|
||||
b.start.put(1, 0.27f); b.start.put(2, 0.14f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f);
|
||||
|
||||
o.leaves = leaves(LEAF_PINE, 1.0f, 1.0f, 1.0f, Billboard.CROSS, 30, 0.09f, 0.29f, 0.201f, 0.3f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions pineLarge() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 44166;
|
||||
o.type = TreeType.EVERGREEN;
|
||||
o.bark.textureFile = BARK3;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 1f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 1;
|
||||
b.angle.put(1, 129f); b.angle.put(2, 16f);
|
||||
b.children.put(0, 22); b.children.put(1, 3);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.009f;
|
||||
b.gnarliness.put(0, 0.05f); b.gnarliness.put(1, 0.08f);
|
||||
b.length.put(0, 16.31f); b.length.put(1, 0.534f); b.length.put(2, 0.782f);
|
||||
b.radius.put(0, 0.32f); b.radius.put(1, 0.55f); b.radius.put(2, 0.50f);
|
||||
b.sections.put(0, 12); b.sections.put(1, 10); b.sections.put(2, 8);
|
||||
b.segments.put(0, 8); b.segments.put(1, 6); b.segments.put(2, 4);
|
||||
b.start.put(1, 0.294f); b.start.put(2, 0.14f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f);
|
||||
|
||||
o.leaves = leaves(LEAF_PINE, 1.0f, 1.0f, 1.0f, Billboard.CROSS, 18, 0.076f, 0.52f, 0.201f, 0.3f);
|
||||
return o;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// BUSH
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions bush1() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 45590;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 0.808f; o.bark.g = 0.800f; o.bark.b = 0.745f;
|
||||
o.bark.textureScaleX = 0.5f; o.bark.textureScaleY = 5f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 21.52f); b.angle.put(2, 62.61f); b.angle.put(3, 60f);
|
||||
b.children.put(0, 7); b.children.put(1, 3); b.children.put(2, 2);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0f;
|
||||
b.gnarliness.put(0, 0.11f); b.gnarliness.put(1, 0.09f);
|
||||
b.gnarliness.put(2, 0.05f); b.gnarliness.put(3, 0.09f);
|
||||
// Near-zero trunk: 0.25f stump, factor adjusted so actual branch1 ≈ 3.8 units
|
||||
b.length.put(0, 0.25f); b.length.put(1, 15.30f); b.length.put(2, 0.365f); b.length.put(3, 0.823f);
|
||||
b.radius.put(0, 0.14f); b.radius.put(1, 0.65f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 4); b.sections.put(1, 6); b.sections.put(2, 10); b.sections.put(3, 10);
|
||||
b.segments.put(0, 4); b.segments.put(1, 4); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.53f); b.start.put(2, 0.33f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
b.twist.put(0, 17.19f); b.twist.put(1, -4.01f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASH, 0.878f, 1.000f, 0.835f, Billboard.CROSS, 12, 0.00f, 0.49f, 0.717f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions bush2() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 45590;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 0.808f; o.bark.g = 0.800f; o.bark.b = 0.745f;
|
||||
o.bark.textureScaleX = 0.5f; o.bark.textureScaleY = 5f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 2;
|
||||
b.angle.put(1, 19.57f); b.angle.put(2, 27.39f); b.angle.put(3, 60f);
|
||||
b.children.put(0, 10); b.children.put(1, 3); b.children.put(2, 2);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0f;
|
||||
b.gnarliness.put(0, 0.022f); b.gnarliness.put(1, 0.109f);
|
||||
b.gnarliness.put(2, 0.050f); b.gnarliness.put(3, 0.090f);
|
||||
// Actual branch1 ≈ 4.9 units (19.646/4 ÷ 0.25)
|
||||
b.length.put(0, 0.25f); b.length.put(1, 19.65f); b.length.put(2, 0.392f); b.length.put(3, 0.597f);
|
||||
b.radius.put(0, 0.14f); b.radius.put(1, 0.65f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 3); b.sections.put(1, 4); b.sections.put(2, 10); b.sections.put(3, 10);
|
||||
b.segments.put(0, 4); b.segments.put(1, 4); b.segments.put(2, 4); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.641f); b.start.put(2, 0.707f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
b.twist.put(0, 20.55f); b.twist.put(1, -2.49f);
|
||||
|
||||
o.leaves = leaves(LEAF_ASPEN, 0.878f, 1.000f, 0.835f, Billboard.CROSS, 7, 0.00f, 0.49f, 0.717f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static TreeOptions bush3() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 31343;
|
||||
o.type = TreeType.EVERGREEN;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 0.808f; o.bark.g = 0.800f; o.bark.b = 0.745f;
|
||||
o.bark.textureScaleX = 0.5f; o.bark.textureScaleY = 5f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 66.52f); b.angle.put(2, 52.83f); b.angle.put(3, 0f);
|
||||
b.children.put(0, 13); b.children.put(1, 4); b.children.put(2, 4);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0f;
|
||||
b.gnarliness.put(0, 0.054f); b.gnarliness.put(1, 0.065f);
|
||||
b.gnarliness.put(2, 0.050f); b.gnarliness.put(3, 0.090f);
|
||||
b.length.put(0, 2.74f); b.length.put(1, 1.991f); b.length.put(2, 0.602f); b.length.put(3, 0.421f);
|
||||
b.radius.put(0, 0.14f); b.radius.put(1, 0.65f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 4); b.sections.put(1, 3); b.sections.put(2, 3); b.sections.put(3, 10);
|
||||
b.segments.put(0, 3); b.segments.put(1, 3); b.segments.put(2, 3); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.141f); b.start.put(2, 0.294f);
|
||||
b.taper.put(0, 0.70f); b.taper.put(1, 0.70f); b.taper.put(2, 0.70f); b.taper.put(3, 0.70f);
|
||||
b.twist.put(0, 17.19f); b.twist.put(1, -1.87f);
|
||||
|
||||
o.leaves = leaves(LEAF_PINE, 0.616f, 0.765f, 1.000f, Billboard.CROSS, 3, 0.152f, 0.61f, 0.457f, 0.5f);
|
||||
return o;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// TRELLIS
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions trellis() {
|
||||
TreeOptions o = new TreeOptions();
|
||||
o.seed = 41563;
|
||||
o.type = TreeType.DECIDUOUS;
|
||||
o.bark.textureFile = BARK1;
|
||||
o.bark.r = 1.0f; o.bark.g = 1.0f; o.bark.b = 1.0f;
|
||||
o.bark.textureScaleX = 1f; o.bark.textureScaleY = 8f;
|
||||
|
||||
BranchOptions b = o.branch;
|
||||
b.levels = 3;
|
||||
b.angle.put(1, 26f); b.angle.put(2, 79f); b.angle.put(3, 0f);
|
||||
b.children.put(0, 7); b.children.put(1, 5); b.children.put(2, 1);
|
||||
b.force.direction.set(0f, 1f, 0f); b.force.strength = 0.026f;
|
||||
b.gnarliness.put(0, 0.00f); b.gnarliness.put(1, 0.02f);
|
||||
b.gnarliness.put(2, 0.41f); b.gnarliness.put(3, 0.09f);
|
||||
b.length.put(0, 1.20f); b.length.put(1, 3.521f); b.length.put(2, 0.669f); b.length.put(3, 0.982f);
|
||||
b.radius.put(0, 0.068f); b.radius.put(1, 0.60f); b.radius.put(2, 0.55f); b.radius.put(3, 0.50f);
|
||||
b.sections.put(0, 6); b.sections.put(1, 12); b.sections.put(2, 10); b.sections.put(3, 4);
|
||||
b.segments.put(0, 3); b.segments.put(1, 3); b.segments.put(2, 3); b.segments.put(3, 3);
|
||||
b.start.put(1, 0.19f); b.start.put(2, 0.10f); b.start.put(3, 0.06f);
|
||||
b.taper.put(0, 0.60f); b.taper.put(1, 0.50f); b.taper.put(2, 0.50f); b.taper.put(3, 0.50f);
|
||||
b.twist.put(0, -1.15f); b.twist.put(1, -0.57f); b.twist.put(2, 5.16f);
|
||||
|
||||
// tint 15204310 = 0xE7D8C6: (231,216,198)/255
|
||||
o.leaves = leaves(LEAF_ASH, 0.906f, 0.847f, 0.776f, Billboard.NONE, 13, 0.00f, 0.34f, 0.50f, 0.5f);
|
||||
|
||||
o.trellis.enabled = true;
|
||||
o.trellis.sections = 8;
|
||||
o.trellis.radius = 0.5f;
|
||||
o.trellis.length = 4f;
|
||||
o.trellis.memberRadius = 0.08f;
|
||||
o.trellis.crossMembers = 4;
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Lookup
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
public static TreeOptions byName(String name) {
|
||||
return switch (name.toLowerCase()) {
|
||||
case "oak small" -> oakSmall();
|
||||
case "oak medium" -> oakMedium();
|
||||
case "oak large" -> oakLarge();
|
||||
case "ash small" -> ashSmall();
|
||||
case "ash medium" -> ashMedium();
|
||||
case "ash large" -> ashLarge();
|
||||
case "aspen small" -> aspenSmall();
|
||||
case "aspen medium" -> aspenMedium();
|
||||
case "aspen large" -> aspenLarge();
|
||||
case "pine small" -> pineSmall();
|
||||
case "pine medium" -> pineMedium();
|
||||
case "pine large" -> pineLarge();
|
||||
case "bush 1" -> bush1();
|
||||
case "bush 2" -> bush2();
|
||||
case "bush 3" -> bush3();
|
||||
case "trellis" -> trellis();
|
||||
default -> oakMedium();
|
||||
};
|
||||
}
|
||||
|
||||
public static String[] presetNames() {
|
||||
return new String[]{
|
||||
"Oak Small", "Oak Medium", "Oak Large",
|
||||
"Ash Small", "Ash Medium", "Ash Large",
|
||||
"Aspen Small", "Aspen Medium", "Aspen Large",
|
||||
"Pine Small", "Pine Medium", "Pine Large",
|
||||
"Bush 1", "Bush 2", "Bush 3",
|
||||
"Trellis"
|
||||
};
|
||||
}
|
||||
|
||||
private static LeavesOptions leaves(String tex, float r, float g, float b,
|
||||
Billboard bb, int count,
|
||||
float start, float size, float sizeVar, float alpha) {
|
||||
LeavesOptions l = new LeavesOptions();
|
||||
l.textureFile = tex;
|
||||
l.r = r; l.g = g; l.b = b;
|
||||
l.billboard = bb;
|
||||
l.count = count;
|
||||
l.start = start;
|
||||
l.size = size;
|
||||
l.sizeVariance = sizeVar;
|
||||
l.alphaTest = alpha;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/TreeType.class
Normal file
5
ez-tree-jme/src/main/java/de/blight/eztree/TreeType.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
public enum TreeType {
|
||||
DECIDUOUS, EVERGREEN
|
||||
}
|
||||
141
ez-tree-jme/src/main/java/de/blight/eztree/Trellis.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builds a cylindrical lattice (trellis) for climbing plants.
|
||||
*
|
||||
* The trellis consists of {@code sections} vertical poles arranged in a circle
|
||||
* of {@code radius}, connected by {@code crossMembers} horizontal rings.
|
||||
* All members are thin cylinders with {@code memberRadius}.
|
||||
*/
|
||||
public final class Trellis {
|
||||
|
||||
private Trellis() {}
|
||||
|
||||
public static Node build(TrellisOptions o) {
|
||||
Node node = new Node("trellis");
|
||||
|
||||
int poles = Math.max(3, o.sections);
|
||||
int crossMembers = Math.max(1, o.crossMembers);
|
||||
float totalHeight = o.length;
|
||||
float poleRadius = o.radius;
|
||||
float memberR = o.memberRadius;
|
||||
int cylSegs = 5;
|
||||
|
||||
List<Float> pos = new ArrayList<>();
|
||||
List<Float> norm = new ArrayList<>();
|
||||
List<Float> uv = new ArrayList<>();
|
||||
List<Integer> idx = new ArrayList<>();
|
||||
|
||||
// Vertical poles
|
||||
for (int p = 0; p < poles; p++) {
|
||||
float phi = (float) p / poles * FastMath.TWO_PI;
|
||||
float px = FastMath.cos(phi) * poleRadius;
|
||||
float pz = FastMath.sin(phi) * poleRadius;
|
||||
addCylinder(pos, norm, uv, idx,
|
||||
new Vector3f(px, 0f, pz),
|
||||
new Vector3f(px, totalHeight, pz),
|
||||
memberR, cylSegs);
|
||||
}
|
||||
|
||||
// Horizontal cross-member rings
|
||||
for (int cm = 0; cm < crossMembers; cm++) {
|
||||
float y = totalHeight * (cm + 1f) / (crossMembers + 1f);
|
||||
for (int p = 0; p < poles; p++) {
|
||||
float phi0 = (float) p / poles * FastMath.TWO_PI;
|
||||
float phi1 = (float) (p + 1) / poles * FastMath.TWO_PI;
|
||||
Vector3f from = new Vector3f(
|
||||
FastMath.cos(phi0) * poleRadius, y, FastMath.sin(phi0) * poleRadius);
|
||||
Vector3f to = new Vector3f(
|
||||
FastMath.cos(phi1) * poleRadius, y, FastMath.sin(phi1) * poleRadius);
|
||||
addCylinder(pos, norm, uv, idx, from, to, memberR, cylSegs);
|
||||
}
|
||||
}
|
||||
|
||||
Geometry geom = toGeometry("trellis-mesh", pos, norm, uv, idx);
|
||||
node.attachChild(geom);
|
||||
return node;
|
||||
}
|
||||
|
||||
// ── Cylinder helper ──────────────────────────────────────────────────────
|
||||
|
||||
private static void addCylinder(List<Float> pos, List<Float> norm, List<Float> uv,
|
||||
List<Integer> idx,
|
||||
Vector3f from, Vector3f to, float radius, int segs) {
|
||||
Vector3f dir = to.subtract(from).normalizeLocal();
|
||||
Vector3f right = findPerp(dir);
|
||||
Vector3f up = dir.cross(right).normalizeLocal();
|
||||
|
||||
int base = pos.size() / 3;
|
||||
|
||||
for (int ring = 0; ring <= 1; ring++) {
|
||||
Vector3f center = (ring == 0) ? from : to;
|
||||
float v = ring;
|
||||
for (int s = 0; s <= segs; s++) {
|
||||
float a = (float) s / segs * FastMath.TWO_PI;
|
||||
float ca = FastMath.cos(a), sa = FastMath.sin(a);
|
||||
float ox = right.x * ca + up.x * sa;
|
||||
float oy = right.y * ca + up.y * sa;
|
||||
float oz = right.z * ca + up.z * sa;
|
||||
pos.add(center.x + ox * radius);
|
||||
pos.add(center.y + oy * radius);
|
||||
pos.add(center.z + oz * radius);
|
||||
float nl = FastMath.sqrt(ox*ox + oy*oy + oz*oz);
|
||||
norm.add(ox / nl); norm.add(oy / nl); norm.add(oz / nl);
|
||||
uv.add((float) s / segs); uv.add(v);
|
||||
}
|
||||
}
|
||||
|
||||
int ringSz = segs + 1;
|
||||
for (int s = 0; s < segs; s++) {
|
||||
int a = base + s, b = base + s + 1;
|
||||
int c = base + ringSz + s, d = base + ringSz + s + 1;
|
||||
idx.add(a); idx.add(b); idx.add(c);
|
||||
idx.add(b); idx.add(d); idx.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static Geometry toGeometry(String name,
|
||||
List<Float> pos, List<Float> norm,
|
||||
List<Float> uv, List<Integer> idx) {
|
||||
int nv = pos.size() / 3;
|
||||
FloatBuffer pb = BufferUtils.createFloatBuffer(nv * 3);
|
||||
FloatBuffer nb = BufferUtils.createFloatBuffer(nv * 3);
|
||||
FloatBuffer ub = BufferUtils.createFloatBuffer(nv * 2);
|
||||
IntBuffer ib = BufferUtils.createIntBuffer(idx.size());
|
||||
|
||||
for (float f : pos) pb.put(f);
|
||||
for (float f : norm) nb.put(f);
|
||||
for (float f : uv) ub.put(f);
|
||||
for (int i : idx) ib.put(i);
|
||||
|
||||
pb.flip(); nb.flip(); ub.flip(); ib.flip();
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(Type.Position, 3, pb);
|
||||
mesh.setBuffer(Type.Normal, 3, nb);
|
||||
mesh.setBuffer(Type.TexCoord, 2, ub);
|
||||
mesh.setBuffer(Type.Index, 3, ib);
|
||||
mesh.updateBound();
|
||||
|
||||
return new Geometry(name, mesh);
|
||||
}
|
||||
|
||||
private static Vector3f findPerp(Vector3f v) {
|
||||
Vector3f p = v.cross(Vector3f.UNIT_X);
|
||||
if (p.lengthSquared() < 0.01f) p = v.cross(Vector3f.UNIT_Z);
|
||||
return p.normalizeLocal();
|
||||
}
|
||||
}
|
||||
BIN
ez-tree-jme/src/main/java/de/blight/eztree/TrellisOptions.class
Normal file
@@ -0,0 +1,24 @@
|
||||
package de.blight.eztree;
|
||||
|
||||
public final class TrellisOptions {
|
||||
|
||||
public boolean enabled = false;
|
||||
public int sections = 4;
|
||||
public float radius = 0.5f;
|
||||
public float length = 2f;
|
||||
/** Radius of the cylindrical trellis members. */
|
||||
public float memberRadius = 0.04f;
|
||||
/** Number of cross-members per section. */
|
||||
public int crossMembers = 3;
|
||||
|
||||
public TrellisOptions copy() {
|
||||
TrellisOptions c = new TrellisOptions();
|
||||
c.enabled = enabled;
|
||||
c.sections = sections;
|
||||
c.radius = radius;
|
||||
c.length = length;
|
||||
c.memberRadius = memberRadius;
|
||||
c.crossMembers = crossMembers;
|
||||
return c;
|
||||
}
|
||||
}
|
||||