Geometry: Primitives, Meshes, and VAOs
Three ways to get geometry on screen, in increasing order of effort: stock primitives, OBJ files, and custom vertex data through the VAO factory.
Stock primitives
Primitives is a static class holding named, ready-to-draw meshes. The
classic NGL teaching models are built in — load them once in
initializeGL, then drawing is one line:
from ncca.ngl.opengl import Primitives
Primitives.load_default_primitives() # once
Primitives.draw("teapot") # per frame
The stock names (also available as the Prims enum from ncca.ngl):
teapot, bunny, buddah, dragon, troll, cube, football,
icosahedron, octahedron, tetrahedron, dodecahedron.
Note
Drawing a stock name without calling load_default_primitives()
first draws nothing — it is the most common first-app mistake.
Parametric primitives
Primitives.create(type, name, ...) generates a shape at the resolution
you ask for and registers it under your own name:
from ncca.ngl import Prims
from ncca.ngl.opengl import Primitives
Primitives.create(Prims.SPHERE, "ball", 0.3, 32) # radius, precision
Primitives.create(Prims.TORUS, "donut", 1.0, 0.3, 40) # radius, tube_radius, precision
Primitives.draw("ball")
The available types and their arguments:
Prims |
Arguments |
|---|---|
SPHERE |
radius, precision |
TORUS |
radius, tube_radius, precision |
CYLINDER / CONE / CAPSULE |
radius, height, slices, stacks |
DISK |
radius, slices |
LINE_GRID |
width, depth, steps (drawn as GL_LINES) |
TRIANGLE_PLANE |
width, depth, w_p, d_p, normal (Vec3) |
Everything except LINE_GRID is interleaved position/normal/UV triangle
data, so the built-in shaders light and texture it correctly. The raw
vertex arrays behind these shapes come from PrimData in ncca.ngl —
useful on its own when you want the data without a VAO (the WebGPU stack
uses it this way).
OBJ files
Obj parses Wavefront OBJ files. The one-liner builds the VAO (and
optionally a texture) for you:
from ncca.ngl import Obj
mesh = Obj.obj_with_vao("models/helix.obj", "textures/helix.png")
mesh.draw() # per frame, with your shader active
Construct Obj("file.obj") instead when you only want the parsed data
(vertices, normals, UVs, Face lists) — see the
Geometry reference. Parse errors raise ObjParse*Error
exceptions rather than returning half-loaded meshes.
Textures
from ncca.ngl.opengl import Texture
tex = Texture("textures/crate.png")
tex_id = tex.set_texture_gl() # creates the GL texture, returns its id
ShaderLib.set_uniform("tex", 0) # sampler uniform = texture *unit* index
Custom geometry: the VAO factory
For your own vertex data, VAOFactory creates a managed vertex array
object. Three implementations ship, chosen with VAOType:
VAOType |
Use for |
|---|---|
SIMPLE |
one interleaved buffer, glDrawArrays |
SIMPLE_INDEX |
interleaved buffer + index buffer, glDrawElements (use IndexVertexData) |
MULTI_BUFFER |
separate buffers per attribute (positions, normals, … each in their own VBO) |
A VAO is used as a context manager — with binds it, leaving the block
unbinds it. Build once:
import numpy as np
import OpenGL.GL as gl
from ncca.ngl.opengl import VAOFactory, VAOType, VertexData
# x,y,z then u,v per vertex
verts = np.array([...], dtype=np.float32)
vao = VAOFactory.create_vao(VAOType.SIMPLE, gl.GL_TRIANGLES)
with vao:
vao.set_data(VertexData(data=verts, size=len(verts) // 5))
stride = 5 * verts.itemsize
vao.set_vertex_attribute_pointer(0, 3, gl.GL_FLOAT, stride, 0) # position
vao.set_vertex_attribute_pointer(1, 2, gl.GL_FLOAT, stride, 3 * verts.itemsize) # uv
Draw per frame:
with vao:
vao.draw()
VertexData(data, size, mode=GL_STATIC_DRAW) carries the array and the
vertex count (not float count); for indexed drawing,
IndexVertexData adds the index array and its GL type. The VAOs also
expose get_buffer_id() and map_buffer() when you need to poke the
underlying VBO — handy for dynamic/streaming data.
Like the WebGPU PipelineFactory, the factory is a registry:
VAOFactory.register_vao_creator(...) adds your own AbstractVAO
subclass without touching call sites —
PyNGLDemos/VertexArrayObject has a worked example.
Text rendering
Text renders a TrueType font via a freetype glyph atlas and
geometry-shader quads, using the built-in DefaultShader.TEXT:
from ncca.ngl import Vec3
from ncca.ngl.opengl import Text
text = Text()
text.add_font("arial", "fonts/Arial.ttf", 40)
text.set_screen_size(self.width(), self.height()) # and again on resize
# in paintGL
text.render_text("arial", 10, 20, "PyNGL!", colour=Vec3(1.0, 1.0, 0.0))
A Text object can hold several fonts/sizes at once — each add_font
builds a glyph atlas under the name you give it.
Full API: Geometry, VAO, Image and Texture, and Text references.