Skip to content

feat(mesh): multi-material OBJ rendering#1465

Merged
obiot merged 13 commits into
masterfrom
feat/multi-material-mesh
May 26, 2026
Merged

feat(mesh): multi-material OBJ rendering#1465
obiot merged 13 commits into
masterfrom
feat/multi-material-mesh

Conversation

@obiot
Copy link
Copy Markdown
Member

@obiot obiot commented May 26, 2026

Summary

  • OBJ parser now emits groups: Array<{materialName, start, count}> matching the Three.js / glTF convention — every usemtl switch opens a new group slicing the shared index buffer.
  • Mesh constructor bakes each material's Kd diffuse color into a per-vertex color buffer at construction time. Vertices on material boundaries are split during parsing so every vertex carries exactly one material's color.
  • WebGL & Canvas renderers draw multi-material meshes in a single draw call — per-vertex aColor (WebGL) / per-triangle solid-fill from vertexColors[v0] (Canvas) supplies the right color per face. mesh.tint multiplies on top at render time, so flash / fade / team-color via setTint work exactly as today.
  • New Multi-material OBJ example loads four Kenney Space Kit spacecraft (CC0, credited in LICENSE.md), gives each one a team color via mesh.tint, and rotates them in a 2×2 grid.

Single-material meshes are unchanged — same code path, zero new allocations, identical behavior.

Why

melonJS's Mesh previously bound one material per mesh — multi-material OBJs (Kenney packs, Blender exports, anything with painted panels) collapsed to a single uniform color because the parser threw away usemtl boundaries. This change adds proper per-material rendering without giving up the engine's single-draw-call goal.

Implementation notes

  • Parser: per-material vertex dedup. Each usemtl resets the active vertexMap so shared (v, vt) pairs across materials get distinct unified-vertex slots — required for per-vertex color baking. Pre-usemtl faces go into an anonymous null-material group so the groups[] contract is always non-empty for non-empty OBJs.
  • Mesh: isMultiMaterial gate keeps the existing single-material code path untouched. When triggered, allocates a vertexColors: Uint32Array and fills it by walking each group's index slice. mesh.groups stays exposed for inspection; the per-group tint field is informational under tier 2 (post-construction mutation is a no-op since colors are baked).
  • WebGL mesh batcher: new mulPackedARGB helper combines per-vertex baked color with the runtime tint (matches Color.toUint32 packing). Single-material meshes (no vertexColors) take the unchanged fast path.
  • Canvas renderer: when vertexColors is present, per-triangle solid-fill reads vertexColors[v0] (all 3 vertices of a triangle share a material color by construction) × currentTint. Single global painter's sort — no inter-group ordering glitches. Per-material textures aren't supported on Canvas (use WebGL for that case).
  • MTL parser: dropped the now-obsolete "multiple materials detected — only the first material's texture will be used per mesh" warning, and switched the remaining warnings from the deprecation helper warning() (which formats as "deprecated since version undefined") to plain console.warn.
  • Backward compat: single-material OBJs still produce a length-1 groups array so consumers don't need a special case. Existing mesh3d / mesh3dMaterial examples render identically to before.

Test plan

  • 8 new parser tests covering single-material default group, multi-usemtl emission, anonymous null-material chunk for pre-usemtl faces, triangulated quads inside groups, winding-fix preserving boundaries, empty OBJ, trailing usemtl with no faces
  • All 3565 tests pass (was 3558 → +7 new)
  • Existing mesh3d + mesh3dMaterial examples render identically — visually verified
  • New multi-material-mesh example renders correctly on both WebGL and Canvas; all 4 ships show distinct per-material colors through the full rotation
  • mesh.tint runtime multiplication works on top of baked colors (team colors in the example)
  • Local browser FPS measurement (Canvas vs WebGL) — defer; Canvas multi-material is correct but not perf-critical, WebGL is the recommended path

🤖 Generated with Claude Code

obiot and others added 5 commits May 26, 2026 07:45
OBJ files with multiple `usemtl` directives (Kenney asset packs,
Blender / Maya exports, any model with painted panels) previously
rendered as a single uniform color in melonJS because `Mesh` bound
one material per mesh and the parser threw away `usemtl` boundaries.

OBJ parser now emits `groups: Array<{materialName, start, count}>`
alongside the existing geometry — each entry is a contiguous slice of
the shared index buffer that draws as one submesh against one
material. Field shape (`start`, `count`, plus a name pointing into a
material table) matches the Three.js / glTF "groups" convention so the
structure is familiar to anyone porting from those engines. Single-
material OBJs still produce a `groups` array of length 1, so
downstream code doesn't need a special case.

`Mesh` detects multi-material OBJs (multiple groups + at least one
named + an MTL bound via `material:`) and builds a per-group descriptor
carrying texture, tint, and opacity resolved from each named MTL
entry. `Mesh.draw()` iterates the descriptors, swapping
`renderer.currentTint` + `mesh.texture` per draw call — one draw per
material region, all sharing the same projected vertex buffer (no
geometry duplication, no per-frame allocation).

`renderer.drawMesh(mesh, group?)` gains an optional group argument
(matches Three.js's `renderer.renderBufferDirect(..., group)`) — when
provided, only the index slice `[group.start, group.start + group.count)`
is pushed to the GPU. WebGL and Canvas renderers updated. Depth clear
moved to "first group only" so per-material draws within one mesh
compose against each other for correct cross-material occlusion.

Kd-only Kenney-style models without `map_Kd` get a shared 1×1 white
texture fallback so the GPU pipeline still has something to sample —
the per-group `tint` does all the visible coloring. Allocated lazily
on first use, shared across every Mesh that needs it.

8 new parser tests cover: single-material default group, multi-usemtl
emission, anonymous null-material chunk for pre-usemtl faces,
triangulated quads inside groups, winding-fix preserves boundaries,
empty OBJ, trailing usemtl with no faces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Loads four Kenney Space Kit (CC0) spacecraft (craft_speederA / B /
racer / miner) and renders them rotating in 3D in a 2x2 grid. Each
spacecraft has 3-5 `usemtl` material groups (metal / metalRed / dark /
metalDark) — the new groups[] OBJ parser API + Mesh.draw per-group
iteration paint each panel with its correct Kd color, instead of
collapsing the whole model into one flat tint.

Compare with the existing mesh3d example (single texture across the
whole mesh): same Mesh class, no extra wiring beyond passing the MTL
name through `material:`. The multi-material code path activates
automatically when the OBJ has multiple `usemtl` directives + a
matching MTL is bound.

LICENSE.md updated to credit Kenney (CC0 — attribution not legally
required, included as a courtesy, mirroring the pool-matter pool-
table assets pattern).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tier 2 of the multi-material work: bake each material's Kd color into
a per-vertex `aColor` attribute at construction time, then render the
whole mesh in ONE draw call regardless of material count. Replaces the
prior per-group draw iteration (one drawElements + texture bind per
material) — same correct result, ~N× less GL state churn on WebGL,
and Canvas no longer needs a multi-material code path at all.

OBJ parser: per-material vertex dedup. Each `usemtl` switch resets the
active `vertexMap`, so vertices shared across materials get separate
slots in the unified vertex buffer. Required for per-vertex color
baking — a vertex shared between two materials needs to carry both
colors.

Mesh constructor: when isMultiMaterial, allocate `vertexColors`
(Uint32Array, one packed ARGB per vertex) and fill it by walking each
group's index slice, assigning the group's Kd to every vertex it
references. `mesh.groups` stays exposed for inspection; the `.tint`
field is now informational (post-construction mutation is a no-op
since colors are already baked). Runtime mutation happens through
`mesh.tint` instead — multiplies on top of every baked color via the
shader's existing `aColor` path.

WebGL mesh batcher: new `mulPackedARGB` helper combines each vertex's
baked color with the runtime `tint` (matches the ARGB packing from
`Color.toUint32`). Single-material meshes (no `vertexColors`) take the
unchanged fast path — same `tint` for every vertex push.

WebGL renderer: reverted `drawMesh(mesh, group?)` back to single-
arg `drawMesh(mesh)`. State setup runs once, batcher handles per-
vertex color from the mesh.

Canvas renderer: same single-arg signature. When `vertexColors` is
present, per-triangle solid-fill reads `vertexColors[v0]` (all 3
vertices of a triangle share a material color by construction)
multiplied by `currentTint`. One global painter's sort — no inter-
group ordering glitches like the per-group approach had. Per-material
textures aren't supported on Canvas (would need per-group passes); use
WebGL for that case.

Canvas also gains a 1×1 solid-fill fast path for Kd-only single-
material meshes (a user could load an OBJ with one material whose MTL
sets only `Kd`) — previously the affine drawImage produced sub-pixel
artifacts at large triangle scale, now it solid-fills with the tinted
color.

MTL parser: dropped the obsolete "multiple materials detected — only
the first material's texture will be used per mesh" warning (no longer
true after tier 2), and switched the remaining MTL warnings from the
deprecation helper `warning()` (which formats as "is deprecated since
version undefined, please use undefined") to plain `console.warn`.

Single-material meshes: zero behavior change, zero perf change. The
multi-material path is gated on `isMultiMaterial` so nothing allocates
or branches for the common single-material case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Canvas multi-material rendering is correct but does per-triangle
solid-fill in JS — 10-50× slower than the GPU rasterizer for the same
scene. Force WebGL at the Application constructor so users opening the
example see usable frame rates. Canvas remains supported in the engine
for fallback / debug / correctness purposes; just not the default for
this example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Restructure example into named sections (layout / entry point /
  craft renderable / scene helpers) with extracted constants
  (CANVAS_W, CANVAS_H, MESH_SIZE, ROW_Y) — same behavior, easier to
  read at a glance
- Trim verbose inline comments to one short sentence each; remove
  stale references to "per draw call" / tier-1 framing
- Restate the file header in tier-2 terms: per-vertex color baking +
  single GPU draw, with `mesh.tint` as the runtime multiplier
- Gallery description shifts from asset-attribution + implementation
  framing to engine-feature framing: "Rotating 3D models with
  multiple materials and per-mesh tinting — each material region
  picks up its diffuse color from the .mtl file, multiplied by a
  runtime tint." Matches the tone of the neighboring 3D Mesh / 3D
  Material gallery cards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 26, 2026 02:33
Add the per-vertex baked color path + single-draw-call multi-material
to the 19.7.0 unreleased section. Documents the OBJ parser groups[]
emission, Mesh's per-vertex color baking, the renderer single-draw
path (WebGL aColor / Canvas solid-fill), and runtime tinting on top.
Calls out single-material backward compatibility (unchanged) and the
new Multi-material OBJ showcase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds multi-material support for OBJ meshes by extending the OBJ parser to emit groups[] (per-usemtl index ranges) and updating mesh rendering to support per-vertex baked diffuse colors, plus a new example and expanded test coverage.

Changes:

  • OBJ parser now emits groups: Array<{ materialName, start, count }> tracking usemtl boundaries (with an anonymous null group fallback).
  • Mesh can bake per-material diffuse (Kd) + opacity into a vertexColors: Uint32Array, and WebGL/Canvas mesh rendering paths use these colors.
  • Adds a new “Multi-material OBJ” example (plus Kenney CC0 assets) and new parser tests covering group emission edge cases.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/melonjs/tests/mesh.spec.js Adds OBJ parser tests for groups[] emission and boundary edge cases.
packages/melonjs/src/video/webgl/webgl_renderer.js Updates drawMesh documentation to describe multi-material behavior.
packages/melonjs/src/video/webgl/batchers/mesh_batcher.js Adds per-vertex color modulation (vertexColors × tint) when present.
packages/melonjs/src/video/canvas/canvas_renderer.js Adds a solid-fill path for meshes with vertexColors (and 1×1 Kd fallback textures).
packages/melonjs/src/renderable/mesh.js Adds groups resolution + baked vertexColors, and a 1×1 white-pixel texture fallback.
packages/melonjs/src/loader/parsers/obj.js Adds usemtl parsing and emits groups[]; resets dedup scope per material.
packages/melonjs/src/loader/parsers/mtl.js Removes obsolete multi-material warning and switches warnings to console.warn.
packages/examples/src/main.tsx Registers the new “Multi-material OBJ” example route/entry.
packages/examples/src/examples/multiMaterialMesh/ExampleMultiMaterialMesh.tsx New example showcasing multi-material OBJ color baking + runtime mesh.tint.
packages/examples/public/assets/multiMaterialMesh/craft_speederB.obj New Kenney CC0 OBJ asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_speederB.mtl New Kenney CC0 MTL asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_speederA.obj New Kenney CC0 OBJ asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_speederA.mtl New Kenney CC0 MTL asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_racer.obj New Kenney CC0 OBJ asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_racer.mtl New Kenney CC0 MTL asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_miner.obj New Kenney CC0 OBJ asset for the example.
packages/examples/public/assets/multiMaterialMesh/craft_miner.mtl New Kenney CC0 MTL asset for the example.
packages/examples/LICENSE.md Adds CC0 credit for the Kenney Space Kit assets used by the new example.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/renderable/mesh.js
Comment thread packages/melonjs/src/loader/parsers/obj.js Outdated
Comment thread packages/melonjs/src/video/webgl/batchers/mesh_batcher.js Outdated
Comment thread packages/melonjs/src/loader/parsers/mtl.js Outdated
Comment thread packages/melonjs/src/video/canvas/canvas_renderer.js Outdated
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/video/canvas/canvas_renderer.js
obiot and others added 2 commits May 26, 2026 10:40
Two outdated MTL parser comments removed:
- Function header claimed "Multiple materials per mesh (`usemtl`) are
  not supported — only the first material is used" — now false since
  Mesh resolves per-material colors / textures via the OBJ `groups[]`
  emitted by the parser.
- "(was: warn on multi-material — obsolete…)" inline comment near
  the `case "newmtl":` block — pure archaeology referencing a removed
  warning; the current code is self-explanatory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two PR review threads pointed out that the multi-material work added
bare `document.createElement("canvas")` calls in Mesh and the Canvas
renderer — these throw in OffscreenCanvas / worker contexts where
`document` is undefined.

Introduce `Renderer.getWhitePixel()` — a lazily-created, shared 1×1
white canvas using `OffscreenCanvas` where supported and falling back
to `document.createElement` otherwise. Static (not instance-bound) so
it's reachable from `Mesh` construction before any active renderer
exists.

Mesh's `isMultiMaterial` Kd-only path now uses
`Renderer.getWhitePixel()` instead of its own local
`getOrCreateWhitePixel()`; CanvasRenderer's `_meshColorCanvas`
scratch sampler now goes through the same OffscreenCanvas-aware
allocation pattern.

Kept inline (not via `CanvasRenderTarget`) — that abstraction is
sized for full-blown render targets with their own GL context, way
heavier than what a 1x1 fallback needs. Also kept off the `video`
namespace — `video.createCanvas` is on a deprecation path; canvas
allocation is renderer-side concern now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 26, 2026 02:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (1)

packages/melonjs/src/video/canvas/canvas_renderer.js:594

  • The rawDet === 0 (degenerate UV) fallback path still allocates _meshColorCanvas via document.createElement("canvas"). In worker/OffscreenCanvas contexts (which the earlier solid-fill path now explicitly supports), document is undefined and this will throw. Consider using the same globalThis.OffscreenCanvas-aware allocation approach here as well.
			} else if (rawDet === 0) {
				// degenerate UV triangle — sample a solid color from the texture
				// (common with color-palette models where all 3 UVs map to the same point)
				context.closePath();
				if (!this._meshColorCanvas) {
					this._meshColorCanvas = document.createElement("canvas");
					this._meshColorCanvas.width = 1;
					this._meshColorCanvas.height = 1;
					this._meshColorCtx = this._meshColorCanvas.getContext("2d");
				}

Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/renderable/mesh.js
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/src/loader/parsers/obj.js Outdated
Comment thread packages/melonjs/src/loader/parsers/obj.js
Comment thread packages/melonjs/src/video/webgl/batchers/mesh_batcher.js Outdated
Comment thread packages/melonjs/CHANGELOG.md Outdated
obiot and others added 2 commits May 26, 2026 11:14
Promotes canvas allocation from the `video` namespace (which is on a
deprecation path) to a static `Renderer.createCanvas(width, height,
returnOffscreenCanvas)` — the natural home for canvas-related
utilities that the renderer-side of the engine needs.

`Renderer.getWhitePixel()` now routes through it; CanvasRenderer's
multi-material 1×1 scratch sampler, CanvasRenderTarget, and TMXLayer
all migrated to `Renderer.createCanvas` (no deprecation warnings from
internal code).

`video.createCanvas` lives on as a deprecated forwarder in
`lang/deprecated.js` and is re-exported from `video.js` so existing
`video.createCanvas(...)` call sites keep working with a runtime
deprecation warning until users migrate. Standard
`warning("video.createCanvas", "Renderer.createCanvas", "19.7.0")`
notice, matching the existing CanvasTexture / setLineWidth patterns.

Also addresses several Copilot review findings in passing:
- Multi-material `Mesh`: `this.tint` no longer adopts the first
  group's color (avoids double-multiplying the baked vertexColors
  against `mesh.tint` at draw time)
- Single-material `Mesh`: now picks the MTL entry named by
  `objGroups[0].materialName` when present, falling back to the first
  entry only when no `usemtl` is set (fixes wrong-material selection
  for OBJs whose `usemtl` targets a non-first MTL entry)
- Doc fixes: `resolveGroupMaterial` JSDoc field rename
  (`material` → `materialName`), `Mesh.draw` comment retired the
  stale tier-1 "one draw per groups[]" framing, obj.js docstring
  consistent `materialName: null`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous version routed the deprecation forwarder through
`deprecated.js`, which inadvertently exposed `createCanvas` as a
top-level melonJS export via the blanket
`export * from "./lang/deprecated.js"` re-export — but `createCanvas`
was never a top-level export, only `video.createCanvas`. Polluting the
top level for a never-top-level symbol breaks the implicit "deprecated
symbols stay at the API position they had before deprecation" rule
the existing entries (`CanvasTexture`, `Compositor` family) all
follow.

Move the implementation back to `video.js` (with the same `warning(
"video.createCanvas", "Renderer.createCanvas", "19.7.0")` notice and
forwarding to `Renderer.createCanvas`). `deprecated.js` no longer
exports it — only `video.createCanvas` is exposed, matching the
original API surface.

Existing top-level deprecated classes (`CanvasTexture`, `Compositor`,
`PrimitiveCompositor`, `QuadCompositor`) keep their top-level slots
since those genuinely were top-level exports before being deprecated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 26, 2026 03:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 7 comments.

Comment thread packages/melonjs/src/video/renderer.js Outdated
Comment thread packages/melonjs/src/video/renderer.js
Comment thread packages/melonjs/src/video/canvas/canvas_renderer.js Outdated
Comment thread packages/melonjs/src/video/canvas/canvas_renderer.js
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/CHANGELOG.md Outdated
Comment thread packages/melonjs/src/loader/parsers/mtl.js Outdated
obiot and others added 2 commits May 26, 2026 11:41
…, doc accuracy

Four findings from the second Copilot review pass:

1. **Single-material texture fallback** — `resolveTextureAtlas(undefined)`
   threw when a single-material `Mesh` was constructed without a
   `texture:` AND without a `material:` (or a `material:` whose MTL
   had no `map_Kd`). The white-pixel fallback was gated on
   `isMultiMaterial`, missing this case. Drop the gate — any path
   that ends with `textureSource` unresolved now falls through to
   `Renderer.getWhitePixel()`, GPU pipeline always has a sampler
   binding.

2. **Per-material vertex dedup cache** — the OBJ parser's
   `startGroup()` reset the vertex `Map` on every `usemtl`. For OBJs
   that interleave materials (`usemtl red ... usemtl blue ... usemtl
   red`), the second "red" block couldn't reuse its earlier
   deduplicated vertices, producing unnecessary duplication. Switch
   to a per-material `Map` keyed by `materialName`; same material
   reappearing hits its existing cache. Same vertex still gets
   distinct slots across DIFFERENT materials (the prerequisite for
   per-vertex color baking).

3. **`mesh_batcher.addMesh` doc wording** — said the shared tint was
   "multiplied per-vertex in the shader". Wrong — it's CPU-side via
   `mulPackedARGB` before `pushMesh`. Mesh shader just does
   `texture * aColor`. Fixed.

4. **CHANGELOG scope claim** — claimed per-material textures
   (each material with its own `map_Kd`) were supported via the
   per-vertex `aColor` path. Misleading — `aColor` carries color,
   not textures. Tier 2 supports per-material COLORS only; the whole
   mesh shares a single texture binding. Clarified the scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. **canvas_renderer**: skip `cache.tint(image, ...)` when
   `vertexColors` is present — the solid-fill path reads color from
   the baked buffer per triangle and never samples the image, so
   tinting the image is wasted work + allocation per frame.

2. **renderer.createCanvas**: gate the OffscreenCanvas path on
   `device.offscreenCanvas` rather than a bare
   `typeof globalThis.OffscreenCanvas !== "undefined"`. The device
   capability check actually instantiates `new OffscreenCanvas(0,0)`
   and verifies `.getContext("2d")` returns non-null inside a
   try/catch — covers Safari's historical "OffscreenCanvas exists
   but only does WebGL{1,2}" quirk. Without this, `getWhitePixel()`
   could end up calling `getContext("2d")` on an OffscreenCanvas that
   doesn't support it and crash.

3. **renderer.getWhitePixel**: guard `getContext("2d")` against null
   with a descriptive error, so a failed allocation surfaces clearly
   instead of a null-deref later.

4. **canvas_renderer solid-fill alpha**: ARGB-extract now reads the
   A byte too and emits `rgba(...)` — preserves per-material MTL `d`
   opacity baked into `vertexColors`. Previously dropped silently.

5. **canvas_renderer degenerate-UV fallback**: the `rawDet === 0`
   branch's `_meshColorCanvas` allocation now routes through
   `Renderer.createCanvas(1, 1, true)` like the `solidFillKd` branch
   above — was the last `document.createElement("canvas")` left in
   the mesh path, would throw in worker / OffscreenCanvas contexts.

6. **Mesh per-group texture storage dropped**: `resolveGroupMaterial`
   no longer resolves a per-group `texture` field, since tier 2's
   mesh shader has a single `uSampler` binding shared across the
   whole mesh — per-material textures aren't switched at draw time.
   The shared `this.texture` falls back to the first group's
   `map_Kd` if any group has one, otherwise to the 1×1 white pixel.
   Removes a misleading field on `mesh.groups[*]` that the render
   path never read.

7. **mtl.js header comment**: dropped — explained a non-choice
   ("we're using console.warn because warning() is for deprecation")
   that's self-evident from looking at `lang/console.js`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 26, 2026 04:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 7 comments.

Comment thread packages/melonjs/src/loader/parsers/obj.js Outdated
Comment thread packages/melonjs/src/video/rendertarget/canvasrendertarget.js
Comment thread packages/melonjs/src/video/webgl/webgl_renderer.js Outdated
Comment thread packages/melonjs/src/video/webgl/batchers/mesh_batcher.js
Comment thread packages/melonjs/src/renderable/mesh.js
Comment thread packages/melonjs/src/renderable/mesh.js Outdated
Comment thread packages/melonjs/CHANGELOG.md Outdated
1. **obj parser tab tolerance** — `mtllib` / `usemtl` detection used
   `startsWith("mtllib ")` (single-space prefix), which fails for valid
   OBJ files that separate the keyword from its argument with tabs or
   multiple spaces. Switched to the same `split(/\s+/)` tokenization
   the `v/vt/f` paths use, then check `parts[0]` against the keyword.

2. **Renderer ↔ CanvasRenderTarget circular import resolved** —
   `CanvasRenderTarget` imported `Renderer` for `Renderer.createCanvas`,
   while `Renderer` already imported `CanvasRenderTarget` (used in its
   constructor). The cycle works today via ES module hoisting but is
   brittle. Extracted the canvas allocator into a new
   `video/canvas_factory.js` helper that both can import without
   referencing each other. `Renderer.createCanvas` becomes a thin
   re-export of the helper, so the public-facing API is unchanged.
   `CanvasRenderTarget` and `TMXLayer` migrated to importing the
   helper directly.

3. **"Single draw call" wording corrected in 5 places** — Mesh.draw
   doc, vertexColors-field doc, mesh_batcher.addMesh doc,
   webgl_renderer.drawMesh doc, and CHANGELOG line 10 all overstated
   the guarantee. `MeshBatcher.addMesh` still chunks very large meshes
   across multiple flushes to respect vertex/index buffer limits —
   what multi-material rendering actually guarantees is **no extra
   draw calls per material** vs single-material rendering, not a
   blanket "single draw call regardless of size". Rephrased
   consistently across all five sites.

README bullet (root + packages/melonjs/) updated to mention
multi-material support in the existing 3D mesh feature line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@obiot obiot merged commit 47afce4 into master May 26, 2026
6 checks passed
@obiot obiot deleted the feat/multi-material-mesh branch May 26, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants