Download scripts/core/vertex_buffer_manager.gd from geter62/monkeystudio: direct link, hf CLI and curl.
- Browser
- Download file 4.2 kB
-
https://huggingface.co/geter62/monkeystudio/resolve/main/scripts/core/vertex_buffer_manager.gd
- Command line
-
hf download hf://geter62/monkeystudio/scripts/core/vertex_buffer_manager.gd
-
curl -L -o vertex_buffer_manager.gd https://huggingface.co/geter62/monkeystudio/resolve/main/scripts/core/vertex_buffer_manager.gd
4.2 kB
| extends Node | |
| ## | |
| ## VertexBufferManager - VRAM-aware helpers for building and editing meshes. | |
| ## | |
| ## Two jobs: | |
| ## 1. estimate_cost_mb(): predict a mesh's RAM/VRAM footprint *before* build so | |
| ## MemoryManager can decide to evict something else first. | |
| ## 2. surface builders that operate on PackedArrays (CPU side) and upload once, | |
| ## avoiding per-vertex set_vertex() calls which are catastrophic on mobile. | |
| ## | |
| ## Mobile rule of thumb used here: ONE glTF/ArrayMesh upload per edit, never a | |
| ## stream of tiny updates. Sculpt strokes batch into a dirty region and flush. | |
| ## | |
| ## Bytes per vertex for the attributes we upload: position(12) + normal(12) + uv(8) | |
| const BYTES_PER_VERTEX := 32 | |
| ## Extra overhead for indices (~4 bytes/vert on a typical closed mesh). | |
| const INDEX_OVERHEAD := 4 | |
| ## Conservative VRAM residency multiplier (driver copies, alignment, etc.). | |
| const VRAM_FACTOR := 1.35 | |
| ## Estimate the RAM+VRAM cost of a mesh with `vertex_count` vertices in MB. | |
| func estimate_cost_mb(vertex_count: int) -> float: | |
| var bytes := float(vertex_count) * float(BYTES_PER_VERTEX + INDEX_OVERHEAD) | |
| return (bytes / 1048576.0) * VRAM_FACTOR | |
| ## Build an ArrayMesh from parallel PackedArrays in a single surface upload. | |
| ## `arrays` must follow Godot's Mesh.ARRAY_* layout. | |
| func build_surface(vertices: PackedVector3Array, normals: PackedVector3Array, | |
| uvs: PackedVector2Array, indices: PackedInt32Array, | |
| mesh: ArrayMesh = null) -> ArrayMesh: | |
| var out := mesh | |
| if out == null: | |
| out = MeshPool.acquire_mesh() | |
| var arrays: Array = [] | |
| arrays.resize(Mesh.ARRAY_MAX) | |
| arrays[Mesh.ARRAY_VERTEX] = vertices | |
| arrays[Mesh.ARRAY_NORMAL] = normals | |
| arrays[Mesh.ARRAY_TEX_UV] = uvs | |
| arrays[Mesh.ARRAY_INDEX] = indices | |
| out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) | |
| return out | |
| ## Replace the geometry of an existing mesh in place (single edit surface). | |
| ## Keeps the same ArrayMesh object so MeshPool accounting stays correct. | |
| func update_surface(mesh: ArrayMesh, surface_idx: int, | |
| vertices: PackedVector3Array, normals: PackedVector3Array, | |
| uvs: PackedVector2Array, indices: PackedInt32Array) -> void: | |
| mesh.clear_surfaces() | |
| build_surface(vertices, normals, uvs, indices, mesh) | |
| # Recompute AABB for correct culling; without this, culling breaks after | |
| # large edits and the object may vanish from the frustum. | |
| mesh.custom_aabb = _compute_aabb(vertices) | |
| func _compute_aabb(vertices: PackedVector3Array) -> AABB: | |
| if vertices.is_empty(): | |
| return AABB() | |
| var mn := vertices[0] | |
| var mx := vertices[0] | |
| for v in vertices: | |
| mn = mn.min(v) | |
| mx = mx.max(v) | |
| return AABB(mn, mx - mn) | |
| ## Recompute smooth normals for a triangle soup. Used after sculpting/vertex | |
| ## edits where normals must reflect the new shape (otherwise lighting freezes). | |
| func recompute_normals(vertices: PackedVector3Array, | |
| indices: PackedInt32Array) -> PackedVector3Array: | |
| var normals := PackedVector3Array() | |
| normals.resize(vertices.size()) | |
| for i in range(normals.size()): | |
| normals[i] = Vector3.ZERO | |
| var tri := indices.size() / 3 | |
| for t in range(tri): | |
| var i0 := indices[t * 3] | |
| var i1 := indices[t * 3 + 1] | |
| var i2 := indices[t * 3 + 2] | |
| var n := (vertices[i1] - vertices[i0]).cross(vertices[i2] - vertices[i0]) | |
| normals[i0] = normals[i0] + n | |
| normals[i1] = normals[i1] + n | |
| normals[i2] = normals[i2] + n | |
| for i in range(normals.size()): | |
| var n := normals[i] | |
| if n.length_squared() > 0.000001: | |
| normals[i] = n.normalized() | |
| else: | |
| normals[i] = Vector3.UP | |
| return normals | |
| ## Convenience: full rebuild + normal recompute + upload in one call. | |
| func commit(vertices: PackedVector3Array, indices: PackedInt32Array, | |
| uvs: PackedVector2Array, mesh: ArrayMesh) -> ArrayMesh: | |
| var normals := recompute_normals(vertices, indices) | |
| update_surface(mesh, 0, vertices, normals, uvs, indices) | |
| return mesh | |
| func debug_vram_mb(meshes: Array) -> float: | |
| var total := 0.0 | |
| for m in meshes: | |
| if m is ArrayMesh: | |
| var vcount := 0 | |
| for s in range(m.get_surface_count()): | |
| var arr: Array = m.surface_get_arrays(s) | |
| if arr.size() > Mesh.ARRAY_VERTEX and arr[Mesh.ARRAY_VERTEX] != null: | |
| vcount += (arr[Mesh.ARRAY_VERTEX] as PackedVector3Array).size() | |
| total += estimate_cost_mb(vcount) | |
| return total | |