From b0318cde16dabf33c182e5f9ecd9954fd87d8488 Mon Sep 17 00:00:00 2001 From: meleki Date: Sun, 27 Sep 2026 15:58:11 +0200 Subject: [PATCH] Describe curve assets (#247) New page Curves with the tab, the rules and the presets, plus the Curve option in Animations and Behaviors, the model in Architecture and the check sum 1472 in Development. --- Animations.md | 8 ++++-- Architecture.md | 6 ++++- Behaviors.md | 4 ++- Curves.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ Development.md | 2 +- _Sidebar.md | 1 + 6 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 Curves.md diff --git a/Animations.md b/Animations.md index 2738428..461ebf5 100644 --- a/Animations.md +++ b/Animations.md @@ -52,7 +52,10 @@ key. The inspector shows it as two fields: - **Easing** is the curve: *Linear* (the default, stored as no field at all), *Step* (holds until the next key, like a texture does), or one of the ten curves also known from CSS and easings.net: - *Sine*, *Quad*, *Cubic*, *Quart*, *Quint*, *Expo*, *Circ*, *Back*, *Elastic*, *Bounce*. + *Sine*, *Quad*, *Cubic*, *Quart*, *Quint*, *Expo*, *Circ*, *Back*, *Elastic*, *Bounce*. Or + *Curve*: an easing you drew yourself as a [curve asset](Curves), picked in the **Curve** row that + appears below. Its first curve is picked for you; a curve that is gone blends linearly, and the + inspector says so. - **Direction** only appears for a curve: *In* starts slow, *Out* starts fast and ends slow, *In-out* is slow at both ends and halfway at half the time. A new curve keeps the direction the key had; a key that was linear or a step gets *In-out*. @@ -72,7 +75,8 @@ when it was the only curve, `ease-in`, `ease-out` and `ease-in-out`, so older pr before. Set the easing in the inspector for all picked keys at once; keys of a row that cannot blend are left alone. A new value or a move keeps a key's easing. The timeline draws the direction into the key: the right half is the start of the way, the left half its end, a round half a slow -one — a diamond is linear, a circle *In-out*, a half-round key *In* or *Out*, a square a step. +one — a diamond is linear, a circle *In-out* or a drawn *Curve*, a half-round key *In* or *Out*, a +square a step. Player and preview evaluate it in one place, so both show the same curve. **The Animation tab.** Each animation opens in a tab of its own. The header picks what it diff --git a/Architecture.md b/Architecture.md index 39570a4..1c6aa71 100644 --- a/Architecture.md +++ b/Architecture.md @@ -73,7 +73,7 @@ ``` web/index.html Vite source page (dev) index.html built page (generated, served) -src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), 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), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), 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/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), 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), 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 (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/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), color (`mixColor`, channel by channel — keys of an animation, stops of a gradient, the particles' Mix colour) 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 @@ -233,6 +233,10 @@ src/editor/vectorart/ VectorArtEditor (header: the three tools, the picked poin src/editor/animation/ AnimationEditor (header, preview over timeline), PreviewCanvas (the root at the playhead, a FreeViewport over animationPreview.ts), TimelineCanvas (rows, keys, ruler, clipboard), timelineView (its geometry), RowDialog + FramesDialog (FormDialogs) +src/editor/curve/ CurveEditor (header: Preset, the picked point's type, Preview, #247), CurveCanvas (the curve as a + graph drawn from curveValue, points and handles edited with the vector art gestures, the + preview dot and its rail) + curveView (the curve laid onto a picture for those gestures and + read back, a point held between its neighbours, a point put down on free space) src/editor/tile/ TileEditor (header + kind), TileCanvas (every tile of the block and a sample map, animated), PictureStrip (the pictures as animation steps; dropIndex is the reorder maths), TilePalette (the tiles as a grid of pictures in the tilemap panel, #139) diff --git a/Behaviors.md b/Behaviors.md index 732ec24..464bf3a 100644 --- a/Behaviors.md +++ b/Behaviors.md @@ -132,7 +132,9 @@ playing use the same two words. `Is asset` (is this object an instance of asset X — how you tell what you collided with), `Variables`, `Properties` (every property row of the target's type, the transform rows `scale`, `rotation`, `mirrorX`, `mirrorY` included — how a graph turns a sprite), `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`/`State machine`/`Sound`/`Scene`/`Animation`/`Asset`, `Add/Subtract/Multiply/Divide`, `Min/Max/Abs/Sqrt/Clamp`, `Lerp` (from a to b as t runs from 0 to 1, t held in 0…1; the setting - *Ease* bends the way: *Linear*, *In* starts slow, *Out* ends slow, *In-out* both, all quadratic — + *Ease* bends the way: *Linear*, *In* starts slow, *Out* ends slow, *In-out* both, all quadratic, + or *Curve*, which follows the [curve asset](Curves) picked in the *Curve* row and may overshoot b; + with none picked, or once it is gone, it runs linear and says so on the node — one node where a slide or a flight used to take a Subtract, a Multiply and an Add per axis), `Floor/Ceil/Round`, `Modulo`, `Random`, `Time`, `To text`, `To number`, `Join text`, `Substring`, `Find text`, `Equals`, `First not empty` (a, unless a is empty, then b; the setting diff --git a/Curves.md b/Curves.md new file mode 100644 index 0000000..8dd0ba1 --- /dev/null +++ b/Curves.md @@ -0,0 +1,69 @@ +A **curve asset** is an easing you draw yourself. Wherever the editor offers a built-in ease — the +**Easing** of an animation key, the **Ease** of a `Lerp` node — it also offers **Curve**, and then +the value runs along your drawing instead: overshoot and settle twice, hesitate in the middle, +anything that has one value for every moment. + +**Making one.** **+ → Curve** creates the asset; it stands in the assets panel under *Curves*. A +double click on its row opens the **Curve** tab. A new curve is a straight line from (0, 0) to +(1, 1), so a key or a `Lerp` that picks it moves exactly as it did before, until you bend it. + +## What a curve is + +A graph: **t** runs to the right, from 0 (the start of the way) to 1 (its end), and **v** runs up, +how far along the way the value is at that moment. 0 is the value the key starts from (or `a` of the +`Lerp`), 1 the one it goes to (`b`). v may go above 1 or below 0 — that is what makes a value shoot +past its target and come back. + +The curve is a chain of **points**, joined the way [Vector art](Vector-Art) joins them: a segment is +straight until one of its two points has a **handle** on that side, and one handle is enough for a +bend. Three rules keep it a curve a value can follow: + +- The **first point** sits at t = 0 and the **last** at t = 1. They move only up and down and + cannot be taken away. +- Points **never overtake** each other in t. A point dragged past its neighbour stops at it. +- A **handle** never reaches past the next point in t, and never points backwards. One that would + is shortened along its own direction, so a smooth point stays smooth; one pointing backwards is + turned upright. Without this the curve could run backwards and have two values at one moment. + +These rules hold for everything written into a curve — a drag, a preset, a hand-edited project file. + +## The Curve tab + +The graph fills the tab, with lines at 0 and 1 in both directions. + +- **Click the line** to put a point there. The curve keeps its shape: a bend is split in two, not + flattened. +- **Click free space** between the two ends to put a plain point there, at that t and that v. +- **Drag** a point or a handle. Each drag is one undo step. +- **Double-click** a point, or pick it and press **Delete**, to take it away. +- **Alt-click** a point to make it round, or straight again. +- **Point: …** in the header sets the picked point's type, as in the Vector art tab: *Straight* (no + handles), *Corner* (handles move on their own), *Smooth* (handles stay in line), *Symmetric* + (in line and of one length). +- **Preset** starts the curve over from one of the built-in eases — *Sine*, *Quad*, *Cubic*, + *Quart*, *Quint*, *Expo*, *Circ*, *Back*, *Elastic*, *Bounce*, each *in*, *out* and *in-out*, + and *Linear*. The menu has a search box. Each preset is traced by a handful of points that stay + within 0.4 % of the ease, a quad by two, a bounce by twenty, and is yours to bend from there. +- **Preview** runs a dot along the curve once a second, with a twin on a rail right of the graph + that only moves up and down: the motion the curve gives a value. The button holds it. +- **Fit** frames the curve again, handles included. A middle drag or a Shift drag pans, the wheel + zooms. + +The line on screen is drawn from the same function the animation player and the `Lerp` node use, so +what the tab shows is what runs. + +## Using a curve + +- **On an animation key**, set **Easing** to *Curve*. A **Curve** row appears below it; the first + curve of the project is picked for you, change it there. See [Animations](Animations#easing). +- **On a `Lerp` node**, set **Ease** to *Curve* and pick the curve in the **Curve** row. `t` is + still held to 0…1, but the result follows the curve past `b` where it overshoots. See + [Behaviors](Behaviors). + +A key or a `Lerp` whose curve is **gone** — deleted, or none picked yet — blends **linearly**. The +inspector says so under the key, and the node shows the problem on its title. Deleting a curve that +is in use asks first and says how many keys and `Lerp` nodes use it. + +Stored, a key names its curve as `curve:` in its interpolation, a `Lerp` in its `curve` +setting. The points are stored like the points of a vector art ring, with `x` for t and `y` for v and +the handles as offsets from their point. diff --git a/Development.md b/Development.md index 0ebdec7..f1d31f4 100644 --- a/Development.md +++ b/Development.md @@ -15,7 +15,7 @@ npm run build # writes dist/ and regenerates index.html php tools/make-user.php [admin|user] # an account; every tool signs in (see Accounts) php tests/php/accounts.test.php # the API's account rules, no server needed (CI's gate job) php tests/php/snapshots.test.php # the snapshot store (#231), the same way -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1459 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1472 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/_Sidebar.md b/_Sidebar.md index f08c151..adc7fd2 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -13,6 +13,7 @@ - [Fonts](Fonts) - [Sounds](Sounds) - [Animations](Animations) +- [Curves](Curves) - [Tiles](Tiles) - [Tilemaps](Tilemaps) - [State machines](State-machines)