Skip to content

Commit 57b5e59

Browse files
committed
Start converting patterns to affine transformations
1 parent ddfb983 commit 57b5e59

7 files changed

Lines changed: 351 additions & 16 deletions

File tree

src/App.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { useEffect, useMemo, useReducer, useRef, useState } from 'react'
33
import { drawFrameIncrementally, drawFramePreview } from './draw/draw-frame'
44
import {
55
ClickedPath,
6+
combineTransformations,
67
findClickedScreenOrPattern,
8+
getMatrixAndOffsetFromRectangle,
79
getMousePoint,
810
getRelativePatternPosition,
911
randomId,
@@ -29,12 +31,26 @@ const getDraftState = (state: State, draftClick: DraftClick, mousePosition: Abso
2931
// Re-interpret as relative pattern.
3032
const draftPatternRelative = draftPattern satisfies AbsolutePattern as unknown as RelativePattern
3133

32-
let newDraft = getRelativePatternPosition(draftPatternRelative, state.screens[screenIndex]!)
34+
let newDraft = getMatrixAndOffsetFromRectangle(
35+
getRelativePatternPosition(draftPatternRelative, state.screens[screenIndex]!)
36+
)
3337
for (const k of nestedPath) {
34-
newDraft = getRelativePatternPosition(newDraft, state.patterns[k]!.pattern)
38+
newDraft = combineTransformations(
39+
newDraft.matrix,
40+
newDraft.offset,
41+
state.patterns[k]!.matrix,
42+
state.patterns[k]!.offset
43+
)
3544
}
3645

37-
return { ...state, patterns: state.patterns.concat({ id: randomId() as PatternId, pattern: newDraft }) }
46+
return {
47+
...state,
48+
patterns: state.patterns.concat({
49+
id: randomId() as PatternId,
50+
matrix: newDraft.matrix,
51+
offset: newDraft.offset,
52+
}),
53+
}
3854
}
3955

4056
type DraftClick = {

src/draw/patterns.worker.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AbsolutePattern, PatternId, RelativePattern, State, ViewportPattern } from '../types'
1+
import { AbsolutePattern, PatternId, State, ViewportPattern } from '../types'
22
import { streamBatchedDrawablePatterns } from './stream-batched-drawable-patterns'
33

44
type WorkerState =
@@ -77,9 +77,7 @@ self.onmessage = event => {
7777
function generatePatterns() {
7878
for (const { depth, patterns } of streamBatchedDrawablePatterns({
7979
state: {
80-
patterns: [
81-
{ id: 'debug' as PatternId, pattern: { anchor: [0, 0], target: [0.5, 0.5] } as RelativePattern },
82-
],
80+
patterns: [{ id: 'debug' as PatternId, matrix: [0.8, 0, 0, 0.8], offset: [0.1, 0.1] }],
8381
screens: [{ anchor: [0.2, 0.2], target: [0.8, 0.8] } as AbsolutePattern],
8482
},
8583
chunkSize: 5,

src/draw/stream-drawable-patterns.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { combinePatterns, mapPatternToViewportSpace, mutateBoundariesFromPattern } from '../functions'
1+
import {
2+
applyMatrixAndOffsetToRectangle,
3+
mapPatternToViewportSpace,
4+
mutateBoundariesFromPattern,
5+
} from '../functions'
26
import { Queue } from '../queue'
37
import { Boundaries, Size, State, ViewportNumber, ViewportPattern } from '../types'
48
import { MAX_DEPTH, MAX_QUEUE_SIZE, MIN_PATTERN_SIZE_PX } from './constants'
@@ -97,9 +101,9 @@ export function* streamDrawablePatterns({
97101
break
98102
}
99103

100-
for (const { pattern } of state.patterns) {
104+
for (const { matrix, offset } of state.patterns) {
101105
// Get new pattern
102-
const newViewportPattern = combinePatterns(entry.currentPattern, pattern)
106+
const newViewportPattern = applyMatrixAndOffsetToRectangle(matrix, offset, entry.currentPattern)
103107

104108
if (isValidPattern(newViewportPattern, viewportBoundaries)) {
105109
patternQueue.push({

src/functions.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,9 @@ export const findClickedScreenOrPattern = (
249249
const screen = screens[i]!
250250
const clickedPath = findClickedPattern(
251251
screen,
252-
patterns.map(p => p.pattern),
252+
patterns.map(
253+
p => applyMatrixAndOffsetToRectangle(p.matrix, p.offset, screen) as unknown as RelativePattern
254+
),
253255
point
254256
)
255257

@@ -278,3 +280,76 @@ export function randomId(): string {
278280
// todo: use uuid or something
279281
return Math.random().toString(36).substring(2, 15)
280282
}
283+
284+
export function getMatrixAndOffsetFromRectangle({ anchor, target }: RelativePattern): {
285+
matrix: [a: number, b: number, c: number, d: number]
286+
offset: [x: number, y: number]
287+
} {
288+
const [x1, y1] = anchor
289+
const [x2, y2] = target
290+
291+
// The transformation is: newX = x1 + relativeX * (x2 - x1), newY = y1 + relativeY * (y2 - y1)
292+
// This can be written as a matrix transformation:
293+
// [newX] = [(x2-x1) 0 ] [relativeX] + [x1]
294+
// [newY] [0 (y2-y1)] [relativeY] [y1]
295+
296+
const scaleX = x2 - x1
297+
const scaleY = y2 - y1
298+
const offsetX = x1
299+
const offsetY = y1
300+
301+
const matrix = [scaleX, 0, 0, scaleY] satisfies [a: number, b: number, c: number, d: number]
302+
const offset = [offsetX, offsetY] satisfies [x: number, y: number]
303+
304+
return { matrix, offset }
305+
}
306+
307+
function applyMatrixAndOffsetToPoint<ParentNumber extends PatternNumber>(
308+
matrix: [a: number, b: number, c: number, d: number],
309+
offset: [x: number, y: number],
310+
point: Point<ParentNumber>
311+
): Point<ParentNumber> {
312+
const [a, b, c, d] = matrix
313+
const [dx, dy] = offset
314+
const [x, y] = point
315+
316+
return [x * a + y * c + dx, x * b + y * d + dy] as Point<ParentNumber>
317+
}
318+
319+
export function applyMatrixAndOffsetToRectangle<ParentNumber extends PatternNumber>(
320+
matrix: [a: number, b: number, c: number, d: number],
321+
offset: [x: number, y: number],
322+
rectangle: Pattern<ParentNumber>
323+
): Pattern<ParentNumber> {
324+
const { anchor, target } = rectangle
325+
326+
return {
327+
anchor: applyMatrixAndOffsetToPoint(matrix, offset, anchor),
328+
target: applyMatrixAndOffsetToPoint(matrix, offset, target),
329+
}
330+
}
331+
332+
export function combineTransformations(
333+
matrix1: [a: number, b: number, c: number, d: number],
334+
offset1: [x: number, y: number],
335+
matrix2: [a: number, b: number, c: number, d: number],
336+
offset2: [x: number, y: number]
337+
): { matrix: [a: number, b: number, c: number, d: number]; offset: [x: number, y: number] } {
338+
const [a1, b1, c1, d1] = matrix1
339+
const [a2, b2, c2, d2] = matrix2
340+
const [x1, y1] = offset1
341+
const [x2, y2] = offset2
342+
343+
const matrix = [a1 * a2 + c1 * b2, b1 * a2 + d1 * b2, a1 * c2 + c1 * d2, b1 * c2 + d1 * d2] satisfies [
344+
a: number,
345+
b: number,
346+
c: number,
347+
d: number,
348+
]
349+
const offset = [a1 * x2 + c1 * y2 + x1, b1 * x2 + d1 * y2 + y1] satisfies [x: number, y: number]
350+
351+
return {
352+
matrix,
353+
offset,
354+
}
355+
}

src/preview.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useMemo, useState } from 'react'
2-
import { getBoundariesFromPattern } from './functions'
2+
import { applyMatrixAndOffsetToRectangle, getBoundariesFromPattern } from './functions'
33
import { NumberPair, PatternId, RelativePattern, RelativePoint, State } from './types'
44

55
function getViewBox(state: State): { x: number; y: number; width: number; height: number } {
@@ -9,7 +9,12 @@ function getViewBox(state: State): { x: number; y: number; width: number; height
99
let yMax = 1
1010

1111
// Make sure that default viewBox contains entire pattern
12-
for (const { pattern } of state.patterns) {
12+
for (const { matrix, offset } of state.patterns) {
13+
const pattern = applyMatrixAndOffsetToRectangle(matrix, offset, {
14+
anchor: [0, 0] as RelativePoint,
15+
target: [1, 1] as RelativePoint,
16+
})
17+
1318
const b = getBoundariesFromPattern(pattern)
1419

1520
xMin = Math.min(xMin, b.xMin)
@@ -231,7 +236,12 @@ export const Preview: React.FC<{
231236
/>
232237
<PartialLine anchor={[0, 0]} target={[1, 1]} />
233238

234-
{state.patterns.map(({ id, pattern }) => {
239+
{state.patterns.map(({ id, matrix, offset }) => {
240+
const pattern = applyMatrixAndOffsetToRectangle(matrix, offset, {
241+
anchor: [0, 0] as RelativePoint,
242+
target: [1, 1] as RelativePoint,
243+
})
244+
235245
const { xMin, xMax, yMin, yMax } = getBoundariesFromPattern(pattern)
236246

237247
const width = xMax - xMin

0 commit comments

Comments
 (0)