-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathquadtree.js
152 lines (144 loc) · 4.97 KB
/
quadtree.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
class QuadTreeNode {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.children = [];
this.oldX = x;
this.oldY = y;
this.oldSize = size;
this.oldChildren = [];
this.isSettled = true;
this.settleTime = 0;
this.settleTimeMax = 0.3;
}
animTo(x, y, size, time) {
this.x = x;
this.y = y;
this.size = size;
this.settleTimeMax = time;
this.settleTime = this.settleTimeMax;
this.isSettled = false;
return this;
}
attemptSplit() {
if (this.children.length === 0 && this.isSettled) {
const t = 0.24;
this.children = [
new QuadTreeNode(this.x, this.y, this.size).animTo(this.x, this.y, this.size / 2, t),
new QuadTreeNode(this.x, this.y, this.size).animTo(this.x + this.size / 2, this.y, this.size / 2, t),
new QuadTreeNode(this.x, this.y, this.size).animTo(this.x, this.y + this.size / 2, this.size / 2, t),
new QuadTreeNode(this.x, this.y, this.size).animTo(this.x + this.size / 2, this.y + this.size / 2, this.size / 2, t),
].filter(child => child.x <= canvas.width && child.y <= canvas.height);
return true;
}
return false;
}
attemptMerge() {
if (this.children.length !== 0) {
for (const child of this.children) {
if (child.children.length !== 0 || child.oldChildren.length !== 0) {
return false;
}
}
for (const child of this.children) {
child.animTo(this.x, this.y, this.size, 0.24);
}
this.oldChildren = this.children;
this.children = [];
return true;
}
return false;
}
draw(ctx, img) {
if (this.oldChildren.length !== 0) {
// Old children are animating
for (const child of this.oldChildren) {
child.draw(ctx, img);
}
if (this.oldChildren[0].settleTime <= 0) {
this.oldChildren = [];
}
} else if (!this.isSettled) {
// This node is animating
this.settleTime -= 0.016;
let t = 1 - this.settleTime / this.settleTimeMax;
t = easeInOutCubic(t)
let x = lerp(this.oldX, this.x, t);
let y = lerp(this.oldY, this.y, t);
let s = lerp(this.oldSize, this.size, t);
ctx.drawImage(img, img.width * x, img.height * y, img.width * s, img.height * s);
if (this.settleTime <= 0) {
this.isSettled = true;
this.oldX = this.x;
this.oldY = this.y;
this.oldSize = this.size;
}
} else if (this.children.length === 0) {
// This not is a leaf
ctx.drawImage(img, img.width * this.x, img.height * this.y, img.width * this.size, img.height * this.size);
} else {
// This node is a branch
for (const child of this.children) {
child.draw(ctx, img);
}
}
}
}
class Quadtree {
reset() {
this.tree = new QuadTreeNode(0, 0, Math.ceil(canvas.height / img.height));
this.ticks = 0;
this.splits = 0;
this.direction = "split";
}
draw(ctx, img) {
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
this.tree.draw(ctx, img);
if (this.ticks >= 4) {
this.ticks = 0;
if (this.direction === "split") {
this.splitRandom(this.tree);
} else {
this.mergeRandom(this.tree);
}
if (this.splits >= 150) {
this.direction = "merge";
} else if (this.splits <= 0) {
this.direction = "split";
}
}
this.ticks++;
}
splitRandom(tree) {
// Use random depth-first search to find a leaf node to split
if (tree.attemptSplit()) {
this.splits++;
return true;
} else {
let rand = Math.floor(Math.random() * tree.children.length);
for (let i = 0; i < tree.children.length; i++) {
if (this.splitRandom(tree.children[(rand + i) % tree.children.length])) {
return true;
}
}
return false;
}
}
mergeRandom(tree) {
// Use random depth-first search to find a node to merge
if (tree.attemptMerge()) {
this.splits--;
return true;
} else {
let rand = Math.floor(Math.random() * tree.children.length);
for (let i = 0; i < tree.children.length; i++) {
if (this.mergeRandom(tree.children[(rand + i) % tree.children.length])) {
return true;
}
}
return false;
}
}
}