What's New in melonJS 19.7.0
Highlights: Camera3d perspective camera lands. Every batched shader carries per-sprite depth as vec3 aVertex, unlocking 3D-projected sprites and meshes. Backward compatible with existing 2D code.
New Features
Camera3d— perspective camera extendingCamera2dwithfov,aspect,pitch,yaw,followOffset,lookAhead. Opt in vianew Application(w, h, { cameraClass: Camera3d })orStage({ cameras: [new Camera3d(...)] }). Y-down + +Z forward.Octreebroadphase — 3D spatial subdivision sibling toQuadTree.World.broadphasereactively swaps based onsortOnso 2D↔3D transitions are transparent. Region queries:queryAABB,querySphere,queryFrustum,queryRay.Spheregeometry primitive (new Sphere(x, y, z, r)) +AABB3d+Frustum— the 3D shape vocabulary.Sphereis the canonical 3D query shape (adapter.querySphere(sphere),Octree.querySphere(sphere)).Camera3d.queryVisible(world)— bulk frustum cull broadphase pass for dense 3D scenes.MeshunderCamera3d— world-space GPU projection with lazy back-face winding reversal.- Multi-material OBJ —
Meshdraws OBJ files with multipleusemtldirectives + MTL, baking per-materialKdinto a per-vertex color buffer. Single draw call regardless of material count. NewMulti-material OBJexample. - Per-sprite depth on the GPU —
Quad,LitQuad,Primitive, and GPU TMX batchers all carry.depthas the z component.renderer.setDepth(depth)mirrorssetTint. PhysicsAdapter.querySphere?+raycast3d?— optional 3D query surface, capability-gated bycapabilities.raycasts3d.BuiltinAdapterimplements both; matter / planck omit them.math.lerp(a, b, t)— scalar linear interpolation, single source of truth for vector lerps.math.damp(current, target, lambda, dt)— frame-rate-independent exponential damping (Three.jsMathUtils.dampparity).Vector2d.damp/Vector3d.dampfollow the same shape.event.GPU_TEXTURE_CACHE_RESET+event.RENDER_TARGET_CHANGED— renderer-agnostic events for cross-batcher state invalidation. Future WebGPU port emits the same events.Application#requestFullscreen/exitFullscreen/isFullscreen— app-instance fullscreen control replacing the deprecateddevice.requestFullscreenglobal-game lookup.device.setAutoFocus(enable)(#1486) — function setter for the autofocus-on-visibility behaviour, finally writable from user code (direct field assignment was a read-only ESM binding).
Changed
Camera2dsmooth follow is now frame-rate independent.updateTargetswapspos.lerpforpos.damp(target, lambda, dt)withlambda = -ln(1 - damping) * timer.maxfps. Existingdampingvalues keep their feel at the target framerate; high-refresh users finally get the same convergence the dev tuned for. No tuning change required.- Mesh rendering clears depth once per target, not per mesh (#1468).
MeshBatchernow owns mesh-mode GL state (bind()enters,unbind()restores). Consecutive mesh draws pay zero state-toggle cost between them. Side-effect: intersecting / painter-wrong-order meshes resolve per-pixel via the GPU depth test (closer wins regardless of draw order), where the old per-mesh clear let the second draw silently overwrite. Applicationfails loudly on WebGL-required misconfiguration (#1479). Throws whenrenderer: video.WEBGLis requested but WebGL is unavailable (was silent Canvas fallback); warns whencameraClass.defaultSortOn === "depth"(Camera3d or subclass) under a Canvas renderer.device.platform.isMobiledrops the dead-platform regexes (wp,BlackBerry,Kindle) — chain is now/Mobi/.test(ua) || iOS || android, covering ~99.9% of mobile traffic per MDN.device.platform.iOS/isMobilecorrectly identify iPads on iPadOS 13+ (#1467). Since Sept 2019 Safari on iPad ships the desktop Mac UA — noiPadtoken — so every modern iPad was falling throughisMobileas desktop. Detection layersnavigator.platform === "MacIntel" && maxTouchPoints > 1on top of the UA regex.timer.stepis now the precise1000 / maxfps(wasMath.ceil), fixing a ~2% tick-interpolation undershoot under frame drops.- Breaking-ish:
QuadTreedropped from public package exports — broadphase is implementation detail behindworld.adapter.*. Directimport { QuadTree } from "melonjs"should be removed; the broadphase instance is still reachable asworld.broadphasefor tooling. - Breaking-ish:
aVertexwidened fromvec2tovec3acrossquad-multi.vert,quad-multi-lit.vert,primitive.vert,orthogonal-tmxlayer.vert. Custom shaders binding by name keep working (attribute vec2 aVertex;is fine, z is dropped). - Breaking-ish:
VertexArrayBuffer.push()gained azparameter betweenyandu. Custom batchers that reimplementaddQuad/drawVerticesneed to insertz(default0); subclasses that delegate tosuper.addQuadare unaffected. Camera2ddefault near / far widened from±1000to±1e6sodepth-participating sprites don't cull-clip underContainer.autoDepthor Y-sort patterns on tall maps.system/deviceconverted to TypeScript (#1467).
Bug Fixes
WebGLRenderer.createPatterncache-key collision (#1448) — two patterns created from the same source image with different repeat modes used to silently collide on a single GL texture unit.TextureCachenow keys units by(source, repeat)via a nestedMap<source, Map<repeat, unit>>; each distinct repeat mode gets its own unit. Canvas / WebGLcreatePattern(image)(norepeatarg) defaults to"no-repeat"in both renderers for parity.- GPU TMX layer reset crash (#1471) when a non-material batcher was active at stage change. Reset now pins to
batchers.get("quad")instead of grabbingrenderer.currentBatcher. - WebGL TextureCache cross-batcher binding desync — a unit-pool reset only cleared the current batcher's
boundTexturesmap; stale entries on every other batcher caused meshes to render as black silhouettes and bullets as pure white in mixed-batcher scenes. Fixed via the newevent.GPU_TEXTURE_CACHE_RESETevent. - WebGL color-attribute NaN canonicalization on Apple Metal / ANGLE —
MeshBatchercolor attribute switched fromUNSIGNED_BYTE × 4 normalizedtoFLOAT × 4; packed-color bytes upload viavertex.toUint8()so they survive driver canonicalization. Stage.resetre-applies the chosen camera'sdefaultSortOnon every reset — covers the loader-pinned-Camera2d → user-stage handoff so distant meshes no longer paint on top of nearer ones under perspective.
Performance
- Mesh-state ownership migration unlocks dense-3D-scene CPU wins (50+ mesh draws). Per-mesh state-toggle cost goes to zero between consecutive draws. Extrapolated 3–30ms saved per frame at 50+ mesh scenes; sparse scenes (~5 meshes / frame) are within noise.
- GPU TMX uniform caching —
gl.uniform*calls on hot per-layer paths skip when the value matches the previously-set cached value.
Install
npm install melonjs@19.7.0