Download scripts/modules/mesh_gen.gd from geter62/monkeystudio: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/geter62/monkeystudio/resolve/main/scripts/modules/mesh_gen.gd
- Command line
-
hf download hf://geter62/monkeystudio/scripts/modules/mesh_gen.gd
-
curl -L -o mesh_gen.gd https://huggingface.co/geter62/monkeystudio/resolve/main/scripts/modules/mesh_gen.gd
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 | |