All docs

3D Editors

The Mesh Editor for .vmesh models and scenery, the Scene Editor for .vscene sets, and the 3D Animation editor for .vskel skeletons and .vclip poses, clips and sets.

Vectrex Studio has three editors for 3D assets. All of them check the file as you edit with the compiler's own rules, undo with Ctrl+Z / Ctrl+Y, and keep any JSON fields they don't know when they save.

AssetEditorFolderCreate it with
.vmeshMesh Editorassets/meshes/ (any sub-folder)File → New → 3D Mesh
.vsceneScene Editorassets/scenes/File → New → 3D Scene
.vskel3D Animation editorassets/skeletons/File → New → 3D Skeleton
.vclip3D Animation editorassets/clips/File → New → 3D Animation

From VPy, these assets work on the uvm2 target, and on rp2350 when the program is built through C. From C they build for the simulator, rp2350 and uvm2. The builtins are in 3D in VPy.


Mesh Editor

A .vmesh is a 3D model: vertices and faces, plus optional scenery lines and occluders. It opens in the Mesh Editor.

The Mesh Editor in solid mode

Views

ViewKeyPlane
Front1X-Y
Top2X-Z
Side3Y-Z
3D4Orbiting perspective
  • The middle button pans, middle button + Shift (or Alt + drag, or the right button) orbits, and the wheel zooms to the cursor — the same controls as the Vector Editor's 3D view.
  • Free orbit turns around the screen's axes, so you can look at a model from above and below.
  • Frame (Home or H) fits the whole mesh in the view.

Display

ControlKeyWhat it shows
SolidDFaces filled, far ones first, lit from the viewer, each outlined. Faces are drawn in their own colour when the file has face_colors
Wireframe / vpy3dWWireframe shows every edge (smooth edges dashed). vpy3d shows only what the game draws from this angle — creases, silhouettes and rims — with the rest faint
Background—A grey slider, black to white, shared by every 3D view
occluders—Shows occluders as a faint orange hull (the game never draws them)

Editing vertices and faces

Tool or actionKeyUse
SelectSClick, Shift-click or drag a box to select vertices; drag to move them. Click inside a face to select it
Add vertexAClick on the view plane; new vertices join the selection in order
Make faceFA face from the selected vertices, in the order you selected them
ConnectJSplit a face along a new edge between two of its corners
DisconnectKRemove the edge between two vertices, merging the faces either side
Flip faceNReverse the winding of the selected face
DeleteDeleteDelete the selected vertices (and their faces), points or face
Wind outward—Re-wind every face anticlockwise seen from outside
Remove unused—Delete vertices no face uses
NudgeArrowsMove the selection 1 unit (10 with Shift)

Vertices also take numeric X/Y/Z in the side panel, and Snap keeps moved and added vertices on the grid in the front, top and side views.

Dissolving edges. With Select, click an edge to pick it (Shift adds more), then press Delete: the faces either side become one, keeping their colours.

Primitives

The Primitives section adds a cube, pyramid, grid or cylinder at the origin. Add puts it next to what is there; Replace replaces the whole mesh (after a confirmation).

Lofted meshes

A mesh can be lofted from cross-sections instead of listing vertices and faces: every section is a ring of points at a z, or takes its ring from a path of a .vec. Every section needs the same point count. The editor previews a lofted file read-only. Convert to vertices + faces replaces the sections with the faces they loft into, so you can edit them. Scenery lines and occluders stay editable in a lofted file.

Scenery: lines and occluders

A .vmesh can also be a piece of scenery — a room, a corridor section — with or without faces.

ToolKeyUse
LineLClick points on the view plane (or onto a vertex or point). Enter or a double-click ends the line, Esc cancels
OccluderODrag a box occluder on the front, top or side view
Box occluder from selectionBAn occluder that is the bounding box of the selected vertices and points
  • Lines are polylines of 2 to 255 points, never culled, drawn at br percent (1–100) of the mesh's brightness. closed: true joins the last point to the first.
  • Occluders are invisible convex solids of 3 to 8 corners. They hide whatever is drawn after the mesh, never the mesh's own lines.
  • Their points select, drag, nudge and delete like vertices; clicking a line or an occluder outline selects all of it.

The preview does not cut lines by occluders and does not show fog.

The format

{"name":"cube","hard":45,
 "vertices":[{"x":-500,"y":-500,"z":-500},{"x":500,"y":-500,"z":-500}, ...],
 "faces":[[0,3,2,1],[4,5,6,7], ...]}

hard is the crease angle in degrees, and open: true makes a plate instead of a solid. A scenery mesh adds lines and occluders:

{"name":"hall",
 "lines":[{"points":[{"x":-3000,"y":280,"z":1800},{"x":3000,"y":280,"z":1800}],"br":70}],
 "occluders":[{"points":[ ...3 to 8 corners of a convex solid... ]}]}

Scenery points may be 32-bit (±1,000,000); vertices keep the 16-bit range. A file with lines or occluders needs SDK v1.3.5 or later.

Using it from VPy: LOAD_MESH("cube") once, then DRAW_MESH("cube", xf, brightness) every frame — see 3D in VPy.

Using it from C: #include "cube.h" for CUBE_vmesh, then vpy3d_load_mesh(&cube, CUBE_vmesh) and vpy3d_draw_mesh(&cube, &place, 127); add $(VPY_C_SDK)/vpy3d.c to the sources — see vpy3d.


Scene Editor

A .vscene arranges .vmesh pieces into a 3D set: the ribs of a tunnel coming at you every 2000 units, two ranges of hills sliding by at different speeds, the stripes of a floor. Each frame the game passes one position, pos — how far it has gone along x, y and z — and the scene places every piece from it.

File → New → 3D Scene creates assets/scenes/<name>.vscene with one piece, the first mesh in assets/meshes, so make a mesh first.

The Scene Editor: items and groups, the tunnel preview, and the selected item

Three columns

  • Left — items and groups. The items in draw order: add, duplicate, delete, move up and down. Below them, the groups (add, rename, delete) and the scene fog (on/off, full, gone).
  • Middle — preview. Places every copy with the runtime's own arithmetic for a preview pos, set with the x/y/z sliders. ▶ (or Space) advances pos.z, so you can watch a repeat scroll and wrap. Views: Eye (a perspective from an editable point, looking along +z), front, top, side and an orbiting 3D view. Click a piece to select its item; the selected item is orange.
  • Right — the selected item. Its name, mesh (or a list of meshes), at, follow, repeat, window, brightness, fog and group.

In the preview, face edges are green (smooth edges faint), scenery lines are drawn at their brightness and occluders show as a faint orange hull. The group checkboxes in the left column form the groups mask.

Draw order and occluders

Items are drawn in the order listed. A piece's occluders hide only the items after it, so list the near pieces first. When a frame has more strokes than the stroke buffer holds, the strokes drawn last are dropped — one more reason to put far pieces last.

Item fields

FieldWhat it does
meshA .vmesh name, or a list of meshes that take turns along a repeat. At most 16 different meshes per scene
atWhere the piece stands (x, y, z; a missing axis is 0)
followHow far the piece moves per unit of pos, per axis. {"z": -1} comes at you as you advance; a fraction makes far scenery slide slowly (kept to 1/65536)
repeatcount copies step apart along axis; they wrap with the movement, so there are always count of them
windowDraw a copy only while its world z is within min..max (each optional)
brPercent (1–100) of the brightness passed to the draw call
fogtrue fades each line segment by depth; "whole" fades each copy as one, faces too; false leaves it unfaded
groupDraw only while the game sets that group's bit; "!name" the other way round. Up to 32 groups

Renaming a group renames it in every item that uses it; deleting a group makes those items always draw. Reload meshes reads the .vmesh files again after you change them. A mesh lofted from .vec sections is not previewed here: open it in the Mesh Editor.

The format

{"name":"tunnel",
 "fog":{"full":3000,"gone":17500},
 "groups":["alarm"],
 "items":[
   {"name":"ribs","mesh":"tunnel_rib","at":{"z":600},"follow":{"z":-1},
    "repeat":{"axis":"z","step":2000,"count":9},"fog":"whole"},
   {"name":"wall panels","mesh":["panels_high","panels_low"],"at":{"z":600},
    "follow":{"z":-1},"repeat":{"axis":"z","step":2000,"count":9},"window":{"max":10999}},
   {"name":"long edges","mesh":"tunnel_edges","fog":false},
   {"name":"red lights","mesh":"alarm_lights","group":"alarm"}]}

.vscene needs SDK v1.3.5 or later.

Using it from VPy: LOAD_SCENE("tunnel") once, then DRAW_SCENE("tunnel", x, y, z, brightness, groups) or DRAW_SCENE_AT(...) every frame — see 3D in VPy.

Using it from C: #include "tunnel.h" for TUNNEL_vscene and TUNNEL_vscene_meshes, then vpy3d_load_scene(&s, TUNNEL_vscene, TUNNEL_vscene_meshes) and vpy3d_draw_scene(&s, place, pos, groups, br) — see vpy3d.


3D Animation editor

.vskel skeletons and .vclip libraries of poses and clips open in one editor. (.vanim is separate: it is a flipbook of 2D frames, edited in the Animation Editor.)

The 3D Animation editor on a skeleton

The view

The view draws the character the way the console draws it: hard edges facing the camera and the outlines of smooth surfaces, with the bones in blue and the sockets in red.

  • Smooth edges shows every edge, faint. Solid fills the faces, as in the Mesh Editor, and the background-grey slider is shared with it.
  • Character picks one of the skeleton's variants. Toolbar checkboxes turn meshes, bones and sockets on and off.
  • 3D, Front, Side and Top show a character facing +z. Dragging orbits, Shift or middle drag pans, the wheel zooms, and clicking a joint selects its bone.
  • The skeleton and meshes are read from assets/skeletons and assets/meshes; ⟳ reads them again.

A skeleton: Bones, Joints, Variants

TabWhat you edit
BonesThe tree. A bone has its parent, its offset, its mesh and its turns (axis, joint, mul, div, add), plus the sockets fixed to it
JointsNames and optional limits, plus test sliders that pose the skeleton without saving anything
VariantsThe scale, and a mesh and an offset per bone. A variant's extra bones, rotations and sockets are listed but edited as text

Bones are rigid and each carries one .vmesh. Joints are single named angles that turn the bones, so a pose made for one character fits every character on the same skeleton. Sockets are named segments on a bone — what a hand holds, where a shot leaves.

{
  "version": 1,
  "angle_unit": 4096,
  "joints": [ { "name": "twist" }, { "name": "lean" }, { "name": "elbow_r" } ],
  "bones": [
    { "name": "hips",  "parent": null,   "offset": [0, 950, 0], "mesh": "hips", "rotate": [] },
    { "name": "torso", "parent": "hips", "offset": [0, 0, 0],   "mesh": "torso",
      "rotate": [ { "joint": "twist", "axis": "y" }, { "joint": "lean", "axis": "x" } ] },
    { "name": "head",  "parent": "torso", "offset": [0, 680, 0], "mesh": "head",
      "rotate": [ { "joint": "twist", "axis": "y", "mul": -1, "div": 2 } ] }
  ],
  "variants": {
    "giant": { "scale": 282, "meshes": { "head": "helmet", "torso": "armour" } },
    "small": { "scale": 72,  "meshes": { "head": "round_head" }, "offsets": { "head": [0, 640, 0] } }
  }
}

angle_unit is angles per turn; each turn's angle is joint * mul / div + add in integer arithmetic. A variant's scale is Q8 (256 = as modelled).

A clip library: Poses, Clips, Sets

TabWhat you edit
PosesThe list of poses, and one slider per joint, in degrees and in the file's own units. The editor warns when a pose is shared by several clips or sets. Clicking a joint's name finds its bone in the view
ClipsThe keys (a pose at a frame), the marks (a named instant with an optional value), the loop flag and the clip's whole-number properties
SetsPer character or style: the pose for each stance, the clip for each role (▶ plays it) and the set's own numbers

The timeline under the view plays the clip at the game's 50 frames a second (Space or ▶, slower speeds too), scrubs with a click, and moves a key when you drag it.

  • + Key at frame n adds a key at the playhead.
  • + New pose here turns what the view shows into a new pose keyed there.
  • Ghost draws the last key passed, faintly.
  • Selecting a key shows its pose's sliders.

Between keys the preview moves in straight lines; how the game eases between them is up to the game.

{
  "version": 1,
  "skeleton": "humanoid",
  "poses": {
    "guard":    { "twist": 227, "lean": 136, "elbow_r": 796 },
    "cut_wind": { "twist": -341, "lean": 56, "elbow_r": 455 }
  },
  "clips": {
    "slash": {
      "keys": [ { "frame": 0, "pose": "guard" }, { "frame": 4, "pose": "cut_wind" },
                { "frame": 12, "pose": "guard" } ],
      "loop": false,
      "props": { "damage": 1, "lunge": 60 },
      "marks": [ { "frame": 4, "name": "strike" } ]
    }
  },
  "sets": {
    "duelist": { "poses": { "guard": "guard" }, "clips": { "slash": "slash" },
                 "props": { "stance": 90 } }
  }
}

Renaming and errors

Renaming a pose, a clip, a bone or a joint renames it wherever the same file uses it. Deleting one that is still used is refused, and the message names the users. Renaming a joint in a .vskel does not rename it in the .vclip files that pose it. Errors show on the left as you edit.

Using it from VPy: import humanoid (the .vskel) or import moves (the .vclip) reads the file as constant tables — moves.T_POSES[moves.POSE_GUARD][moves.JOINT_TWIST] — and you build the character with BONE_ADD. See 3D in VPy.

Using it from C: #include "gen/moves.h" (built with vpy_cli compile-asset moves.vclip --format c; a new C project's Makefile has the rule) gives the same tables with the library's prefix, MOVES_T_POSES[MOVES_POSE_GUARD][MOVES_JOINT_TWIST], for vpybone — see Animation.