melonjs 20.13.0 → 20.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -22
- package/build/application/application.d.ts +6 -1
- package/build/application/application.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/resize.d.ts.map +1 -1
- package/build/application/settings.d.ts +13 -8
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/audio.d.ts +8 -4
- package/build/audio/audio.d.ts.map +1 -1
- package/build/audio/backend/engine.d.ts.map +1 -1
- package/build/audio/playback.d.ts +2 -2
- package/build/audio/playback.d.ts.map +1 -1
- package/build/audio/spatial.d.ts.map +1 -1
- package/build/audio/types.d.ts +15 -5
- package/build/audio/types.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts +10 -9
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +11 -8
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/geometries/path2d.d.ts +9 -8
- package/build/geometries/path2d.d.ts.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +40993 -39738
- package/build/index.js.map +4 -4
- package/build/input/keyboard.d.ts.map +1 -1
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/level.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +2 -1
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +13 -3
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +1 -0
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +7 -3
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/lighting/light3d.d.ts +55 -0
- package/build/lighting/light3d.d.ts.map +1 -1
- package/build/loader/cache.d.ts +146 -97
- package/build/loader/cache.d.ts.map +1 -1
- package/build/loader/internal-types.d.ts +69 -0
- package/build/loader/internal-types.d.ts.map +1 -0
- package/build/loader/loader.d.ts +584 -292
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/parsers/audio.d.ts.map +1 -1
- package/build/loader/parsers/fontface.d.ts.map +1 -1
- package/build/loader/parsers/gltf.d.ts.map +1 -1
- package/build/loader/parsers/tmx.d.ts.map +1 -1
- package/build/math/color.d.ts +4 -0
- package/build/math/color.d.ts.map +1 -1
- package/build/math/noise.d.ts +16 -16
- package/build/math/noise.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +64 -25
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +9 -0
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/builtin-adapter.d.ts +25 -3
- package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts.map +1 -1
- package/build/physics/builtin/raycast.d.ts.map +1 -1
- package/build/physics/world.d.ts +40 -10
- package/build/physics/world.d.ts.map +1 -1
- package/build/renderable/container.d.ts +1 -0
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/draggable.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +16 -14
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +62 -5
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/trigger.d.ts +5 -0
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/uibaseelement.d.ts +8 -1
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uicheckbox.d.ts.map +1 -1
- package/build/system/device.d.ts +11 -2
- package/build/system/device.d.ts.map +1 -1
- package/build/system/fullscreen.d.ts.map +1 -1
- package/build/system/pool.d.ts.map +1 -1
- package/build/utils/utils.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +16 -14
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/canvas_factory.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +127 -0
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs/SKILL.md +38 -14
- package/skills/melonjs-20-migration/SKILL.md +50 -7
- package/skills/melonjs-3d/SKILL.md +138 -38
- package/skills/melonjs-3d-assets/SKILL.md +83 -33
- package/skills/melonjs-audio/SKILL.md +55 -14
- package/skills/melonjs-camera-and-drawing/SKILL.md +95 -8
- package/skills/melonjs-deployment/SKILL.md +92 -14
- package/skills/melonjs-display-and-scaling/SKILL.md +8 -3
- package/skills/melonjs-effects-and-shaders/SKILL.md +46 -15
- package/skills/melonjs-events/SKILL.md +3 -2
- package/skills/melonjs-getting-started/SKILL.md +5 -0
- package/skills/melonjs-input/SKILL.md +65 -15
- package/skills/melonjs-lighting/SKILL.md +13 -1
- package/skills/melonjs-loading-assets/SKILL.md +202 -27
- package/skills/melonjs-math/SKILL.md +12 -5
- package/skills/melonjs-particles-and-trails/SKILL.md +7 -3
- package/skills/melonjs-performance/SKILL.md +18 -8
- package/skills/melonjs-physics/SKILL.md +385 -127
- package/skills/melonjs-plugins/SKILL.md +45 -11
- package/skills/melonjs-renderables/SKILL.md +24 -7
- package/skills/melonjs-renderer-backends/SKILL.md +38 -17
- package/skills/melonjs-scenes-and-state/SKILL.md +30 -5
- package/skills/melonjs-sprites-and-animation/SKILL.md +23 -2
- package/skills/melonjs-tilemaps/SKILL.md +56 -18
- package/skills/melonjs-ui-and-text/SKILL.md +120 -25
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-3d
|
|
3
|
-
description: "Use this skill for anything 3D or 2.5D in melonJS — Camera3d, Mesh, InstancedMesh, Sprite3d billboards, Light3d, ground shadows, glTF/GLB scenes, and depth sorting. Covers the Y-down/+Z-forward convention that is the inverse of OpenGL, the cameraClass opt-in, clip planes, and what does not work on the Canvas fallback. Triggers on: Camera3d, Mesh, InstancedMesh, Sprite3d, Trail3d, ribbon, exhaust trail, Light3d, billboard, glTF, glb, 3D, 2.5D, depth, cameraClass, fov, setClipPlanes, setFog, fog, distance fog, height fog, heightFalloff, transparent, transparency, alpha, blendMode, fade, castGroundShadow, lit, setBasis, getBasis, lookAt, up vector, gimbal, slerp, great circle, turn a direction."
|
|
3
|
+
description: "Use this skill for anything 3D or 2.5D in melonJS — Camera3d, Mesh, InstancedMesh, Sprite3d billboards, Light3d, ground shadows, glTF/GLB scenes, and depth sorting. Covers the Y-down/+Z-forward convention that is the inverse of OpenGL, the cameraClass opt-in, clip planes, and what does not work on the Canvas fallback. Triggers on: Camera3d, Mesh, InstancedMesh, Sprite3d, Trail3d, ribbon, exhaust trail, Light3d, billboard, glTF, glb, 3D, 2.5D, depth, cameraClass, fov, setClipPlanes, setFog, fog, distance fog, height fog, heightFalloff, transparent, transparency, alpha, blendMode, fade, castGroundShadow, shadowGroundPoint, lit, emissive, specular, shininess, normalMap, alphaCutoff, depthTest, vertexColors, worldToScreen, followOffset, setBasis, getBasis, lookAt, up vector, gimbal, slerp, great circle, turn a direction."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -69,7 +69,8 @@ await app.init();
|
|
|
69
69
|
|
|
70
70
|
That flips the world's sort to `"depth"`, switches the broadphase to a 3D index,
|
|
71
71
|
and renders sprites and meshes with perspective projection. `Camera3d` extends
|
|
72
|
-
`Camera2d`, so follow, fade, shake and post effects all still work
|
|
72
|
+
`Camera2d`, so follow, fade, shake and post effects all still work (follow with
|
|
73
|
+
a difference, see the camera traps).
|
|
73
74
|
|
|
74
75
|
### Keep the camera, do not fish it back out
|
|
75
76
|
|
|
@@ -193,8 +194,15 @@ class GameStage extends Stage {
|
|
|
193
194
|
sense to `lookAt`'s, and one negation apart.
|
|
194
195
|
- **`worldToLocal` does not project.** It is a 2D camera's offset subtraction.
|
|
195
196
|
To pin a label or marker to a point in the scene use
|
|
196
|
-
`camera.worldToScreen(
|
|
197
|
+
`camera.worldToScreen(point, out?)`: it takes a `Vector3d`, applies the
|
|
198
|
+
projection and returns a `Vector2d` in the camera's pixels (y down), or
|
|
197
199
|
`null` behind the camera. See `melonjs-camera-and-drawing`.
|
|
200
|
+
- **`follow()` tracks exactly.** Under a `Camera3d` the camera sits at the
|
|
201
|
+
target's position plus `followOffset` (world space, set with
|
|
202
|
+
`camera.setFollowOffset(x, y, z)`) every frame. The 2D follow's axis,
|
|
203
|
+
deadzone and damping are not applied. For a damped chase camera, leave the
|
|
204
|
+
target unset and move the camera yourself (`math.damp`, or `slerp` below for
|
|
205
|
+
its facing).
|
|
198
206
|
|
|
199
207
|
## Where an up vector earns its keep
|
|
200
208
|
|
|
@@ -299,6 +307,11 @@ index** — a real world depth in a 3D scene, and never the one you wanted. Pass
|
|
|
299
307
|
it (`world.addChild(mesh, z)`), or set `mesh.depth` afterwards. The glTF
|
|
300
308
|
importer turns `autoDepth` off on the container it loads into for this reason.
|
|
301
309
|
|
|
310
|
+
A `Container`'s depth carries to its children when they are drawn, as it
|
|
311
|
+
already did for culling and the sort: a group at depth 400 draws its sprites
|
|
312
|
+
and meshes 400 units further away. One more reason to give containers an
|
|
313
|
+
explicit z.
|
|
314
|
+
|
|
302
315
|
**`floating` does not opt out of this sort.** It skips the camera *transform*,
|
|
303
316
|
not the depth *order*. A floating child is ordered by `|pos.z|` alone — its
|
|
304
317
|
`pos.x/y` are screen pixels, and the camera does not move relative to it — so
|
|
@@ -327,8 +340,9 @@ Meshes render in two phases. The **opaque pass** writes depth in sort order; the
|
|
|
327
340
|
depth. A draw lands in the second whenever its alpha is fractional.
|
|
328
341
|
|
|
329
342
|
That default matters because the opaque path writes premultiplied colour with
|
|
330
|
-
blending off, so
|
|
331
|
-
rather than see-through — the background
|
|
343
|
+
blending off, so a faded mesh that stays there (as every one did before 20.4)
|
|
344
|
+
comes out **darkened toward black** rather than see-through — the background
|
|
345
|
+
contributes nothing.
|
|
332
346
|
|
|
333
347
|
**`transparent: true`** when the transparency is in the TEXTURE rather than the
|
|
334
348
|
opacity — a soft-edged glow, smoke, a glTF material with `alphaMode: "BLEND"`.
|
|
@@ -376,8 +390,9 @@ const halo = new Sprite3d(0, 0, {
|
|
|
376
390
|
});
|
|
377
391
|
```
|
|
378
392
|
|
|
379
|
-
Honoured in the transparent pass on both GPU backends
|
|
380
|
-
|
|
393
|
+
Honoured in the **transparent pass only**, on both GPU backends: a mesh drawn
|
|
394
|
+
in the opaque pass ignores it, so pair it with `transparent: true` as above.
|
|
395
|
+
The Canvas renderer has no depth buffer and ignores it.
|
|
381
396
|
|
|
382
397
|
Blending uses the renderable's existing `blendMode`, so a glow is one property.
|
|
383
398
|
The advanced modes (`"overlay"`, `"difference"`, and the rest that need a
|
|
@@ -482,6 +497,8 @@ the built-in ones:
|
|
|
482
497
|
uniform vec3 uFogColor; // straight (unpremultiplied) fog colour
|
|
483
498
|
uniform vec4 uFogParams; // x = mode (0 off / 1 linear / 2 exp2),
|
|
484
499
|
// y = near, z = 1/(far - near), w = density
|
|
500
|
+
uniform vec4 uFogHeight; // height falloff: xyz = falloff x world-up axis,
|
|
501
|
+
// in VIEW space; w = pre-baked altitude term
|
|
485
502
|
```
|
|
486
503
|
|
|
487
504
|
Your vertex stage computes the distance itself — `length((uViewMatrix *
|
|
@@ -490,6 +507,12 @@ camera turns — and the blend must scale the fog colour by the fragment's own
|
|
|
490
507
|
alpha, `mix(uFogColor * a, rgb, f)`, because `vColor` arrives premultiplied.
|
|
491
508
|
Mixing toward the unscaled colour haloes every alpha-cutout edge.
|
|
492
509
|
|
|
510
|
+
`uFogHeight` is only needed to honour `heightFalloff`, and is fed only to a
|
|
511
|
+
program that also declares `uFogParams`. The engine's own shaders multiply the
|
|
512
|
+
distance by `uFogHeight.w * (exp(k) - 1.0) / k`, with `k = dot(uFogHeight.xyz,
|
|
513
|
+
viewPos)` and the factor taken as `uFogHeight.w` when `k` is near zero. Leave it
|
|
514
|
+
out and your mesh fogs uniformly at every altitude.
|
|
515
|
+
|
|
493
516
|
The flip side is the reason fog costs nothing when unused: with no camera fog,
|
|
494
517
|
the mesh programs are compiled without any of it, on both backends. It is not a
|
|
495
518
|
branch that is skipped at runtime — the code is not there.
|
|
@@ -510,7 +533,32 @@ const cube = new Mesh(x, y, {
|
|
|
510
533
|
`Camera3d` it opts out of the anchor offset entirely, so `anchorPoint` is inert
|
|
511
534
|
there — place the origin where you want the pivot at authoring time, or nest the
|
|
512
535
|
mesh under a transformed parent. The anchor is only honoured on the legacy
|
|
513
|
-
2D-camera path.
|
|
536
|
+
2D-camera path. `Sprite3d` is the exception: it bakes its `anchorPoint` into
|
|
537
|
+
the quad's vertices, so a `"bottom"` anchor works under either camera.
|
|
538
|
+
|
|
539
|
+
### Material settings
|
|
540
|
+
|
|
541
|
+
What a material file can state is also a setting, so a hand-built mesh gets the
|
|
542
|
+
same surface. All but `baseColorFactor` are GPU-only; the Canvas renderer
|
|
543
|
+
ignores them.
|
|
544
|
+
|
|
545
|
+
| setting | what it does | needs |
|
|
546
|
+
|---|---|---|
|
|
547
|
+
| `emissive: [r, g, b]` | self-illumination added on top, 0..1 (above 1 for an HDR glow) | nothing, lit or unlit |
|
|
548
|
+
| `specular: [r, g, b]` + `shininess` | a highlight; `shininess` 0, the default, turns it off however bright `specular` is | `lit`, a `Light3d`, normals |
|
|
549
|
+
| `normalMap` | tangent-space relief, a loader key or image (see Lighting below) | `lit` and a `Camera3d` |
|
|
550
|
+
| `alphaCutoff` | discard texels below this alpha: foliage, fences | nothing |
|
|
551
|
+
| `alphaMap` | per-texel opacity, multiplied in before the cutoff | a non-zero `alphaCutoff` |
|
|
552
|
+
| `baseColorFactor` | a LINEAR colour, encoded into `tint` (see `melonjs-3d-assets`) | nothing |
|
|
553
|
+
|
|
554
|
+
`emissive`, `specular` and `shininess` can be changed after construction too;
|
|
555
|
+
`setBaseColorFactor()` is the method form of the last row.
|
|
556
|
+
|
|
557
|
+
A `ShaderEffect` can shade a mesh as well: `mesh.addPostEffect(fx)` splices it
|
|
558
|
+
into the mesh shader as a colour hook on both GPU backends, so the mesh keeps its
|
|
559
|
+
placement, cutout, lighting and fog. A few builtins are unavailable there, and an
|
|
560
|
+
`InstancedMesh` cannot host one; both warn once. See
|
|
561
|
+
`melonjs-effects-and-shaders`.
|
|
514
562
|
|
|
515
563
|
### InstancedMesh, and when it is the wrong tool
|
|
516
564
|
|
|
@@ -578,9 +626,9 @@ vertices. Want smooth: share them.
|
|
|
578
626
|
An explicit `settings.normals` always wins, and an unlit mesh gets none — there
|
|
579
627
|
would be nothing to read them.
|
|
580
628
|
|
|
581
|
-
**A lit mesh with no normals used to render fullbright
|
|
582
|
-
lighting is broken rather than absent. If an older scene suddenly
|
|
583
|
-
shading, that is why.
|
|
629
|
+
**A lit mesh with no normals used to render fullbright** (before 20.4), which
|
|
630
|
+
looks like the lighting is broken rather than absent. If an older scene suddenly
|
|
631
|
+
picks up shading, that is why.
|
|
584
632
|
|
|
585
633
|
## Colouring a mesh
|
|
586
634
|
|
|
@@ -591,7 +639,7 @@ There are four levels, and picking the wrong one is the usual reason a colour
|
|
|
591
639
|
|---|---|---|
|
|
592
640
|
| whole object | `mesh.setTint("#ff8800")`, or `mesh.tint.setColor(r, g, b)` | flash on hit, team colour, fading one object |
|
|
593
641
|
| per vertex | `settings.vertexColors`, or `mesh.setVertexColor(i, color)` | a gradient *within* one mesh — distance haze, a darker crease |
|
|
594
|
-
| per material |
|
|
642
|
+
| per material | a multi-material OBJ + MTL | a model whose parts differ: each `Kd` is baked into the vertex colours, and only a different `map_Kd` costs a draw (one per `textureGroups` entry) |
|
|
595
643
|
| per instance | `new InstancedMesh(…, { instanceColors: true })` then `setInstanceColor(i, color)` | a thousand copies that differ |
|
|
596
644
|
|
|
597
645
|
**`tint` is per object.** That is the trap: build a terrain as one big mesh and
|
|
@@ -629,10 +677,11 @@ On a lit mesh the colour multiplies the **lit** result, so it behaves as albedo
|
|
|
629
677
|
rather than as an emissive override — a vertex colour will not make an unlit
|
|
630
678
|
face bright.
|
|
631
679
|
|
|
632
|
-
`alpha` hides a mesh as it hides anything else:
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
680
|
+
`alpha` hides a mesh as it hides anything else: below 1/255 the draw is skipped.
|
|
681
|
+
A fractional `alpha` fades it, through the transparent pass described under
|
|
682
|
+
Transparency above, which needs a `Camera3d` on a GPU backend. Without that pass
|
|
683
|
+
(a 2D camera on a GPU backend, or `transparent: false`) the mesh path does not
|
|
684
|
+
blend, and a faded mesh darkens instead of turning see-through.
|
|
636
685
|
|
|
637
686
|
## Moving a mesh in 3D
|
|
638
687
|
|
|
@@ -649,6 +698,12 @@ rotates about that axis. Both write to `currentTransform`, a **`Matrix3d`**,
|
|
|
649
698
|
which `Mesh` folds into its model matrix — so orienting a mesh costs a matrix
|
|
650
699
|
update, not new geometry.
|
|
651
700
|
|
|
701
|
+
Under a `Camera3d` on a GPU backend the geometry is uploaded once and redrawn
|
|
702
|
+
from the GPU, so moving, turning, scaling or re-tinting needs no signal. Editing the geometry
|
|
703
|
+
itself does: after writing into `originalVertices`, the UVs, normals or
|
|
704
|
+
indices, set `mesh.needsUpdate = true`, or the change shows under a 2D camera and
|
|
705
|
+
not under the 3D one.
|
|
706
|
+
|
|
652
707
|
This matters most for the case it is least obvious for: a horizontal plane.
|
|
653
708
|
`Sprite3d` with `billboard: false` is a quad in the sprite plane — upright —
|
|
654
709
|
and the way to lay one flat (water, a road, a shadow decal) is to rotate it a
|
|
@@ -657,13 +712,14 @@ geometry instead is tiling: a `Sprite3d` bakes its UVs 0..1 from its atlas
|
|
|
657
712
|
frame, so a surface that needs `textureRepeat` across many world units still
|
|
658
713
|
wants an explicit `vertices`/`uvs` mesh.
|
|
659
714
|
|
|
660
|
-
## The
|
|
715
|
+
## The opaque pass does not blend
|
|
661
716
|
|
|
662
717
|
Worth stating plainly, because the symptom does not look like a blending
|
|
663
|
-
problem: **the mesh pass disables blending
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
718
|
+
problem: **the opaque mesh pass disables blending**, and a draw goes there
|
|
719
|
+
unless its alpha is fractional or it says `transparent: true`. A soft-edged
|
|
720
|
+
texture — a glow, a sun, a halo, anything with a gradient alpha — drawn at full
|
|
721
|
+
opacity composites as a hard-edged disc with a grey rim, which reads as a broken
|
|
722
|
+
texture rather than as a missing feature.
|
|
667
723
|
|
|
668
724
|
`transparent: true` routes it through the transparent pass, and `blendMode:
|
|
669
725
|
"additive"` on top of that makes a light source add to the sky instead of
|
|
@@ -741,11 +797,13 @@ world.addChild(new Light3d({ type: "spot", position: torch.pos, range: 400 }));
|
|
|
741
797
|
```
|
|
742
798
|
|
|
743
799
|
`direction` and `position` take an `[x, y, z]` array **or** a `Vector3d`, and
|
|
744
|
-
`color` takes a `Color`, a CSS string or an `[r, g, b]` array
|
|
745
|
-
|
|
746
|
-
|
|
800
|
+
`color` takes a `Color`, a CSS string or an `[r, g, b]` array in **0..1** (the
|
|
801
|
+
glTF convention, not 0..255) — so a value the game already holds can go straight
|
|
802
|
+
in. The vectors are read, not retained: move your vector afterwards and the
|
|
803
|
+
light stays where it was. A `Color` is kept as given, so mutating it recolours
|
|
804
|
+
the light.
|
|
747
805
|
|
|
748
|
-
|
|
806
|
+
Three things still bite, all silently:
|
|
749
807
|
|
|
750
808
|
- `new Light3d(0, 0, {…})` — the constructor takes **options alone**. JavaScript
|
|
751
809
|
drops the extra arguments, so `options` becomes the number `0` and every
|
|
@@ -766,6 +824,10 @@ Two things still bite, both silently:
|
|
|
766
824
|
|
|
767
825
|
`position` follows the same convention: a lamp above the floor has a
|
|
768
826
|
**smaller** y than the floor.
|
|
827
|
+
- **`position` and `direction` are in world coordinates, wherever the light
|
|
828
|
+
sits in the tree.** A `Light3d` added under a container does not move or turn
|
|
829
|
+
with it, and its own `pos` is never read. To carry a lamp with a car or a
|
|
830
|
+
torch, write the object's world position into `light.position` each frame.
|
|
769
831
|
|
|
770
832
|
Use **both halves**: with a key light but no ambient, the shadow side of a mesh
|
|
771
833
|
goes black. With no `Light3d` in the world at all, a `lit: true` mesh falls back
|
|
@@ -776,6 +838,10 @@ Other defaults worth knowing: `intensity` 1, `color` white, `range` 1000 (point
|
|
|
776
838
|
and spot, a stylised quadratic falloff, not inverse-square),
|
|
777
839
|
`innerConeAngle` 0 and `outerConeAngle` π/4 (spot).
|
|
778
840
|
|
|
841
|
+
Up to **32** directional, point and spot lights shade a mesh; ambient lights are
|
|
842
|
+
summed into one fill and do not count. Lights past the 32nd are dropped without
|
|
843
|
+
a warning.
|
|
844
|
+
|
|
779
845
|
**`Light2d` is 2D-only** and produces visible artifacts under perspective
|
|
780
846
|
projection. Do not combine it with `Camera3d`.
|
|
781
847
|
|
|
@@ -858,7 +924,10 @@ the fade clamps to full strength and the blob never fades at all; and a point
|
|
|
858
924
|
on the wrong side of the caster reads as a large height, so the blob
|
|
859
925
|
disappears outright rather than thinning to a ring the way a backwards
|
|
860
926
|
`shadowGroundY` does. It is also the one shadow setting that THROWS rather
|
|
861
|
-
than clamping or hiding: a non-finite component is rejected on the spot.
|
|
927
|
+
than clamping or hiding: a non-finite component is rejected on the spot. A
|
|
928
|
+
two-component point (a `Vector2d`, a 2-element array) does not throw: its z
|
|
929
|
+
silently becomes 0. The point is copied, so moving the vector you passed moves
|
|
930
|
+
nothing; assign it again.
|
|
862
931
|
|
|
863
932
|
The blob is an ellipse sized to the caster's own footprint and placed at the
|
|
864
933
|
caster's x/z, or at `shadowGroundPoint` when one is set. So a tall or narrow object (a character, a tree, a pickup) shows
|
|
@@ -1028,7 +1097,9 @@ level.load("diorama", {
|
|
|
1028
1097
|
An **animated** asset instantiates as a `GLTFModel`, which is a game object as
|
|
1029
1098
|
much as a scene: construct it straight from the parsed descriptor, and place it
|
|
1030
1099
|
with `pos` / `depth` / `rotate` / `scale` like any other renderable — the
|
|
1031
|
-
placement drives the whole rig and composes with the playing clip
|
|
1100
|
+
placement drives the whole rig and composes with the playing clip, and still
|
|
1101
|
+
applies while the clip is paused, finished or was never played. `scale()`
|
|
1102
|
+
multiplies the `scale` option it was built with.
|
|
1032
1103
|
|
|
1033
1104
|
```js
|
|
1034
1105
|
const model = new GLTFModel(loader.getGLTF("boat"), { scale: 40, lit: false });
|
|
@@ -1048,14 +1119,20 @@ animation (and the skinning that is out of scope), and OBJ/MTL.
|
|
|
1048
1119
|
|
|
1049
1120
|
## Requires a GPU backend
|
|
1050
1121
|
|
|
1051
|
-
The
|
|
1052
|
-
|
|
1053
|
-
render
|
|
1054
|
-
are inert too.
|
|
1055
|
-
|
|
1122
|
+
The 3D tier is built for WebGPU or WebGL 2: target one of them. The Canvas
|
|
1123
|
+
renderer has no depth buffer and no perspective path, so a `Camera3d` scene
|
|
1124
|
+
does not render there — usually a black canvas. `Light3d` and shader effects
|
|
1125
|
+
are inert too.
|
|
1126
|
+
|
|
1127
|
+
A `Mesh` under a *2D* camera does render on Canvas, but only as a last resort:
|
|
1128
|
+
textured, CPU-projected, sorted back to front a triangle at a time (painter's
|
|
1129
|
+
algorithm) and unlit. Overlapping parts can show slivers of the wrong
|
|
1130
|
+
triangle, large textured faces are mapped without perspective correction, and
|
|
1131
|
+
it slows down as the triangle count grows. It keeps a 2D-camera game from going
|
|
1132
|
+
blank on a device with no GPU backend; do not design for it or tune on it.
|
|
1056
1133
|
|
|
1057
|
-
`Application` does warn:
|
|
1058
|
-
`defaultSortOn` is `"depth"
|
|
1134
|
+
`Application` does warn: `await app.init()` with a `cameraClass` whose
|
|
1135
|
+
`defaultSortOn` is `"depth"`, on a renderer with no depth buffer, logs a
|
|
1059
1136
|
`console.warn`. But `video.AUTO` falls back silently to Canvas, so the strong
|
|
1060
1137
|
gate is to ask for a GPU backend by name — `renderer: video.WEBGL` (or
|
|
1061
1138
|
`video.WEBGPU`) makes `await app.init()` reject instead of misrendering:
|
|
@@ -1070,23 +1147,42 @@ await app.init();
|
|
|
1070
1147
|
```
|
|
1071
1148
|
|
|
1072
1149
|
To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
|
|
1073
|
-
`init()
|
|
1150
|
+
`init()`: a `Camera3d` game should refuse Canvas there with a message, while a
|
|
1151
|
+
game whose meshes sit under a 2D camera can let it through as the fallback.
|
|
1152
|
+
|
|
1153
|
+
```js
|
|
1154
|
+
const app = new Application(1024, 768, { parent: "screen", renderer: video.AUTO });
|
|
1155
|
+
await app.init();
|
|
1156
|
+
if (!app.renderer.supportsDepthBuffer) {
|
|
1157
|
+
// the Canvas fallback: no GPU backend on this device. Meshes under the
|
|
1158
|
+
// 2D camera still draw (CPU-projected, unlit); keep the scene small, or
|
|
1159
|
+
// tell the player the game needs WebGL 2 or WebGPU.
|
|
1160
|
+
}
|
|
1161
|
+
```
|
|
1074
1162
|
|
|
1075
1163
|
## Symptom → cause
|
|
1076
1164
|
|
|
1077
1165
|
| symptom | cause |
|
|
1078
1166
|
|---|---|
|
|
1079
1167
|
| scene lit from underneath | `direction` Y sign — this is Y-down, so a sun overhead is **+Y** |
|
|
1080
|
-
|
|
|
1168
|
+
| `model.scale(2)` throws "not a function" on a `GLTFModel` (before 20.14) | the `scale` option hid the method: update |
|
|
1169
|
+
| a `GLTFModel` ignores `pos` / `rotate` once its clip is paused or finished (before 20.14) | it was only re-posed while a clip played: update |
|
|
1170
|
+
| a sprite or mesh in a container draws too near under `Camera3d`, and pops in and out at the wrong time (before 20.14) | the container's depth was used to cull and sort its children but not to draw them: update |
|
|
1171
|
+
| a lit mesh inside a moved or turned container is lit as if the container were at the origin (before 20.14) | lights were not taken into the container's frame: update. Under a *scaled* container, a point light's range is measured in the container's units |
|
|
1172
|
+
| objects at the screen edge pop in a frame late while the camera turns, or a camera cut shows one empty frame (before 20.14) | culling used the camera as it was at the last `camera.update()`: update |
|
|
1173
|
+
| a `lit` mesh renders fullbright | no `Light3d` in the world, or a 2D camera (which warns once). Before 20.4 a mesh with no normals did it too; they are generated now |
|
|
1081
1174
|
| a gradient across one mesh is impossible | `tint` is per object — use `vertexColors` / `setVertexColor` |
|
|
1082
|
-
| a mesh stays solid as you fade it out | meshes render opaque; only `alpha` 0 (hidden) and 1 differ |
|
|
1083
1175
|
| a glow is cut along a hard straight line, worst near a round object's limb | a flat billboard being depth tested against the geometry behind it — `depthTest: false` |
|
|
1084
|
-
|
|
|
1176
|
+
| `depthTest: false` changes nothing | the mesh draws in the opaque pass, which ignores it — add `transparent: true` |
|
|
1177
|
+
| `specular` is set but no highlight appears | `shininess` is 0 (the default), or the mesh is not `lit` |
|
|
1178
|
+
| `setInstanceColor` does nothing | the set was built without `instanceColors: true`; the slot does not exist |
|
|
1179
|
+
| a vertex colour or a geometry edit applies under a 2D camera but not `Camera3d` | wrote the array directly without setting `needsUpdate` |
|
|
1085
1180
|
| `Mesh: vertexColors has N entries, expected M` | one colour per *vertex*, not per triangle or per index |
|
|
1086
1181
|
| nothing renders, or a backdrop covers everything | wrong depth sign — "far" is *larger* z when looking along +Z |
|
|
1087
1182
|
| distant objects vanish or warp | scene exceeds the default far plane; `setClipPlanes` |
|
|
1088
1183
|
| distant surfaces z-fight | `near` too small for the scene scale |
|
|
1089
1184
|
| black canvas under `Camera3d` | Canvas renderer (no depth buffer) — check the `console.warn` |
|
|
1185
|
+
| a 2D-camera mesh shows slivers where it overlaps itself, or slows with many triangles | the Canvas fallback, a last resort: ask for `video.WEBGL` or `video.WEBGPU` |
|
|
1090
1186
|
| everything flat and unlit | `lit: true` with no `Light3d` in the world (falls back to fullbright), or a mesh under a 2D camera |
|
|
1091
1187
|
| a faded mesh goes dark instead of see-through | `transparent: false` on it, or a 2D camera — the transparent pass needs a `Camera3d` |
|
|
1092
1188
|
| a soft-edged glow has hard edges | `alphaCutoff` discarded the soft texels; lower it |
|
|
@@ -1114,9 +1210,13 @@ To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
|
|
|
1114
1210
|
| `setBasis` throws "forward must have a non-zero length" | `forward` is all zeros, the one case that names no direction; a short one is fine, so a surface frame that shrank as it drifted still works |
|
|
1115
1211
|
| roll is ignored when the target is straight up or down | no rotation about the view axis is determined there, so `lookAt` keeps the roll it had and `setBasis` falls back to the `right` axis |
|
|
1116
1212
|
| an object jumps to the camera plane | `Vector3d.set(x, y)` / `camera.pos.set(x, y)` zeroed its z |
|
|
1213
|
+
| a follow camera ignores its deadzone and damping | `Camera3d` follows exactly, at the target plus `followOffset`; damp it yourself with no target set |
|
|
1214
|
+
| a `Light3d` parented to a moving object stays behind | `position` is in world coordinates and its container is not applied; copy the world position into it each frame |
|
|
1117
1215
|
| lighting looks wrong under a 3D camera | `Light2d` used instead of `Light3d` |
|
|
1118
1216
|
|
|
1119
1217
|
## Related skills
|
|
1120
1218
|
|
|
1219
|
+
- `melonjs-3d-assets` — glTF/GLB and OBJ/MTL loading, imported materials and lights
|
|
1220
|
+
- `melonjs-effects-and-shaders` — a `ShaderEffect` hosted on a mesh, and custom mesh shaders
|
|
1121
1221
|
- `melonjs-renderables` — the 2D z-ordering rules, anchors, custom draw
|
|
1122
1222
|
- `melonjs-getting-started` — Application settings and the renderer fallback
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: melonjs-3d-assets
|
|
3
|
-
description: "Use this skill when loading 3D models into melonJS — glTF and GLB scenes, OBJ/MTL models, materials, imported lights, node animation, ground shadows and GPU instancing. Covers level.load options including the async flag, the rightHanded conversion, and exactly what the loader does and does not support. Triggers on: glTF, gltf, glb, OBJ, MTL, 3D model, getGLTF, getOBJ, getMTL, GLTFModel, GLTFScene, level.load glb, rightHanded, lightIntensityScale, castGroundShadow, shadowGroundY, EXT_mesh_gpu_instancing, KHR_lights_punctual, skinning, Blender export, 3D asset."
|
|
3
|
+
description: "Use this skill when loading 3D models into melonJS — glTF and GLB scenes, OBJ/MTL models, materials, imported lights, node animation, ground shadows and GPU instancing. Covers level.load options including the async flag, the rightHanded conversion, and exactly what the loader does and does not support. Triggers on: glTF, gltf, glb, OBJ, MTL, 3D model, getGLTF, getOBJ, getMTL, GLTFModel, GLTFScene, level.load glb, rightHanded, lightIntensityScale, castGroundShadow, shadowGroundY, EXT_mesh_gpu_instancing, KHR_lights_punctual, alphaMode, doubleSided, map_d, map_bump, normalize, animationspeed, skinning, Blender export, 3D asset."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -36,16 +36,8 @@ or it renders flat. See `melonjs-3d` for the camera.
|
|
|
36
36
|
| `onLoaded` | `game.onLevelLoaded` | called with the **level id**, not the scene |
|
|
37
37
|
| `async` | `false` | return a promise that settles once the scene is in the world, instead of a boolean |
|
|
38
38
|
| `rightHanded` | `true` | see below |
|
|
39
|
-
| `lights` | `true` | instantiate authored `KHR_lights_punctual` lights as `Light3d`
|
|
39
|
+
| `lights` | `true` | instantiate authored `KHR_lights_punctual` lights as `Light3d` children of `container`; `false` also builds the meshes unlit |
|
|
40
40
|
| `lightIntensityScale` | — | keep authored intensity ratios instead of normalising every light to 1 |
|
|
41
|
-
|
|
42
|
-
Those `game.` defaults are the engine's, not a recommendation. `game` is
|
|
43
|
-
whichever `Application` finished `init()` last, so **pass `container`
|
|
44
|
-
explicitly** from a `Stage`, where the right one is in your hand:
|
|
45
|
-
|
|
46
|
-
```js
|
|
47
|
-
level.load("sceneId", { container: this.app.world, scale: 40 });
|
|
48
|
-
```
|
|
49
41
|
| `castGroundShadow` | inherits the app setting (**on**) | blob shadows for this scene's meshes |
|
|
50
42
|
| `shadowGroundY` | each object's own base | world Y of the floor the blobs land on |
|
|
51
43
|
| `shadowOpacity` | `0.45` | how dark the blobs are |
|
|
@@ -56,6 +48,16 @@ level.load("sceneId", { container: this.app.world, scale: 40 });
|
|
|
56
48
|
| `shadowGroundNormal` | world up | up normal of the floor, for a scene that is not level |
|
|
57
49
|
| `shadowLight` | — | a `Light3d` to take the direction from, re-read every draw |
|
|
58
50
|
|
|
51
|
+
Those `game.` defaults are the engine's, not a recommendation. `game` is
|
|
52
|
+
whichever `Application` finished `init()` last, so **pass `container`
|
|
53
|
+
explicitly** from a `Stage`, where the right one is in your hand:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
onResetEvent(app) {
|
|
57
|
+
level.load("sceneId", { container: app.world, scale: 40 });
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
59
61
|
`shadowGroundPoint` is **not** on that list, and a scene load drops it without
|
|
60
62
|
a warning. One floor POINT shared by every part of a scene would stack the
|
|
61
63
|
whole thing's blobs on a single spot, which is nonsense where one floor
|
|
@@ -102,9 +104,10 @@ world.addChild(new Trigger(x, y, {
|
|
|
102
104
|
|
|
103
105
|
Every `level.load` option above travels, including the whole ground-shadow set,
|
|
104
106
|
and so do `container`, `onLoaded`, `flatten` and `setViewportBounds`. The list is
|
|
105
|
-
**derived** from
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
**derived** from the options each level format declares beside the code that
|
|
108
|
+
reads them, rather than restated, so an option added to a level format arrives
|
|
109
|
+
here for free. It used to be hand-written and had fallen three releases behind
|
|
110
|
+
the glTF path (#1649).
|
|
108
111
|
|
|
109
112
|
**`async` is deliberately excluded.** A trigger with a `color` + `duration`
|
|
110
113
|
sets it itself, because it needs the promise to sequence its fade and reveal; a
|
|
@@ -132,24 +135,34 @@ Do not add a flip of your own on top; two corrections cancel back to wrong.
|
|
|
132
135
|
The importer covers a deliberate subset:
|
|
133
136
|
|
|
134
137
|
- `pbrMetallicRoughness` **base colour texture and factor**
|
|
138
|
+
- `COLOR_0` **vertex colours**, multiplied with the factor and the texture
|
|
135
139
|
- metallic/roughness **factors** converted to the engine's specular model
|
|
136
140
|
- `emissiveFactor`, including `KHR_materials_emissive_strength`
|
|
137
141
|
- `KHR_materials_unlit` — honoured, for baked/stylised workflows
|
|
138
142
|
- `alphaMode: "MASK"` — the cutout threshold is applied
|
|
139
143
|
- `doubleSided`
|
|
144
|
+
- the base colour texture's sampler: `wrapS` / `wrapT` (repeat by default, as
|
|
145
|
+
the spec says) and `magFilter`
|
|
146
|
+
|
|
147
|
+
`alphaMode: "BLEND"` is read as opaque: the loader does not set `transparent`,
|
|
148
|
+
because one loaded mesh can merge materials and the flag routes the whole mesh.
|
|
149
|
+
Set `transparent: true` on the part yourself (see `melonjs-3d`).
|
|
140
150
|
|
|
141
151
|
Not imported: **normal maps**, metallic/roughness *textures*, occlusion maps,
|
|
142
152
|
morph targets, `KHR_texture_transform`. Nothing errors — the material simply
|
|
143
153
|
arrives without them, which reads as "flatter than the preview in my authoring
|
|
144
|
-
tool". Bake what you need into the base colour.
|
|
154
|
+
tool". Bake what you need into the base colour. (OBJ/MTL does carry a normal
|
|
155
|
+
map, see below.)
|
|
145
156
|
|
|
146
157
|
**Draco compression is the exception that does error.** Its geometry sits in
|
|
147
158
|
accessors with no buffer view, which the parser rejects outright, so the whole
|
|
148
159
|
asset fails to load. Export uncompressed.
|
|
149
160
|
|
|
150
|
-
`textureFilter` on the Application settings (`"
|
|
151
|
-
`"
|
|
152
|
-
it through. Pixel-art models want `"nearest"`.
|
|
161
|
+
`textureFilter` on the Application settings (`"auto"` / `"nearest"` /
|
|
162
|
+
`"linear"` / `"linear-mipmap"`) drives sampling; glTF materials that declare
|
|
163
|
+
their own sampler carry it through. Pixel-art models want `"nearest"`. A mesh
|
|
164
|
+
already samples mipmaps under `"linear"`, so `"linear-mipmap"` changes nothing
|
|
165
|
+
for one.
|
|
153
166
|
|
|
154
167
|
### `baseColorFactor` is LINEAR; a `tint` is sRGB
|
|
155
168
|
**Pass the factor as a setting and the encoding is done for you.** `Mesh`
|
|
@@ -222,8 +235,10 @@ screen, a HUD tint or a glow, are already sRGB: `* 255` is right for those.
|
|
|
222
235
|
## Lights
|
|
223
236
|
|
|
224
237
|
Authored `KHR_lights_punctual` lights — sun, point, spot — become `Light3d`
|
|
225
|
-
|
|
226
|
-
A scene carrying any of them
|
|
238
|
+
children of the container the scene loads into, plus a soft white ambient fill
|
|
239
|
+
at intensity 0.3, and each keeps its authored name. A scene carrying any of them
|
|
240
|
+
renders through the **lit** path automatically (except `KHR_materials_unlit`
|
|
241
|
+
materials); one carrying none renders unlit.
|
|
227
242
|
|
|
228
243
|
glTF stores physical units — lux for suns, candela for lamps — and a Blender
|
|
229
244
|
daylight sun is 1000+, which blows out the engine's stylised shading. So every
|
|
@@ -235,7 +250,10 @@ set, scale instead of normalising:
|
|
|
235
250
|
level.load("diorama", { scale: 50, lightIntensityScale: 0.001 });
|
|
236
251
|
```
|
|
237
252
|
|
|
238
|
-
Pass `lights: false` to
|
|
253
|
+
Pass `lights: false` to leave the authored lights out. The meshes are then built
|
|
254
|
+
**unlit**, so a `Light3d` of your own does not shade them either. To light a
|
|
255
|
+
scene your own way, keep the authored lights and retune them by name, or remove
|
|
256
|
+
them once the load has finished and add yours: the meshes stay lit.
|
|
239
257
|
|
|
240
258
|
## Animation
|
|
241
259
|
|
|
@@ -258,13 +276,18 @@ model.pause();
|
|
|
258
276
|
|
|
259
277
|
`speed` is a playback multiplier (1 = authored speed), not a frame delay. Only
|
|
260
278
|
an explicit `loop: false` stops looping. `setCurrentAnimation` **throws** on a
|
|
261
|
-
clip name the asset does not define — check `getAnimationNames()` first.
|
|
279
|
+
clip name the asset does not define — check `getAnimationNames()` first. Called
|
|
280
|
+
with the clip that is already playing it returns at once and ignores its
|
|
281
|
+
options, so retime a running clip through `model.animationspeed`, not a second
|
|
282
|
+
`setCurrentAnimation(name, { speed })`.
|
|
262
283
|
|
|
263
284
|
### Placing and moving a model
|
|
264
285
|
|
|
265
286
|
A `GLTFModel` is placed like any other renderable — `pos`, `depth`, `rotate`
|
|
266
287
|
and `scale` move the **whole rig**, and compose with whatever the active clip
|
|
267
|
-
is doing, so a walk cycle plays wherever the character stands
|
|
288
|
+
is doing, so a walk cycle plays wherever the character stands. They apply just
|
|
289
|
+
the same while the clip is paused, finished or was never played, and `scale()`
|
|
290
|
+
multiplies the `scale` option the model was built with:
|
|
268
291
|
|
|
269
292
|
```js
|
|
270
293
|
const boat = new me.GLTFModel(me.loader.getGLTF("boat"), { scale: 40, lit: false });
|
|
@@ -295,6 +318,11 @@ hundred thousand. Export with instancing enabled where your tool supports it.
|
|
|
295
318
|
Sparse accessors on the instancing attributes are rejected with a clear error
|
|
296
319
|
rather than silently misplacing every instance.
|
|
297
320
|
|
|
321
|
+
Face culling is decided once per mesh, so instances with a **negative scale**
|
|
322
|
+
(mirrored linked duplicates) render inside-out. Building the scene or model
|
|
323
|
+
warns when it meets them; mark the material `doubleSided`, which turns culling
|
|
324
|
+
off for the set.
|
|
325
|
+
|
|
298
326
|
## Ground shadows
|
|
299
327
|
|
|
300
328
|
```js
|
|
@@ -353,7 +381,7 @@ world.addChild(mesh);
|
|
|
353
381
|
```
|
|
354
382
|
|
|
355
383
|
**The MTL loader fetches the textures it references itself** — `map_Kd`,
|
|
356
|
-
`map_d` and
|
|
384
|
+
`map_d` and the normal map are resolved relative to the `.mtl` and preloaded for
|
|
357
385
|
you, so there is no image entry in the manifest above. Passing `texture:`
|
|
358
386
|
explicitly overrides that and pins *one* binding over the whole model, which on
|
|
359
387
|
a multi-material model suppresses the per-material texture split; leave it out
|
|
@@ -361,19 +389,23 @@ unless you mean that.
|
|
|
361
389
|
|
|
362
390
|
MTL contributes `Kd` (diffuse tint), `d` / `Tr` (opacity), `Ke` (emissive),
|
|
363
391
|
`Ks` + `Ns` (specular highlight), `map_Kd` (diffuse texture), `map_d`
|
|
364
|
-
(per-texel opacity) and `map_bump` / `bump` / `norm`
|
|
365
|
-
map — needs `lit: true`, and the tangent frame is derived
|
|
366
|
-
model needs no tangent attribute).
|
|
367
|
-
normal
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
need a real PBR model.
|
|
392
|
+
(per-texel opacity) and `map_bump` / `map_Bump` / `bump` / `norm`
|
|
393
|
+
(tangent-space normal map — needs `lit: true`, and the tangent frame is derived
|
|
394
|
+
per fragment, so the model needs no tangent attribute). Every normal-map
|
|
395
|
+
spelling is read as a normal map even though the format specifies `bump` and
|
|
396
|
+
`map_bump` as height maps, because a normal map is what exporters actually
|
|
397
|
+
write there. Specular *maps* (`map_Ks`) and the PBR extension's `map_Pr` /
|
|
398
|
+
`map_Pm` are not supported and warn — the `Pr` / `Pm` *scalars* are read and
|
|
399
|
+
approximated onto the specular terms, the maps would need a real PBR model.
|
|
400
|
+
|
|
401
|
+
`map_d` cuts out only against a non-zero `alphaCutoff`, and the loader sets
|
|
402
|
+
none: pass one in the `Mesh` settings (`alphaCutoff: 0.5`), or nothing is cut
|
|
403
|
+
out.
|
|
372
404
|
|
|
373
405
|
`Ka` (ambient) and `illum` (illumination model) are read onto the material and
|
|
374
406
|
readable from `loader.getMTL(name)`, but take no part in shading. That is
|
|
375
407
|
deliberate rather than pending: `Ka` predates scene-wide ambient light and would
|
|
376
|
-
fight `
|
|
408
|
+
fight an `"ambient"` `Light3d`, and `illum` enumerates fixed-function
|
|
377
409
|
behaviours this renderer has no equivalent for. In particular an `illum` of 0 or
|
|
378
410
|
1 nominally means "no specular", and the engine does **not** honour that — `Ns`
|
|
379
411
|
stays the only gate, so a model that renders correctly today is not restyled by
|
|
@@ -469,11 +501,19 @@ const node = me.loader.getGLTF("palm").nodes[0];
|
|
|
469
501
|
const trees = new me.InstancedMesh(0, groundY, {
|
|
470
502
|
vertices: node.vertices, uvs: node.uvs,
|
|
471
503
|
normals: node.normals, indices: node.indices,
|
|
504
|
+
normalize: false, // keep the authored units; see below
|
|
472
505
|
texture: palette, textureFilter: "nearest", scale: 40,
|
|
473
506
|
instanceCount: count,
|
|
474
507
|
});
|
|
475
508
|
```
|
|
476
509
|
|
|
510
|
+
**Pass `normalize: false` whenever the geometry comes off a descriptor.** The
|
|
511
|
+
default fits the geometry into a unit cube, centred, which throws away the
|
|
512
|
+
authored size and the base-on-the-floor origin. Before 20.14 it was worse: the
|
|
513
|
+
default normalized the loader's own array in place, so every later mesh built
|
|
514
|
+
from that node, and a `level.load` of the same asset, got the shrunken
|
|
515
|
+
geometry. A normalized mesh works on a copy now.
|
|
516
|
+
|
|
477
517
|
`nodes` is one entry **per primitive**, each in its own local space with a
|
|
478
518
|
separate `world` matrix — so this only works cleanly when the asset is a single
|
|
479
519
|
merged primitive exported with an identity node transform. Model it at the
|
|
@@ -515,8 +555,16 @@ need a prefix.
|
|
|
515
555
|
| the asset fails to load entirely | an external buffer/image `uri` that 404s, or Draco-compressed geometry |
|
|
516
556
|
| everything is blown out white | authored lux/candela intensities; use `lightIntensityScale` |
|
|
517
557
|
| materials flatter than the authoring-tool preview | no normal or metallic/roughness *textures* are imported |
|
|
558
|
+
| a glTF `alphaMode: "BLEND"` material draws opaque | the loader does not set `transparent`; set it on that part yourself |
|
|
559
|
+
| an MTL `map_d` cuts nothing out | it needs a non-zero `alphaCutoff`, which the loader does not set |
|
|
560
|
+
| a mesh built from `getGLTF(name).nodes` comes out unit-sized | `normalize` defaults to `true`; pass `normalize: false` to keep the authored units |
|
|
561
|
+
| after one normalized mesh of a model, the scene or other meshes of that model load shrunken (before 20.14) | the default normalized the loader's cached array in place; update, or pass `normalize: false` |
|
|
562
|
+
| some copies in an imported scatter render inside-out | negative-scale (mirrored) instances under per-mesh face culling; make the material `doubleSided` |
|
|
518
563
|
| a character does not deform | vertex skinning is out of scope; rig hierarchically or billboard |
|
|
519
564
|
| animation names come back empty | the asset has no node-TRS channels (skin-only rig) |
|
|
565
|
+
| `setCurrentAnimation(name, { speed })` changes nothing | that clip is already playing, so the call returns at once; set `animationspeed` |
|
|
566
|
+
| `model.scale(2)` throws "not a function" on a `GLTFModel` (before 20.14) | the `scale` option hid the method: update |
|
|
567
|
+
| a `GLTFModel` ignores `pos` / `rotate` once its clip is paused or finished (before 20.14) | it was only re-posed while a clip played: update |
|
|
520
568
|
| a hundred copies tank the frame rate | exported without `EXT_mesh_gpu_instancing` |
|
|
521
569
|
| a whole scatter looks like one object repeated | every instance shares the group transform — vary yaw/scale per instance, rotating *after* the translate |
|
|
522
570
|
| three clips where you authored one | one action per object; use a single action with a slot each |
|
|
@@ -530,12 +578,14 @@ need a prefix.
|
|
|
530
578
|
| a shadow smeared across the whole floor | a ground plane cast its own blob — use the scene-wide opt-in |
|
|
531
579
|
| `onLoaded` gets a string, not the scene | it is called with the level id; load into your own container instead |
|
|
532
580
|
| scene renders flat and unlit | no `Camera3d` — the 2D-camera path is CPU-projected and unlit (with a `Camera3d` on Canvas you get a black canvas instead) |
|
|
581
|
+
| scene renders in perspective but unlit | the asset carries no sun, point or spot light (or was loaded with `lights: false`), so its meshes are built unlit; author one in the asset and tune it at runtime by name |
|
|
533
582
|
|
|
534
583
|
## Related skills
|
|
535
584
|
|
|
536
585
|
- `melonjs-3d` — conventions, `Camera3d`, meshes, `Light3d`, instancing, and
|
|
537
586
|
`camera.setFog` (an outdoor scene almost always wants it; it is set on the
|
|
538
587
|
camera, so a loaded scene needs no per-node work)
|
|
539
|
-
- `melonjs-lighting` —
|
|
588
|
+
- `melonjs-lighting` — the 2D `Light2d` system; imported lights are `Light3d`,
|
|
589
|
+
covered in `melonjs-3d`
|
|
540
590
|
- `melonjs-loading-assets` — the loader, asset types and base URLs
|
|
541
591
|
- `melonjs-plugins` — the Spine plugin, for skeletal characters
|