M0.5: detailed scenery, mountain/sea-cliff boundary, Godot 4.7
Scenery (all procedural, seeded, no imported assets): - Terrain shader: grass/dry grass/dirt/sand/rock strata/snow + detail normals - 5 tree types, faceted mossy rocks, cliff outcrops, sea stacks, bushes, logs and stumps, batched as MultiMesh per 48 m cell - Wind-animated grass and flowers streamed in tiles around the player - Lake (depth-shaded water with shore foam), ocean to the horizon, cloud sky shader, volumetric fog, glow, softer shadows - Swimming: float at the surface, swim-jump to climb out World bounds: - Steep mountain rim with an invisible wall partway up; one side opens to a sheer sea cliff with the wall 1 m inside the edge; whole chunk is an island so the mesh edge is never visible - Respawn safety net if the player leaves the walkable area or the map - Fix: the cliff edge now tracks the wall in the headlands (it used to drop away inside the wall, leaving a pit with lake water hanging over it) Tests: tests/test_boundary.gd (24-direction escape attempt) and tests/test_terrain_edges.gd (no ground below waterline near the wall). Upgrades the project to Godot 4.7 (project.godot rewritten by the 4.7 importer, .uid files added) and documents it in the setup guide.
This commit is contained in:
372
scripts/world/proc_mesh.gd
Normal file
372
scripts/world/proc_mesh.gd
Normal file
@@ -0,0 +1,372 @@
|
||||
extends RefCounted
|
||||
## Procedural low-poly mesh builders: trees, rocks, bushes, logs, grass, flowers.
|
||||
##
|
||||
## Every builder takes a seed, so the same seed gives the same mesh on every
|
||||
## machine (the determinism rule in docs/ARCHITECTURE.md §4). Meshes are
|
||||
## flat-shaded and coloured per face through vertex colours (authored as sRGB).
|
||||
## Callers assign materials per surface; tree dictionaries list which surface is
|
||||
## which in "surfaces".
|
||||
##
|
||||
## Winding: Godot treats triangle (a, b, c) as front-facing when
|
||||
## (b - a).cross(c - a) points AWAY from the viewer. `_tri()` orders the
|
||||
## vertices from an outward hint, so builders never have to think about it.
|
||||
|
||||
const BARK := Color(0.31, 0.22, 0.15)
|
||||
const BARK_GREY := Color(0.42, 0.38, 0.33)
|
||||
const BIRCH_WHITE := Color(0.86, 0.85, 0.80)
|
||||
const BIRCH_DARK := Color(0.16, 0.15, 0.14)
|
||||
const WOOD := Color(0.70, 0.55, 0.36)
|
||||
const SPRUCE := Color(0.13, 0.27, 0.17)
|
||||
const PINE := Color(0.18, 0.31, 0.16)
|
||||
const LEAF_GREEN := Color(0.27, 0.43, 0.16)
|
||||
const LEAF_GOLD := Color(0.62, 0.50, 0.15)
|
||||
const BIRCH_LEAF := Color(0.42, 0.54, 0.20)
|
||||
const BUSH := Color(0.19, 0.34, 0.14)
|
||||
const ROCK := Color(0.50, 0.49, 0.47)
|
||||
const STEM := Color(0.25, 0.42, 0.15)
|
||||
|
||||
static var _ico_cache := {}
|
||||
|
||||
# --- primitives -------------------------------------------------------------
|
||||
|
||||
static func _begin() -> SurfaceTool:
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
st.set_smooth_group(-1) # flat shading
|
||||
return st
|
||||
|
||||
static func _commit(st: SurfaceTool, mesh: ArrayMesh = null) -> ArrayMesh:
|
||||
st.generate_normals()
|
||||
return st.commit(mesh)
|
||||
|
||||
static func _mul(c: Color, k: float) -> Color:
|
||||
return Color(c.r * k, c.g * k, c.b * k, 1.0)
|
||||
|
||||
static func _tri(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, outward: Vector3, col: Color) -> void:
|
||||
if (b - a).cross(c - a).dot(outward) > 0.0:
|
||||
var tmp := b
|
||||
b = c
|
||||
c = tmp
|
||||
st.set_color(col)
|
||||
st.add_vertex(a)
|
||||
st.set_color(col)
|
||||
st.add_vertex(b)
|
||||
st.set_color(col)
|
||||
st.add_vertex(c)
|
||||
|
||||
## Tapered cylinder from `base` to `top`. `off` fixes the ring rotation so
|
||||
## stacked segments line up; cap_col.a > 0 adds end caps in that colour.
|
||||
static func _cylinder(st: SurfaceTool, base: Vector3, top: Vector3, r0: float, r1: float, segs: int,
|
||||
col: Color, rng: RandomNumberGenerator, off: float = -1.0, cap_col: Color = Color(0, 0, 0, 0)) -> void:
|
||||
var ax := (top - base).normalized()
|
||||
var ref := Vector3.UP if absf(ax.y) < 0.95 else Vector3.RIGHT
|
||||
var u := ax.cross(ref).normalized()
|
||||
var v := ax.cross(u).normalized()
|
||||
if off < 0.0:
|
||||
off = rng.randf() * TAU
|
||||
var ring0: Array[Vector3] = []
|
||||
var ring1: Array[Vector3] = []
|
||||
for i in range(segs):
|
||||
var a := off + TAU * i / segs
|
||||
var dir := u * cos(a) + v * sin(a)
|
||||
ring0.append(base + dir * r0)
|
||||
ring1.append(top + dir * r1)
|
||||
for i in range(segs):
|
||||
var j := (i + 1) % segs
|
||||
var mid := (ring0[i] + ring0[j] + ring1[i] + ring1[j]) * 0.25
|
||||
var out := mid - (base + ax * (mid - base).dot(ax))
|
||||
var c := _mul(col, rng.randf_range(0.85, 1.1))
|
||||
_tri(st, ring0[i], ring0[j], ring1[j], out, c)
|
||||
_tri(st, ring0[i], ring1[j], ring1[i], out, c)
|
||||
if cap_col.a > 0.0:
|
||||
for i in range(segs):
|
||||
var j := (i + 1) % segs
|
||||
_tri(st, top, ring1[i], ring1[j], ax, cap_col)
|
||||
_tri(st, base, ring0[i], ring0[j], -ax, cap_col)
|
||||
|
||||
static func icosphere(subdiv: int) -> Array:
|
||||
if _ico_cache.has(subdiv):
|
||||
return _ico_cache[subdiv]
|
||||
var t := (1.0 + sqrt(5.0)) / 2.0
|
||||
var v := [
|
||||
Vector3(-1, t, 0), Vector3(1, t, 0), Vector3(-1, -t, 0), Vector3(1, -t, 0),
|
||||
Vector3(0, -1, t), Vector3(0, 1, t), Vector3(0, -1, -t), Vector3(0, 1, -t),
|
||||
Vector3(t, 0, -1), Vector3(t, 0, 1), Vector3(-t, 0, -1), Vector3(-t, 0, 1),
|
||||
]
|
||||
var faces := [
|
||||
[0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11],
|
||||
[1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8],
|
||||
[3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9],
|
||||
[4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1],
|
||||
]
|
||||
var tris := []
|
||||
for f in faces:
|
||||
tris.append([(v[f[0]] as Vector3).normalized(), (v[f[1]] as Vector3).normalized(), (v[f[2]] as Vector3).normalized()])
|
||||
for _s in range(subdiv):
|
||||
var nt := []
|
||||
for tr in tris:
|
||||
var a: Vector3 = tr[0]
|
||||
var b: Vector3 = tr[1]
|
||||
var c: Vector3 = tr[2]
|
||||
var ab := ((a + b) * 0.5).normalized()
|
||||
var bc := ((b + c) * 0.5).normalized()
|
||||
var ca := ((c + a) * 0.5).normalized()
|
||||
nt.append([a, ab, ca])
|
||||
nt.append([b, bc, ab])
|
||||
nt.append([c, ca, bc])
|
||||
nt.append([ab, bc, ca])
|
||||
tris = nt
|
||||
_ico_cache[subdiv] = tris
|
||||
return tris
|
||||
|
||||
## Lumpy flat-shaded sphere. Displacement is a function of position, so shared
|
||||
## corners move together and the surface stays watertight.
|
||||
static func _blob(st: SurfaceTool, center: Vector3, radii: Vector3, noise: FastNoiseLite, amount: float,
|
||||
col: Color, rng: RandomNumberGenerator, subdiv: int = 1) -> void:
|
||||
for tr in icosphere(subdiv):
|
||||
var p: Array[Vector3] = []
|
||||
for k in range(3):
|
||||
var q: Vector3 = tr[k]
|
||||
var d := 1.0 + noise.get_noise_3dv(q * 1.7 + center) * amount
|
||||
p.append(center + q * radii * d)
|
||||
_tri(st, p[0], p[1], p[2], (p[0] + p[1] + p[2]) / 3.0 - center, _mul(col, rng.randf_range(0.88, 1.1)))
|
||||
|
||||
## One drooping tier of a conifer: a star-shaped ring (alternating branch tips
|
||||
## and notches) rising to an apex, with a darker underside.
|
||||
static func _conifer_tier(st: SurfaceTool, center: Vector3, r: float, h: float, segs: int,
|
||||
col: Color, rng: RandomNumberGenerator) -> void:
|
||||
var n := segs * 2
|
||||
var ring: Array[Vector3] = []
|
||||
var off := rng.randf() * TAU
|
||||
for i in range(n):
|
||||
var a := off + TAU * i / n + rng.randf_range(-0.12, 0.12)
|
||||
var tip := i % 2 == 0
|
||||
var rr := r * (rng.randf_range(0.9, 1.12) if tip else rng.randf_range(0.58, 0.7))
|
||||
var yy := -(r * 0.32 if tip else r * 0.12) + rng.randf_range(-0.08, 0.08)
|
||||
ring.append(center + Vector3(cos(a) * rr, yy, sin(a) * rr))
|
||||
var apex := center + Vector3(rng.randf_range(-0.08, 0.08), h, rng.randf_range(-0.08, 0.08))
|
||||
var under := center + Vector3(0.0, h * 0.18, 0.0)
|
||||
for i in range(n):
|
||||
var a := ring[i]
|
||||
var b := ring[(i + 1) % n]
|
||||
var mid := (a + b) * 0.5 - center
|
||||
var out := Vector3(mid.x, 0.0, mid.z).normalized() + Vector3(0.0, 0.8, 0.0)
|
||||
_tri(st, apex, a, b, out, _mul(col, rng.randf_range(0.85, 1.12)))
|
||||
_tri(st, under, a, b, Vector3(mid.x * 0.3, -1.0, mid.z * 0.3), _mul(col, 0.55))
|
||||
|
||||
# --- trees ------------------------------------------------------------------
|
||||
|
||||
## Spruce (bushy, tiers from near the ground) or pine (tall, tiers only up top).
|
||||
static func conifer(seed: int, tall: bool) -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var bark := _begin()
|
||||
var leaf := _begin()
|
||||
var trunk_h := rng.randf_range(8.5, 10.5) if tall else rng.randf_range(5.5, 7.0)
|
||||
var trunk_r := 0.26 if tall else 0.3
|
||||
var lean := Vector3(rng.randf_range(-0.15, 0.15), 0.0, rng.randf_range(-0.15, 0.15))
|
||||
_cylinder(bark, Vector3(0, -0.5, 0), Vector3(0, trunk_h, 0) + lean, trunk_r, 0.05, 7, BARK, rng)
|
||||
|
||||
var tiers := rng.randi_range(4, 5) if tall else rng.randi_range(5, 7)
|
||||
var y0 := trunk_h * 0.55 if tall else 1.3
|
||||
var y1 := trunk_h - 0.6
|
||||
var r_bottom := 1.7 if tall else 2.4
|
||||
var col := _mul(PINE if tall else SPRUCE, rng.randf_range(0.9, 1.1))
|
||||
for i in range(tiers):
|
||||
var f := float(i) / float(tiers - 1)
|
||||
var y := lerpf(y0, y1, f)
|
||||
var r := lerpf(r_bottom, 0.55, pow(f, 0.9)) * rng.randf_range(0.9, 1.1)
|
||||
_conifer_tier(leaf, Vector3(0, y, 0) + lean * (y / trunk_h), r, lerpf(1.9, 1.3, f), 9, col, rng)
|
||||
_conifer_tier(leaf, Vector3(0, y1 + 0.9, 0) + lean, 0.35, 1.1, 5, col, rng)
|
||||
|
||||
var mesh := _commit(bark)
|
||||
_commit(leaf, mesh)
|
||||
return {"mesh": mesh, "surfaces": ["bark", "leaf"], "trunk_r": trunk_r, "trunk_h": trunk_h}
|
||||
|
||||
## Broadleaf (oak-ish): short thick trunk, a few branches, a crown of blobs.
|
||||
static func broadleaf(seed: int, leaf_col: Color) -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var noise := FastNoiseLite.new()
|
||||
noise.seed = seed
|
||||
noise.frequency = 1.3
|
||||
var bark := _begin()
|
||||
var leaf := _begin()
|
||||
var trunk_h := rng.randf_range(2.6, 3.6)
|
||||
var top := Vector3(rng.randf_range(-0.3, 0.3), trunk_h, rng.randf_range(-0.3, 0.3))
|
||||
_cylinder(bark, Vector3(0, -0.4, 0), top, 0.36, 0.2, 7, BARK, rng)
|
||||
var k := rng.randi_range(3, 4)
|
||||
for i in range(k):
|
||||
var a := TAU * i / k + rng.randf_range(-0.4, 0.4)
|
||||
var start := top * rng.randf_range(0.75, 1.0)
|
||||
var end := top + Vector3(cos(a) * rng.randf_range(1.1, 1.8), rng.randf_range(0.6, 1.4), sin(a) * rng.randf_range(1.1, 1.8))
|
||||
_cylinder(bark, start, end, 0.15, 0.06, 5, BARK, rng)
|
||||
|
||||
var crown := top + Vector3(0, 1.4, 0)
|
||||
var col := _mul(leaf_col, rng.randf_range(0.92, 1.08))
|
||||
var blobs := rng.randi_range(6, 9)
|
||||
for i in range(blobs):
|
||||
var c := crown + Vector3(rng.randf_range(-1.7, 1.7), rng.randf_range(-0.6, 1.2), rng.randf_range(-1.7, 1.7))
|
||||
if i == 0:
|
||||
c = crown + Vector3(0, 0.8, 0)
|
||||
var r := rng.randf_range(1.0, 1.6)
|
||||
_blob(leaf, c, Vector3(r, r * 0.82, r), noise, 0.28, _mul(col, rng.randf_range(0.85, 1.15)), rng)
|
||||
|
||||
var mesh := _commit(bark)
|
||||
_commit(leaf, mesh)
|
||||
return {"mesh": mesh, "surfaces": ["bark", "leaf"], "trunk_r": 0.36, "trunk_h": trunk_h}
|
||||
|
||||
## Birch: slim white trunk with dark bands, light airy crown.
|
||||
static func birch(seed: int) -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var noise := FastNoiseLite.new()
|
||||
noise.seed = seed
|
||||
noise.frequency = 1.5
|
||||
var bark := _begin()
|
||||
var leaf := _begin()
|
||||
var trunk_h := rng.randf_range(6.0, 8.0)
|
||||
var lean := Vector3(rng.randf_range(-0.4, 0.4), 0.0, rng.randf_range(-0.4, 0.4))
|
||||
var off := rng.randf() * TAU
|
||||
var y := -0.4
|
||||
while y < trunk_h:
|
||||
var dark := rng.randf() < 0.3
|
||||
var seg_h := rng.randf_range(0.12, 0.22) if dark else rng.randf_range(0.5, 1.0)
|
||||
var y2 := minf(y + seg_h, trunk_h)
|
||||
var r0 := lerpf(0.18, 0.05, maxf(y, 0.0) / trunk_h)
|
||||
var r1 := lerpf(0.18, 0.05, y2 / trunk_h)
|
||||
var col := BIRCH_DARK if dark else _mul(BIRCH_WHITE, rng.randf_range(0.92, 1.03))
|
||||
_cylinder(bark, Vector3(0, y, 0) + lean * (maxf(y, 0.0) / trunk_h), Vector3(0, y2, 0) + lean * (y2 / trunk_h),
|
||||
r0, r1, 6, col, rng, off)
|
||||
y = y2
|
||||
var col_leaf := _mul(BIRCH_LEAF, rng.randf_range(0.92, 1.08))
|
||||
for i in range(rng.randi_range(5, 7)):
|
||||
var cy := rng.randf_range(trunk_h * 0.5, trunk_h + 0.6)
|
||||
var c := Vector3(rng.randf_range(-0.9, 0.9), cy, rng.randf_range(-0.9, 0.9)) + lean * (cy / trunk_h)
|
||||
var r := rng.randf_range(0.7, 1.1)
|
||||
_blob(leaf, c, Vector3(r, r * 1.35, r), noise, 0.3, _mul(col_leaf, rng.randf_range(0.85, 1.15)), rng)
|
||||
var mesh := _commit(bark)
|
||||
_commit(leaf, mesh)
|
||||
return {"mesh": mesh, "surfaces": ["bark", "leaf"], "trunk_r": 0.2, "trunk_h": trunk_h}
|
||||
|
||||
## Dead tree: bare grey trunk with forked branches.
|
||||
static func dead_tree(seed: int) -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var bark := _begin()
|
||||
var trunk_h := rng.randf_range(4.0, 6.0)
|
||||
var top := Vector3(rng.randf_range(-0.3, 0.3), trunk_h, rng.randf_range(-0.3, 0.3))
|
||||
_cylinder(bark, Vector3(0, -0.4, 0), top, 0.28, 0.07, 6, BARK_GREY, rng)
|
||||
for i in range(rng.randi_range(4, 6)):
|
||||
var a := rng.randf() * TAU
|
||||
var start := top * rng.randf_range(0.45, 0.95)
|
||||
var end := start + Vector3(cos(a) * rng.randf_range(0.8, 1.6), rng.randf_range(0.5, 1.5), sin(a) * rng.randf_range(0.8, 1.6))
|
||||
_cylinder(bark, start, end, 0.09, 0.03, 4, BARK_GREY, rng)
|
||||
var a2 := a + rng.randf_range(-0.8, 0.8)
|
||||
_cylinder(bark, end, end + Vector3(cos(a2) * 0.6, rng.randf_range(0.3, 0.8), sin(a2) * 0.6), 0.04, 0.015, 4, BARK_GREY, rng)
|
||||
return {"mesh": _commit(bark), "surfaces": ["bark"], "trunk_r": 0.28, "trunk_h": trunk_h}
|
||||
|
||||
# --- small props ------------------------------------------------------------
|
||||
|
||||
static func bush(seed: int) -> Dictionary:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var noise := FastNoiseLite.new()
|
||||
noise.seed = seed
|
||||
noise.frequency = 2.0
|
||||
var leaf := _begin()
|
||||
var col := _mul(BUSH, rng.randf_range(0.9, 1.15))
|
||||
for i in range(rng.randi_range(3, 5)):
|
||||
var r := rng.randf_range(0.45, 0.8)
|
||||
var c := Vector3(rng.randf_range(-0.55, 0.55), rng.randf_range(0.25, 0.6), rng.randf_range(-0.55, 0.55))
|
||||
_blob(leaf, c, Vector3(r, r * 0.8, r), noise, 0.3, _mul(col, rng.randf_range(0.85, 1.15)), rng)
|
||||
return {"mesh": _commit(leaf), "surfaces": ["leaf"]}
|
||||
|
||||
## Faceted rock. `tall` gives slab/pillar shapes for cliff outcrops.
|
||||
static func rock(seed: int, tall: bool) -> ArrayMesh:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var noise := FastNoiseLite.new()
|
||||
noise.seed = seed
|
||||
noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
noise.frequency = 0.9
|
||||
var squash := Vector3(rng.randf_range(0.9, 1.4), rng.randf_range(0.55, 0.85), rng.randf_range(0.8, 1.2))
|
||||
if tall:
|
||||
squash = Vector3(rng.randf_range(0.7, 1.0), rng.randf_range(1.2, 1.6), rng.randf_range(0.6, 0.9))
|
||||
var st := _begin()
|
||||
var base_col := _mul(ROCK, rng.randf_range(0.9, 1.1))
|
||||
for tr in icosphere(1):
|
||||
var p: Array[Vector3] = []
|
||||
for k in range(3):
|
||||
var q: Vector3 = tr[k]
|
||||
q = q * (1.0 + noise.get_noise_3dv(q) * 0.38) * squash
|
||||
q.y = maxf(q.y, -0.35 * squash.y) # flat-ish base so it sits on the ground
|
||||
p.append(q)
|
||||
_tri(st, p[0], p[1], p[2], p[0] + p[1] + p[2], _mul(base_col, rng.randf_range(0.82, 1.12)))
|
||||
return _commit(st)
|
||||
|
||||
static func log_mesh(seed: int, length: float, radius: float) -> ArrayMesh:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var st := _begin()
|
||||
_cylinder(st, Vector3(-length * 0.5, 0, 0), Vector3(length * 0.5, 0, 0), radius, radius * 0.85, 8, BARK, rng, -1.0, WOOD)
|
||||
return _commit(st)
|
||||
|
||||
static func stump_mesh(seed: int, height: float, radius: float) -> ArrayMesh:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var st := _begin()
|
||||
_cylinder(st, Vector3(0, -0.3, 0), Vector3(0, height, 0), radius * 1.15, radius, 8, BARK, rng, -1.0, WOOD)
|
||||
return _commit(st)
|
||||
|
||||
## A clump of tapered blades. UV.y = 0 at the root, 1 at the tip (drives wind).
|
||||
static func grass_clump(seed: int, blades: int = 10) -> ArrayMesh:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
for i in range(blades):
|
||||
var a := rng.randf() * TAU
|
||||
var rr := sqrt(rng.randf()) * 0.3
|
||||
var pos := Vector3(cos(a) * rr, -0.05, sin(a) * rr)
|
||||
var face := rng.randf() * TAU
|
||||
var side := Vector3(cos(face), 0.0, sin(face))
|
||||
var fwd := Vector3(-side.z, 0.0, side.x)
|
||||
var h := rng.randf_range(0.32, 0.72)
|
||||
var w := rng.randf_range(0.035, 0.06)
|
||||
var bend := rng.randf_range(0.08, 0.28)
|
||||
var b0 := pos - side * w
|
||||
var b1 := pos + side * w
|
||||
var mc := pos + fwd * bend * 0.35 + Vector3(0, h * 0.55, 0)
|
||||
var m0 := mc - side * w * 0.7
|
||||
var m1 := mc + side * w * 0.7
|
||||
var tip := pos + fwd * bend + Vector3(0, h, 0)
|
||||
var u := rng.randf()
|
||||
var data := [[b0, 0.0], [b1, 0.0], [m1, 0.55], [b0, 0.0], [m1, 0.55], [m0, 0.55], [m0, 0.55], [m1, 0.55], [tip, 1.0]]
|
||||
for d in data:
|
||||
st.set_normal(Vector3.UP)
|
||||
st.set_uv(Vector2(u, d[1]))
|
||||
st.add_vertex(d[0])
|
||||
return st.commit()
|
||||
|
||||
## Small flower: a stem (surface 0) and a five-petal head (surface 1, white so
|
||||
## the MultiMesh instance colour tints it).
|
||||
static func flower(seed: int) -> ArrayMesh:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
var stem := _begin()
|
||||
var head := _begin()
|
||||
var h := 0.32
|
||||
var c := Vector3(0, h, 0)
|
||||
_tri(stem, Vector3(-0.012, -0.05, 0), Vector3(0.012, -0.05, 0), c, Vector3.BACK, STEM)
|
||||
_tri(stem, Vector3(0, -0.05, -0.012), Vector3(0, -0.05, 0.012), c, Vector3.RIGHT, STEM)
|
||||
for k in range(5):
|
||||
var a := TAU * k / 5.0
|
||||
var tip := c + Vector3(cos(a) * 0.085, 0.025, sin(a) * 0.085)
|
||||
var l := c + Vector3(cos(a - 0.45) * 0.045, 0.012, sin(a - 0.45) * 0.045)
|
||||
var r := c + Vector3(cos(a + 0.45) * 0.045, 0.012, sin(a + 0.45) * 0.045)
|
||||
_tri(head, c, l, tip, Vector3.UP, Color.WHITE)
|
||||
_tri(head, c, tip, r, Vector3.UP, Color.WHITE)
|
||||
var mesh := _commit(stem)
|
||||
_commit(head, mesh)
|
||||
return mesh
|
||||
Reference in New Issue
Block a user