diff --git a/Architecture.md b/Architecture.md index 9213217..ec28ee9 100644 --- a/Architecture.md +++ b/Architecture.md @@ -117,7 +117,7 @@ web/play.html a released game (#266) play.html built pag src/main.ts, play.ts the two entries; page.ts is the canvas both are (the WebGL context, root, resize) src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject (and what a list calls one: `objectName`, `groupName`), Properties (value types, and the walks over a nested value: `mapNested`, a struct field taken out or renamed), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:@`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), structTypes (#253: Struct assets as types — every typed struct variable, struct field and `Pack`/`Expand` found and brought in line with its asset, a renamed field carried through every value first, the cycle rule), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by) -src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry, clip (the region clip areas cut each object to, a convex polygon, #260) (with the side a one-way collider blocks from, and the rectangle around a shape a broad phase looks at first, #184), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both) +src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry, clip (the region clip areas cut each object to, a convex polygon, #260) (with the side a one-way collider blocks from, and the rectangle around a shape a broad phase looks at first, #184), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), joint (what it reads off a joint: its settings, where its far end is, its length and a hinge's limits, #154), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both) src/core/util/ list (take out, put back and move an item — what a command's undo is made of), vec (dot, the closest point on a segment — shared by the collider maths, the outline hit area and the polygon grips; a cubic Bezier at `t` and cut in two at `t` — shared by vector art, its Boolean operations and the curve grips), color (`mixColor`, channel by channel — keys of an animation, stops of a gradient, the particles' Mix colour), recorder (#253: assignments taken back last first — the undo of an edit whose reach is not known in advance) src/core/commands/ undoable commands (pixelArtCommands: one command runs any pixel art edit and keeps the state before it, #130), CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), MoveCommand base (undone by moving back: tile picture, tilemap layer, vector shape), vectorArtCommands (#129: the shapes of a drawing, one gesture one command), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts src/core/graph/ graph model, NodeRegistry/NodeContext/values (a travelling list may hold live objects, `storedList` @@ -161,7 +161,8 @@ src/runtime/physics/ engine (loads Rapier once, reports its state, converts be what the physics nodes reach through `NodeContext.physics`), oneWay (the rule itself: whether a body lands on, is held by or passes a one-way collider — pure, so it is measured without the engine), bodyGeometry (a world shape as the engine colliders of a body, and the - poses handed between the two) + poses handed between the two), joints (one engine joint per joint entity whose object has + a rigidbody, and a body of the joint's own for an end that has none, #154) src/ui/ widget toolkit (generic, knows nothing about assets); fonts (the faces the editor ships: Source Sans 3 in 400, 500 and 600, Source Code Pro 400, Latin only, #202/#214/#273), icons + Icon (the menu's tick boxes and current-entry dot as SVG files, and a widget that shows one file diff --git a/Behaviors.md b/Behaviors.md index 576e8c1..d7ab238 100644 --- a/Behaviors.md +++ b/Behaviors.md @@ -231,7 +231,7 @@ playing use the same two words. `Key was pressed` and `Key was released` (true for the one frame in which the key went down or came up, the frame `On key down` and `On key up` fire in; a key that stays held is not pressed again), `Mouse position`, `Mouse button`, `Is hovered`, `Is touching`, `Layer of` (the collision layer of an object by name and number, see [Colliders](Colliders)), `Animation playing` (whether an animation runs on an object, its progress and time left) and `Animation length` (see [Animations](Animations)), `Get velocity`, `Current state` (name and id), `Is in state` (one boolean per state) · actions `Move` (dx, dy, scale, dt — connect an event's dt for per-second movement; in the object's own axes, which a turned parent turns too; with *Collide with solids* it slides along what it hits, less friction, and outputs `blocked x/y`, `hit` and the contact `normal x/y`, see [Colliders](Colliders)), `Set position`, `Spawn` - (asset input + x/y/z, optional parent → outputs the new object; the asset is picked on the node or arrives over an edge, from an `Asset` node, a variable or a list item. A pick saved while it was still a setting reads as that input's value, so older projects spawn as before; the same holds for the asset of `Is asset`), `Destroy` (removes the object with everything below it; with **keep children** on, only the object goes: its children move up to its parent (top level when it had none) and keep their place, rotation and size in the world, and the output `children` is the list of them, nearest level only. Its behaviors and its rigidbody go with it either way. In the editor run, children that are content of a placed container move up for the run only: their place belongs to the template, so the scene does not learn it), `Emit` (starts a particle source over, see *Particles*), + (asset input + x/y/z, optional parent → outputs the new object; the asset is picked on the node or arrives over an edge, from an `Asset` node, a variable or a list item. A pick saved while it was still a setting reads as that input's value, so older projects spawn as before; the same holds for the asset of `Is asset`), `Destroy` (removes the object with everything below it; with **keep children** on, only the object goes: its children move up to its parent (top level when it had none) and keep their place, rotation and size in the world, and the output `children` is the list of them, nearest level only. Its behaviors, its rigidbody and its joints go with it either way. In the editor run, children that are content of a placed container move up for the run only: their place belongs to the template, so the scene does not learn it), `Add joint` (ties an object to another while the game runs: the picked Joint asset is placed on *object* at x/y and connected to *connected*, the scene when nothing arrives; outputs the new joint, which `Destroy` lets go again, see [Physics](Physics#joints)), `Emit` (starts a particle source over, see *Particles*), `Play sound` / `Stop sound` (see [Sounds](Sounds): the asset says how loud and whether it loops, the node's `volume` multiplies that and its `loop` either leaves it alone or overrules it in either direction; every play is a voice of its own, so two in a row overlap, and the voice ends with the diff --git a/Concepts.md b/Concepts.md index 74fc6ba..22363ea 100644 --- a/Concepts.md +++ b/Concepts.md @@ -20,6 +20,7 @@ | **Collider** | A shape (box or circle) for collision detection, placed as a **child** of the object it belongs to (its *body*). Drawn as a green outline in edit mode only; its area is tinted only while the collider itself is selected. | | **Collision layer** | One of up to 16 named layers of the project (**menu → Project settings…**). A collider or tilemap names the layer it is on and the layers it **collides with**; two of them meet only when each one names the layer of the other. A new project has one layer, *Default*, which every collider is on and meets — so nothing collides differently until a layer is named. See [Colliders](Colliders#collision-layers). | | **Rigidbody** | Placed as a **child** of an object, it hands that object to the physics engine while playing: it falls with the scene's gravity, bounces and rests on colliders, pushed by what it meets. At most one per object. See [Physics](Physics). | +| **Joint** | Placed as a **child** of an object with a rigidbody, it ties that object to another one or to the scene while playing: a hinge, a spring, a rope or a fixed joint. Where it sits is the point it holds. See [Physics](Physics#joints). | | **Override** | A per-object value that replaces the asset's value for one property. Only changed keys are stored; the inspector paints their label gold and offers a reset (↺) and an **apply** (↑) that writes the value into the asset instead. Both appear only where the value really differs from its base — an override carrying the asset's own value is none. | | **Default** | Every property has a schema default. Asset values that differ from it are marked gold in the inspector and can be reset (↺) the same way an override can. The asset's name in the **Assets** panel carries the same gold while any of them does (`assetDeviates`, `src/core/properties/deviation.ts` — the inspector marks the rows of that same schema, so the list cannot claim a deviation the inspector then fails to show). A behavior's variables are not in it: their default is declared on the variable itself, so the asset value cannot differ from it. | | **Instance** | An object resolved from a placed container's template. Its **values** can be edited — position, name, property overrides — and only what actually deviates is stored, as a sparse `instanceEdits` entry on the container object keyed by the template path. Everything else keeps following the template. Its **structure** cannot: it can not be removed, re-parented or given a collider, and nothing of the scene can be parented into it. ↺ ▾ resets to the nearest base, which inside a container is the **template as the containers in between leave it**; ↑ ▾ offers **Apply to container** (write the value into the template, where every other instance still at the old value picks it up) and/or **Apply to asset** (which drops the template's override as well). With one base left the menu collapses to a plain ↺ / ↑, with none the buttons disappear. Gold marks the same thing the buttons offer. | @@ -195,8 +196,8 @@ Right-clicking a row offers *Set as active scene*, *Set as start scene* / *Clear *Create sprite from texture*, *Create animation from texture* and *Delete…*. In **Scene objects** it offers *Select asset*, *Open asset* (the same as double-clicking the asset in the asset panel; greyed out for a kind that has no editor of its own, such as a sprite or a -camera), *Add collider…* and *Add rigidbody…* (each opens a searchable menu: a new one or one -the project has, see [Colliders](Colliders)), *Fit to sprite* on a collider, *Create container*, +camera), *Add collider…*, *Add rigidbody…* and *Add joint…* (each opens a searchable menu: a new one +or one the project has, see [Colliders](Colliders) and [Physics](Physics#joints)), *Fit to sprite* on a collider, *Create container*, *Unpack container*, *Unparent*, *Expand all* / *Collapse all* and *Remove*. The scene view's right-click menu is the same. Both menus also offer **one *Open …* entry per asset the values point at** (#221): a sprite wearing a picture and a shader has *Open texture* and *Open shader*, in the asset panel at the diff --git a/Development.md b/Development.md index 372072b..98579c5 100644 --- a/Development.md +++ b/Development.md @@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun # has and the web server's container has not (SKIP there) php tests/php/snapshots.test.php # the snapshot store (#231), the same way php tests/php/releases.test.php # the release store (#266), the same way -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1687 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1691 editor checks, screenshots in /tmp/gm-e2e-*.png GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result diff --git a/Physics.md b/Physics.md index 2c3811b..6b1698c 100644 --- a/Physics.md +++ b/Physics.md @@ -161,6 +161,61 @@ object above it) takes it out of the simulation for as long as the mouse holds i pointer as if it had no rigidbody, and gravity does not pull it. Let go, it starts again at rest, with no velocity. +## Joints + +A **joint** ties the object it sits on to another one (#154). It is an asset placed as a child of +the object it holds, like a collider or a rigidbody: right-click the object in **Scene objects** → +*Add joint…* and pick **Create new joint** or one the project has already. An object takes as many +as it needs; a collider, a behavior, a rigidbody, a tilemap or another joint takes none. In the +scene view it is the icon of two ends on a bar, and it is not drawn in the game. + +**Where the joint sits is the point it holds its object at** — move the joint and that point moves. +Its rows: + +| Row | Default | What it does | +|---|---|---| +| Type | Hinge | **Hinge**: the object turns round the point where the joint sits (a door, a pendulum, a wheel). **Spring**: pulled towards a length, swinging about it. **Rope**: free up to a length, held beyond it. **Fixed**: kept exactly as the two were placed, moving as one. | +| Connected object | None | The other end, picked from the objects of the scene or with the eyedropper. None is a point of the scene that never moves. | +| Connected anchor X / Y | 0, 0 | Spring and rope only: where the other end is, in the connected object's own units (0, 0 is its origin, and it turns and scales with it) — with no connected object, in the scene's. A hinge and a fixed joint have one point only, the one the joint sits on. | +| Length (0 = as placed) | 0 | Spring and rope only: the length the spring rests at or the rope allows, in world units. 0 takes the distance between the two ends as they were placed. | +| Stiffness | 50 | Spring only: the force per 100 units of stretch (N/m). A body of 1 kg on 40 swings once in 0.99 s. | +| Damping | 1 | Spring only: how fast the swinging dies down; 0 swings on. | +| Limit angle | off | Hinge only. On: **Min angle** and **Max angle** say how far the object turns either way, in degrees, counted from how it was placed. | +| Collide with connected | off | Off: the two objects of the joint pass through each other. On: they collide like any two. | + +**The object needs a rigidbody**, its own or one of an object above it. Without one the joint holds +nothing and the console says so once: `Physics: "Lamp joint" sits on an object without a rigidbody, +so it holds nothing.` + +**The connected object need not have one.** With a rigidbody the two pull on each other. Without +one it is a moving anchor: nothing pulls it, but the joint follows it wherever a behavior or an +animation puts it — a lamp on a platform that drives along. A connected object that is destroyed +while the game runs makes the joint let go (`… lost its connected object, so it holds nothing.`); +removed in the editor, the row goes back to None like any reference to it (#269), so the joint then +holds on to the scene. A joint connected to the object it sits on holds nothing either. + +A reference follows its object as every object's place does (see [Behaviors](Behaviors)): pasted +together, the copy of the joint names the copy of the other object, and inside a container every +placement holds on to its own copy. + +In the **scene view** a line leads from the joint to what it holds on to: to the connected anchor +of a spring or a rope, to the connected object of a hinge or a fixed joint. The held point is +ringed, since the icon stands aside when it shares the spot with a collider's. A selected spring or +rope has a round **handle** on its far end: drag it to move the connected anchor (one undo step). + +A joint that is switched off lets go, and holds again when it is switched back on. `Destroy` on a +joint lets go for good. **`Add joint`** ties two objects while the game runs: it places the picked +Joint asset on *object* at (x, y) of that object, connects it to *connected* (nothing there: the +scene) and outputs the new joint. The asset is the template for the type and every value; +`Properties` with Type *Joint* changes them on a placed joint. + +Joints hold while the game is **played**. The editor run simulates nothing (#148), so there they do +nothing, and the inspector of a placed joint says so. + +Measured in the unit tests: a pendulum of 100 units crosses the middle after 0.50 s (2.007 s the +whole period), a spring of 40 N/m on 1 kg turns round after 0.50 s, a rope of 150 lets a body fall +to 150 and no further, and a hinge limited to 30 degrees stops between 27 and 33. + ## Play and the editor run The editor loads the engine right after opening the project; Play waits for it if it is not there @@ -179,6 +234,6 @@ are placed, because what the editor run moves is saved. The nodes above do nothi crates), kept a gap of up to 1.7 units and knows no friction; `Move` works out its contacts itself, in double precision, and slides over the same seams without catching (#150). - The simulation in the editor run (#148). -- Joints (#154). Raycasts (#153) with their layer filter (#158), `Raycast all` with every hit on the - line (#157), one-way platforms (#155) and the collision layers themselves (#152) are there, - see [Colliders](Colliders#raycast). +- A slider joint, and motors on a hinge (#154, second step). Raycasts (#153) with their layer + filter (#158), `Raycast all` with every hit on the line (#157), one-way platforms (#155) and the + collision layers themselves (#152) are there, see [Colliders](Colliders#raycast).