-
Notifications
You must be signed in to change notification settings - Fork 192
/
BoxZone.lua
226 lines (193 loc) · 7.24 KB
/
BoxZone.lua
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
BoxZone = {}
-- Inherits from PolyZone
setmetatable(BoxZone, { __index = PolyZone })
-- Utility functions
local rad, cos, sin = math.rad, math.cos, math.sin
function PolyZone.rotate(origin, point, theta)
if theta == 0.0 then return point end
local p = point - origin
local pX, pY = p.x, p.y
theta = rad(theta)
local cosTheta = cos(theta)
local sinTheta = sin(theta)
local x = pX * cosTheta - pY * sinTheta
local y = pX * sinTheta + pY * cosTheta
return vector2(x, y) + origin
end
function BoxZone.calculateMinAndMaxZ(minZ, maxZ, scaleZ, offsetZ)
local minScaleZ, maxScaleZ, minOffsetZ, maxOffsetZ = scaleZ[1] or 1.0, scaleZ[2] or 1.0, offsetZ[1] or 0.0, offsetZ[2] or 0.0
if (minZ == nil and maxZ == nil) or (minScaleZ == 1.0 and maxScaleZ == 1.0 and minOffsetZ == 0.0 and maxOffsetZ == 0.0) then
return minZ, maxZ
end
if minScaleZ ~= 1.0 or maxScaleZ ~= 1.0 then
if minZ ~= nil and maxZ ~= nil then
local halfHeight = (maxZ - minZ) / 2
local centerZ = minZ + halfHeight
minZ = centerZ - halfHeight * minScaleZ
maxZ = centerZ + halfHeight * maxScaleZ
else
print(string.format(
"[PolyZone] Warning: The minZ/maxZ of a BoxZone can only be scaled if both minZ and maxZ are non-nil (minZ=%s, maxZ=%s)",
tostring(minZ),
tostring(maxZ)
))
end
end
if minZ then minZ = minZ - minOffsetZ end
if maxZ then maxZ = maxZ + maxOffsetZ end
return minZ, maxZ
end
local function _calculateScaleAndOffset(options)
-- Scale and offset tables are both formatted as {forward, back, left, right, up, down}
-- or if symmetrical {forward/back, left/right, up/down}
local scale = options.scale or {1.0, 1.0, 1.0, 1.0, 1.0, 1.0}
local offset = options.offset or {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}
assert(#scale == 3 or #scale == 6, "Scale must be of length 3 or 6")
assert(#offset == 3 or #offset == 6, "Offset must be of length 3 or 6")
if #scale == 3 then
scale = {scale[1], scale[1], scale[2], scale[2], scale[3], scale[3]}
end
if #offset == 3 then
offset = {offset[1], offset[1], offset[2], offset[2], offset[3], offset[3]}
end
local minOffset = vector3(offset[3], offset[2], offset[6])
local maxOffset = vector3(offset[4], offset[1], offset[5])
local minScale = vector3(scale[3], scale[2], scale[6])
local maxScale = vector3(scale[4], scale[1], scale[5])
return minOffset, maxOffset, minScale, maxScale
end
local function _calculatePoints(center, length, width, minScale, maxScale, minOffset, maxOffset)
local halfLength, halfWidth = length / 2, width / 2
local min = vector3(-halfWidth, -halfLength, 0.0)
local max = vector3(halfWidth, halfLength, 0.0)
min = min * minScale - minOffset
max = max * maxScale + maxOffset
-- Box vertices
local p1 = center.xy + vector2(min.x, min.y)
local p2 = center.xy + vector2(max.x, min.y)
local p3 = center.xy + vector2(max.x, max.y)
local p4 = center.xy + vector2(min.x, max.y)
return {p1, p2, p3, p4}
end
-- Debug drawing functions
function BoxZone:TransformPoint(point)
-- Overriding TransformPoint function to take into account rotation and position offset
return PolyZone.rotate(self.startPos, point, self.offsetRot) + self.offsetPos
end
-- Initialization functions
local function _initDebug(zone, options)
if options.debugBlip then zone:addDebugBlip() end
if not options.debugPoly then
return
end
Citizen.CreateThread(function()
while not zone.destroyed do
zone:draw(false)
Citizen.Wait(0)
end
end)
end
local defaultMinOffset, defaultMaxOffset, defaultMinScale, defaultMaxScale = vector3(0.0, 0.0, 0.0), vector3(0.0, 0.0, 0.0), vector3(1.0, 1.0, 1.0), vector3(1.0, 1.0, 1.0)
local defaultScaleZ, defaultOffsetZ = {defaultMinScale.z, defaultMaxScale.z}, {defaultMinOffset.z, defaultMaxOffset.z}
function BoxZone:new(center, length, width, options)
local minOffset, maxOffset, minScale, maxScale = defaultMinOffset, defaultMaxOffset, defaultMinScale, defaultMaxScale
local scaleZ, offsetZ = defaultScaleZ, defaultOffsetZ
if options.scale ~= nil or options.offset ~= nil then
minOffset, maxOffset, minScale, maxScale = _calculateScaleAndOffset(options)
scaleZ, offsetZ = {minScale.z, maxScale.z}, {minOffset.z, maxOffset.z}
end
local points = _calculatePoints(center, length, width, minScale, maxScale, minOffset, maxOffset)
local min = points[1]
local max = points[3]
local size = max - min
local minZ, maxZ = BoxZone.calculateMinAndMaxZ(options.minZ, options.maxZ, scaleZ, offsetZ)
options.minZ = minZ
options.maxZ = maxZ
-- Box Zones don't use the grid optimization because they are already rectangles/cubes
options.useGrid = false
-- Pre-setting all these values to avoid PolyZone:new() having to calculate them
options.min = min
options.max = max
options.size = size
options.center = center
options.area = size.x * size.y
local zone = PolyZone:new(points, options)
zone.length = length
zone.width = width
zone.startPos = center.xy
zone.offsetPos = vector2(0.0, 0.0)
zone.offsetRot = options.heading or 0.0
zone.minScale, zone.maxScale = minScale, maxScale
zone.minOffset, zone.maxOffset = minOffset, maxOffset
zone.scaleZ, zone.offsetZ = scaleZ, offsetZ
zone.isBoxZone = true
setmetatable(zone, self)
self.__index = self
return zone
end
function BoxZone:Create(center, length, width, options)
local zone = BoxZone:new(center, length, width, options)
_initDebug(zone, options)
return zone
end
-- Helper functions
function BoxZone:isPointInside(point)
if self.destroyed then
print("[PolyZone] Warning: Called isPointInside on destroyed zone {name=" .. self.name .. "}")
return false
end
local startPos = self.startPos
local actualPos = point.xy - self.offsetPos
if #(actualPos - startPos) > self.boundingRadius then
return false
end
local rotatedPoint = PolyZone.rotate(startPos, actualPos, -self.offsetRot)
local pX, pY, pZ = rotatedPoint.x, rotatedPoint.y, point.z
local min, max = self.min, self.max
local minX, minY, maxX, maxY = min.x, min.y, max.x, max.y
local minZ, maxZ = self.minZ, self.maxZ
if pX < minX or pX > maxX or pY < minY or pY > maxY then
return false
end
if (minZ and pZ < minZ) or (maxZ and pZ > maxZ) then
return false
end
return true
end
function BoxZone:getHeading()
return self.offsetRot
end
function BoxZone:setHeading(heading)
if not heading then
return
end
self.offsetRot = heading
end
function BoxZone:setCenter(center)
if not center or center == self.center then
return
end
self.center = center
self.startPos = center.xy
self.points = _calculatePoints(self.center, self.length, self.width, self.minScale, self.maxScale, self.minOffset, self.maxOffset)
end
function BoxZone:getLength()
return self.length
end
function BoxZone:setLength(length)
if not length or length == self.length then
return
end
self.length = length
self.points = _calculatePoints(self.center, self.length, self.width, self.minScale, self.maxScale, self.minOffset, self.maxOffset)
end
function BoxZone:getWidth()
return self.width
end
function BoxZone:setWidth(width)
if not width or width == self.width then
return
end
self.width = width
self.points = _calculatePoints(self.center, self.length, self.width, self.minScale, self.maxScale, self.minOffset, self.maxOffset)
end