From a726d4a9070e11f137100c70c5b3ecdf9e446337 Mon Sep 17 00:00:00 2001 From: meleki Date: Mon, 21 Sep 2026 13:05:05 +0200 Subject: [PATCH] Document gradient and texture fills in Vector Art (#186) - Vector Art gets the section "Gradients and textures" with the paint types and the handles of the Paint tool. Toolbar, inspector, combining and known limits are updated. - Architecture names vectorPaint and the gradient bar. - Development carries the check sum 1232. --- Architecture.md | 15 ++++++++---- Development.md | 2 +- Vector-Art.md | 61 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/Architecture.md b/Architecture.md index 56a50e0..40ce977 100644 --- a/Architecture.md +++ b/Architecture.md @@ -72,7 +72,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:@` and the register of readers that tell the picture path an asset's canvas size and revision), 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) + 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) + 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), 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)), 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 (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead), 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:@` and the register of readers that tell the picture path an asset's canvas size and revision), 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), 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)), 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 (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead), 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) src/core/commands/ undoable commands, CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts @@ -114,7 +114,9 @@ src/viewport/ view (transforms), items (what to draw), symbols (how to without a browser, vectorArtPicture (the rasteriser of #129: the draw commands of core/model/vectorPath painted onto a render texture of the canvas size, at twice its - resolution; registers vector art in both halves of the picture path), + resolution; registers vector art in both halves of the picture path; PaintCache keeps a + painter's gradients and hands the ones it no longer draws to Pixi's texture garbage + collector, and a texture fill waits for its picture instead of drawing without it, #186), SoundLibrary, ShaderLibrary, ParticleLibrary, particles (one ParticleContainer per source), tilemap (one container per layer), drawOrder (objects and tilemap layers by z, #174), activeCamera — no editor concepts, @@ -151,7 +153,8 @@ src/editor/inspector/ Inspector panel, host (what a section builds into), field (where a value lives — shared with the List tab), one section per selection kind (asset/object/node/frame/shape), variableRows (one row shape for every variable), shapeSection (the shapes of a vector drawing: the list, the four order - buttons and the style of the picked one, #129), portSection, aspectAction, + buttons and the style of the picked one, #129; Fill type and Line type with the stop bar, + the picked stop's rows or the texture, #186), portSection, aspectAction, objectQueries/vectorRows/parentRow src/editor/panels/ AssetPanel, assetGroups (groups by type and their order, #176), assetIcons (one SVG per asset type in icons/, shared by the panel, the + menu, the tabs and Scene objects, @@ -168,9 +171,11 @@ src/editor/graph/ GraphEditor (header, breadcrumb), GraphCanvas, graphView src/editor/container/ ContainerEditor (header), ContainerCanvas (a SceneViewport on a template) src/editor/texture/ TextureEditor (header), TextureCanvas (picture + frames), SliceDialog (a FormDialog), GenerateCollisionDialog (#161: the rows, and a summary that traces while the numbers change; the texture library hands it the sheet's pixels), sheetView (the grid maths), ImagePicker + imageGrid (the "+ → Texture" dialog), checker (the transparency board) -src/editor/vectorart/ VectorArtEditor (header: the two tools, the picked point's type and the magnet), VectorArtCanvas (the drawing +src/editor/vectorart/ VectorArtEditor (header: the three tools, the picked point's type or, with Paint, whose handles, and the magnet), VectorArtCanvas (the drawing painted from core/model/vectorPath onto its own Graphics, with points and handles over it; - the pen puts points down, the arrow drags points, handles and whole shapes, #129), + the pen puts points down, the arrow drags points, handles and whole shapes, #129; the Paint + tool drags the two handles of a gradient or a texture tile, #186), + GradientBar (the colour stops of a gradient as a bar: pick, drag and add a stop, #186), ShapeList (the shapes as rows with a preview each, in the inspector; a row may be dragged to another place in the drawing order) + shapeRows (that list's arithmetic: the order is shown upside down, and a drop counts the dragged row which the model has already taken out) diff --git a/Development.md b/Development.md index 073cc6b..038c129 100644 --- a/Development.md +++ b/Development.md @@ -13,7 +13,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs npm run build # writes dist/ and regenerates index.html ./build.sh # the same under `set -o pipefail`, for callers that pipe the output php tools/make-user.php # an account; every tool signs in (see Accounts) -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1222 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1232 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/Vector-Art.md b/Vector-Art.md index 8208337..62fa80d 100644 --- a/Vector-Art.md +++ b/Vector-Art.md @@ -52,8 +52,9 @@ The type only steers the next drag; it changes nothing that is drawn. ## The tab -The toolbar holds **Select**, **Pen**, **Point**, the magnet and **Fit**; the view pans and zooms -like the Texture tab, and the checkerboard behind the canvas is what is transparent. +The toolbar holds **Select**, **Pen**, **Paint**, **Point**, the magnet and **Fit**; the view pans +and zooms like the Texture tab, and the checkerboard behind the canvas is what is transparent. +With **Paint** the Point menu makes way for **Handles**, see [Gradients and textures](#gradients-and-textures). **Pen** draws a new shape: @@ -106,9 +107,10 @@ Selecting a shape — in the tab or in the list — gives it its own rows: - **Forward / To front / Backward / To back** move the selected shape through that order, and a row can be **dragged** to another place in the list. - **COMBINE** holds the four boolean operations, see [below](#combining-shapes). -- **VECTOR SHAPE** carries Name, Fill, Opacity and an **Outline** switch; with the switch on, Line - colour and Line width appear. The switch *gives and takes away* the outline rather than hiding it, - and a width of zero is no outline, so the row stops at 0.1. +- **VECTOR SHAPE** carries Name, **Fill type** with what that type needs, Opacity and an + **Outline** switch; with the switch on, **Line type** and Line width appear. The switch *gives and + takes away* the outline rather than hiding it, and a width of zero is no outline, so the row stops + at 0.1. - **Delete shape** and **Add shape** sit below; with several shapes selected it reads **Delete shapes** and removes them all. @@ -117,6 +119,48 @@ last. A dragged colour or opacity slider is one undo step, not one per pixel of the **asset** instead of a shape shows the same list under the asset's own rows, with nothing selected. +## Gradients and textures + +**Fill type** says what the inside of a shape is painted with, **Line type** the same for the +outline: + +| Type | Fill | Outline | Rows under it | +|---|---|---|---| +| **Solid colour** | yes | yes | Fill, or Line colour | +| **Linear gradient** | yes | yes | the stop bar and the picked stop | +| **Radial gradient** | yes | yes | the stop bar and the picked stop | +| **Texture** | yes | no | Texture | + +- **Switching** is one undo step. A new gradient runs from the shape's colour (or the line's) to + white, across the middle of the shape. Changing between linear and radial keeps the colours and + lays the gradient out anew. The solid colour stays on the shape underneath, so **Solid colour** + brings back exactly the colour it had. +- **The stop bar** shows the gradient over a checkerboard, with a marker under it for every colour + stop. A click on a marker picks that stop, dragging it moves it along. A click on the bar anywhere + else adds a stop there, in the colour the gradient already has at that place, so adding one + changes nothing until it is edited. **Stop colour**, **Stop opacity** and **Stop position** edit + the picked stop; **Remove stop** takes it out, down to the two a gradient needs. +- **Texture** offers the uploaded textures and their frames. Drawings are left out: a drawing filled + with a drawing, itself included, would have to be rasterised while it is being rasterised. +- **Opacity** still scales the whole shape, on top of each stop's own opacity. + +**The Paint tool** shows the two handles of the selected shape's paint, and **Handles** in the +toolbar says whose: the fill's or the outline's. A click on another shape shows its handles. + +| Type | The square handle | The round handle | +|---|---|---| +| Linear | where the first colour is | where the last colour is | +| Radial | the centre | where the circle reaches the last colour | +| Texture | one corner of a tile | the opposite corner | + +- Before the first and past the last colour of a gradient, the end colours hold. +- **A texture always repeats.** When it is switched on, one tile covers the whole shape, so the + picture fills it exactly once; a smaller tile repeats it across the shape. Dragging the round + handle past the square one mirrors the picture. +- Dragging a **whole shape** with Select takes its gradients and its tile along. Dragging a single + point does not. +- Each handle dragged is one undo step, and the magnet snaps the handles too. + ## Combining shapes Select two or more shapes (Ctrl+click) and press one of the four buttons under **COMBINE**: @@ -129,7 +173,8 @@ Select two or more shapes (Ctrl+click) and press one of the four buttons under * | **Exclude** | What an odd number of them covers: the overlaps are cut out. | - **The result replaces the lowest shape** of the selection, the one drawn first. It keeps that - shape's name, fill, opacity, outline and place in the drawing order; the other shapes are gone. + shape's name, fill, gradient or texture, opacity, outline and place in the drawing order; the + other shapes are gone. The note under the buttons names it. Which shape was clicked first does not matter. - It is an **ordinary shape** again, not a compound that remembers its parts. To change the parts, undo: the whole operation is **one undo step**. @@ -157,7 +202,9 @@ image — the project file holds the shapes, and the picture is rasterised again ## Known limits -- **A fill is one colour.** Gradients and texture fills are #186. +- **A texture fill always repeats.** Pixi turns a non-repeating picture fill into a repeating one, + so "once, then nothing" is not offered; the tile starts out covering the whole shape instead. +- **A radial gradient is a circle.** It cannot be squeezed into an ellipse. - **Combining keeps no curves.** The result of a boolean operation is made of corners only (#187). - **A collider does not follow the drawing.** A sprite wearing vector art collides with its box or with a shape you give it by hand, as with any picture; taking the outline as a collider shape is