melonjs 20.6.0 → 20.8.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 +6 -3
- package/build/application/defaultApplicationSettings.d.ts +5 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/header.d.ts.map +1 -1
- package/build/application/settings.d.ts +88 -0
- package/build/application/settings.d.ts.map +1 -1
- package/build/audio/backend/engine.d.ts +0 -1
- package/build/audio/backend/sound.d.ts +0 -1
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/camera/camera3d.d.ts +95 -8
- package/build/camera/camera3d.d.ts.map +1 -1
- package/build/geometries/box3d.d.ts +24 -6
- package/build/geometries/box3d.d.ts.map +1 -1
- package/build/geometries/ellipse.d.ts +14 -0
- package/build/geometries/ellipse.d.ts.map +1 -1
- package/build/geometries/polygon.d.ts +14 -0
- package/build/geometries/polygon.d.ts.map +1 -1
- package/build/geometries/sphere.d.ts +35 -6
- package/build/geometries/sphere.d.ts.map +1 -1
- package/build/index.d.ts +10 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23361 -20195
- package/build/index.js.map +4 -4
- package/build/input/pointerevent.d.ts +8 -0
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/lang/deprecated.d.ts.map +1 -1
- package/build/level/gltf/GLTFModel.d.ts +17 -31
- package/build/level/gltf/GLTFModel.d.ts.map +1 -1
- package/build/level/gltf/GLTFScene.d.ts +40 -1
- package/build/level/gltf/GLTFScene.d.ts.map +1 -1
- package/build/level/level.d.ts +36 -0
- package/build/level/level.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +5 -2
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXObject.d.ts +0 -1
- package/build/level/tiled/TMXTile.d.ts +2 -1
- package/build/level/tiled/TMXTile.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts +7 -4
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts +1 -1
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/TMXTilesetGroup.d.ts +5 -0
- package/build/level/tiled/TMXTilesetGroup.d.ts.map +1 -1
- package/build/level/tiled/factories/shape.d.ts +0 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +6 -5
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +5 -0
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +8 -7
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +5 -0
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXRenderer.d.ts +19 -4
- package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/autodetect.d.ts +0 -4
- package/build/level/tiled/renderer/autodetect.d.ts.map +1 -1
- package/build/lighting/light2d.d.ts +68 -2
- package/build/lighting/light2d.d.ts.map +1 -1
- package/build/lighting/light3d.d.ts.map +1 -1
- package/build/loader/loader.d.ts +63 -0
- package/build/loader/loader.d.ts.map +1 -1
- package/build/loader/loadingscreen.d.ts +1 -2
- package/build/loader/loadingscreen.d.ts.map +1 -1
- package/build/loader/parsers/fontface.d.ts +2 -0
- package/build/loader/parsers/fontface.d.ts.map +1 -1
- package/build/loader/parsers/mtl.d.ts.map +1 -1
- package/build/loader/parsers/shader.d.ts +0 -2
- package/build/math/color.d.ts +27 -4
- package/build/math/color.d.ts.map +1 -1
- package/build/particles/emitter.d.ts.map +1 -1
- package/build/particles/particle.d.ts +22 -1
- package/build/particles/particle.d.ts.map +1 -1
- package/build/particles/settings.d.ts +69 -0
- package/build/particles/settings.d.ts.map +1 -1
- package/build/physics/adapter.d.ts +152 -3
- package/build/physics/adapter.d.ts.map +1 -1
- package/build/physics/builtin/body.d.ts +9 -18
- package/build/physics/builtin/body.d.ts.map +1 -1
- package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
- package/build/physics/builtin/detector.d.ts +0 -1
- package/build/physics/builtin/detector.d.ts.map +1 -1
- package/build/physics/builtin/sat.d.ts +0 -7
- package/build/physics/builtin/sat.d.ts.map +1 -1
- package/build/physics/builtin/sat3d.d.ts +46 -0
- package/build/physics/builtin/sat3d.d.ts.map +1 -1
- package/build/physics/physicseditor.d.ts +2 -0
- package/build/physics/physicseditor.d.ts.map +1 -0
- package/build/physics/response.d.ts +45 -62
- package/build/physics/response.d.ts.map +1 -1
- package/build/renderable/collectable.d.ts +6 -1
- package/build/renderable/collectable.d.ts.map +1 -1
- package/build/renderable/colorlayer.d.ts +4 -4
- package/build/renderable/colorlayer.d.ts.map +1 -1
- package/build/renderable/container.d.ts +23 -17
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/entity/entity.d.ts +14 -16
- package/build/renderable/entity/entity.d.ts.map +1 -1
- package/build/renderable/frameAnimation.d.ts +1 -1
- package/build/renderable/groundshadow.d.ts +1 -0
- package/build/renderable/groundshadow.d.ts.map +1 -1
- package/build/renderable/imagelayer.d.ts +12 -5
- package/build/renderable/imagelayer.d.ts.map +1 -1
- package/build/renderable/instanced_mesh.d.ts +2 -9
- package/build/renderable/instanced_mesh.d.ts.map +1 -1
- package/build/renderable/mesh.d.ts +306 -5
- package/build/renderable/mesh.d.ts.map +1 -1
- package/build/renderable/nineslicesprite.d.ts +3 -13
- package/build/renderable/nineslicesprite.d.ts.map +1 -1
- package/build/renderable/renderable.d.ts +129 -28
- package/build/renderable/renderable.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +77 -19
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/renderable/sprite3d.d.ts +27 -16
- package/build/renderable/sprite3d.d.ts.map +1 -1
- package/build/renderable/text/bitmaptext.d.ts +13 -3
- package/build/renderable/text/bitmaptext.d.ts.map +1 -1
- package/build/renderable/text/bitmaptextdata.d.ts +0 -4
- package/build/renderable/text/bitmaptextdata.d.ts.map +1 -1
- package/build/renderable/text/text.d.ts +5 -3
- package/build/renderable/text/text.d.ts.map +1 -1
- package/build/renderable/text/textmetrics.d.ts +2 -0
- package/build/renderable/text/textmetrics.d.ts.map +1 -1
- package/build/renderable/trail.d.ts +0 -1
- package/build/renderable/trail.d.ts.map +1 -1
- package/build/renderable/trigger.d.ts +39 -14
- package/build/renderable/trigger.d.ts.map +1 -1
- package/build/renderable/ui/progressbar.d.ts +194 -0
- package/build/renderable/ui/progressbar.d.ts.map +1 -0
- package/build/renderable/ui/uibaseelement.d.ts +48 -3
- package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
- package/build/renderable/ui/uispriteelement.d.ts +19 -3
- package/build/renderable/ui/uispriteelement.d.ts.map +1 -1
- package/build/state/stage.d.ts +22 -0
- package/build/state/stage.d.ts.map +1 -1
- package/build/system/legacy_pool.d.ts +2 -2
- package/build/system/legacy_pool.d.ts.map +1 -1
- package/build/system/pool.d.ts +10 -0
- package/build/system/pool.d.ts.map +1 -1
- package/build/tweens/tween.d.ts +49 -2
- package/build/tweens/tween.d.ts.map +1 -1
- package/build/video/buffer/vertex.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +34 -23
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/effects/blendEffect.d.ts +0 -1
- package/build/video/effects/bloom.d.ts +72 -0
- package/build/video/effects/bloom.d.ts.map +1 -0
- package/build/video/effects/blur.d.ts +3 -2
- package/build/video/effects/blur.d.ts.map +1 -1
- package/build/video/effects/chromaticAberration.d.ts +3 -2
- package/build/video/effects/chromaticAberration.d.ts.map +1 -1
- package/build/video/effects/colorMatrix.d.ts +3 -2
- package/build/video/effects/colorMatrix.d.ts.map +1 -1
- package/build/video/effects/desaturate.d.ts +10 -2
- package/build/video/effects/desaturate.d.ts.map +1 -1
- package/build/video/effects/dissolve.d.ts +3 -2
- package/build/video/effects/dissolve.d.ts.map +1 -1
- package/build/video/effects/dropShadow.d.ts +3 -2
- package/build/video/effects/dropShadow.d.ts.map +1 -1
- package/build/video/effects/flash.d.ts +3 -2
- package/build/video/effects/flash.d.ts.map +1 -1
- package/build/video/effects/glow.d.ts +3 -2
- package/build/video/effects/glow.d.ts.map +1 -1
- package/build/video/effects/hologram.d.ts +3 -2
- package/build/video/effects/hologram.d.ts.map +1 -1
- package/build/video/effects/invert.d.ts +10 -2
- package/build/video/effects/invert.d.ts.map +1 -1
- package/build/video/effects/outline.d.ts +3 -2
- package/build/video/effects/outline.d.ts.map +1 -1
- package/build/video/effects/pixelate.d.ts +3 -2
- package/build/video/effects/pixelate.d.ts.map +1 -1
- package/build/video/effects/radialGradient.d.ts +8 -3
- package/build/video/effects/radialGradient.d.ts.map +1 -1
- package/build/video/effects/scanline.d.ts +3 -2
- package/build/video/effects/scanline.d.ts.map +1 -1
- package/build/video/effects/sepia.d.ts +10 -2
- package/build/video/effects/sepia.d.ts.map +1 -1
- package/build/video/effects/shadereffect.d.ts +10 -4
- package/build/video/effects/shadereffect.d.ts.map +1 -1
- package/build/video/effects/shine.d.ts +3 -2
- package/build/video/effects/shine.d.ts.map +1 -1
- package/build/video/effects/tintPulse.d.ts +3 -2
- package/build/video/effects/tintPulse.d.ts.map +1 -1
- package/build/video/effects/tonemap.d.ts +121 -0
- package/build/video/effects/tonemap.d.ts.map +1 -0
- package/build/video/effects/vignette.d.ts +3 -2
- package/build/video/effects/vignette.d.ts.map +1 -1
- package/build/video/effects/wave.d.ts +3 -2
- package/build/video/effects/wave.d.ts.map +1 -1
- package/build/video/gpu/meshmaterial.d.ts +2 -0
- package/build/video/gpu/meshmaterial.d.ts.map +1 -0
- package/build/video/gpu/vertexformat.d.ts +26 -0
- package/build/video/gpu/vertexformat.d.ts.map +1 -1
- package/build/video/gradient.d.ts +0 -1
- package/build/video/gradient.d.ts.map +1 -1
- package/build/video/renderer.d.ts +111 -7
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/renderstate.d.ts +2 -0
- package/build/video/renderstate.d.ts.map +1 -1
- package/build/video/rendertarget/canvasrendertarget.d.ts +3 -4
- package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/render_target_pool.d.ts +0 -1
- package/build/video/rendertarget/webglrendertarget.d.ts +0 -1
- package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -1
- package/build/video/rendertarget/webgpurendertarget.d.ts +18 -2
- package/build/video/rendertarget/webgpurendertarget.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +1 -0
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/texture/cache.d.ts +1 -3
- package/build/video/texture/frametexture.d.ts +0 -1
- package/build/video/texture/frametexture.d.ts.map +1 -1
- package/build/video/texture/noise_texture2d.d.ts +1 -0
- package/build/video/texture/noise_texture2d.d.ts.map +1 -1
- package/build/video/utils/autodetect.d.ts +0 -2
- package/build/video/webgl/batchers/batcher.d.ts +0 -9
- package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts +13 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/material_batcher.d.ts +4 -2
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/mesh_batcher.d.ts +2 -3
- package/build/video/webgl/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts +4 -0
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/buffer/index.d.ts +0 -1
- package/build/video/webgl/buffer/retained_geometry.d.ts +0 -1
- package/build/video/webgl/glshader.d.ts +5 -3
- package/build/video/webgl/glshader.d.ts.map +1 -1
- package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts +0 -4
- package/build/video/webgl/shaders/multitexture-lit.d.ts.map +1 -1
- package/build/video/webgl/texture/store.d.ts +0 -1
- package/build/video/webgl/webgl_renderer.d.ts +63 -24
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts +17 -0
- package/build/video/webgpu/batchers/lit_quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/mesh_batcher.d.ts +0 -3
- package/build/video/webgpu/batchers/mesh_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/quad_batcher.d.ts +0 -2
- package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgpu/batchers/webgpu_batcher.d.ts +0 -2
- package/build/video/webgpu/batchers/webgpu_batcher.d.ts.map +1 -1
- package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
- package/build/video/webgpu/renderers/tmxlayer/orthogonal.d.ts +0 -2
- package/build/video/webgpu/texture/frametexture.d.ts +0 -2
- package/build/video/webgpu/texture/frametexture.d.ts.map +1 -1
- package/build/video/webgpu/texture/store.d.ts +0 -1
- package/build/video/webgpu/texture/store.d.ts.map +1 -1
- package/build/video/webgpu/webgpu_renderer.d.ts +8 -75
- package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/melonjs-3d/SKILL.md +293 -19
- package/skills/melonjs-3d-assets/SKILL.md +123 -13
- package/skills/melonjs-camera-and-drawing/SKILL.md +57 -1
- package/skills/melonjs-effects-and-shaders/SKILL.md +341 -29
- package/skills/melonjs-input/SKILL.md +48 -4
- package/skills/melonjs-lighting/SKILL.md +45 -1
- package/skills/melonjs-loading-assets/SKILL.md +1 -1
- package/skills/melonjs-particles-and-trails/SKILL.md +106 -4
- package/skills/melonjs-performance/SKILL.md +79 -2
- package/skills/melonjs-physics/SKILL.md +420 -29
- package/skills/melonjs-renderables/SKILL.md +15 -1
- package/skills/melonjs-scenes-and-state/SKILL.md +121 -1
- package/skills/melonjs-tilemaps/SKILL.md +9 -0
- package/skills/melonjs-ui-and-text/SKILL.md +165 -4
- package/build/level/gltf/srgb.d.ts +0 -20
- package/build/level/gltf/srgb.d.ts.map +0 -1
|
@@ -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, 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."
|
|
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, 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."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -20,11 +20,15 @@ This is the single most important thing on this page.
|
|
|
20
20
|
| vertical | **Y-down** — higher `y` is *lower* on screen | Y-up |
|
|
21
21
|
| depth | **+Z forward** — higher `z` is *farther* away | −Z forward |
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
`Camera3d` exposes three rotation angles: `camera.pitch`
|
|
24
24
|
(X, look up/down), `camera.yaw` (Y, look left/right) and `camera.roll` (Z, bank
|
|
25
25
|
the horizon). The view is `R(yaw) ∘ R(pitch) ∘ R(roll)` inverted, and the frustum
|
|
26
26
|
planes come off that same matrix — so culling follows a banked view.
|
|
27
27
|
|
|
28
|
+
The basis those angles produce is **readable and writable**: `getBasis(right,
|
|
29
|
+
up, forward)` reads it, `setBasis(right, up, forward)` poses the camera from
|
|
30
|
+
one.
|
|
31
|
+
|
|
28
32
|
`camera.rotate()` is NOT the way to bank a 3D camera. It writes the inherited
|
|
29
33
|
`currentTransform`, which a 3D view never reads, so the call is silently inert.
|
|
30
34
|
Set `roll`. (On a `Camera2d` it is the other way round: roll IS that transform's
|
|
@@ -89,11 +93,72 @@ class GameStage extends Stage {
|
|
|
89
93
|
`camera.setClipPlanes(near, far)`.
|
|
90
94
|
- **`camera.pos.set(x, y)` is 2-argument and zeroes z.** Use `camera.depth` —
|
|
91
95
|
the documented z accessor — or assign `pos.x`/`pos.y` individually.
|
|
96
|
+
- **`lookAt(target)` solves pitch and yaw only**, so on its own it cannot
|
|
97
|
+
express "up is this direction" and it leaves `roll` alone. Pass an up to
|
|
98
|
+
bank the horizon too: `camera.lookAt(target, up)`, where `up` is what should
|
|
99
|
+
appear UP ON SCREEN. This engine renders Y-down, so a level horizon is
|
|
100
|
+
`(0, -1, 0)`. There is deliberately no default, because giving one would
|
|
101
|
+
silently level every camera that had banked its own horizon.
|
|
102
|
+
- **When the game already has all three axes, hand them over**:
|
|
103
|
+
`camera.setBasis(right, up, forward)`, the writable counterpart of
|
|
104
|
+
`getBasis`. The case that needs it is a view over a curved surface, where up
|
|
105
|
+
is the surface normal and differs at every point, so no pitch and yaw pair
|
|
106
|
+
expresses it and integrating towards one drifts and gimbals at the poles.
|
|
107
|
+
`setBasis` absorbs a triple that has drifted off orthonormal rather than
|
|
108
|
+
leaving the camera holding something that is not a rotation, and it does not
|
|
109
|
+
care what the axes are scaled to: a direction is a direction, so anything
|
|
110
|
+
from a denormal to 1e300 resolves the same way. Note its `up` is the camera's
|
|
111
|
+
local +Y column, which under Y-down points DOWN the screen: the opposite
|
|
112
|
+
sense to `lookAt`'s, and one negation apart.
|
|
92
113
|
- **`worldToLocal` does not project.** It is a 2D camera's offset subtraction.
|
|
93
114
|
To pin a label or marker to a point in the scene use
|
|
94
115
|
`camera.worldToScreen(x, y, z)`, which applies the projection and returns
|
|
95
116
|
`null` behind the camera. See `melonjs-camera-and-drawing`.
|
|
96
117
|
|
|
118
|
+
## Where an up vector earns its keep
|
|
119
|
+
|
|
120
|
+
Three cases. In the first two, `lookAt(target)` on its own is already right and
|
|
121
|
+
an up would be noise. The third is the one it exists for.
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
// 1. ordinary third-person: world up never changes, so say nothing
|
|
125
|
+
camera.setPosition(x, y, z).lookAt(player);
|
|
126
|
+
|
|
127
|
+
// 2. a deliberate dutch angle: this is a ROLL, not an up vector
|
|
128
|
+
camera.lookAt(player);
|
|
129
|
+
camera.roll = Math.PI / 32;
|
|
130
|
+
|
|
131
|
+
// 3. a view over a curved surface: up is the surface normal, and it is
|
|
132
|
+
// different at every point on the globe. No pitch and yaw pair expresses
|
|
133
|
+
// it, and integrating towards it drifts and gimbals over the poles.
|
|
134
|
+
const up = player.pos.clone().sub(planetCentre).normalize(); // surface normal
|
|
135
|
+
camera.setPosition(eye.x, eye.y, eye.z).lookAt(player, up.negate());
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Note the `negate()` in case 3. The surface normal points away from the planet,
|
|
139
|
+
which is the direction the player's head points, and `up` wants what should
|
|
140
|
+
appear UP ON SCREEN. This engine renders Y-down, so `(0, -1, 0)` is a level
|
|
141
|
+
horizon and a world-space "up" direction has to be handed over in that sense.
|
|
142
|
+
Get it backwards and the view is upside down, which is the single most likely
|
|
143
|
+
mistake with this argument.
|
|
144
|
+
|
|
145
|
+
If the game already computes all three axes, skip the solve entirely:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
// a surface frame the game is already maintaining
|
|
149
|
+
camera.setBasis(right, up, forward);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`setBasis` takes the camera's OWN columns, where `up` is local +Y and so points
|
|
153
|
+
down the screen: the opposite sense to `lookAt`'s, one negation apart. It also
|
|
154
|
+
absorbs a frame that has drifted off orthonormal, which one integrated over
|
|
155
|
+
thousands of steps will have.
|
|
156
|
+
|
|
157
|
+
**A camera posed this way and a world rotated into a fixed camera's frame are
|
|
158
|
+
the same picture.** If a game already transforms its world into view space by
|
|
159
|
+
hand, there is nothing to gain by switching; the value of `setBasis` is not
|
|
160
|
+
having to write that transform in the first place.
|
|
161
|
+
|
|
97
162
|
## Depth sorting
|
|
98
163
|
|
|
99
164
|
Under `Camera3d` the world sorts on `"depth"` **every frame** (recursively), so
|
|
@@ -153,6 +218,40 @@ geometry with an opaque one would drag the opaque half into the transparent pass
|
|
|
153
218
|
|
|
154
219
|
**`transparent: false`** pins a mesh to the opaque pass however it is faded.
|
|
155
220
|
|
|
221
|
+
### `depthTest: false` for something with no surface to be occluded on
|
|
222
|
+
|
|
223
|
+
`transparent` decides the PASS, and the transparent pass turns depth *writing*
|
|
224
|
+
off on its own so overlapping blended draws blend instead of fighting. Whether
|
|
225
|
+
geometry in front of a mesh **hides** it is a separate question, and
|
|
226
|
+
`depthTest` is where you answer it. The default, `true`, is what you want for
|
|
227
|
+
anything genuinely in the scene — a muzzle flash behind a wall should still be
|
|
228
|
+
behind the wall, and this is not a synonym for "additive".
|
|
229
|
+
|
|
230
|
+
Set it `false` for a mesh standing in for a screen-space effect: an additive
|
|
231
|
+
glow carrying a world position only so it can sort and move with the thing it
|
|
232
|
+
belongs to. A world-space caption billboarded over a prop wants it for the
|
|
233
|
+
same reason, since it has no surface for the prop's own foliage to be in front
|
|
234
|
+
of. Left depth tested, a flat billboard is sliced along a hard straight
|
|
235
|
+
line the moment any geometry is nearer at some pixel. That shows up worst
|
|
236
|
+
around something round — a glow on a rock near a planet's limb gets cut,
|
|
237
|
+
and no offset escapes it, because the near surface bulges further toward the
|
|
238
|
+
camera than any offset you would dare apply.
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
const halo = new Sprite3d(0, 0, {
|
|
242
|
+
image: glowTexture,
|
|
243
|
+
width: rock.radius * 3, height: rock.radius * 3,
|
|
244
|
+
billboard: "spherical",
|
|
245
|
+
blendMode: "additive",
|
|
246
|
+
transparent: true, // the blended pass
|
|
247
|
+
alphaCutoff: 0, // do not cut the soft falloff
|
|
248
|
+
depthTest: false, // and do not let the world slice it
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Honoured in the transparent pass on both GPU backends; the Canvas renderer has
|
|
253
|
+
no depth buffer and ignores it.
|
|
254
|
+
|
|
156
255
|
Blending uses the renderable's existing `blendMode`, so a glow is one property.
|
|
157
256
|
The advanced modes (`"overlay"`, `"difference"`, and the rest that need a
|
|
158
257
|
compositing pass) fall back to `"normal"` here, on both backends:
|
|
@@ -233,10 +332,14 @@ mesh shaders, so a screen-space overlay stays clean with no work.
|
|
|
233
332
|
**A custom mesh shader is not fogged unless it asks to be.** Fog is compiled
|
|
234
333
|
into the engine's own mesh programs — `#define FOG` on WebGL, an `enable_fog`
|
|
235
334
|
overridable constant on WebGPU — and a shader you supply is yours: the engine
|
|
236
|
-
binds it as written and never substitutes a fogged variant. So a mesh
|
|
237
|
-
`
|
|
238
|
-
— nothing throws, and the camera's fog is simply not applied — but it is
|
|
239
|
-
surprising.
|
|
335
|
+
binds it as written and never substitutes a fogged variant. So a mesh given its
|
|
336
|
+
own `GLShader` keeps full contrast while the scene around it recedes. It is
|
|
337
|
+
safe — nothing throws, and the camera's fog is simply not applied — but it is
|
|
338
|
+
usually surprising.
|
|
339
|
+
|
|
340
|
+
A hosted `ShaderEffect` is the other case and needs nothing: its body is
|
|
341
|
+
spliced into the engine's own mesh program as a colour hook, and fog is applied
|
|
342
|
+
to the hook's result, so such a mesh fogs like any other.
|
|
240
343
|
|
|
241
344
|
To opt in, declare the same uniforms and the engine will feed them, because the
|
|
242
345
|
fog values are pushed to any mesh program that declares them rather than only to
|
|
@@ -475,6 +578,22 @@ Billboarding needs a `Camera3d` drawing the frame; under a 2D camera the quad
|
|
|
475
578
|
renders fixed-orientation. **Any other string falls through to the spherical
|
|
476
579
|
branch**, so a guessed value like `"upright"` silently gives you spherical.
|
|
477
580
|
|
|
581
|
+
A billboard takes its **orientation** from the camera, so `rotate()` has no
|
|
582
|
+
effect on one: a card already turned to face the camera has no free rotation
|
|
583
|
+
left to give. Everything that is not an orientation still applies, `scale()`
|
|
584
|
+
and `meshScale` included, so animating the size of a shockwave or a pickup is
|
|
585
|
+
the ordinary call and the cull bounds grow with it:
|
|
586
|
+
|
|
587
|
+
```js
|
|
588
|
+
const wave = new Sprite3d(x, y, { image: "ring", width: 64, height: 64,
|
|
589
|
+
billboard: "spherical", transparent: true });
|
|
590
|
+
wave.scale(1 + 3 * t); // grows; `scale` accumulates, so drive it
|
|
591
|
+
// from the authored size, not per frame
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
For an in-plane rotation, a bolt streak or an exhaust, use `billboard: false`
|
|
595
|
+
and orient the quad yourself.
|
|
596
|
+
|
|
478
597
|
## Lighting
|
|
479
598
|
|
|
480
599
|
`Light3d` takes **one argument — the options object** (it has no `x, y, z`
|
|
@@ -527,23 +646,147 @@ and spot, a stylised quadratic falloff, not inverse-square),
|
|
|
527
646
|
**`Light2d` is 2D-only** and produces visible artifacts under perspective
|
|
528
647
|
projection. Do not combine it with `Camera3d`.
|
|
529
648
|
|
|
649
|
+
**`lit: true` only does anything under a `Camera3d`.** Lighting a mesh needs
|
|
650
|
+
world-space normals and a world-space fragment position, and the 2D-camera path
|
|
651
|
+
has neither: its vertices are already the projected output, so there is no world
|
|
652
|
+
space left to light in. Such a mesh degrades to unlit and warns once, naming the
|
|
653
|
+
fix. If you want lit 3D props in an otherwise 2D game, set
|
|
654
|
+
`cameraClass: Camera3d` on the application. (For 2D **sprites**, lighting is a
|
|
655
|
+
separate system that works under a 2D camera as normal, see the lighting skill.)
|
|
656
|
+
|
|
657
|
+
A `lit` mesh takes a **tangent-space normal map**, so relief comes from the
|
|
658
|
+
material rather than from geometry — `settings.normalMap` (a loader key or any
|
|
659
|
+
image-like source), or MTL `map_bump` / `bump` / `norm`, which the MTL loader
|
|
660
|
+
fetches and applies per material. The tangent frame is derived per fragment, so
|
|
661
|
+
the model needs no tangent attribute, and an unmapped mesh is unaffected. This
|
|
662
|
+
is the 3D counterpart of the `Sprite` normal maps in the lighting skill; they
|
|
663
|
+
are separate systems and a `Mesh` uses this one.
|
|
664
|
+
|
|
665
|
+
### What the ground-shadow tier can and cannot do
|
|
666
|
+
|
|
667
|
+
Check this table before claiming a setting is unsupported. Every one of these
|
|
668
|
+
is read **every draw**, so all of them can be animated.
|
|
669
|
+
|
|
670
|
+
| setting | `Mesh` / `Sprite3d` / `GLTFModel` part | `InstancedMesh` |
|
|
671
|
+
|---|---|---|
|
|
672
|
+
| `castGroundShadow` | yes | yes |
|
|
673
|
+
| `shadowGroundY` | yes, the caster's own floor | yes, **one plane** through the SET's origin |
|
|
674
|
+
| `shadowGroundNormal` | yes | yes, one plane for the set |
|
|
675
|
+
| `shadowOpacity` | yes | yes |
|
|
676
|
+
| `shadowScale` | yes | yes |
|
|
677
|
+
| `shadowOffset` | yes | yes, one throw for the set |
|
|
678
|
+
| `shadowStretch` | yes | yes, one stretch for the set |
|
|
679
|
+
| `shadowDirectionX` / `Z` | yes | yes |
|
|
680
|
+
| `shadowLight` | yes | yes |
|
|
681
|
+
| height fade as the caster rises | yes | **no** |
|
|
682
|
+
| a different value per instance | n/a | **no**, and there is no plan for one |
|
|
683
|
+
|
|
684
|
+
The two "no"s are the same limitation seen twice: a set gets ONE shadow plane
|
|
685
|
+
and ONE shared quad, so the instance's own height is dropped and nothing can
|
|
686
|
+
vary between instances. Everything else is one value for the whole set, which
|
|
687
|
+
is what a set means. A scatter over curved ground is therefore one set per flat
|
|
688
|
+
facet, and a flying scatter wanting a height fade has to be per-object meshes.
|
|
689
|
+
|
|
690
|
+
Nothing here is per-object-only. If a claim that a setting "does not work on an
|
|
691
|
+
`InstancedMesh`" is in your head, it is out of date: `shadowOffset` and
|
|
692
|
+
`shadowStretch` both reached the instanced tier in 20.8.
|
|
693
|
+
|
|
530
694
|
Ground shadows are **on by default** (the `castGroundShadow` application
|
|
531
695
|
setting), and need a GPU backend and a `Camera3d`. As a blanket default they
|
|
532
696
|
skip geometry with no vertical extent — a ground plane. Per object,
|
|
533
697
|
`castGroundShadow: true`/`false` overrides the app setting and is obeyed as
|
|
534
|
-
given, safeguard included; `shadowGroundY` names the floor the blob lands on
|
|
698
|
+
given, safeguard included; `shadowGroundY` names the floor the blob lands on,
|
|
699
|
+
`shadowOpacity` how dark it is and `shadowScale` how big it is.
|
|
535
700
|
|
|
536
701
|
The blob is an ellipse sized to the caster's own footprint and placed at the
|
|
537
|
-
caster's x/z
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
702
|
+
caster's x/z. So a tall or narrow object (a character, a tree, a pickup) shows
|
|
703
|
+
its shadow clearly, while a wide, flat-bottomed one resting on the floor covers
|
|
704
|
+
its own completely from a camera looking down at it. That is the shadow
|
|
705
|
+
behaving correctly, not a bug.
|
|
541
706
|
|
|
542
707
|
**Do not chase it by raising `shadowGroundY`.** Lifting the plane does not slide
|
|
543
708
|
the blob out from under the object, it floats the blob *up* — and past a few
|
|
544
|
-
units it projects over the top of the caster as a dark halo ringing it.
|
|
545
|
-
|
|
546
|
-
|
|
709
|
+
units it projects over the top of the caster as a dark halo ringing it.
|
|
710
|
+
|
|
711
|
+
The simplest fix is a bigger blob, so its edge shows past the object:
|
|
712
|
+
|
|
713
|
+
```js
|
|
714
|
+
mesh.shadowScale = 1.5; // 1 is the default; 0 hides the shadow
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
`shadowScale` works everywhere, `InstancedMesh` included. Changing it on a
|
|
718
|
+
set rewrites the shared quad's four corners in place rather than rebuilding
|
|
719
|
+
it, as does a `shadowLight` that moves, so an animated sun costs no
|
|
720
|
+
allocation.
|
|
721
|
+
|
|
722
|
+
Or, for the look of a sun that is not overhead, use the shape controls:
|
|
723
|
+
|
|
724
|
+
```js
|
|
725
|
+
mesh.shadowOffset = 1.5; // slide it out, in blob radii
|
|
726
|
+
mesh.shadowDirectionX = 1; // the ground direction it is cast along
|
|
727
|
+
mesh.shadowDirectionZ = 0;
|
|
728
|
+
mesh.shadowStretch = 2; // and lengthen it along the same line
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
`shadowOffset` is measured in multiples of the blob's **own radius**, not in
|
|
732
|
+
world units, so one value serves a boulder and a pebble and every part of a
|
|
733
|
+
glTF model. About `stretch - 1` is what keeps the shadow attached to its
|
|
734
|
+
caster. It is honoured **only when `shadowGroundY` is set**, because the
|
|
735
|
+
blob is a flat quad on one named plane: sliding it across a plane the game has
|
|
736
|
+
not named puts it where there may be no floor. `shadowStretch` is clamped to 3
|
|
737
|
+
and the blob fades as it pulls, so an extreme value degrades to nothing rather
|
|
738
|
+
than to a smear.
|
|
739
|
+
|
|
740
|
+
An `InstancedMesh` honours both, as **one** offset and **one** stretch for the
|
|
741
|
+
whole set, which is all a shared set can carry. Both ride the quad the blobs
|
|
742
|
+
share rather than the matrix that also places them: a stretch in the matrix
|
|
743
|
+
would scale the instance positions too and smear the scatter instead of
|
|
744
|
+
lengthening its blobs. So a scatter can sit next to loose props under the same
|
|
745
|
+
sun without looking lit from somewhere else.
|
|
746
|
+
|
|
747
|
+
`shadowLight` takes the direction from a `Light3d` you name, re-read every
|
|
748
|
+
draw, so a moving sun carries the shadows with it. Nothing is inferred: the
|
|
749
|
+
engine never picks a dominant light, because a scene with several has no
|
|
750
|
+
non-arbitrary answer and one with none has no answer at all.
|
|
751
|
+
|
|
752
|
+
### A floor that is not level
|
|
753
|
+
|
|
754
|
+
`shadowGroundY` says where the floor is; `shadowGroundNormal` says which way
|
|
755
|
+
it faces:
|
|
756
|
+
|
|
757
|
+
```js
|
|
758
|
+
prop.shadowGroundY = floorY;
|
|
759
|
+
prop.shadowGroundNormal = [Math.sin(0.35), -Math.cos(0.35), 0]; // a 20 deg slope
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
World up is `(0, -1, 0)`, because render space is Y-down, and that is the
|
|
763
|
+
default. The blob is **rotated** onto the plane rather than projected, so it
|
|
764
|
+
keeps its size; it still sits directly under the caster, since the normal
|
|
765
|
+
turns it rather than moving it. The tilt is clamped at 75 degrees, past which
|
|
766
|
+
an edge-on blob has nothing left to show. It needs `shadowGroundY`, for the
|
|
767
|
+
same reason the offset does: tilting the bounds fallback has no anchor to turn
|
|
768
|
+
about.
|
|
769
|
+
|
|
770
|
+
On an `InstancedMesh` one plane serves the **whole set**: it passes through
|
|
771
|
+
the set's origin at `shadowGroundY`, faces the normal, and every blob is
|
|
772
|
+
projected onto it whatever height its own instance sits at, exactly as the
|
|
773
|
+
level case puts every blob at one height. A scatter over curved ground is
|
|
774
|
+
therefore one set per flat facet, each with the facet's own plane, not one
|
|
775
|
+
set with an averaged normal.
|
|
776
|
+
|
|
777
|
+
All three compose on a set: the stretch turns the shared quad, the offset
|
|
778
|
+
slides the whole set along the light, and both are carried onto the tilted
|
|
779
|
+
plane rather than applied flat, so a bank's planting lies along its slope
|
|
780
|
+
instead of hovering across it.
|
|
781
|
+
|
|
782
|
+
Give blobs to the plants that stand apart. Dense low ground cover is better
|
|
783
|
+
left without: a thousand blobs each as wide as its own fern sum to a darkened
|
|
784
|
+
hillside, not to a thousand shadows, and ground cover is the ground.
|
|
785
|
+
|
|
786
|
+
These are **art direction, not a projection**. A stretched ellipse is not a
|
|
787
|
+
silhouette and has no contact with terrain, so on ground that is not the plane
|
|
788
|
+
you named it will not lie on it. Direction-correct shadows want a shadow map,
|
|
789
|
+
which this tier does not do.
|
|
547
790
|
|
|
548
791
|
### Get the sign right: the floor is a GREATER y
|
|
549
792
|
|
|
@@ -571,10 +814,28 @@ caster's own y and check which one is larger.
|
|
|
571
814
|
|
|
572
815
|
## Collision in 3D
|
|
573
816
|
|
|
574
|
-
Use
|
|
575
|
-
|
|
576
|
-
response carries `overlapNZ` / `overlapZ` for the depth axis.
|
|
577
|
-
|
|
817
|
+
Use a body with a depth shape, not a hand-rolled distance check. Two shapes
|
|
818
|
+
carry depth — `Box3d` and `Sphere` — and any pair of them is resolved in three
|
|
819
|
+
dimensions; the response carries `overlapNZ` / `overlapZ` for the depth axis.
|
|
820
|
+
|
|
821
|
+
Pick by whether the thing turns. `Box3d` is world-axis-aligned and cannot be
|
|
822
|
+
oriented, so it fits walls, floors and crates and misfits anything that
|
|
823
|
+
tumbles. `Sphere` has no orientation at all, which makes it the shape for
|
|
824
|
+
spinning debris, projectiles, and anything spread over a curved surface: on a
|
|
825
|
+
globe each object's own "up" points a different way, so one axis-aligned box
|
|
826
|
+
cannot fit any of them.
|
|
827
|
+
|
|
828
|
+
A hand-rolled check is the usual mistake here and it costs twice — it throws
|
|
829
|
+
away the penetration vector the response already carries, and it is invisible
|
|
830
|
+
to the debug plugin's hitbox overlay, which draws from `body.shapes`. If the
|
|
831
|
+
red boxes never appear, the reason is usually that there is no body to draw.
|
|
832
|
+
|
|
833
|
+
Against a planar shape, either 3D shape degrades to its XY footprint with the
|
|
834
|
+
planar side read as unbounded along Z, so a sphere or box body still collides
|
|
835
|
+
with an ordinary collision layer, just without a depth result. And mind the
|
|
836
|
+
normal: a box pair separates along one world axis, a sphere along the line
|
|
837
|
+
between two centres, so a sphere contact fills the 2D and depth halves of the
|
|
838
|
+
response at the same time. See the physics skill for the full contract.
|
|
578
839
|
|
|
579
840
|
## glTF / GLB scenes
|
|
580
841
|
|
|
@@ -583,6 +844,13 @@ Loaded through the same level director as everything else:
|
|
|
583
844
|
```js
|
|
584
845
|
await loader.preload([{ name: "diorama", type: "glb", src: "data/diorama.glb" }]);
|
|
585
846
|
level.load("diorama", { scale, castGroundShadow, shadowGroundY, onLoaded });
|
|
847
|
+
|
|
848
|
+
// every ground-shadow setting travels, not just those two: a loaded scene can
|
|
849
|
+
// be told where its sun is, and `shadowLight` is re-read every draw
|
|
850
|
+
level.load("diorama", {
|
|
851
|
+
scale, castGroundShadow: true, shadowGroundY: 0,
|
|
852
|
+
shadowLight: sun, shadowOffset: 0.6, shadowStretch: 1.8, async: true,
|
|
853
|
+
});
|
|
586
854
|
```
|
|
587
855
|
|
|
588
856
|
`.obj` / `.mtl` are also supported loader types.
|
|
@@ -642,6 +910,7 @@ To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
|
|
|
642
910
|
| a `lit` mesh renders fullbright | it had no normals — supply them, or let the engine generate them |
|
|
643
911
|
| a gradient across one mesh is impossible | `tint` is per object — use `vertexColors` / `setVertexColor` |
|
|
644
912
|
| a mesh stays solid as you fade it out | meshes render opaque; only `alpha` 0 (hidden) and 1 differ |
|
|
913
|
+
| 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` |
|
|
645
914
|
| vertex colour applies under a 2D camera but not `Camera3d` | wrote the array directly without setting `needsUpdate` |
|
|
646
915
|
| `Mesh: vertexColors has N entries, expected M` | one colour per *vertex*, not per triangle or per index |
|
|
647
916
|
| nothing renders, or a backdrop covers everything | wrong depth sign — "far" is *larger* z when looking along +Z |
|
|
@@ -660,11 +929,16 @@ To branch rather than fail, read `app.renderer.supportsDepthBuffer` after
|
|
|
660
929
|
| fog does not match the sky after a background fade | an explicit `color` was passed; omit it to track `renderer.backgroundColor` |
|
|
661
930
|
| geometry clips before it has finished fading | fog `far` beyond the clip far — omit the distances and they default to the clip planes |
|
|
662
931
|
| one marker must stay readable in fog | `fog: false` on that mesh |
|
|
663
|
-
| an object casts no visible shadow | wide and flat-bottomed — its own blob is underneath it;
|
|
932
|
+
| an object casts no visible shadow | wide and flat-bottomed — its own blob is underneath it; raise `shadowScale` (or slide it out with `shadowOffset`), not `shadowGroundY`, which haloes it instead of revealing it |
|
|
664
933
|
| a dark ring around the top of an object | `shadowGroundY` lifted too far, floating the blob up into the caster |
|
|
665
934
|
| a mesh sits at the wrong depth after being added | `autoDepth` overwrote `pos.z` with the child index — pass `addChild(mesh, z)` |
|
|
666
935
|
| a mesh sits half its size off | `anchorPoint` — only on the 2D-camera path; a `Camera3d` mesh pivots on its model origin |
|
|
667
936
|
| a billboard tips over when the camera looks down | `"spherical"`, or a mistyped mode string falling through to it — use `true` / `"cylindrical"` |
|
|
937
|
+
| `rotate()` does nothing on a billboard | the camera owns its orientation; `billboard: false` and orient the quad yourself |
|
|
938
|
+
| the camera will not tilt its horizon to an arbitrary up | `lookAt(target)` solves pitch and yaw only; pass the up as the next argument, or hand all three axes to `setBasis` |
|
|
939
|
+
| the view is upside down after `lookAt(target, up)` | `up` is the direction that should appear UP ON SCREEN, and this is Y-down, so a level horizon is `(0, -1, 0)` |
|
|
940
|
+
| `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 |
|
|
941
|
+
| 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 |
|
|
668
942
|
| an object jumps to the camera plane | `Vector3d.set(x, y)` / `camera.pos.set(x, y)` zeroed its z |
|
|
669
943
|
| lighting looks wrong under a 3D camera | `Light2d` used instead of `Light3d` |
|
|
670
944
|
|
|
@@ -40,6 +40,13 @@ or it renders flat. See `melonjs-3d` for the camera.
|
|
|
40
40
|
| `lightIntensityScale` | — | keep authored intensity ratios instead of normalising every light to 1 |
|
|
41
41
|
| `castGroundShadow` | inherits the app setting (**on**) | blob shadows for this scene's meshes |
|
|
42
42
|
| `shadowGroundY` | each object's own base | world Y of the floor the blobs land on |
|
|
43
|
+
| `shadowOpacity` | `0.45` | how dark the blobs are |
|
|
44
|
+
| `shadowScale` | `1` | blob footprint multiplier; `0` or less hides them |
|
|
45
|
+
| `shadowOffset` | `0` | slide them along the light, in blob radii; needs `shadowGroundY` |
|
|
46
|
+
| `shadowStretch` | `1` | lengthen them along it, clamped to 3 |
|
|
47
|
+
| `shadowDirectionX` / `shadowDirectionZ` | `0` | the ground direction they are cast along |
|
|
48
|
+
| `shadowGroundNormal` | world up | up normal of the floor, for a scene that is not level |
|
|
49
|
+
| `shadowLight` | — | a `Light3d` to take the direction from, re-read every draw |
|
|
43
50
|
|
|
44
51
|
`onLoaded` receives the level id — it is a "done" signal, not a handle on the
|
|
45
52
|
scene. You need it, or `async`: with the game loop running, `level.load` stops
|
|
@@ -63,6 +70,33 @@ level.load("diorama", { scale: 50, onLoaded: () => {
|
|
|
63
70
|
}});
|
|
64
71
|
```
|
|
65
72
|
|
|
73
|
+
### The same options from a Tiled `Trigger`
|
|
74
|
+
|
|
75
|
+
A `Trigger` authored in Tiled forwards these to `level.load()`, so a trigger that
|
|
76
|
+
walks the player into a glTF level carries the scene's settings as Tiled
|
|
77
|
+
properties:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
world.addChild(new Trigger(x, y, {
|
|
81
|
+
shapes: [new Rect(0, 0, 100, 100)],
|
|
82
|
+
to: "diorama", // the level id — `to`, not `level`
|
|
83
|
+
scale: 50,
|
|
84
|
+
rightHanded: false,
|
|
85
|
+
castGroundShadow: false,
|
|
86
|
+
}));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Every `level.load` option above travels, including the whole ground-shadow set,
|
|
90
|
+
and so do `container`, `onLoaded`, `flatten` and `setViewportBounds`. The list is
|
|
91
|
+
**derived** from `level.loadOptions` rather than restated, so an option added to
|
|
92
|
+
a level format arrives here for free. It used to be hand-written and had fallen
|
|
93
|
+
three releases behind the glTF path (#1649).
|
|
94
|
+
|
|
95
|
+
**`async` is deliberately excluded.** A trigger with a `color` + `duration`
|
|
96
|
+
sets it itself, because it needs the promise to sequence its fade and reveal; a
|
|
97
|
+
trigger without one loads fire-and-forget and never sets it. Either way an
|
|
98
|
+
authored `async` property is ignored rather than honoured.
|
|
99
|
+
|
|
66
100
|
## `rightHanded` — and the default that differs between the two APIs
|
|
67
101
|
|
|
68
102
|
glTF is authored **Y-up, right-handed**; melonJS is **Y-down, +Z forward**.
|
|
@@ -103,6 +137,54 @@ asset fails to load. Export uncompressed.
|
|
|
103
137
|
`"auto"`) drives sampling; glTF materials that declare their own sampler carry
|
|
104
138
|
it through. Pixel-art models want `"nearest"`.
|
|
105
139
|
|
|
140
|
+
### `baseColorFactor` is LINEAR; a `tint` is sRGB
|
|
141
|
+
|
|
142
|
+
The loader handles this for you. It only bites when you build meshes yourself
|
|
143
|
+
from `loader.getGLTF(name).nodes` — which is what you do for per-node control,
|
|
144
|
+
since there is no public per-node `Mesh` factory — and reach for the obvious
|
|
145
|
+
`* 255`:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
// WRONG — every untextured material renders far too dark
|
|
149
|
+
mesh.tint.setColor(f[0] * 255, f[1] * 255, f[2] * 255, 1);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
glTF defines `pbrMetallicRoughness.baseColorFactor` in **linear** space (spec
|
|
153
|
+
3.9.2), while a melonJS `tint` is 8-bit **sRGB** — the same space as a CSS
|
|
154
|
+
colour or a texel out of a PNG. They are not the same number, and the gap is
|
|
155
|
+
widest exactly where art tends to live:
|
|
156
|
+
|
|
157
|
+
| linear | `* 255` | correct |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| 0.05 | 13 | 63 |
|
|
160
|
+
| 0.20 | 51 | 124 |
|
|
161
|
+
| 0.42 | 107 | 173 |
|
|
162
|
+
| 0.72 | 184 | 221 |
|
|
163
|
+
| 1.00 | 255 | 255 |
|
|
164
|
+
|
|
165
|
+
`Color.setLinear` does the encoding for you — it is the linear-space sibling of
|
|
166
|
+
`setFloat`, which takes the same `0..1` range but treats it as already-sRGB:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
mesh.tint.setLinear(...node.baseColorFactor); // encodes; what you want
|
|
170
|
+
mesh.tint.setFloat(...node.baseColorFactor); // does NOT encode; too dark
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
It clamps, so the slightly out-of-range factors some exporters emit cannot turn
|
|
174
|
+
into `NaN`, and it keeps the encoded value as a float rather than rounding
|
|
175
|
+
through 8 bits.
|
|
176
|
+
|
|
177
|
+
Measured on a scene whose every material went through `* 255`: mean frame luma
|
|
178
|
+
85.5 against 147.8 once encoded, with each channel sitting 60 to 73 counts low.
|
|
179
|
+
|
|
180
|
+
**Fix it early.** It is nearly invisible as a bug, because the scene still
|
|
181
|
+
looks coherent — just moody — so lighting and bloom thresholds get tuned
|
|
182
|
+
against the wrong values. Correcting the encoding afterwards blows the frame
|
|
183
|
+
out and forces a lighting retune, which is a much bigger job than the encode.
|
|
184
|
+
|
|
185
|
+
This applies only to factors coming out of glTF. Colours **you** author for the
|
|
186
|
+
screen, a HUD tint or a glow, are already sRGB: `* 255` is right for those.
|
|
187
|
+
|
|
106
188
|
## Lights
|
|
107
189
|
|
|
108
190
|
Authored `KHR_lights_punctual` lights — sun, point, spot — become `Light3d`
|
|
@@ -197,11 +279,21 @@ Omit `shadowGroundY` and each blob sits at its own object's base at full
|
|
|
197
279
|
strength, which is right for props already resting on the ground. Set it when
|
|
198
280
|
things jump or fly, so the shadow stays on the floor and shrinks with height.
|
|
199
281
|
|
|
200
|
-
A blob
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
the
|
|
282
|
+
A blob sits on its caster's x/z by default, so a wide flat-bottomed prop hides
|
|
283
|
+
its own shadow under itself. The knob for that is `shadowScale`, which widens
|
|
284
|
+
the blob until its edge shows past the prop, or `shadowOffset` plus a direction,
|
|
285
|
+
which slides it clear. **Not** `shadowGroundY`: raising that floats the blob up
|
|
286
|
+
over the object as a dark ring instead of sliding it out, because it is for
|
|
287
|
+
things that leave the ground, not a visibility control.
|
|
288
|
+
|
|
289
|
+
For a light that is not overhead, name one: `shadowLight` takes the direction
|
|
290
|
+
from a `Light3d` and is re-read every draw, so a day or night cycle carries
|
|
291
|
+
every shadow in the scene with it. Nothing is inferred — a scene with no named
|
|
292
|
+
light and no `shadowDirectionX`/`Z` gets no direction, because a scene with
|
|
293
|
+
several lights has no non-arbitrary answer. `shadowStretch` lengthens the blob
|
|
294
|
+
along that direction and fades it as it pulls; `shadowGroundNormal` lays it on
|
|
295
|
+
a floor that is not level. All of these apply to an `InstancedMesh` too, as one
|
|
296
|
+
value for the whole set.
|
|
205
297
|
|
|
206
298
|
## OBJ/MTL
|
|
207
299
|
|
|
@@ -226,16 +318,33 @@ const mesh = new Mesh(400, 300, {
|
|
|
226
318
|
world.addChild(mesh);
|
|
227
319
|
```
|
|
228
320
|
|
|
229
|
-
**The MTL loader fetches the textures it references itself** — `map_Kd
|
|
230
|
-
`map_d` are resolved relative to the `.mtl` and preloaded for
|
|
231
|
-
no image entry in the manifest above. Passing `texture:`
|
|
232
|
-
that and pins *one* binding over the whole model, which on
|
|
233
|
-
model suppresses the per-material texture split; leave it out
|
|
234
|
-
that.
|
|
321
|
+
**The MTL loader fetches the textures it references itself** — `map_Kd`,
|
|
322
|
+
`map_d` and `map_bump` are resolved relative to the `.mtl` and preloaded for
|
|
323
|
+
you, so there is no image entry in the manifest above. Passing `texture:`
|
|
324
|
+
explicitly overrides that and pins *one* binding over the whole model, which on
|
|
325
|
+
a multi-material model suppresses the per-material texture split; leave it out
|
|
326
|
+
unless you mean that.
|
|
235
327
|
|
|
236
328
|
MTL contributes `Kd` (diffuse tint), `d` / `Tr` (opacity), `Ke` (emissive),
|
|
237
|
-
`Ks` + `Ns` (specular highlight), `map_Kd` (diffuse texture)
|
|
238
|
-
(per-texel opacity)
|
|
329
|
+
`Ks` + `Ns` (specular highlight), `map_Kd` (diffuse texture), `map_d`
|
|
330
|
+
(per-texel opacity) and `map_bump` / `bump` / `norm` (tangent-space normal
|
|
331
|
+
map — needs `lit: true`, and the tangent frame is derived per fragment, so the
|
|
332
|
+
model needs no tangent attribute). All three normal-map spellings are read as
|
|
333
|
+
normal maps even though the format specifies two of them as height maps, which
|
|
334
|
+
is what every exporter actually writes. Specular *maps* (`map_Ks`) and the PBR
|
|
335
|
+
extension's `map_Pr` / `map_Pm` are not supported and warn — the `Pr` / `Pm`
|
|
336
|
+
*scalars* are read and approximated onto the specular terms, the maps would
|
|
337
|
+
need a real PBR model.
|
|
338
|
+
|
|
339
|
+
`Ka` (ambient) and `illum` (illumination model) are read onto the material and
|
|
340
|
+
readable from `loader.getMTL(name)`, but take no part in shading. That is
|
|
341
|
+
deliberate rather than pending: `Ka` predates scene-wide ambient light and would
|
|
342
|
+
fight `Stage.ambientLightingColor`, and `illum` enumerates fixed-function
|
|
343
|
+
behaviours this renderer has no equivalent for. In particular an `illum` of 0 or
|
|
344
|
+
1 nominally means "no specular", and the engine does **not** honour that — `Ns`
|
|
345
|
+
stays the only gate, so a model that renders correctly today is not restyled by
|
|
346
|
+
a field its exporter wrote inconsistently.
|
|
347
|
+
|
|
239
348
|
`loader.getOBJ(name)` and `loader.getMTL(name)` return the parsed data if you
|
|
240
349
|
want it directly. OBJ has no scene graph, no lights, no animation — use glTF for
|
|
241
350
|
anything beyond a single static model.
|
|
@@ -380,6 +489,7 @@ need a prefix.
|
|
|
380
489
|
| one part of a rig renders rigid while its curves exist | it was re-parented mid-pose, baking the inverse into `matrix_parent_inverse` |
|
|
381
490
|
| a merged mesh lights wrong along one axis | it inherited the active object's non-uniform scale on join — apply transforms |
|
|
382
491
|
| an authored palette comes out uniformly dark, but white is correct | linear values written to a strip that is saved verbatim |
|
|
492
|
+
| every untextured glTF material renders too dark, white unaffected | `baseColorFactor` is linear and a `tint` is sRGB — `tint.setLinear(...)`, not `* 255` or `setFloat` |
|
|
383
493
|
| `getGLTF(name).nodes[0]` geometry lands in the wrong place | `nodes` is per primitive, each with its own `world`; merge to one primitive and export at the origin |
|
|
384
494
|
| a prop casts no visible shadow | wide and flat-bottomed — the blob is under it; `shadowGroundY` haloes it rather than revealing it |
|
|
385
495
|
| shadows only show on casters near the camera | `shadowGroundY` is on the wrong side — Y-down means the floor is a **greater** y, so a `pos.y - lift` puts the blob inside the caster and the depth test leaves only a hairline ring |
|