simvx.core.graphics.mesh

Geometry data with procedural primitives.

High-level: Mesh.cube(), Mesh.sphere(), Mesh.cone() Low-level: Mesh(positions, indices, normals, texcoords) + interleaved_bytes() Optional per-vertex attributes: tangents (N, 4), colours (N, 4), texcoords2 (N, 2)

Module Contents

Classes

Mesh

Vertex data with optional GPU buffers. Pure data until renderer uploads.

Data

API

simvx.core.graphics.mesh.log

‘getLogger(…)’

simvx.core.graphics.mesh.MeshSource

None

class simvx.core.graphics.mesh.Mesh(positions, indices=None, normals=None, texcoords=None, topology='triangles', tangents=None, colours=None, texcoords2=None)[source]

Vertex data with optional GPU buffers. Pure data until renderer uploads.

Create via class methods: Mesh.cube(), Mesh.sphere(), Mesh.cone(), Mesh.cylinder() Or from raw data: Mesh(positions, indices, normals, texcoords) Or load from a Wavefront OBJ: Mesh.from_obj(“model.obj”) Mesh.from_obj(Resource(“game.assets”, “ship.obj”))

The optional :attr:factory_spec records the call that produced a mesh ({"name": "cube", "kwargs": {"size": 1.0}} or {"name": "obj", "source": <spec>}) so scene serialisation can round-trip the mesh by replaying the factory. Replayable mutators applied afterwards (:meth:generate_tangents) are recorded under a "mutators" key and replayed in order on deserialisation.

Initialization

property vertex_count: int[source]
property index_count: int[source]
property stride: int[source]

Bytes per interleaved vertex.

property has_normals: bool[source]
property has_texcoords: bool[source]
property has_tangents: bool[source]
property has_colours: bool[source]
property has_texcoords2: bool[source]
interleaved_bytes() bytes[source]

Pack vertex data interleaved (pos[,normal][,uv]) for GPU upload.

Optional tangents/colours/texcoords2 are CPU-side data and are not packed here; they ride the split vertex streams.

index_bytes() bytes[source]
bounding_box() tuple[numpy.ndarray, numpy.ndarray][source]

Returns (min_corner, max_corner) as vec3 arrays.

bounding_radius() float[source]

Radius of bounding sphere centered at origin.

generate_normals() simvx.core.graphics.mesh.Mesh[source]

Compute smooth vertex normals from face geometry. Returns self.

Vectorised via np.add.at: each face normal is scattered into the three vertex slots that share it, in the same order the per-triangle loop would have visited them. The result matches the prior implementation bit-for-bit because addition is commutative.

generate_tangents() simvx.core.graphics.mesh.Mesh[source]

Compute per-vertex tangents (xyz, w = handedness) from UV gradients. Returns self.

Vectorised in the :meth:generate_normals style: per-triangle tangents and bitangents derived from the UV gradients are scattered into the shared vertex slots with np.add.at, then each vertex tangent is Gram-Schmidt orthonormalised against the vertex normal. w follows the glTF convention: +1, or -1 for mirrored UVs, so bitangent = cross(normal, tangent.xyz) * w.

Requires texcoords. Missing normals are generated first (which needs indices); triangles with degenerate UVs contribute nothing and isolated degenerate vertices keep a zero tangent.

classmethod cube(size: float = 1.0) simvx.core.graphics.mesh.Mesh[source]

Axis-aligned cube centered at origin. 24 vertices, 36 indices.

classmethod sphere(radius: float = 1.0, rings: int = 16, segments: int = 16) simvx.core.graphics.mesh.Mesh[source]

UV sphere.

classmethod cone(radius: float = 0.5, height: float = 1.0, segments: int = 16) simvx.core.graphics.mesh.Mesh[source]

Cone pointing up +Y, base centered at origin.

classmethod cylinder(radius: float = 0.5, height: float = 1.0, segments: int = 16) simvx.core.graphics.mesh.Mesh[source]

Cylinder along +Y axis, centered at origin.

classmethod extrude_path(centerline: list[tuple[float, float, float]] | numpy.ndarray, sides: int = 8, radius: float = 0.5, profile: numpy.ndarray | None = None, closed: bool = False) simvx.core.graphics.mesh.Mesh[source]

Sweep a profile (default: regular polygon) along a 3D centerline.

Builds a tube / ribbon mesh: the bread-and-butter primitive for racing tracks, river meshes, electric arcs, tentacles, and anything else that follows a polyline. HexGL hand-rolled ~80 LOC of NumPy ribbon code; this method replaces that with a single call.

Args: centerline: (N, 3) sequence of world-space points. Must have N >= 2. Successive duplicate points are tolerated (collapsed segments produce zero-length quads). sides: Number of segments around the tube. Default 8. Ignored if profile is supplied. radius: Radius of the default circular profile. Ignored when profile is supplied. profile: Optional (sides, 2) array of 2D points in the profile’s local frame (X = right, Y = up relative to the centerline tangent). When None a regular sides-gon of the given radius is generated. closed: When True, the last centerline segment loops back to the first: useful for closed tracks. The profile orientation at the join is whatever the local frame returns; for racetracks pre-join the geometry yourself if you need smooth continuity.

Returns: A Mesh with positions, normals (vertex-averaged from face normals), texcoords (V along the path, U around the ring), and triangle indices. factory_spec is set so the mesh round-trips through the scene serialiser.

Example::

track = Mesh.extrude_path(
    centerline=[(0, 0, 0), (5, 0.5, -1), (8, 0, -6)],
    sides=12,
    radius=0.8,
)
classmethod load(source: simvx.core.graphics.mesh.MeshSource) simvx.core.graphics.mesh.Mesh[source]

Load a mesh from any supported source.

Currently delegates to :meth:from_obj (only Wavefront OBJ is supported). The check is by file extension on the resolved path.

classmethod from_obj(source: simvx.core.graphics.mesh.MeshSource) simvx.core.graphics.mesh.Mesh[source]

Load from Wavefront OBJ. Handles v/vt/vn/f directives.

Accepts str / os.PathLike (filesystem path),

Class:

~simvx.core.Resource (package handle), or

Class:

importlib.resources.abc.Traversable (raw importlib.resources result).