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.
admin committed 2026-09-27 15:58:11 +02:00
1 parent 7c8d02952c
commit b0318cde16
6 files changed
+85 -5

No files matched your search

+6 -2
@@ -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
+5 -1
@@ -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:<assetId>@<revision>`, 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:<assetId>@<revision>`, 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)
+3 -1
@@ -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
+69
@@ -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:<id>` 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.
+1 -1
@@ -15,7 +15,7 @@ npm run build # writes dist/ and regenerates index.html
php tools/make-user.php <name> <password> [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
+1
@@ -13,6 +13,7 @@
- [Fonts](Fonts)
- [Sounds](Sounds)
- [Animations](Animations)
- [Curves](Curves)
- [Tiles](Tiles)
- [Tilemaps](Tilemaps)
- [State machines](State-machines)