@quenty/octree 11.8.1 → 11.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/package.json +4 -4
- package/src/Shared/Octree.lua +28 -20
- package/src/Shared/OctreeRegionUtils.lua +169 -100
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [11.8.2](https://github.com/Quenty/NevermoreEngine/compare/@quenty/octree@11.8.1...@quenty/octree@11.8.2) (2025-04-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Add types to packages ([2374fb2](https://github.com/Quenty/NevermoreEngine/commit/2374fb2b043cfbe0e9b507b3316eec46a4e353a0))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## [11.8.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/octree@11.8.0...@quenty/octree@11.8.1) (2025-03-21)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @quenty/octree
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/octree",
|
|
3
|
-
"version": "11.8.
|
|
3
|
+
"version": "11.8.2",
|
|
4
4
|
"description": "Octree implementation for Roblox - fast spatial queries",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"Quenty"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@quenty/draw": "^7.8.
|
|
30
|
-
"@quenty/loader": "^10.8.
|
|
29
|
+
"@quenty/draw": "^7.8.2",
|
|
30
|
+
"@quenty/loader": "^10.8.1"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "78c3ac0ab08dd18085b6e6e6e4f745e76ed99f68"
|
|
36
36
|
}
|
package/src/Shared/Octree.lua
CHANGED
|
@@ -87,10 +87,10 @@ end
|
|
|
87
87
|
function Octree:GetAllNodes()
|
|
88
88
|
local options = {}
|
|
89
89
|
|
|
90
|
-
for _, regionList in
|
|
91
|
-
for _, region in
|
|
92
|
-
for node, _ in
|
|
93
|
-
options[#options+1] = node
|
|
90
|
+
for _, regionList in self._regionHashMap do
|
|
91
|
+
for _, region in regionList do
|
|
92
|
+
for node, _ in region.nodes do
|
|
93
|
+
options[#options + 1] = node
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -116,7 +116,7 @@ end
|
|
|
116
116
|
@param object T
|
|
117
117
|
@return OctreeNode<T>
|
|
118
118
|
]=]
|
|
119
|
-
function Octree:CreateNode(position, object)
|
|
119
|
+
function Octree:CreateNode(position: Vector3, object: any)
|
|
120
120
|
assert(typeof(position) == "Vector3", "Bad position value")
|
|
121
121
|
assert(object, "Bad object value")
|
|
122
122
|
|
|
@@ -144,11 +144,11 @@ end
|
|
|
144
144
|
@return { T } -- Objects found
|
|
145
145
|
@return { number } -- Distances squared
|
|
146
146
|
]=]
|
|
147
|
-
function Octree:RadiusSearch(position, radius)
|
|
147
|
+
function Octree:RadiusSearch(position: Vector3, radius: number)
|
|
148
148
|
assert(typeof(position) == "Vector3", "Bad position")
|
|
149
149
|
assert(type(radius) == "number", "Bad radius")
|
|
150
150
|
|
|
151
|
-
local px, py, pz = position.
|
|
151
|
+
local px, py, pz = position.X, position.Y, position.Z
|
|
152
152
|
return self:_radiusSearch(px, py, pz, radius)
|
|
153
153
|
end
|
|
154
154
|
|
|
@@ -164,18 +164,18 @@ end
|
|
|
164
164
|
@return { any } -- Objects found
|
|
165
165
|
@return { number } -- Distances squared
|
|
166
166
|
]=]
|
|
167
|
-
function Octree:KNearestNeighborsSearch(position, k, radius)
|
|
167
|
+
function Octree:KNearestNeighborsSearch(position: Vector3, k: number, radius: number)
|
|
168
168
|
assert(typeof(position) == "Vector3", "Bad position")
|
|
169
169
|
assert(type(radius) == "number", "Bad radius")
|
|
170
170
|
|
|
171
|
-
local px, py, pz = position.
|
|
171
|
+
local px, py, pz = position.X, position.Y, position.Z
|
|
172
172
|
local objects, nodeDistances2 = self:_radiusSearch(px, py, pz, radius)
|
|
173
173
|
|
|
174
174
|
local sortable = {}
|
|
175
|
-
for index, dist2 in
|
|
175
|
+
for index, dist2 in nodeDistances2 do
|
|
176
176
|
table.insert(sortable, {
|
|
177
|
-
dist2 = dist2
|
|
178
|
-
index = index
|
|
177
|
+
dist2 = dist2,
|
|
178
|
+
index = index,
|
|
179
179
|
})
|
|
180
180
|
end
|
|
181
181
|
|
|
@@ -203,28 +203,36 @@ end
|
|
|
203
203
|
@param pz number
|
|
204
204
|
@return OctreeSubregion
|
|
205
205
|
]=]
|
|
206
|
-
function Octree:GetOrCreateLowestSubRegion(px, py, pz)
|
|
206
|
+
function Octree:GetOrCreateLowestSubRegion(px: number, py: number, pz: number)
|
|
207
207
|
local region = self:_getOrCreateRegion(px, py, pz)
|
|
208
208
|
return OctreeRegionUtils.getOrCreateSubRegionAtDepth(region, px, py, pz, self._maxDepth)
|
|
209
209
|
end
|
|
210
210
|
|
|
211
|
-
function Octree:_radiusSearch(px, py, pz, radius)
|
|
211
|
+
function Octree:_radiusSearch(px: number, py: number, pz: number, radius: number)
|
|
212
212
|
local objectsFound = {}
|
|
213
213
|
local nodeDistances2 = {}
|
|
214
214
|
|
|
215
215
|
local diameter = self._maxRegionSize[1]
|
|
216
216
|
local searchRadiusSquared = OctreeRegionUtils.getSearchRadiusSquared(radius, diameter, EPSILON)
|
|
217
217
|
|
|
218
|
-
for _, regionList in
|
|
219
|
-
for _, region in
|
|
218
|
+
for _, regionList in self._regionHashMap do
|
|
219
|
+
for _, region in regionList do
|
|
220
220
|
local rpos = region.position
|
|
221
221
|
local rpx, rpy, rpz = rpos[1], rpos[2], rpos[3]
|
|
222
222
|
local ox, oy, oz = px - rpx, py - rpy, pz - rpz
|
|
223
|
-
local dist2 = ox*ox + oy*oy + oz*oz
|
|
223
|
+
local dist2 = ox * ox + oy * oy + oz * oz
|
|
224
224
|
|
|
225
225
|
if dist2 <= searchRadiusSquared then
|
|
226
226
|
OctreeRegionUtils.getNeighborsWithinRadius(
|
|
227
|
-
region,
|
|
227
|
+
region,
|
|
228
|
+
radius,
|
|
229
|
+
px,
|
|
230
|
+
py,
|
|
231
|
+
pz,
|
|
232
|
+
objectsFound,
|
|
233
|
+
nodeDistances2,
|
|
234
|
+
self._maxDepth
|
|
235
|
+
)
|
|
228
236
|
end
|
|
229
237
|
end
|
|
230
238
|
end
|
|
@@ -232,11 +240,11 @@ function Octree:_radiusSearch(px, py, pz, radius)
|
|
|
232
240
|
return objectsFound, nodeDistances2
|
|
233
241
|
end
|
|
234
242
|
|
|
235
|
-
function Octree:_getRegion(px, py, pz)
|
|
243
|
+
function Octree:_getRegion(px: number, py: number, pz: number)
|
|
236
244
|
return OctreeRegionUtils.findRegion(self._regionHashMap, self._maxRegionSize, px, py, pz)
|
|
237
245
|
end
|
|
238
246
|
|
|
239
|
-
function Octree:_getOrCreateRegion(px, py, pz)
|
|
247
|
+
function Octree:_getOrCreateRegion(px: number, py: number, pz: number)
|
|
240
248
|
return OctreeRegionUtils.getOrCreateRegion(self._regionHashMap, self._maxRegionSize, px, py, pz)
|
|
241
249
|
end
|
|
242
250
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
--!native
|
|
2
|
+
--!strict
|
|
2
3
|
--[=[
|
|
3
4
|
Octree implementation utilities. Primarily this utility code
|
|
4
5
|
should not be used directly and should be considered private to
|
|
@@ -14,39 +15,20 @@ local require = require(script.Parent.loader).load(script)
|
|
|
14
15
|
local Draw = require("Draw")
|
|
15
16
|
|
|
16
17
|
local EPSILON = 1e-6
|
|
17
|
-
local SQRT_3_OVER_2 = math.sqrt(3)/2
|
|
18
|
+
local SQRT_3_OVER_2 = math.sqrt(3) / 2
|
|
18
19
|
local SUB_REGION_POSITION_OFFSET = {
|
|
19
|
-
{ 0.25, 0.25, -0.25 }
|
|
20
|
-
{ -0.25, 0.25, -0.25 }
|
|
21
|
-
{ 0.25, 0.25, 0.25 }
|
|
22
|
-
{ -0.25, 0.25, 0.25 }
|
|
23
|
-
{ 0.25, -0.25, -0.25 }
|
|
24
|
-
{ -0.25, -0.25, -0.25 }
|
|
25
|
-
{ 0.25, -0.25, 0.25 }
|
|
26
|
-
{ -0.25, -0.25, 0.25 }
|
|
20
|
+
{ 0.25, 0.25, -0.25 },
|
|
21
|
+
{ -0.25, 0.25, -0.25 },
|
|
22
|
+
{ 0.25, 0.25, 0.25 },
|
|
23
|
+
{ -0.25, 0.25, 0.25 },
|
|
24
|
+
{ 0.25, -0.25, -0.25 },
|
|
25
|
+
{ -0.25, -0.25, -0.25 },
|
|
26
|
+
{ 0.25, -0.25, 0.25 },
|
|
27
|
+
{ -0.25, -0.25, 0.25 },
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
local OctreeRegionUtils = {}
|
|
30
31
|
|
|
31
|
-
--[=[
|
|
32
|
-
Visualizes the octree region.
|
|
33
|
-
|
|
34
|
-
@param region OctreeRegion<T>
|
|
35
|
-
@return MaidTask
|
|
36
|
-
]=]
|
|
37
|
-
function OctreeRegionUtils.visualize(region)
|
|
38
|
-
local size = region.size
|
|
39
|
-
local position = region.position
|
|
40
|
-
local sx, sy, sz = size[1], size[2], size[3]
|
|
41
|
-
local px, py, pz = position[1], position[2], position[3]
|
|
42
|
-
|
|
43
|
-
local box = Draw.box(Vector3.new(px, py, pz), Vector3.new(sx, sy, sz))
|
|
44
|
-
box.Transparency = 0.9
|
|
45
|
-
box.Name = "OctreeRegion_" .. tostring(region.depth)
|
|
46
|
-
|
|
47
|
-
return box
|
|
48
|
-
end
|
|
49
|
-
|
|
50
32
|
--[=[
|
|
51
33
|
A Vector3 equivalent for octrees. This type is primarily internal and
|
|
52
34
|
used for faster access than a Vector3.
|
|
@@ -54,6 +36,12 @@ end
|
|
|
54
36
|
@type OctreeVector3 { [1]: number, [2]: number, [3]: number }
|
|
55
37
|
@within OctreeRegionUtils
|
|
56
38
|
]=]
|
|
39
|
+
export type OctreeVector3 = { number }
|
|
40
|
+
|
|
41
|
+
export type OctreeNode<T> = {
|
|
42
|
+
GetRawPosition: (self: OctreeNode<T>) -> (number, number, number),
|
|
43
|
+
GetObject: (self: OctreeNode<T>) -> T,
|
|
44
|
+
}
|
|
57
45
|
|
|
58
46
|
--[=[
|
|
59
47
|
An internal region which stores the data.
|
|
@@ -71,6 +59,39 @@ end
|
|
|
71
59
|
.node_count number
|
|
72
60
|
@within OctreeRegionUtils
|
|
73
61
|
]=]
|
|
62
|
+
export type OctreeRegion<T> = {
|
|
63
|
+
subRegions: { OctreeRegion<T> },
|
|
64
|
+
lowerBounds: OctreeVector3,
|
|
65
|
+
upperBounds: OctreeVector3,
|
|
66
|
+
position: OctreeVector3,
|
|
67
|
+
size: OctreeVector3,
|
|
68
|
+
parent: OctreeRegion<T>?,
|
|
69
|
+
parentIndex: number?,
|
|
70
|
+
depth: number,
|
|
71
|
+
nodes: { [OctreeNode<T>]: OctreeNode<T> },
|
|
72
|
+
node_count: number,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type OctreeRegionHashMap<T> = { [number]: { OctreeRegion<T> } }
|
|
76
|
+
|
|
77
|
+
--[=[
|
|
78
|
+
Visualizes the octree region.
|
|
79
|
+
|
|
80
|
+
@param region OctreeRegion<T>
|
|
81
|
+
@return MaidTask
|
|
82
|
+
]=]
|
|
83
|
+
function OctreeRegionUtils.visualize<T>(region: OctreeRegion<T>): Instance
|
|
84
|
+
local size = region.size
|
|
85
|
+
local position = region.position
|
|
86
|
+
local sx, sy, sz = size[1], size[2], size[3]
|
|
87
|
+
local px, py, pz = position[1], position[2], position[3]
|
|
88
|
+
|
|
89
|
+
local box = Draw.box(Vector3.new(px, py, pz), Vector3.new(sx, sy, sz))
|
|
90
|
+
box.Transparency = 0.9
|
|
91
|
+
box.Name = "OctreeRegion_" .. tostring(region.depth)
|
|
92
|
+
|
|
93
|
+
return box
|
|
94
|
+
end
|
|
74
95
|
|
|
75
96
|
--[=[
|
|
76
97
|
Creates a new OctreeRegion<T>
|
|
@@ -85,8 +106,17 @@ end
|
|
|
85
106
|
@param parentIndex number?
|
|
86
107
|
@return OctreeRegion<T>
|
|
87
108
|
]=]
|
|
88
|
-
function OctreeRegionUtils.create(
|
|
89
|
-
|
|
109
|
+
function OctreeRegionUtils.create<T>(
|
|
110
|
+
px: number,
|
|
111
|
+
py: number,
|
|
112
|
+
pz: number,
|
|
113
|
+
sx: number,
|
|
114
|
+
sy: number,
|
|
115
|
+
sz: number,
|
|
116
|
+
parent: OctreeRegion<T>?,
|
|
117
|
+
parentIndex: number?
|
|
118
|
+
): OctreeRegion<T>
|
|
119
|
+
local hsx, hsy, hsz = sx / 2, sy / 2, sz / 2
|
|
90
120
|
|
|
91
121
|
local region = {
|
|
92
122
|
subRegions = {
|
|
@@ -98,16 +128,16 @@ function OctreeRegionUtils.create(px, py, pz, sx, sy, sz, parent, parentIndex)
|
|
|
98
128
|
--bottomNorthWest
|
|
99
129
|
--bottomSouthEast
|
|
100
130
|
--bottomSouthWest
|
|
101
|
-
}
|
|
102
|
-
lowerBounds = { px - hsx, py - hsy, pz - hsz }
|
|
103
|
-
upperBounds = { px + hsx, py + hsy, pz + hsz }
|
|
104
|
-
position = { px, py, pz }
|
|
105
|
-
size = { sx, sy, sz }
|
|
106
|
-
parent = parent
|
|
107
|
-
depth = parent and (parent.depth + 1) or 1
|
|
108
|
-
parentIndex = parentIndex
|
|
109
|
-
nodes = {}
|
|
110
|
-
node_count = 0
|
|
131
|
+
},
|
|
132
|
+
lowerBounds = { px - hsx, py - hsy, pz - hsz },
|
|
133
|
+
upperBounds = { px + hsx, py + hsy, pz + hsz },
|
|
134
|
+
position = { px, py, pz },
|
|
135
|
+
size = { sx, sy, sz }, -- { sx, sy, sz }
|
|
136
|
+
parent = parent,
|
|
137
|
+
depth = parent and (parent.depth + 1) or 1,
|
|
138
|
+
parentIndex = parentIndex,
|
|
139
|
+
nodes = {}, -- [node] = true (contains subchild nodes too)
|
|
140
|
+
node_count = 0,
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
-- if region.depth >= 5 then
|
|
@@ -122,16 +152,16 @@ end
|
|
|
122
152
|
@param lowestSubregion OctreeRegion<T>
|
|
123
153
|
@param node OctreeNode
|
|
124
154
|
]=]
|
|
125
|
-
function OctreeRegionUtils.addNode(lowestSubregion
|
|
155
|
+
function OctreeRegionUtils.addNode<T>(lowestSubregion: OctreeRegion<T>, node: OctreeNode<T>)
|
|
126
156
|
assert(node, "Bad node")
|
|
127
157
|
|
|
128
|
-
local current = lowestSubregion
|
|
158
|
+
local current: OctreeRegion<T> = lowestSubregion
|
|
129
159
|
while current do
|
|
130
160
|
if not current.nodes[node] then
|
|
131
|
-
current.nodes[node] = node
|
|
161
|
+
current.nodes[node] = node :: OctreeNode<T>
|
|
132
162
|
current.node_count = current.node_count + 1
|
|
133
163
|
end
|
|
134
|
-
current = current.parent
|
|
164
|
+
current = current.parent :: OctreeRegion<T>
|
|
135
165
|
end
|
|
136
166
|
end
|
|
137
167
|
|
|
@@ -142,12 +172,12 @@ end
|
|
|
142
172
|
@param toLowest OctreeRegion<T>
|
|
143
173
|
@param node OctreeNode
|
|
144
174
|
]=]
|
|
145
|
-
function OctreeRegionUtils.moveNode(fromLowest
|
|
175
|
+
function OctreeRegionUtils.moveNode<T>(fromLowest: OctreeRegion<T>, toLowest: OctreeRegion<T>, node: OctreeNode<T>)
|
|
146
176
|
assert(fromLowest.depth == toLowest.depth, "fromLowest.depth ~= toLowest.depth")
|
|
147
177
|
assert(fromLowest ~= toLowest, "fromLowest == toLowest")
|
|
148
178
|
|
|
149
|
-
local currentFrom = fromLowest
|
|
150
|
-
local currentTo = toLowest
|
|
179
|
+
local currentFrom: OctreeRegion<T> = fromLowest
|
|
180
|
+
local currentTo: OctreeRegion<T> = toLowest
|
|
151
181
|
while currentFrom ~= currentTo do
|
|
152
182
|
-- remove from current
|
|
153
183
|
do
|
|
@@ -155,13 +185,14 @@ function OctreeRegionUtils.moveNode(fromLowest, toLowest, node)
|
|
|
155
185
|
assert(currentFrom.node_count > 0, "No nodes in currentFrom")
|
|
156
186
|
|
|
157
187
|
currentFrom.nodes[node] = nil
|
|
158
|
-
currentFrom.node_count
|
|
188
|
+
currentFrom.node_count -= 1
|
|
159
189
|
|
|
160
190
|
-- remove subregion!
|
|
161
|
-
|
|
191
|
+
local parentIndex = currentFrom.parentIndex
|
|
192
|
+
if currentFrom.node_count <= 0 and parentIndex then
|
|
162
193
|
assert(currentFrom.parent, "Bad currentFrom.parent")
|
|
163
|
-
assert(currentFrom.parent.subRegions[
|
|
164
|
-
currentFrom.parent.subRegions[
|
|
194
|
+
assert(currentFrom.parent.subRegions[parentIndex] == currentFrom, "Not in subregion")
|
|
195
|
+
currentFrom.parent.subRegions[parentIndex] = nil
|
|
165
196
|
end
|
|
166
197
|
end
|
|
167
198
|
|
|
@@ -172,8 +203,8 @@ function OctreeRegionUtils.moveNode(fromLowest, toLowest, node)
|
|
|
172
203
|
currentTo.node_count = currentTo.node_count + 1
|
|
173
204
|
end
|
|
174
205
|
|
|
175
|
-
currentFrom = currentFrom.parent
|
|
176
|
-
currentTo = currentTo.parent
|
|
206
|
+
currentFrom = currentFrom.parent :: OctreeRegion<T>
|
|
207
|
+
currentTo = currentTo.parent :: OctreeRegion<T>
|
|
177
208
|
end
|
|
178
209
|
end
|
|
179
210
|
|
|
@@ -183,29 +214,29 @@ end
|
|
|
183
214
|
@param lowestSubregion OctreeRegion<T>
|
|
184
215
|
@param node OctreeNode
|
|
185
216
|
]=]
|
|
186
|
-
function OctreeRegionUtils.removeNode(lowestSubregion
|
|
217
|
+
function OctreeRegionUtils.removeNode<T>(lowestSubregion: OctreeRegion<T>, node: OctreeNode<T>)
|
|
187
218
|
assert(node, "Bad node")
|
|
188
219
|
|
|
189
|
-
local current = lowestSubregion
|
|
220
|
+
local current: OctreeRegion<T> = lowestSubregion
|
|
190
221
|
while current do
|
|
191
222
|
assert(current.nodes[node], "Not in current")
|
|
192
223
|
assert(current.node_count > 0, "Current has bad node count")
|
|
193
224
|
|
|
194
225
|
current.nodes[node] = nil
|
|
195
|
-
current.node_count
|
|
226
|
+
current.node_count -= 1
|
|
196
227
|
|
|
197
228
|
-- remove subregion!
|
|
198
|
-
|
|
229
|
+
local parentIndex = current.parentIndex
|
|
230
|
+
if current.node_count <= 0 and parentIndex then
|
|
199
231
|
assert(current.parent, "No parent")
|
|
200
|
-
assert(current.parent.subRegions[
|
|
201
|
-
current.parent.subRegions[
|
|
232
|
+
assert(current.parent.subRegions[parentIndex] == current, "Not in subregion")
|
|
233
|
+
current.parent.subRegions[parentIndex] = nil
|
|
202
234
|
end
|
|
203
235
|
|
|
204
|
-
current = current.parent
|
|
236
|
+
current = current.parent :: OctreeRegion<T>
|
|
205
237
|
end
|
|
206
238
|
end
|
|
207
239
|
|
|
208
|
-
|
|
209
240
|
--[=[
|
|
210
241
|
Retrieves the search radius for a given radius given the region
|
|
211
242
|
diameter
|
|
@@ -215,10 +246,10 @@ end
|
|
|
215
246
|
@param epsilon number
|
|
216
247
|
@return number
|
|
217
248
|
]=]
|
|
218
|
-
function OctreeRegionUtils.getSearchRadiusSquared(radius, diameter, epsilon)
|
|
219
|
-
local diagonal = SQRT_3_OVER_2*diameter
|
|
249
|
+
function OctreeRegionUtils.getSearchRadiusSquared(radius: number, diameter: number, epsilon: number): number
|
|
250
|
+
local diagonal = SQRT_3_OVER_2 * diameter
|
|
220
251
|
local searchRadius = radius + diagonal
|
|
221
|
-
return searchRadius*searchRadius + epsilon
|
|
252
|
+
return searchRadius * searchRadius + epsilon
|
|
222
253
|
end
|
|
223
254
|
|
|
224
255
|
-- luacheck: push ignore
|
|
@@ -237,30 +268,39 @@ end
|
|
|
237
268
|
@param nodeDistances2 { number }
|
|
238
269
|
@param maxDepth number
|
|
239
270
|
]=]
|
|
240
|
-
function OctreeRegionUtils.getNeighborsWithinRadius(
|
|
241
|
-
|
|
271
|
+
function OctreeRegionUtils.getNeighborsWithinRadius<T>(
|
|
272
|
+
region: OctreeRegion<T>,
|
|
273
|
+
radius: number,
|
|
274
|
+
px: number,
|
|
275
|
+
py: number,
|
|
276
|
+
pz: number,
|
|
277
|
+
objectsFound: { T },
|
|
278
|
+
nodeDistances2: { number },
|
|
279
|
+
maxDepth: number
|
|
280
|
+
)
|
|
281
|
+
-- luacheck: pop
|
|
242
282
|
assert(maxDepth, "Bad maxDepth")
|
|
243
283
|
|
|
244
|
-
local childDiameter = region.size[1]/2
|
|
284
|
+
local childDiameter = region.size[1] / 2
|
|
245
285
|
local searchRadiusSquared = OctreeRegionUtils.getSearchRadiusSquared(radius, childDiameter, EPSILON)
|
|
246
286
|
|
|
247
|
-
local radiusSquared = radius*radius
|
|
287
|
+
local radiusSquared = radius * radius
|
|
248
288
|
|
|
249
289
|
-- for each child
|
|
250
|
-
for _, childRegion in
|
|
290
|
+
for _, childRegion in region.subRegions do
|
|
251
291
|
local cposition = childRegion.position
|
|
252
292
|
local cpx, cpy, cpz = cposition[1], cposition[2], cposition[3]
|
|
253
293
|
|
|
254
294
|
local ox, oy, oz = px - cpx, py - cpy, pz - cpz
|
|
255
|
-
local dist2 = ox*ox + oy*oy + oz*oz
|
|
295
|
+
local dist2 = ox * ox + oy * oy + oz * oz
|
|
256
296
|
|
|
257
297
|
-- within search radius
|
|
258
298
|
if dist2 <= searchRadiusSquared then
|
|
259
299
|
if childRegion.depth == maxDepth then
|
|
260
|
-
for node, _ in
|
|
300
|
+
for node, _ in childRegion.nodes do
|
|
261
301
|
local npx, npy, npz = node:GetRawPosition()
|
|
262
302
|
local nox, noy, noz = px - npx, py - npy, pz - npz
|
|
263
|
-
local ndist2 = nox*nox + noy*noy + noz*noz
|
|
303
|
+
local ndist2 = nox * nox + noy * noy + noz * noz
|
|
264
304
|
if ndist2 <= radiusSquared then
|
|
265
305
|
objectsFound[#objectsFound + 1] = node:GetObject()
|
|
266
306
|
nodeDistances2[#nodeDistances2 + 1] = ndist2
|
|
@@ -268,7 +308,15 @@ function OctreeRegionUtils.getNeighborsWithinRadius(region, radius, px, py, pz,
|
|
|
268
308
|
end
|
|
269
309
|
else
|
|
270
310
|
OctreeRegionUtils.getNeighborsWithinRadius(
|
|
271
|
-
childRegion,
|
|
311
|
+
childRegion,
|
|
312
|
+
radius,
|
|
313
|
+
px,
|
|
314
|
+
py,
|
|
315
|
+
pz,
|
|
316
|
+
objectsFound,
|
|
317
|
+
nodeDistances2,
|
|
318
|
+
maxDepth
|
|
319
|
+
)
|
|
272
320
|
end
|
|
273
321
|
end
|
|
274
322
|
end
|
|
@@ -285,7 +333,13 @@ end
|
|
|
285
333
|
@param maxDepth number
|
|
286
334
|
@return OctreeRegion<T>
|
|
287
335
|
]=]
|
|
288
|
-
function OctreeRegionUtils.getOrCreateSubRegionAtDepth(
|
|
336
|
+
function OctreeRegionUtils.getOrCreateSubRegionAtDepth<T>(
|
|
337
|
+
region: OctreeRegion<T>,
|
|
338
|
+
px: number,
|
|
339
|
+
py: number,
|
|
340
|
+
pz: number,
|
|
341
|
+
maxDepth: number
|
|
342
|
+
): OctreeRegion<T>
|
|
289
343
|
local current = region
|
|
290
344
|
for _ = region.depth, maxDepth do
|
|
291
345
|
local index = OctreeRegionUtils.getSubRegionIndex(current, px, py, pz)
|
|
@@ -309,15 +363,15 @@ end
|
|
|
309
363
|
@param parentIndex number
|
|
310
364
|
@return OctreeRegion<T>
|
|
311
365
|
]=]
|
|
312
|
-
function OctreeRegionUtils.createSubRegion(parentRegion
|
|
366
|
+
function OctreeRegionUtils.createSubRegion<T>(parentRegion: OctreeRegion<T>, parentIndex: number): OctreeRegion<T>
|
|
313
367
|
local size = parentRegion.size
|
|
314
368
|
local position = parentRegion.position
|
|
315
369
|
local multiplier = SUB_REGION_POSITION_OFFSET[parentIndex]
|
|
316
370
|
|
|
317
|
-
local px = position[1] + multiplier[1]*size[1]
|
|
318
|
-
local py = position[2] + multiplier[2]*size[2]
|
|
319
|
-
local pz = position[3] + multiplier[3]*size[3]
|
|
320
|
-
local sx, sy, sz = size[1]/2, size[2]/2, size[3]/2
|
|
371
|
+
local px = position[1] + multiplier[1] * size[1]
|
|
372
|
+
local py = position[2] + multiplier[2] * size[2]
|
|
373
|
+
local pz = position[3] + multiplier[3] * size[3]
|
|
374
|
+
local sx, sy, sz = size[1] / 2, size[2] / 2, size[3] / 2
|
|
321
375
|
|
|
322
376
|
return OctreeRegionUtils.create(px, py, pz, sx, sy, sz, parentRegion, parentIndex)
|
|
323
377
|
end
|
|
@@ -333,13 +387,16 @@ end
|
|
|
333
387
|
@param pz number
|
|
334
388
|
@return boolean
|
|
335
389
|
]=]
|
|
336
|
-
function OctreeRegionUtils.inRegionBounds(region
|
|
390
|
+
function OctreeRegionUtils.inRegionBounds<T>(region: OctreeRegion<T>, px: number, py: number, pz: number): boolean
|
|
337
391
|
local lowerBounds = region.lowerBounds
|
|
338
392
|
local upperBounds = region.upperBounds
|
|
339
393
|
return (
|
|
340
|
-
px >= lowerBounds[1]
|
|
341
|
-
|
|
342
|
-
|
|
394
|
+
px >= lowerBounds[1]
|
|
395
|
+
and px <= upperBounds[1]
|
|
396
|
+
and py >= lowerBounds[2]
|
|
397
|
+
and py <= upperBounds[2]
|
|
398
|
+
and pz >= lowerBounds[3]
|
|
399
|
+
and pz <= upperBounds[3]
|
|
343
400
|
)
|
|
344
401
|
end
|
|
345
402
|
|
|
@@ -352,7 +409,7 @@ end
|
|
|
352
409
|
@param pz number
|
|
353
410
|
@return number
|
|
354
411
|
]=]
|
|
355
|
-
function OctreeRegionUtils.getSubRegionIndex(region
|
|
412
|
+
function OctreeRegionUtils.getSubRegionIndex<T>(region: OctreeRegion<T>, px: number, py: number, pz: number): number
|
|
356
413
|
local index = px > region.position[1] and 1 or 2
|
|
357
414
|
if py <= region.position[2] then
|
|
358
415
|
index = index + 4
|
|
@@ -374,9 +431,9 @@ end
|
|
|
374
431
|
@param cz number
|
|
375
432
|
@return number
|
|
376
433
|
]=]
|
|
377
|
-
function OctreeRegionUtils.getTopLevelRegionHash(cx, cy, cz)
|
|
434
|
+
function OctreeRegionUtils.getTopLevelRegionHash(cx: number, cy: number, cz: number): number
|
|
378
435
|
-- Normally you would modulus this to hash table size, but we want as flat of a structure as possible
|
|
379
|
-
return cx * 73856093 + cy*19351301 + cz*83492791
|
|
436
|
+
return cx * 73856093 + cy * 19351301 + cz * 83492791
|
|
380
437
|
end
|
|
381
438
|
|
|
382
439
|
--[=[
|
|
@@ -390,7 +447,12 @@ end
|
|
|
390
447
|
@return number -- rpy
|
|
391
448
|
@return number -- rpz
|
|
392
449
|
]=]
|
|
393
|
-
function OctreeRegionUtils.getTopLevelRegionCellIndex(
|
|
450
|
+
function OctreeRegionUtils.getTopLevelRegionCellIndex(
|
|
451
|
+
maxRegionSize: OctreeVector3,
|
|
452
|
+
px: number,
|
|
453
|
+
py: number,
|
|
454
|
+
pz: number
|
|
455
|
+
): (number, number, number)
|
|
394
456
|
return math.floor(px / maxRegionSize[1] + 0.5),
|
|
395
457
|
math.floor(py / maxRegionSize[2] + 0.5),
|
|
396
458
|
math.floor(pz / maxRegionSize[3] + 0.5)
|
|
@@ -407,10 +469,13 @@ end
|
|
|
407
469
|
@return number
|
|
408
470
|
@return number
|
|
409
471
|
]=]
|
|
410
|
-
function OctreeRegionUtils.getTopLevelRegionPosition(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
472
|
+
function OctreeRegionUtils.getTopLevelRegionPosition(
|
|
473
|
+
maxRegionSize: OctreeVector3,
|
|
474
|
+
cx: number,
|
|
475
|
+
cy: number,
|
|
476
|
+
cz: number
|
|
477
|
+
): (number, number, number)
|
|
478
|
+
return maxRegionSize[1] * cx, maxRegionSize[2] * cy, maxRegionSize[3] * cz
|
|
414
479
|
end
|
|
415
480
|
|
|
416
481
|
--[=[
|
|
@@ -423,11 +488,9 @@ end
|
|
|
423
488
|
@param rpz number
|
|
424
489
|
@return boolean
|
|
425
490
|
]=]
|
|
426
|
-
function OctreeRegionUtils.areEqualTopRegions(region
|
|
491
|
+
function OctreeRegionUtils.areEqualTopRegions<T>(region: OctreeRegion<T>, rpx: number, rpy: number, rpz: number)
|
|
427
492
|
local position = region.position
|
|
428
|
-
return position[1] == rpx
|
|
429
|
-
and position[2] == rpy
|
|
430
|
-
and position[3] == rpz
|
|
493
|
+
return position[1] == rpx and position[2] == rpy and position[3] == rpz
|
|
431
494
|
end
|
|
432
495
|
|
|
433
496
|
--[=[
|
|
@@ -440,17 +503,23 @@ end
|
|
|
440
503
|
@param pz number
|
|
441
504
|
@return OctreeRegion3?
|
|
442
505
|
]=]
|
|
443
|
-
function OctreeRegionUtils.findRegion(
|
|
506
|
+
function OctreeRegionUtils.findRegion<T>(
|
|
507
|
+
regionHashMap: OctreeRegionHashMap<T>,
|
|
508
|
+
maxRegionSize: OctreeVector3,
|
|
509
|
+
px: number,
|
|
510
|
+
py: number,
|
|
511
|
+
pz: number
|
|
512
|
+
): OctreeRegion<T>?
|
|
444
513
|
local cx, cy, cz = OctreeRegionUtils.getTopLevelRegionCellIndex(maxRegionSize, px, py, pz)
|
|
445
514
|
local hash = OctreeRegionUtils.getTopLevelRegionHash(cx, cy, cz)
|
|
446
515
|
|
|
447
|
-
local regionList = regionHashMap[hash]
|
|
516
|
+
local regionList: { OctreeRegion<T> } = regionHashMap[hash]
|
|
448
517
|
if not regionList then
|
|
449
518
|
return nil
|
|
450
519
|
end
|
|
451
520
|
|
|
452
521
|
local rpx, rpy, rpz = OctreeRegionUtils.getTopLevelRegionPosition(maxRegionSize, cx, cy, cz)
|
|
453
|
-
for _, region in
|
|
522
|
+
for _, region in regionList do
|
|
454
523
|
if OctreeRegionUtils.areEqualTopRegions(region, rpx, rpy, rpz) then
|
|
455
524
|
return region
|
|
456
525
|
end
|
|
@@ -469,7 +538,7 @@ end
|
|
|
469
538
|
@param pz number
|
|
470
539
|
@return OctreeRegion<T>
|
|
471
540
|
]=]
|
|
472
|
-
function OctreeRegionUtils.getOrCreateRegion(regionHashMap
|
|
541
|
+
function OctreeRegionUtils.getOrCreateRegion<T>(regionHashMap: OctreeRegionHashMap<T>, maxRegionSize: OctreeVector3, px: number, py: never, pz: number): OctreeRegion<T>
|
|
473
542
|
local cx, cy, cz = OctreeRegionUtils.getTopLevelRegionCellIndex(maxRegionSize, px, py, pz)
|
|
474
543
|
local hash = OctreeRegionUtils.getTopLevelRegionHash(cx, cy, cz)
|
|
475
544
|
|
|
@@ -480,7 +549,7 @@ function OctreeRegionUtils.getOrCreateRegion(regionHashMap, maxRegionSize, px, p
|
|
|
480
549
|
end
|
|
481
550
|
|
|
482
551
|
local rpx, rpy, rpz = OctreeRegionUtils.getTopLevelRegionPosition(maxRegionSize, cx, cy, cz)
|
|
483
|
-
for _, region in
|
|
552
|
+
for _, region in regionList do
|
|
484
553
|
if OctreeRegionUtils.areEqualTopRegions(region, rpx, rpy, rpz) then
|
|
485
554
|
return region
|
|
486
555
|
end
|