diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md
index 05efa29..e7719a6 100644
--- a/ATTRIBUTION.md
+++ b/ATTRIBUTION.md
@@ -134,13 +134,11 @@ is committed and listed here with its exports.
| assets/icons/ui.sun.svg, assets/icons/ui.sun.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.sun.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/ui.views.svg, assets/icons/ui.views.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.views.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
| assets/icons/ui.zoom.svg, assets/icons/ui.zoom.vectorcraft | FieldCraft contributors | Original interface icon (navigation bar, title bar, browser) drawn with VectorCraft from `assets/icons/src/ui.zoom.json` (`cargo xtask icons`) | MIT OR Apache-2.0 |
-| assets/app-icon/icon.vectorcraft, assets/app-icon/icon.svg, assets/app-icon/icon.png | FieldCraft contributors | Original application icon (a hexahedral-meshed cube with a banded colour fringe on its faces and speed-coloured flow streamlines with arrowheads that run into its mesh lines or wrap around it, on a rounded-square backdrop), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs); editable source, SVG and 1024 × 1024 PNG export | MIT OR Apache-2.0 |
-| assets/app-icon/icon-small.vectorcraft, assets/app-icon/icon-small.svg, assets/app-icon/icon-small.png | FieldCraft contributors | Original simplified application icon for 16–32 px (fringe cube and one coloured streamline with an arrowhead; no mesh or floor grid), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
+| assets/app-icon/icon.vectorcraft, assets/app-icon/icon.svg, assets/app-icon/icon.png | FieldCraft contributors | Original application icon (a hexahedral-meshed cube with a banded colour fringe on its faces on a rounded-square backdrop), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs); editable source, SVG and 1024 × 1024 PNG export | MIT OR Apache-2.0 |
+| assets/app-icon/icon-small.vectorcraft, assets/app-icon/icon-small.svg, assets/app-icon/icon-small.png | FieldCraft contributors | Original simplified application icon for 16–32 px (the fringe cube alone; no mesh or floor grid), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
| assets/app-icon/layers/01-floor.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/01-floor.svg | FieldCraft contributors | Original isometric floor grid with a radial fade (Default appearance), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
| assets/app-icon/layers/01-floor-dark.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg | FieldCraft contributors | Original isometric floor grid with a radial fade (Dark appearance), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
-| assets/app-icon/layers/02-flow-behind.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/02-flow-behind.svg | FieldCraft contributors | Original speed-coloured flow streamlines with arrowheads (the runs behind the cube), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
-| assets/app-icon/layers/03-part.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/03-part.svg | FieldCraft contributors | Original cube faces with a banded colour fringe and transparent gaps at the shared edges, and the hexahedral mesh lines clipped to them, drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
-| assets/app-icon/layers/04-flow-front.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/04-flow-front.svg | FieldCraft contributors | Original speed-coloured flow streamlines with arrowheads (the runs in front of the cube), drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
+| assets/app-icon/layers/02-part.vectorcraft, assets/app-icon/FieldCraft.icon/Assets/02-part.svg | FieldCraft contributors | Original cube faces with a banded colour fringe and transparent gaps at the shared edges, and the hexahedral mesh lines clipped to them, drawn with VectorCraft by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
| assets/app-icon/FieldCraft.icon/icon.json | FieldCraft contributors | Icon Composer composition of the layers above (Default and Dark floor grid), written by `cargo xtask app-icon` (xtask/src/app_icon.rs) | MIT OR Apache-2.0 |
| assets/app-icon/Assets.car | FieldCraft contributors | Apple actool compilation of assets/app-icon/FieldCraft.icon; contains original artwork only | MIT OR Apache-2.0 |
| assets/app-icon/icon.icns | FieldCraft contributors | iconutil compilation of assets/app-icon/icon.iconset | MIT OR Apache-2.0 |
diff --git a/assets/app-icon/Assets.car b/assets/app-icon/Assets.car
index a00bd0b..d4182cc 100644
Binary files a/assets/app-icon/Assets.car and b/assets/app-icon/Assets.car differ
diff --git a/assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg b/assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg
index 89b8906..64b7f13 100644
--- a/assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg
+++ b/assets/app-icon/FieldCraft.icon/Assets/01-floor-dark.svg
@@ -4,7 +4,7 @@
-
+
@@ -13,6 +13,6 @@
FieldCraft icon floor grid (Dark)
-
+
diff --git a/assets/app-icon/FieldCraft.icon/Assets/01-floor.svg b/assets/app-icon/FieldCraft.icon/Assets/01-floor.svg
index 9d4203e..02a3e2a 100644
--- a/assets/app-icon/FieldCraft.icon/Assets/01-floor.svg
+++ b/assets/app-icon/FieldCraft.icon/Assets/01-floor.svg
@@ -4,7 +4,7 @@
-
+
@@ -13,6 +13,6 @@
FieldCraft icon floor grid
-
+
diff --git a/assets/app-icon/FieldCraft.icon/Assets/02-flow-behind.svg b/assets/app-icon/FieldCraft.icon/Assets/02-flow-behind.svg
deleted file mode 100644
index 6612704..0000000
--- a/assets/app-icon/FieldCraft.icon/Assets/02-flow-behind.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
diff --git a/assets/app-icon/FieldCraft.icon/Assets/02-part.svg b/assets/app-icon/FieldCraft.icon/Assets/02-part.svg
new file mode 100644
index 0000000..1c742d4
--- /dev/null
+++ b/assets/app-icon/FieldCraft.icon/Assets/02-part.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/assets/app-icon/FieldCraft.icon/Assets/03-part.svg b/assets/app-icon/FieldCraft.icon/Assets/03-part.svg
deleted file mode 100644
index fcdd684..0000000
--- a/assets/app-icon/FieldCraft.icon/Assets/03-part.svg
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
diff --git a/assets/app-icon/FieldCraft.icon/Assets/04-flow-front.svg b/assets/app-icon/FieldCraft.icon/Assets/04-flow-front.svg
deleted file mode 100644
index 508d749..0000000
--- a/assets/app-icon/FieldCraft.icon/Assets/04-flow-front.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
diff --git a/assets/app-icon/FieldCraft.icon/icon.json b/assets/app-icon/FieldCraft.icon/icon.json
index bcb26e7..06dc90f 100644
--- a/assets/app-icon/FieldCraft.icon/icon.json
+++ b/assets/app-icon/FieldCraft.icon/icon.json
@@ -10,8 +10,8 @@
"value": true
}
],
- "image-name": "04-flow-front.svg",
- "name": "04-flow-front",
+ "image-name": "02-part.svg",
+ "name": "02-part",
"position": {
"scale": 0.9,
"translation-in-points": [
@@ -21,38 +21,7 @@
}
}
],
- "name": "04 Flow front",
- "shadow": {
- "kind": "none",
- "opacity": 0
- },
- "specular": false,
- "translucency": {
- "enabled": false,
- "value": 0.3
- }
- },
- {
- "layers": [
- {
- "glass-specializations": [
- {
- "appearance": "tinted",
- "value": true
- }
- ],
- "image-name": "03-part.svg",
- "name": "03-part",
- "position": {
- "scale": 0.9,
- "translation-in-points": [
- 0,
- 0
- ]
- }
- }
- ],
- "name": "03 Part",
+ "name": "02 Part",
"shadow": {
"kind": "none",
"opacity": 0
@@ -63,37 +32,6 @@
"value": 0.3
}
},
- {
- "layers": [
- {
- "glass-specializations": [
- {
- "appearance": "tinted",
- "value": true
- }
- ],
- "image-name": "02-flow-behind.svg",
- "name": "02-flow-behind",
- "position": {
- "scale": 0.9,
- "translation-in-points": [
- 0,
- 0
- ]
- }
- }
- ],
- "name": "02 Flow behind",
- "shadow": {
- "kind": "none",
- "opacity": 0
- },
- "specular": false,
- "translucency": {
- "enabled": false,
- "value": 0.3
- }
- },
{
"layers": [
{
diff --git a/assets/app-icon/README.md b/assets/app-icon/README.md
index d02a285..02da28b 100644
--- a/assets/app-icon/README.md
+++ b/assets/app-icon/README.md
@@ -2,9 +2,7 @@
FieldCraft's mark is a cube in isometric view, covered by a structured hexahedral mesh, whose
faces carry a banded colour fringe (blue to red, hottest at the front corner) as a stress or
-temperature result would. Streamlines coloured by flow speed in the same bands, with arrowheads,
-sweep in from the lower left: two run into the top face and narrow into its mesh lines, so the
-flow becomes the structure, and one wraps around the cube. Its faces are separated by
+temperature result would. Its faces are separated by
transparent gaps rather than painted edge lines, so the backdrop shows through them in every
appearance. It is original artwork, attributed in `ATTRIBUTION.md`, and shares the Craft
family's layout (rounded-square backdrop, isometric floor grid, lit top faces).
@@ -17,10 +15,10 @@ hand. It runs on macOS (sips, iconutil, `xcrun actool`) with ImageMagick (`magic
| File | What it is |
|---|---|
| `icon.vectorcraft`, `icon.svg`, `icon.png` | The full icon on its backdrop, 1024 × 1024 |
-| `icon-small.vectorcraft`, `icon-small.svg`, `icon-small.png` | Small-size artwork used at 16–32 px: the fringe cube and one bold coloured streamline with an arrowhead, no mesh or floor grid, faces edge to edge |
+| `icon-small.vectorcraft`, `icon-small.svg`, `icon-small.png` | Small-size artwork used at 16–32 px: the fringe cube alone, no mesh or floor grid, faces edge to edge |
| `icon.iconset/`, `icon.icns` | macOS iconset (sips) and its iconutil compilation |
| `icon.ico` | Windows icon, 16–256 px |
-| `FieldCraft.icon/` | Icon Composer document: floor grid (Default and Dark), flow behind, cube (faces and mesh) and flow in front layers, each scaled to 90% |
+| `FieldCraft.icon/` | Icon Composer document: floor grid (Default and Dark), and cube (faces and mesh) layers, each scaled to 90% |
| `layers/*.vectorcraft` | Editable sources of the Icon Composer layers |
| `Assets.car` | `actool` compilation of `FieldCraft.icon` for macOS 26 and later (needs Xcode 26 or later; the task skips it with a note otherwise) |
diff --git a/assets/app-icon/icon-small.png b/assets/app-icon/icon-small.png
index 1454fec..cf183b3 100644
Binary files a/assets/app-icon/icon-small.png and b/assets/app-icon/icon-small.png differ
diff --git a/assets/app-icon/icon-small.svg b/assets/app-icon/icon-small.svg
index d6c883d..3e3fe7d 100644
--- a/assets/app-icon/icon-small.svg
+++ b/assets/app-icon/icon-small.svg
@@ -7,13 +7,13 @@
-
+
-
+
-
+ FieldCraft application icon, small sizes
@@ -22,49 +22,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/assets/app-icon/icon-small.vectorcraft b/assets/app-icon/icon-small.vectorcraft
index 3241283..6be9558 100644
Binary files a/assets/app-icon/icon-small.vectorcraft and b/assets/app-icon/icon-small.vectorcraft differ
diff --git a/assets/app-icon/icon.icns b/assets/app-icon/icon.icns
index 6eec029..4cd5253 100644
Binary files a/assets/app-icon/icon.icns and b/assets/app-icon/icon.icns differ
diff --git a/assets/app-icon/icon.ico b/assets/app-icon/icon.ico
index 057f1d7..6a995dc 100644
Binary files a/assets/app-icon/icon.ico and b/assets/app-icon/icon.ico differ
diff --git a/assets/app-icon/icon.iconset/icon_128x128.png b/assets/app-icon/icon.iconset/icon_128x128.png
index d945c19..0096299 100644
Binary files a/assets/app-icon/icon.iconset/icon_128x128.png and b/assets/app-icon/icon.iconset/icon_128x128.png differ
diff --git a/assets/app-icon/icon.iconset/icon_128x128@2x.png b/assets/app-icon/icon.iconset/icon_128x128@2x.png
index a8638c1..af995a0 100644
Binary files a/assets/app-icon/icon.iconset/icon_128x128@2x.png and b/assets/app-icon/icon.iconset/icon_128x128@2x.png differ
diff --git a/assets/app-icon/icon.iconset/icon_16x16.png b/assets/app-icon/icon.iconset/icon_16x16.png
index 1cf2e49..42055d5 100644
Binary files a/assets/app-icon/icon.iconset/icon_16x16.png and b/assets/app-icon/icon.iconset/icon_16x16.png differ
diff --git a/assets/app-icon/icon.iconset/icon_16x16@2x.png b/assets/app-icon/icon.iconset/icon_16x16@2x.png
index 317996b..03f2277 100644
Binary files a/assets/app-icon/icon.iconset/icon_16x16@2x.png and b/assets/app-icon/icon.iconset/icon_16x16@2x.png differ
diff --git a/assets/app-icon/icon.iconset/icon_256x256.png b/assets/app-icon/icon.iconset/icon_256x256.png
index a8638c1..af995a0 100644
Binary files a/assets/app-icon/icon.iconset/icon_256x256.png and b/assets/app-icon/icon.iconset/icon_256x256.png differ
diff --git a/assets/app-icon/icon.iconset/icon_256x256@2x.png b/assets/app-icon/icon.iconset/icon_256x256@2x.png
index ae93e80..9c775a5 100644
Binary files a/assets/app-icon/icon.iconset/icon_256x256@2x.png and b/assets/app-icon/icon.iconset/icon_256x256@2x.png differ
diff --git a/assets/app-icon/icon.iconset/icon_32x32.png b/assets/app-icon/icon.iconset/icon_32x32.png
index 317996b..03f2277 100644
Binary files a/assets/app-icon/icon.iconset/icon_32x32.png and b/assets/app-icon/icon.iconset/icon_32x32.png differ
diff --git a/assets/app-icon/icon.iconset/icon_32x32@2x.png b/assets/app-icon/icon.iconset/icon_32x32@2x.png
index f28b46b..17a7dd0 100644
Binary files a/assets/app-icon/icon.iconset/icon_32x32@2x.png and b/assets/app-icon/icon.iconset/icon_32x32@2x.png differ
diff --git a/assets/app-icon/icon.iconset/icon_512x512.png b/assets/app-icon/icon.iconset/icon_512x512.png
index ae93e80..9c775a5 100644
Binary files a/assets/app-icon/icon.iconset/icon_512x512.png and b/assets/app-icon/icon.iconset/icon_512x512.png differ
diff --git a/assets/app-icon/icon.iconset/icon_512x512@2x.png b/assets/app-icon/icon.iconset/icon_512x512@2x.png
index 969190d..b409e75 100644
Binary files a/assets/app-icon/icon.iconset/icon_512x512@2x.png and b/assets/app-icon/icon.iconset/icon_512x512@2x.png differ
diff --git a/assets/app-icon/icon.png b/assets/app-icon/icon.png
index 969190d..b409e75 100644
Binary files a/assets/app-icon/icon.png and b/assets/app-icon/icon.png differ
diff --git a/assets/app-icon/icon.svg b/assets/app-icon/icon.svg
index c4f5208..c2cd6ca 100644
--- a/assets/app-icon/icon.svg
+++ b/assets/app-icon/icon.svg
@@ -9,23 +9,23 @@
-
+
-
+
-
+
-
+
-
+ FieldCraft application icon
@@ -35,76 +35,39 @@
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/assets/app-icon/icon.vectorcraft b/assets/app-icon/icon.vectorcraft
index 881258f..24937e7 100644
Binary files a/assets/app-icon/icon.vectorcraft and b/assets/app-icon/icon.vectorcraft differ
diff --git a/assets/app-icon/layers/01-floor-dark.vectorcraft b/assets/app-icon/layers/01-floor-dark.vectorcraft
index 6b7c84d..45da97c 100644
Binary files a/assets/app-icon/layers/01-floor-dark.vectorcraft and b/assets/app-icon/layers/01-floor-dark.vectorcraft differ
diff --git a/assets/app-icon/layers/01-floor.vectorcraft b/assets/app-icon/layers/01-floor.vectorcraft
index 2728d26..f2eaa7e 100644
Binary files a/assets/app-icon/layers/01-floor.vectorcraft and b/assets/app-icon/layers/01-floor.vectorcraft differ
diff --git a/assets/app-icon/layers/02-flow-behind.vectorcraft b/assets/app-icon/layers/02-flow-behind.vectorcraft
deleted file mode 100644
index 14d5600..0000000
Binary files a/assets/app-icon/layers/02-flow-behind.vectorcraft and /dev/null differ
diff --git a/assets/app-icon/layers/02-part.vectorcraft b/assets/app-icon/layers/02-part.vectorcraft
new file mode 100644
index 0000000..c0d65d5
Binary files /dev/null and b/assets/app-icon/layers/02-part.vectorcraft differ
diff --git a/assets/app-icon/layers/03-part.vectorcraft b/assets/app-icon/layers/03-part.vectorcraft
deleted file mode 100644
index 9f82458..0000000
Binary files a/assets/app-icon/layers/03-part.vectorcraft and /dev/null differ
diff --git a/assets/app-icon/layers/04-flow-front.vectorcraft b/assets/app-icon/layers/04-flow-front.vectorcraft
deleted file mode 100644
index 40bc088..0000000
Binary files a/assets/app-icon/layers/04-flow-front.vectorcraft and /dev/null differ
diff --git a/xtask/src/app_icon.rs b/xtask/src/app_icon.rs
index e1189ed..70fdc31 100644
--- a/xtask/src/app_icon.rs
+++ b/xtask/src/app_icon.rs
@@ -2,28 +2,24 @@
//!
//! The mark is a hexahedral-meshed cube in isometric view whose faces carry a banded colour
//! fringe (blue to red, hottest at the cube's front corner) as a stress or temperature result
-//! would. Streamlines coloured by flow speed in the same bands, with arrowheads, sweep in: two
-//! run into the top face and narrow into its mesh lines (the flow becomes the structure), one
-//! wraps around the cube. The faces are
-//! separated by transparent gaps instead of painted edge lines, so the backdrop shows through
-//! them in every appearance. All geometry is computed here, turned into VectorCraft commands and
+//! would. The faces are separated by transparent gaps instead of painted edge lines, so the
+//! backdrop shows through them in every appearance. All geometry is computed here, turned into VectorCraft commands and
//! run headlessly with `vectorcraft-cli run` (`VECTORCRAFT_CLI`, else `vectorcraft-cli` on the
//! PATH), which saves each document's editable `.vectorcraft` source and exports its SVG (and PNG).
//!
//! Outputs, all under `assets/app-icon/` (see its README.md):
//! - `icon.vectorcraft`, `icon.svg`, `icon.png`: the full icon on its backdrop, 1024 × 1024.
//! - `icon-small.vectorcraft`, `icon-small.svg`, `icon-small.png`: simplified artwork for 16–32 px
-//! (no mesh or floor grid, faces edge to edge, one bold streamline with an arrowhead).
+//! (no mesh or floor grid, faces edge to edge).
//! - `icon.iconset/` (sips), `icon.icns` (iconutil) and `icon.ico` (ImageMagick).
//! - `FieldCraft.icon/`: an Icon Composer document with separate floor-grid (Default and Dark),
-//! flow (behind and in front) and cube layers (the mesh shares the cube's: Icon Composer allows
-//! four groups); their editable sources are `layers/*.vectorcraft`.
+//! and cube layers (the mesh shares the cube's); their editable sources are `layers/*.vectorcraft`.
//! - `Assets.car`: Apple's actool compilation of `FieldCraft.icon`, when Xcode 26 or later is
//! installed (otherwise the step is skipped with a note and the checked-in catalog stays).
//!
//! macOS only (sips, iconutil); ImageMagick (`magick`) writes the Windows icon.
-use std::f64::consts::{FRAC_PI_2, PI};
+use std::f64::consts::FRAC_PI_2;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -35,7 +31,7 @@ type P3 = [f64; 3];
/// Canvas side in pixels; every document is one square artboard.
const CANVAS: f64 = 1024.0;
/// Pixels per model unit, and the screen point the model origin projects to.
-const SCALE: f64 = 286.0;
+const SCALE: f64 = 300.0;
const CENTRE: P2 = [512.0, 500.0];
const COS30: f64 = 0.866_025_403_784_438_6;
@@ -55,17 +51,13 @@ const BAND_RADII: [f64; 6] = [1.28, 1.07, 0.87, 0.67, 0.47, 0.27];
/// The macOS icon grid: the backdrop's square, inset 100 px on a 1024 canvas.
const BACKDROP: [f64; 2] = [100.0, 924.0];
-/// The streamlines' halo colour (the same in both appearances: it keeps the fringe-coloured
-/// cores readable on the light backdrop and over the cube).
-const FLOW_HALO: &str = "#0b2236";
-
#[derive(Clone, Copy, PartialEq)]
enum Appearance {
Light,
Dark,
}
-/// How much of the mark is drawn: the full mesh, flow and floor grid, or the small-size artwork.
+/// How much of the mark is drawn: the full mesh and floor grid, or the small-size artwork.
#[derive(Clone, Copy, PartialEq)]
enum Detail {
Full,
@@ -308,144 +300,6 @@ fn mesh_d() -> String {
segs.iter().map(|&(a, b)| polyline(&[project(a), project(b)], false)).collect::>().join(" ")
}
-/// A streamline's path: a swept curve around the cube's vertical axis, or a feed that sweeps in
-/// from the lower left, climbs over the left face and runs into the top face's mesh line
-/// `x = c`, carrying on along it as the hex edge.
-enum Path3 {
- /// Radius `r`, height `z0 + tilt · sin(θ − 45°)`, from angle `from` to `to` (degrees,
- /// decreasing: the flow passes in front of the cube from screen left to right).
- Swirl { r: f64, z0: f64, tilt: f64, from: f64, to: f64 },
- /// In the plane `x = c`: a cubic from `start` (y, z) to the top face's front-left edge,
- /// arriving level so it runs straight on into the mesh line.
- Feed { c: f64, start: P2, bend: P2 },
-}
-
-struct Stream {
- path: Path3,
- /// Widest core, in pixels on the 1024 canvas.
- width: f64,
- /// Where the arrowheads sit, as fractions of the path.
- arrows: &'static [f64],
-}
-
-const STREAMS: [Stream; 3] = [
- Stream { path: Path3::Feed { c: -0.25, start: [1.25, -0.7], bend: [0.95, -0.6] }, width: 17.0, arrows: &[0.45] },
- Stream { path: Path3::Feed { c: 0.25, start: [1.05, -0.6], bend: [0.9, -0.45] }, width: 17.0, arrows: &[0.5] },
- Stream { path: Path3::Swirl { r: 0.98, z0: -0.08, tilt: 0.22, from: 215.0, to: -60.0 }, width: 17.0, arrows: &[0.3, 1.0] },
-];
-
-/// The small-size artwork's one streamline.
-const SMALL_STREAM: Stream = Stream { path: Path3::Swirl { r: 1.0, z0: -0.12, tilt: 0.14, from: 190.0, to: -40.0 }, width: 44.0, arrows: &[1.0] };
-
-/// Samples along each streamline.
-const STREAM_SAMPLES: usize = 160;
-
-/// The fringe band of the flow speed at fraction `t` along a streamline: slow (blue) at its
-/// tail, fastest (red) where it reaches the cube or its head.
-fn speed_band(t: f64) -> usize {
- ((t * FRINGE.len() as f64) as usize).min(FRINGE.len() - 1)
-}
-
-impl Path3 {
- fn at(&self, t: f64) -> P3 {
- match *self {
- Path3::Swirl { r, z0, tilt, from, to } => {
- let a = (from + (to - from) * t).to_radians();
- [r * a.cos(), r * a.sin(), z0 + tilt * (a - PI / 4.0).sin()]
- }
- Path3::Feed { c, start, bend } => {
- let (p0, p1, p2, p3) = (start, bend, [A + 0.35, A], [A, A]);
- let u = 1.0 - t;
- let b = |k: usize| u * u * u * p0[k] + 3.0 * u * u * t * p1[k] + 3.0 * u * t * t * p2[k] + t * t * t * p3[k];
- [c, b(0), b(1)]
- }
- }
- }
-
- /// The mesh line a feed runs on into, across the top face (none for a swirl).
- fn continuation(&self) -> Option<(P3, P3)> {
- match *self {
- Path3::Swirl { .. } => None,
- Path3::Feed { c, .. } => Some(([c, A, A], [c, -A, A])),
- }
- }
-}
-
-/// A filled shape of a streamline: its path data and colour (`None` for the halo colour).
-type Piece = (String, Option<&'static str>);
-
-/// A streamline drawn as tapered ribbons: the halo (`grow` pixels wider each side) and the core
-/// split into speed bands, then the arrowheads. Returns (behind the cube, in front of it).
-fn stream_pieces(s: &Stream, grow: f64) -> (Vec, Vec) {
- let n = STREAM_SAMPLES;
- let ts: Vec = (0..=n).map(|i| i as f64 / n as f64).collect();
- let pts3: Vec = ts.iter().map(|&t| s.path.at(t)).collect();
- let pts: Vec = pts3.iter().map(|&p| project(p)).collect();
- // A tail that grows to full width over the first third.
- let half: Vec = ts.iter().map(|&t| s.width / 2.0 * (t / 0.3).min(1.0).powf(0.6) + grow).collect();
- let dir = |i: usize| {
- let v = sub(pts[(i + 1).min(n)], pts[i.saturating_sub(1)]);
- let len = v[0].hypot(v[1]).max(1e-9);
- [v[0] / len, v[1] / len]
- };
- let ribbon = |lo: usize, hi: usize| {
- let side = |i: usize, k: f64| {
- let d = dir(i);
- [pts[i][0] - d[1] * half[i] * k, pts[i][1] + d[0] * half[i] * k]
- };
- let mut poly: Vec = (lo..=hi).map(|i| side(i, 1.0)).collect();
- poly.extend((lo..=hi).rev().map(|i| side(i, -1.0)));
- polyline(&poly, true)
- };
- let colour = |i: usize| if grow > 0.0 { None } else { Some(FRINGE[speed_band(ts[i])]) };
- let front = |i: usize| pts3[i][0] + pts3[i][1] > 0.0;
- let (mut behind, mut ahead) = (Vec::new(), Vec::new());
- // Runs of one side of the cube and one colour; neighbours share their boundary sample, so
- // they meet without a seam.
- let mut lo = 0;
- for i in 1..=n {
- if i == n || front(i) != front(lo) || colour(i) != colour(lo) {
- let piece = (ribbon(lo, i), colour(lo));
- if front(lo) {
- ahead.push(piece)
- } else {
- behind.push(piece)
- }
- lo = i;
- }
- }
- // A feed carries on along its mesh line, narrowing to the mesh's stroke: the flow becomes
- // the hex edge.
- if let Some((a, b)) = s.path.continuation() {
- {
- let (p, q) = (project(a), project(b));
- let v = sub(q, p);
- let len = v[0].hypot(v[1]).max(1e-9);
- let m = [-v[1] / len, v[0] / len];
- let (w0, w1) = (half[n], 0.6 + grow * 0.6);
- let poly = [
- [p[0] + m[0] * w0, p[1] + m[1] * w0],
- [q[0] + m[0] * w1, q[1] + m[1] * w1],
- [q[0] - m[0] * w1, q[1] - m[1] * w1],
- [p[0] - m[0] * w0, p[1] - m[1] * w0],
- ];
- ahead.push((polyline(&poly, true), colour(n)));
- }
- }
- for &f in s.arrows {
- let i = ((f * n as f64).round() as usize).min(n);
- let (d, p, w) = (dir(i), pts[i], s.width);
- let g = grow * 1.6;
- let tip = [p[0] + d[0] * (1.5 * w + g), p[1] + d[1] * (1.5 * w + g)];
- let back = [p[0] - d[0] * (0.7 * w + g), p[1] - d[1] * (0.7 * w + g)];
- let wing = 1.25 * w + g;
- let poly = [tip, [back[0] - d[1] * wing, back[1] + d[0] * wing], [back[0] + d[1] * wing, back[1] - d[0] * wing]];
- let piece = (polyline(&poly, true), colour(i));
- if front(i) { ahead.push(piece) } else { behind.push(piece) }
- }
- (behind, ahead)
-}
-
/// Clip a segment to the axis-aligned square [lo, hi]² (Liang–Barsky).
fn clip_to_square(a: P2, b: P2, lo: f64, hi: f64) -> Option<(P2, P2)> {
let d = sub(b, a);
@@ -556,28 +410,6 @@ fn mesh_layer() -> Result {
Ok(Layer { name: "Mesh", shapes: vec![Shape::stroked("hexahedral mesh", mesh_d(), stroke, 0.45)], clip: Some(clip) })
}
-/// The streamlines' runs behind the cube (`front == false`) or in front of it.
-fn flow_layer(detail: Detail, front: bool) -> Layer {
- let streams: Vec<&Stream> = match detail {
- Detail::Full => STREAMS.iter().collect(),
- Detail::Small => vec![&SMALL_STREAM],
- };
- let mut shapes = Vec::new();
- for (k, s) in streams.into_iter().enumerate() {
- let pick = |(behind, ahead): (Vec, Vec)| if front { ahead } else { behind };
- let halo = pick(stream_pieces(s, (s.width * 0.18).max(3.0)));
- if halo.is_empty() {
- continue;
- }
- let d = halo.into_iter().map(|p| p.0).collect::>().join(" ");
- shapes.push(Shape::filled(&format!("streamline {} halo", k + 1), d, colour(FLOW_HALO)));
- for (j, (d, c)) in pick(stream_pieces(s, 0.0)).into_iter().enumerate() {
- shapes.push(Shape::filled(&format!("streamline {} part {}", k + 1, j + 1), d, colour(c.unwrap_or(FLOW_HALO))));
- }
- }
- Layer { name: if front { "Flow in front" } else { "Flow behind" }, shapes, clip: None }
-}
-
fn floor_layer(appearance: Appearance) -> Layer {
let colour = match appearance {
Appearance::Light => "#8aa0b4",
@@ -608,12 +440,10 @@ fn composite(detail: Detail, appearance: Appearance) -> Result {
if detail == Detail::Full {
layers.push(floor_layer(appearance));
}
- layers.push(flow_layer(detail, false));
layers.extend(part_layers(detail)?);
if detail == Detail::Full {
layers.push(mesh_layer()?);
}
- layers.push(flow_layer(detail, true));
let title = match (detail, appearance) {
(Detail::Full, Appearance::Light) => "FieldCraft application icon",
(Detail::Full, Appearance::Dark) => "FieldCraft application icon (dark preview)",
@@ -723,9 +553,7 @@ fn icon_json() -> Value {
json!({
"fill": "system-light",
"groups": [
- group("04 Flow front", plain("04-flow-front"), false),
- group("03 Part", plain("03-part"), true),
- group("02 Flow behind", plain("02-flow-behind"), false),
+ group("02 Part", plain("02-part"), true),
group("01 Floor", json!({"image-name-specializations": [{"value": "01-floor.svg"}, {"appearance": "dark", "value": "01-floor-dark.svg"}],
"name": "01-floor", "fill": "none", "position": position,
"glass-specializations": [{"value": false}, {"appearance": "tinted", "value": true}]}), false),
@@ -757,16 +585,14 @@ pub fn run(root: &Path) -> Result<(), String> {
vectorcraft(&composite(Detail::Small, Appearance::Dark)?, &work.join("icon-small-dark.vectorcraft"), &[work.join("icon-small-dark.png")])?;
// Icon Composer layers: the system supplies the backdrop.
- // Icon Composer allows four groups, so the mesh shares the cube's.
+ // The mesh shares the cube's Icon Composer group.
let single = |title, layer| Doc { title, layers: vec![layer] };
let mut part_with_mesh = part_layers(Detail::Full)?;
part_with_mesh.push(mesh_layer()?);
let layer_docs = [
("01-floor", single("FieldCraft icon floor grid", floor_layer(Appearance::Light))),
("01-floor-dark", single("FieldCraft icon floor grid (Dark)", floor_layer(Appearance::Dark))),
- ("02-flow-behind", single("FieldCraft icon flow behind the cube", flow_layer(Detail::Full, false))),
- ("03-part", Doc { title: "FieldCraft icon cube", layers: part_with_mesh }),
- ("04-flow-front", single("FieldCraft icon flow in front of the cube", flow_layer(Detail::Full, true))),
+ ("02-part", Doc { title: "FieldCraft icon cube", layers: part_with_mesh }),
];
for (name, doc) in &layer_docs {
vectorcraft(doc, &layers.join(format!("{name}.vectorcraft")), &[assets.join(format!("{name}.svg"))])?;
@@ -850,18 +676,6 @@ mod tests {
}
}
- #[test]
- fn streamlines_pass_where_they_should() {
- for s in &STREAMS {
- let (behind, ahead) = stream_pieces(s, 0.0);
- assert!(!ahead.is_empty());
- // Only the swirl goes round the back; a feed stays in front and ends on the mesh.
- assert_eq!(behind.is_empty(), s.path.continuation().is_some());
- }
- assert_eq!(speed_band(0.0), 0);
- assert_eq!(speed_band(1.0), FRINGE.len() - 1);
- }
-
#[test]
fn segments_clip_to_the_square() {
assert_eq!(clip_to_square([-5.0, 5.0], [15.0, 5.0], 0.0, 10.0), Some(([0.0, 5.0], [10.0, 5.0])));
@@ -874,8 +688,8 @@ mod tests {
let steps = commands(&doc, Path::new("/tmp/icon.vectorcraft"));
assert_eq!(steps.first().map(|s| s.0), Some("file.new"));
assert_eq!(steps.last().map(|s| s.0), Some("document.save"));
- // Floor, flow behind, three faces, mesh, flow in front.
- assert_eq!(steps.iter().filter(|s| s.0 == "layer.new").count(), 7);
+ // Floor, three faces, mesh.
+ assert_eq!(steps.iter().filter(|s| s.0 == "layer.new").count(), 5);
// The floor grid is clipped to the backdrop, each face's fringe and the mesh to the faces.
assert_eq!(steps.iter().filter(|s| s.0 == "layer.clippingMask.toggle").count(), 5);
}