@newkrok/nape-js 3.30.3 → 3.31.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/llms-full.txt CHANGED
@@ -1517,6 +1517,180 @@ Generate random points inside a polygon for use as Voronoi sites.
1517
1517
 
1518
1518
  ---
1519
1519
 
1520
+ ## Helpers
1521
+
1522
+ Higher-level building blocks layered on top of the core engine. Each is a thin, optional module — import only what you need.
1523
+
1524
+ ### CharacterController
1525
+
1526
+ Velocity-based 2D platformer controller. Wraps a dynamic body and provides ground/slope/wall raycasts, coyote-time tracking, one-way platform support, and moving-platform inheritance.
1527
+
1528
+ ```typescript
1529
+ import { CharacterController, Body, BodyType, Vec2, Capsule } from "@newkrok/nape-js";
1530
+
1531
+ const player = new Body(BodyType.DYNAMIC, new Vec2(100, 100));
1532
+ player.shapes.add(new Capsule(36, 18));
1533
+ player.allowRotation = false;
1534
+ player.isBullet = true;
1535
+ player.space = space;
1536
+
1537
+ const cc = new CharacterController(space, player, {
1538
+ maxSlopeAngle: Math.PI / 4, // walkable slope cap (default: PI/4)
1539
+ oneWayPlatformTag: platformCbType, // optional — auto-creates a PreListener
1540
+ characterTag: playerCbType, // required if oneWayPlatformTag set
1541
+ filter: customFilter, // raycast InteractionFilter (default: auto-excludes player shapes)
1542
+ down: new Vec2(0, 1), // override "down" — see planet platformer
1543
+ });
1544
+
1545
+ // Each frame, AFTER space.step():
1546
+ const result = cc.update();
1547
+ result.grounded; // boolean
1548
+ result.groundNormal; // Vec2 | null
1549
+ result.groundBody; // Body | null
1550
+ result.onMovingPlatform; // boolean
1551
+ result.slopeAngle; // radians
1552
+ result.wallLeft; // boolean
1553
+ result.wallRight; // boolean
1554
+ result.timeSinceGrounded; // seconds (for coyote-time)
1555
+
1556
+ // Override "down" each frame for radial-gravity worlds:
1557
+ cc.setDown(downX, downY);
1558
+ ```
1559
+
1560
+ **Gotchas:**
1561
+ - The controller does **not** set velocity itself — your code does (typical pattern: read input, compute target velocity, write `body.velocity`). The controller only provides raycast queries and the auto-PreListener for one-way platforms.
1562
+ - `oneWayPlatformTag` requires `characterTag` — without it the auto-listener can't tell which body is the character.
1563
+ - For radial-gravity / planet-platformer scenarios, set `down` to the unit vector from player to "ground" each frame; walls are detected perpendicular to it.
1564
+
1565
+ ### RadialGravityField
1566
+
1567
+ Point-source gravity field — pulls bodies toward an anchor with a chosen falloff law. Replaces the manual `for (body of space.bodies) body.force = ...` loops common in orbital / planet / multi-body gravity scenarios.
1568
+
1569
+ ```typescript
1570
+ import { RadialGravityField, RadialGravityFieldGroup } from "@newkrok/nape-js";
1571
+
1572
+ // Mario-Galaxy-style planet pulling everything toward its center.
1573
+ const field = new RadialGravityField({
1574
+ source: planetBody, // Vec2 (fixed point) or Body (auto-tracking)
1575
+ strength: 800000,
1576
+ falloff: "inverse-square", // "inverse-square" (default) | "inverse" | "constant" | (d) => number
1577
+ scaleByMass: true, // default true → Newtonian; false → constant accel
1578
+ maxRadius: 250, // hard cutoff — bodies farther than this get 0 force
1579
+ minRadius: 1, // clamp distance for falloff calc (singularity guard)
1580
+ softening: 100, // adds to d² in inverse-square (smooths near-source)
1581
+ bodyFilter: (body) => body !== sun, // optional per-body predicate
1582
+ enabled: true,
1583
+ });
1584
+
1585
+ // Each frame, BEFORE space.step():
1586
+ field.apply(space); // adds force to every eligible dynamic body in space
1587
+ space.step(1 / 60);
1588
+
1589
+ // Compose multiple fields:
1590
+ const group = new RadialGravityFieldGroup();
1591
+ group.add(field);
1592
+ group.add(new RadialGravityField({ source: moon, strength: 50000 }));
1593
+ group.apply(space); // runs all member fields once
1594
+
1595
+ // Compute the force on a specific body without applying it:
1596
+ const f = field.forceOn(body); // Vec2
1597
+
1598
+ // Move the field at runtime (Vec2 source — Body sources auto-track):
1599
+ field.getPosition(); // { x, y }
1600
+ field.enabled = false;
1601
+ field.strength = 1200000;
1602
+ ```
1603
+
1604
+ **Gotchas:**
1605
+ - `body.force` is **persistent** across `space.step()` — nape never zeroes it. `apply()` *adds* to existing force, so per-frame field application accumulates unbounded if you don't clear `body.force` yourself each frame. Pattern: `body.force = new Vec2(0, 0)` before `field.apply()`.
1606
+ - `scaleByMass: true` produces real Newtonian behavior; switch to `false` for direct acceleration (simpler tuning for arcade games).
1607
+ - Set `softening` (inverse-square only) to avoid extreme accelerations when bodies pass close to the source.
1608
+ - Static and kinematic bodies are always skipped (they don't respond to force anyway).
1609
+ - For planet platformers where multiple wells overlap and the player should only feel one at a time, use `bodyFilter` to gate the player against `_currentPlanet` while letting other dynamic bodies feel every well they're inside.
1610
+
1611
+ ### Tilemap (`buildTilemapBody`, `meshTilemap`)
1612
+
1613
+ Turns a 2D tile grid into a physics body using greedy meshing — collapses adjacent solid tiles into the minimal set of axis-aligned rectangles. Cuts shape count by 5–50× on typical level data, which directly speeds up broadphase + narrowphase.
1614
+
1615
+ ```typescript
1616
+ import {
1617
+ buildTilemapBody, meshTilemap, tiledLayerToGrid, ldtkLayerToGrid,
1618
+ } from "@newkrok/nape-js";
1619
+
1620
+ // Hand-authored grid (1 = solid, 0 = empty)
1621
+ const grid = [
1622
+ [1, 1, 1, 1, 1],
1623
+ [1, 0, 0, 0, 1],
1624
+ [1, 0, 0, 0, 1],
1625
+ [1, 1, 1, 1, 1],
1626
+ ];
1627
+
1628
+ const body = buildTilemapBody(grid, {
1629
+ tileSize: 32, // square — or { w: 32, h: 24 } for non-square
1630
+ position: new Vec2(0, 0), // top-left of the map in world space
1631
+ merge: "greedy", // "none" | "rows" | "greedy" (default: greedy)
1632
+ solid: (v, x, y) => v !== 0, // default: any non-zero is solid
1633
+ material: customMaterial, // applied to every generated polygon
1634
+ filter: customFilter,
1635
+ cbTypes: [groundCbType],
1636
+ bodyType: BodyType.STATIC, // default STATIC — also accepts KINEMATIC for moving levels
1637
+ body: existingBody, // optional: append shapes to a body that already exists
1638
+ });
1639
+ body.space = space;
1640
+
1641
+ // Pure geometry (no Body) — useful for streaming chunks or precomputing meshes:
1642
+ const rects = meshTilemap(grid, { tileSize: 32, merge: "greedy" });
1643
+ // rects: Array<{ x, y, w, h }> in tile coordinates
1644
+
1645
+ // Parse external level editors:
1646
+ const grid1 = tiledLayerToGrid(tiledJson.layers[0]); // Tiled JSON tile layer
1647
+ const grid2 = ldtkLayerToGrid(ldtkJson.levels[0].layerInstances[0]); // LDtk IntGrid
1648
+ ```
1649
+
1650
+ **Gotchas:**
1651
+ - The generated polygons are axis-aligned boxes — no slopes. For sloped terrain combine with hand-authored polygons or use marching squares.
1652
+ - Greedy merging is the right default; only use `merge: "rows"` if you need to preserve per-row stripes (e.g. for per-tile properties stored on shapes), or `"none"` for one polygon per cell when you intend to replace cells dynamically.
1653
+ - `tiledLayerToGrid` / `ldtkLayerToGrid` only consume the data + dimension fields — they don't depend on the full Tiled/LDtk JSON shape, so you can pass a hand-shaped subset.
1654
+ - For destructible terrain, rebuild the body when the grid changes (`body.shapes.clear()` then call `buildTilemapBody(grid, { ..., body })`).
1655
+
1656
+ ### TriggerZone
1657
+
1658
+ Sensor-based zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing so you don't have to wire it up by hand.
1659
+
1660
+ ```typescript
1661
+ import { TriggerZone } from "@newkrok/nape-js";
1662
+
1663
+ const zone = new TriggerZone(space, body, {
1664
+ type: InteractionType.SENSOR, // default — also accepts COLLISION
1665
+ onEnter: (interactor) => { /* ... */ },
1666
+ onExit: (interactor) => { /* ... */ },
1667
+ filter: filterCbType, // optional CbType filter
1668
+ });
1669
+
1670
+ zone.destroy(); // remove the listeners
1671
+ ```
1672
+
1673
+ ### createConcaveBody
1674
+
1675
+ Decomposes a concave polygon outline into convex pieces and adds them all to a single body — needed because nape's `Polygon` shape is convex-only.
1676
+
1677
+ ```typescript
1678
+ import { createConcaveBody, Vec2 } from "@newkrok/nape-js";
1679
+
1680
+ const body = createConcaveBody(
1681
+ [new Vec2(0, 0), new Vec2(100, 0), /* ... */], // CCW outline (or GeomPoly)
1682
+ {
1683
+ bodyType: BodyType.DYNAMIC, // default
1684
+ position: new Vec2(200, 100), // body's world position (vertices are local)
1685
+ material: customMaterial,
1686
+ filter: customFilter,
1687
+ },
1688
+ );
1689
+ body.space = space;
1690
+ ```
1691
+
1692
+ ---
1693
+
1520
1694
  ## Common Patterns
1521
1695
 
1522
1696
  ### Adding Bodies to Space
package/llms.txt CHANGED
@@ -95,6 +95,16 @@ function update() {
95
95
  - [computeVoronoi](https://newkrok.github.io/nape-js/api/functions/computeVoronoi.html): Raw Voronoi diagram computation from point set
96
96
  - [generateFractureSites](https://newkrok.github.io/nape-js/api/functions/generateFractureSites.html): Generate random fracture site points within a polygon
97
97
 
98
+ ## Helpers
99
+
100
+ Higher-level building blocks layered on top of the engine — opt-in modules.
101
+
102
+ - [CharacterController](https://newkrok.github.io/nape-js/api/classes/CharacterController.html): Velocity-based 2D platformer controller with ground/slope/wall raycasts, coyote-time, one-way platforms, moving-platform inheritance, and an overridable `down` direction (radial-gravity worlds)
103
+ - [RadialGravityField](https://newkrok.github.io/nape-js/api/classes/RadialGravityField.html) / [RadialGravityFieldGroup](https://newkrok.github.io/nape-js/api/classes/RadialGravityFieldGroup.html): Point-source gravity field with `inverse-square` / `inverse` / `constant` / custom falloff, `maxRadius` / `softening` / `minRadius`, body filter, mass scaling — replaces hand-rolled `body.force = ...` loops for orbital, planet, and multi-body scenarios
104
+ - `buildTilemapBody` / `meshTilemap` / `tiledLayerToGrid` / `ldtkLayerToGrid`: Greedy-meshed collision body from a 2D tile grid (5–50× fewer shapes vs one-polygon-per-cell), with built-in parsers for Tiled JSON tile layers and LDtk IntGrid layers
105
+ - [TriggerZone](https://newkrok.github.io/nape-js/api/classes/TriggerZone.html): Sensor zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing
106
+ - [createConcaveBody](https://newkrok.github.io/nape-js/api/functions/createConcaveBody.html): Decomposes a concave outline into convex polygons and packs them into a single body
107
+
98
108
  ## Enums
99
109
 
100
110
  - [BodyType](https://newkrok.github.io/nape-js/api/classes/BodyType.html): STATIC, DYNAMIC, KINEMATIC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/nape-js",
3
- "version": "3.30.3",
3
+ "version": "3.31.0",
4
4
  "description": "High-performance 2D physics engine for TypeScript & JavaScript — rigid bodies, constraints, fluid simulation, raycasting, and deterministic multiplayer. Tree-shakeable, zero dependencies.",
5
5
  "type": "module",
6
6
  "sideEffects": [