
Standing Timber
A wood seen against a low sun. The light is behind the trees, so every trunk stands as a dark column, with at most a hair of lit edge on the side facing it, and every trunk throws its shadow straight at the viewer. Those shadows lie flat on the ground, so in the picture they are not parallel: they fan out from the one point on the horizon under the sun, which is also where the air is brightest. One light sets the value of everything in the plate, and where it stands is the composition. The seed moves the sun, the horizon and every tree, so each seed is a different wood lit from a different place, and the shadows fan out again from wherever the light has gone.
Technique
`painting:flow-lines` layers reading tone and direction channels that the sketch publishes on the algorithm data bridge. The sketch itself makes no marks. Air and ground are one tone map, ruled level by one family of passes, so the floor of the wood runs back into the haze with no edge where it meets the air. The trunks are hatched along their grain and gated only on the tone they carry in excess of the air behind them: a far trunk is the air plus a little, and near the sun the little goes to nothing. The shadows are a pass of their own, whose marks run along each shadow toward the sun's point on the horizon.
Seeds
The same system at three seeds. The composition itself re-cuts: the geometry is derived from the seed, so masses, edges and placement all move, while the palette and the drawing language stay put.
7788
2718
52
Layer stack
In paint order. Every mark in the image comes from these; there is no handwritten drawing code.
textures:paperSheetpainting:flow-linesAir and Ground Pass 1 — Ruledpainting:flow-linesAir and Ground Pass 2 — Crossedpainting:flow-linesAir and Ground Pass 3 — Canopy and Shadepainting:flow-linesAir and Ground Pass 4 — Deepest Onlypainting:flow-linesCast Shadows — Toward the Viewerpainting:flow-linesTrunk Pass 1 — Grainpainting:flow-linesTrunk Pass 2 — Half Tonepainting:flow-linesTrunk Pass 3 — Round the Bolepainting:flow-linesTrunk Pass 4 — Cross, Deepest Onlyshapes:rectPlate Markfilter:grainPlate Tone
Source
The complete composition. Open it in GenArt to re-render, re-seed, or take it apart.
{
"genart": "1.2",
"id": "standing-timber",
"title": "Standing Timber",
"created": "2026-09-10T00:00:00Z",
"modified": "2026-09-10T22:01:51.122Z",
"renderer": {
"type": "canvas2d",
"version": "1.x"
},
"canvas": {
"width": 1400,
"height": 1000
},
"parameters": [],
"colors": [],
"dataChannels": [
{
"name": "air1",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "air2",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "air3",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "air4",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "trunk1",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "trunk2",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "trunk3",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "trunk4",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "shadow",
"type": "vector",
"cols": 400,
"rows": 300
}
],
"state": {
"seed": 7788,
"params": {},
"colorPalette": [
"#2b1a12",
"#5c3320",
"#8a5a3c",
"#c9a982",
"#ece3d4"
]
},
"algorithm": "// Standing Timber — field generation only. This code makes NO marks.\n// It publishes tone and direction maps on the ADR 062 data bridge; every line\n// in the picture is drawn by a plugin layer that reads them.\n//\n// A wood against a low sun. The sun is behind the trees, so\n// every trunk is a dark column with a thin lit edge on the side facing the\n// light, and every trunk throws its shadow straight toward the viewer. Those\n// shadows lie on the ground plane, so in the picture they are not parallel:\n// they radiate from the one point on the horizon under the sun, which is also\n// where the glow in the air is brightest. One light source sets the value of\n// everything, and its position is the whole composition.\n//\n// The same two rules as the rest of the series:\n// the direction of a hatch = the surface or grain it describes\n// the density of a hatch = how far that surface is turned from the light\nfunction sketch(ctx, state) {\n var W = state.canvas.width, H = state.canvas.height;\n var seed = state.seed || 0;\n\n ctx.fillStyle = state.colorPalette[4];\n ctx.fillRect(0, 0, W, H);\n\n // The field grid scales with the canvas so a cell stays the same size in\n // pixels. Holding it fixed softens every mask edge as the plate grows.\n var COLS = Math.round(200 * W / 700), ROWS = Math.round(150 * H / 500);\n // The plate is inset in the sheet (see the builder), and the field maps\n // onto the plate, so this is the plate's aspect, not the canvas's.\n var ASPECT = (W * 0.85) / (H * 0.81);\n\n function rngFrom(a) {\n return function () {\n a |= 0; a = a + 0x6D2B79F5 | 0;\n var t = Math.imul(a ^ a >>> 15, 1 | a);\n t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;\n return ((t ^ t >>> 14) >>> 0) / 4294967296;\n };\n }\n var rand = rngFrom(seed * 2654435761 + 7919);\n\n /** A lattice of random values, bilinearly sampled over [0,1]. */\n function lattice(nx, ny) {\n var v = new Float32Array(nx * ny);\n for (var k = 0; k < nx * ny; k++) v[k] = rand() * 2 - 1;\n return function (u, t) {\n // 🔴 Clamp. A sample outside [0,1] returns undefined, becomes a NaN\n // direction in a published channel, and takes the whole render down on\n // a frame the CLI still exits 0 for. See wind-algorithm.js.\n if (u < 0) u = 0; else if (u > 1) u = 1;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n var x = u * (nx - 1), y = t * (ny - 1);\n var x0 = Math.floor(x), y0 = Math.floor(y);\n var x1 = x0 + 1 > nx - 1 ? nx - 1 : x0 + 1, y1 = y0 + 1 > ny - 1 ? ny - 1 : y0 + 1;\n var fx = x - x0, fy = y - y0;\n fx = fx * fx * (3 - 2 * fx); fy = fy * fy * (3 - 2 * fy);\n var a = v[y0 * nx + x0], b = v[y0 * nx + x1], c = v[y1 * nx + x0], d = v[y1 * nx + x1];\n return (a + (b - a) * fx) * (1 - fy) + (c + (d - c) * fx) * fy;\n };\n }\n function smooth(e0, e1, x) {\n var t = (x - e0) / (e1 - e0);\n if (t < 0) t = 0; else if (t > 1) t = 1;\n return t * t * (3 - 2 * t);\n }\n\n var mist = lattice(11, 9); // the air is not evenly thick\n var kink = lattice(151, 113); // finer than the line spacing, so neighbours disagree\n var edge = lattice(53, 41); // breaks mask boundaries\n // Bark runs along the trunk, so its lattice is fine across and coarse along.\n var bark = lattice(211, 23);\n // One dither lattice per pass, each different, so the pass boundaries fray\n // independently instead of jogging together into one contour.\n var dith = [lattice(83, 63), lattice(79, 59), lattice(89, 67), lattice(73, 55)];\n\n // --- The ground and the light -----------------------------------------\n var hv = 0.57 + rand() * 0.08; // the horizon, below the middle: a wood is tall\n var vpU = 0.5; // the viewer looks straight in\n var XK = 1.4; // ground units visible across the near edge\n var DNEAR = 1.0;\n // The sun sits off centre, never in the middle and never at an edge. Its\n // point on the horizon is where every ground shadow converges.\n var sunU = rand() < 0.5 ? 0.26 + rand() * 0.16 : 0.58 + rand() * 0.16;\n var sunV = hv - 0.06 - rand() * 0.07;\n var sx = (sunU - vpU) * XK; // the sun's direction on the ground, per unit depth\n\n /** Screen v of a ground point at depth d. */\n function groundV(d) { return hv + (1 - hv) * DNEAR / d; }\n\n // --- The trees --------------------------------------------------------\n // Four ranks, nearest first. Each tree is placed across the view, then set\n // on the ground at its depth, so its base, its width and its shadow all come\n // out of one projection instead of being set by hand.\n //\n // 🔴 Fewer than looks right on paper. The first version stood 42 trees in\n // four evenly stratified ranks and read as a colonnade: a stage set of\n // pillars, not a wood. The back ranks are now thrown at random, so they\n // clump and leave gaps the way self-sown trees do, and they lean.\n var RANKS = [\n { n: 3, d0: 0.80, d1: 1.45, w0: 0.080, w1: 0.160, lean: 0.025, strat: true },\n { n: 5, d0: 1.80, d1: 3.20, w0: 0.070, w1: 0.120, lean: 0.045, strat: true },\n { n: 8, d0: 3.60, d1: 6.50, w0: 0.060, w1: 0.110, lean: 0.040, strat: false },\n { n: 13, d0: 7.00, d1: 15.0, w0: 0.060, w1: 0.100, lean: 0.035, strat: false },\n ];\n var trees = [];\n for (var ri = 0; ri < RANKS.length; ri++) {\n var R = RANKS[ri];\n var tries = 0;\n var slot = 1.14 / R.n;\n for (var s = 0; s < R.n && tries < 400; tries++) {\n var su = R.strat ? -0.07 + slot * (s + 0.15 + rand() * 0.7) : -0.07 + rand() * 1.14;\n var d = R.d0 + rand() * (R.d1 - R.d0);\n // The nearest rank carries the composition, so it is placed with care:\n // never over the sun, where a dark column would put out the light the\n // whole plate is organised around, and never evenly spaced.\n if (ri === 0) {\n if (Math.abs(su - sunU) < 0.13) continue;\n var clash = false;\n for (var q = 0; q < trees.length; q++) if (Math.abs(trees[q].u - su) < 0.2) clash = true;\n if (clash) continue;\n }\n var wt = R.w0 + rand() * (R.w1 - R.w0);\n trees.push({\n u: su, d: d, rank: ri,\n gx: (su - vpU) * d * XK,\n wt: wt,\n hw: wt / (d * XK) / 2, // half width on screen, in u\n vb: groundV(d), // where it stands\n lean: (rand() * 2 - 1) * R.lean, // du per unit of height\n sway: rand() * Math.PI * 2,\n });\n s++;\n }\n }\n trees.sort(function (a, b) { return a.d - b.d; });\n\n // Fray lattices for the ruled air, fine ACROSS the lines and coarse ALONG\n // them, so a pass boundary lands at a different point on every line and the\n // ruling ends raggedly, the way an engraver lets a sky thin out. The square\n // dither lattices cut it into stair-stepped blocks: pixel art, round a sun.\n // ⚠️ Allocated after the trees on purpose. Every lattice spends hundreds of\n // rand() calls, so one allocated above would re-cut every seed's wood.\n // 🔴 Fine along the lines too, at about a line's length. At 7 cells across,\n // a whole row took one jog, every line in it stopped at the same height, and\n // the top of the glow came out as a straight edge: a window, not a sun.\n var streak = [lattice(23, 211), lattice(19, 199), lattice(27, 223), lattice(21, 191)];\n\n /** Screen centre and half width of a tree at height v, or null below its foot. */\n function trunkAt(t, v) {\n if (v > t.vb) return null;\n var up = t.vb - v;\n // A trunk tapers only slightly over the height of a plate, but it\n // flares at the root, where it grips the ground. Without the flare a trunk\n // stands on the ground like a post pushed into it.\n var flare = 1 + 0.55 * Math.exp(-up / (5 * t.hw * ASPECT));\n var hw = t.hw * (1 - 0.14 * up) * flare;\n var cu = t.u + t.lean * up + 0.004 * Math.sin(up * 6.0 + t.sway) * (t.rank === 0 ? 1 : 0.4);\n return { cu: cu, hw: hw };\n }\n\n /** The air's own value: bright round the sun, closing in toward the canopy. */\n function airTone(u, v) {\n var dx = (u - sunU) * ASPECT, dy = (v - sunV) * 1.25;\n var rr = Math.sqrt(dx * dx + dy * dy);\n // 🔴 Kept small, and its edge is frayed by the pass dither below. Broad\n // and cut at one threshold, the glow came out as a hard-edged white disc\n // hung in the trees — a moon, not a low sun in mist.\n // Graded slowly outward rather than steeply: pulled in tight to kill the\n // disc, it left the whole plate at one murky middle value — night in a\n // wood, not a winter morning.\n var T = 0.02 + 0.40 * Math.pow(smooth(0.0, 1.1, rr), 0.9);\n // The canopy closes overhead, so the top of the plate is darker than the\n // glow's falloff alone would make it.\n T += 0.12 * smooth(0.35, 0.0, v);\n T += 0.04 * mist(u, v);\n return T;\n }\n\n var HAZE = 4.2; // the depth over which a trunk loses most of its contrast\n\n var N = COLS * ROWS;\n var airTone2 = new Float32Array(N); // air above the horizon AND ground below it\n var airDir = new Float32Array(N);\n var trunkTone = new Float32Array(N);\n var trunkAlong = new Float32Array(N);\n var trunkAcross = new Float32Array(N);\n var shadowMask = new Float32Array(N);\n var rayDir = new Float32Array(N);\n\n for (var r2 = 0; r2 < ROWS; r2++) {\n for (var c = 0; c < COLS; c++) {\n var u = c / (COLS - 1), v = r2 / (ROWS - 1);\n var idx = r2 * COLS + c;\n var air = airTone(u, v);\n\n // 🔴 Air and ground are ONE tone map, ruled level by one family of\n // passes. Kept as two, they were gated and marked differently, so equal\n // tones drew as unequal densities: the floor of the wood ended at the\n // horizon in an edge like a table top, and every trunk stepped in value\n // there, because the air ran under it above the horizon and nothing ran\n // under it below. Far ground now goes to exactly the air's value, so the\n // horizon dissolves into the mist wherever the light says it should.\n var base = air, dg = 0, gxg = 0;\n if (v >= hv) {\n var rec = (v - hv) / (1 - hv);\n if (rec < DNEAR / 60) rec = DNEAR / 60;\n dg = DNEAR / rec;\n gxg = (u - vpU) * dg * XK;\n var hg = Math.exp(-(dg - 0.8) / HAZE);\n base = air + (0.17 - air) * hg; // ground raked by low light: one pass\n }\n var levelDir = 0.05 * kink(u, v) + 0.03 * mist(u, v);\n\n // Nearest tree covering this cell, if any.\n var hit = null, geo = null;\n for (var ti = 0; ti < trees.length; ti++) {\n var g = trunkAt(trees[ti], v);\n if (g && Math.abs(u - g.cu) < g.hw) { hit = trees[ti]; geo = g; break; }\n }\n\n if (hit) {\n // 🔴 The air runs UNDER the trunks. Cut out round each one, it gave\n // every trunk a bright outline on both sides (two masks that abut do\n // not meet) and cut the air between trunks into hatched panels. So a\n // trunk only adds the tone it has IN EXCESS of what lies behind it,\n // which is also the right model of haze: a far trunk is the air plus a\n // little, and near the sun the little goes to nothing.\n var x = (u - geo.cu) / geo.hw; // -1..1 across the trunk\n var toSun = sunU > geo.cu ? 1 : -1;\n var xs = x * toSun; // 1 at the edge facing the sun\n var h = Math.exp(-(hit.d - 0.8) / HAZE);\n var Tint = 0.80 + 0.06 * bark(u, v);\n Tint -= 0.10 * smooth(-0.55, -1.0, xs); // sky fill on the shadow side\n var ex = (Tint - base) * h;\n // The lit edge: a hair of bare paper on the side facing the sun, and\n // only where there is bright air behind the trunk to light it. Wider,\n // or everywhere, it was a white stripe down every tree.\n // Its jitter is kept small: at twice this the edge read as torn paper.\n var rim = xs > 0.86 + 0.03 * edge(u, v) && air < 0.20 && h > 0.25;\n trunkTone[idx] = rim ? 0 : (ex < 0 ? 0 : ex > 1 ? 1 : ex);\n trunkAlong[idx] = -Math.PI / 2 + Math.atan(hit.lean * ASPECT) + 0.04 * kink(u, v);\n // Across the trunk the marks bow round it, the way a line drawn round\n // a column bends at its sides. Above eye level the rings are seen from\n // underneath, so they bow up at the middle.\n trunkAcross[idx] = Math.atan(0.45 * x * (v < hv ? 1 : -1)) + 0.03 * kink(u, v);\n airTone2[idx] = rim ? 0 : base;\n airDir[idx] = levelDir;\n continue;\n }\n\n if (v < hv) {\n airTone2[idx] = air;\n airDir[idx] = levelDir;\n continue;\n }\n\n // --- Ground, and the shadows cast across it ---\n var shade = 0;\n for (var tj = 0; tj < trees.length; tj++) {\n var tr = trees[tj];\n if (tr.d <= dg) continue; // shadows fall toward the viewer only\n var tt = tr.d - dg;\n var xc = tr.gx - tt * sx;\n var half = tr.wt * 0.5 * (1 + 0.05 * tt); // the penumbra spreads with distance\n var e = 1 - smooth(0.75, 1.1, Math.abs(gxg - xc) / half);\n if (e > shade) shade = e;\n }\n var hs = Math.exp(-(dg - 0.8) / (HAZE * 1.4));\n var Tg = base + 0.40 * shade * hs;\n airTone2[idx] = Tg < 0 ? 0 : Tg > 1 ? 1 : Tg;\n airDir[idx] = levelDir; // ground is ruled level, like the air\n // The shadows run to the sun's point on the horizon, so their marks do.\n var du = u - sunU, dv = (v - hv) / ASPECT;\n rayDir[idx] = Math.atan2(dv, du) + (du < 0 ? Math.PI : 0);\n if (shade * hs > 0.35 + 0.08 * edge(u, v)) shadowMask[idx] = 1;\n }\n }\n\n // --- Publish ----------------------------------------------------------\n var g2 = (typeof globalThis !== 'undefined') ? globalThis : window;\n g2.__genart_data = g2.__genart_data || {};\n g2.__genart_data.cols = COLS;\n g2.__genart_data.rows = ROWS;\n\n function pack(name, mag, ang, skew) {\n var f32 = new Float32Array(N * 3);\n for (var n = 0; n < N; n++) {\n var a = ang[n] + (skew || 0);\n var m = mag[n];\n if (!isFinite(a)) a = 0;\n if (!isFinite(m)) m = 0;\n f32[n * 3] = Math.cos(a);\n f32[n * 3 + 1] = Math.sin(a);\n f32[n * 3 + 2] = m;\n }\n g2.__genart_data[name] = f32;\n }\n\n /** Gate a tone map at a threshold, frayed by a lattice (or not at all, when null). */\n function gate(tone, th, fray) {\n var m = new Float32Array(N);\n for (var n = 0; n < N; n++) {\n if (tone[n] <= 0) continue;\n var uu = (n % COLS) / (COLS - 1), vv = Math.floor(n / COLS) / (ROWS - 1);\n var t = fray ? th + 0.07 * fray(uu, vv) : th;\n m[n] = tone[n] >= t ? 1 : 0;\n }\n return m;\n }\n\n // Air and ground. Unlike the barley, the first pass IS frayed: the glow is\n // meant to be bare paper, and its edge is the one boundary in the plate that\n // must not show.\n // ⚠️ The first two passes do NOT share a threshold, against the squall's\n // rule that crossing must start at the bottom of the stack. Shared, they put\n // two crossed passes over nearly the whole plate and it went to one murky\n // middle value. Most of the air here is a single ruling, as an engraved sky\n // is; the kink lattice is what keeps it from beating into corrugation.\n // 🔴 Passes 2 and 4 are traced LEFTWARD (skew + PI: the same orientation,\n // the other way along it). The flow-line stepper traces forward only from\n // each seed point and stops at the bounds, so the upstream edge of every\n // pass is thin for one line's length: lines seeded there only cover what\n // lies downstream. With all four passes running right, the left edge of the\n // plate measured 0.76 luminance against 0.30 a little further in, and the\n // ragged line starts along that ramp read as a scribbled border. Two passes\n // each way puts two full-density passes ending at each edge.\n pack('air1', gate(airTone2, 0.10, streak[0]), airDir, 0.06);\n pack('air2', gate(airTone2, 0.22, streak[1]), airDir, -0.14 + Math.PI);\n pack('air3', gate(airTone2, 0.34, streak[2]), airDir, 0.30);\n pack('air4', gate(airTone2, 0.48, streak[3]), airDir, -0.40 + Math.PI);\n\n // Trunks, gated on their tone in EXCESS of what lies behind them: grain\n // first, then the crossing that turns them round. Four passes at four\n // unrelated angles read as scribble; these stay close to the grain and only\n // the third goes round the bole.\n pack('trunk1', gate(trunkTone, 0.06, null), trunkAlong, 0);\n pack('trunk2', gate(trunkTone, 0.26, dith[2]), trunkAlong, 0.12);\n pack('trunk3', gate(trunkTone, 0.40, dith[3]), trunkAcross, 0);\n pack('trunk4', gate(trunkTone, 0.52, dith[0]), trunkAlong, -0.16);\n\n pack('shadow', shadowMask, rayDir, 0);\n}\n",
"layers": [
{
"id": "sheet",
"type": "textures:paper",
"name": "Sheet",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 0,
"y": 0,
"width": 1400,
"height": 1000,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"preset": "rough",
"roughness": -1,
"color": "#ece3d4",
"seed": 7788
}
},
{
"id": "air-1",
"type": "painting:flow-lines",
"name": "Air and Ground Pass 1 — Ruled",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:air1",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7799,
"lineCount": 26000,
"seedDistribution": "grid-jittered",
"lineLength": 18,
"stepSize": 4,
"lineWeight": 1.04,
"lineWeightVariation": 0.14,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.24,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "air-2",
"type": "painting:flow-lines",
"name": "Air and Ground Pass 2 — Crossed",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:air2",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7811,
"lineCount": 22000,
"seedDistribution": "grid-jittered",
"lineLength": 16,
"stepSize": 4,
"lineWeight": 1.04,
"lineWeightVariation": 0.14,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.22,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "air-3",
"type": "painting:flow-lines",
"name": "Air and Ground Pass 3 — Canopy and Shade",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:air3",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7825,
"lineCount": 24000,
"seedDistribution": "grid-jittered",
"lineLength": 12,
"stepSize": 4,
"lineWeight": 1.12,
"lineWeightVariation": 0.14,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.32,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "air-4",
"type": "painting:flow-lines",
"name": "Air and Ground Pass 4 — Deepest Only",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:air4",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7829,
"lineCount": 22000,
"seedDistribution": "grid-jittered",
"lineLength": 9,
"stepSize": 4,
"lineWeight": 1.2,
"lineWeightVariation": 0.14,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.4,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "shadows",
"type": "painting:flow-lines",
"name": "Cast Shadows — Toward the Viewer",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:shadow",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7855,
"lineCount": 34000,
"seedDistribution": "grid-jittered",
"lineLength": 14,
"stepSize": 4,
"lineWeight": 1.16,
"lineWeightVariation": 0.14,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.36,
"paintMode": "multiply",
"depthScale": true,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "trunk-1",
"type": "painting:flow-lines",
"name": "Trunk Pass 1 — Grain",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:trunk1",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7859,
"lineCount": 32000,
"seedDistribution": "grid-jittered",
"lineLength": 20,
"stepSize": 4,
"lineWeight": 1.08,
"lineWeightVariation": 0.1,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.27,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "trunk-2",
"type": "painting:flow-lines",
"name": "Trunk Pass 2 — Half Tone",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:trunk2",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7871,
"lineCount": 30000,
"seedDistribution": "grid-jittered",
"lineLength": 18,
"stepSize": 4,
"lineWeight": 1.12,
"lineWeightVariation": 0.1,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.3,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "trunk-3",
"type": "painting:flow-lines",
"name": "Trunk Pass 3 — Round the Bole",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:trunk3",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7885,
"lineCount": 30000,
"seedDistribution": "grid-jittered",
"lineLength": 9,
"stepSize": 4,
"lineWeight": 1.16,
"lineWeightVariation": 0.1,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.34,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "trunk-4",
"type": "painting:flow-lines",
"name": "Trunk Pass 4 — Cross, Deepest Only",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"field": "algorithm:trunk4",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 7889,
"lineCount": 26000,
"seedDistribution": "grid-jittered",
"lineLength": 16,
"stepSize": 4,
"lineWeight": 1.24,
"lineWeightVariation": 0.1,
"taper": "none",
"color": "#2b1a12",
"colorVariation": 0.05,
"opacity": 0.38,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0.61,
"depthWeightRange": "[0.70, 1.08]",
"depthOpacityRange": "[0.90, 1.00]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "plate-mark",
"type": "shapes:rect",
"name": "Plate Mark",
"visible": true,
"locked": false,
"opacity": 0.55,
"blendMode": "normal",
"transform": {
"x": 105,
"y": 75,
"width": 1190,
"height": 810,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"fillColor": "#ffffff",
"fillEnabled": false,
"strokeColor": "#8a5a3c",
"strokeWidth": 2,
"strokeEnabled": true,
"cornerRadius": 0
}
},
{
"id": "tone",
"type": "filter:grain",
"name": "Plate Tone",
"visible": true,
"locked": false,
"opacity": 1,
"blendMode": "normal",
"transform": {
"x": 0,
"y": 0,
"width": 1400,
"height": 1000,
"rotation": 0,
"scaleX": 1,
"scaleY": 1,
"anchorX": 0,
"anchorY": 0
},
"properties": {
"intensity": 0.1,
"size": 2,
"seed": 7788,
"monochrome": true
}
}
]
}