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