Skip to content

Commit 713ca14

Browse files
committed
Use closure for boundaries in isValidPattern
1 parent a50505a commit 713ca14

2 files changed

Lines changed: 117 additions & 64 deletions

File tree

src/draw.ts

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ const patternQueue = new Queue<QueueEntry>({
5454
size: MAX_QUEUE_SIZE,
5555
})
5656

57+
const isValidPattern = (() => {
58+
// Optimization: Only allocate boundaries object once and mutate it in place.
59+
// Make sure to update this object before using it!
60+
const patternBoundaries: Boundaries<ViewportNumber> = {
61+
xMin: 0 as ViewportNumber,
62+
xMax: 0 as ViewportNumber,
63+
yMin: 0 as ViewportNumber,
64+
yMax: 0 as ViewportNumber,
65+
}
66+
67+
return function (pattern: ViewportPattern, viewportBoundaries: Boundaries<ViewportNumber>): boolean {
68+
mutateBoundariesFromPattern(pattern, patternBoundaries)
69+
70+
return (
71+
// Pattern fulfills minimum size.
72+
patternBoundaries.xMax - patternBoundaries.xMin >= MIN_PATTERN_SIZE_PX &&
73+
patternBoundaries.yMax - patternBoundaries.yMin >= MIN_PATTERN_SIZE_PX &&
74+
// X and Y ranges overlap with the valid boundaries.
75+
// We will only render patterns if at least one corner is inside the valid boundaries.
76+
patternBoundaries.xMin <= viewportBoundaries.xMax &&
77+
patternBoundaries.xMax >= viewportBoundaries.xMin &&
78+
patternBoundaries.yMin <= viewportBoundaries.yMax &&
79+
patternBoundaries.yMax >= viewportBoundaries.yMin
80+
)
81+
}
82+
})()
83+
5784
/**
5885
* Creates a generator that yields drawable patterns one by one.
5986
*
@@ -87,31 +114,6 @@ function* streamDrawablePatterns({
87114
})
88115
}
89116

90-
// Optimization: Only allocate boundaries object once and mutate it in place.
91-
// Make sure to update this object before using it!
92-
const patternBoundaries: Boundaries<ViewportNumber> = {
93-
xMin: 0 as ViewportNumber,
94-
xMax: 0 as ViewportNumber,
95-
yMin: 0 as ViewportNumber,
96-
yMax: 0 as ViewportNumber,
97-
}
98-
99-
function isValidPattern(pattern: ViewportPattern): boolean {
100-
mutateBoundariesFromPattern(pattern, patternBoundaries)
101-
102-
return (
103-
// Pattern fulfills minimum size.
104-
patternBoundaries.xMax - patternBoundaries.xMin >= MIN_PATTERN_SIZE_PX &&
105-
patternBoundaries.yMax - patternBoundaries.yMin >= MIN_PATTERN_SIZE_PX &&
106-
// X and Y ranges overlap with the valid boundaries.
107-
// We will only render patterns if at least one corner is inside the valid boundaries.
108-
patternBoundaries.xMin <= viewportBoundaries.xMax &&
109-
patternBoundaries.xMax >= viewportBoundaries.xMin &&
110-
patternBoundaries.yMin <= viewportBoundaries.yMax &&
111-
patternBoundaries.yMax >= viewportBoundaries.yMin
112-
)
113-
}
114-
115117
let iterations = 0
116118
while (patternQueue.size > 0) {
117119
iterations += 1
@@ -129,11 +131,12 @@ function* streamDrawablePatterns({
129131
}
130132

131133
for (const pattern of state.patterns) {
132-
const viewportPattern = combinePatterns(entry.currentPattern, pattern)
134+
// Get new pattern
135+
const newViewportPattern = combinePatterns(entry.currentPattern, pattern)
133136

134-
if (isValidPattern(viewportPattern)) {
137+
if (isValidPattern(newViewportPattern, viewportBoundaries)) {
135138
patternQueue.push({
136-
currentPattern: viewportPattern,
139+
currentPattern: newViewportPattern,
137140
depth: entry.depth + 1,
138141
})
139142

src/functions.ts

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,46 @@ import {
1010
RelativePoint,
1111
Size,
1212
State,
13+
ViewportNumber,
1314
ViewportPattern,
14-
ViewportPoint
15+
ViewportPoint,
1516
} from './types'
1617

17-
function minPatternNumber<N extends PatternNumber>(a: N, b: N): N {
18-
return Math.min(a, b) as N
19-
}
20-
21-
function maxPatternNumber<N extends PatternNumber>(a: N, b: N): N {
22-
return Math.max(a, b) as N
23-
}
24-
2518
/**
2619
* This function mutates the boundaries object in place.
27-
*
20+
*
2821
* This is a performance optimization to avoid creating new objects, since this function is called very frequently
2922
* during rendering.
3023
*/
31-
export function mutateBoundariesFromPattern<N extends PatternNumber>(pattern: Pattern<N>, boundaries: Boundaries<N>): void {
32-
const [x1, y1] = pattern.anchor
33-
const [x2, y2] = pattern.target
34-
35-
boundaries.xMin = minPatternNumber(x1, x2)
36-
boundaries.xMax = maxPatternNumber(x1, x2)
37-
boundaries.yMin = minPatternNumber(y1, y2)
38-
boundaries.yMax = maxPatternNumber(y1, y2)
39-
}
40-
24+
export const mutateBoundariesFromPattern = (() => {
25+
let x1: PatternNumber
26+
let y1: PatternNumber
27+
let x2: PatternNumber
28+
let y2: PatternNumber
29+
30+
return function <N extends PatternNumber>(pattern: Pattern<N>, boundaries: Boundaries<N>): void {
31+
x1 = pattern.anchor[0]
32+
y1 = pattern.anchor[1]
33+
x2 = pattern.target[0]
34+
y2 = pattern.target[1]
35+
36+
boundaries.xMin = Math.min(x1, x2) as N
37+
boundaries.xMax = Math.max(x1, x2) as N
38+
boundaries.yMin = Math.min(y1, y2) as N
39+
boundaries.yMax = Math.max(y1, y2) as N
40+
}
41+
})()
4142

42-
const getBoundariesFromPattern = <N extends PatternNumber>(pattern: Pattern<N>): Boundaries<N>=> {
43+
const getBoundariesFromPattern = <N extends PatternNumber>(pattern: Pattern<N>): Boundaries<N> => {
4344
const obj: Boundaries<N> = { xMin: 0 as N, xMax: 0 as N, yMin: 0 as N, yMax: 0 as N }
4445
mutateBoundariesFromPattern(pattern, obj)
4546
return obj
4647
}
4748

48-
49-
const pointIsInBoundaries = <N extends PatternNumber>(point: Point<N>, boundaries: Boundaries<N>): boolean => {
49+
const pointIsInBoundaries = <N extends PatternNumber>(
50+
point: Point<N>,
51+
boundaries: Boundaries<N>
52+
): boolean => {
5053
const { xMin, xMax, yMin, yMax } = boundaries
5154
const [pointX, pointY] = point
5255

@@ -61,10 +64,9 @@ const mapPointToViewportSpace = (
6164
[x, y]: AbsolutePoint,
6265
[viewportWidth, viewportHeight]: Size
6366
): ViewportPoint => {
64-
return [Math.round(x * viewportWidth), Math.round(y * viewportHeight)] satisfies NumberPair as ViewportPoint
67+
return [x * viewportWidth, y * viewportHeight] satisfies NumberPair as ViewportPoint
6568
}
6669

67-
6870
// ts-unused-exports:disable-next-line
6971
export const mapPatternToViewportSpace = (pattern: AbsolutePattern, screenSize: Size): ViewportPattern => ({
7072
anchor: mapPointToViewportSpace(pattern.anchor, screenSize),
@@ -93,7 +95,10 @@ export const mapPointFromViewportSpace = (
9395
return [x / viewportWidth, y / viewportHeight] satisfies NumberPair as AbsolutePoint
9496
}
9597

96-
function getRelativePointPosition(point: RelativePoint, pattern: AbsolutePattern | RelativePattern): RelativePoint {
98+
function getRelativePointPosition(
99+
point: RelativePoint,
100+
pattern: AbsolutePattern | RelativePattern
101+
): RelativePoint {
97102
const [x1, y1] = pattern.anchor
98103
const [x2, y2] = pattern.target
99104
const [x, y] = point
@@ -104,22 +109,50 @@ function getRelativePointPosition(point: RelativePoint, pattern: AbsolutePattern
104109
return [relativeX, relativeY] satisfies NumberPair as RelativePoint
105110
}
106111

107-
export function getRelativePatternPosition(pattern: RelativePattern, basePattern: AbsolutePattern | RelativePattern): RelativePattern {
112+
export function getRelativePatternPosition(
113+
pattern: RelativePattern,
114+
basePattern: AbsolutePattern | RelativePattern
115+
): RelativePattern {
108116
return {
109117
anchor: getRelativePointPosition(pattern.anchor, basePattern),
110118
target: getRelativePointPosition(pattern.target, basePattern),
111119
}
112120
}
113121

114-
const resolveRelativePointPosition = <N extends PatternNumber>(relativePoint: RelativePoint, pattern: Pattern<N>): Point<N> => {
115-
const [x1, y1] = pattern.anchor
116-
const [x2, y2] = pattern.target
117-
const [x, y] = relativePoint
122+
const resolveRelativePointPositionInPlace = (() => {
123+
let x1: PatternNumber
124+
let y1: PatternNumber
125+
let x2: PatternNumber
126+
let y2: PatternNumber
127+
let x: PatternNumber
128+
let y: PatternNumber
129+
130+
return function <N extends PatternNumber>(
131+
relativePoint: RelativePoint,
132+
pattern: Pattern<N>,
133+
newPoint: Point<N>
134+
): void {
135+
x1 = pattern.anchor[0]
136+
y1 = pattern.anchor[1]
137+
x2 = pattern.target[0]
138+
y2 = pattern.target[1]
139+
x = relativePoint[0]
140+
y = relativePoint[1]
141+
142+
newPoint[0] = (x1 + x * (x2 - x1)) as N
143+
newPoint[1] = (y1 + y * (y2 - y1)) as N
144+
}
145+
})()
118146

119-
const resolvedX = x1 + x * (x2 - x1)
120-
const resolvedY = y1 + y * (y2 - y1)
147+
const resolveRelativePointPosition = <N extends PatternNumber>(
148+
relativePoint: RelativePoint,
149+
pattern: Pattern<N>
150+
): Point<N> => {
151+
const newPoint = [0, 0] satisfies NumberPair as Point<N>
152+
153+
resolveRelativePointPositionInPlace(relativePoint, pattern, newPoint)
121154

122-
return [resolvedX, resolvedY] satisfies NumberPair as Point<N>
155+
return newPoint
123156
}
124157

125158
export const getScreenSize = (ctx: CanvasRenderingContext2D): Size => [ctx.canvas.width, ctx.canvas.height]
@@ -133,11 +166,28 @@ export const getMousePoint = (
133166
return mapPointFromViewportSpace(mousePosition, getScreenSize(ctx))
134167
}
135168

136-
export function combinePatterns<ParentNumber extends PatternNumber>(parent: Pattern<ParentNumber>, child: RelativePattern): Pattern<ParentNumber> {
169+
export function combinePatterns<ParentNumber extends PatternNumber>(
170+
parent: Pattern<ParentNumber>,
171+
child: RelativePattern
172+
): Pattern<ParentNumber> {
137173
return {
138174
anchor: resolveRelativePointPosition(child.anchor, parent),
139175
target: resolveRelativePointPosition(child.target, parent),
140-
}
176+
}
177+
}
178+
179+
/**
180+
* Rounds coordinates of the pattern to the nearest integer. NOTE: Mutates the pattern in place.
181+
*
182+
* This is a performance optimization to avoid anti-aliasing.
183+
*/
184+
// ts-unused-exports:disable-next-line
185+
export function roundViewportPatternInPlace(pattern: ViewportPattern): ViewportPattern {
186+
pattern.anchor[0] = Math.round(pattern.anchor[0]) as ViewportNumber
187+
pattern.anchor[1] = Math.round(pattern.anchor[1]) as ViewportNumber
188+
pattern.target[0] = Math.round(pattern.target[0]) as ViewportNumber
189+
pattern.target[1] = Math.round(pattern.target[1]) as ViewportNumber
190+
return pattern
141191
}
142192

143193
type NestedPath = number[]

0 commit comments

Comments
 (0)