← Cycles
Qmap · c23

custom-obj-models

activecreated 2026-04-17· last activity 2026-08-15

Validate import, normalization, and rendering of custom `.obj` actor models in qmap

New in this cycle: MagicaVoxel .obj actor models — palette-aware import and lighting studies

Why

The current actor meshes are intentionally simple geometric stand-ins. They have been good enough for layout, grouping, and flow experiments, but they do not answer the next visual question: how well does qmap hold up when actors are represented by real 3D assets with irregular silhouettes, custom proportions, and more demanding lighting?

This cycle exists to prove the full path from a hand-authored .obj file to an in-scene qmap actor. It should also answer a second design question: what combination of material treatment and lighting makes custom models feel readable in qmap's isometric scene without losing the crisp, diagrammatic quality of the map.

This is adjacent to:

  • c2.entity-meshes, which established the current primitive-mesh vocabulary
  • c14.actor-expansion, which expanded actor types but still used procedural geometry

Objective

One runnable experiment that:

  • Loads at least one custom .obj file into the scene as an actor mesh
  • Supports a round-trip asset workflow where the supplied test model can be swapped and reloaded without changing scene logic
  • Compares a small set of lighting presets in the same scene
  • Compares at least three color/material strategies for imported models
  • Identifies the minimum reusable abstraction needed to bring custom-model actors into mainline qmap without destabilizing existing primitive actors

Scope

  • dev/experiments/c23.custom-obj-models/ExperimentsApp.vue — dedicated model-import and look-dev experiment
  • dev/experiments/c23.custom-obj-models/shared/ — asset manifest, fixture actors, lighting/material presets, and helpers
  • dev/experiments/c23.custom-obj-models/assets/ — local test .obj files and optional companion .mtl files or textures
  • inbox/vox-exports-2/ — source MagicaVoxel export set provided for import validation
  • src/entities/ — only if the experiment proves a reusable CustomModelMesh or equivalent factory seam is warranted
  • cycles/c23.custom-obj-models/ — cycle metadata

Deliverables

  • Runnable experiment
    • dev/experiments/c23.custom-obj-models/ExperimentsApp.vue
  • Asset manifest + fixtures
    • One or more actors configured to load the supplied test model
    • Lighting preset definitions
    • Material/color strategy definitions
  • Cycle metadata
    • cycles/c23.custom-obj-models/CYCLE.md
    • cycles/c23.custom-obj-models/initial-prompt.md

Optional if the experiment proves it out:

  • Reusable model mesh abstraction
    • Example: src/entities/CustomModelMesh.js
  • Configurable lighting seam
    • Example: experiment-only wrapper first, then a narrow QMap prop if it earns promotion

Non-Goals

  • Full asset-pipeline generalization for every 3D format
  • Skeletal animation, rigging, or animated model playback
  • Production-ready asset compression or optimization tooling
  • Replacing all existing actor types with imported models during this cycle
  • Final art direction for the entire product

Asset Contract To Validate

The experiment should validate a small, explicit contract for imported models. An actor should be able to declare something close to:

{
  id: 'actor-1',
  type: 'app',
  label: 'Custom App',
  model: {
    kind: 'obj',
    src: '/dev/experiments/c23.custom-obj-models/assets/test-model.obj',
    mtl: null,
    scale: 1,
    recenter: true,
    normalizeSize: true,
    rotation: [0, 0, 0],
    offset: [0, 0, 0],
    materialMode: 'override',
  },
}

The exact shape can change, but this cycle should settle the important decisions:

  • where the model descriptor lives
  • how scale/origin normalization works
  • when source materials are respected vs overridden
  • how imported meshes inherit qmap state colors and opacity changes

Provided Asset Set

Primary source assets for this cycle now live in:

  • inbox/vox-exports-2/

Current inventory:

  • Abstract Monsters.obj + .mtl + .png
  • Bob Wire (Cowboy).obj + .mtl + .png
  • Bob Wire (Sheriff).obj + .mtl + .png
  • Eye Monsters.obj + .mtl + .png
  • Helpers.obj + .mtl + .png
  • Intern.obj + .mtl + .png
  • Manager.obj + .mtl + .png
  • Monsters.obj + .mtl + .png
  • Mouse&Pointer.obj + .mtl + .png
  • Puzzle.obj + .mtl + .png
  • Receptionist.obj + .mtl + .png
  • Robot.obj + .mtl + .png

Deferred variant exports now present in the same folder but intentionally excluded from the stable 12-asset manifest for this pass:

  • Intern Type1/2/3
  • Receptionist Hair/Bald
  • Mouse
  • Pointer
  • Abs Mon Green/Purple/White
  • Eye Mon Green/Purple/Red
  • Monster Green/Purple/Red

Observed characteristics of this export set:

  • Exporter: MagicaVoxel
  • Material shape: one palette material per model
  • Texture shape: one 256x1 RGBA palette PNG per model
  • Geometry character: low-poly voxel surfaces with very few UV rows and only six normal directions
  • Filename character: several assets include spaces and parentheses, so path handling must be tested directly rather than normalized away

Initial rendering implications:

  • preserve palette crispness with nearest-neighbor texture filtering
  • treat .mtl + palette texture loading as part of the baseline path, not a stretch goal
  • normalize scale and origin per asset because sizes vary significantly across the set
  • verify whether imported assets sit on the ground plane consistently; at least one asset (Robot.obj) appears slightly elevated above y = 0

Asset-Specific Rendering Plan

This cycle should not treat the provided files as generic placeholders. Use the set intentionally, in passes that answer different questions.

Pass 1: Baseline Import Pair

Start with two simpler human-scale assets:

  • Manager.obj
  • Bob Wire (Sheriff).obj

Why these first:

  • compact geometry
  • lower face counts
  • easy to visually inspect for orientation, palette sampling, and label placement
  • good first test for whether the imported-model wrapper feels comparable to existing actor scale

Goal of this pass:

  • prove OBJLoader + MTLLoader + palette PNG loading
  • confirm upright orientation and ground contact
  • verify nearest-filter palette rendering

Pass 2: Hero Character Pair

Promote to more detailed representative assets:

  • Receptionist.obj
  • Robot.obj

Why these next:

  • more visually distinctive silhouettes
  • higher geometric complexity
  • likely good candidates for presentation-grade screenshots
  • strong test for lighting, especially readable front/side planes and shadow noise

Goal of this pass:

  • tune normalization and scale rules on larger assets
  • compare source-material vs override-material strategies
  • validate whether the current label offset logic still works on taller meshes

Pass 3: Stylized / Non-Humanoid Pair

Use expressive shape tests:

  • Puzzle.obj
  • Mouse&Pointer.obj

Why these matter:

  • less character-like silhouettes
  • likely useful for non-human actor types such as tools, devices, or abstract system objects
  • good test of whether qmap can support custom-model actors beyond a single visual family

Goal of this pass:

  • verify that custom models still read as qmap actors when the silhouette is unusual
  • test whether outline/edge support becomes necessary for flatter or more horizontal forms

Pass 4: Stress / Texture Cohesion Set

Use the monster family as a batch:

  • Abstract Monsters.obj
  • Eye Monsters.obj
  • Monsters.obj

Optional additions if useful:

  • Helpers.obj
  • Intern.obj
  • Bob Wire (Cowboy).obj

Why these last:

  • they provide several related assets with different massing and palette usage
  • they are well suited for multi-actor scenes where we compare coherence across several imported meshes at once

Goal of this pass:

  • test scene readability when multiple custom assets coexist
  • compare whether source palettes create too much scene noise
  • evaluate grouped scenes rather than one-off hero placement

Asset Normalization Rules To Prove

The provided assets suggest a concrete normalization checklist:

  1. Load .mtl before .obj so palette textures resolve cleanly.
  2. Apply nearest filtering to palette textures to preserve voxel sharpness.
  3. Compute bounding box per asset and recenter in x/z.
  4. Drop the asset to the stage by aligning min y to ground level.
  5. Normalize by target height or max dimension so Manager, Robot, and Intern can share the same actor slot without manual scene edits.
  6. Preserve an actor-level override for rotation, because exporter axes may still differ on future drops.

Experiment Structure For These Assets

The experiment should include three small controls specific to the provided asset set:

  • asset picker: switch among the twelve supplied exports
  • render mode: source, override, hybrid tint
  • lighting preset: baseline, studio, dramatic, flat-readable

Optional but useful:

  • compare mode: primitive placeholder vs imported asset
  • multi-asset scene toggle: one hero actor vs several imported actors together

The asset manifest for this experiment should store, per file:

  • source paths for .obj, .mtl, and .png
  • display label
  • intended actor type for qmap
  • any per-asset rotation/offset overrides discovered during setup
  • notes on whether the asset is a baseline, hero, or stress-test candidate

Runtime Session Follow-on

The next c23 branch should duplicate the c22 runtime-session experiment and use it as the first integration sandbox for imported models inside a real qmap topology.

Goal:

  • keep c22's successful runtime-session controls and fixtures intact
  • create a second c23 page dedicated to runtime-session model substitution
  • replace primitive runtime actors incrementally rather than all at once

Recommended implementation path:

Phase A: Baseline Duplicate

  • duplicate the c22 runtime-session experiment into a second c23 page
  • keep all current session fixtures, grouping controls, and configurator wiring
  • add a visible model-substitution plan panel to the page

Primary output:

  • a stable before/after lab where runtime-session behavior can be compared without disturbing c22

Phase B: Root Process Substitution

  • add model-backed actor support at the actor level
  • replace only the root process first
  • initial candidate: Manager.obj

Why first:

  • one actor only
  • easiest to visually validate scale, orientation, and selection behavior
  • already matches the conservative runtime/inventory integration that is currently shipping

Phase C: Child Process Substitution

  • replace child processes selectively, not every runtime actor at once
  • initial candidates:
    • Puzzle.obj for app-like subprocesses
    • Helpers.obj for lighter utility/process roles

Questions to answer:

  • do imported subprocess models remain legible when grouped densely?
  • do labels still anchor clearly when several model-backed actors occupy one rug?

Phase D: External Host Substitution

  • test a small host family using the monster assets
  • initial candidates:
    • Monsters.obj
    • Eye Monsters.obj
    • Abstract Monsters.obj

This is intentionally stylized and may or may not survive promotion. The point is to stress silhouette diversity in the runtime-session map, not to lock final semantics.

Phase E: File Actor Decision

  • keep file actors primitive in the first integration pass
  • revisit file substitution only after root/process/host model behavior is proven

Reason:

  • files are the densest actor class in c22 fixtures
  • primitive file meshes currently support readability better than arbitrary imported forms
  • model-backed files could easily reduce map legibility before we learn anything useful

Runtime Session Model Mapping Proposal

First-pass mapping:

  • Root process (role: root) → Manager.obj
  • Child process (type: app) → Puzzle.obj
  • Optional utility subprocess variant → Helpers.obj
  • External host (type: external-host) → monster family (Monsters, Eye Monsters, Abstract Monsters)
  • File (type: file) → keep existing primitive file mesh for now

Follow-on candidate to revisit after the refreshed asset set settles:

  • Robot.obj as a more dramatic root-process hero model if it proves readable without destabilizing the rug composition

Success criteria for this follow-on:

  • the duplicated c23 runtime-session page matches c22 behavior before model substitution begins
  • root process substitution works without breaking selection or layout
  • at least one non-root imported actor type remains readable under entity-type grouping
  • the experiment ends with a clear recommendation on which runtime actor classes should stay primitive

Replay-oriented synthetic live-session work has been split into c24.synthetic-live-session-replay so this cycle can stay focused on imported model loading, look-dev, and conservative runtime-session substitution.

Visual Study Areas

Lighting

Test a small number of intentionally different presets rather than exposing a dozen unrelated knobs. Initial candidates:

  1. Current baseline: ambient + directional
  2. Soft studio: hemisphere + key + fill
  3. Dramatic diagram: low ambient + strong key + rim
  4. Flat readable: bright ambient-forward lighting for maximum label/model clarity

For each preset, judge:

  • silhouette legibility
  • face readability at qmap's normal camera angle
  • compatibility with labels and dataflows
  • whether shadows help or just add noise

Material / Color Strategy

Test at least these approaches:

  1. Source material: use model-authored material/color if present
  2. qmap override: replace with qmap-controlled MeshStandardMaterial
  3. Hybrid tint: preserve value/roughness feel but tint to actor/state color

Questions to answer:

  • Can imported models still communicate actor type/state as clearly as primitives?
  • Does one shared material improve diagram coherence?
  • Do per-model source colors fight the rest of the map?

Key Questions

  1. What is the thinnest integration seam: experiment-local loader, reusable CustomModelMesh, or extension to createEntity()?
  2. How should .obj models be normalized so different authoring tools do not produce wildly different scale/origin results?
  3. Should qmap treat imported meshes as fully authored objects, or as geometry that still receives qmap-owned materials and state styling?
  4. Which lighting preset preserves readability best from the default isometric camera?
  5. Does the main QMap.vue lighting setup need to become configurable, or should custom-model lighting live in a specialized wrapper/experiment first?
  6. What round-trip workflow is easiest when the user iterates on the same test model repeatedly?

Success Criteria

  • A supplied .obj file loads reliably in a qmap experiment
  • The imported model can be swapped for a revised version without changing core experiment code
  • At least one material strategy preserves qmap state readability (default, active, ignored, highlighted)
  • At least one lighting preset produces clearly readable silhouettes and surfaces at normal zoom
  • The cycle ends with a concrete recommendation for the reusable abstraction, if any
  • Existing primitive actors remain untouched unless a narrow, low-risk extension is clearly justified

Implementation Plan

Phase 1: Loader Spike

  • Add one experiment-local path for loading .obj + .mtl + palette PNG assets
  • Load Manager.obj into an otherwise minimal scene first
  • Add Bob Wire (Sheriff).obj immediately after as a second sanity check
  • Normalize its origin, scale, and upright orientation
  • Capture the exact metadata needed for reliable reloads

Primary output:

  • proof that the MagicaVoxel asset set can be loaded, centered, scaled, and rendered consistently

Phase 2: Actor Integration

  • Replace one primitive actor with Manager.obj or Receptionist.obj inside a normal qmap actor/dataflow scene
  • Preserve existing label placement, selection, and state transitions
  • Decide whether imported-model actors should wrap BaseMesh, extend it, or sit beside it

Primary output:

  • one imported model functioning as a first-class qmap actor

Phase 3: Material Strategy Study

  • Implement source, override, and hybrid material modes for the palette-textured assets
  • Verify state coloring/opacity transitions still read clearly
  • Compare whether edges/outlines are needed for irregular geometry

Primary output:

  • a recommended default material strategy for diagram contexts

Phase 4: Lighting Study

  • Implement a small preset switcher in the experiment
  • Compare readability across the same scene and camera using Receptionist, Robot, Puzzle, and one monster asset
  • Decide whether shadows, rim light, or hemisphere lighting materially improve the result

Primary output:

  • one recommended lighting preset and one acceptable fallback

Phase 5: Round-Trip Workflow

  • Make the supplied asset set easy to replace in-place with revised exports from the same toolchain
  • Verify reloading behavior during iteration
  • Document any naming, axis, palette, or export conventions that make the loop smoother

Primary output:

  • a repeatable designer-to-qmap asset workflow

Phase 6: Promotion Decision

  • Decide what, if anything, should move from experiment code into src/
  • Keep promoted API surface narrow
  • Park anything that is interesting but not yet stable

Primary output:

  • explicit recommendation: experiment-only, narrow promotion, or no promotion yet

Notes For Implementation

  • Start with experiment-local code first; promote only after the shape is proven
  • Prefer OBJLoader + MTLLoader from Three's examples before inventing a custom parser
  • Keep a primitive fallback path for comparison
  • Default to qmap-owned materials unless source materials clearly outperform them
  • For this asset set, treat .mtl support as baseline because the palette PNG is part of the authored look
  • Expose a small set of lighting presets, not raw light-object editing UI
  • Use inbox/vox-exports-2/ as the primary validation asset set

Visual record

13 captures — heroes, page slices, and full-page renders. Click to view.

The Manager model, source palette, baseline light
The Manager model, source palette, baseline light
Manager under dramatic lighting
Manager under dramatic lighting
Bob Wire (Cowboy) — studio light
Bob Wire (Cowboy) — studio light
Bob Wire (Sheriff)
Bob Wire (Sheriff)
The monster set assembled
The monster set assembled
Monsters with override material — silhouette study
Monsters with override material — silhouette study
Intern types, hybrid tint, flat-readable light
Intern types, hybrid tint, flat-readable light
Eye Mon Red — source palette back on
Eye Mon Red — source palette back on
Runtime lab: Mixed Runtime with vox actors
Runtime lab: Mixed Runtime with vox actors
Soft Lit render mode
Soft Lit render mode
Overdrive Runtime, dramatic lighting
Overdrive Runtime, dramatic lighting
Network Heavy, true color
Network Heavy, true color
c23 hero

Artifacts

/experiments/c23.custom-obj-models/ (local dev)/experiments/c23.custom-obj-models/runtime-session-map.html (local dev)

Qpoint Brand Style Guide