def ScaleProbesAroundCenter(self): x, y = uicore.ScaleDpi(uicore.uilib.x), uicore.ScaleDpi(uicore.uilib.y) mousePos = geo2.Vector(x, y, 0) probeData = sm.GetService('scanSvc').GetProbeData() scannerWnd = form.Scanner.GetIfOpen() if scannerWnd is None: return probes = scannerWnd.GetProbeSpheres() centroid = geo2.Vector(0, 0, 0) numProbes = 0 for probeID, probeControl in probes.iteritems(): if probeID not in probeData or probeData[ probeID].state != const.probeStateIdle: continue probePos = probeControl.GetWorldPosition() centroid += probePos numProbes += 1 if numProbes <= 1: return centroid /= numProbes projectionParams = GetWorldToScreenParameters() centroidTansform = ((SYSTEMMAP_SCALE, 0, 0, 0), (0, SYSTEMMAP_SCALE, 0, 0), (0, 0, SYSTEMMAP_SCALE, 0), (centroid.x, centroid.y, centroid.z, 1.0)) screenCentroid = geo2.Vector( *ProjectTransform(projectionParams, centroidTansform)) screenCentroid.z = 0 probeScreenPos = geo2.Vector(*ProjectTransform( projectionParams, self.sr.movingProbe.locator.worldTransform)) probeScreenPos.z = 0 centerToProbe = probeScreenPos - screenCentroid centerToProbeLength = geo2.Vec2Length(centerToProbe) if centerToProbeLength < 0.1: return centerToProbeNormal = centerToProbe / centerToProbeLength toMouseDotProduct = geo2.Vec2Dot(mousePos - screenCentroid, centerToProbeNormal) projectedPos = screenCentroid + toMouseDotProduct * centerToProbeNormal toProjectedLength = geo2.Vec2Length(projectedPos - screenCentroid) if toProjectedLength < 0.1: return moveScale = toProjectedLength / centerToProbeLength if toMouseDotProduct < 0: moveScale = -moveScale for probeID, probeControl in probes.iteritems(): if probeID not in probeData or probeData[ probeID].state != const.probeStateIdle: continue pos = probeControl.GetWorldPosition() toProbe = pos - centroid endPos = centroid + toProbe * moveScale endPos = (endPos.x / SYSTEMMAP_SCALE, endPos.y / SYSTEMMAP_SCALE, endPos.z / SYSTEMMAP_SCALE) probeControl.SetPosition(endPos) scannerWnd.ShowCentroidLines() scannerWnd.HighlightProbeIntersections() sm.GetService('systemmap').HighlightItemsWithinProbeRange()
def closest_point_on_seg(seg_a, seg_b, circ_pos): seg_v = geo2.Vec2Subtract(seg_b, seg_a) pt_v = geo2.Vec2Subtract(circ_pos, seg_a) if geo2.Vec2Length(seg_v) <= 0: raise ValueError, 'Invalid segment length' seg_v_unit = geo2.Vec2Normalize(seg_v) proj = geo2.Vec2Dot(seg_v_unit, pt_v) if proj <= 0: return seg_a if proj >= geo2.Vec2Length(seg_v): return seg_b proj_v = geo2.Vec2Scale(seg_v_unit, proj) closest = geo2.Vec2Add(proj_v, seg_a) return closest
def GetLengthFromCenter(self): """ finding the mouse distance from center """ aX, bY = self.GetDistancesFromMenuCenter() length = geo2.Vec2Length((aX, bY)) return length
def RotationAdjust(self, camera, timeSinceTargetChange, arc, itemID, timeDelta): br = sm.GetService('bracket') itemBracket = br.GetBracket(itemID) if itemBracket is None: itemBracket = sm.GetService('sensorSuite').GetBracketByBallID( itemID) if itemBracket and itemBracket not in br.overlaps: bracketRender = itemBracket.renderObject if bracketRender is not None and bracketRender.display: if itemBracket.parent is uicore.layer.inflight: offset = uicore.layer.inflight.absoluteLeft else: offset = 0 dx = self.trackingPointX - ( bracketRender.displayX + bracketRender.displayWidth / 2) - offset dy = self.trackingPointY - (bracketRender.displayY + bracketRender.displayHeight / 2) tdx, tdy = self.GetRotationDeltas(dx, dy, arc, timeSinceTargetChange, timeDelta) tiltAngle = geo2.Vec2Length((tdx, tdy)) multiplier = min(timeDelta / 16.6, 1) minMoveAngle = 0.0005 * multiplier if tiltAngle > minMoveAngle: self.tiltX += tdx self.tiltY += tdy self.tiltX = math.fmod(self.tiltX, math.pi * 2) self.tiltY = math.fmod(self.tiltY, math.pi * 2) camera.SetRotationOnOrbit(self.tiltX, self.tiltY)
def PlotLineTrace(self): self.Flush() if self.glowLine: self.glowLine.Flush() if self.lineType in (LINE_DASHED, LINE_DASHED_ACTIVE): self.PlotDashLine() elif self.lineType == LINE_SOLID: self.PlotSolidLine() else: return if self.lineType == LINE_DASHED_ACTIVE: vecDir = geo2.Vec2Subtract(self.toPosition, self.fromPosition) vecLength = geo2.Vec2Length(vecDir) vecDirNorm = geo2.Vec2Normalize(vecDir) r, g, b = self.GetRGB() GLOWCOLOR = (r, g, b, 1.0) GAPCOLOR = (r, g, b, 0.0) self.glowLine.AddPoint(self.fromPosition, GAPCOLOR) point = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, vecLength * 0.5)) self.glowLine.AddPoint(point, GLOWCOLOR) self.glowLine.AddPoint(self.toPosition, GAPCOLOR) self.glowLine.textureWidth = vecLength uicore.animations.MorphScalar(self.glowLine, 'textureOffset', startVal=0.0, endVal=1.0, curveType=uiconst.ANIM_LINEAR, duration=2.0, loops=uiconst.ANIM_REPEAT)
def ScanForObjectLineOverlaps(hexMap, startPenalty=0, stopAtPenalty=None): penalty = startPenalty object_line_overlaps = [] objectIDs = sorted(hexMap.objectByID.keys()) for objectID in objectIDs: uiObject = hexMap.objectByID[objectID] objPos = (uiObject.left + hexMap.width / 2.0, uiObject.top + hexMap.height / 2.0) for connectionID, line in hexMap.connectionsByID.iteritems(): if objectID in connectionID: continue if not line.renderObject.vertices: continue p1x, p1y = line.renderObject.vertices[0].position p2x, p2y = line.renderObject.vertices[1].position pointOnLine = hexUtil.closest_point_on_seg((p1x, p1y), (p2x, p2y), objPos) dist_v = geo2.Vec2Subtract(pointOnLine, objPos) if geo2.Vec2Length(dist_v) < uiObject.hexSize: object_line_overlaps.append(uiObject) penalty += 100 if stopAtPenalty is not None and penalty >= stopAtPenalty: return (object_line_overlaps, penalty) return (object_line_overlaps, penalty)
def PlotDashLine(self): dashSize = 2.0 gapSize = 7.0 r, g, b = self.GetRGB() DASHCOLOR = (r, g, b, 1.0) GAPCOLOR = (r, g, b, 0.0) MARGIN = 16.0 * self.localScale vecDir = geo2.Vec2Subtract(self.toPosition, self.fromPosition) vecLength = geo2.Vec2Length(vecDir) vecDirNorm = geo2.Vec2Normalize(vecDir) p = MARGIN while p < vecLength - MARGIN: startPoint = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, p - 0.5)) self.AddPoint(startPoint, GAPCOLOR) fromPoint = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, p)) self.AddPoint(fromPoint, DASHCOLOR) p = min(vecLength - MARGIN, dashSize + p) toPoint = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, p)) self.AddPoint(toPoint, DASHCOLOR) endPoint = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, p + 0.5)) self.AddPoint(endPoint, GAPCOLOR) p += gapSize
def intersect_line_segments(seg1, seg2): A, B = seg1 C, D = seg2 if seg1 == seg2: return geo2.Vec2Lerp(A, B, 0.5) cV = segment_circle(A, B, C) cL = geo2.Vec2Length(cV) dV = segment_circle(A, B, D) dL = geo2.Vec2Length(dV) aV = segment_circle(C, D, A) aL = geo2.Vec2Length(aV) bV = segment_circle(C, D, B) bL = geo2.Vec2Length(bV) Ax, Ay = A Bx, By = B Cx, Cy = C Dx, Dy = D X = Y = 0.0 if Ax == Bx and Ay == By or Cx == Dx and Cy == Dy: print 'False1' return False if Ax == Cx and Ay == Cy or Bx == Cx and By == Cy or Ax == Dx and Ay == Dy or Bx == Dx and By == Dy: return False Bx -= Ax By -= Ay Cx -= Ax Cy -= Ay Dx -= Ax Dy -= Ay distAB = math.sqrt(Bx * Bx + By * By) theCos = Bx / distAB theSin = By / distAB newX = Cx * theCos + Cy * theSin Cy = Cy * theCos - Cx * theSin Cx = newX newX = Dx * theCos + Dy * theSin Dy = Dy * theCos - Dx * theSin Dx = newX if Cy < 0 and Dy < 0 or Cy >= 0 and Dy >= 0: return False ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy) if ABpos < 0 or ABpos > distAB: return False X = Ax + ABpos * theCos Y = Ay + ABpos * theSin return (X, Y)
def GetNewLineStartPos(self, node, childNode): pos = self._GetLinePosition(node) childPos = self._GetLinePosition(childNode) if node.nodeType == NODETYPE_GROUP: if childPos[1] == pos[1]: offset = node.GetGroupWidth() + 24 s = offset / geo2.Vec2Length(geo2.Vec2Subtract(childPos, pos)) pos = geo2.Vec2Lerp(pos, childPos, s) return (int(pos[0]), int(pos[1]))
def PanToMouseOver(self, duration = None, timeOffset = 0.0, sleep = False): panLeft, panTop = self.GetMousePositionProportional() if not duration: length = geo2.Vec2Length((panLeft - self.panLeft, panTop - self.panTop)) if length < 0.2: return 0.0 duration = max(0.3, length * 0.4) self.PanTo(panLeft, panTop, duration=duration, timeOffset=timeOffset, sleep=sleep) return duration
def ApplyLineMargin(self, p1, p2, radius1, radius2): v = geo2.Vec2Subtract(p1, p2) vn = geo2.Vec2Normalize(v) l = geo2.Vec2Length(v) if not l: return (None, None) s = (radius1 + radius2) / l mp1 = geo2.Vec2Subtract(p1, geo2.Vec2Scale(vn, radius1)) mp2 = geo2.Vec2Add(p2, geo2.Vec2Scale(vn, radius2)) return (mp1, mp2)
def _TryExpandActionMenu(self, itemID, x, y, clickedObject, **kwargs): if getattr(clickedObject, 'isDragObject', False): if x != uicore.uilib.x or y != uicore.uilib.y: return self.expandTimer = None if clickedObject.destroyed: return v = geo2.Vector(uicore.uilib.x - x, uicore.uilib.y - y) if int(geo2.Vec2Length(v) > 12): return self.ExpandActionMenu(itemID, x, y, clickedObject, **kwargs)
def __UpdateCompass(self): bp = self.michelle.GetBallpark() if bp is None: return camera = self.GetCamera() camRotation = geo2.QuaternionRotationGetYawPitchRoll( camera.rotationAroundParent) yaw, pitch, roll = camRotation cx, cy, cz = geo2.QuaternionTransformVector( camera.rotationAroundParent, (0, 0, -1.0)) camLengthInPlane = geo2.Vec2Length((cx, cz)) camAngle = math.atan2(cy, camLengthInPlane) self.compassTransform.rotation = -yaw + math.pi myPos = bp.GetCurrentEgoPos() if self.lastPose: lastCamRot, lastPos = self.lastPose isNewCamRotation = not AreVectorsEqual(lastCamRot, camRotation, 0.05) isNewPosition = not AreVectorsEqual(lastPos, myPos, 0.5) isNewPose = isNewPosition or isNewCamRotation else: isNewPosition = True isNewPose = True for siteID, indicator in self.siteIndicatorsBySiteID.iteritems(): if indicator.isNew or isNewPose: toSiteVec = geo2.Vec3SubtractD(indicator.data.position, myPos) toSiteVec = geo2.Vec3NormalizeD(toSiteVec) if indicator.isNew or isNewPosition: angle = math.atan2(-toSiteVec[2], toSiteVec[0]) indicator.SetRotation(angle + MATH_PI_2) sx, sy, sz = toSiteVec siteLengthInPlane = geo2.Vec2Length((sx, sz)) siteAngle = math.atan2(sy, siteLengthInPlane) inclinationAngle = siteAngle - camAngle verticalAngle = min(inclinationAngle, MATH_PI_2) indicator.SetInclination(verticalAngle) indicator.isNew = False self.lastPose = (camRotation, myPos)
def Update(self): if self.updateFocus: right = self.GetBonePosition('fj_eyeballRight') left = self.GetBonePosition('fj_eyeballLeft') self.focus = geo2.Add(right, left) self.focus = geo2.Vector(*self.focus) * 0.5 if self.moveCallback: self.moveCallback(self.viewMatrix) if self.controlStyle == CONTROL_VERTICAL: length = geo2.Vec2Length(geo2.Vector(self.focus[0], self.focus[2])) self.focus = (0.0, self.focus[1], length) self.updateFocus = False cameras.PolarCamera.Update(self)
def _GetLookAtRadius(self, balls): ret = 0.0 v0 = GetBallPosition(balls[0]) camera = self.cameraController.GetCamera() v0 = camera.ProjectWorldToCamera(v0) for ball in balls[1:]: v1 = GetBallPosition(ball) if geo2.Vec3Length(v1) > GetCameraMaxLookAtRange(): continue v1 = camera.ProjectWorldToCamera(v1) diff = geo2.Vec3Subtract(v1, v0) dist = geo2.Vec2Length(diff[:2]) if ret < dist: ret = dist return ret
def GetMousePositionAngle(self): x, y = self.GetAbsolutePosition() x = uicore.uilib.x - x - self.radius y = uicore.uilib.y - y - self.radius v1 = (x, y) if geo2.Vec2Length(v1) < 5: return self._angle v2 = (0.0, 1.0) dot = geo2.Vec2Dot(v1, v2) cross = v1[0] * v2[1] - v1[1] * v2[0] angle = atan2(dot, cross) angle -= self.startAngle if angle < 0: angle += 2 * pi self._angle = angle return angle
def Update(self, *args): t = blue.os.GetSimTime() if t == self.lastUpdateTime: return if sm.GetService('michelle').GetBall(self.itemID) is None: self.Close() return self.UpdateBoxPosition() bracketPos = self.GetContainerPosition(self.bracket) boxPos = self.GetContainerPosition(self.floatingBox) lineTo = self.GetLineConnectionPointOnBox(bracketPos, self.floatingBox) cornerPos = geo2.Vec2Add(boxPos, lineTo) vec = geo2.Vec2Subtract(bracketPos, cornerPos) length = geo2.Vec2Length(vec) vec = geo2.Scale(vec, (length - uicore.ScaleDpi(ICON_SIZE / 2)) / length) self.line.translationTo = geo2.Vec2Add(vec, lineTo) self.line.translationFrom = lineTo
def RotateUpdateThread(self): try: while True: if self.rotateTarget is None: break distLeft = geo2.Vec2Length( geo2.Vec2Subtract(self.rotateTarget, self._rotateOffset)) if not distLeft: break moveProp = self._GetRotateSpeed() / blue.os.fps if math.fabs(distLeft) < self.kRotateStopDist: moveProp *= self.kRotateStopDist / math.fabs(distLeft) moveProp = min(moveProp, 1.0) self._rotateOffset = geo2.Lerp(self._rotateOffset, self.rotateTarget, moveProp) blue.synchro.Yield() finally: self.rotateUpdateThread = None
def PointCameraToPos(self, camera, shipPos, itemPos, panSpeed, timeDelta, trackingPoint=None): m, h = uicore.desktop.width / 2, uicore.desktop.height center = trinity.TriVector( uicore.ScaleDpi(m * (1 - camera.centerOffset)), uicore.ScaleDpi(h / 2), 0) v2 = shipPos - itemPos v2.Normalize() yzProj = trinity.TriVector(0, v2.y, v2.z) zxProj = trinity.TriVector(v2.x, 0, v2.z) yaw = self.CalcAngle(zxProj.z, zxProj.x) pitch = -math.asin(min(1.0, max(-1.0, yzProj.y))) oldYaw = camera.yaw oldPitch = self.clampPitch(camera.pitch) dx2 = 0.0 dy2 = 0.0 if trackingPoint is not None: dx2 = center.x - trackingPoint[0] dy2 = center.y - trackingPoint[1] alphaX = math.pi * dx2 * camera.fieldOfView / uicore.ScaleDpi( uicore.desktop.width) alphaY = math.pi * dy2 * camera.fieldOfView / uicore.ScaleDpi( uicore.desktop.width) dPitchTotal = pitch - oldPitch dYawTotal = (yaw - camera.yaw) % (2 * math.pi) - alphaX * 0.75 clampedPitchTotal = min(2 * math.pi - dPitchTotal, dPitchTotal) - alphaY * 0.75 if dYawTotal > math.pi: dYawTotal = -(2 * math.pi - dYawTotal) arc = geo2.Vec2Length((dYawTotal, clampedPitchTotal)) part = min(1, timeDelta * panSpeed) dYawPart = dYawTotal * part dPitchPart = clampedPitchTotal * part Yaw = oldYaw + dYawPart Pitch = oldPitch + dPitchPart camera.SetOrbit(Yaw, Pitch) return arc
def _PanUpdateThread(self): while True: if self.panTarget is None or not self.mainCont.children: break distLeft = geo2.Vec2Length(self.panTarget) if distLeft == 0: break dist = self.panSpeed / blue.os.fps if distLeft < 1.0: dist *= 1.0 / distLeft dist = min(dist, 1.0) toMove = geo2.Vec2Scale(self.panTarget, dist) self.panTarget -= toMove dx, dy = toMove self.panLeft -= dx / self.mainCont.width self.panTop -= dy / self.mainCont.height blue.synchro.Yield() self.panUpdateThread = None self.panTarget = None
def PlotSolidLine(self): r, g, b = self.GetRGB() DASHCOLOR = (r, g, b, 1.0) GAPCOLOR = (r, g, b, 0.0) MARGIN = 16.0 * self.localScale vecDir = geo2.Vec2Subtract(self.toPosition, self.fromPosition) vecLength = geo2.Vec2Length(vecDir) vecDirNorm = geo2.Vec2Normalize(vecDir) startPoint = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, MARGIN)) self.AddPoint(startPoint, GAPCOLOR) startPoint = geo2.Vec2Add(self.fromPosition, geo2.Vec2Scale(vecDirNorm, MARGIN + 8)) self.AddPoint(startPoint, DASHCOLOR) startPoint = geo2.Vec2Add( self.fromPosition, geo2.Vec2Scale(vecDirNorm, vecLength - MARGIN - 8)) self.AddPoint(startPoint, DASHCOLOR) startPoint = geo2.Vec2Add( self.fromPosition, geo2.Vec2Scale(vecDirNorm, vecLength - MARGIN)) self.AddPoint(startPoint, GAPCOLOR)
def _GetNewDirection(self, direction): y = geo2.Vec2Length((direction[0], direction[2])) direction = (direction[0], y, direction[2]) direction = geo2.Vec3Normalize(direction) return direction
boxPoint = (x, yMin) elif case == CASE3: boxPoint = (xMax, yMin) elif case == CASE4: boxPoint = (xMin, y) elif case == CASE5: boxPoint = (xMax, y) elif case == CASE6: boxPoint = (xMin, yMax) elif case == CASE7: boxPoint = (x, yMax) elif case == CASE8: boxPoint = (xMax, yMax) elif case == CASE9: return 0.0 return geo2.Vec2Length(geo2.Vec2Subtract(point, boxPoint)) def GetLineConnectionPointOnBox(self, point, box): case = self.ClassifyPointNearBox(point, box) xMax, yMax = uicore.ScaleDpi(box.width) * 0.5, uicore.ScaleDpi(box.height) * 0.5 xMin, yMin = -xMax, -yMax if case == CASE1: boxPoint = (xMin, yMin) elif case == CASE2: boxPoint = (0, yMin) elif case == CASE3: boxPoint = (xMax, yMin) elif case == CASE4: boxPoint = (xMin, 0) elif case == CASE5: boxPoint = (xMax, 0)
def PointCameraTo(self, itemID, panSpeed=pi / 500, retrack=False): timeDelta = max( blue.os.TimeDiffInMs(self.lastTime, blue.os.GetWallclockTime()), 16) self.lastTime = blue.os.GetWallclockTime() camera = sm.GetService('sceneManager').GetRegisteredCamera('default') if camera is None: return shipBall = sm.GetService('michelle').GetBall(self.lookingAt) if shipBall is None: return itemBall = sm.GetService('michelle').GetBall(itemID) if not itemBall: return try: if itemBall.exploded: return except: return if itemBall.IsCloaked(): return maxDistance = shipBall.radius * 150 if camera.translationFromParent > maxDistance: if self.oldTracking != self.tracking: camera.translationFromParent -= 100 + ( camera.translationFromParent - maxDistance) / 10 else: return else: self.oldTracking = self.tracking shipPos = shipBall.GetVectorAt(blue.os.GetSimTime()) m, h = uicore.desktop.width / 2, uicore.desktop.height center = trinity.TriVector(m * (1 - camera.centerOffset), h / 2, 0) itemPos = itemBall.GetVectorAt(blue.os.GetSimTime()) v2 = shipPos - itemPos dx2 = center.x - self.trackingPointX dy2 = center.y - self.trackingPointY v2.Normalize() yzProj = trinity.TriVector(0, v2.y, v2.z) zxProj = trinity.TriVector(v2.x, 0, v2.z) yaw = self.CalcAngle(zxProj.z, zxProj.x) pitch = -asin(yzProj.y) oldYaw = camera.yaw oldPitch = camera.pitch alphaX = pi * dx2 * camera.fieldOfView / uicore.desktop.width alphaY = pi * dy2 * camera.fieldOfView / uicore.desktop.width dPitchTotal = pitch - camera.pitch dYawTotal = (yaw - camera.yaw) % (2 * pi) - alphaX * 0.75 dPitchTotal = min(2 * pi - dPitchTotal, dPitchTotal) - alphaY * 0.75 if dYawTotal > pi: dYawTotal = -(2 * pi - dYawTotal) arc = geo2.Vec2Length((dYawTotal, dPitchTotal)) if self.previousTracking != self.tracking or self.retrack: self.retrack = False self.trackSwitchTime = blue.os.GetWallclockTime() self.previousTracking = self.tracking t = blue.os.GetWallclockTime() rampUp = 1 timeSinceTargetChange = 0 if t > self.trackSwitchTime: timeSinceTargetChange = min( float(blue.os.TimeDiffInMs(self.trackSwitchTime, t)), 5000.0) rampUp = min(timeSinceTargetChange / 2000.0, 1.0) panSpeed = pi / 500 * rampUp part = min(1, timeDelta * panSpeed) dYawPart = dYawTotal * part dPitchPart = dPitchTotal * part arcPart = geo2.Vec2Length((dYawPart, dPitchPart)) Yaw = oldYaw + dYawPart Pitch = oldPitch + dPitchPart camera.SetOrbit(Yaw, Pitch) br = sm.GetService('bracket') itemBracket = br.GetBracket(itemID) if not self.chaseCam and itemBracket and itemBracket not in br.overlaps: if itemBracket.renderObject is not None and itemBracket.renderObject.display: dx = self.trackingPointX - itemBracket.renderObject.displayX dy = self.trackingPointY - itemBracket.renderObject.displayY dist = geo2.Vec2Length((dx, dy)) tiltBrake = 25000 + 1000000 * pow( arc, 2) * (1 - min(timeSinceTargetChange / 5000, 1)) tdx = min(round(dx / tiltBrake, 4), 0.005) tdy = min(round(dy / tiltBrake, 4), 0.005) tiltAngle = geo2.Vec2Length((tdx, tdy)) if tiltAngle > 0.0005: self.tiltX += tdx self.tiltY += tdy self.tiltX = fmod(self.tiltX, pi * 2) self.tiltY = fmod(self.tiltY, pi * 2) camera.SetRotationOnOrbit(self.tiltX, self.tiltY)