tosijs-3d 0.10.5 → 0.10.6

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +2 -2
  3. package/dist/anchor-layout.d.ts +60 -0
  4. package/dist/anchor-layout.d.ts.map +1 -0
  5. package/dist/anchor-layout.js +140 -0
  6. package/dist/anchor-layout.js.map +1 -0
  7. package/dist/b3d-biped.d.ts +17 -3
  8. package/dist/b3d-biped.d.ts.map +1 -1
  9. package/dist/b3d-biped.js +83 -14
  10. package/dist/b3d-biped.js.map +1 -1
  11. package/dist/b3d-panel.d.ts +10 -1
  12. package/dist/b3d-panel.d.ts.map +1 -1
  13. package/dist/b3d-panel.js +21 -0
  14. package/dist/b3d-panel.js.map +1 -1
  15. package/dist/b3d-planet.d.ts.map +1 -1
  16. package/dist/b3d-planet.js +31 -5
  17. package/dist/b3d-planet.js.map +1 -1
  18. package/dist/b3d-primitives.d.ts +13 -0
  19. package/dist/b3d-primitives.d.ts.map +1 -1
  20. package/dist/b3d-primitives.js +57 -10
  21. package/dist/b3d-primitives.js.map +1 -1
  22. package/dist/b3d-reflections.js +1 -1
  23. package/dist/b3d-shadows.d.ts +9 -2
  24. package/dist/b3d-shadows.d.ts.map +1 -1
  25. package/dist/b3d-shadows.js +17 -6
  26. package/dist/b3d-shadows.js.map +1 -1
  27. package/dist/b3d-star-system.d.ts +2 -0
  28. package/dist/b3d-star-system.d.ts.map +1 -1
  29. package/dist/b3d-star-system.js +43 -11
  30. package/dist/b3d-star-system.js.map +1 -1
  31. package/dist/b3d-svg-plane.d.ts +5 -0
  32. package/dist/b3d-svg-plane.d.ts.map +1 -1
  33. package/dist/b3d-svg-plane.js +1 -0
  34. package/dist/b3d-svg-plane.js.map +1 -1
  35. package/dist/b3d-utils.d.ts.map +1 -1
  36. package/dist/b3d-utils.js +2 -0
  37. package/dist/b3d-utils.js.map +1 -1
  38. package/dist/box.d.ts +16 -0
  39. package/dist/box.d.ts.map +1 -1
  40. package/dist/box.js +155 -16
  41. package/dist/box.js.map +1 -1
  42. package/dist/buoyancy.d.ts +11 -0
  43. package/dist/buoyancy.d.ts.map +1 -1
  44. package/dist/buoyancy.js +19 -2
  45. package/dist/buoyancy.js.map +1 -1
  46. package/dist/carry-box.d.ts +128 -0
  47. package/dist/carry-box.d.ts.map +1 -0
  48. package/dist/carry-box.js +809 -0
  49. package/dist/carry-box.js.map +1 -0
  50. package/dist/carry.d.ts +52 -0
  51. package/dist/carry.d.ts.map +1 -0
  52. package/dist/carry.js +140 -0
  53. package/dist/carry.js.map +1 -0
  54. package/dist/demo-utils.d.ts +1 -1
  55. package/dist/demo-utils.d.ts.map +1 -1
  56. package/dist/demo-utils.js +1 -1
  57. package/dist/demo-utils.js.map +1 -1
  58. package/dist/frame-panel.d.ts +69 -0
  59. package/dist/frame-panel.d.ts.map +1 -1
  60. package/dist/frame-panel.js +200 -16
  61. package/dist/frame-panel.js.map +1 -1
  62. package/dist/index.d.ts +13 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +8 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/inventory-box.d.ts +144 -0
  67. package/dist/inventory-box.d.ts.map +1 -0
  68. package/dist/inventory-box.js +1123 -0
  69. package/dist/inventory-box.js.map +1 -0
  70. package/dist/inventory.d.ts +193 -0
  71. package/dist/inventory.d.ts.map +1 -0
  72. package/dist/inventory.js +495 -0
  73. package/dist/inventory.js.map +1 -0
  74. package/dist/playground.d.ts +2 -0
  75. package/dist/playground.d.ts.map +1 -1
  76. package/dist/playground.js +77 -15
  77. package/dist/playground.js.map +1 -1
  78. package/dist/scene-schemas.d.ts.map +1 -1
  79. package/dist/scene-schemas.js +2 -1
  80. package/dist/scene-schemas.js.map +1 -1
  81. package/dist/sol.d.ts.map +1 -1
  82. package/dist/sol.js +81 -14
  83. package/dist/sol.js.map +1 -1
  84. package/dist/solar-system.d.ts.map +1 -1
  85. package/dist/solar-system.js +23 -6
  86. package/dist/solar-system.js.map +1 -1
  87. package/dist/svg-texture.d.ts +9 -0
  88. package/dist/svg-texture.d.ts.map +1 -1
  89. package/dist/svg-texture.js +6 -2
  90. package/dist/svg-texture.js.map +1 -1
  91. package/dist/system-layout.d.ts +31 -2
  92. package/dist/system-layout.d.ts.map +1 -1
  93. package/dist/system-layout.js +37 -4
  94. package/dist/system-layout.js.map +1 -1
  95. package/dist/tosi-b3d.d.ts.map +1 -1
  96. package/dist/tosi-b3d.js +91 -8
  97. package/dist/tosi-b3d.js.map +1 -1
  98. package/package.json +2 -2
  99. package/static/attributes.json +73 -3
  100. package/static/attributes.txt +26 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,70 @@ All notable changes to **tosijs-3d**. This project is pre-1.0 (`0.x`), so minor
6
6
  versions may carry breaking peer-dependency changes — each is called out in a
7
7
  **⚠️ Breaking** block in its version section below, with what a consumer must do.
8
8
 
9
+ ## 0.10.6
10
+
11
+ ### Added
12
+
13
+ - **Flat panels can be placed in screen terms**: `screenAnchor` (on `b3dPanel` and frame panels) pins a panel to the view's edges in pixels, a fraction of the axis (`'5%'`) or of the shorter side (`'3vmin'`), with an anchor point, and `height` sets its size on screen. It is solved with `anchorLayout` every frame and placed square to the screen in the head-locked frame, so a resize or a different window shape keeps it where you put it; it wins over `flatAnchor`'s degrees, which suit a headset, where a panel is around you. The demo loadouts use it: 64 px in, 2vmin down, 14% of the screen tall. Opened, a screen-pinned panel comes to the middle of the screen square to it, not tilted up from below eye level.
14
+ - **A box scrolls with a pointer alone**: a scrollbar on its right edge when its content overflows a fixed height (drag the thumb; press the track to page). A wheel scrolls a box on a page, but a panel in a scene gets no wheel and a headset has none, so a long list was cut off with no way to reach the rest. **Sticky children** (`sticky: true` on a leading run) stay put while the rest scrolls under them: the inventory list's heading and drop row (#3290).
15
+ - **The Biped Playground has the loadout, and the pistol lives on your hip.** Firing mode follows where the pistol is: in a hand the gun is up, on the hip it is down, in the bag or dropped there is nothing to raise. The weapon button draws it from the hip or holsters it, and dragging it into a hand does the same. `b3dBiped`'s new `whenWeapon` (JS) lets a game answer the weapon button itself and call `setGunplay`; `demoLoadout({ equip })` starts with things worn and returns its `inv` (#3305).
16
+ - **The Biped Playground has a pool**: 14 x 10 m, 3.5 m deep, in the far corner past the catwalk, with a shelving ramp at one end to walk in on (where `isSwimming`'s hysteresis is tested) and caustics on the floor. Sunk into the ground, because a biped swims anywhere below the water's height: `b3dGround`'s new `holes` (JS) cuts rectangles out of it, keeping its texture unbroken around them. `playground({ pool: false })` leaves it out.
17
+ - **A drop target.** `inventoryPanel`/`inventoryDock` `handleDrop(item, kind, from)` adds a **drop** row at the top of the list: a thing dropped there leaves the inventory, after the game has had its say (return reasons to refuse and it springs back; otherwise put it in the world). Both demos have one (#3290).
18
+ - **`ui.anchorLayout`, the third layout** beside the flow: positioned and layered. An item is pinned to any edge with an offset (units or a fraction, `'50%'`), with a natural or fixed size; between two pins its size is what they leave and a stated size is a minimum. Its anchor (`anchorX`/`anchorY`, `anchor: 'center'`; default the top left) is the point a pin places and the way a minimum that does not fit overflows. `within` pins it inside a rectangle of the container (a window in the artwork). Paint order is the order given or `z`. Pure and unit-tested; the box mode that uses it is next (#3309).
19
+ - **Things have pictures, and say what they are.** An item kind's `icon` (SVG markup) is drawn on its tile: beside the name in a list, alone and filling a socket, and as the ghost a drag carries. Pointing at a thing, or selecting it with a gamepad, shows an info floater (its name, `description`, mass and what it adds by default; `info` replaces that), drawn in the ghost's layer so nothing clips it. `carryTile` takes `icon` and `info`. The demos load one picture per kind from `/item-icons.json` (#3290).
20
+ - **A frame panel can come to you.** Content that dispatches `panelfocus` (`detail: { focus: true }`) on its SVG brings its panel front and centre (`focusAnchor`, default straight ahead and a little low), enlarged (`focusScale`, default 1.9, at 0.75 m) and always revealed; `focus: false` sends it back. A focused panel never sits inside the camera's near plane (it is pushed out and scaled to look the same size) and is never bigger than the view (it fits 92% of it, allowing for being off-centre), so a monitor shows all of it. `flatAnchor` places a panel somewhere else in the flat view (the shoulder is off a monitor's edge), and `<tosi-b3d-panel>` takes `svg`, `flatAnchor`, `focusAnchor` and `focusScale` as JS properties. A frame panel also grows with its SVG's viewBox now, so content that opens wider gets a wider plane. `inventoryDock` asks for focus as it opens, and the main `tosi-b3d` demo pins your loadout up and to the left (over the shoulder in VR, the top left flat): touch or click it and it comes front and centre with your bag (#3290).
21
+ - **An inventory in 3D: `inventoryDock`.** A loadout that lives on a plane in the scene, flat and in a headset. Closed, it is the schematic alone, small; a tap opens it, with the list beside it (a bag) to drag to and from, and a button closes it. Both are drawn in one SVG, so a drag between them is one plane with one ghost (`carryGhostLayer`). Without a `list` it opens to the schematic alone (a craft fitted from a carrier's stock). Demo: "In 3D" on the inventory-box page, where the loadout replaces the "Inventory" placeholder over your shoulder (top left flat) and a touch brings it front and centre (#3290).
22
+ - **A two-handed weapon shows in both hands**: `inventoryPanel`'s `alsoIn(item, kind, at)` names the other slots an item takes up, where it is drawn faded (and can be picked up from either). The game decides which; the panel only draws it (#3290).
23
+ - **Drag and drop, as a model and on a panel** (INVENTORY-DESIGN step 1). `createCarry` is picking up and putting down with nothing drawn: what is carried and from where, the target under the pointer, and its verdict as REASONS; drop is all or nothing; dragging and two taps are one model, and one carry spans every panel. `carryTile`, `carrySocket` and `carryPanel` draw it on a box: things with decorations (a status bar, a damaged or broken outline, a badge), sockets that light when the held thing would fit, dim when not and say why, and a ghost that follows the pointer. A drag goes from one panel to another (they share the carry), and letting go outside every panel puts the thing back.
24
+ - **An inventory model** (`createInventory`, INVENTORY-DESIGN step 2): pure, with the game's catalogue, rules, contributions and prices plugged in. Holders hold slots (a paper doll, hardpoints, mounts), lists (a bag, a stock) and grids (a hull of boxes). A move is checked against every rule on a copy and kept only if all pass, so it happens completely or not at all; it can swap, split a stack, take from the catalogue, or pay. `stage()` is a draft that may be incomplete (a cart, a barter, a design not yet committed); `totals` adds up what is fitted (and what its mount adds); `damage` crosses off a system's boxes. `craft` runs a recipe (inputs used up, tools required but kept, outputs made, one move) and `compile` turns a finished design into a new kind (a ship class, a learned spell, a forged sword). Tested against a party, a loadout, a starship design, a shop and a smithy.
25
+ - **`inventoryPanel`**: one holder of an inventory on a panel, with drag and drop wired to the model, so a socket lights, dims and explains with the game's own rules. Panels sharing an inventory and a carry are a party, a trade or (with `moveFor` adding a price) a shop.
26
+ - **Schematics**: `inventoryPanel({ schematics: { body: svgText } })` draws a container as art, and its slots go where the art's sockets are (`data-slot="…"`, or ids in a `Sockets` group; `readSchematic` reads one). Sockets on art draw nothing at rest, outline when what is held fits, darken when not, and name themselves and the reason on hover. Demos: equip a person, arm a Manta from a carrier's counted stock, and a shop (its goods left, yours right, the deal in the middle: one `move` that buys, sells and pays, or does none of it). Panels take `height` (a scrolling list), `only`/`hide` (split one holder across panels) and `refuse` (a view's own reasons), and show the holder's purse.
27
+ - **A schematic is drawn at the art's own scale**, cropped to what it draws and shrunk only when the panel is too narrow (`schematicFit`), so a socket designed 44 units square is a 44-unit target, not whatever the file's margins left of it.
28
+ - **The demo schematics are coloured by CSS variables** with defaults for a dark panel (`--schematic-body`, `--schematic-socket`, `--schematic-socket-edge`, `--schematic-label`): a host restyles them on the page, and a texture draws the defaults.
29
+ - **A box child can be placed** (`at: { x, y }`): over the flowed child before it, at that offset, taking no flow space and hit first. Sockets on a schematic are this.
30
+
31
+ ### Changed
32
+
33
+ - **The closed loadout is a small picture**: no heading over it and no sockets drawn, only what is in them (all return when open), corners rounded well past the panel's own, 0.125 m wide (was 0.3; `focusScale` 4.56 keeps the opened size), and flat it sits nearer the top-left corner (18° up, was 10°), square to the screen. Frame panel anchors gain `flatOn` (parallel to the frame's view plane rather than turned to face the eye) and `tiltDeg` (tip it about its own horizontal axis without moving it) (#3290).
34
+ - **Inventory list rows are full width**, so a row is a button you can hit anywhere along it. **Swimming starts chest deep** (0.65 submerged, down to 0.5 to stand again; it was waist deep, 0.5 and 0.35), and a swimmer rides a little higher than the last change (12 cm of water above the head bone, was 20; treading unchanged) (#3290).
35
+ - **The opened dock's list is as tall as the loadout beside it** (it scrolls), not a fixed 540 that made the whole dock taller than its picture. `height` still sets it explicitly (#3290).
36
+ - **An inventory panel's heading sits over its schematic's top left** (title, totals, the container's name) instead of stacked above it, so the picture is larger, which matters most on the small pinned loadout. A panel whose first container is not a schematic keeps its heading above (#3290).
37
+ - **SVG panels are mipmapped** (`SvgTexture` `mipmaps`, default `true`: trilinear, anisotropic 4). A panel is nearly always drawn smaller than its texture (1024 px over ~300 px of a Quest 3's view at arm's length), and unfiltered its hairlines broke up and its text crawled; the small pinned loadout's socket outlines were visibly broken and now draw whole. Every in-scene panel, popup and HUD goes through it. Mip generation measured under 0.1 ms per repaint at 1024 px; it costs a third more texture memory. `mipmaps: false` for a texture always seen at about a texel per pixel.
38
+ - **An inventory panel on a page fits the space it is given** instead of being cropped (`fit`, default on): side by side they shrink in proportion, in a column it is no wider than the column, never taller than its container, never larger than drawn, always the same shape. Anything the page sets inline on the host wins (#3290).
39
+ - The default body panels are an inventory over the left shoulder and a **journal** over the right (both were "Inventory") (#3290).
40
+ - `panelScene`'s orbit camera stops at half its starting distance (`camera.minRadius`), so zooming cannot pass through the panel (#3290).
41
+ - **A planet's edge has a golden hour.** Where you see its air edge on and the sun is low over it, the air takes the colour left when its own is scattered out: Earth's goes gold, Mars's dusty air goes blue. The whole lit rim takes it together, by how far the sun has gone behind the planet from where you are: lit from the side, the rim (its "poles" included) stays the air's own colour. Face on, nothing changes (#3285).
42
+ - **The Solar System's sky is tilted as ours is**: the planets orbit about 60.2° to the galaxy's plane, so the Milky Way crosses the orbits steeply instead of running along them (`spaceBackdrop({ tilt })`).
43
+ - **While you carry something, the sockets that would take it light up**, the one under the pointer brighter and named, and the rest go dark. The ghost hangs above the pointer, so the socket you are over stays in sight (#3290).
44
+ - **A panel that shows one list takes a drop anywhere on it**, not just on its tiles or the "put in" row at the bottom of a scrolled list, and is outlined while it would take what you carry. Dragging a thing off a socket back into the bag is a drop on the bag (`carryPanel(box, carry, { fallback })`) (#3290).
45
+ - **A drop that does not land springs back.** Refused by its target, let go on nothing, or released outside every panel, the ghost returns to where it was picked up and shows why it was refused there for a moment. `carryPanel` now returns `dispose`, and `inventoryPanel` calls it on each rebuild (a rebuilt panel used to leave its old carry subscription behind) (#3290).
46
+
47
+ ### Fixed
48
+
49
+ - **In a headset, a screen-pinned panel is where its VR anchor says** (found by the release review). Screen pins are for a monitor: in a session they were still applied, putting the loadout a metre ahead, sized from the flat canvas, instead of on your left shoulder. `presentationSpec(spec, flat)` is now the one rule every attach site uses (flat: the flat anchor and screen pins; in VR: neither). Checked in the emulated headset.
50
+ - **Two-tap works on a page** (found by the release review). The page examples cancelled the carry on every `pointerup`, and a tap pick-up happens ON a release, so it was put straight back. `carryOverlay` now cancels a drag let go over nothing itself, after the panels have had the release, and never a tap; the examples no longer add their own.
51
+ - **A press on a panel in the scene no longer also works the game's controls.** The biped reads mouse buttons (the left button is A, a jump), so every press on the playground's loadout made the hero jump, and with a weapon up could fire it. A frame panel now cancels the `pointerdown` it takes, which by the web's own rule stops the matching `mousedown`/`mouseup`; the scene's picking is pointer events and carries on.
52
+ - **A focused panel fits the view a looking-down camera actually has.** Its room was worked out from where it sits in its frame, which is level while a follow camera pitches down; it is now measured in the camera's own view.
53
+ - **Swimmers ride lower.** The water sat at the head bone (the base of the skull), which left a treader's shoulders and arms out and a swimmer's head, shoulders and back riding on top. It now sits 10 cm higher on a treader (at the jaw: the head out) and 20 cm on a swimmer (over the back: the head breaking).
54
+ - **Raising the weapon aims straight ahead.** The aim is the look pitch, which kept whatever it was left at (looking at the ground, the last dive), so the gun often came up pointing at your feet. `setGunplay(true)` levels it, flat only (in a headset the aim is the controller); lowering the weapon leaves the view alone.
55
+ - **First person shows the world, not the back of your head.** Sprinting leans the head 22° forward and swimming far more, and the back and underside of the skull filled the view (measured in the playground: half the screen while sprinting). The head bone is collapsed while in first person, the way first-person games do it, so the arms and body stay (your hands show as you swim); it is restored out of first person, and the shadow is headless while you are in it. The eye now sits along the head's own axes, 10 cm up the skull, rather than offset along the body's level facing (#3291).
56
+ - **Aiming down while swimming dives.** At the surface a swimmer's buoyancy (set so the swim pose floats on the waterline) lifted about as fast as a downward stroke sank, so pitching down with the right stick and swimming went nowhere, and up is capped at the surface by design: the stick seemed to do nothing either way. A stroke aimed down now takes the vertical from buoyancy in proportion to how steeply and how hard you swim (`diveBuoyancy`); let go and buoyancy surfaces you as before. Measured in the new pool: 1.3 m deeper in two seconds, where it was 0.3.
57
+ - **An open inventory survives entering or leaving a headset.** Opened flat, then into VR, the loadout came up small on its shoulder still showing the bag, and a tap did nothing (it was already open), there and back out. A session change rebuilds the frame panels, and the new one never heard the dock's `panelfocus`. Content now also marks its SVG `data-focus="true"` while focused, and a frame panel made later starts from it (#3290).
58
+ - **A sun's shadows can be turned off**: `<tosi-b3d-sun shadows="off">` (`'on'` by default; live, so a toggle works). The demos, the README and this repo's notes passed `shadowCascading: true`, which was never an attribute: shadows are always cascaded, and `shadowCascading: false` silently did nothing. It is gone from every example.
59
+ - **No more fine bands over every lit surface.** `<tosi-b3d-sun>`'s `shadowBias` default is 0.0005 (was Babylon's 0.00005). With `shadowNormalBias` at 0.02, which has to stay low or thin limbs lose their shadow, plain scenes (the primitives and getting-started demos) were striped with shadow acne on the ground, the boxes and the spheres. A character's shadow is unchanged. The attribute table also said `shadowNormalBias` defaulted to 0.05; it is 0.02.
60
+ - **The checker ground is checkered.** `b3dGround({ texture: 'checker' })` drew one flat grey: the texture clamped at its edge instead of repeating, so every tile past the first was the edge colour. It repeats now, and is mipmapped so it does not shimmer toward the horizon. Its dark squares are 70% of the ground colour (they were a fixed grey barely darker than the default ground).
61
+ - **Getting started opens on a running scene**: the first scene is a live example, and below it the same scene as plain HTML markup, live too.
62
+ - **A star system keeps real time.** Every day, month and year is the real one, and a new `timeScale` attribute (simulated seconds per real second, default 3600: an hour a second, an Earth day in 24 seconds) says how fast it runs; both demos have a log **time** slider from real time to a year every ten seconds. Planets used to turn once in about 80 seconds against a 60-second year, so they looked still, and moons took longer to go round their planet than the planet took to go round the Sun. Now `SystemPlanetSpec.day` (Earth days, negative backwards) and `SystemBodySpec.period` (a moon's month in days, negative backwards) carry the truth for Sol (Venus and Uranus turn backwards, Triton goes round backwards); a generated moon gets Kepler's month for a true distance (`moonPeriod`), a generated rock an Earth day, a giant a Jupiter one. Orbits, months and days are angles of one clock (`spinAngle`), so they keep time together and stop together with `animate`.
63
+ - **Dragging on a frame panel no longer turns the camera.** Flat, the camera's own controls took the same pointer, so a drag across a panel orbited the view behind it. A press on a frame panel now holds the camera's controls until release (#3290).
64
+ - **A drag that leaves its panel at once still drags.** A press on a thing, then a pointer that was already over another panel by its next move (a controller ray moves in big steps; so does a fast flick), forgot the press and dragged nothing. Leaving a panel with a thing pressed now starts the drag. Found driving the loadout with an emulated controller (#3290).
65
+ - **A trigger press in VR always counts, whichever hand it is.** Babylon points with one controller, the first to connect, and a press on the other one only switched to it: the press was dropped, so the first press of a session (and any press after the headset swapped controllers) did nothing. Now the right hand points first, and a press on the hand that is not pointing switches to it and is delivered. It reads Babylon's private per-controller state; a Babylon upgrade that renames it brings back the dropped press rather than breaking anything (#3290).
66
+ - An inventory panel's totals line is always there, so the first thing worn no longer pushes the schematic down (#3290).
67
+ - **Dragging between inventory panels works with a finger or a headset's laser.** Both are captured by the element they press, so the moves and the release never reached another panel; `inventoryPanel` now lets the capture go as it arrives (a mouse was never captured, which is why it worked on a desktop). And on a page the ghost is drawn in one overlay above every panel (`carryOverlay(carry)`, with `carryPanel(…, { ghost: false })`), so it crosses from one panel to the next instead of being clipped by each. A panel on a 3D plane keeps its own ghost (#3290).
68
+
69
+ - **A thing can be dragged out of a socket** (it could only be dragged out of a list), and a thing in a list is itself a place to drop into that list, so a long, scrolled list takes a drop anywhere. A thing put back in a list joins a stack of its kind with room (a missile returned to the carrier is counted again); one drawn from a stack swaps with what a slot held.
70
+
71
+ Size: barrel (min, packages external) 1082.1 KB raw / 368.9 KB gz, +3.80% on 0.10.5.
72
+
9
73
  ## 0.10.5
10
74
 
11
75
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@ markup you can read:
17
17
 
18
18
  ```javascript
19
19
  b3d(
20
- b3dSun({ shadowCascading: true }),
20
+ b3dSun(),
21
21
  b3dSkybox({ timeOfDay: 6 }),
22
22
  b3dLoader({ url: './scene.glb' }),
23
23
  b3dWater({ y: -0.2 })
@@ -45,7 +45,7 @@ import { b3d, b3dSun, b3dSkybox, b3dGround, b3dBox, b3dSphere } from 'tosijs-3d'
45
45
 
46
46
  document.body.append(
47
47
  b3d(
48
- b3dSun({ shadowCascading: true }),
48
+ b3dSun(),
49
49
  b3dSkybox({ timeOfDay: 10 }),
50
50
  b3dGround({ size: 24, texture: 'checker' }),
51
51
  b3dBox({ x: -1.5, y: 0.5, color: '#c8553d' }),
@@ -0,0 +1,60 @@
1
+ /** A length: layout units, or a fraction of the container (`'50%'`). */
2
+ export type AnchorLength = number | `${number}%`;
3
+ /** A rectangle, top-left origin, in layout units. */
4
+ export interface AnchorBox {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ }
10
+ export interface AnchorItem {
11
+ left?: AnchorLength;
12
+ right?: AnchorLength;
13
+ top?: AnchorLength;
14
+ bottom?: AnchorLength;
15
+ /**
16
+ * The point of the item a pin places, as a fraction of its width: 0 its
17
+ * left edge (default), 0.5 its centre, 1 its right edge. Between two pins,
18
+ * which way a minimum that does not fit overflows.
19
+ */
20
+ anchorX?: number;
21
+ /** The same, of its height: 0 its top edge (default). */
22
+ anchorY?: number;
23
+ /** Both anchors at once: `'center'` is `anchorX: 0.5, anchorY: 0.5`. */
24
+ anchor?: 'center';
25
+ /** Exact with one pin or none; a minimum between two pins. */
26
+ width?: AnchorLength;
27
+ /** Exact with one pin or none; a minimum between two pins. */
28
+ height?: AnchorLength;
29
+ /** Paint order; higher is on top. Default 0, ties keep the order given. */
30
+ z?: number;
31
+ /**
32
+ * The item's natural size: called with no width for its natural width,
33
+ * then with the resolved width for its height. Leave `width` out of the
34
+ * answer to fill what it is given. Absent: no natural size, so it fills
35
+ * what it is given on both axes.
36
+ */
37
+ measure?: (width?: number) => {
38
+ width?: number;
39
+ height: number;
40
+ };
41
+ /** Pin within this rectangle of the container instead of its edges. */
42
+ within?: AnchorBox;
43
+ }
44
+ export interface AnchorResult {
45
+ /** One box per item, in the order given. */
46
+ boxes: AnchorBox[];
47
+ /** Item indices bottom to top: paint in this order, hit-test from the end. */
48
+ order: number[];
49
+ }
50
+ /** A length in layout units, given the size a fraction is of. */
51
+ export declare function anchorLength(v: AnchorLength, of: number): number;
52
+ /**
53
+ * Lay out `items` in a container `width` x `height`. Returns a box per item
54
+ * and the order to paint them in.
55
+ */
56
+ export declare function anchorLayout(items: AnchorItem[], container: {
57
+ width: number;
58
+ height: number;
59
+ }): AnchorResult;
60
+ //# sourceMappingURL=anchor-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor-layout.d.ts","sourceRoot":"","sources":["../src/anchor-layout.ts"],"names":[],"mappings":"AA4EA,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,CAAA;AAEhD,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,GAAG,CAAC,EAAE,YAAY,CAAA;IAClB,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,MAAM,CAAC,EAAE,QAAQ,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,2EAA2E;IAC3E,CAAC,CAAC,EAAE,MAAM,CAAA;IACV;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAChE,uEAAuE;IACvE,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAGhE;AAuCD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C,YAAY,CAoCd"}
@@ -0,0 +1,140 @@
1
+ /*#
2
+ # anchor-layout
3
+
4
+ Pure **anchored layout**: the third way to lay out the SVG UI, beside the
5
+ flow ([flow-layout](/flow-layout/)). Where the flow stacks and wraps, this
6
+ **positions and layers**: each item is pinned to the edges of its container
7
+ (or to a named rectangle inside it, such as a window in a piece of artwork),
8
+ and is drawn over whatever came before it. A HUD, widgets composed onto
9
+ artwork, a desktop-style window with its title bar and close button: all of
10
+ these are this.
11
+
12
+ Like the flow it is pure (no DOM, no Babylon), measured in layout units, and
13
+ re-run whenever the container changes size.
14
+
15
+ ## The rule, per axis
16
+
17
+ Horizontally (vertically the same, with `top`, `bottom`, `anchorY` and
18
+ `height`):
19
+
20
+ | Pins | Width | Where |
21
+ | --- | --- | --- |
22
+ | `left` and `right` | what the pins leave, but never less than `width` (a stated width is a MINIMUM) | between the pins; a minimum that does not fit overflows by the anchor |
23
+ | `left` | `width`, else the item's natural width | its anchor point at `left` |
24
+ | `right` | the same | its right edge at `right` from the right edge (its anchor point, if you give one) |
25
+ | none | the same | as `left: 0` |
26
+
27
+ An offset or a size is a number (layout units) or a **fraction** of the
28
+ container, written `'50%'`.
29
+
30
+ **The anchor** is the point of the ITEM that a pin places, as fractions of its
31
+ own size: `anchorX` 0 is its left edge, 0.5 its centre, 1 its right edge. The
32
+ default is the TOP LEFT, to keep surprises down, so `left: '50%'` puts the left
33
+ edge at the middle and **`anchor: 'center'` with `left: '50%'` and `top: '50%'`
34
+ centres an item**. A lone `right` (or `bottom`) pin places the right (bottom)
35
+ edge unless an anchor is given, or a default top-left anchor would hang the
36
+ item outside the container.
37
+
38
+ **The anchor also decides overflow.** Between two pins the item is stretched;
39
+ when its minimum does not fit, the anchor says which way the excess goes: 0
40
+ runs past the right pin (as CSS's `min-width` does), 1 past the left, 0.5
41
+ spills evenly both ways. It is never squashed below its minimum.
42
+
43
+ - **Width comes first.** The item is then asked for its height AT that width,
44
+ so text in a stretched item wraps to the width it was given.
45
+ - **An item with no natural width** (a block that fills what it is given)
46
+ fills from its pin to the opposite edge, or the whole width with no pin.
47
+ - **Layers** paint in the order given unless an item has a `z` (higher is
48
+ on top; equal `z` keeps the order given). `order` lists the items bottom
49
+ to top; hit-test it from the end.
50
+ - **`within`** is a rectangle inside the container (a window in the art, by
51
+ name, measured by the caller) that the item's pins and fractions are
52
+ measured against instead of the container's edges.
53
+
54
+ ## Example
55
+
56
+ ```javascript
57
+ import { ui } from 'tosijs-3d'
58
+ const { anchorLayout } = ui
59
+
60
+ // A 400 x 300 window: a title bar across the top, a close button in its
61
+ // corner, content filling the rest, and a dialog centred over it all.
62
+ const { boxes } = anchorLayout(
63
+ [
64
+ { left: 0, right: 0, top: 0, height: 28 },
65
+ { right: 4, top: 4, width: 20, height: 20 },
66
+ { left: 0, right: 0, top: 28, bottom: 0 },
67
+ { left: '50%', top: '50%', anchor: 'center', width: '60%', height: 120 },
68
+ ],
69
+ { width: 400, height: 300 }
70
+ )
71
+ // boxes[0] = { x: 0, y: 0, width: 400, height: 28 }
72
+ // boxes[3] = { x: 80, y: 90, width: 240, height: 120 }
73
+ ```
74
+ */
75
+ /*{ "parent": "box", "order": 11 }*/
76
+ /** A length in layout units, given the size a fraction is of. */
77
+ export function anchorLength(v, of) {
78
+ if (typeof v === 'number')
79
+ return v;
80
+ return (parseFloat(v) / 100) * of;
81
+ }
82
+ /** Size and offset along one axis; `natural` is the item's own size, if any. */
83
+ function solveAxis(a, span, natural) {
84
+ const L = (v) => anchorLength(v, span);
85
+ const stated = a.size != null ? L(a.size) : undefined;
86
+ const anchor = a.anchor ?? 0;
87
+ if (a.start != null && a.end != null) {
88
+ const at = L(a.start);
89
+ const between = span - at - L(a.end);
90
+ const size = Math.max(between, stated ?? 0);
91
+ return { at: at - anchor * (size - between), size };
92
+ }
93
+ const own = (room) => stated ?? natural() ?? room;
94
+ if (a.start != null) {
95
+ const p = L(a.start);
96
+ const size = own(span - p);
97
+ return { at: p - anchor * size, size };
98
+ }
99
+ if (a.end != null) {
100
+ const p = span - L(a.end);
101
+ const size = own(p);
102
+ return { at: p - (a.anchor ?? 1) * size, size };
103
+ }
104
+ const size = own(span);
105
+ return { at: -anchor * size, size };
106
+ }
107
+ /**
108
+ * Lay out `items` in a container `width` x `height`. Returns a box per item
109
+ * and the order to paint them in.
110
+ */
111
+ export function anchorLayout(items, container) {
112
+ const boxes = items.map((it) => {
113
+ const r = it.within ?? {
114
+ x: 0,
115
+ y: 0,
116
+ width: container.width,
117
+ height: container.height,
118
+ };
119
+ const centre = it.anchor === 'center' ? 0.5 : undefined;
120
+ const h = solveAxis({
121
+ start: it.left,
122
+ end: it.right,
123
+ anchor: it.anchorX ?? centre,
124
+ size: it.width,
125
+ }, r.width, () => it.measure?.().width);
126
+ const v = solveAxis({
127
+ start: it.top,
128
+ end: it.bottom,
129
+ anchor: it.anchorY ?? centre,
130
+ size: it.height,
131
+ }, r.height, () => it.measure?.(h.size).height);
132
+ return { x: r.x + h.at, y: r.y + v.at, width: h.size, height: v.size };
133
+ });
134
+ const order = items
135
+ .map((it, i) => ({ z: it.z ?? 0, i }))
136
+ .sort((a, b) => a.z - b.z || a.i - b.i)
137
+ .map((o) => o.i);
138
+ return { boxes, order };
139
+ }
140
+ //# sourceMappingURL=anchor-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor-layout.js","sourceRoot":"","sources":["../src/anchor-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyEE;AACF,oCAAoC;AAoDpC,iEAAiE;AACjE,MAAM,UAAU,YAAY,CAAC,CAAe,EAAE,EAAU;IACtD,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAA;IACnC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,CAAA;AACnC,CAAC;AASD,gFAAgF;AAChF,SAAS,SAAS,CAChB,CAAO,EACP,IAAY,EACZ,OAAiC;IAEjC,MAAM,CAAC,GAAG,CAAC,CAAe,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IACpD,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACrD,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QACrC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QACrB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,CAAA;QAC3C,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,CAAA;IACrD,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,IAAI,OAAO,EAAE,IAAI,IAAI,CAAA;IACzD,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QACpB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAA;QAC1B,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,CAAA;IACxC,CAAC;IACD,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACzB,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QACnB,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,CAAA;IACjD,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;IACtB,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,CAAA;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAmB,EACnB,SAA4C;IAE5C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE;QAC7B,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,IAAI;YACrB,CAAC,EAAE,CAAC;YACJ,CAAC,EAAE,CAAC;YACJ,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,MAAM,EAAE,SAAS,CAAC,MAAM;SACzB,CAAA;QACD,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;QACvD,MAAM,CAAC,GAAG,SAAS,CACjB;YACE,KAAK,EAAE,EAAE,CAAC,IAAI;YACd,GAAG,EAAE,EAAE,CAAC,KAAK;YACb,MAAM,EAAE,EAAE,CAAC,OAAO,IAAI,MAAM;YAC5B,IAAI,EAAE,EAAE,CAAC,KAAK;SACf,EACD,CAAC,CAAC,KAAK,EACP,GAAG,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAC3B,CAAA;QACD,MAAM,CAAC,GAAG,SAAS,CACjB;YACE,KAAK,EAAE,EAAE,CAAC,GAAG;YACb,GAAG,EAAE,EAAE,CAAC,MAAM;YACd,MAAM,EAAE,EAAE,CAAC,OAAO,IAAI,MAAM;YAC5B,IAAI,EAAE,EAAE,CAAC,MAAM;SAChB,EACD,CAAC,CAAC,MAAM,EACR,GAAG,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAClC,CAAA;QACD,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACxE,CAAC,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,KAAK;SAChB,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAClB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACzB,CAAC"}
@@ -219,9 +219,16 @@ export declare class B3dBiped extends B3dControllable {
219
219
  /** Eye position for first-person: the head bone (or eyeHeight fallback) nudged
220
220
  * up + forward to roughly the eyes, so the camera sits at the eyes rather than
221
221
  * the neck / head-bone base — which would leave the head & neck in front of the
222
- * view. Forward is body-facing (root-aligned), since the camera ignores the
223
- * head's own animation. Returns a cached vector — read it now, don't retain. */
224
- getHeadPosition(): BABYLON.Vector3 | null;
222
+ * view. Returns a cached vector — read it now, don't retain.
223
+ *
224
+ * ALONG THE HEAD'S OWN AXES, not the body's. A sprint leans the head 22°
225
+ * forward and a swim far more, and an offset along the body's level facing
226
+ * left the eye inside the skull: in first person the back of your own head
227
+ * filled the screen as you ran (Tonio: "first person view when swimming is a
228
+ * disaster and it's still a big problem when running"). The UAL head bone's
229
+ * forward is the face's, measured. Only the eye's POSITION follows the head;
230
+ * the view's orientation does not (no bob in the rotation, no roll). */
231
+ getHeadPosition(extraForward?: number): BABYLON.Vector3 | null;
225
232
  xrStuff?: XRStuff;
226
233
  private xrInputProvider?;
227
234
  animationState?: AnimState;
@@ -302,6 +309,13 @@ export declare class B3dBiped extends B3dControllable {
302
309
  */
303
310
  gunplay: boolean;
304
311
  private weaponWasPressed;
312
+ /**
313
+ * What the weapon button does, when a game decides (JS only). Called with
314
+ * whether the button asks for the weapon UP; the game moves it (draw from
315
+ * the hip, holster it) and calls `setGunplay` if and when it is in hand.
316
+ * Unset, the button toggles `gunplay` as before.
317
+ */
318
+ whenWeapon: ((up: boolean) => void) | null;
305
319
  /**
306
320
  * Put the weapon up or down.
307
321
  *
@@ -1 +1 @@
1
- {"version":3,"file":"b3d-biped.d.ts","sourceRoot":"","sources":["../src/b3d-biped.ts"],"names":[],"mappings":"AAkOA,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EACL,OAAO,EAKR,MAAM,gBAAgB,CAAA;AAwBvB,OAAO,EAQL,KAAK,GAAG,EAET,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAWxE,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,qBAAa,SAAS;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;gBAEN,IAAI,EAAE,aAAa;IAQ/B,MAAM,CAAC,SAAS,CAAC,GAAG,KAAK,EAAE,aAAa,EAAE,GAAG,SAAS,EAAE;CAGzD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,GAAE,aAAa,EAAO,GAAG,SAAS,EAAE,CAuE3E;AA+BD,qBAAa,QAAS,SAAQ,eAAe;IAC3C,MAAM,CAAC,gBAAgB,SAAmB;IAElC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,OAAO,CAAA;IAEvB,MAAM,CAAC,cAAc;;;;;;;;;;;;;;;;;;;QA8CnB,0DAA0D;;QAE1D;;;;;;;;;;;;;;;;WAgBG;gBACc,IAAI,GAAG,KAAK;QAC7B,+EAA+E;;QAiB/E,8DAA8D;;QAE9D,gDAAgD;;QAEhD;;;;;;;;;WASG;QAgBH;;;;;;WAMG;;qBAEmB,IAAI,GAAG,KAAK;QAClC;;;;;WAKG;;QAEH;;;;;WAKG;;QAGH,gDAAgD;;QAEhD,+EAA+E;;QAE/E;;;;;;;;;;;WAWG;QACH;;;;;;;;;;;;;;;;;WAiBG;QACH;;;;;;;;;WASG;;;;QAIH;;;;;;;;;;;;;;;;WAgBG;;;;;;;;;MAEJ;IAED,OAAO,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAA;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAA;IACvB;;6EAEyE;IACzE,UAAU,EAAE,OAAO,GAAG,KAAK,CAAU;IACrC,OAAO,CAAC,SAAS,CAAkC;IACnD,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,UAAU,CAA6B;IAE/C;;gFAE4E;IAC5E,WAAW,SAAM;IACjB,aAAa,SAAM;IACnB,OAAO,CAAC,OAAO,CAAwB;IACvC;;;;oFAIgF;IAChF,eAAe,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI;IAsBzC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,CAAA;IACvC,kFAAkF;IAClF,OAAO,CAAC,UAAU,CAGf;IACH,0FAA0F;IAC1F,OAAO,CAAC,QAAQ,CAAI;IACpB;;;;OAIG;IACH,kFAAkF;IAClF,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,OAAO,CAQA;IACf,0EAA0E;IAC1E,OAAO,CAAC,UAAU,CAQH;IACf,6FAA6F;IAC7F,OAAO,CAAC,aAAa,CAIN;IACf,cAAc,CAAC,EAAE,cAAc,CAAA;IAE/B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAI;IACpB,8EAA8E;IAC9E,OAAO,CAAC,SAAS,CAAQ;IACzB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAI;IACpB,uEAAuE;IACvE,OAAO,CAAC,UAAU,CAAI;IACtB,8EAA8E;IAC9E,OAAO,CAAC,eAAe,CAAQ;IAC/B,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAI;IACpB;;;OAGG;IACH,OAAO,CAAC,UAAU,CAAI;IACtB,qEAAqE;IACrE,OAAO,CAAC,IAAI,CAAI;IAChB,uEAAuE;IACvE,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IACD;;;;;OAKG;IACH,OAAO,CAAC,IAAI,CAAkC;IAE9C;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;OAWG;IACH,OAAO,UAAQ;IACf,OAAO,CAAC,gBAAgB,CAAQ;IAEhC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAK7B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,YAAY;IAIpB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,WAAW;IAwCnB;;;;;;;;;;;;OAYG;IACH,YAAY,GAAI,KAAK,cAAc,EAAE,IAAI,MAAM,KAAG,YAAY,CACzB;IAErC;;;;;;;;;;;OAWG;IACH,OAAO,KAAK,SAAS,GAEpB;IAcD,OAAO,CAAC,YAAY,CAIb;IACP,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,QAAQ;IAahB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,eAAe,CAGtB;IAED,OAAO,CAAC,cAAc;IAsHtB,OAAO,CAAC,OAAO,CAId;IACD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAuB;IAEzC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,YAAY;IAgBpB;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;IAQjB,sEAAsE;IACtE,OAAO,CAAC,OAAO,CAA2D;IAC1E,2EAA2E;IAC3E,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,OAAO,CAId;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IAmC1B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW;IAWnB,iFAAiF;IACjF,IAAI,GAAG,IAAI,GAAG,CAEb;IAED;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,OAAO,CAAC,OAAO,CAQlC;IAED;;;;;;;OAOG;IACH,IAAI,SAAS,IAAI,OAAO,CAAC,OAAO,CAO/B;IACD,OAAO,CAAC,SAAS,CAAQ;IACzB,yEAAyE;IACzE,OAAO,CAAC,YAAY,CAAQ;IAC5B,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAkC;IACnD;;;;;OAKG;IACH,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,UAAU,CAAI;IAEtB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,SAAS,CAAQ;IACzB,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,QAAQ,CAMH;IACb,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAkB;IAElC;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,SAAS,CAAO;IAExB,eAAe,cAkBd;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,OAAO,CAAC,WAAW;IAqCnB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAYnB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,YAAY;IAgDpB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,CAAC,cAAc;IAatB;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;IAiGlB;;;OAGG;IACH,OAAO,CAAC,UAAU;IA8ClB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,QAAQ;IAKhB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,YAAY,CAAK;IAEzB,OAAO,CAAC,eAAe;IA+CvB,8CAA8C;IAC9C,OAAO,CAAC,SAAS,CAAI;IAErB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAI;IA+EzC,eAAe,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI;IAItC,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM;IA2qC1C,OAAO,CAAC,YAAY;IASd,aAAa;IAoJb,iBAAiB;IAwDvB;;;mDAG+C;IAC/C,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK;IAyBnC;;;;+EAI2E;IAC3E,OAAO,CAAC,aAAa;IAUrB,iBAAiB;IAoBjB;;mDAE+C;IAC/C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAmB5B,OAAO,CAAC,SAAS,CAAiD;IAElE;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAoB1C,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ/B,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK;IA+F3C,YAAY;IAiCZ,MAAM;CA6BP;AAED,eAAO,MAAM,QAAQ,2CAA4B,CAAA"}
1
+ {"version":3,"file":"b3d-biped.d.ts","sourceRoot":"","sources":["../src/b3d-biped.ts"],"names":[],"mappings":"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"}
package/dist/b3d-biped.js CHANGED
@@ -226,7 +226,7 @@ document.body.append(
226
226
  import * as BABYLON from '@babylonjs/core';
227
227
  import { collidable, isOff, markUiMesh, refuseOwnAncestor, } from './b3d-utils.js';
228
228
  import { CLIMB_PHASE, canMantle, mantleClip, mantlePath, defaultMantleLimits, } from './mantle.js';
229
- import { buoyantStep, submergedFraction, isSwimming, swimBuoyancy, } from './buoyancy.js';
229
+ import { buoyantStep, diveBuoyancy, submergedFraction, isSwimming, swimBuoyancy, } from './buoyancy.js';
230
230
  import { aimFromLook, clampAim, easeAim, aimTarget, surfaceAimLimit, } from './swim-aim.js';
231
231
  import { iceSide } from './water-shore.js';
232
232
  import { fitChase } from './camera-fit.js';
@@ -374,6 +374,10 @@ const ICE_GROUP = ['ice'];
374
374
  * jetpack.
375
375
  */
376
376
  const SWIM_THRUST = 6;
377
+ // How far above the head bone the water sits, metres: treading (water at the
378
+ // jaw, the head out) and stroking (over the back, the head breaking).
379
+ const TREAD_SINK = 0.1;
380
+ const SWIM_SINK = 0.12;
377
381
  /** Local +Z. Allocated once — this is read every frame while swimming. */
378
382
  const LOCAL_FORWARD = new BABYLON.Vector3(0, 0, 1);
379
383
  /** Scratch for the yaw read-back; never allocate inside the per-frame loop. */
@@ -602,21 +606,32 @@ export class B3dBiped extends B3dControllable {
602
606
  /** Eye position for first-person: the head bone (or eyeHeight fallback) nudged
603
607
  * up + forward to roughly the eyes, so the camera sits at the eyes rather than
604
608
  * the neck / head-bone base — which would leave the head & neck in front of the
605
- * view. Forward is body-facing (root-aligned), since the camera ignores the
606
- * head's own animation. Returns a cached vector — read it now, don't retain. */
607
- getHeadPosition() {
609
+ * view. Returns a cached vector — read it now, don't retain.
610
+ *
611
+ * ALONG THE HEAD'S OWN AXES, not the body's. A sprint leans the head 22°
612
+ * forward and a swim far more, and an offset along the body's level facing
613
+ * left the eye inside the skull: in first person the back of your own head
614
+ * filled the screen as you ran (Tonio: "first person view when swimming is a
615
+ * disaster and it's still a big problem when running"). The UAL head bone's
616
+ * forward is the face's, measured. Only the eye's POSITION follows the head;
617
+ * the view's orientation does not (no bob in the rotation, no roll). */
618
+ getHeadPosition(extraForward = 0) {
608
619
  if (this.mesh == null)
609
620
  return null;
610
- const f = this.mesh.forward;
611
621
  const EYE_FWD = 0.12;
612
- const EYE_UP = 0.06;
622
+ // The head bone is at the base of the skull; the eyes are ~10 cm up it.
623
+ const EYE_UP = 0.1;
613
624
  if (this.headNode != null) {
614
625
  const hp = this.headNode.getAbsolutePosition();
615
- this._eyePos.set(hp.x + f.x * EYE_FWD, hp.y + EYE_UP, hp.z + f.z * EYE_FWD);
626
+ const hf = this.headNode.forward;
627
+ const hu = this.headNode.up;
628
+ const fwd = EYE_FWD + extraForward;
629
+ this._eyePos.set(hp.x + hf.x * fwd + hu.x * EYE_UP, hp.y + hf.y * fwd + hu.y * EYE_UP, hp.z + hf.z * fwd + hu.z * EYE_UP);
616
630
  }
617
631
  else {
632
+ const f = this.mesh.forward;
618
633
  const o = this.mesh.getAbsolutePosition();
619
- this._eyePos.set(o.x + f.x * EYE_FWD, o.y + this.eyeHeight, o.z + f.z * EYE_FWD);
634
+ this._eyePos.set(o.x + f.x * (EYE_FWD + extraForward), o.y + this.eyeHeight, o.z + f.z * (EYE_FWD + extraForward));
620
635
  }
621
636
  return this._eyePos;
622
637
  }
@@ -703,6 +718,13 @@ export class B3dBiped extends B3dControllable {
703
718
  */
704
719
  gunplay = false;
705
720
  weaponWasPressed = false;
721
+ /**
722
+ * What the weapon button does, when a game decides (JS only). Called with
723
+ * whether the button asks for the weapon UP; the game moves it (draw from
724
+ * the hip, holster it) and calls `setGunplay` if and when it is in hand.
725
+ * Unset, the button toggles `gunplay` as before.
726
+ */
727
+ whenWeapon = null;
706
728
  /**
707
729
  * Put the weapon up or down.
708
730
  *
@@ -710,6 +732,16 @@ export class B3dBiped extends B3dControllable {
710
732
  * a mode a consumer cannot set is a mode they have to fight.
711
733
  */
712
734
  setGunplay(on) {
735
+ /*
736
+ RAISING THE WEAPON AIMS STRAIGHT AHEAD. The aim is the look pitch, and the
737
+ look pitch is whatever it was left at: walking around looking at the
738
+ ground, or the last dive, so the gun came up pointing at your feet. Tonio:
739
+ "It seems quite common for it to be aiming at your feet which is weird."
740
+ Levelled only on the way UP (lowering it leaves the view alone) and only
741
+ flat: in a headset the aim is the controller.
742
+ */
743
+ if (on && !this.gunplay && !this.owner?.xrActive)
744
+ this._lookPitch = 0;
713
745
  this.gunplay = on;
714
746
  this._showWeapons(on);
715
747
  }
@@ -1694,7 +1726,12 @@ export class B3dBiped extends B3dControllable {
1694
1726
  */
1695
1727
  const weaponPressed = (input.weapon ?? 0) > 0.5;
1696
1728
  if (weaponPressed && !this.weaponWasPressed) {
1697
- this.setGunplay(!this.gunplay);
1729
+ // A game that owns where the weapon IS (an inventory: on the hip, in
1730
+ // the hand) answers the button itself, and calls setGunplay.
1731
+ if (this.whenWeapon)
1732
+ this.whenWeapon(!this.gunplay);
1733
+ else
1734
+ this.setGunplay(!this.gunplay);
1698
1735
  }
1699
1736
  this.weaponWasPressed = weaponPressed;
1700
1737
  /*
@@ -1739,6 +1776,20 @@ export class B3dBiped extends B3dControllable {
1739
1776
  this.setCameraView(this.cameraView === 'chase' ? 'fpv' : 'chase');
1740
1777
  }
1741
1778
  this.viewWasPressed = viewPressed;
1779
+ /*
1780
+ NO HEAD IN FIRST PERSON. Whatever the eye offset, a sprint or a stroke
1781
+ swings the head through the view: the back and underside of your own skull
1782
+ filled the screen as you ran. Every first-person game hides it, and the
1783
+ standard way is to collapse the head BONE, which takes the head's geometry
1784
+ with it and leaves the arms and body (you still see your hands swim). Set
1785
+ every frame, after the clips have had their say; restored out of first
1786
+ person. The cost is a headless shadow while you are in it.
1787
+ */
1788
+ if (this.headNode != null) {
1789
+ const k = this.cameraView === 'fpv' ? 0.001 : 1;
1790
+ if (this.headNode.scaling.x !== k)
1791
+ this.headNode.scaling.setAll(k);
1792
+ }
1742
1793
  // First-person camera tracks the head node — so it doesn't fall behind the
1743
1794
  // head when walking, or float above it when crouching. Orientation stays
1744
1795
  // root-aligned (yaw only) to avoid the head's animation bob/rotation. Flat
@@ -1747,7 +1798,8 @@ export class B3dBiped extends B3dControllable {
1747
1798
  this.fpvCamera != null &&
1748
1799
  this.mesh != null &&
1749
1800
  !this.owner?.xrActive) {
1750
- const eye = this.getHeadPosition();
1801
+ // The extra `eyeForward` along the head too (see getHeadPosition).
1802
+ const eye = this.getHeadPosition(attrs.eyeForward);
1751
1803
  this.fpvCamera.parent = null;
1752
1804
  if (eye != null) {
1753
1805
  /*
@@ -1765,8 +1817,7 @@ export class B3dBiped extends B3dControllable {
1765
1817
  any wall you stand against — and standing against walls is most of what
1766
1818
  this arena is for.
1767
1819
  */
1768
- const f = this.mesh.forward;
1769
- this.fpvCamera.position.set(eye.x + f.x * attrs.eyeForward, eye.y, eye.z + f.z * attrs.eyeForward);
1820
+ this.fpvCamera.position.copyFrom(eye);
1770
1821
  }
1771
1822
  const f = this.mesh.forward;
1772
1823
  /*
@@ -2476,9 +2527,25 @@ export class B3dBiped extends B3dControllable {
2476
2527
  Below, `swimBuoyancy` blends this toward neutral as you go deeper, so
2477
2528
  diving still holds its depth; this only sets where the SURFACE is.
2478
2529
  */
2479
- const poseBuoyancy = this._swimWaterline(pose, attrs.buoyancy);
2530
+ /*
2531
+ HOW DEEP THE BODY RIDES. Water at the head bone (the base of the
2532
+ skull) left a treader's shoulders and arms out and a swimmer's head,
2533
+ shoulders and back riding on top. Tonio: "They should be almost
2534
+ completely submerged when swimming and just head mostly above water
2535
+ when treading water." So the water sits higher up the body: at the jaw
2536
+ treading, over the back with only the head breaking while stroking.
2537
+ */
2538
+ const stroking = this.animationState?.name === 'swim';
2539
+ const poseBuoyancy = this._swimWaterline(pose, attrs.buoyancy - (stroking ? SWIM_SINK : TREAD_SINK));
2540
+ /*
2541
+ AIMING DOWN AND SWIMMING DIVES. That surface buoyancy floats the root
2542
+ on the waterline, a lift of about 1.1 m/s, which cancelled a downward
2543
+ stroke: the right stick tipped the swimmer and nothing happened. Tonio:
2544
+ "I can't seem to steer swimming up or down with right stick." So a
2545
+ stroke aimed down takes the vertical from buoyancy in proportion.
2546
+ */
2480
2547
  this._fallVel = buoyantStep(this._fallVel, submerged, dt, {
2481
- buoyancy: swimBuoyancy(headDepth, { buoyancy: poseBuoyancy }),
2548
+ buoyancy: diveBuoyancy(swimBuoyancy(headDepth, { buoyancy: poseBuoyancy }), (this._swimPitch * Math.PI) / 180, input.forward ?? 0),
2482
2549
  thrust: (swimUp - swimDown) * SWIM_THRUST,
2483
2550
  });
2484
2551
  let nextY = node.position.y + this._fallVel * dt;
@@ -2788,6 +2855,8 @@ export class B3dBiped extends B3dControllable {
2788
2855
  // Create XR experience first
2789
2856
  const xr = await scene.createDefaultXRExperienceAsync({
2790
2857
  uiOptions: { sessionMode: mode },
2858
+ // The right hand points first (see tosi-b3d: otherwise a first press is lost).
2859
+ pointerSelectionOptions: { preferredHandedness: 'right' },
2791
2860
  });
2792
2861
  // Register controller observables BEFORE entering XR so we catch controller connect events
2793
2862
  this.setupXRInput(xr);