Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

- `Light3d`: `direction` and `position` accept a `Vector3d` as well as an `[x, y, z]` array, so a value the game already holds goes straight in — `color` already took a `Color`, a string or an array. The vector is read, not retained. Passing one used to be read by index and silently produce a `NaN` direction, so the light contributed nothing ([#1661](https://github.com/melonjs/melonJS/issues/1661))

- Typings: a parsed glTF node's geometry is readable from TypeScript. `GLTFData.nodes` was `object[]` with its fields listed in prose, so `node.vertices` did not compile — the array is `GLTFNode[]` now. `Mesh`'s `texture` accepts a `Texture2d` and its `indices` a `Uint32Array`, both of which it already handled; a shader `Asset` may carry the `{glsl, wgsl}` pair the loader has always parsed

### Fixed
- Renderable: opacity now cascades — `alpha` multiplies with the alpha already on the renderer instead of replacing it, so a child at 0.5 inside a parent at 0.5 draws at 0.25. **A nested renderable that was visibly opaque under a faded ancestor will now fade with it**
- GLTFModel: a loaded model reported no bounds at all, so a model with a `Body` was filed in the broadphase away from where it stood and collided with nothing. Its extent now comes from the glTF bounding box, measured once at load
Expand All @@ -22,6 +24,7 @@
- Mesh: a mesh with `normalize: false` and an explicit `scale` now sizes itself from its geometry when no `width`/`height` is given. It reported a zero-size box while drawing at full size, misleading frustum culling, pointer picking and the broadphase alike
- Mesh: a `ShaderEffect` attached to a mesh drew the geometry unplaced and without the camera on WebGL, and was refused on WebGPU with a message naming the wrong reason ([#1658](https://github.com/melonjs/melonJS/issues/1658))
- `Sprite3d`: `fog: false` reaches the mesh — a sprite builds its own settings for `Mesh` and did not copy it, so a sun could not be kept out of the haze. `transparent` and `castGroundShadow` are documented on `Sprite3d` too
- Typings: `setUniform` accepts a number. It was typed `object|Float32Array`, so setting a `float` uniform — the commonest case, and the one the method's own examples show — did not compile from TypeScript. Scalars, arrays, `Float32Array` and any `toArray()`-bearing object are now all in the signature
- Typings: documented options no longer fail to compile — the `Noise` settings `NoiseTexture2d` forwards, the `Mesh` settings `InstancedMesh` forwards, `setCurrentAnimation(name, { loop: true })`, non-object values for `Container#setChildsProperty`, an `HTMLCanvasElement` as `image`/`texture`, and `super.update(dt)` in a custom `Stage`. Settings shapes are exported as `MeshSettings`, `InstancedMeshSettings`, `NoiseTexture2dSettings` and `AnimationOptionsInput`

## [20.4.0] (melonJS 2) - _2026-09-09_
Expand Down
28 changes: 28 additions & 0 deletions packages/melonjs/skills/melonjs-effects-and-shaders/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,34 @@ app.viewport.addPostEffect(new VignetteEffect(app.renderer));
`renderable.shader = …` is **deprecated since 19.2.0**, and its setter destroys
whatever it replaces. Use `addPostEffect` / `getPostEffect` / `removePostEffect`.

### A camera effect covers the HUD too

A camera's post-effect brackets the **entire** world draw — floating children
included. So the obvious way to write a full-screen pass also washes over every
HUD label in that world, which is rarely what you want.

To land a pass *after* the world but *before* the HUD, host the effect on a
screen-filling floating renderable ordered below the HUD's z instead:

```js
const quad = new Sprite(0, 0, { image: anyImage });
quad.anchorPoint.set(0, 0);
quad.scale(viewW / anyImage.width, viewH / anyImage.height);
quad.floating = true; // its own uv is now screen space
quad.blendMode = "additive";
quad.addPostEffect(myEffect);
world.addChild(quad, HUD_Z - 10); // BELOW the labels, not above
```

Two things follow from the quad filling the frame: its own `uv` is screen space
(so a pass like this needs none of the `screen_uv` / `screen_texture` builtins),
and the incoming `color` is the quad's own texture, which a body that paints
from scratch can ignore entirely.

Depth here is easy to get backwards — a higher z draws **later**, i.e. on top.
Verify it by returning a flat colour from the body for one frame: whatever it
tints is what the pass covers.

## Toggle with `enabled`, do not remove

**`removePostEffect()` destroys the effect** — it calls `effect.destroy()` and
Expand Down
29 changes: 27 additions & 2 deletions packages/melonjs/src/loader/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ function onLoadingError(res) {
* @property {string|string[]} [src] - path and/or file name of the resource (for audio assets only the path is required).
* For image assets, an array of sources can be provided as a fallback chain (e.g. compressed texture formats by priority, with a PNG fallback).
* The loader will try each source in order and use the first one that loads successfully.
* @property {string} [data] - inline content if not provided through a src url: TMX data for "tmx" assets, GLSL source (the ShaderEffect fragment-body convention) for "shader" assets
* @property {string|{glsl?: string, wgsl?: string}} [data] - inline content if not provided through a src url: TMX data for "tmx" assets, GLSL source (the ShaderEffect fragment-body convention) for "shader" assets. A shader may instead carry a `{glsl, wgsl}` pair, so one asset serves both backends; either half may be omitted
* @property {boolean} [stream=false] - Set to true to not to wait for large audio or video file to be downloaded before playing.
* @property {boolean} [autoplay=false] - Set to true to automatically start playing audio or video when loaded or added to a scene (using autoplay might require user iteraction to enable it)
* @property {boolean} [loop=false] - Set to true to automatically loop the audio or video when playing
Expand Down Expand Up @@ -931,10 +931,35 @@ export function getOBJ(elt) {
return null;
}

/**
* One mesh primitive out of a parsed glTF/GLB scene.
*
* Spelled out rather than left as `object` so the geometry can be read from
* TypeScript — feeding `vertices`/`uvs`/`normals`/`indices` straight into a
* {@link Mesh} or {@link InstancedMesh} is the whole point of exposing it.
* @typedef {object} GLTFNode
* @property {number[]} world - accumulated world transform, 16 floats, column-major
* @property {Float32Array} vertices - positions, x,y,z triplets
* @property {Float32Array} normals - per-vertex normals
* @property {Float32Array} uvs - texture coordinates, u,v pairs
* @property {Uint16Array|Uint32Array} indices - triangle vertex indices
* @property {number} vertexCount - number of vertices
* @property {HTMLImageElement|null} image - decoded baseColor texture, or `null`
* @property {number[]} [baseColorFactor] - material baseColor factor, `[r, g, b, a]`
* @property {Uint32Array} [colors] - per-vertex colour, packed RGBA8
* @property {string} [textureRepeat] - wrap mode derived from the glTF sampler
* @property {string} [textureFilter] - magnification filter derived from the glTF sampler
* @property {number} [alphaCutoff] - cutout threshold from `alphaMode: "MASK"`
* @property {number[]} [emissive] - emissive factor, `[r, g, b]`
* @property {boolean} [unlit] - the material carried `KHR_materials_unlit`
* @property {boolean} [doubleSided] - the material is double-sided
* @property {string} [name] - the source node's name
*/

/**
* a parsed glTF/GLB scene descriptor, as returned by {@link loader.getGLTF}
* @typedef {object} GLTFData
* @property {object[]} nodes - one entry per mesh primitive: accumulated `world` transform, `vertices`, `normals`, `uvs`, `indices`, `vertexCount`, decoded baseColor `image` (or `null`), `baseColorFactor`, per-vertex `colors`, sampler-derived `textureRepeat`/`textureFilter`, `alphaCutoff`, `emissive`, `unlit` (KHR_materials_unlit), `doubleSided`, and the source node `name`
* @property {GLTFNode[]} nodes - one entry per mesh primitive
* @property {Array<{world: number[], type?: string, perspective?: {yfov?: number, aspectRatio?: number, znear?: number, zfar?: number}, orthographic?: object}>} cameras - glTF cameras, each with its `world` transform + the glTF camera parameters (`perspective` for perspective cameras, `orthographic` otherwise)
* @property {object[]} lights - parsed `KHR_lights_punctual` lights (`type`, `color`, `intensity`, `range`, `innerConeAngle`/`outerConeAngle` for spots, world-space `direction`/`position`, `name`)
* @property {{min: number[], max: number[]}} bounds - world-space scene bounds in glTF units
Expand Down
4 changes: 2 additions & 2 deletions packages/melonjs/src/renderable/mesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ function buildTextureGroups(
* @property {string} [model] - name of a preloaded OBJ model (via loader.preload with type "obj"). Vertex normals come with it — authored `vn` when the file has them, generated from face geometry when it does not — so an OBJ model can be `lit`.
* @property {Float32Array|number[]} [vertices] - vertex positions as x,y,z triplets (alternative to `model`)
* @property {Float32Array|number[]} [uvs] - texture coordinates as u,v pairs (alternative to `model`)
* @property {Uint16Array|number[]} [indices] - triangle vertex indices (alternative to `model`)
* @property {HTMLImageElement|HTMLCanvasElement|TextureAtlas|string} [texture] - the texture to apply (image name, HTMLImageElement, or TextureAtlas). If omitted and settings.material is provided, the texture is resolved from the MTL material's map_Kd. Passing this pins ONE binding over the whole model, which on a multi-material model suppresses the per-material texture split — see {@link Mesh#textureGroups}.
* @property {Uint16Array|Uint32Array|number[]} [indices] - triangle vertex indices (alternative to `model`). A `Uint32Array` is preserved as-is for meshes past 65535 vertices — which is what the glTF parser emits for them — while a plain array is materialized as `Uint16Array`
* @property {HTMLImageElement|HTMLCanvasElement|Texture2d|TextureAtlas|string} [texture] - the texture to apply (image name, HTMLImageElement, or TextureAtlas). If omitted and settings.material is provided, the texture is resolved from the MTL material's map_Kd. Passing this pins ONE binding over the whole model, which on a multi-material model suppresses the per-material texture split — see {@link Mesh#textureGroups}.
* @property {string} [material] - name of a preloaded MTL material (via loader.preload with type "mtl"). When provided, the diffuse texture (map_Kd), tint color (Kd), and opacity (d) are automatically applied. On a multi-material model each material's own `map_Kd` is bound for its own slice of the geometry (#1573) and each `Kd` is baked per-vertex, so one `Mesh` renders the whole model.
* @property {number} width - display width in pixels. With normalization on (the default) the model is scaled to fit this size; with `normalize: false` this is the uniform pixels-per-unit scale applied to the raw geometry. With `normalize: false` and an explicit `scale`, an omitted `width`/`height` is derived from the GEOMETRY's own extent, the way a Sprite takes its size from its frame — a mesh that reports no extent misleads frustum culling, pointer picking and the physics broadphase alike.
* @property {number} [height] - display height in pixels (normalized models only; ignored when `normalize: false`)
Expand Down
12 changes: 11 additions & 1 deletion packages/melonjs/src/video/effects/shadereffect.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,17 @@ export default class ShaderEffect {
/**
* Set the uniform to the given value
* @param {string} name - the uniform name
* @param {object|Float32Array} value - the value to assign to that uniform
* @param {number|boolean|number[]|Float32Array|object} value - the value to assign to that
* uniform. Scalars (`float`, `int`, `bool`) take a number or a boolean;
* vectors and matrices take an array, a `Float32Array`, or any object
* exposing `toArray()` — which is every {@link Vector2d},
* {@link Vector3d}, {@link Color} and {@link Matrix3d}.
* @example
* // a scalar the body declares as `uniform float uStrength;`
* fx.setUniform("uStrength", 0.5);
* // a vec3 — an array, or anything with toArray()
* fx.setUniform("uTint", [1.0, 0.82, 0.55]);
* fx.setUniform("uOrigin", new me.Vector2d(0.5, 0.5));
*/
setUniform(name, value) {
// forward whenever a live shader exists (WebGL mode): GLShader handles
Expand Down
7 changes: 6 additions & 1 deletion packages/melonjs/src/video/webgl/glshader.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,14 @@ export default class GLShader {
/**
* Set the uniform to the given value
* @param {string} name - the uniform name
* @param {object|Float32Array} value - the value to assign to that uniform
* @param {number|boolean|number[]|Float32Array|object} value - the value to assign to that
* uniform. Scalars (`float`, `int`, `bool`) take a number or a boolean;
* vectors and matrices take an array, a `Float32Array`, or any object
* exposing `toArray()` — which is every {@link Vector2d},
* {@link Vector3d}, {@link Color} and {@link Matrix3d}.
* @example
* myShader.setUniform("uProjectionMatrix", this.projectionMatrix);
* myShader.setUniform("uStrength", 0.5); // a scalar is a plain number
*/
setUniform(name, value) {
if (this.destroyed) {
Expand Down
Loading