Files
Starworld/tools/generate_primitives.py

233 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Generate simple GLTF primitive models for Starworld entity rendering.
Requires: pip install pygltflib numpy
"""
import numpy as np
from pygltflib import *
import struct
import os
from pathlib import Path
def create_cube_gltf(output_path):
"""Create a 1x1x1 cube centered at origin"""
# Cube vertices (8 vertices)
vertices = np.array([
[-0.5, -0.5, -0.5], # 0
[ 0.5, -0.5, -0.5], # 1
[ 0.5, 0.5, -0.5], # 2
[-0.5, 0.5, -0.5], # 3
[-0.5, -0.5, 0.5], # 4
[ 0.5, -0.5, 0.5], # 5
[ 0.5, 0.5, 0.5], # 6
[-0.5, 0.5, 0.5], # 7
], dtype=np.float32)
# Cube indices (12 triangles, 36 indices)
indices = np.array([
# Front face
0, 1, 2, 0, 2, 3,
# Back face
5, 4, 7, 5, 7, 6,
# Top face
3, 2, 6, 3, 6, 7,
# Bottom face
4, 5, 1, 4, 1, 0,
# Right face
1, 5, 6, 1, 6, 2,
# Left face
4, 0, 3, 4, 3, 7,
], dtype=np.uint16)
# Normals (per-face normals repeated for each vertex of triangle)
normals = np.array([
# Front
[0, 0, -1], [0, 0, -1], [0, 0, -1], [0, 0, -1], [0, 0, -1], [0, 0, -1],
# Back
[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1],
# Top
[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0],
# Bottom
[0, -1, 0], [0, -1, 0], [0, -1, 0], [0, -1, 0], [0, -1, 0], [0, -1, 0],
# Right
[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0],
# Left
[-1, 0, 0], [-1, 0, 0], [-1, 0, 0], [-1, 0, 0], [-1, 0, 0], [-1, 0, 0],
], dtype=np.float32)
# Create binary data
vertices_binary = vertices.tobytes()
indices_binary = indices.tobytes()
# Build GLTF
gltf = GLTF2(
scene=0,
scenes=[Scene(nodes=[0])],
nodes=[Node(mesh=0)],
meshes=[
Mesh(primitives=[
Primitive(
attributes=Attributes(POSITION=0),
indices=1,
)
])
],
accessors=[
Accessor(
bufferView=0,
componentType=FLOAT,
count=len(vertices),
type=VEC3,
max=vertices.max(axis=0).tolist(),
min=vertices.min(axis=0).tolist(),
),
Accessor(
bufferView=1,
componentType=UNSIGNED_SHORT,
count=len(indices),
type=SCALAR,
),
],
bufferViews=[
BufferView(
buffer=0,
byteOffset=0,
byteLength=len(vertices_binary),
target=ARRAY_BUFFER,
),
BufferView(
buffer=0,
byteOffset=len(vertices_binary),
byteLength=len(indices_binary),
target=ELEMENT_ARRAY_BUFFER,
),
],
buffers=[
Buffer(byteLength=len(vertices_binary) + len(indices_binary))
],
)
gltf.set_binary_blob(vertices_binary + indices_binary)
gltf.save(output_path)
print(f"Created cube: {output_path}")
def create_sphere_gltf(output_path, segments=32, rings=16):
"""Create a UV sphere"""
vertices = []
normals = []
indices = []
# Generate vertices
for ring in range(rings + 1):
theta = ring * np.pi / rings
sin_theta = np.sin(theta)
cos_theta = np.cos(theta)
for seg in range(segments + 1):
phi = seg * 2 * np.pi / segments
sin_phi = np.sin(phi)
cos_phi = np.cos(phi)
x = cos_phi * sin_theta
y = cos_theta
z = sin_phi * sin_theta
vertices.append([x * 0.5, y * 0.5, z * 0.5]) # radius 0.5
normals.append([x, y, z])
# Generate indices
for ring in range(rings):
for seg in range(segments):
first = ring * (segments + 1) + seg
second = first + segments + 1
indices.extend([first, second, first + 1])
indices.extend([second, second + 1, first + 1])
vertices = np.array(vertices, dtype=np.float32)
normals = np.array(normals, dtype=np.float32)
indices = np.array(indices, dtype=np.uint16)
vertices_binary = vertices.tobytes()
normals_binary = normals.tobytes()
indices_binary = indices.tobytes()
gltf = GLTF2(
scene=0,
scenes=[Scene(nodes=[0])],
nodes=[Node(mesh=0)],
meshes=[
Mesh(primitives=[
Primitive(
attributes=Attributes(POSITION=0, NORMAL=1),
indices=2,
)
])
],
accessors=[
Accessor(
bufferView=0,
componentType=FLOAT,
count=len(vertices),
type=VEC3,
max=vertices.max(axis=0).tolist(),
min=vertices.min(axis=0).tolist(),
),
Accessor(
bufferView=1,
componentType=FLOAT,
count=len(normals),
type=VEC3,
),
Accessor(
bufferView=2,
componentType=UNSIGNED_SHORT,
count=len(indices),
type=SCALAR,
),
],
bufferViews=[
BufferView(
buffer=0,
byteOffset=0,
byteLength=len(vertices_binary),
target=ARRAY_BUFFER,
),
BufferView(
buffer=0,
byteOffset=len(vertices_binary),
byteLength=len(normals_binary),
target=ARRAY_BUFFER,
),
BufferView(
buffer=0,
byteOffset=len(vertices_binary) + len(normals_binary),
byteLength=len(indices_binary),
target=ELEMENT_ARRAY_BUFFER,
),
],
buffers=[
Buffer(byteLength=len(vertices_binary) + len(normals_binary) + len(indices_binary))
],
)
gltf.set_binary_blob(vertices_binary + normals_binary + indices_binary)
gltf.save(output_path)
print(f"Created sphere: {output_path}")
if __name__ == "__main__":
# Create output directory
cache_dir = Path.home() / ".cache" / "starworld" / "primitives"
cache_dir.mkdir(parents=True, exist_ok=True)
# Generate primitives
create_cube_gltf(str(cache_dir / "cube.glb"))
create_sphere_gltf(str(cache_dir / "sphere.glb"))
print(f"\n✓ Primitive models generated in: {cache_dir}")
print(f" - cube.glb")
print(f" - sphere.glb")