diff --git a/src/main.ts b/src/main.ts index e4f202c..11cbdd1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,9 @@ -import { line, group, translate, asSvg, svgDoc, scale, flip } from "@thi.ng/geom"; +import { line, type Line, group, translate, asSvg, svgDoc, scale, flip } from "@thi.ng/geom"; +import { vec2, type Vec2 } from "@thi.ng/vectors" import { $canvas } from "@thi.ng/rdom-canvas"; import { reactive, sync } from "@thi.ng/rstream"; import { $compile, $input } from '@thi.ng/rdom' -import { cycle, takeWhile, comp, mapIndexed, range, iterator, map, mapcat, reverse } from "@thi.ng/transducers" -import { vec2 } from "@thi.ng/vectors" +import { cycle, comp, range, iterator, map, mapcat, reverse, zip } from "@thi.ng/transducers" const cellSize_ = "12" const xs_ = "101110100101100" @@ -11,8 +11,8 @@ const ys_ = "101001111001110000101" const parsePattern = (pattern: string) => pattern.split('').map(x => Number.parseInt(x, 10)) -const xs = reactive(xs_).map((pattern) => parsePattern(pattern)) -const ys = reactive(ys_).map((pattern) => parsePattern(pattern)) +const xSeeds = reactive(xs_).map((pattern) => parsePattern(pattern)) +const ySeeds = reactive(ys_).map((pattern) => parsePattern(pattern)) const cellSize = reactive(cellSize_).map(num => { const next = Number.parseInt(num, 10) return Number.isNaN(next)|| next < 5 ? 5 : next @@ -28,36 +28,51 @@ const height = 594 const size = [width, height] -// build a list of start positions in a one-dimensional grid; lines will start -// perpendicular from the first value (cell index) with a given seed (1 or 0) - -const seededGrid = (pattern: Iterable, maxCells: number) => - [...iterator( - comp( - mapIndexed((cell, val) => [cell, val] as const), - takeWhile(([cell, _]) => cell <= maxCells), - ), - cycle(pattern) - )] +// some explanation for what's happening below: +// +// - a coord is a position on a single axis +// - from this coord, a sequence of stitches will be generate in perpendicular +// direction +// - how this line is generated is decided by a seed, which is either 0 or 1; +// if a seed is 0, the stitches start right at the axis, if it is one it will +// skip exactly one coordinate // points from which to which a stitch is showing for a given seed -const stitches = (seed: number, maxCells: number) => - [...iterator( - comp( - mapcat((cell) => (cell + seed) % 2 === 0 // start at the beginning or skip it - ? [vec2(cell, cell + 1)] - : null), - takeWhile(([a, _]) => a < maxCells) - ), - range() - )] +const stitches = (seed: number, maxCoord: number) => + iterator( + mapcat((cell) => (cell + seed) % 2 === 0 // start at the beginning or skip it + ? [vec2(cell, cell + 1)] + : null) + , + range(maxCoord) + ) + +const xLineGen = (coord: number, [p1, p2]: Vec2) => line([p1, coord], [p2, coord]) +const yLineGen = (coord: number, [p1, p2]: Vec2) => line([coord, p1], [coord, p2]) + +const generateLines = (maxCoordOnAxis: number, maxCoordPerpendicular: number, seeds: Iterable, lineGen: (coord: number, l: Vec2) => Line) => + iterator( + mapcat(([coord, seed]) => { + // optimization for quicker drawing: when we're in an odd row / col, + // reverse drawing direction + const onEvenCoord = coord % 2 === 0 + return iterator( + comp( + map(l => lineGen(coord, l)), + map(l => onEvenCoord ? l : flip(l)) + ), + onEvenCoord ? stitches(seed, maxCoordPerpendicular) : reverse(stitches(seed, maxCoordPerpendicular)) + ) + }), + zip(range(maxCoordOnAxis), seeds) + ) + +// now we can put the functions above together based on the current settings and generate a scene to be drawn const scene = sync({ - src: { cellSize, xs, ys, } -}).map(({ cellSize, xs, ys }) => { - // console.log({ cellSize, xs, ys }) - + src: { cellSize, xSeeds, ySeeds, } +}).map(({ cellSize, xSeeds, ySeeds }) => { const minPadding = cellSize // calculate available drawing area @@ -67,31 +82,8 @@ const scene = sync({ const yCells = Math.floor((height - 2 * minPadding) / cellSize) const yPadding = (height - (yCells * cellSize)) / 2 - const xSeeds = seededGrid(xs, xCells) - const ySeeds = seededGrid(ys, yCells) - - const yLines = iterator( - mapcat(([x, seed]) => { - // optimization for quicker drawing: when we're in an odd row, - // reverse drawing direction - const inEvenRow = x % 2 === 0 - return iterator( - comp( - map(([y1, y2]) => line([x, y1], [x, y2])), - map(l => inEvenRow ? l : flip(l)), - map(l => scale(l, cellSize)) - ), - inEvenRow ? stitches(seed, yCells) : reverse(stitches(seed, yCells)) - ) - }), - xSeeds - ) - - const xLines = iterator( - mapcat(([y, seed]) => - map(([x1, x2]) => scale(line([x1, y], [x2, y]), cellSize), stitches(seed, xCells))), - ySeeds - ) + const xLines = [...map(l => scale(l, cellSize), generateLines(yCells, xCells, cycle(ySeeds), xLineGen))] + const yLines = [...map(l => scale(l, cellSize), generateLines(xCells, yCells, cycle(xSeeds), yLineGen))] const scene = translate( group( @@ -122,13 +114,13 @@ $compile( ["input", { type: "text", value: xs_, - oninput: $input(xs) + oninput: $input(xSeeds) }]]], ["div", {}, ["label", {}, "Y Pattern: ", ["input", { type: "text", value: ys_, - oninput: $input(ys) + oninput: $input(ySeeds) }]]], ["h2", {}, "SVG Export"], ["div", {}, ["textarea", {