monkeystudio / scripts /modules /mesh_gen.gd
geter62's picture
Upload folder using huggingface_hub
84e8204 verified
Raw History Blame Contribute Delete
10.8 kB
extends Node
##
## MeshGenModule - procedural primitive generators (Features 1-20).
##
## Every generator returns a MeshData Dictionary so it can be built for a
## MeshPool mesh via VertexBufferManager. Nothing here allocates GPU buffers
## until commit() is called, keeping generation cheap and evictable.
##
## MeshData := { verts: PackedVector3Array, indices: PackedInt32Array, uvs }
##
var _host: Node = null
var _root: Node3D = null
func _activate(host: Node) -> void:
_host = host
_root = host.get_node_or_null("Geometry") as Node3D
print("[MeshGen] activated")
func _deactivate() -> void:
_host = null
_root = null
func _make_mesh_data() -> Dictionary:
return {
"verts": PackedVector3Array(),
"indices": PackedInt32Array(),
"uvs": PackedVector2Array(),
}
## --- Public generator API ---------------------------------------------------
func generate(kind: String, params: Dictionary = {}) -> Dictionary:
match kind:
"cube": return cube(float(params.get("size", 1.0)))
"sphere": return uv_sphere(int(params.get("rings", 16)), int(params.get("segments", 24)), float(params.get("radius", 0.5)))
"cylinder": return cylinder(int(params.get("segments", 24)), float(params.get("top_r", 0.5)), float(params.get("bottom_r", 0.5)), float(params.get("height", 1.0)))
"plane": return plane(int(params.get("subdiv", 1)), float(params.get("size", 2.0)))
"torus": return torus(int(params.get("major_seg", 24)), int(params.get("minor_seg", 12)), float(params.get("major_r", 0.6)), float(params.get("minor_r", 0.2)))
"ico": return icosphere(int(params.get("subdiv", 1)), float(params.get("radius", 0.5)))
"suzanne": return suzanne(float(params.get("scale", 1.0)))
_:
push_warning("MeshGen: unknown kind '%s'" % kind)
return _make_mesh_data()
## --- Generators -------------------------------------------------------------
func cube(size: float) -> Dictionary:
var d := _make_mesh_data()
var h := size * 0.5
var faces := [
# (normal, tangent, bitangent) for +X,-X,+Y,-Y,+Z,-Z
[Vector3.RIGHT, Vector3.BACK, Vector3.UP],
[Vector3.LEFT, Vector3.FORWARD, Vector3.UP],
[Vector3.UP, Vector3.RIGHT, Vector3.BACK],
[Vector3.DOWN, Vector3.RIGHT, Vector3.FORWARD],
[Vector3.BACK, Vector3.RIGHT, Vector3.UP],
[Vector3.FORWARD, Vector3.LEFT, Vector3.UP],
]
for f in faces:
var n: Vector3 = f[0]
var t: Vector3 = f[1]
var b: Vector3 = f[2]
var base: int = d["verts"].size()
var c := n * h
d["verts"].append(c - t * h - b * h)
d["verts"].append(c + t * h - b * h)
d["verts"].append(c + t * h + b * h)
d["verts"].append(c - t * h + b * h)
d["uvs"].append(Vector2(0, 0))
d["uvs"].append(Vector2(1, 0))
d["uvs"].append(Vector2(1, 1))
d["uvs"].append(Vector2(0, 1))
d["indices"].append(base + 0); d["indices"].append(base + 1); d["indices"].append(base + 2)
d["indices"].append(base + 0); d["indices"].append(base + 2); d["indices"].append(base + 3)
return d
func uv_sphere(rings: int, segments: int, radius: float) -> Dictionary:
var d := _make_mesh_data()
rings = maxi(2, rings)
segments = maxi(3, segments)
for y in range(rings + 1):
var v := float(y) / float(rings)
var phi := v * PI
for x in range(segments + 1):
var u := float(x) / float(segments)
var theta := u * TAU
var pos := Vector3(
sin(phi) * cos(theta),
cos(phi),
sin(phi) * sin(theta)
) * radius
d["verts"].append(pos)
d["uvs"].append(Vector2(u, v))
for y in range(rings):
for x in range(segments):
var a := y * (segments + 1) + x
var b := a + segments + 1
d["indices"].append(a); d["indices"].append(b); d["indices"].append(a + 1)
d["indices"].append(a + 1); d["indices"].append(b); d["indices"].append(b + 1)
return d
func cylinder(segments: int, top_r: float, bottom_r: float, height: float) -> Dictionary:
var d := _make_mesh_data()
segments = maxi(3, segments)
var hy := height * 0.5
for y in range(2):
var r := top_r if y == 1 else bottom_r
var yy := hy if y == 1 else -hy
for x in range(segments + 1):
var u := float(x) / float(segments)
var theta := u * TAU
d["verts"].append(Vector3(cos(theta) * r, yy, sin(theta) * r))
d["uvs"].append(Vector2(u, float(y)))
for x in range(segments):
var a := x
var b := x + segments + 1
d["indices"].append(a); d["indices"].append(b); d["indices"].append(a + 1)
d["indices"].append(a + 1); d["indices"].append(b); d["indices"].append(b + 1)
# Caps
var top_center: int = d["verts"].size()
d["verts"].append(Vector3(0, hy, 0)); d["uvs"].append(Vector2(0.5, 0.5))
var bot_center: int = d["verts"].size()
d["verts"].append(Vector3(0, -hy, 0)); d["uvs"].append(Vector2(0.5, 0.5))
for x in range(segments):
var theta0 := (float(x) / float(segments)) * TAU
var theta1 := (float(x + 1) / float(segments)) * TAU
var t0: int = d["verts"].size()
d["verts"].append(Vector3(cos(theta0) * top_r, hy, sin(theta0) * top_r)); d["uvs"].append(Vector2(0, 0))
d["verts"].append(Vector3(cos(theta1) * top_r, hy, sin(theta1) * top_r)); d["uvs"].append(Vector2(1, 0))
d["indices"].append(top_center); d["indices"].append(t0); d["indices"].append(t0 + 1)
var b0: int = d["verts"].size()
d["verts"].append(Vector3(cos(theta0) * bottom_r, -hy, sin(theta0) * bottom_r)); d["uvs"].append(Vector2(0, 0))
d["verts"].append(Vector3(cos(theta1) * bottom_r, -hy, sin(theta1) * bottom_r)); d["uvs"].append(Vector2(1, 0))
d["indices"].append(bot_center); d["indices"].append(b0 + 1); d["indices"].append(b0)
return d
func plane(subdiv: int, size: float) -> Dictionary:
var d := _make_mesh_data()
subdiv = maxi(1, subdiv)
var half := size * 0.5
for z in range(subdiv + 1):
for x in range(subdiv + 1):
var u := float(x) / float(subdiv)
var v := float(z) / float(subdiv)
d["verts"].append(Vector3(-half + u * size, 0, -half + v * size))
d["uvs"].append(Vector2(u, v))
var row := subdiv + 1
for z in range(subdiv):
for x in range(subdiv):
var a := z * row + x
var b := a + row
d["indices"].append(a); d["indices"].append(a + 1); d["indices"].append(b)
d["indices"].append(a + 1); d["indices"].append(b + 1); d["indices"].append(b)
return d
func torus(major_seg: int, minor_seg: int, major_r: float, minor_r: float) -> Dictionary:
var d := _make_mesh_data()
major_seg = maxi(3, major_seg)
minor_seg = maxi(3, minor_seg)
for i in range(major_seg + 1):
var u := float(i) / float(major_seg) * TAU
for j in range(minor_seg + 1):
var v := float(j) / float(minor_seg) * TAU
var center := Vector3(cos(u) * major_r, 0, sin(u) * major_r)
var normal := Vector3(cos(u) * cos(v), sin(v), sin(u) * cos(v))
d["verts"].append(center + normal * minor_r)
d["uvs"].append(Vector2(float(i) / float(major_seg), float(j) / float(minor_seg)))
for i in range(major_seg):
for j in range(minor_seg):
var a := i * (minor_seg + 1) + j
var b := a + minor_seg + 1
d["indices"].append(a); d["indices"].append(b); d["indices"].append(a + 1)
d["indices"].append(a + 1); d["indices"].append(b); d["indices"].append(b + 1)
return d
func icosphere(subdiv: int, radius: float) -> Dictionary:
var d := _make_mesh_data()
var t := (1.0 + sqrt(5.0)) * 0.5
var verts := [
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],
]
for s in range(maxi(0, subdiv)):
var new_faces := []
var cache := {}
for f in faces:
var a: int = f[0]; var b: int = f[1]; var c: int = f[2]
var ab := _midpoint(verts, cache, a, b)
var bc := _midpoint(verts, cache, b, c)
var ca := _midpoint(verts, cache, c, a)
new_faces.append([a, ab, ca])
new_faces.append([b, bc, ab])
new_faces.append([c, ca, bc])
new_faces.append([ab, bc, ca])
faces = new_faces
for v in verts:
d["verts"].append(v.normalized() * radius)
d["uvs"].resize(d["verts"].size())
for f in faces:
d["indices"].append(f[0]); d["indices"].append(f[1]); d["indices"].append(f[2])
return d
func _midpoint(verts: Array, cache: Dictionary, a: int, b: int) -> int:
var key := "%d_%d" % [mini(a, b), maxi(a, b)]
if cache.has(key):
return cache[key]
var mid: Vector3 = (verts[a] + verts[b]) * 0.5
verts.append(mid)
var idx := verts.size() - 1
cache[key] = idx
return idx
## A low-poly "Suzanne"-style monkey head silhouette (ears + cranium + snout).
## Not bit-identical to Blender's Suzanne, but generated purely from code.
func suzanne(scale: float) -> Dictionary:
var d := _make_mesh_data()
# Cranium: squashed sphere
var cranium := uv_sphere(10, 16, 0.6)
_append(d, cranium["verts"], cranium["indices"], cranium["uvs"], Vector3(0, 0.1, 0) * scale, scale)
# Snout: a cube-ish extension forward
var snout := cube(0.4)
_append(d, snout["verts"], snout["indices"], snout["uvs"], Vector3(0, -0.1, 0.5) * scale, scale)
# Ears: two flat spheres left/right
var ear_l := uv_sphere(6, 10, 0.25)
_append(d, ear_l["verts"], ear_l["indices"], ear_l["uvs"], Vector3(-0.6, 0.35, 0) * scale, scale)
var ear_r := uv_sphere(6, 10, 0.25)
_append(d, ear_r["verts"], ear_r["indices"], ear_r["uvs"], Vector3(0.6, 0.35, 0) * scale, scale)
return d
## Merge a sub-mesh into a target MeshData with an offset and uniform scale.
func _append(dst: Dictionary, verts: PackedVector3Array, indices: PackedInt32Array, uvs: PackedVector2Array, offset: Vector3, s: float) -> void:
var base: int = dst["verts"].size()
for v in verts:
dst["verts"].append(v * s + offset)
for u in uvs:
dst["uvs"].append(u)
for i in indices:
dst["indices"].append(base + i)
## --- Instantiation helper ---------------------------------------------------
## Build a MeshInstance3D from a generated MeshData and attach under Geometry.
## Returns the instance (caller owns its lifetime; use MeshPool.release on free).
func spawn(kind: String, params: Dictionary = {}) -> MeshInstance3D:
var data := generate(kind, params)
return spawn_from_data(data, kind)
func spawn_from_data(data: Dictionary, name_hint: String = "mesh") -> MeshInstance3D:
var mesh := MeshPool.acquire_mesh()
VertexBufferManager.commit(data["verts"], data["indices"], data["uvs"], mesh)
var mi := MeshInstance3D.new()
mi.name = name_hint.capitalize()
mi.mesh = mesh
var mat := StandardMaterial3D.new()
mat.albedo_color = Color(0.7, 0.72, 0.8)
mat.shading_mode = BaseMaterial3D.SHADING_MODE_PER_PIXEL
mi.material_override = mat
if _root != null:
_root.add_child(mi)
return mi