Optimize drawng pattern on both axes

This commit is contained in:
arne 2024-07-20 14:21:25 +02:00
commit b71c522096

View file

@ -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<number>, 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<number>, 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", {