local Array = require('Array') local AABB = require('AABB') local CubicInterpolatingLane = require('CubicInterpolatingLane') local lastIndex = 0 ---@class EditorLane : ClassBase ---@field name string ---@field loop boolean ---@field aabb AABB ---@field role integer ---@field priorityOffset number ---@field finalized SerializedLane ---@field points vec3[]|Array local EditorLane = class('EditorLane') ---@param p1 vec3|SerializedLane ---@param p2 vec3 ---@return EditorLane function EditorLane:initialize(p1, p2) lastIndex = lastIndex + 1 self.uniqueID = lastIndex if vec3.isvec3(p1) then self.name = 'Lane #'..lastIndex self.id = lastIndex self.loop = false self.role = 3 self.priorityOffset = 0 self.points = Array{ p1, p2 } self.params = {} else -- deserialization self.name = p1.name self.id = p1.id or lastIndex self.loop = p1.loop == true self.role = p1.role or 3 self.priorityOffset = p1.priorityOffset or 0 self.points = Array(p1.points, vec3.new) self.params = p1.params or {} end self.aabb = AABB() self:recalculate() if #self.points == 0 then error('wtf') end end function EditorLane:cubicCurve() local r = self._cubicCurve if r == nil then r = CubicInterpolatingLane(self.points, self.loop, true) self._cubicCurve = r end return r end ---@return SerializedLane function EditorLane:encode() return { name = self.name, id = self.id, loop = self.loop, role = self.role ~= 3 and self.role or nil, priorityOffset = self.priorityOffset, points = self.points:map(vec3.table, nil, {}), params = self.params } end local vecDown = vec3(0, -1, 0) ---@param editor EditorMain ---@return SerializedLane function EditorLane:finalize(editor) if self.finalized == nil then local baseLane = try(function () return CubicInterpolatingLane(self.points, self.loop) end, function (err) ac.error(string.format('Lane is damaged: %s (%s)', self.name, err)) end) if not baseLane then self.finalized = { aabb = { vec3():table(), vec3():table() }, points = {} } else local length = math.ceil(baseLane.totalDistance / 3) local resampledPoints = Array(self.loop and length or length + 1) resampledPoints[1] = baseLane.points[1] local loopLength = self.loop and length - 1 or length for i = 1, loopLength do local p = baseLane:interpolate(baseLane:distanceToPointEdgePos(baseLane.totalDistance * i / length)) p.y = p.y + 2 local offset = physics.raycastTrack(p, vecDown, 4) p.y = p.y - (offset ~= -1 and offset or 2) resampledPoints[i + 1] = p end self.finalized = { aabb = { self.aabb.min:table(), self.aabb.max:table() }, points = resampledPoints:map(vec3.table, nil, {}) } class.recycle(baseLane) end end local role = editor.rules.laneRoles[self.role] self.finalized.name = self.name self.finalized.id = self.id self.finalized.loop = self.loop self.finalized.role = self.role self.finalized.priority = (role and role.priority or 0) + self.priorityOffset self.finalized.priorityOffset = self.priorityOffset self.finalized.speedLimit = role and role.speedLimit or 90 self.finalized.params = self.params return self.finalized end function EditorLane:getPointRef(pointIndex) return self.points[pointIndex] end function EditorLane:extend(point, insertInFront) if insertInFront then self.points:insert(1, point) else self.points:push(point) end self:recalculate() end function EditorLane:removePointAt(pointIndex) self.points:removeAt(pointIndex) self:recalculate() end function EditorLane:insertPointNextTo(point, pointIndex) local p = self.points:at(pointIndex) if p == nil then self:extend(point) else local n = self.points:at(pointIndex + 1) local r = self.points:at(pointIndex - 1) if n ~= nil and r ~= nil and (point - p):dot(n - p) < (point - p):dot(r - p) then self.points:insert(pointIndex, point) else self.points:insert(pointIndex + 1, point) end self:recalculate() end end function EditorLane:recalculate() self.finalized = nil class.recycle(self._cubicCurve) self._cubicCurve = nil local aabb = self.aabb aabb:reset() for i = 1, #self.points do aabb:extend(self.points[i]) end aabb:finalize() local len = 0 for i = 2, #self.points do len = len + self.points[i - 1]:distance(self.points[i]) end self.length = len end return class.emmy(EditorLane, EditorLane.initialize)