-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
172 lines (148 loc) · 5.46 KB
/
index.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const vec3 = require('pex-math/Vec3')
const assert = require('assert')
// space colonization
// give me positions of buds and hormones and i'll make a tree for you
module.exports = function (opts) {
opts = opts || {}
// supplied data
assert.ok(Array.isArray(opts.buds), 'space-colonization: you need to supply bud positions as an array of vec3s')
assert.ok(Array.isArray(opts.hormones), 'space-colonization: you need to supply hormone positions as an array of vec3s')
let budPosArray = opts.buds
let hormPosArray = opts.hormones
// algorithm params
let deadZone = opts.deadZone || 0.1
let growthStep = opts.growthStep || 0.02
let splitChance = opts.splitChance || 0.4
let viewAngle = opts.viewAngle || 50
let straightSplit = opts.straightSplit || false
let branchAngle = opts.branchAngle || 30
let viewDistance = opts.viewDistance || 0.3
let growthDirection = opts.growthDirection || [0, 1, 0]
let growthBias = opts.growthBias || 0
let buds = budPosArray.map((budPos) => { return { state: 0, position: budPos } })
let hormones = hormPosArray.map((hormonePos) => { return { state: 0, position: hormonePos } })
// iterate function
return function (opts) {
opts = opts || {}
deadZone = opts.deadZone || deadZone
growthStep = opts.growthStep || growthStep
splitChance = opts.splitChance || splitChance
viewAngle = opts.viewAngle || viewAngle
branchAngle = opts.branchAngle || branchAngle
viewDistance = opts.viewDistance || viewDistance
growthDirection = opts.growthDirection || growthDirection
growthBias = opts.growthBias || growthBias
straightSplit = opts.straightSplit || straightSplit
growthDirection = vec3.normalize(growthDirection)
// if (opts.buds) {
// opts.buds.forEach((budPos) => {
// buds.push({ state: 0, position: budPos})
// })
// }
if (opts.hormones) {
opts.hormones.forEach((hormonePos) => {
hormones.push({ state: 0, position: hormonePos })
})
}
// find attractors
let attractors = []
for (let k = 0, length = buds.length; k < length; k++) {
attractors.push([])
}
for (let i = 0; i < hormones.length; i++) {
let hormone = hormones[i]
if (hormone.state !== 0) continue
let minDist = viewDistance
let minDistIndex = -1
for (let j = 0; j < buds.length; j++) {
let bud = buds[j]
if (bud.state > 0) continue
let dist = vec3.distance(hormone.position, bud.position)
if (bud.position.direction) {
let budPosDirNorm = vec3.normalize(vec3.copy(bud.position.direction))
let hormPosNorm = vec3.normalize(vec3.sub(vec3.copy(hormone.position), bud.position))
let dot = vec3.dot(budPosDirNorm, hormPosNorm)
let radians = Math.acos(dot)
let degrees = radians * (180 / Math.PI)
if (degrees > viewAngle * 2) {
continue
}
}
if (dist < minDist) {
minDist = dist
minDistIndex = j
}
}
if (minDistIndex === -1) continue
attractors[minDistIndex].push(i)
if (minDist < deadZone && minDistIndex !== -1) {
hormone.state++
}
}
// iterate over buds and grow/kill them
for (let i = 0, length = buds.length; i < length; i++) {
let bud = buds[i]
if (bud.state === 1) continue
if (attractors[i].length === 0) {
bud.state++
continue
}
let budPos = vec3.copy(bud.position)
// calculate the average vector of all attractors for bud
let avgPos = vec3.create()
for (let l = 0; l < attractors[i].length; l++) {
let hormone = hormones[attractors[i][l]]
vec3.add(avgPos, hormone.position)
}
const avgVec = vec3.scale(avgPos, 1 / attractors[i].length)
// split at random
let didSplit = false
if (Math.random() > (1.0 - splitChance)) {
// make new branch
let dir = vec3.sub(vec3.copy(avgVec), budPos)
vec3.scale(vec3.normalize(dir), growthStep)
let sinBranchAngle = Math.sin((-branchAngle / 2) * (Math.PI / 180))
let cosBranchAngle = Math.cos((-branchAngle / 2) * (Math.PI / 180))
dir[0] = dir[0] * cosBranchAngle + dir[1] * sinBranchAngle
dir[1] = -(dir[0] * sinBranchAngle) + dir[1] * cosBranchAngle
let nextPos = vec3.add(vec3.copy(budPos), dir)
nextPos.direction = dir
buds.push({
state: 0,
position: nextPos,
parent: bud
})
didSplit = true
}
// find next position for bud
let dir = vec3.sub(vec3.copy(avgVec), budPos)
// global bias
vec3.lerp(vec3.normalize(dir), vec3.normalize(growthDirection), growthBias)
vec3.scale(vec3.normalize(dir), growthStep)
if (didSplit && !straightSplit) {
let sinBranchAngle = Math.sin(branchAngle * (Math.PI / 180))
let cosBranchAngle = Math.cos(branchAngle * (Math.PI / 180))
dir[0] = dir[0] * cosBranchAngle + dir[1] * sinBranchAngle
dir[1] = -(dir[1] * sinBranchAngle) + dir[1] * cosBranchAngle
}
let nextPos = vec3.add(vec3.copy(budPos), dir)
nextPos.direction = dir
bud.state++
buds.push({
state: 0,
position: nextPos,
parent: bud
})
}
return {
buds: buds.map((bud) => {
return {
'state': bud.state,
'position': bud.position,
'parentIndex': buds.indexOf(bud.parent) || -1
}
}),
hormones: hormones
}
}
}