Example #1
0
 def __init__(self, dataSec, aim):
     CallbackDelayer.__init__(self)
     TimeDeltaMeter.__init__(self)
     self.__shiftKeySensor = None
     self.__movementOscillator = None
     self.__impulseOscillator = None
     self.__noiseOscillator = None
     self.__dynamicCfg = CameraDynamicConfig()
     self.__accelerationSmoother = None
     self.__readCfg(dataSec)
     self.__cam = None
     self.__aim = None
     self.__onChangeControlMode = None
     self.__aimingSystem = None
     self.__curSense = 0
     self.__curScrollSense = 0
     self.__postmortemMode = False
     self.__vehiclesToCollideWith = set()
     self.__focalPointDist = 1.0
     self.__autoUpdateDxDyDz = Vector3(0.0)
     self.__defaultAimOffset = (0.0, 0.0)
     if aim is None:
         return
     else:
         self.__aim = weakref.proxy(aim)
         self.__cam = BigWorld.HomingCamera()
         aimOffset = self.__aim.offset()
         self.__cam.aimPointClipCoords = Vector2(aimOffset)
         self.__defaultAimOffset = (aimOffset[0], aimOffset[1])
         return
Example #2
0
 def __init__(self, dataSec, defaultOffset = None, binoculars = None):
     CallbackDelayer.__init__(self)
     self.__impulseOscillator = None
     self.__movementOscillator = None
     self.__noiseOscillator = None
     self.__dynamicCfg = CameraDynamicConfig()
     self.__accelerationSmoother = None
     self.__readCfg(dataSec)
     if binoculars is None:
         return
     else:
         self.__cam = BigWorld.FreeCamera()
         self.__zoom = self.__cfg['zoom']
         self.__curSense = 0
         self.__curScrollSense = 0
         self.__waitVehicleCallbackId = None
         self.__onChangeControlMode = None
         self.__aimingSystem = SniperAimingSystem(dataSec)
         self.__binoculars = binoculars
         self.__defaultAimOffset = defaultOffset or Vector2()
         self.__crosshairMatrix = createCrosshairMatrix(offsetFromNearPlane=self.__dynamicCfg['aimMarkerDistance'])
         self.__prevTime = BigWorld.time()
         self.__autoUpdateDxDyDz = Vector3(0, 0, 0)
         if BattleReplay.g_replayCtrl.isPlaying:
             BattleReplay.g_replayCtrl.setDataCallback('applyZoom', self.__applyZoom)
         return
Example #3
0
 def __init__(self, dataSec, aim, binoculars):
     CallbackDelayer.__init__(self)
     self.__impulseOscillator = None
     self.__movementOscillator = None
     self.__noiseOscillator = None
     self.__dynamicCfg = CameraDynamicConfig()
     self.__accelerationSmoother = None
     self.__readCfg(dataSec)
     if aim is None or binoculars is None:
         return
     else:
         self.__cam = BigWorld.FreeCamera()
         self.__zoom = self.__cfg['zoom']
         self.__curSense = 0
         self.__curScrollSense = 0
         self.__waitVehicleCallbackId = None
         self.__onChangeControlMode = None
         self.__aimingSystem = SniperAimingSystem(dataSec)
         self.__aim = weakref.proxy(aim)
         self.__binoculars = binoculars
         self.__defaultAimOffset = self.__aim.offset()
         self.__defaultAimOffset = (self.__defaultAimOffset[0], self.__defaultAimOffset[1])
         self.__crosshairMatrix = createCrosshairMatrix(offsetFromNearPlane=self.__dynamicCfg['aimMarkerDistance'])
         self.__prevTime = BigWorld.time()
         self.__autoUpdateDxDyDz = Vector3(0, 0, 0)
         return
    def __init__(self, dataSec, defaultOffset = None):
        CallbackDelayer.__init__(self)
        TimeDeltaMeter.__init__(self)
        self.__shiftKeySensor = None
        self.__movementOscillator = None
        self.__impulseOscillator = None
        self.__noiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__accelerationSmoother = None
        self.__readCfg(dataSec)
        self.__onChangeControlMode = None
        self.__aimingSystem = None
        self.__curSense = 0
        self.__curScrollSense = 0
        self.__postmortemMode = False
        self.__vehiclesToCollideWith = set()
        self.__focalPointDist = 1.0
        self.__autoUpdateDxDyDz = Vector3(0.0)
        self.__updatedByKeyboard = False
        if defaultOffset is not None:
            self.__defaultAimOffset = defaultOffset
            self.__cam = BigWorld.HomingCamera(self.__adCfg['enable'])
            if self.__adCfg['enable']:
                self.__cam.initAdvancedCollider(self.__adCfg['fovRatio'], self.__adCfg['rollbackSpeed'], self.__adCfg['minimalCameraDistance'], self.__adCfg['speedThreshold'], self.__adCfg['minimalVolume'])
                for group_name in VOLUME_GROUPS_NAMES:
                    self.__cam.addVolumeGroup(self.__adCfg['volumeGroups'][group_name])

            self.__cam.aimPointClipCoords = defaultOffset
        else:
            self.__defaultAimOffset = Vector2()
            self.__cam = None
        return
Example #5
0
 def __init__(self, dataSec, defaultOffset = None):
     CallbackDelayer.__init__(self)
     TimeDeltaMeter.__init__(self)
     self.__shiftKeySensor = None
     self.__movementOscillator = None
     self.__impulseOscillator = None
     self.__noiseOscillator = None
     self.__dynamicCfg = CameraDynamicConfig()
     self.__accelerationSmoother = None
     self.__readCfg(dataSec)
     self.__onChangeControlMode = None
     self.__aimingSystem = None
     self.__curSense = 0
     self.__curScrollSense = 0
     self.__postmortemMode = False
     self.__vehiclesToCollideWith = set()
     self.__focalPointDist = 1.0
     self.__autoUpdateDxDyDz = Vector3(0.0)
     self.__updatedByKeyboard = False
     if defaultOffset is not None:
         self.__defaultAimOffset = defaultOffset
         self.__cam = BigWorld.HomingCamera()
         self.__cam.aimPointClipCoords = defaultOffset
     else:
         self.__defaultAimOffset = Vector2()
         self.__cam = None
     return
Example #6
0
 def __init__(self, dataSec, aim):
     CallbackDelayer.__init__(self)
     self.__positionOscillator = None
     self.__positionNoiseOscillator = None
     self.__dynamicCfg = CameraDynamicConfig()
     self.__readCfg(dataSec)
     self.__cam = BigWorld.CursorCamera()
     self.__curSense = self.__cfg['sensitivity']
     self.__onChangeControlMode = None
     self.__aimingSystem = StrategicAimingSystem(self.__cfg['distRange'][0], StrategicCamera._CAMERA_YAW)
     self.__prevTime = BigWorld.time()
     self.__aimOffsetFunc = None
     if aim is None:
         self.__aimOffsetFunc = lambda x = None: (0, 0)
     else:
         self.__aimOffsetFunc = aim.offset
     self.__autoUpdatePosition = False
     self.__dxdydz = Vector3(0, 0, 0)
     self.__needReset = 0
Example #7
0
class ArcadeCamera(ICamera, CallbackDelayer, TimeDeltaMeter):
    REASONS_AFFECT_CAMERA_DIRECTLY = (ImpulseReason.MY_SHOT,
     ImpulseReason.OTHER_SHOT,
     ImpulseReason.VEHICLE_EXPLOSION,
     ImpulseReason.HE_EXPLOSION)
    _DYNAMIC_ENABLED = True

    @staticmethod
    def enableDynamicCamera(enable):
        ArcadeCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return ArcadeCamera._DYNAMIC_ENABLED

    _FILTER_LENGTH = 5
    _DEFAULT_MAX_ACCELERATION_DURATION = 1.5
    _FOCAL_POINT_CHANGE_SPEED = 100.0
    _FOCAL_POINT_MIN_DIFF = 5.0
    _MIN_REL_SPEED_ACC_SMOOTHING = 0.7

    def getReasonsAffectCameraDirectly(self):
        return ArcadeCamera.REASONS_AFFECT_CAMERA_DIRECTLY

    def __getVehicleMProv(self):
        refinedMProv = self.__aimingSystem.vehicleMProv
        return refinedMProv.b.source

    def __setVehicleMProv(self, vehicleMProv):
        prevAimRel = self.__cam.aimPointProvider.a if self.__cam.aimPointProvider is not None else None
        refinedVehicleMProv = self.__refineVehicleMProv(vehicleMProv)
        self.__aimingSystem.vehicleMProv = refinedVehicleMProv
        self.__setupCameraProviders(refinedVehicleMProv)
        self.__cam.speedTreeTarget = self.__aimingSystem.vehicleMProv
        self.__aimingSystem.update(0.0)
        if prevAimRel is not None:
            self.__cam.aimPointProvider.a = prevAimRel
        baseTranslation = Matrix(refinedVehicleMProv).translation
        relativePosition = self.__aimingSystem.matrix.translation - baseTranslation
        self.__setCameraPosition(relativePosition)
        return

    camera = property(lambda self: self.__cam)
    angles = property(lambda self: (self.__aimingSystem.yaw, self.__aimingSystem.pitch))
    aimingSystem = property(lambda self: self.__aimingSystem)
    vehicleMProv = property(__getVehicleMProv, __setVehicleMProv)

    def __init__(self, dataSec, aim):
        CallbackDelayer.__init__(self)
        TimeDeltaMeter.__init__(self)
        self.__shiftKeySensor = None
        self.__movementOscillator = None
        self.__impulseOscillator = None
        self.__noiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__accelerationSmoother = None
        self.__readCfg(dataSec)
        self.__cam = None
        self.__aim = None
        self.__onChangeControlMode = None
        self.__aimingSystem = None
        self.__curSense = 0
        self.__curScrollSense = 0
        self.__postmortemMode = False
        self.__vehiclesToCollideWith = set()
        self.__focalPointDist = 1.0
        self.__autoUpdateDxDyDz = Vector3(0.0)
        self.__defaultAimOffset = (0.0, 0.0)
        if aim is None:
            return
        else:
            self.__aim = weakref.proxy(aim)
            self.__cam = BigWorld.HomingCamera()
            aimOffset = self.__aim.offset()
            self.__cam.aimPointClipCoords = Vector2(aimOffset)
            self.__defaultAimOffset = (aimOffset[0], aimOffset[1])
            return

    def create(self, pivotPos, onChangeControlMode = None, postmortemMode = False):
        self.__onChangeControlMode = onChangeControlMode
        self.__postmortemMode = postmortemMode
        targetMat = self.getTargetMProv()
        self.__aimingSystem = ArcadeAimingSystem(self.__refineVehicleMProv(targetMat), pivotPos.y, pivotPos.z, self.__calcAimMatrix(), self.__cfg['angleRange'], not postmortemMode)
        self.setCameraDistance(self.__cfg['startDist'])
        self.__aimingSystem.pitch = self.__cfg['startAngle']
        self.__aimingSystem.yaw = Math.Matrix(targetMat).yaw
        self.__updateAngles(0, 0)
        cameraPosProvider = Math.Vector4Translation(self.__aimingSystem.matrix)
        self.__cam.cameraPositionProvider = cameraPosProvider

    def getTargetMProv(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.playerVehicleID != 0:
            vehicleID = replayCtrl.playerVehicleID
        else:
            vehicleID = BigWorld.player().playerVehicleID
        return BigWorld.entity(vehicleID).matrix

    def reinitMatrix(self):
        self.__setVehicleMProv(self.getTargetMProv())

    def setToVehicleDirection(self):
        matrix = Math.Matrix(self.getTargetMProv())
        self.setYawPitch(matrix.yaw, matrix.pitch)

    def destroy(self):
        CallbackDelayer.destroy(self)
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        self.__aim = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        return

    def getPivotSettings(self):
        return self.__aimingSystem.getPivotSettings()

    def setPivotSettings(self, heightAboveBase, focusRadius):
        self.__aimingSystem.setPivotSettings(heightAboveBase, focusRadius)

    def addVehicleToCollideWith(self, vehicle):
        self.__vehiclesToCollideWith.add(vehicle)
        self.__setModelsToCollideWith(self.__vehiclesToCollideWith)

    def removeVehicleToCollideWith(self, vehicle):
        lengthBefore = len(self.__vehiclesToCollideWith)
        self.__vehiclesToCollideWith = set([ v for v in self.__vehiclesToCollideWith if v.id != vehicle.id ])
        if lengthBefore != len(self.__vehiclesToCollideWith):
            self.__setModelsToCollideWith(self.__vehiclesToCollideWith)

    def clearVehicleToCollideWith(self):
        self.__vehiclesToCollideWith = set()
        self.__setModelsToCollideWith(self.__vehiclesToCollideWith)

    def __setModelsToCollideWith(self, vehicles):
        colliders = []
        for vehicle in vehicles:
            vehicleAppearance = vehicle.appearance
            compound = vehicleAppearance.compoundModel
            colliders.append((compound.node(TankPartNames.HULL), compound.bounds, compound.getPartGeometryLink(TankPartIndexes.HULL)))
            if not vehicle.isTurretDetached:
                colliders.append((compound.node(TankPartNames.TURRET), compound.bounds, compound.getPartGeometryLink(TankPartIndexes.TURRET)))

        self.__cam.setDynamicColliders(colliders)
        self.__aimingSystem.setDynamicColliders(colliders)

    def focusOnPos(self, preferredPos):
        self.__aimingSystem.focusOnPos(preferredPos)

    def shiftCamPos(self, shift = None):
        matrixProduct = self.__aimingSystem.vehicleMProv
        shiftMat = matrixProduct.a
        if shift is not None:
            camDirection = Math.Vector3(math.sin(self.angles[0]), 0, math.cos(self.angles[0]))
            normal = Math.Vector3(camDirection)
            normal.x = camDirection.z
            normal.z = -camDirection.x
            shiftMat.translation += camDirection * shift.z + Math.Vector3(0, 1, 0) * shift.y + normal * shift.x
        else:
            shiftMat.setIdentity()
        return

    def enable(self, preferredPos = None, closesDist = False, postmortemParams = None, turretYaw = None, gunPitch = None):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isRecording:
            replayCtrl.setAimClipPosition(Vector2(self.__aim.offset()))
        self.measureDeltaTime()
        camDist = None
        vehicle = BigWorld.player().getVehicleAttached()
        initialVehicleMatrix = BigWorld.player().getOwnVehicleMatrix() if vehicle is None else vehicle.matrix
        vehicleMProv = initialVehicleMatrix
        if not self.__postmortemMode:
            if closesDist:
                camDist = self.__cfg['distRange'][0]
        elif postmortemParams is not None:
            self.__aimingSystem.yaw = postmortemParams[0][0]
            self.__aimingSystem.pitch = postmortemParams[0][1]
            camDist = postmortemParams[1]
        else:
            camDist = self.__cfg['distRange'][1]
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying:
            camDist = None
            vehicle = BigWorld.entity(replayCtrl.playerVehicleID)
            if vehicle is not None:
                vehicleMProv = vehicle.matrix
        if camDist is not None:
            self.setCameraDistance(camDist)
        else:
            self.__inputInertia.teleport(self.__calcRelativeDist())
        self.vehicleMProv = vehicleMProv
        self.__setModelsToCollideWith(self.__vehiclesToCollideWith)
        BigWorld.camera(self.__cam)
        self.__aimingSystem.enable(preferredPos, turretYaw, gunPitch)
        self.__aimingSystem.aimMatrix = self.__calcAimMatrix()
        self.__cameraUpdate()
        self.delayCallback(0.0, self.__cameraUpdate)
        from gui import g_guiResetters
        g_guiResetters.add(self.__onRecreateDevice)
        return

    def __refineVehicleMProv(self, vehicleMProv):
        vehicleTranslationOnly = Math.WGTranslationOnlyMP()
        vehicleTranslationOnly.source = vehicleMProv
        refinedMatrixProvider = Math.MatrixProduct()
        refinedMatrixProvider.a = mathUtils.createIdentityMatrix()
        refinedMatrixProvider.b = vehicleTranslationOnly
        return refinedMatrixProvider

    def __setupCameraProviders(self, vehicleMProv):
        vehiclePos = Math.Vector4Translation(vehicleMProv)
        cameraPositionProvider = Math.Vector4Combiner()
        cameraPositionProvider.fn = 'ADD'
        cameraPositionProvider.a = Vector4(0, 0, 0, 0)
        cameraPositionProvider.b = vehiclePos
        cameraAimPointProvider = Math.Vector4Combiner()
        cameraAimPointProvider.fn = 'ADD'
        cameraAimPointProvider.a = Vector4(0, 0, 1, 0)
        cameraAimPointProvider.b = vehiclePos
        self.__cam.cameraPositionProvider = cameraPositionProvider
        self.__cam.aimPointProvider = cameraAimPointProvider
        self.__cam.pivotPositionProvider = self.__aimingSystem.positionAboveVehicleProv

    def __setCameraPosition(self, relativeToVehiclePosition):
        self.__cam.cameraPositionProvider.a = Vector4(relativeToVehiclePosition.x, relativeToVehiclePosition.y, relativeToVehiclePosition.z, 1.0)

    def __setCameraAimPoint(self, relativeToVehiclePosition):
        self.__cam.aimPointProvider.a = Vector4(relativeToVehiclePosition.x, relativeToVehiclePosition.y, relativeToVehiclePosition.z, 1.0)

    def disable(self):
        from gui import g_guiResetters
        if self.__onRecreateDevice in g_guiResetters:
            g_guiResetters.remove(self.__onRecreateDevice)
        self.__setModelsToCollideWith([])
        self.__cam.speedTreeTarget = None
        BigWorld.camera(None)
        if self.__shiftKeySensor is not None:
            self.__shiftKeySensor.reset(Math.Vector3())
        self.stopCallback(self.__cameraUpdate)
        self.__movementOscillator.reset()
        self.__impulseOscillator.reset()
        self.__noiseOscillator.reset()
        self.__accelerationSmoother.reset()
        self.__autoUpdateDxDyDz.set(0)
        self.__inputInertia.teleport(self.__calcRelativeDist())
        FovExtended.instance().resetFov()
        return

    def update(self, dx, dy, dz, rotateMode = True, zoomMode = True, updatedByKeyboard = False):
        self.__curSense = self.__cfg['keySensitivity'] if updatedByKeyboard else self.__cfg['sensitivity']
        self.__curScrollSense = self.__cfg['keySensitivity'] if updatedByKeyboard else self.__cfg['scrollSensitivity']
        if updatedByKeyboard:
            self.__autoUpdateDxDyDz.set(dx, dy, dz)
        else:
            self.__autoUpdateDxDyDz.set(0)
            self.__update(dx, dy, dz, rotateMode, zoomMode, False)

    def restoreDefaultsState(self):
        LOG_ERROR('ArcadeCamera::restoreDefaultState is obsolete!')

    def getConfigValue(self, name):
        return self.__cfg.get(name)

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        else:
            self.__userCfg[name] = value
            if name not in ('keySensitivity', 'sensitivity', 'scrollSensitivity'):
                self.__cfg[name] = self.__userCfg[name]
                if name == 'fovMultMinMaxDist' and getattr(self, '_ArcadeCamera__aimingSystem', None) is not None:
                    self.__inputInertia.teleport(self.__calcRelativeDist(), value)
            else:
                self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]
            return

    def setCameraDistance(self, distance):
        distRange = self.__cfg['distRange']
        clampedDist = mathUtils.clamp(distRange[0], distRange[1], distance)
        self.__aimingSystem.distanceFromFocus = clampedDist
        self.__inputInertia.teleport(self.__calcRelativeDist())

    def getCameraDistance(self):
        return self.__aimingSystem.distanceFromFocus

    def setYawPitch(self, yaw, pitch):
        self.__aimingSystem.yaw = yaw
        self.__aimingSystem.pitch = pitch
        matrix = Math.Matrix()
        matrix.setRotateYPR((self.__aimingSystem.yaw, self.__aimingSystem.pitch, 0))

    def calcYawPitchDelta(self, dx, dy):
        return (dx * self.__curSense * (-1 if self.__cfg['horzInvert'] else 1), dy * self.__curSense * (-1 if self.__cfg['vertInvert'] else 1))

    def __updateAngles(self, dx, dy):
        yawDelta, pitchDelta = self.calcYawPitchDelta(dx, dy)
        self.__aimingSystem.handleMovement(yawDelta, -pitchDelta)
        return (self.__aimingSystem.yaw, self.__aimingSystem.pitch, 0)

    def __update(self, dx, dy, dz, rotateMode = True, zoomMode = True, isCallback = False):
        prevPos = self.__inputInertia.calcWorldPos(self.__aimingSystem.matrix)
        distChanged = False
        if zoomMode and dz != 0:
            prevDist = self.__aimingSystem.distanceFromFocus
            distDelta = dz * float(self.__curScrollSense)
            distMinMax = self.__cfg['distRange']
            newDist = mathUtils.clamp(distMinMax.min, distMinMax.max, prevDist - distDelta)
            if abs(newDist - prevDist) > 0.001:
                self.__aimingSystem.distanceFromFocus = newDist
                self.__userCfg['startDist'] = newDist
                self.__inputInertia.glideFov(self.__calcRelativeDist())
                self.__aimingSystem.aimMatrix = self.__calcAimMatrix()
                distChanged = True
            changeControlMode = prevDist == newDist and mathUtils.almostZero(newDist - distMinMax.min)
            if changeControlMode and self.__onChangeControlMode is not None:
                self.__onChangeControlMode()
                return
        if rotateMode:
            self.__updateAngles(dx, dy)
        if ENABLE_INPUT_ROTATION_INERTIA and not distChanged:
            self.__aimingSystem.update(0.0)
        if ENABLE_INPUT_ROTATION_INERTIA or distChanged:
            worldDeltaPos = prevPos - self.__aimingSystem.matrix.translation
            matInv = Matrix(self.__aimingSystem.matrix)
            matInv.invert()
            self.__inputInertia.glide(matInv.applyVector(worldDeltaPos))
        return

    def __cameraUpdate(self):
        if not (self.__autoUpdateDxDyDz.x == 0.0 and self.__autoUpdateDxDyDz.y == 0.0 and self.__autoUpdateDxDyDz.z == 0.0):
            self.__update(self.__autoUpdateDxDyDz.x, self.__autoUpdateDxDyDz.y, self.__autoUpdateDxDyDz.z)
        inertDt = deltaTime = self.measureDeltaTime()
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying:
            repSpeed = replayCtrl.playbackSpeed
            if repSpeed == 0.0:
                inertDt = 0.01
                deltaTime = 0.0
            else:
                inertDt = deltaTime = deltaTime / repSpeed
        self.__aimingSystem.update(deltaTime)
        virginShotPoint = self.__aimingSystem.getThirdPersonShotPoint()
        delta = self.__inputInertia.positionDelta
        sign = delta.dot(Vector3(0, 0, 1))
        self.__inputInertia.update(inertDt)
        delta = (delta - self.__inputInertia.positionDelta).length
        if delta != 0.0:
            self.__cam.setScrollDelta(math.copysign(delta, sign))
        FovExtended.instance().setFovByMultiplier(self.__inputInertia.fovZoomMultiplier)
        unshakenPos = self.__inputInertia.calcWorldPos(self.__aimingSystem.idealMatrix if self.__cam.advancedColliderEnabled else self.__aimingSystem.matrix)
        vehMatrix = Math.Matrix(self.__aimingSystem.vehicleMProv)
        vehiclePos = vehMatrix.translation
        fromVehicleToUnshakedPos = unshakenPos - vehiclePos
        deviationBasis = mathUtils.createRotationMatrix(Vector3(self.__aimingSystem.yaw, 0, 0))
        impulseDeviation, movementDeviation, oscillationsZoomMultiplier = self.__updateOscillators(deltaTime)
        relCamPosMatrix = mathUtils.createTranslationMatrix(impulseDeviation + movementDeviation)
        relCamPosMatrix.postMultiply(deviationBasis)
        relCamPosMatrix.translation += fromVehicleToUnshakedPos
        upRotMat = mathUtils.createRotationMatrix(Vector3(0, 0, -impulseDeviation.x * self.__dynamicCfg['sideImpulseToRollRatio'] - self.__noiseOscillator.deviation.z))
        upRotMat.postMultiply(relCamPosMatrix)
        self.__cam.up = upRotMat.applyVector(Vector3(0, 1, 0))
        relTranslation = relCamPosMatrix.translation
        self.__setCameraPosition(relTranslation)
        shotPoint = self.__calcFocalPoint(virginShotPoint, deltaTime)
        vehToShotPoint = shotPoint - vehiclePos
        self.__setCameraAimPoint(vehToShotPoint)
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
        else:
            aimOffset = self.__calcAimOffset(oscillationsZoomMultiplier)
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__cam.aimPointClipCoords = aimOffset
        self.__aim.offset(aimOffset)
        if self.__shiftKeySensor is not None:
            self.__shiftKeySensor.update(1.0)
            if self.__shiftKeySensor.currentVelocity.lengthSquared > 0.0:
                self.shiftCamPos(self.__shiftKeySensor.currentVelocity)
                self.__shiftKeySensor.currentVelocity = Math.Vector3()
        return 0.0

    def __calcFocalPoint(self, shotPoint, deltaTime):
        aimStartPoint = self.__aimingSystem.matrix.translation
        aimDir = shotPoint - aimStartPoint
        distToShotPoint = aimDir.length
        if distToShotPoint < 0.001:
            return shotPoint
        aimDir /= distToShotPoint
        absDiff = abs(distToShotPoint - self.__focalPointDist)
        if absDiff < ArcadeCamera._FOCAL_POINT_MIN_DIFF or absDiff <= ArcadeCamera._FOCAL_POINT_CHANGE_SPEED * deltaTime:
            self.__focalPointDist = distToShotPoint
            return shotPoint
        changeDir = (distToShotPoint - self.__focalPointDist) / absDiff
        self.__focalPointDist += changeDir * ArcadeCamera._FOCAL_POINT_CHANGE_SPEED * deltaTime
        return aimStartPoint + aimDir * self.__focalPointDist

    def __calcAimOffset(self, oscillationsZoomMultiplier):
        fov = BigWorld.projection().fov
        aspect = cameras.getScreenAspectRatio()
        yTan = math.tan(fov * 0.5)
        xTan = yTan * aspect
        defaultX = self.__defaultAimOffset[0]
        defaultY = self.__defaultAimOffset[1]
        yawFromImpulse = self.__impulseOscillator.deviation.x * self.__dynamicCfg['sideImpulseToYawRatio']
        xImpulseDeviationTan = math.tan(-(yawFromImpulse + self.__noiseOscillator.deviation.x) * oscillationsZoomMultiplier)
        pitchFromImpulse = self.__impulseOscillator.deviation.z * self.__dynamicCfg['frontImpulseToPitchRatio']
        yImpulseDeviationTan = math.tan((pitchFromImpulse + self.__noiseOscillator.deviation.y) * oscillationsZoomMultiplier)
        totalOffset = Vector2((defaultX * xTan + xImpulseDeviationTan) / (xTan * (1 - defaultX * xTan * xImpulseDeviationTan)), (defaultY * yTan + yImpulseDeviationTan) / (yTan * (1 - defaultY * yTan * yImpulseDeviationTan)))
        return totalOffset

    def __calcRelativeDist(self):
        distRange = self.__cfg['distRange']
        curDist = self.__aimingSystem.distanceFromFocus
        relDist = (curDist - distRange[0]) / (distRange[1] - distRange[0])
        return relDist

    def __calcCurOscillatorAcceleration(self, deltaTime):
        vehicle = BigWorld.player().getVehicleAttached()
        if vehicle is None:
            return Vector3(0, 0, 0)
        else:
            vehFilter = vehicle.filter
            curVelocity = getattr(vehFilter, 'velocity', Vector3(0.0))
            relativeSpeed = curVelocity.length / vehicle.typeDescriptor.physics['speedLimits'][0]
            if relativeSpeed >= ArcadeCamera._MIN_REL_SPEED_ACC_SMOOTHING:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg['accelerationThreshold']
            else:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg['accelerationMax']
            acceleration = self.__accelerationSmoother.update(vehicle, deltaTime)
            yawMat = mathUtils.createRotationMatrix((-self.__aimingSystem.yaw, 0, 0))
            acceleration = yawMat.applyVector(-acceleration)
            oscillatorAcceleration = Vector3(acceleration.x, acceleration.y, acceleration.z)
            return oscillatorAcceleration * self.__dynamicCfg['accelerationSensitivity']

    def __updateOscillators(self, deltaTime):
        if not ArcadeCamera.isCameraDynamic():
            self.__impulseOscillator.reset()
            self.__movementOscillator.reset()
            self.__noiseOscillator.reset()
            return (Vector3(0), Vector3(0), 1.0)
        oscillatorAcceleration = self.__calcCurOscillatorAcceleration(deltaTime)
        self.__movementOscillator.externalForce += oscillatorAcceleration
        self.__impulseOscillator.update(deltaTime)
        self.__movementOscillator.update(deltaTime)
        self.__noiseOscillator.update(deltaTime)
        self.__impulseOscillator.externalForce.set(0, 0, 0)
        self.__movementOscillator.externalForce.set(0, 0, 0)
        self.__noiseOscillator.externalForce.set(0, 0, 0)
        relDist = self.__calcRelativeDist()
        zoomMultiplier = mathUtils.lerp(1.0, self.__dynamicCfg['zoomExposure'], relDist)
        impulseDeviation = Vector3(self.__impulseOscillator.deviation)
        impulseDeviation.set(impulseDeviation.x * zoomMultiplier, impulseDeviation.y * zoomMultiplier, impulseDeviation.z * zoomMultiplier)
        movementDeviation = Vector3(self.__movementOscillator.deviation)
        movementDeviation.set(movementDeviation.x * zoomMultiplier, movementDeviation.y * zoomMultiplier, movementDeviation.z * zoomMultiplier)
        return (impulseDeviation, movementDeviation, zoomMultiplier)

    def applyImpulse(self, position, impulse, reason = ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(impulse, reason)
        yawMat = mathUtils.createRotationMatrix((-self.__aimingSystem.yaw, 0, 0))
        impulseLocal = yawMat.applyVector(adjustedImpulse)
        self.__impulseOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self, position, impulseValue, reason = ImpulseReason.ME_HIT):
        applicationPosition = self.__cam.position
        if reason == ImpulseReason.SPLASH:
            applicationPosition = Matrix(self.vehicleMProv).translation
        impulse = applicationPosition - position
        distance = impulse.length
        if distance < 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.OTHER_SHOT and distance <= self.__dynamicCfg['maxShotImpulseDistance']:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.SPLASH or reason == ImpulseReason.HE_EXPLOSION:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.VEHICLE_EXPLOSION and distance <= self.__dynamicCfg['maxExplosionImpulseDistance']:
            impulse *= impulseValue / distance
        else:
            return
        self.applyImpulse(position, impulse, reason)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3(noiseMagnitude)
        self.__noiseOscillator.applyImpulse(noiseImpulse)

    def handleKeyEvent(self, isDown, key, mods, event = None):
        if self.__shiftKeySensor is None:
            return False
        elif BigWorld.isKeyDown(Keys.KEY_CAPSLOCK) and mods & 4:
            if key == Keys.KEY_C:
                self.shiftCamPos()
            return self.__shiftKeySensor.handleKeyEvent(key, isDown)
        else:
            self.__shiftKeySensor.reset(Math.Vector3())
            return False

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection('gui/avatar_input_handler.xml/arcadeMode/camera/')
        self.__readCfg(cameraSec)

    def __calcAimMatrix(self):
        endMult = self.__inputInertia.endZoomMultiplier
        fov = FovExtended.instance().actualDefaultVerticalFov * endMult
        offset = self.__defaultAimOffset
        return cameras.getAimMatrix(-offset[0], -offset[1], fov)

    def __readCfg(self, dataSec):
        if dataSec is None:
            LOG_WARNING('Invalid section <arcadeMode/camera> in avatar_input_handler.xml')
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0, 10, 0.01)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0, 10, 0.01)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0, 10, 0.01)
        bcfg['angleRange'] = readVec2(dataSec, 'angleRange', (0, 0), (180, 180), (10, 110))
        distRangeVec = readVec2(dataSec, 'distRange', (1, 1), (100, 100), (2, 20))
        bcfg['distRange'] = MinMax(distRangeVec.x, distRangeVec.y)
        bcfg['minStartDist'] = readFloat(dataSec, 'minStartDist', bcfg['distRange'][0], bcfg['distRange'][1], bcfg['distRange'][0])
        bcfg['optimalStartDist'] = readFloat(dataSec, 'optimalStartDist', bcfg['distRange'][0], bcfg['distRange'][1], bcfg['distRange'][0])
        bcfg['angleRange'][0] = math.radians(bcfg['angleRange'][0]) - math.pi * 0.5
        bcfg['angleRange'][1] = math.radians(bcfg['angleRange'][1]) - math.pi * 0.5
        bcfg['fovMultMinMaxDist'] = MinMax(readFloat(dataSec, 'fovMultMinDist', 0.1, 100, 1.0), readFloat(dataSec, 'fovMultMaxDist', 0.1, 100, 1.0))
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['arcadeMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        from account_helpers.settings_core.SettingsCore import g_settingsCore
        ucfg['horzInvert'] = g_settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = g_settingsCore.getSetting('mouseVertInvert')
        ucfg['sniperModeByShift'] = g_settingsCore.getSetting('sniperModeByShift')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0, 1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0, 10.0, 1.0)
        ucfg['startDist'] = readFloat(ds, 'startDist', bcfg['distRange'][0], 500, bcfg['optimalStartDist'])
        if ucfg['startDist'] < bcfg['minStartDist']:
            ucfg['startDist'] = bcfg['optimalStartDist']
        ucfg['startAngle'] = readFloat(ds, 'startAngle', 5, 180, 60)
        ucfg['startAngle'] = math.radians(ucfg['startAngle']) - math.pi * 0.5
        ucfg['fovMultMinMaxDist'] = MinMax(readFloat(ds, 'fovMultMinDist', 0.1, 100, bcfg['fovMultMinMaxDist'].min), readFloat(ds, 'fovMultMaxDist', 0.1, 100, bcfg['fovMultMinMaxDist'].max))
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['angleRange'] = bcfg['angleRange']
        cfg['distRange'] = bcfg['distRange']
        cfg['minStartDist'] = bcfg['minStartDist']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['startDist'] = ucfg['startDist']
        cfg['startAngle'] = ucfg['startAngle']
        cfg['fovMultMinMaxDist'] = ucfg['fovMultMinMaxDist']
        cfg['sniperModeByShift'] = ucfg['sniperModeByShift']
        enableShift = dataSec.readBool('shift', False)
        if enableShift:
            movementMappings = dict()
            movementMappings[Keys.KEY_A] = Math.Vector3(-1, 0, 0)
            movementMappings[Keys.KEY_D] = Math.Vector3(1, 0, 0)
            movementMappings[Keys.KEY_Q] = Math.Vector3(0, 1, 0)
            movementMappings[Keys.KEY_E] = Math.Vector3(0, -1, 0)
            movementMappings[Keys.KEY_W] = Math.Vector3(0, 0, 1)
            movementMappings[Keys.KEY_S] = Math.Vector3(0, 0, -1)
            shiftSensitivity = dataSec.readFloat('shiftSensitivity', 0.5)
            self.__shiftKeySensor = KeySensor(movementMappings, shiftSensitivity)
            self.__shiftKeySensor.reset(Math.Vector3())
        dynamicsSection = dataSec['dynamics']
        self.__impulseOscillator = createOscillatorFromSection(dynamicsSection['impulseOscillator'], False)
        self.__movementOscillator = createOscillatorFromSection(dynamicsSection['movementOscillator'], False)
        self.__movementOscillator = CompoundOscillator(self.__movementOscillator, Oscillator(1.0, Vector3(50), Vector3(20), Vector3(0.01, 0.0, 0.01)))
        self.__noiseOscillator = createOscillatorFromSection(dynamicsSection['randomNoiseOscillatorSpherical'])
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__dynamicCfg['accelerationSensitivity'] = readFloat(dynamicsSection, 'accelerationSensitivity', -1000, 1000, 0.1)
        self.__dynamicCfg['frontImpulseToPitchRatio'] = math.radians(readFloat(dynamicsSection, 'frontImpulseToPitchRatio', -1000, 1000, 0.1))
        self.__dynamicCfg['sideImpulseToRollRatio'] = math.radians(readFloat(dynamicsSection, 'sideImpulseToRollRatio', -1000, 1000, 0.1))
        self.__dynamicCfg['sideImpulseToYawRatio'] = math.radians(readFloat(dynamicsSection, 'sideImpulseToYawRatio', -1000, 1000, 0.1))
        accelerationThreshold = readFloat(dynamicsSection, 'accelerationThreshold', 0.0, 1000.0, 0.1)
        self.__dynamicCfg['accelerationThreshold'] = accelerationThreshold
        self.__dynamicCfg['accelerationMax'] = readFloat(dynamicsSection, 'accelerationMax', 0.0, 1000.0, 0.1)
        self.__dynamicCfg['maxShotImpulseDistance'] = readFloat(dynamicsSection, 'maxShotImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['maxExplosionImpulseDistance'] = readFloat(dynamicsSection, 'maxExplosionImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['zoomExposure'] = readFloat(dynamicsSection, 'zoomExposure', 0.0, 1000.0, 0.25)
        accelerationFilter = mathUtils.RangeFilter(self.__dynamicCfg['accelerationThreshold'], self.__dynamicCfg['accelerationMax'], 100, mathUtils.SMAFilter(ArcadeCamera._FILTER_LENGTH))
        maxAccelerationDuration = readFloat(dynamicsSection, 'maxAccelerationDuration', 0.0, 10000.0, ArcadeCamera._DEFAULT_MAX_ACCELERATION_DURATION)
        self.__accelerationSmoother = AccelerationSmoother(accelerationFilter, maxAccelerationDuration)
        self.__inputInertia = _InputInertia(self.__cfg['fovMultMinMaxDist'], 0.0)
        advancedCollision = dataSec['advancedCollision']
        if advancedCollision is None:
            LOG_ERROR('<advancedCollision> dataSection is not found!')
        else:
            BigWorld.setAdvancedColliderConstants(AdvancedColliderConstants.fromSection(advancedCollision), cfg['distRange'][0])
        return

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeBool('arcadeMode/camera/horzInvert', ucfg['horzInvert'])
        ds.writeBool('arcadeMode/camera/vertInvert', ucfg['vertInvert'])
        ds.writeFloat('arcadeMode/camera/keySensitivity', ucfg['keySensitivity'])
        ds.writeFloat('arcadeMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('arcadeMode/camera/scrollSensitivity', ucfg['scrollSensitivity'])
        ds.writeFloat('arcadeMode/camera/startDist', ucfg['startDist'])
        ds.writeFloat('arcadeMode/camera/fovMultMinDist', ucfg['fovMultMinMaxDist'].min)
        ds.writeFloat('arcadeMode/camera/fovMultMaxDist', ucfg['fovMultMinMaxDist'].max)
        ucfg['startAngle'] = math.degrees(ucfg['startAngle'] + math.pi * 0.5)
        ds.writeFloat('arcadeMode/camera/startAngle', ucfg['startAngle'])

    def __onRecreateDevice(self):
        self.__aimingSystem.aimMatrix = self.__calcAimMatrix()
Example #8
0
class StrategicCamera(ICamera, CallbackDelayer):
    _CAMERA_YAW = 0.0
    _DYNAMIC_ENABLED = True
    ABSOLUTE_VERTICAL_FOV = math.radians(60.0)

    @staticmethod
    def enableDynamicCamera(enable):
        StrategicCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return StrategicCamera._DYNAMIC_ENABLED

    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)

    def __init__(self, dataSec, aim):
        CallbackDelayer.__init__(self)
        self.__positionOscillator = None
        self.__positionNoiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__readCfg(dataSec)
        self.__cam = BigWorld.CursorCamera()
        self.__curSense = self.__cfg['sensitivity']
        self.__onChangeControlMode = None
        self.__aimingSystem = StrategicAimingSystem(self.__cfg['distRange'][0], StrategicCamera._CAMERA_YAW)
        self.__prevTime = BigWorld.time()
        self.__aimOffsetFunc = None
        if aim is None:
            self.__aimOffsetFunc = lambda x = None: (0, 0)
        else:
            self.__aimOffsetFunc = aim.offset
        self.__autoUpdatePosition = False
        self.__dxdydz = Vector3(0, 0, 0)
        self.__needReset = 0

    def create(self, onChangeControlMode = None):
        self.__onChangeControlMode = onChangeControlMode
        self.__camDist = self.__cfg['camDist']
        self.__cam.pivotMaxDist = 0.0
        self.__cam.maxDistHalfLife = 0.01
        self.__cam.movementHalfLife = 0.0
        self.__cam.turningHalfLife = -1
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)

    def destroy(self):
        CallbackDelayer.destroy(self)
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        self._writeUserPreferences()
        self.__aimingSystem.destroy()
        self.__aimingSystem = None

    def enable(self, targetPos, saveDist):
        self.__prevTime = BigWorld.time()
        self.__aimingSystem.enable(targetPos)
        srcMat = mathUtils.createRotationMatrix((0.0, -math.pi * 0.499, 0.0))
        self.__cam.source = srcMat
        if not saveDist:
            self.__camDist = self.__cfg['camDist']
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        camTarget = Math.MatrixProduct()
        camTarget.b = self.__aimingSystem.matrix
        self.__cam.target = camTarget
        BigWorld.camera(self.__cam)
        BigWorld.player().positionControl.moveTo(self.__aimingSystem.matrix.translation)
        BigWorld.player().positionControl.followCamera(True)
        FovExtended.instance().enabled = False
        BigWorld.projection().fov = StrategicCamera.ABSOLUTE_VERTICAL_FOV
        self.__cameraUpdate()
        self.delayCallback(0.0, self.__cameraUpdate)
        self.__needReset = 1

    def disable(self):
        self.__aimingSystem.disable()
        self.stopCallback(self.__cameraUpdate)
        BigWorld.camera(None)
        BigWorld.player().positionControl.followCamera(False)
        self.__positionOscillator.reset()
        FovExtended.instance().resetFov()
        FovExtended.instance().enabled = True

    def teleport(self, pos):
        self.moveToPosition(pos)

    def setMaxDist(self):
        distRange = self.__cfg['distRange']
        if len(distRange) > 1:
            self.__camDist = distRange[1]

    def restoreDefaultsState(self):
        LOG_ERROR('StrategiCamera::restoreDefaultState is obsolete!')
        return
        vPos = BigWorld.player().getOwnVehiclePosition()
        self.__camDist = self.__cfg['camDist']
        self.__cam.pivotMaxDist = 0.0
        self.__cam.maxDistHalfLife = 0.01
        self.__cam.movementHalfLife = 0.0
        self.__cam.turningHalfLife = 0.01
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        self.__cam.target = trgMat
        BigWorld.player().positionControl.moveTo(self.__totalMove)

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        self.__userCfg[name] = value
        if name not in ('keySensitivity', 'sensitivity', 'scrollSensitivity'):
            self.__cfg[name] = self.__userCfg[name]
        else:
            self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]

    def update(self, dx, dy, dz, updatedByKeyboard = False):
        self.__curSense = self.__cfg['keySensitivity'] if updatedByKeyboard else self.__cfg['sensitivity']
        self.__autoUpdatePosition = updatedByKeyboard
        self.__dxdydz = Vector3(dx, dy, dz)

    def moveToPosition(self, pos):
        self.__aimingSystem.enable(pos)
        self.update(0.0, 0.0, 0.0)

    def calcVisibleAreaRatio(self):
        points = [Math.Vector2(1, 1),
         Math.Vector2(1, -1),
         Math.Vector2(-1, -1),
         Math.Vector2(-1, 1)]
        dirsPos = [ getWorldRayAndPoint(point.x, point.y) for point in points ]
        planeXZ = Plane(Math.Vector3(0, 1, 0), 0)
        collisionPoints = []
        for dir, begPos in dirsPos:
            endPos = begPos + dir * 1000
            testResult = BigWorld.wg_collideSegment(BigWorld.player().spaceID, begPos, endPos, 3)
            collPoint = Math.Vector3(0, 0, 0)
            if collPoint is not None:
                collPoint = testResult[0]
            else:
                collPoint = planeXZ.intersectSegment(begPos, endPos)
            collisionPoints.append(collPoint)

        x0 = abs(collisionPoints[1].x - collisionPoints[2].x)
        x1 = abs(collisionPoints[0].x - collisionPoints[3].x)
        z0 = abs(collisionPoints[0].z - collisionPoints[1].z)
        z1 = abs(collisionPoints[3].z - collisionPoints[2].z)
        bb = BigWorld.player().arena.arenaType.boundingBox
        arenaBottomLeft = bb[0]
        arenaUpperRight = bb[1]
        arenaX = arenaUpperRight[0] - arenaBottomLeft[0]
        arenaZ = arenaUpperRight[1] - arenaBottomLeft[1]
        return ((x0 + x1) * 0.5 / arenaX, (z0 + z1) * 0.5 / arenaZ)

    def applyImpulse(self, position, impulse, reason = ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(impulse, reason)
        impulseFlatDir = Vector3(adjustedImpulse.x, 0, adjustedImpulse.z)
        impulseFlatDir.normalise()
        cameraYawMat = mathUtils.createRotationMatrix(Vector3(-StrategicCamera._CAMERA_YAW, 0.0, 0.0))
        impulseLocal = cameraYawMat.applyVector(impulseFlatDir * (-1 * adjustedImpulse.length))
        self.__positionOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self, position, impulseValue, reason = ImpulseReason.ME_HIT):
        if reason != ImpulseReason.SPLASH and reason != ImpulseReason.PROJECTILE_HIT:
            return
        impulse = BigWorld.player().getOwnVehiclePosition() - position
        distance = impulse.length
        if distance <= 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.PROJECTILE_HIT:
            if not cameras.isPointOnScreen(position):
                return
            distance = 1.0
        impulse *= impulseValue / distance
        self.applyImpulse(position, impulse, reason)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3Flat(noiseMagnitude)
        self.__positionNoiseOscillator.applyImpulse(noiseImpulse)

    def _writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeFloat('strategicMode/camera/keySensitivity', ucfg['keySensitivity'])
        ds.writeFloat('strategicMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('strategicMode/camera/scrollSensitivity', ucfg['scrollSensitivity'])
        ds.writeFloat('strategicMode/camera/camDist', self.__cfg['camDist'])

    def __cameraUpdate(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
        else:
            aimOffset = self.__calcAimOffset()
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__aimOffsetFunc(aimOffset)
        shotDescr = BigWorld.player().vehicleTypeDescriptor.shot
        BigWorld.wg_trajectory_drawer().setParams(shotDescr['maxDistance'], Math.Vector3(0, -shotDescr['gravity'], 0), self.__aimOffsetFunc())
        curTime = BigWorld.time()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        if replayCtrl.isPlaying:
            if self.__needReset != 0:
                if self.__needReset > 1:
                    from helpers import isPlayerAvatar
                    player = BigWorld.player()
                    if isPlayerAvatar():
                        if player.inputHandler.ctrl is not None:
                            player.inputHandler.ctrl.resetGunMarkers()
                    self.__needReset = 0
                else:
                    self.__needReset += 1
            if replayCtrl.isControllingCamera:
                self.__aimingSystem.updateTargetPos(replayCtrl.getGunRotatorTargetPoint())
            else:
                self.__aimingSystem.handleMovement(self.__dxdydz.x * self.__curSense, -self.__dxdydz.y * self.__curSense)
                if self.__dxdydz.x != 0 or self.__dxdydz.y != 0 or self.__dxdydz.z != 0:
                    self.__needReset = 2
        else:
            self.__aimingSystem.handleMovement(self.__dxdydz.x * self.__curSense, -self.__dxdydz.y * self.__curSense)
        distRange = self.__cfg['distRange']
        self.__camDist -= self.__dxdydz.z * float(self.__curSense)
        maxPivotHeight = distRange[1] - distRange[0]
        self.__camDist = mathUtils.clamp(0, maxPivotHeight, self.__camDist)
        self.__cfg['camDist'] = self.__camDist
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        if self.__dxdydz.z != 0 and self.__onChangeControlMode is not None and mathUtils.almostZero(self.__camDist - maxPivotHeight):
            self.__onChangeControlMode()
        self.__updateOscillator(deltaTime)
        if not self.__autoUpdatePosition:
            self.__dxdydz = Vector3(0, 0, 0)
        return 0.0

    def __calcAimOffset(self):
        aimWorldPos = self.__aimingSystem.matrix.applyPoint(Vector3(0, -self.__aimingSystem.height, 0))
        aimOffset = cameras.projectPoint(aimWorldPos)
        return Vector2(mathUtils.clamp(-0.95, 0.95, aimOffset.x), mathUtils.clamp(-0.95, 0.95, aimOffset.y))

    def __updateOscillator(self, deltaTime):
        if StrategicCamera.isCameraDynamic():
            self.__positionOscillator.update(deltaTime)
            self.__positionNoiseOscillator.update(deltaTime)
        else:
            self.__positionOscillator.reset()
            self.__positionNoiseOscillator.reset()
        self.__cam.target.a = mathUtils.createTranslationMatrix(self.__positionOscillator.deviation + self.__positionNoiseOscillator.deviation)

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection('gui/avatar_input_handler.xml/strategicMode/camera/')
        self.__readCfg(cameraSec)

    def __readCfg(self, dataSec):
        if not dataSec:
            LOG_WARNING('Invalid section <strategicMode/camera> in avatar_input_handler.xml')
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.005, 10, 0.025)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.005, 10, 0.025)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0.005, 10, 0.025)
        bcfg['distRange'] = readVec2(dataSec, 'distRange', (1, 1), (10000, 10000), (2, 30))
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['strategicMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        from account_helpers.settings_core.SettingsCore import g_settingsCore
        ucfg['horzInvert'] = g_settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = g_settingsCore.getSetting('mouseVertInvert')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0, 1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0, 10.0, 1.0)
        ucfg['camDist'] = readFloat(ds, 'camDist', 0.0, 60.0, 0)
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['distRange'] = bcfg['distRange']
        cfg['camDist'] = ucfg['camDist']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        dynamicsSection = dataSec['dynamics']
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__positionOscillator = createOscillatorFromSection(dynamicsSection['oscillator'], False)
        self.__positionNoiseOscillator = createOscillatorFromSection(dynamicsSection['randomNoiseOscillatorFlat'], False)
Example #9
0
class SniperCamera(ICamera, CallbackDelayer):
    _DYNAMIC_ENABLED = True

    @staticmethod
    def enableDynamicCamera(enable):
        SniperCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return SniperCamera._DYNAMIC_ENABLED

    _FILTER_LENGTH = 5
    _DEFAULT_MAX_ACCELERATION_DURATION = 1.5
    _MIN_REL_SPEED_ACC_SMOOTHING = 0.7
    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)

    def __init__(self, dataSec, aim, binoculars):
        CallbackDelayer.__init__(self)
        self.__impulseOscillator = None
        self.__movementOscillator = None
        self.__noiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__accelerationSmoother = None
        self.__readCfg(dataSec)
        if aim is None or binoculars is None:
            return
        else:
            self.__cam = BigWorld.FreeCamera()
            self.__zoom = self.__cfg['zoom']
            self.__curSense = 0
            self.__curScrollSense = 0
            self.__waitVehicleCallbackId = None
            self.__onChangeControlMode = None
            self.__aimingSystem = SniperAimingSystem(dataSec)
            self.__aim = weakref.proxy(aim)
            self.__binoculars = binoculars
            self.__defaultAimOffset = self.__aim.offset()
            self.__defaultAimOffset = (self.__defaultAimOffset[0], self.__defaultAimOffset[1])
            self.__crosshairMatrix = createCrosshairMatrix(offsetFromNearPlane=self.__dynamicCfg['aimMarkerDistance'])
            self.__prevTime = BigWorld.time()
            self.__autoUpdateDxDyDz = Vector3(0, 0, 0)
            return

    def create(self, onChangeControlMode = None):
        self.__onChangeControlMode = onChangeControlMode

    def destroy(self):
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        self._writeUserPreferences()
        self.__aimingSystem.destroy()
        self.__aimingSystem = None
        self.__aim = None
        CallbackDelayer.destroy(self)
        return

    def enable(self, targetPos, saveZoom):
        self.__prevTime = BigWorld.time()
        player = BigWorld.player()
        if saveZoom:
            self.__zoom = self.__cfg['zoom']
        else:
            self.__cfg['zoom'] = self.__zoom = self.__cfg['zooms'][0]
        self.__applyZoom(self.__zoom)
        self.__setupCamera(targetPos)
        vehicle = BigWorld.entity(player.playerVehicleID)
        if self.__waitVehicleCallbackId is not None:
            BigWorld.cancelCallback(self.__waitVehicleCallbackId)
        if vehicle is None:
            self.__whiteVehicleCallbackId = BigWorld.callback(0.1, self.__waitVehicle)
        else:
            self.__showVehicle(False)
        BigWorld.camera(self.__cam)
        if self.__cameraUpdate(False) >= 0.0:
            self.delayCallback(0.0, self.__cameraUpdate)
        return

    def disable(self):
        BigWorld.camera(None)
        if self.__waitVehicleCallbackId is not None:
            BigWorld.cancelCallback(self.__waitVehicleCallbackId)
            self.__waitVehicleCallbackId = None
        self.__showVehicle(True)
        self.stopCallback(self.__cameraUpdate)
        self.__aimingSystem.disable()
        self.__movementOscillator.reset()
        self.__impulseOscillator.reset()
        self.__noiseOscillator.reset()
        self.__accelerationSmoother.reset()
        self.__autoUpdateDxDyDz.set(0)
        FovExtended.instance().resetFov()
        return

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        self.__userCfg[name] = value
        if name not in ('keySensitivity', 'sensitivity', 'scrollSensitivity'):
            self.__cfg[name] = self.__userCfg[name]
        else:
            self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]

    def update(self, dx, dy, dz, updatedByKeyboard = False):
        self.__curSense = self.__cfg['keySensitivity'] if updatedByKeyboard else self.__cfg['sensitivity']
        self.__curScrollSense = self.__cfg['keySensitivity'] if updatedByKeyboard else self.__cfg['scrollSensitivity']
        self.__curSense *= 1.0 / self.__zoom
        if updatedByKeyboard:
            self.__autoUpdateDxDyDz.set(dx, dy, dz)
        else:
            self.__autoUpdateDxDyDz.set(0, 0, 0)
            self.__rotateAndZoom(dx, dy, dz)

    def onRecreateDevice(self):
        self.__applyZoom(self.__zoom)

    def applyImpulse(self, position, impulse, reason = ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(impulse, reason)
        camMatrix = Matrix(self.__cam.matrix)
        impulseLocal = camMatrix.applyVector(adjustedImpulse)
        impulseAsYPR = Vector3(impulseLocal.x, -impulseLocal.y + impulseLocal.z, 0)
        rollPart = self.__dynamicCfg['impulsePartToRoll']
        impulseAsYPR.z = -rollPart * impulseAsYPR.x
        impulseAsYPR.x *= 1 - rollPart
        self.__impulseOscillator.applyImpulse(impulseAsYPR)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self, position, impulseValue, reason = ImpulseReason.ME_HIT):
        impulse = self.__cam.position - position
        distance = impulse.length
        if distance < 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.OTHER_SHOT and distance <= self.__dynamicCfg['maxShotImpulseDistance']:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.SPLASH:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.VEHICLE_EXPLOSION and distance <= self.__dynamicCfg['maxExplosionImpulseDistance']:
            impulse *= impulseValue / distance
        else:
            return
        self.applyImpulse(position, impulse, reason)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3(noiseMagnitude)
        self.__noiseOscillator.applyImpulse(noiseImpulse)

    def __rotateAndZoom(self, dx, dy, dz):
        self.__aimingSystem.handleMovement(*self.__calcYawPitchDelta(dx, dy))
        self.__setupZoom(dz)

    def __calcYawPitchDelta(self, dx, dy):
        return (dx * self.__curSense * (-1 if self.__cfg['horzInvert'] else 1), dy * self.__curSense * (-1 if self.__cfg['vertInvert'] else 1))

    def __showVehicle(self, show):
        vehicle = BigWorld.entity(BigWorld.player().playerVehicleID)
        if vehicle is not None:
            vehicle.show(show)
        return

    def __setupCamera(self, targetPos):
        self.__aimingSystem.enable(targetPos)

    def __waitVehicle(self):
        vehicle = BigWorld.entity(BigWorld.player().playerVehicleID)
        if vehicle is not None and vehicle.isStarted:
            self.__waitVehicleCallbackId = None
        else:
            self.__waitVehicleCallbackId = BigWorld.callback(0.1, self.__waitVehicle)
            return
        self.__showVehicle(False)
        return

    def __applyZoom(self, zoomFactor):
        FovExtended.instance().setFovByMultiplier(1 / zoomFactor)

    def __setupZoom(self, dz):
        if dz == 0:
            return
        else:
            zooms = self.__cfg['zooms']
            prevZoom = self.__zoom
            if self.__zoom == zooms[0] and dz < 0 and self.__onChangeControlMode is not None:
                self.__onChangeControlMode(True)
            if dz > 0:
                for elem in zooms:
                    if self.__zoom < elem:
                        self.__zoom = elem
                        self.__cfg['zoom'] = self.__zoom
                        break

            elif dz < 0:
                for i in range(len(zooms) - 1, -1, -1):
                    if self.__zoom > zooms[i]:
                        self.__zoom = zooms[i]
                        self.__cfg['zoom'] = self.__zoom
                        break

            if prevZoom != self.__zoom:
                self.__applyZoom(self.__zoom)
            return

    def __cameraUpdate(self, allowModeChange = True):
        curTime = BigWorld.time()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        if not self.__autoUpdateDxDyDz.x == self.__autoUpdateDxDyDz.y == self.__autoUpdateDxDyDz.z == 0.0:
            self.__rotateAndZoom(self.__autoUpdateDxDyDz.x, self.__autoUpdateDxDyDz.y, self.__autoUpdateDxDyDz.z)
        self.__aimingSystem.update(deltaTime)
        localTransform, impulseTransform = self.__updateOscillators(deltaTime)
        aimMatrix = cameras.getAimMatrix(*self.__defaultAimOffset)
        camMat = Matrix(aimMatrix)
        rodMat = mathUtils.createTranslationMatrix(-self.__dynamicCfg['pivotShift'])
        antiRodMat = mathUtils.createTranslationMatrix(self.__dynamicCfg['pivotShift'])
        camMat.postMultiply(rodMat)
        camMat.postMultiply(localTransform)
        camMat.postMultiply(antiRodMat)
        camMat.postMultiply(self.__aimingSystem.matrix)
        camMat.invert()
        self.__cam.set(camMat)
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
            binocularsOffset = aimOffset
        else:
            aimOffset = self.__calcAimOffset(impulseTransform)
            binocularsOffset = self.__calcAimOffset()
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__aim.offset(aimOffset)
        self.__binoculars.setMaskCenter(binocularsOffset.x, binocularsOffset.y)
        player = BigWorld.player()
        if allowModeChange and (self.__isPositionUnderwater(self.__aimingSystem.matrix.translation) or player.isGunLocked):
            self.__onChangeControlMode(False)
            return -1
        return 0.0

    def __calcAimOffset(self, aimLocalTransform = None):
        worldCrosshair = Matrix(self.__crosshairMatrix)
        aimingSystemMatrix = self.__aimingSystem.matrix
        if aimLocalTransform is not None:
            worldCrosshair.postMultiply(aimLocalTransform)
        worldCrosshair.postMultiply(aimingSystemMatrix)
        aimOffset = cameras.projectPoint(worldCrosshair.translation)
        return Vector2(mathUtils.clamp(-0.95, 0.95, aimOffset.x), mathUtils.clamp(-0.95, 0.95, aimOffset.y))

    def __calcCurOscillatorAcceleration(self, deltaTime):
        vehicle = BigWorld.player().vehicle
        if vehicle is None:
            return Vector3(0, 0, 0)
        else:
            curVelocity = vehicle.filter.velocity
            relativeSpeed = curVelocity.length / vehicle.typeDescriptor.physics['speedLimits'][0]
            if relativeSpeed >= SniperCamera._MIN_REL_SPEED_ACC_SMOOTHING:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg['accelerationThreshold']
            else:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg['accelerationMax']
            acceleration = self.__accelerationSmoother.update(vehicle, deltaTime)
            camMat = Matrix(self.__cam.matrix)
            acceleration = camMat.applyVector(-acceleration)
            accelSensitivity = self.__dynamicCfg['accelerationSensitivity']
            acceleration.x *= accelSensitivity.x
            acceleration.y *= accelSensitivity.y
            acceleration.z *= accelSensitivity.z
            oscillatorAcceleration = Vector3(0, -acceleration.y + acceleration.z, -acceleration.x)
            return oscillatorAcceleration

    def __updateOscillators(self, deltaTime):
        if not SniperCamera.isCameraDynamic():
            self.__impulseOscillator.reset()
            self.__movementOscillator.reset()
            self.__noiseOscillator.reset()
            return (mathUtils.createRotationMatrix(Vector3(0, 0, 0)), mathUtils.createRotationMatrix(Vector3(0, 0, 0)))
        oscillatorAcceleration = self.__calcCurOscillatorAcceleration(deltaTime)
        self.__movementOscillator.externalForce += oscillatorAcceleration
        self.__impulseOscillator.update(deltaTime)
        self.__movementOscillator.update(deltaTime)
        self.__noiseOscillator.update(deltaTime)
        noiseDeviation = Vector3(self.__noiseOscillator.deviation)
        deviation = self.__impulseOscillator.deviation + self.__movementOscillator.deviation + noiseDeviation
        oscVelocity = self.__impulseOscillator.velocity + self.__movementOscillator.velocity + self.__noiseOscillator.velocity
        if abs(deviation.x) < 1e-05 and abs(oscVelocity.x) < 0.0001:
            deviation.x = 0
        if abs(deviation.y) < 1e-05 and abs(oscVelocity.y) < 0.0001:
            deviation.y = 0
        if abs(deviation.z) < 1e-05 and abs(oscVelocity.z) < 0.0001:
            deviation.z = 0
        curZoomIdx = 0
        zooms = self.__cfg['zooms']
        for idx in xrange(len(zooms)):
            if self.__zoom == zooms[idx]:
                curZoomIdx = idx
                break

        zoomExposure = self.__zoom * self.__dynamicCfg['zoomExposure'][curZoomIdx]
        deviation /= zoomExposure
        impulseDeviation = (self.__impulseOscillator.deviation + noiseDeviation) / zoomExposure
        self.__impulseOscillator.externalForce.set(0, 0, 0)
        self.__movementOscillator.externalForce.set(0, 0, 0)
        self.__noiseOscillator.externalForce.set(0, 0, 0)
        return (mathUtils.createRotationMatrix(Vector3(deviation.x, deviation.y, deviation.z)), mathUtils.createRotationMatrix(impulseDeviation))

    def __isPositionUnderwater(self, position):
        return BigWorld.wg_collideWater(position, position + Vector3(0, 1, 0), False) > -1.0

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection('gui/avatar_input_handler.xml/sniperMode/camera/')
        self.__readCfg(cameraSec)

    def __readCfg(self, dataSec):
        if not dataSec:
            LOG_WARNING('Invalid section <sniperMode/camera> in avatar_input_handler.xml')
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0, 10, 0.005)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0, 10, 0.005)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0, 10, 0.005)
        zooms = readVec3(dataSec, 'zooms', (0, 0, 0), (10, 10, 10), (2, 4, 8))
        bcfg['zooms'] = [zooms.x, zooms.y, zooms.z]
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['sniperMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        from account_helpers.settings_core.SettingsCore import g_settingsCore
        ucfg['horzInvert'] = g_settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = g_settingsCore.getSetting('mouseVertInvert')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0, 1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0, 10.0, 1.0)
        ucfg['zoom'] = readFloat(ds, 'zoom', 0.0, 10.0, bcfg['zooms'][0])
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['zooms'] = bcfg['zooms']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        cfg['zoom'] = ucfg['zoom']
        dynamicsSection = dataSec['dynamics']
        self.__impulseOscillator = createOscillatorFromSection(dynamicsSection['impulseOscillator'])
        self.__movementOscillator = createOscillatorFromSection(dynamicsSection['movementOscillator'])
        self.__noiseOscillator = createOscillatorFromSection(dynamicsSection['randomNoiseOscillatorSpherical'])
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__dynamicCfg['accelerationSensitivity'] = readVec3(dynamicsSection, 'accelerationSensitivity', (-1000, -1000, -1000), (1000, 1000, 1000), (0.5, 0.5, 0.5))
        accelerationThreshold = readFloat(dynamicsSection, 'accelerationThreshold', 0.0, 1000.0, 0.1)
        self.__dynamicCfg['accelerationThreshold'] = accelerationThreshold
        self.__dynamicCfg['accelerationMax'] = readFloat(dynamicsSection, 'accelerationMax', 0.0, 1000.0, 0.1)
        self.__dynamicCfg['maxShotImpulseDistance'] = readFloat(dynamicsSection, 'maxShotImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['maxExplosionImpulseDistance'] = readFloat(dynamicsSection, 'maxExplosionImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['impulsePartToRoll'] = readFloat(dynamicsSection, 'impulsePartToRoll', 0.0, 1000.0, 0.3)
        self.__dynamicCfg['pivotShift'] = Vector3(0, readFloat(dynamicsSection, 'pivotShift', -1000, 1000, -0.5), 0)
        self.__dynamicCfg['aimMarkerDistance'] = readFloat(dynamicsSection, 'aimMarkerDistance', -1000, 1000, 1.0)
        self.__dynamicCfg['zoomExposure'] = readVec3(dynamicsSection, 'zoomExposure', (0.1, 0.1, 0.1), (10, 10, 10), (0.5, 0.5, 0.5))
        accelerationFilter = mathUtils.RangeFilter(self.__dynamicCfg['accelerationThreshold'], self.__dynamicCfg['accelerationMax'], 100, mathUtils.SMAFilter(SniperCamera._FILTER_LENGTH))
        maxAccelerationDuration = readFloat(dynamicsSection, 'maxAccelerationDuration', 0.0, 10000.0, SniperCamera._DEFAULT_MAX_ACCELERATION_DURATION)
        self.__accelerationSmoother = AccelerationSmoother(accelerationFilter, maxAccelerationDuration)
        return

    def _writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeBool('sniperMode/camera/horzInvert', ucfg['horzInvert'])
        ds.writeBool('sniperMode/camera/vertInvert', ucfg['vertInvert'])
        ds.writeFloat('sniperMode/camera/keySensitivity', ucfg['keySensitivity'])
        ds.writeFloat('sniperMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('sniperMode/camera/scrollSensitivity', ucfg['scrollSensitivity'])
        ds.writeFloat('sniperMode/camera/zoom', self.__cfg['zoom'])
class ArtyCamera(CameraWithSettings, CallbackDelayer):
    _DYNAMIC_ENABLED = True

    @staticmethod
    def enableDynamicCamera(enable):
        ArtyCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return ArtyCamera._DYNAMIC_ENABLED

    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)
    __aimOffset = aih_global_binding.bindRW(
        aih_global_binding.BINDING_ID.AIM_OFFSET)

    def __init__(self, dataSec):
        super(ArtyCamera, self).__init__()
        CallbackDelayer.__init__(self)
        self.__positionOscillator = None
        self.__positionNoiseOscillator = None
        self.__switchers = CameraSwitcherCollection(cameraSwitchers=[
            CameraSwitcher(switchType=SwitchTypes.FROM_TRANSITION_DIST_AS_MAX,
                           switchToName=CTRL_MODE_NAME.STRATEGIC,
                           switchToPos=0.0)
        ],
                                                    isEnabled=True)
        self.__dynamicCfg = CameraDynamicConfig()
        self._readConfigs(dataSec)
        self.__cam = BigWorld.CursorCamera()
        self.__curSense = self._cfg['sensitivity']
        self.__onChangeControlMode = None
        self.__camDist = 0.0
        self.__desiredCamDist = 0.0
        self.__aimingSystem = None
        self.__prevTime = 0.0
        self.__prevAimPoint = Vector3()
        self.__dxdydz = Vector3(0.0, 0.0, 0.0)
        self.__autoUpdatePosition = False
        self.__needReset = 0
        self.__sourceMatrix = None
        self.__targetMatrix = None
        self.__rotation = 0.0
        self.__positionHysteresis = None
        self.__timeHysteresis = None
        self.__transitionEnabled = True
        self.__scrollSmoother = SPGScrollSmoother(0.3)
        self.__collisionDist = 0.0
        self.__camViewPoint = Vector3()
        return

    @staticmethod
    def _getConfigsKey():
        return ArtyCamera.__name__

    def create(self, onChangeControlMode=None):
        super(ArtyCamera, self).create()
        self.__onChangeControlMode = onChangeControlMode
        aimingSystemClass = ArtyAimingSystemRemote if BigWorld.player(
        ).isObserver() else ArtyAimingSystem
        self.__aimingSystem = aimingSystemClass()
        self.__camDist = self._cfg['camDist']
        self.__desiredCamDist = self.__camDist
        self.__positionHysteresis = PositionHysteresis(
            self._cfg['hPositionThresholdSq'])
        self.__timeHysteresis = TimeHysteresis(self._cfg['hTimeThreshold'])
        self.__cam.pivotMaxDist = 0.0
        self.__cam.maxDistHalfLife = 0.01
        self.__cam.movementHalfLife = 0.0
        self.__cam.turningHalfLife = -1
        self.__cam.pivotPosition = Vector3(0.0, 0.0, 0.0)
        self.__sourceMatrix = Matrix()
        self.__targetMatrix = Matrix()
        self.__rotation = 0.0
        self.__enableSwitchers()
        self.__scrollSmoother.setTime(self._cfg['scrollSmoothingTime'])

    def destroy(self):
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        CallbackDelayer.destroy(self)
        CameraWithSettings.destroy(self)
        return

    def enable(self,
               targetPos,
               saveDist,
               switchToPos=None,
               switchToPlace=None):
        BigWorld.wg_trajectory_drawer().setStrategicMode(False)
        self.__prevTime = 0.0
        if switchToPlace == SwitchToPlaces.TO_TRANSITION_DIST:
            self.__camDist = math_utils.clamp(self._cfg['distRange'][0],
                                              self._cfg['distRange'][1],
                                              self._cfg['transitionDist'])
        elif switchToPlace == SwitchToPlaces.TO_RELATIVE_POS and switchToPos is not None:
            minDist, maxDist = self._cfg['distRange']
            self.__camDist = (maxDist - minDist) * switchToPos + minDist
        elif switchToPlace == SwitchToPlaces.TO_NEAR_POS and switchToPos is not None:
            minDist, maxDist = self._cfg['distRange']
            self.__camDist = math_utils.clamp(minDist, maxDist, switchToPos)
        elif self.settingsCore.getSetting(SPGAim.AUTO_CHANGE_AIM_MODE):
            self.__camDist = math_utils.clamp(self._cfg['distRange'][0],
                                              self._cfg['transitionDist'],
                                              self.__camDist)
        self.__desiredCamDist = self.__camDist
        self.__scrollSmoother.start(self.__desiredCamDist)
        self.__enableSwitchers()
        self.__aimingSystem.enable(targetPos)
        self.__positionHysteresis.update(Vector3(0.0, 0.0, 0.0))
        self.__timeHysteresis.update(BigWorld.timeExact())
        camTarget = MatrixProduct()
        self.__rotation = max(self.__aimingSystem.direction.pitch,
                              self._cfg['minimalPitch'])
        cameraDirection = self.__getCameraDirection()
        self.__targetMatrix.translation = self.aimingSystem.aimPoint - cameraDirection.scale(
            self.__camDist)
        self.__cam.target = camTarget
        self.__cam.target.b = self.__targetMatrix
        self.__sourceMatrix = math_utils.createRotationMatrix(
            (cameraDirection.yaw, -cameraDirection.pitch, 0.0))
        self.__cam.source = self.__sourceMatrix
        self.__cam.forceUpdate()
        BigWorld.camera(self.__cam)
        BigWorld.player().positionControl.moveTo(self.__aimingSystem.hitPoint)
        BigWorld.player().positionControl.followCamera(False)
        self.__cameraUpdate()
        self.delayCallback(0.01, self.__cameraUpdate)
        self.__needReset = 1
        return

    def disable(self):
        self.__scrollSmoother.stop()
        if self.__aimingSystem is not None:
            self.__aimingSystem.disable()
        self.stopCallback(self.__cameraUpdate)
        self.__switchers.clear()
        self.__positionOscillator.reset()
        return

    def teleport(self, pos):
        self.__aimingSystem.updateTargetPos(pos)
        self.update(0.0, 0.0, 0.0)

    def getDistRatio(self):
        minDist, maxDist = self._cfg['distRange']
        return (self.__desiredCamDist - minDist) / (maxDist - minDist)

    def getCurrentCamDist(self):
        return self.__desiredCamDist

    def getCamDistRange(self):
        return self._cfg['distRange']

    def getCamTransitionDist(self):
        return self._cfg['transitionDist']

    def update(self, dx, dy, dz, updateByKeyboard=False):
        self.__curSense = self._cfg[
            'keySensitivity'] if updateByKeyboard else self._cfg['sensitivity']
        self.__autoUpdatePosition = updateByKeyboard
        self.__dxdydz = Vector3(dx if not self._cfg['horzInvert'] else -dx,
                                dy if not self._cfg['vertInvert'] else -dy, dz)

    def applyImpulse(self, position, impulse, reason=ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(
            impulse, reason)
        impulseFlatDir = Vector3(adjustedImpulse.x, 0, adjustedImpulse.z)
        impulseFlatDir.normalise()
        impulseLocal = self.__sourceMatrix.applyVector(
            impulseFlatDir * (-1 * adjustedImpulse.length))
        self.__positionOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self,
                            position,
                            impulseValue,
                            reason=ImpulseReason.ME_HIT):
        if reason != ImpulseReason.SPLASH and reason != ImpulseReason.PROJECTILE_HIT:
            return
        impulse = BigWorld.player().getOwnVehiclePosition() - position
        distance = impulse.length
        if distance <= 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.PROJECTILE_HIT:
            if not cameras.isPointOnScreen(position):
                return
            distance = 1.0
        impulse *= impulseValue / distance
        self.applyImpulse(position, impulse, reason)

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self._userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeBool('artyMode/camera/horzInvert', ucfg['horzInvert'])
        ds.writeBool('artyMode/camera/vertInvert', ucfg['vertInvert'])
        ds.writeFloat('artyMode/camera/keySensitivity', ucfg['keySensitivity'])
        ds.writeFloat('artyMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('artyMode/camera/scrollSensitivity',
                      ucfg['scrollSensitivity'])
        ds.writeFloat('artyMode/camera/camDist', self._cfg['camDist'])

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = math_utils.RandomVectors.random3Flat(noiseMagnitude)
        self.__positionNoiseOscillator.applyImpulse(noiseImpulse)

    def __calculateAimOffset(self, aimWorldPos):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
        else:
            proj = BigWorld.projection()
            aimLocalPos = Matrix(self.__cam.matrix).applyPoint(aimWorldPos)
            if aimLocalPos.z < 0:
                aimLocalPos.z = max(0.0,
                                    proj.nearPlane - _OFFSET_FROM_NEAR_PLANE)
                aimWorldPos = Matrix(
                    self.__cam.invViewMatrix).applyPoint(aimLocalPos)
            aimOffset = cameras.projectPoint(aimWorldPos)
            aimOffset = Vector2(math_utils.clamp(-0.95, 0.95, aimOffset.x),
                                math_utils.clamp(-0.95, 0.95, aimOffset.y))
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        return aimOffset

    def __handleMovement(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying:
            if self.__needReset != 0:
                if self.__needReset > 1:
                    from helpers import isPlayerAvatar
                    player = BigWorld.player()
                    if isPlayerAvatar():
                        if player.inputHandler.ctrl is not None:
                            player.inputHandler.ctrl.resetGunMarkers()
                    self.__needReset = 0
                else:
                    self.__needReset += 1
            if replayCtrl.isControllingCamera:
                self.__aimingSystem.updateTargetPos(
                    replayCtrl.getGunRotatorTargetPoint())
            else:
                self.__aimingSystem.handleMovement(
                    self.__dxdydz.x * self.__curSense,
                    -self.__dxdydz.y * self.__curSense)
                if self.__dxdydz.x != 0 or self.__dxdydz.y != 0 or self.__dxdydz.z != 0:
                    self.__needReset = 2
        else:
            self.__aimingSystem.handleMovement(
                self.__dxdydz.x * self.__curSense,
                -self.__dxdydz.y * self.__curSense)
        return

    def __getCameraDirection(self):
        direction = Vector3()
        direction.setPitchYaw(self.__rotation,
                              self.__aimingSystem.direction.yaw)
        direction.normalise()
        return direction

    def __getDesiredCameraDistance(self):
        distRange = self.__switchersDistRange()
        self.__desiredCamDist -= self.__dxdydz.z * self.__curSense
        self.__desiredCamDist = math_utils.clamp(distRange[0], distRange[1],
                                                 self.__desiredCamDist)
        self.__desiredCamDist = self.__aimingSystem.overrideCamDist(
            self.__desiredCamDist)
        self._cfg['camDist'] = self.__camDist
        self.__scrollSmoother.moveTo(self.__desiredCamDist, distRange)
        return self.__desiredCamDist

    def __updateTrajectoryDrawer(self):
        shotDescr = BigWorld.player().getVehicleDescriptor().shot
        BigWorld.wg_trajectory_drawer().setParams(
            shotDescr.maxDistance, Vector3(0, -shotDescr.gravity, 0),
            self.__aimOffset)

    def __updateTime(self):
        curTime = BigWorld.timeExact()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        return deltaTime

    def __choosePitchLevel(self, aimPoint):
        useHighPitch = (aimPoint - self.__aimingSystem.hitPoint
                        ).lengthSquared > self._cfg['highPitchThresholdSq']
        if useHighPitch:
            useHighPitch = self.__positionHysteresis.check(
                aimPoint) or self.__timeHysteresis.check(self.__prevTime)
        else:
            self.__positionHysteresis.update(aimPoint)
            self.__timeHysteresis.update(self.__prevTime)
        return useHighPitch

    def __getCollisionTestOrigin(self, aimPoint, direction):
        distRange = self.__switchersDistRange()
        vehiclePosition = BigWorld.player().getVehicleAttached().position
        collisionTestOrigin = Vector3(vehiclePosition)
        if direction.x * direction.x > direction.z * direction.z and not math_utils.almostZero(
                direction.x):
            collisionTestOrigin.y = direction.y / direction.x * (
                vehiclePosition.x - aimPoint.x) + aimPoint.y
        elif not math_utils.almostZero(direction.z):
            collisionTestOrigin.y = direction.y / direction.z * (
                vehiclePosition.z - aimPoint.z) + aimPoint.y
        else:
            collisionTestOrigin = aimPoint - direction.scale(
                (distRange[1] - distRange[0]) / 2.0)
            LOG_WARNING(
                "Can't find point on direction ray. Using half point as collision test origin"
            )
        return collisionTestOrigin

    def __resolveCollisions(self, aimPoint, distance, direction):
        distRange = self.__switchersDistRange()
        collisionDist = 0.0
        collisionTestOrigin = self.__getCollisionTestOrigin(
            aimPoint, direction)
        sign = copysign(
            1.0, distance * distance -
            (aimPoint - collisionTestOrigin).lengthSquared)
        srcPoint = collisionTestOrigin
        endPoint = aimPoint - direction.scale(
            distance + sign * (distRange[0] + _SEARCH_COLLISION_DEPTH))
        collision = collideDynamicAndStatic(
            srcPoint, endPoint, (BigWorld.player().playerVehicleID, ))
        if collision:
            collisionDistance = (aimPoint - collision[0]).length
            collisionDist = collisionDistance - sign * distRange[0]
            if sign * (collisionDistance - distance) < distRange[0]:
                distance = collisionDist
        if math_utils.almostZero(self.__rotation):
            srcPoint = aimPoint - direction.scale(distance)
            endPoint = aimPoint
            collision = collideDynamicAndStatic(
                srcPoint, endPoint, (BigWorld.player().playerVehicleID, ))
            if collision:
                self.__aimingSystem.aimPoint = collision[0]
                if collision[1]:
                    self.__positionHysteresis.update(aimPoint)
                    self.__timeHysteresis.update(self.__prevTime)
        return collisionDist

    def __calculateIdealState(self, deltaTime):
        aimPoint = self.__aimingSystem.aimPoint
        direction = self.__aimingSystem.direction
        impactPitch = max(direction.pitch, self._cfg['minimalPitch'])
        self.__rotation = max(self.__rotation, impactPitch)
        distRange = self.__switchersDistRange()
        distanceCurSq = (
            aimPoint -
            BigWorld.player().getVehicleAttached().position).lengthSquared
        distanceMinSq = distRange[0]**2.0
        forcedPitch = impactPitch
        if distanceCurSq < distanceMinSq:
            forcedPitch = atan2(sqrt(distanceMinSq - distanceCurSq),
                                sqrt(distanceCurSq))
        angularShift = self._cfg['angularSpeed'] * deltaTime
        angularShift = angularShift if self.__choosePitchLevel(
            aimPoint) else -angularShift
        minPitch = max(forcedPitch, impactPitch)
        maxPitch = max(forcedPitch, self._cfg['maximalPitch'])
        self.__rotation = math_utils.clamp(minPitch, maxPitch,
                                           self.__rotation + angularShift)
        desiredDistance = self.__getDesiredCameraDistance()
        cameraDirection = self.__getCameraDirection()
        collisionDist = self.__resolveCollisions(aimPoint, desiredDistance,
                                                 cameraDirection)
        collisionDist = max(distRange[0], collisionDist)
        desiredDistance = self.__scrollSmoother.update(deltaTime)
        rotation = Vector3(cameraDirection.yaw, -cameraDirection.pitch, 0.0)
        return (rotation, desiredDistance, collisionDist)

    def __interpolateStates(self, deltaTime, rotation, desiredDistance,
                            collisionDist):
        lerpParam = math_utils.clamp(
            0.0, 1.0, deltaTime * self._cfg['interpolationSpeed'])
        self.__sourceMatrix = slerp(self.__sourceMatrix, rotation, lerpParam)
        camDirection = Vector3()
        camDirection.setPitchYaw(-self.__sourceMatrix.pitch,
                                 self.__sourceMatrix.yaw)
        camDirection.normalise()
        self.__camViewPoint = math_utils.lerp(self.__camViewPoint,
                                              self.__aimingSystem.aimPoint,
                                              lerpParam)
        self.__collisionDist = math_utils.lerp(self.__collisionDist,
                                               collisionDist, lerpParam)
        desiredDistance = max(desiredDistance, self.__collisionDist)
        self.__targetMatrix.translation = self.__camViewPoint - camDirection.scale(
            desiredDistance)
        return (self.__sourceMatrix, self.__targetMatrix)

    def __cameraUpdate(self):
        deltaTime = self.__updateTime()
        self.__aimOffset = self.__calculateAimOffset(
            self.__aimingSystem.aimPoint)
        self.__handleMovement()
        aimPoint = Vector3(self.__aimingSystem.aimPoint)
        self.__updateTrajectoryDrawer()
        rotation, desiredDistance, collisionDist = self.__calculateIdealState(
            deltaTime)
        self.__interpolateStates(deltaTime,
                                 math_utils.createRotationMatrix(rotation),
                                 desiredDistance, collisionDist)
        self.__camDist = aimPoint - self.__targetMatrix.translation
        self.__camDist = self.__camDist.length
        self.__cam.source = self.__sourceMatrix
        self.__cam.target.b = self.__targetMatrix
        self.__cam.pivotPosition = Vector3(0, 0, 0)
        if aimPoint.distSqrTo(self.__prevAimPoint) > 0.010000000000000002:
            BigWorld.player().positionControl.moveTo(aimPoint)
            self.__prevAimPoint = aimPoint
        self.__updateOscillator(deltaTime)
        self.__aimingSystem.update(deltaTime)
        if self.__onChangeControlMode is not None and self.__switchers.needToSwitch(
                self.__dxdydz.z, self.__desiredCamDist,
                self._cfg['distRange'][0], self._cfg['distRange'][1],
                self._cfg['transitionDist']):
            self.__onChangeControlMode(*self.__switchers.getSwitchParams())
        if not self.__transitionEnabled and self.__desiredCamDist - TRANSITION_DIST_HYSTERESIS <= self._cfg[
                'transitionDist']:
            self.__transitionEnabled = True
            self.__enableSwitchers(False)
        if not self.__autoUpdatePosition:
            self.__dxdydz = Vector3(0, 0, 0)
        return 0.01

    def __updateOscillator(self, deltaTime):
        if ArtyCamera.isCameraDynamic():
            self.__positionOscillator.update(deltaTime)
            self.__positionNoiseOscillator.update(deltaTime)
        else:
            self.__positionOscillator.reset()
            self.__positionNoiseOscillator.reset()
        self.__cam.target.a = math_utils.createTranslationMatrix(
            self.__positionOscillator.deviation +
            self.__positionNoiseOscillator.deviation)

    def __switchersDistRange(self):
        distRange = self._cfg['distRange']
        if self.__switchers.isEnabled():
            distRange = self.__switchers.getDistLimitationForSwitch(
                distRange[0], distRange[1], self._cfg['transitionDist'])
        return distRange

    def _readConfigs(self, dataSec):
        if not dataSec:
            LOG_WARNING(
                'Invalid section <artyMode/camera> in avatar_input_handler.xml'
            )
        super(ArtyCamera, self)._readConfigs(dataSec)
        dynamicSection = dataSec['dynamics']
        self.__dynamicCfg.readImpulsesConfig(dynamicSection)
        self.__positionOscillator = createOscillatorFromSection(
            dynamicSection['oscillator'], False)
        self.__positionNoiseOscillator = createOscillatorFromSection(
            dynamicSection['randomNoiseOscillatorFlat'])

    def _readBaseCfg(self, dataSec):
        bcfg = self._baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.005,
                                           10.0, 0.025)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.005, 10.0,
                                        0.025)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity',
                                              0.005, 10.0, 0.025)
        bcfg['angularSpeed'] = readFloat(dataSec, 'angularSpeed', pi / 720.0,
                                         pi / 0.5, pi / 360.0)
        bcfg['distRange'] = readVec2(dataSec, 'distRange', (1.0, 1.0),
                                     (10000.0, 10000.0), (2.0, 30.0))
        bcfg['transitionDist'] = readFloat(dataSec, 'transitionDist', 1.0,
                                           10000.0, 60.0)
        bcfg['minimalPitch'] = readFloat(dataSec, 'minimalPitch', pi / 36.0,
                                         pi / 3.0, pi / 18.0)
        bcfg['maximalPitch'] = readFloat(dataSec, 'maximalPitch', pi / 6.0,
                                         pi / 3.0, pi / 3.5)
        bcfg['interpolationSpeed'] = readFloat(dataSec, 'interpolationSpeed',
                                               0.0, 10.0, 5.0)
        bcfg['highPitchThreshold'] = readFloat(dataSec, 'highPitchThreshold',
                                               0.1, 10.0, 3.0)
        bcfg['hTimeThreshold'] = readFloat(dataSec, 'hysteresis/timeThreshold',
                                           0.0, 10.0, 0.5)
        bcfg['hPositionThreshold'] = readFloat(dataSec,
                                               'hysteresis/positionThreshold',
                                               0.0, 100.0, 7.0)
        bcfg['scrollSmoothingTime'] = readFloat(dataSec, 'scrollSmoothingTime',
                                                0.0, 1.0, 0.3)

    def _readUserCfg(self):
        ucfg = self._userCfg
        dataSec = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if dataSec is not None:
            dataSec = dataSec['artyMode/camera']
        ucfg['horzInvert'] = False
        ucfg['vertInvert'] = False
        ucfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.0,
                                           10.0, 1.0)
        ucfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity',
                                              0.0, 10.0, 1.0)
        ucfg['camDist'] = readFloat(dataSec, 'camDist', 0.0, 60.0, 0.0)
        return

    def _makeCfg(self):
        bcfg = self._baseCfg
        ucfg = self._userCfg
        cfg = self._cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['distRange'] = bcfg['distRange']
        cfg['transitionDist'] = bcfg['transitionDist']
        cfg['angularSpeed'] = bcfg['angularSpeed']
        cfg['minimalPitch'] = bcfg['minimalPitch']
        cfg['maximalPitch'] = bcfg['maximalPitch']
        cfg['interpolationSpeed'] = bcfg['interpolationSpeed']
        cfg['highPitchThresholdSq'] = bcfg['highPitchThreshold'] * bcfg[
            'highPitchThreshold']
        cfg['hTimeThreshold'] = bcfg['hTimeThreshold']
        cfg['hPositionThresholdSq'] = bcfg['hPositionThreshold'] * bcfg[
            'hPositionThreshold']
        cfg['scrollSmoothingTime'] = bcfg['scrollSmoothingTime']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['camDist'] = ucfg['camDist']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']

    def _handleSettingsChange(self, diff):
        super(ArtyCamera, self)._handleSettingsChange(diff)
        if SPGAim.AUTO_CHANGE_AIM_MODE in diff:
            self.__enableSwitchers()
        if SPGAim.SCROLL_SMOOTHING_ENABLED in diff:
            self.__scrollSmoother.setIsEnabled(
                self.settingsCore.getSetting(SPGAim.SCROLL_SMOOTHING_ENABLED))

    def _updateSettingsFromServer(self):
        super(ArtyCamera, self)._updateSettingsFromServer()
        if self.settingsCore.isReady:
            self.__enableSwitchers()
            self.__scrollSmoother.setIsEnabled(
                self.settingsCore.getSetting(SPGAim.SCROLL_SMOOTHING_ENABLED))

    def __enableSwitchers(self, updateTransitionEnabled=True):
        if updateTransitionEnabled and self.__desiredCamDist - TRANSITION_DIST_HYSTERESIS >= self._cfg[
                'transitionDist']:
            self.__transitionEnabled = False
        if self.settingsCore.isReady:
            self.__switchers.setIsEnabled(
                self.settingsCore.getSetting(SPGAim.AUTO_CHANGE_AIM_MODE)
                and self.__transitionEnabled)
Example #11
0
class SniperCamera(ICamera, CallbackDelayer):
    _DYNAMIC_ENABLED = True

    @staticmethod
    def enableDynamicCamera(enable):
        SniperCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return SniperCamera._DYNAMIC_ENABLED

    _FILTER_LENGTH = 5
    _DEFAULT_MAX_ACCELERATION_DURATION = 1.5
    _MIN_REL_SPEED_ACC_SMOOTHING = 0.7
    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)
    settingsCore = dependency.descriptor(ISettingsCore)
    __aimOffset = aih_global_binding.bindRW(
        aih_global_binding.BINDING_ID.AIM_OFFSET)
    __zoomFactor = aih_global_binding.bindRW(
        aih_global_binding.BINDING_ID.ZOOM_FACTOR)

    def __init__(self, dataSec, defaultOffset=None, binoculars=None):
        CallbackDelayer.__init__(self)
        self.__impulseOscillator = None
        self.__movementOscillator = None
        self.__noiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__accelerationSmoother = None
        self.__readCfg(dataSec)
        if binoculars is None:
            return
        else:
            self.__cam = BigWorld.FreeCamera()
            self.__zoom = self.__cfg['zoom']
            self.__curSense = 0
            self.__curScrollSense = 0
            self.__waitVehicleCallbackId = None
            self.__onChangeControlMode = None
            self.__aimingSystem = None
            self.__binoculars = binoculars
            self.__defaultAimOffset = defaultOffset or Vector2()
            self.__crosshairMatrix = createCrosshairMatrix(
                offsetFromNearPlane=self.__dynamicCfg['aimMarkerDistance'])
            self.__prevTime = BigWorld.time()
            self.__autoUpdateDxDyDz = Vector3(0, 0, 0)
            if BattleReplay.g_replayCtrl.isPlaying:
                BattleReplay.g_replayCtrl.setDataCallback(
                    'applyZoom', self.__applySerializedZoom)
            return

    def __onSettingsChanged(self, diff):
        if 'increasedZoom' in diff:
            self.__cfg['increasedZoom'] = diff['increasedZoom']
            if not self.__cfg['increasedZoom']:
                self.__cfg['zoom'] = self.__zoom = self.__cfg['zooms'][:3][-1]
                if self.camera is BigWorld.camera():
                    self.delayCallback(0.0, self.__applyZoom,
                                       self.__cfg['zoom'])
        if 'fov' in diff and self.camera is BigWorld.camera():
            self.delayCallback(0.01, self.__applyZoom, self.__cfg['zoom'])

    def create(self, onChangeControlMode=None):
        self.__onChangeControlMode = onChangeControlMode
        self.settingsCore.onSettingsChanged += self.__onSettingsChanged
        aimingSystemClass = SniperAimingSystemRemote if BigWorld.player(
        ).isObserver() else SniperAimingSystem
        self.__aimingSystem = aimingSystemClass()

    def destroy(self):
        self.settingsCore.onSettingsChanged -= self.__onSettingsChanged
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        CallbackDelayer.destroy(self)
        return

    def enable(self, targetPos, saveZoom):
        self.__prevTime = BigWorld.time()
        player = BigWorld.player()
        if saveZoom:
            self.__zoom = self.__cfg['zoom']
        else:
            self.__cfg['zoom'] = self.__zoom = self.__cfg['zooms'][0]
        self.__applyZoom(self.__zoom)
        self.__setupCamera(targetPos)
        vehicle = player.getVehicleAttached()
        if self.__waitVehicleCallbackId is not None:
            BigWorld.cancelCallback(self.__waitVehicleCallbackId)
        if vehicle is None:
            self.__waitVehicleCallbackId = BigWorld.callback(
                0.1, self.__waitVehicle)
        else:
            self.__showVehicle(False)
        BigWorld.camera(self.__cam)
        if self.__cameraUpdate(False) >= 0.0:
            self.delayCallback(0.0, self.__cameraUpdate)
        return

    def disable(self):
        BigWorld.camera(None)
        if self.__waitVehicleCallbackId is not None:
            BigWorld.cancelCallback(self.__waitVehicleCallbackId)
            self.__waitVehicleCallbackId = None
        self.__showVehicle(True)
        self.stopCallback(self.__cameraUpdate)
        if self.__aimingSystem is not None:
            self.__aimingSystem.disable()
        self.__movementOscillator.reset()
        self.__impulseOscillator.reset()
        self.__noiseOscillator.reset()
        self.__accelerationSmoother.reset()
        self.__autoUpdateDxDyDz.set(0)
        FovExtended.instance().resetFov()
        return

    def getConfigValue(self, name):
        return self.__cfg.get(name)

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        self.__userCfg[name] = value
        if name not in ('keySensitivity', 'sensitivity', 'scrollSensitivity'):
            self.__cfg[name] = self.__userCfg[name]
        else:
            self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]

    def update(self, dx, dy, dz, updatedByKeyboard=False):
        self.__curSense = self.__cfg[
            'keySensitivity'] if updatedByKeyboard else self.__cfg[
                'sensitivity']
        self.__curScrollSense = self.__cfg[
            'keySensitivity'] if updatedByKeyboard else self.__cfg[
                'scrollSensitivity']
        self.__curSense *= 1.0 / self.__zoom
        if updatedByKeyboard:
            self.__autoUpdateDxDyDz.set(dx, dy, dz)
        else:
            self.__autoUpdateDxDyDz.set(0, 0, 0)
            self.__rotateAndZoom(dx, dy, dz)

    def onRecreateDevice(self):
        self.__applyZoom(self.__zoom)

    def applyImpulse(self, position, impulse, reason=ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(
            impulse, reason)
        camMatrix = Matrix(self.__cam.matrix)
        impulseLocal = camMatrix.applyVector(adjustedImpulse)
        impulseAsYPR = Vector3(impulseLocal.x,
                               -impulseLocal.y + impulseLocal.z, 0)
        rollPart = self.__dynamicCfg['impulsePartToRoll']
        impulseAsYPR.z = -rollPart * impulseAsYPR.x
        impulseAsYPR.x *= 1 - rollPart
        self.__impulseOscillator.applyImpulse(impulseAsYPR)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self,
                            position,
                            impulseValue,
                            reason=ImpulseReason.ME_HIT):
        impulse = self.__cam.position - position
        distance = impulse.length
        if distance < 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.OTHER_SHOT and distance <= self.__dynamicCfg[
                'maxShotImpulseDistance']:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.SPLASH or reason == ImpulseReason.HE_EXPLOSION:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.VEHICLE_EXPLOSION and distance <= self.__dynamicCfg[
                'maxExplosionImpulseDistance']:
            impulse *= impulseValue / distance
        else:
            return
        self.applyImpulse(position, impulse, reason)

    def setMaxZoom(self):
        zooms = self.__getZooms()
        self.__zoom = zooms[-1]
        self.__applyZoom(self.__zoom)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3(noiseMagnitude)
        self.__noiseOscillator.applyImpulse(noiseImpulse)

    def __rotateAndZoom(self, dx, dy, dz):
        self.__aimingSystem.handleMovement(*self.__calcYawPitchDelta(dx, dy))
        self.__setupZoom(dz)

    def __calcYawPitchDelta(self, dx, dy):
        return (dx * self.__curSense * (-1 if self.__cfg['horzInvert'] else 1),
                dy * self.__curSense * (-1 if self.__cfg['vertInvert'] else 1))

    def __showVehicle(self, show):
        player = BigWorld.player()
        vehicle = player.getVehicleAttached()
        if vehicle is not None:
            LOG_DEBUG('__showVehicle: ', vehicle.id, show)
            vehicle.show(show)
        return

    def __setupCamera(self, targetPos):
        vehicleTypeDescriptor = BigWorld.player().vehicleTypeDescriptor
        playerGunMatFunction = AimingSystems.getPlayerGunMat
        if vehicleTypeDescriptor is not None and vehicleTypeDescriptor.isYawHullAimingAvailable:
            playerGunMatFunction = AimingSystems.getCenteredPlayerGunMat
        self.__aimingSystem.enable(targetPos, playerGunMatFunction)
        return

    def __waitVehicle(self):
        player = BigWorld.player()
        vehicle = player.getVehicleAttached()
        if vehicle is not None and vehicle.isStarted:
            self.__waitVehicleCallbackId = None
        else:
            self.__waitVehicleCallbackId = BigWorld.callback(
                0.1, self.__waitVehicle)
            return
        self.__showVehicle(False)
        return

    def __applySerializedZoom(self, zoomFactor):
        if BattleReplay.g_replayCtrl.isPlaying:
            if BattleReplay.g_replayCtrl.isControllingCamera:
                self.__applyZoom(zoomFactor)

    def __applyZoom(self, zoomFactor):
        self.__zoomFactor = zoomFactor
        if BattleReplay.g_replayCtrl.isRecording:
            BattleReplay.g_replayCtrl.serializeCallbackData(
                'applyZoom', (zoomFactor, ))
        FovExtended.instance().setFovByMultiplier(1 / zoomFactor)

    def __getZooms(self):
        zooms = self.__cfg['zooms']
        if not self.__cfg['increasedZoom']:
            zooms = zooms[:3]
        return zooms

    def __setupZoom(self, dz):
        if dz == 0:
            return
        else:
            zooms = self.__getZooms()
            prevZoom = self.__zoom
            if self.__zoom == zooms[
                    0] and dz < 0 and self.__onChangeControlMode is not None:
                self.__onChangeControlMode(True)
            if dz > 0:
                for elem in zooms:
                    if self.__zoom < elem:
                        self.__zoom = elem
                        self.__cfg['zoom'] = self.__zoom
                        break

            elif dz < 0:
                for i in range(len(zooms) - 1, -1, -1):
                    if self.__zoom > zooms[i]:
                        self.__zoom = zooms[i]
                        self.__cfg['zoom'] = self.__zoom
                        break

            if prevZoom != self.__zoom:
                self.__applyZoom(self.__zoom)
            return

    def __cameraUpdate(self, allowModeChange=True):
        curTime = BigWorld.time()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        if not self.__autoUpdateDxDyDz.x == self.__autoUpdateDxDyDz.y == self.__autoUpdateDxDyDz.z == 0.0:
            self.__rotateAndZoom(self.__autoUpdateDxDyDz.x,
                                 self.__autoUpdateDxDyDz.y,
                                 self.__autoUpdateDxDyDz.z)
        self.__aimingSystem.update(deltaTime)
        localTransform, impulseTransform = self.__updateOscillators(deltaTime)
        zoom = self.__aimingSystem.overrideZoom(self.__zoom)
        aimMatrix = cameras.getAimMatrix(self.__defaultAimOffset.x,
                                         self.__defaultAimOffset.y)
        camMat = Matrix(aimMatrix)
        rodMat = mathUtils.createTranslationMatrix(
            -self.__dynamicCfg['pivotShift'])
        antiRodMat = mathUtils.createTranslationMatrix(
            self.__dynamicCfg['pivotShift'])
        camMat.postMultiply(rodMat)
        camMat.postMultiply(localTransform)
        camMat.postMultiply(antiRodMat)
        camMat.postMultiply(self.__aimingSystem.matrix)
        camMat.invert()
        self.__cam.set(camMat)
        if zoom != self.__zoom:
            self.__zoom = zoom
            self.__applyZoom(self.__zoom)
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
            binocularsOffset = aimOffset
            if not BigWorld.player().isForcedGuiControlMode() and GUI.mcursor(
            ).inFocus:
                GUI.mcursor().position = aimOffset
        else:
            aimOffset = self.__calcAimOffset(impulseTransform)
            binocularsOffset = self.__calcAimOffset()
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__aimOffset = aimOffset
        self.__binoculars.setMaskCenter(binocularsOffset.x, binocularsOffset.y)
        player = BigWorld.player()
        if allowModeChange and (self.__isPositionUnderwater(
                self.__aimingSystem.matrix.translation) or player.isGunLocked):
            self.__onChangeControlMode(False)
            return -1
        return 0.0

    def __calcAimOffset(self, aimLocalTransform=None):
        aimingSystemMatrix = self.__aimingSystem.matrix
        worldCrosshair = Matrix(self.__crosshairMatrix)
        if aimLocalTransform is not None:
            worldCrosshair.postMultiply(aimLocalTransform)
        worldCrosshair.postMultiply(aimingSystemMatrix)
        aimOffset = cameras.projectPoint(worldCrosshair.translation)
        return Vector2(mathUtils.clamp(-0.95, 0.95, aimOffset.x),
                       mathUtils.clamp(-0.95, 0.95, aimOffset.y))

    def __calcCurOscillatorAcceleration(self, deltaTime):
        vehicle = BigWorld.player().vehicle
        if vehicle is None or not vehicle.isAlive():
            return Vector3(0, 0, 0)
        else:
            curVelocity = vehicle.filter.velocity
            relativeSpeed = curVelocity.length / vehicle.typeDescriptor.physics[
                'speedLimits'][0]
            if relativeSpeed >= SniperCamera._MIN_REL_SPEED_ACC_SMOOTHING:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg[
                    'accelerationThreshold']
            else:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg[
                    'accelerationMax']
            acceleration = self.__accelerationSmoother.update(
                vehicle, deltaTime)
            camMat = Matrix(self.__cam.matrix)
            acceleration = camMat.applyVector(-acceleration)
            accelSensitivity = self.__dynamicCfg['accelerationSensitivity']
            acceleration.x *= accelSensitivity.x
            acceleration.y *= accelSensitivity.y
            acceleration.z *= accelSensitivity.z
            oscillatorAcceleration = Vector3(0,
                                             -acceleration.y + acceleration.z,
                                             -acceleration.x)
            return oscillatorAcceleration

    def __updateOscillators(self, deltaTime):
        if not SniperCamera.isCameraDynamic():
            self.__impulseOscillator.reset()
            self.__movementOscillator.reset()
            self.__noiseOscillator.reset()
            return (mathUtils.createRotationMatrix(Vector3(0, 0, 0)),
                    mathUtils.createRotationMatrix(Vector3(0, 0, 0)))
        oscillatorAcceleration = self.__calcCurOscillatorAcceleration(
            deltaTime)
        self.__movementOscillator.externalForce += oscillatorAcceleration
        self.__impulseOscillator.update(deltaTime)
        self.__movementOscillator.update(deltaTime)
        self.__noiseOscillator.update(deltaTime)
        noiseDeviation = Vector3(self.__noiseOscillator.deviation)
        deviation = self.__impulseOscillator.deviation + self.__movementOscillator.deviation + noiseDeviation
        oscVelocity = self.__impulseOscillator.velocity + self.__movementOscillator.velocity + self.__noiseOscillator.velocity
        if abs(deviation.x) < 1e-05 and abs(oscVelocity.x) < 0.0001:
            deviation.x = 0
        if abs(deviation.y) < 1e-05 and abs(oscVelocity.y) < 0.0001:
            deviation.y = 0
        if abs(deviation.z) < 1e-05 and abs(oscVelocity.z) < 0.0001:
            deviation.z = 0
        curZoomIdx = 0
        zooms = self.__cfg['zooms']
        for idx in xrange(len(zooms)):
            if self.__zoom == zooms[idx]:
                curZoomIdx = idx
                break

        zoomExposure = self.__zoom * self.__dynamicCfg['zoomExposure'][
            curZoomIdx]
        deviation /= zoomExposure
        impulseDeviation = (self.__impulseOscillator.deviation +
                            noiseDeviation) / zoomExposure
        self.__impulseOscillator.externalForce = Vector3(0)
        self.__movementOscillator.externalForce = Vector3(0)
        self.__noiseOscillator.externalForce = Vector3(0)
        return (mathUtils.createRotationMatrix(
            Vector3(deviation.x, deviation.y, deviation.z)),
                mathUtils.createRotationMatrix(impulseDeviation))

    def __isPositionUnderwater(self, position):
        return BigWorld.wg_collideWater(position, position + Vector3(0, 1, 0),
                                        False) > -1.0

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection(
            'gui/avatar_input_handler.xml/sniperMode/camera/')
        self.__readCfg(cameraSec)

    def __readCfg(self, dataSec):
        if not dataSec:
            LOG_WARNING(
                'Invalid section <sniperMode/camera> in avatar_input_handler.xml'
            )
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0, 10,
                                           0.005)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0, 10, 0.005)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0,
                                              10, 0.005)
        zooms = dataSec.readString('zooms', '2 4 8 16 25')
        bcfg['zooms'] = [float(x) for x in zooms.split()]
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['sniperMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        ucfg['horzInvert'] = self.settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = self.settingsCore.getSetting('mouseVertInvert')
        ucfg['increasedZoom'] = self.settingsCore.getSetting('increasedZoom')
        maxZoom = bcfg['zooms'][-1] if ucfg['increasedZoom'] else bcfg[
            'zooms'][:3][-1]
        ucfg['zoom'] = readFloat(ds, 'zoom', 0.0, maxZoom, bcfg['zooms'][0])
        ucfg['sniperModeByShift'] = self.settingsCore.getSetting(
            'sniperModeByShift')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0,
                                           1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0,
                                              10.0, 1.0)
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['zooms'] = bcfg['zooms']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        cfg['zoom'] = ucfg['zoom']
        cfg['increasedZoom'] = ucfg['increasedZoom']
        cfg['sniperModeByShift'] = ucfg['sniperModeByShift']
        dynamicsSection = dataSec['dynamics']
        self.__impulseOscillator = createOscillatorFromSection(
            dynamicsSection['impulseOscillator'])
        self.__movementOscillator = createOscillatorFromSection(
            dynamicsSection['movementOscillator'])
        self.__noiseOscillator = createOscillatorFromSection(
            dynamicsSection['randomNoiseOscillatorSpherical'])
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__dynamicCfg['accelerationSensitivity'] = readVec3(
            dynamicsSection, 'accelerationSensitivity', (-1000, -1000, -1000),
            (1000, 1000, 1000), (0.5, 0.5, 0.5))
        accelerationThreshold = readFloat(dynamicsSection,
                                          'accelerationThreshold', 0.0, 1000.0,
                                          0.1)
        self.__dynamicCfg['accelerationThreshold'] = accelerationThreshold
        self.__dynamicCfg['accelerationMax'] = readFloat(
            dynamicsSection, 'accelerationMax', 0.0, 1000.0, 0.1)
        self.__dynamicCfg['maxShotImpulseDistance'] = readFloat(
            dynamicsSection, 'maxShotImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['maxExplosionImpulseDistance'] = readFloat(
            dynamicsSection, 'maxExplosionImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['impulsePartToRoll'] = readFloat(
            dynamicsSection, 'impulsePartToRoll', 0.0, 1000.0, 0.3)
        self.__dynamicCfg['pivotShift'] = Vector3(
            0, readFloat(dynamicsSection, 'pivotShift', -1000, 1000, -0.5), 0)
        self.__dynamicCfg['aimMarkerDistance'] = readFloat(
            dynamicsSection, 'aimMarkerDistance', -1000, 1000, 1.0)
        rawZoomExposure = dynamicsSection.readString('zoomExposure',
                                                     '0.6 0.5 0.4 0.3 0.2')
        self.__dynamicCfg['zoomExposure'] = [
            float(x) for x in rawZoomExposure.split()
        ]
        accelerationFilter = mathUtils.RangeFilter(
            self.__dynamicCfg['accelerationThreshold'],
            self.__dynamicCfg['accelerationMax'], 100,
            mathUtils.SMAFilter(SniperCamera._FILTER_LENGTH))
        maxAccelerationDuration = readFloat(
            dynamicsSection, 'maxAccelerationDuration', 0.0, 10000.0,
            SniperCamera._DEFAULT_MAX_ACCELERATION_DURATION)
        self.__accelerationSmoother = AccelerationSmoother(
            accelerationFilter, maxAccelerationDuration)
        return

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeBool('sniperMode/camera/horzInvert', ucfg['horzInvert'])
        ds.writeBool('sniperMode/camera/vertInvert', ucfg['vertInvert'])
        ds.writeFloat('sniperMode/camera/keySensitivity',
                      ucfg['keySensitivity'])
        ds.writeFloat('sniperMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('sniperMode/camera/scrollSensitivity',
                      ucfg['scrollSensitivity'])
        ds.writeFloat('sniperMode/camera/zoom', self.__cfg['zoom'])
Example #12
0
class StrategicCamera(ICamera, CallbackDelayer):
    _CAMERA_YAW = 0.0
    _DYNAMIC_ENABLED = True
    ABSOLUTE_VERTICAL_FOV = math.radians(60.0)
    _SMOOTHING_PIVOT_DELTA_FACTOR = 6.0

    @staticmethod
    def enableDynamicCamera(enable):
        StrategicCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return StrategicCamera._DYNAMIC_ENABLED

    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)
    settingsCore = dependency.descriptor(ISettingsCore)
    __aimOffset = aih_global_binding.bindRW(
        aih_global_binding.BINDING_ID.AIM_OFFSET)

    def __init__(self, dataSec):
        CallbackDelayer.__init__(self)
        self.__positionOscillator = None
        self.__positionNoiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__readCfg(dataSec)
        self.__cam = BigWorld.CursorCamera()
        self.__curSense = self.__cfg['sensitivity']
        self.__onChangeControlMode = None
        self.__aimingSystem = None
        self.__prevTime = BigWorld.time()
        self.__autoUpdatePosition = False
        self.__dxdydz = Vector3(0, 0, 0)
        self.__needReset = 0
        self.__smoothingPivotDelta = 0
        return

    def create(self, onChangeControlMode=None):
        self.__onChangeControlMode = onChangeControlMode
        self.__camDist = self.__cfg['camDist']
        self.__cam.pivotMaxDist = 0.0
        self.__cam.maxDistHalfLife = 0.01
        self.__cam.movementHalfLife = 0.0
        self.__cam.turningHalfLife = -1
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        aimingSystemClass = StrategicAimingSystemRemote if BigWorld.player(
        ).isObserver() else StrategicAimingSystem
        self.__aimingSystem = aimingSystemClass(self.__cfg['distRange'][0],
                                                StrategicCamera._CAMERA_YAW)

    def destroy(self):
        CallbackDelayer.destroy(self)
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        return

    def enable(self, targetPos, saveDist):
        self.__prevTime = BigWorld.time()
        self.__aimingSystem.enable(targetPos)
        srcMat = mathUtils.createRotationMatrix((0.0, -math.pi * 0.499, 0.0))
        self.__cam.source = srcMat
        if not saveDist:
            self.__camDist = self.__cfg['camDist']
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        camTarget = Math.MatrixProduct()
        camTarget.b = self.__aimingSystem.matrix
        self.__cam.target = camTarget
        self.__cam.wg_applyParams()
        BigWorld.camera(self.__cam)
        BigWorld.player().positionControl.moveTo(
            self.__aimingSystem.matrix.translation)
        BigWorld.player().positionControl.followCamera(True)
        FovExtended.instance().enabled = False
        BigWorld.projection().fov = StrategicCamera.ABSOLUTE_VERTICAL_FOV
        self.__cameraUpdate()
        self.delayCallback(0.0, self.__cameraUpdate)
        self.__needReset = 1

    def disable(self):
        if self.__aimingSystem is not None:
            self.__aimingSystem.disable()
        self.stopCallback(self.__cameraUpdate)
        BigWorld.camera(None)
        positionControl = BigWorld.player().positionControl
        if positionControl is not None:
            positionControl.followCamera(False)
        self.__positionOscillator.reset()
        FovExtended.instance().resetFov()
        FovExtended.instance().enabled = True
        return

    def teleport(self, pos):
        self.moveToPosition(pos)

    def setMaxDist(self):
        distRange = self.__cfg['distRange']
        if len(distRange) > 1:
            self.__camDist = distRange[1]

    def restoreDefaultsState(self):
        LOG_ERROR('StrategiCamera::restoreDefaultState is obsolete!')

    def getConfigValue(self, name):
        return self.__cfg.get(name)

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        self.__userCfg[name] = value
        if name not in ('keySensitivity', 'sensitivity', 'scrollSensitivity'):
            self.__cfg[name] = self.__userCfg[name]
        else:
            self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]

    def update(self, dx, dy, dz, updatedByKeyboard=False):
        self.__curSense = self.__cfg[
            'keySensitivity'] if updatedByKeyboard else self.__cfg[
                'sensitivity']
        self.__autoUpdatePosition = updatedByKeyboard
        self.__dxdydz = Vector3(dx, dy, dz)

    def moveToPosition(self, pos):
        self.__aimingSystem.enable(pos)
        self.update(0.0, 0.0, 0.0)

    def calcVisibleAreaRatio(self):
        points = [
            Math.Vector2(1, 1),
            Math.Vector2(1, -1),
            Math.Vector2(-1, -1),
            Math.Vector2(-1, 1)
        ]
        dirsPos = [getWorldRayAndPoint(point.x, point.y) for point in points]
        planeXZ = Plane(Math.Vector3(0, 1, 0), 0)
        collisionPoints = []
        for dir, begPos in dirsPos:
            endPos = begPos + dir * 1000
            testResult = BigWorld.wg_collideSegment(BigWorld.player().spaceID,
                                                    begPos, endPos, 3)
            collPoint = Math.Vector3(0, 0, 0)
            if collPoint is not None:
                collPoint = testResult[0]
            else:
                collPoint = planeXZ.intersectSegment(begPos, endPos)
            collisionPoints.append(collPoint)

        x0 = abs(collisionPoints[1].x - collisionPoints[2].x)
        x1 = abs(collisionPoints[0].x - collisionPoints[3].x)
        z0 = abs(collisionPoints[0].z - collisionPoints[1].z)
        z1 = abs(collisionPoints[3].z - collisionPoints[2].z)
        bb = BigWorld.player().arena.arenaType.boundingBox
        arenaBottomLeft = bb[0]
        arenaUpperRight = bb[1]
        arenaX = arenaUpperRight[0] - arenaBottomLeft[0]
        arenaZ = arenaUpperRight[1] - arenaBottomLeft[1]
        return ((x0 + x1) * 0.5 / arenaX, (z0 + z1) * 0.5 / arenaZ)

    def applyImpulse(self, position, impulse, reason=ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(
            impulse, reason)
        impulseFlatDir = Vector3(adjustedImpulse.x, 0, adjustedImpulse.z)
        impulseFlatDir.normalise()
        cameraYawMat = mathUtils.createRotationMatrix(
            Vector3(-StrategicCamera._CAMERA_YAW, 0.0, 0.0))
        impulseLocal = cameraYawMat.applyVector(impulseFlatDir *
                                                (-1 * adjustedImpulse.length))
        self.__positionOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self,
                            position,
                            impulseValue,
                            reason=ImpulseReason.ME_HIT):
        if reason != ImpulseReason.SPLASH and reason != ImpulseReason.PROJECTILE_HIT:
            return
        impulse = BigWorld.player().getOwnVehiclePosition() - position
        distance = impulse.length
        if distance <= 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.PROJECTILE_HIT:
            if not cameras.isPointOnScreen(position):
                return
            distance = 1.0
        impulse *= impulseValue / distance
        self.applyImpulse(position, impulse, reason)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3Flat(noiseMagnitude)
        self.__positionNoiseOscillator.applyImpulse(noiseImpulse)

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeFloat('strategicMode/camera/keySensitivity',
                      ucfg['keySensitivity'])
        ds.writeFloat('strategicMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('strategicMode/camera/scrollSensitivity',
                      ucfg['scrollSensitivity'])
        ds.writeFloat('strategicMode/camera/camDist', self.__cfg['camDist'])

    def __cameraUpdate(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
        else:
            aimOffset = self.__calcAimOffset()
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__aimOffset = aimOffset
        shotDescr = BigWorld.player().getVehicleDescriptor().shot
        BigWorld.wg_trajectory_drawer().setParams(
            shotDescr.maxDistance, Math.Vector3(0, -shotDescr.gravity, 0),
            aimOffset)
        curTime = BigWorld.time()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        self.__aimingSystem.update(deltaTime)
        if replayCtrl.isPlaying:
            if self.__needReset != 0:
                if self.__needReset > 1:
                    from helpers import isPlayerAvatar
                    player = BigWorld.player()
                    if isPlayerAvatar():
                        if player.inputHandler.ctrl is not None:
                            player.inputHandler.ctrl.resetGunMarkers()
                    self.__needReset = 0
                else:
                    self.__needReset += 1
            if replayCtrl.isControllingCamera:
                self.__aimingSystem.updateTargetPos(
                    replayCtrl.getGunRotatorTargetPoint())
            else:
                self.__aimingSystem.handleMovement(
                    self.__dxdydz.x * self.__curSense,
                    -self.__dxdydz.y * self.__curSense)
                if self.__dxdydz.x != 0 or self.__dxdydz.y != 0 or self.__dxdydz.z != 0:
                    self.__needReset = 2
        else:
            self.__aimingSystem.handleMovement(
                self.__dxdydz.x * self.__curSense,
                -self.__dxdydz.y * self.__curSense)
        self.__calcSmoothingPivotDelta(deltaTime)
        self.__camDist -= self.__dxdydz.z * float(self.__curSense)
        self.__camDist = self.__aimingSystem.overrideCamDist(self.__camDist)
        distRange = self.__cfg['distRange']
        maxPivotHeight = distRange[1] - distRange[0]
        self.__camDist = mathUtils.clamp(0, maxPivotHeight, self.__camDist)
        self.__cfg['camDist'] = self.__camDist
        camDistWithSmoothing = self.__camDist + self.__smoothingPivotDelta - self.__aimingSystem.heightFromPlane
        self.__cam.pivotPosition = Math.Vector3(0.0, camDistWithSmoothing, 0.0)
        if self.__dxdydz.z != 0 and self.__onChangeControlMode is not None and mathUtils.almostZero(
                self.__camDist - maxPivotHeight):
            self.__onChangeControlMode()
        self.__updateOscillator(deltaTime)
        if not self.__autoUpdatePosition:
            self.__dxdydz = Vector3(0, 0, 0)
        return 0.0

    def __calcSmoothingPivotDelta(self, deltaTime):
        heightsDy = self.__aimingSystem.heightFromPlane - self.__smoothingPivotDelta
        smoothingPivotDeltaDy = StrategicCamera._SMOOTHING_PIVOT_DELTA_FACTOR * heightsDy * deltaTime
        self.__smoothingPivotDelta += smoothingPivotDeltaDy

    def __calcAimOffset(self):
        aimWorldPos = self.__aimingSystem.matrix.applyPoint(
            Vector3(0, -self.__aimingSystem.height, 0))
        aimOffset = cameras.projectPoint(aimWorldPos)
        return Vector2(mathUtils.clamp(-0.95, 0.95, aimOffset.x),
                       mathUtils.clamp(-0.95, 0.95, aimOffset.y))

    def __updateOscillator(self, deltaTime):
        if StrategicCamera.isCameraDynamic():
            self.__positionOscillator.update(deltaTime)
            self.__positionNoiseOscillator.update(deltaTime)
        else:
            self.__positionOscillator.reset()
            self.__positionNoiseOscillator.reset()
        self.__cam.target.a = mathUtils.createTranslationMatrix(
            self.__positionOscillator.deviation +
            self.__positionNoiseOscillator.deviation)

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection(
            'gui/avatar_input_handler.xml/strategicMode/camera/')
        self.__readCfg(cameraSec)

    def __readCfg(self, dataSec):
        if not dataSec or dataSec['strategic']:
            LOG_WARNING(
                'Invalid section <strategicMode/camera> in avatar_input_handler.xml'
            )
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.005,
                                           10, 0.025)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.005, 10,
                                        0.025)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity',
                                              0.005, 10, 0.025)
        bcfg['distRange'] = readVec2(dataSec, 'distRange', (1, 1),
                                     (10000, 10000), (2, 30))
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['strategicMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        ucfg['horzInvert'] = self.settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = self.settingsCore.getSetting('mouseVertInvert')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0,
                                           1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0,
                                              10.0, 1.0)
        ucfg['camDist'] = readFloat(ds, 'camDist', 0.0, 60.0, 0)
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['distRange'] = bcfg['distRange']
        cfg['camDist'] = ucfg['camDist']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        dynamicsSection = dataSec['dynamics']
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__positionOscillator = createOscillatorFromSection(
            dynamicsSection['oscillator'], False)
        self.__positionNoiseOscillator = createOscillatorFromSection(
            dynamicsSection['randomNoiseOscillatorFlat'], False)
        return
Example #13
0
class ArcadeCamera(ICamera, CallbackDelayer, TimeDeltaMeter):
    REASONS_AFFECT_CAMERA_DIRECTLY = (ImpulseReason.MY_SHOT,
                                      ImpulseReason.OTHER_SHOT,
                                      ImpulseReason.VEHICLE_EXPLOSION,
                                      ImpulseReason.HE_EXPLOSION)
    _DYNAMIC_ENABLED = True

    @staticmethod
    def enableDynamicCamera(enable):
        ArcadeCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return ArcadeCamera._DYNAMIC_ENABLED

    _FILTER_LENGTH = 5
    _DEFAULT_MAX_ACCELERATION_DURATION = 1.5
    _FOCAL_POINT_CHANGE_SPEED = 100.0
    _FOCAL_POINT_MIN_DIFF = 5.0
    _MIN_REL_SPEED_ACC_SMOOTHING = 0.7

    def getReasonsAffectCameraDirectly(self):
        return ArcadeCamera.REASONS_AFFECT_CAMERA_DIRECTLY

    def __getVehicleMProv(self):
        refinedMProv = self.__aimingSystem.vehicleMProv
        return refinedMProv.b.source

    def __setVehicleMProv(self, vehicleMProv):
        prevAimRel = self.__cam.aimPointProvider.a if self.__cam.aimPointProvider is not None else None
        refinedVehicleMProv = self.__refineVehicleMProv(vehicleMProv)
        self.__aimingSystem.vehicleMProv = refinedVehicleMProv
        self.__setupCameraProviders(refinedVehicleMProv)
        self.__cam.speedTreeTarget = self.__aimingSystem.vehicleMProv
        self.__aimingSystem.update(0.0)
        if prevAimRel is not None:
            self.__cam.aimPointProvider.a = prevAimRel
        baseTranslation = Matrix(refinedVehicleMProv).translation
        relativePosition = self.__aimingSystem.matrix.translation - baseTranslation
        self.__setCameraPosition(relativePosition)
        return

    camera = property(lambda self: self.__cam)
    angles = property(lambda self:
                      (self.__aimingSystem.yaw, self.__aimingSystem.pitch))
    aimingSystem = property(lambda self: self.__aimingSystem)
    vehicleMProv = property(__getVehicleMProv, __setVehicleMProv)
    __aimOffset = aim_global_binding.bind(
        aim_global_binding.BINDING_ID.AIM_OFFSET)

    def __init__(self, dataSec, defaultOffset=None):
        CallbackDelayer.__init__(self)
        TimeDeltaMeter.__init__(self)
        self.__shiftKeySensor = None
        self.__movementOscillator = None
        self.__impulseOscillator = None
        self.__noiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__accelerationSmoother = None
        self.__readCfg(dataSec)
        self.__onChangeControlMode = None
        self.__aimingSystem = None
        self.__curSense = 0
        self.__curScrollSense = 0
        self.__postmortemMode = False
        self.__vehiclesToCollideWith = set()
        self.__focalPointDist = 1.0
        self.__autoUpdateDxDyDz = Vector3(0.0)
        self.__updatedByKeyboard = False
        if defaultOffset is not None:
            self.__defaultAimOffset = defaultOffset
            self.__cam = BigWorld.HomingCamera(self.__adCfg['enable'])
            if self.__adCfg['enable']:
                self.__cam.initAdvancedCollider(
                    self.__adCfg['fovRatio'], self.__adCfg['rollbackSpeed'],
                    self.__adCfg['minimalCameraDistance'],
                    self.__adCfg['speedThreshold'],
                    self.__adCfg['minimalVolume'])
                for group_name in VOLUME_GROUPS_NAMES:
                    self.__cam.addVolumeGroup(
                        self.__adCfg['volumeGroups'][group_name])

            self.__cam.aimPointClipCoords = defaultOffset
        else:
            self.__defaultAimOffset = Vector2()
            self.__cam = None
        return

    def create(self, pivotPos, onChangeControlMode=None, postmortemMode=False):
        self.__onChangeControlMode = onChangeControlMode
        self.__postmortemMode = postmortemMode
        targetMat = self.getTargetMProv()
        self.__aimingSystem = ArcadeAimingSystem(
            self.__refineVehicleMProv(targetMat), pivotPos.y, pivotPos.z,
            self.__calcAimMatrix(), self.__cfg['angleRange'],
            not postmortemMode)
        self.setCameraDistance(self.__cfg['startDist'])
        self.__aimingSystem.pitch = self.__cfg['startAngle']
        self.__aimingSystem.yaw = Math.Matrix(targetMat).yaw
        self.__updateAngles(0, 0)
        cameraPosProvider = Math.Vector4Translation(self.__aimingSystem.matrix)
        self.__cam.cameraPositionProvider = cameraPosProvider

    def getTargetMProv(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.playerVehicleID != 0:
            vehicleID = replayCtrl.playerVehicleID
        else:
            vehicleID = BigWorld.player().playerVehicleID
        return BigWorld.entity(vehicleID).matrix

    def reinitMatrix(self):
        self.__setVehicleMProv(self.getTargetMProv())

    def setToVehicleDirection(self):
        matrix = Math.Matrix(self.getTargetMProv())
        self.setYawPitch(matrix.yaw, matrix.pitch)

    def destroy(self):
        CallbackDelayer.destroy(self)
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        return

    def getPivotSettings(self):
        return self.__aimingSystem.getPivotSettings()

    def setPivotSettings(self, heightAboveBase, focusRadius):
        self.__aimingSystem.setPivotSettings(heightAboveBase, focusRadius)

    def addVehicleToCollideWith(self, vehicle):
        self.__vehiclesToCollideWith.add(vehicle)
        self.__setModelsToCollideWith(self.__vehiclesToCollideWith)

    def removeVehicleToCollideWith(self, vehicle):
        lengthBefore = len(self.__vehiclesToCollideWith)
        self.__vehiclesToCollideWith = set(
            [v for v in self.__vehiclesToCollideWith if v.id != vehicle.id])
        if lengthBefore != len(self.__vehiclesToCollideWith):
            self.__setModelsToCollideWith(self.__vehiclesToCollideWith)

    def clearVehicleToCollideWith(self):
        self.__vehiclesToCollideWith = set()
        self.__setModelsToCollideWith(self.__vehiclesToCollideWith)

    def __setModelsToCollideWith(self, vehicles):
        colliders = []
        for vehicle in vehicles:
            vehicleAppearance = vehicle.appearance
            compound = vehicleAppearance.compoundModel
            if not vehicle.isTurretDetached:
                colliders.append(
                    (compound.node(TankPartNames.TURRET),
                     compound.getBoundsForPart(TankPartIndexes.TURRET),
                     compound.getPartGeometryLink(TankPartIndexes.TURRET)))
            colliders.append(
                (compound.node(TankPartNames.HULL),
                 compound.getBoundsForPart(TankPartIndexes.HULL),
                 compound.getPartGeometryLink(TankPartIndexes.HULL)))

        self.__cam.setDynamicColliders(colliders)
        self.__aimingSystem.setDynamicColliders(colliders)

    def focusOnPos(self, preferredPos):
        self.__aimingSystem.focusOnPos(preferredPos)

    def shiftCamPos(self, shift=None):
        matrixProduct = self.__aimingSystem.vehicleMProv
        shiftMat = matrixProduct.a
        if shift is not None:
            camDirection = Math.Vector3(math.sin(self.angles[0]), 0,
                                        math.cos(self.angles[0]))
            normal = Math.Vector3(camDirection)
            normal.x = camDirection.z
            normal.z = -camDirection.x
            shiftMat.translation += camDirection * shift.z + Math.Vector3(
                0, 1, 0) * shift.y + normal * shift.x
        else:
            shiftMat.setIdentity()
        return

    def enable(self,
               preferredPos=None,
               closesDist=False,
               postmortemParams=None,
               turretYaw=None,
               gunPitch=None):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isRecording:
            replayCtrl.setAimClipPosition(self.__aimOffset)
        self.measureDeltaTime()
        camDist = None
        vehicle = BigWorld.player().getVehicleAttached()
        initialVehicleMatrix = BigWorld.player().getOwnVehicleMatrix(
        ) if vehicle is None else vehicle.matrix
        vehicleMProv = initialVehicleMatrix
        if not self.__postmortemMode:
            if closesDist:
                camDist = self.__cfg['distRange'][0]
        elif postmortemParams is not None:
            self.__aimingSystem.yaw = postmortemParams[0][0]
            self.__aimingSystem.pitch = postmortemParams[0][1]
            camDist = postmortemParams[1]
        else:
            camDist = self.__cfg['distRange'][1]
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying:
            camDist = None
            vehicle = BigWorld.entity(replayCtrl.playerVehicleID)
            if vehicle is not None:
                vehicleMProv = vehicle.matrix
        if camDist is not None:
            self.setCameraDistance(camDist)
        else:
            self.__inputInertia.teleport(self.__calcRelativeDist())
        self.vehicleMProv = vehicleMProv
        self.__setModelsToCollideWith(self.__vehiclesToCollideWith)
        BigWorld.camera(self.__cam)
        self.__aimingSystem.enable(preferredPos, turretYaw, gunPitch)
        self.__aimingSystem.aimMatrix = self.__calcAimMatrix()
        self.__cameraUpdate()
        self.delayCallback(0.0, self.__cameraUpdate)
        from gui import g_guiResetters
        g_guiResetters.add(self.__onRecreateDevice)
        return

    def __refineVehicleMProv(self, vehicleMProv):
        vehicleTranslationOnly = Math.WGTranslationOnlyMP()
        vehicleTranslationOnly.source = vehicleMProv
        refinedMatrixProvider = Math.MatrixProduct()
        refinedMatrixProvider.a = mathUtils.createIdentityMatrix()
        refinedMatrixProvider.b = vehicleTranslationOnly
        return refinedMatrixProvider

    def __setupCameraProviders(self, vehicleMProv):
        vehiclePos = Math.Vector4Translation(vehicleMProv)
        cameraPositionProvider = Math.Vector4Combiner()
        cameraPositionProvider.fn = 'ADD'
        cameraPositionProvider.a = Vector4(0, 0, 0, 0)
        cameraPositionProvider.b = vehiclePos
        cameraAimPointProvider = Math.Vector4Combiner()
        cameraAimPointProvider.fn = 'ADD'
        cameraAimPointProvider.a = Vector4(0, 0, 1, 0)
        cameraAimPointProvider.b = vehiclePos
        self.__cam.cameraPositionProvider = cameraPositionProvider
        self.__cam.aimPointProvider = cameraAimPointProvider
        self.__cam.pivotPositionProvider = self.__aimingSystem.positionAboveVehicleProv

    def __setCameraPosition(self, relativeToVehiclePosition):
        self.__cam.cameraPositionProvider.a = Vector4(
            relativeToVehiclePosition.x, relativeToVehiclePosition.y,
            relativeToVehiclePosition.z, 1.0)

    def __setCameraAimPoint(self, relativeToVehiclePosition):
        self.__cam.aimPointProvider.a = Vector4(relativeToVehiclePosition.x,
                                                relativeToVehiclePosition.y,
                                                relativeToVehiclePosition.z,
                                                1.0)

    def disable(self):
        from gui import g_guiResetters
        if self.__onRecreateDevice in g_guiResetters:
            g_guiResetters.remove(self.__onRecreateDevice)
        self.__setModelsToCollideWith([])
        self.__cam.speedTreeTarget = None
        BigWorld.camera(None)
        if self.__shiftKeySensor is not None:
            self.__shiftKeySensor.reset(Math.Vector3())
        self.stopCallback(self.__cameraUpdate)
        self.__movementOscillator.reset()
        self.__impulseOscillator.reset()
        self.__noiseOscillator.reset()
        self.__accelerationSmoother.reset()
        self.__autoUpdateDxDyDz.set(0)
        self.__updatedByKeyboard = False
        self.__inputInertia.teleport(self.__calcRelativeDist())
        FovExtended.instance().resetFov()
        return

    def update(self,
               dx,
               dy,
               dz,
               rotateMode=True,
               zoomMode=True,
               updatedByKeyboard=False):
        self.__curSense = self.__cfg[
            'keySensitivity'] if updatedByKeyboard else self.__cfg[
                'sensitivity']
        self.__curScrollSense = self.__cfg[
            'keySensitivity'] if updatedByKeyboard else self.__cfg[
                'scrollSensitivity']
        self.__updatedByKeyboard = updatedByKeyboard
        if updatedByKeyboard:
            self.__autoUpdateDxDyDz.set(dx, dy, dz)
        else:
            self.__autoUpdateDxDyDz.set(0)
            self.__update(dx, dy, dz, rotateMode, zoomMode, False)

    def restoreDefaultsState(self):
        LOG_ERROR('ArcadeCamera::restoreDefaultState is obsolete!')

    def getConfigValue(self, name):
        return self.__cfg.get(name)

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        else:
            self.__userCfg[name] = value
            if name not in ('keySensitivity', 'sensitivity',
                            'scrollSensitivity'):
                self.__cfg[name] = self.__userCfg[name]
                if name == 'fovMultMinMaxDist' and getattr(
                        self, '_ArcadeCamera__aimingSystem', None) is not None:
                    self.__inputInertia.teleport(self.__calcRelativeDist(),
                                                 value)
            else:
                self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]
            return

    def setCameraDistance(self, distance):
        distRange = self.__cfg['distRange']
        clampedDist = mathUtils.clamp(distRange[0], distRange[1], distance)
        self.__aimingSystem.distanceFromFocus = clampedDist
        self.__inputInertia.teleport(self.__calcRelativeDist())

    def getCameraDistance(self):
        return self.__aimingSystem.distanceFromFocus

    def setYawPitch(self, yaw, pitch):
        self.__aimingSystem.yaw = yaw
        self.__aimingSystem.pitch = pitch
        matrix = Math.Matrix()
        matrix.setRotateYPR(
            (self.__aimingSystem.yaw, self.__aimingSystem.pitch, 0))

    def calcYawPitchDelta(self, dx, dy):
        return (dx * self.__curSense * (-1 if self.__cfg['horzInvert'] else 1),
                dy * self.__curSense * (-1 if self.__cfg['vertInvert'] else 1))

    def __updateAngles(self, dx, dy):
        yawDelta, pitchDelta = self.calcYawPitchDelta(dx, dy)
        self.__aimingSystem.handleMovement(yawDelta, -pitchDelta)
        return (self.__aimingSystem.yaw, self.__aimingSystem.pitch, 0)

    def __update(self,
                 dx,
                 dy,
                 dz,
                 rotateMode=True,
                 zoomMode=True,
                 isCallback=False):
        prevPos = self.__inputInertia.calcWorldPos(self.__aimingSystem.matrix)
        distChanged = False
        if zoomMode and dz != 0:
            prevDist = self.__aimingSystem.distanceFromFocus
            distDelta = dz * float(self.__curScrollSense)
            distMinMax = self.__cfg['distRange']
            newDist = mathUtils.clamp(distMinMax.min, distMinMax.max,
                                      prevDist - distDelta)
            floatEps = 0.001
            if abs(newDist - prevDist) > floatEps:
                self.__aimingSystem.distanceFromFocus = newDist
                self.__userCfg['startDist'] = newDist
                self.__inputInertia.glideFov(self.__calcRelativeDist())
                self.__aimingSystem.aimMatrix = self.__calcAimMatrix()
                distChanged = True
            if not self.__updatedByKeyboard:
                if abs(newDist - prevDist) < floatEps and mathUtils.almostZero(
                        newDist - distMinMax.min):
                    if self.__onChangeControlMode is not None:
                        self.__onChangeControlMode()
                        return
        if rotateMode:
            self.__updateAngles(dx, dy)
        if ENABLE_INPUT_ROTATION_INERTIA and not distChanged:
            self.__aimingSystem.update(0.0)
        if ENABLE_INPUT_ROTATION_INERTIA or distChanged:
            worldDeltaPos = prevPos - self.__aimingSystem.matrix.translation
            matInv = Matrix(self.__aimingSystem.matrix)
            matInv.invert()
            self.__inputInertia.glide(matInv.applyVector(worldDeltaPos))
        return

    def __cameraUpdate(self):
        if not (self.__autoUpdateDxDyDz.x == 0.0 and self.__autoUpdateDxDyDz.y
                == 0.0 and self.__autoUpdateDxDyDz.z == 0.0):
            self.__update(self.__autoUpdateDxDyDz.x, self.__autoUpdateDxDyDz.y,
                          self.__autoUpdateDxDyDz.z)
        inertDt = deltaTime = self.measureDeltaTime()
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying:
            repSpeed = replayCtrl.playbackSpeed
            if repSpeed == 0.0:
                inertDt = 0.01
                deltaTime = 0.0
            else:
                inertDt = deltaTime = deltaTime / repSpeed
        self.__aimingSystem.update(deltaTime)
        virginShotPoint = self.__aimingSystem.getThirdPersonShotPoint()
        delta = self.__inputInertia.positionDelta
        sign = delta.dot(Vector3(0, 0, 1))
        self.__inputInertia.update(inertDt)
        delta = (delta - self.__inputInertia.positionDelta).length
        if delta != 0.0:
            self.__cam.setScrollDelta(math.copysign(delta, sign))
        FovExtended.instance().setFovByMultiplier(
            self.__inputInertia.fovZoomMultiplier)
        unshakenPos = self.__inputInertia.calcWorldPos(
            self.__aimingSystem.idealMatrix if self.
            __adCfg['enable'] else self.__aimingSystem.matrix)
        vehMatrix = Math.Matrix(self.__aimingSystem.vehicleMProv)
        vehiclePos = vehMatrix.translation
        fromVehicleToUnshakedPos = unshakenPos - vehiclePos
        deviationBasis = mathUtils.createRotationMatrix(
            Vector3(self.__aimingSystem.yaw, 0, 0))
        impulseDeviation, movementDeviation, oscillationsZoomMultiplier = self.__updateOscillators(
            deltaTime)
        relCamPosMatrix = mathUtils.createTranslationMatrix(impulseDeviation +
                                                            movementDeviation)
        relCamPosMatrix.postMultiply(deviationBasis)
        relCamPosMatrix.translation += fromVehicleToUnshakedPos
        upRotMat = mathUtils.createRotationMatrix(
            Vector3(
                0, 0, -impulseDeviation.x *
                self.__dynamicCfg['sideImpulseToRollRatio'] -
                self.__noiseOscillator.deviation.z))
        upRotMat.postMultiply(relCamPosMatrix)
        self.__cam.up = upRotMat.applyVector(Vector3(0, 1, 0))
        relTranslation = relCamPosMatrix.translation
        self.__setCameraPosition(relTranslation)
        shotPoint = self.__calcFocalPoint(virginShotPoint, deltaTime)
        vehToShotPoint = shotPoint - vehiclePos
        self.__setCameraAimPoint(vehToShotPoint)
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
            if not BigWorld.player().isForcedGuiControlMode() and GUI.mcursor(
            ).inFocus:
                GUI.mcursor().position = aimOffset
        else:
            aimOffset = self.__calcAimOffset(oscillationsZoomMultiplier)
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__cam.aimPointClipCoords = aimOffset
        self.__aimOffset = aimOffset
        if self.__shiftKeySensor is not None:
            self.__shiftKeySensor.update(1.0)
            if self.__shiftKeySensor.currentVelocity.lengthSquared > 0.0:
                self.shiftCamPos(self.__shiftKeySensor.currentVelocity)
                self.__shiftKeySensor.currentVelocity = Math.Vector3()
        return 0.0

    def __calcFocalPoint(self, shotPoint, deltaTime):
        aimStartPoint = self.__aimingSystem.matrix.translation
        aimDir = shotPoint - aimStartPoint
        distToShotPoint = aimDir.length
        if distToShotPoint < 0.001:
            return shotPoint
        aimDir /= distToShotPoint
        absDiff = abs(distToShotPoint - self.__focalPointDist)
        if absDiff < ArcadeCamera._FOCAL_POINT_MIN_DIFF or absDiff <= ArcadeCamera._FOCAL_POINT_CHANGE_SPEED * deltaTime:
            self.__focalPointDist = distToShotPoint
            return shotPoint
        changeDir = (distToShotPoint - self.__focalPointDist) / absDiff
        self.__focalPointDist += changeDir * ArcadeCamera._FOCAL_POINT_CHANGE_SPEED * deltaTime
        return aimStartPoint + aimDir * self.__focalPointDist

    def __calcAimOffset(self, oscillationsZoomMultiplier):
        fov = BigWorld.projection().fov
        aspect = cameras.getScreenAspectRatio()
        yTan = math.tan(fov * 0.5)
        xTan = yTan * aspect
        defaultX = self.__defaultAimOffset[0]
        defaultY = self.__defaultAimOffset[1]
        yawFromImpulse = self.__impulseOscillator.deviation.x * self.__dynamicCfg[
            'sideImpulseToYawRatio']
        xImpulseDeviationTan = math.tan(
            -(yawFromImpulse + self.__noiseOscillator.deviation.x) *
            oscillationsZoomMultiplier)
        pitchFromImpulse = self.__impulseOscillator.deviation.z * self.__dynamicCfg[
            'frontImpulseToPitchRatio']
        yImpulseDeviationTan = math.tan(
            (pitchFromImpulse + self.__noiseOscillator.deviation.y) *
            oscillationsZoomMultiplier)
        totalOffset = Vector2(
            (defaultX * xTan + xImpulseDeviationTan) /
            (xTan * (1 - defaultX * xTan * xImpulseDeviationTan)),
            (defaultY * yTan + yImpulseDeviationTan) /
            (yTan * (1 - defaultY * yTan * yImpulseDeviationTan)))
        return totalOffset

    def __calcRelativeDist(self):
        distRange = self.__cfg['distRange']
        curDist = self.__aimingSystem.distanceFromFocus
        relDist = (curDist - distRange[0]) / (distRange[1] - distRange[0])
        return relDist

    def __calcCurOscillatorAcceleration(self, deltaTime):
        vehicle = BigWorld.player().getVehicleAttached()
        if vehicle is None:
            return Vector3(0, 0, 0)
        else:
            vehFilter = vehicle.filter
            curVelocity = getattr(vehFilter, 'velocity', Vector3(0.0))
            relativeSpeed = curVelocity.length / vehicle.typeDescriptor.physics[
                'speedLimits'][0]
            if relativeSpeed >= ArcadeCamera._MIN_REL_SPEED_ACC_SMOOTHING:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg[
                    'accelerationThreshold']
            else:
                self.__accelerationSmoother.maxAllowedAcceleration = self.__dynamicCfg[
                    'accelerationMax']
            acceleration = self.__accelerationSmoother.update(
                vehicle, deltaTime)
            yawMat = mathUtils.createRotationMatrix(
                (-self.__aimingSystem.yaw, 0, 0))
            acceleration = yawMat.applyVector(-acceleration)
            oscillatorAcceleration = Vector3(acceleration.x, acceleration.y,
                                             acceleration.z)
            return oscillatorAcceleration * self.__dynamicCfg[
                'accelerationSensitivity']

    def __updateOscillators(self, deltaTime):
        if not ArcadeCamera.isCameraDynamic():
            self.__impulseOscillator.reset()
            self.__movementOscillator.reset()
            self.__noiseOscillator.reset()
            return (Vector3(0), Vector3(0), 1.0)
        oscillatorAcceleration = self.__calcCurOscillatorAcceleration(
            deltaTime)
        self.__movementOscillator.externalForce += oscillatorAcceleration
        self.__impulseOscillator.update(deltaTime)
        self.__movementOscillator.update(deltaTime)
        self.__noiseOscillator.update(deltaTime)
        self.__impulseOscillator.externalForce = Vector3(0)
        self.__movementOscillator.externalForce = Vector3(0)
        self.__noiseOscillator.externalForce = Vector3(0)
        relDist = self.__calcRelativeDist()
        zoomMultiplier = mathUtils.lerp(1.0, self.__dynamicCfg['zoomExposure'],
                                        relDist)
        impulseDeviation = Vector3(self.__impulseOscillator.deviation)
        impulseDeviation.set(impulseDeviation.x * zoomMultiplier,
                             impulseDeviation.y * zoomMultiplier,
                             impulseDeviation.z * zoomMultiplier)
        movementDeviation = Vector3(self.__movementOscillator.deviation)
        movementDeviation.set(movementDeviation.x * zoomMultiplier,
                              movementDeviation.y * zoomMultiplier,
                              movementDeviation.z * zoomMultiplier)
        return (impulseDeviation, movementDeviation, zoomMultiplier)

    def applyImpulse(self, position, impulse, reason=ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(
            impulse, reason)
        yawMat = mathUtils.createRotationMatrix(
            (-self.__aimingSystem.yaw, 0, 0))
        impulseLocal = yawMat.applyVector(adjustedImpulse)
        self.__impulseOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self,
                            position,
                            impulseValue,
                            reason=ImpulseReason.ME_HIT):
        applicationPosition = self.__cam.position
        if reason == ImpulseReason.SPLASH:
            applicationPosition = Matrix(self.vehicleMProv).translation
        impulse = applicationPosition - position
        distance = impulse.length
        if distance < 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.OTHER_SHOT and distance <= self.__dynamicCfg[
                'maxShotImpulseDistance']:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.SPLASH or reason == ImpulseReason.HE_EXPLOSION:
            impulse *= impulseValue / distance
        elif reason == ImpulseReason.VEHICLE_EXPLOSION and distance <= self.__dynamicCfg[
                'maxExplosionImpulseDistance']:
            impulse *= impulseValue / distance
        else:
            return
        self.applyImpulse(position, impulse, reason)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3(noiseMagnitude)
        self.__noiseOscillator.applyImpulse(noiseImpulse)

    def handleKeyEvent(self, isDown, key, mods, event=None):
        if self.__shiftKeySensor is None:
            return False
        elif BigWorld.isKeyDown(Keys.KEY_CAPSLOCK) and mods & 4:
            if key == Keys.KEY_C:
                self.shiftCamPos()
            return self.__shiftKeySensor.handleKeyEvent(key, isDown)
        else:
            self.__shiftKeySensor.reset(Math.Vector3())
            return False

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection(
            'gui/avatar_input_handler.xml/arcadeMode/camera/')
        self.__readCfg(cameraSec)

    def __calcAimMatrix(self):
        endMult = self.__inputInertia.endZoomMultiplier
        fov = FovExtended.instance().actualDefaultVerticalFov * endMult
        offset = self.__defaultAimOffset
        return cameras.getAimMatrix(-offset[0], -offset[1], fov)

    def __readCfg(self, dataSec):
        if dataSec is None:
            LOG_WARNING(
                'Invalid section <arcadeMode/camera> in avatar_input_handler.xml'
            )
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0, 10,
                                           0.01)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0, 10, 0.01)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0,
                                              10, 0.01)
        bcfg['angleRange'] = readVec2(dataSec, 'angleRange', (0, 0),
                                      (180, 180), (10, 110))
        distRangeVec = readVec2(dataSec, 'distRange', (1, 1), (100, 100),
                                (2, 20))
        bcfg['distRange'] = MinMax(distRangeVec.x, distRangeVec.y)
        bcfg['minStartDist'] = readFloat(dataSec, 'minStartDist',
                                         bcfg['distRange'][0],
                                         bcfg['distRange'][1],
                                         bcfg['distRange'][0])
        bcfg['optimalStartDist'] = readFloat(dataSec, 'optimalStartDist',
                                             bcfg['distRange'][0],
                                             bcfg['distRange'][1],
                                             bcfg['distRange'][0])
        bcfg['angleRange'][0] = math.radians(
            bcfg['angleRange'][0]) - math.pi * 0.5
        bcfg['angleRange'][1] = math.radians(
            bcfg['angleRange'][1]) - math.pi * 0.5
        bcfg['fovMultMinMaxDist'] = MinMax(
            readFloat(dataSec, 'fovMultMinDist', 0.1, 100, 1.0),
            readFloat(dataSec, 'fovMultMaxDist', 0.1, 100, 1.0))
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['arcadeMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        from account_helpers.settings_core.SettingsCore import g_settingsCore
        ucfg['horzInvert'] = g_settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = g_settingsCore.getSetting('mouseVertInvert')
        ucfg['sniperModeByShift'] = g_settingsCore.getSetting(
            'sniperModeByShift')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0,
                                           1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0,
                                              10.0, 1.0)
        ucfg['startDist'] = readFloat(ds, 'startDist', bcfg['distRange'][0],
                                      500, bcfg['optimalStartDist'])
        if ucfg['startDist'] < bcfg['minStartDist']:
            ucfg['startDist'] = bcfg['optimalStartDist']
        ucfg['startAngle'] = readFloat(ds, 'startAngle', 5, 180, 60)
        ucfg['startAngle'] = math.radians(ucfg['startAngle']) - math.pi * 0.5
        ucfg['fovMultMinMaxDist'] = MinMax(
            readFloat(ds, 'fovMultMinDist', 0.1, 100,
                      bcfg['fovMultMinMaxDist'].min),
            readFloat(ds, 'fovMultMaxDist', 0.1, 100,
                      bcfg['fovMultMinMaxDist'].max))
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['angleRange'] = bcfg['angleRange']
        cfg['distRange'] = bcfg['distRange']
        cfg['minStartDist'] = bcfg['minStartDist']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['startDist'] = ucfg['startDist']
        cfg['startAngle'] = ucfg['startAngle']
        cfg['fovMultMinMaxDist'] = ucfg['fovMultMinMaxDist']
        cfg['sniperModeByShift'] = ucfg['sniperModeByShift']
        enableShift = dataSec.readBool('shift', False)
        if enableShift:
            movementMappings = dict()
            movementMappings[Keys.KEY_A] = Math.Vector3(-1, 0, 0)
            movementMappings[Keys.KEY_D] = Math.Vector3(1, 0, 0)
            movementMappings[Keys.KEY_Q] = Math.Vector3(0, 1, 0)
            movementMappings[Keys.KEY_E] = Math.Vector3(0, -1, 0)
            movementMappings[Keys.KEY_W] = Math.Vector3(0, 0, 1)
            movementMappings[Keys.KEY_S] = Math.Vector3(0, 0, -1)
            shiftSensitivity = dataSec.readFloat('shiftSensitivity', 0.5)
            self.__shiftKeySensor = KeySensor(movementMappings,
                                              shiftSensitivity)
            self.__shiftKeySensor.reset(Math.Vector3())
        dynamicsSection = dataSec['dynamics']
        self.__impulseOscillator = createOscillatorFromSection(
            dynamicsSection['impulseOscillator'], False)
        self.__movementOscillator = createOscillatorFromSection(
            dynamicsSection['movementOscillator'], False)
        self.__movementOscillator = Math.PyCompoundOscillator(
            self.__movementOscillator,
            Math.PyOscillator(1.0, Vector3(50), Vector3(20),
                              Vector3(0.01, 0.0, 0.01)))
        self.__noiseOscillator = createOscillatorFromSection(
            dynamicsSection['randomNoiseOscillatorSpherical'])
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__dynamicCfg['accelerationSensitivity'] = readFloat(
            dynamicsSection, 'accelerationSensitivity', -1000, 1000, 0.1)
        self.__dynamicCfg['frontImpulseToPitchRatio'] = math.radians(
            readFloat(dynamicsSection, 'frontImpulseToPitchRatio', -1000, 1000,
                      0.1))
        self.__dynamicCfg['sideImpulseToRollRatio'] = math.radians(
            readFloat(dynamicsSection, 'sideImpulseToRollRatio', -1000, 1000,
                      0.1))
        self.__dynamicCfg['sideImpulseToYawRatio'] = math.radians(
            readFloat(dynamicsSection, 'sideImpulseToYawRatio', -1000, 1000,
                      0.1))
        accelerationThreshold = readFloat(dynamicsSection,
                                          'accelerationThreshold', 0.0, 1000.0,
                                          0.1)
        self.__dynamicCfg['accelerationThreshold'] = accelerationThreshold
        self.__dynamicCfg['accelerationMax'] = readFloat(
            dynamicsSection, 'accelerationMax', 0.0, 1000.0, 0.1)
        self.__dynamicCfg['maxShotImpulseDistance'] = readFloat(
            dynamicsSection, 'maxShotImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['maxExplosionImpulseDistance'] = readFloat(
            dynamicsSection, 'maxExplosionImpulseDistance', 0.0, 1000.0, 10.0)
        self.__dynamicCfg['zoomExposure'] = readFloat(dynamicsSection,
                                                      'zoomExposure', 0.0,
                                                      1000.0, 0.25)
        accelerationFilter = mathUtils.RangeFilter(
            self.__dynamicCfg['accelerationThreshold'],
            self.__dynamicCfg['accelerationMax'], 100,
            mathUtils.SMAFilter(ArcadeCamera._FILTER_LENGTH))
        maxAccelerationDuration = readFloat(
            dynamicsSection, 'maxAccelerationDuration', 0.0, 10000.0,
            ArcadeCamera._DEFAULT_MAX_ACCELERATION_DURATION)
        self.__accelerationSmoother = AccelerationSmoother(
            accelerationFilter, maxAccelerationDuration)
        self.__inputInertia = _InputInertia(self.__cfg['fovMultMinMaxDist'],
                                            0.0)
        advancedCollider = dataSec['advancedCollider']
        self.__adCfg = dict()
        cfg = self.__adCfg
        if advancedCollider is None:
            LOG_ERROR('<advancedCollider> dataSection is not found!')
            cfg['enable'] = False
        else:
            cfg['enable'] = advancedCollider.readBool('enable', False)
            cfg['fovRatio'] = advancedCollider.readFloat('fovRatio', 2.0)
            cfg['rollbackSpeed'] = advancedCollider.readFloat(
                'rollbackSpeed', 1.0)
            cfg['minimalCameraDistance'] = self.__cfg['distRange'][0]
            cfg['speedThreshold'] = advancedCollider.readFloat(
                'speedThreshold', 0.1)
            cfg['minimalVolume'] = advancedCollider.readFloat(
                'minimalVolume', 200.0)
            cfg['volumeGroups'] = dict()
            for group in VOLUME_GROUPS_NAMES:
                groups = advancedCollider['volumeGroups']
                cfg['volumeGroups'][group] = CollisionVolumeGroup.fromSection(
                    groups[group])

        return

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeBool('arcadeMode/camera/horzInvert', ucfg['horzInvert'])
        ds.writeBool('arcadeMode/camera/vertInvert', ucfg['vertInvert'])
        ds.writeFloat('arcadeMode/camera/keySensitivity',
                      ucfg['keySensitivity'])
        ds.writeFloat('arcadeMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('arcadeMode/camera/scrollSensitivity',
                      ucfg['scrollSensitivity'])
        ds.writeFloat('arcadeMode/camera/startDist', ucfg['startDist'])
        ds.writeFloat('arcadeMode/camera/fovMultMinDist',
                      ucfg['fovMultMinMaxDist'].min)
        ds.writeFloat('arcadeMode/camera/fovMultMaxDist',
                      ucfg['fovMultMinMaxDist'].max)
        ucfg['startAngle'] = math.degrees(ucfg['startAngle'] + math.pi * 0.5)
        ds.writeFloat('arcadeMode/camera/startAngle', ucfg['startAngle'])

    def __onRecreateDevice(self):
        self.__aimingSystem.aimMatrix = self.__calcAimMatrix()
Example #14
0
class ArtyCamera(ICamera, CallbackDelayer):
    _DYNAMIC_ENABLED = True

    @staticmethod
    def enableDynamicCamera(enable):
        ArtyCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return ArtyCamera._DYNAMIC_ENABLED

    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)
    settingsCore = dependency.descriptor(ISettingsCore)
    __aimOffset = aih_global_binding.bindRW(aih_global_binding.BINDING_ID.AIM_OFFSET)

    def __init__(self, dataSec):
        CallbackDelayer.__init__(self)
        self.__positionOscillator = None
        self.__positionNoiseOscillator = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__readCfg(dataSec)
        self.__cam = BigWorld.CursorCamera()
        self.__curSense = self.__cfg['sensitivity']
        self.__onChangeControlMode = None
        self.__camDist = 0.0
        self.__desiredCamDist = 0.0
        self.__aimingSystem = None
        self.__prevTime = 0.0
        self.__dxdydz = Vector3(0.0, 0.0, 0.0)
        self.__autoUpdatePosition = False
        self.__needReset = 0
        self.__sourceMatrix = None
        self.__targetMatrix = None
        self.__rotation = 0.0
        self.__positionHysteresis = None
        self.__timeHysteresis = None
        return

    def create(self, onChangeControlMode=None):
        self.__onChangeControlMode = onChangeControlMode
        aimingSystemClass = ArtyAimingSystemRemote if BigWorld.player().isObserver() else ArtyAimingSystem
        self.__aimingSystem = aimingSystemClass()
        self.__camDist = self.__cfg['camDist']
        self.__desiredCamDist = self.__camDist
        self.__positionHysteresis = PositionHysteresis(self.__cfg['hPositionThresholdSq'])
        self.__timeHysteresis = TimeHysteresis(self.__cfg['hTimeThreshold'])
        self.__cam.pivotMaxDist = 0.0
        self.__cam.maxDistHalfLife = 0.01
        self.__cam.movementHalfLife = 0.0
        self.__cam.turningHalfLife = -1
        self.__cam.pivotPosition = Vector3(0.0, 0.0, 0.0)
        self.__sourceMatrix = Matrix()
        self.__targetMatrix = Matrix()
        self.__rotation = 0.0

    def destroy(self):
        CallbackDelayer.destroy(self)
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        return

    def enable(self, targetPos, saveDist):
        self.__prevTime = 0.0
        self.__aimingSystem.enable(targetPos)
        self.__positionHysteresis.update(Vector3(0.0, 0.0, 0.0))
        self.__timeHysteresis.update(BigWorld.timeExact())
        camTarget = MatrixProduct()
        self.__cam.target = camTarget
        self.__cam.source = self.__sourceMatrix
        self.__cam.wg_applyParams()
        BigWorld.camera(self.__cam)
        BigWorld.player().positionControl.moveTo(self.__aimingSystem.hitPoint)
        BigWorld.player().positionControl.followCamera(False)
        self.__rotation = 0.0
        self.__cameraUpdate()
        self.delayCallback(0.0, self.__cameraUpdate)
        self.__needReset = 1

    def disable(self):
        if self.__aimingSystem is not None:
            self.__aimingSystem.disable()
        self.stopCallback(self.__cameraUpdate)
        self.__positionOscillator.reset()
        return

    def teleport(self, pos):
        self.__aimingSystem.updateTargetPos(pos)
        self.update(0.0, 0.0, 0.0)

    def getConfigValue(self, name):
        return self.__cfg.get(name)

    def getUserConfigValue(self, name):
        return self.__userCfg.get(name)

    def setUserConfigValue(self, name, value):
        if name not in self.__userCfg:
            return
        self.__userCfg[name] = value
        if name not in ('keySensitivity', 'sensitivity', 'scrollSensitivity'):
            self.__cfg[name] = self.__userCfg[name]
        else:
            self.__cfg[name] = self.__baseCfg[name] * self.__userCfg[name]

    def update(self, dx, dy, dz, updateByKeyboard=False):
        self.__curSense = self.__cfg['keySensitivity'] if updateByKeyboard else self.__cfg['sensitivity']
        self.__autoUpdatePosition = updateByKeyboard
        self.__dxdydz = Vector3(dx if not self.__cfg['horzInvert'] else -dx, dy if not self.__cfg['vertInvert'] else -dy, dz)

    def applyImpulse(self, position, impulse, reason=ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(impulse, reason)
        impulseFlatDir = Vector3(adjustedImpulse.x, 0, adjustedImpulse.z)
        impulseFlatDir.normalise()
        impulseLocal = self.__sourceMatrix.applyVector(impulseFlatDir * (-1 * adjustedImpulse.length))
        self.__positionOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self, position, impulseValue, reason=ImpulseReason.ME_HIT):
        if reason != ImpulseReason.SPLASH and reason != ImpulseReason.PROJECTILE_HIT:
            return
        impulse = BigWorld.player().getOwnVehiclePosition() - position
        distance = impulse.length
        if distance <= 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.PROJECTILE_HIT:
            if not cameras.isPointOnScreen(position):
                return
            distance = 1.0
        impulse *= impulseValue / distance
        self.applyImpulse(position, impulse, reason)

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self.__userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeBool('artyMode/camera/horzInvert', ucfg['horzInvert'])
        ds.writeBool('artyMode/camera/vertInvert', ucfg['vertInvert'])
        ds.writeFloat('artyMode/camera/keySensitivity', ucfg['keySensitivity'])
        ds.writeFloat('artyMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('artyMode/camera/scrollSensitivity', ucfg['scrollSensitivity'])
        ds.writeFloat('artyMode/camera/camDist', self.__cfg['camDist'])

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = mathUtils.RandomVectors.random3Flat(noiseMagnitude)
        self.__positionNoiseOscillator.applyImpulse(noiseImpulse)

    def __calculateAimOffset(self, aimWorldPos):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
        else:
            aimOffset = cameras.projectPoint(aimWorldPos)
            aimOffset = Vector2(mathUtils.clamp(-0.95, 0.95, aimOffset.x), mathUtils.clamp(-0.95, 0.95, aimOffset.y))
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        return aimOffset

    def __handleMovement(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying:
            if self.__needReset != 0:
                if self.__needReset > 1:
                    from helpers import isPlayerAvatar
                    player = BigWorld.player()
                    if isPlayerAvatar():
                        if player.inputHandler.ctrl is not None:
                            player.inputHandler.ctrl.resetGunMarkers()
                    self.__needReset = 0
                else:
                    self.__needReset += 1
            if replayCtrl.isControllingCamera:
                self.__aimingSystem.updateTargetPos(replayCtrl.getGunRotatorTargetPoint())
            else:
                self.__aimingSystem.handleMovement(self.__dxdydz.x * self.__curSense, -self.__dxdydz.y * self.__curSense)
                if self.__dxdydz.x != 0 or self.__dxdydz.y != 0 or self.__dxdydz.z != 0:
                    self.__needReset = 2
        else:
            self.__aimingSystem.handleMovement(self.__dxdydz.x * self.__curSense, -self.__dxdydz.y * self.__curSense)
        return

    def __getCameraDirection(self):
        direction = Vector3()
        direction.setPitchYaw(self.__rotation, self.__aimingSystem.direction.yaw)
        direction.normalise()
        return direction

    def __getDesiredCameraDistance(self):
        distRange = self.__cfg['distRange']
        self.__desiredCamDist -= self.__dxdydz.z * self.__curSense
        self.__desiredCamDist = mathUtils.clamp(distRange[0], distRange[1], self.__desiredCamDist)
        self.__desiredCamDist = self.__aimingSystem.overrideCamDist(self.__desiredCamDist)
        self.__cfg['camDist'] = self.__camDist
        return self.__desiredCamDist

    def __updateTrajectoryDrawer(self):
        shotDescr = BigWorld.player().getVehicleDescriptor().shot
        BigWorld.wg_trajectory_drawer().setParams(shotDescr.maxDistance, Vector3(0, -shotDescr.gravity, 0), self.__aimOffset)

    def __updateTime(self):
        curTime = BigWorld.timeExact()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        return deltaTime

    def __choosePitchLevel(self, aimPoint):
        useHighPitch = (aimPoint - self.__aimingSystem.hitPoint).lengthSquared > self.__cfg['highPitchThresholdSq']
        if useHighPitch:
            useHighPitch = self.__positionHysteresis.check(aimPoint) or self.__timeHysteresis.check(self.__prevTime)
        else:
            self.__positionHysteresis.update(aimPoint)
            self.__timeHysteresis.update(self.__prevTime)
        return useHighPitch

    def __getCollisionTestOrigin(self, aimPoint, direction):
        distRange = self.__cfg['distRange']
        vehiclePosition = BigWorld.player().getVehicleAttached().position
        collisionTestOrigin = Vector3(vehiclePosition)
        if direction.x * direction.x > direction.z * direction.z and not mathUtils.almostZero(direction.x):
            collisionTestOrigin.y = direction.y / direction.x * (vehiclePosition.x - aimPoint.x) + aimPoint.y
        elif not mathUtils.almostZero(direction.z):
            collisionTestOrigin.y = direction.y / direction.z * (vehiclePosition.z - aimPoint.z) + aimPoint.y
        else:
            collisionTestOrigin = aimPoint - direction.scale((distRange[1] - distRange[0]) / 2.0)
            LOG_WARNING("Can't find point on direction ray. Using half point as collision test origin")
        return collisionTestOrigin

    def __resolveCollisions(self, aimPoint, distance, direction):
        distRange = self.__cfg['distRange']
        collisionTestOrigin = self.__getCollisionTestOrigin(aimPoint, direction)
        sign = copysign(1.0, distance * distance - (aimPoint - collisionTestOrigin).lengthSquared)
        srcPoint = collisionTestOrigin
        endPoint = aimPoint - direction.scale(distance + sign * distRange[0])
        collision = collideDynamicAndStatic(srcPoint, endPoint, (BigWorld.player().playerVehicleID,))
        if collision:
            collisionDistance = (aimPoint - collision[0]).length
            if sign * (collisionDistance - distance) < distRange[0]:
                distance = collisionDistance - sign * distRange[0]
        if mathUtils.almostZero(self.__rotation):
            srcPoint = aimPoint - direction.scale(distance)
            endPoint = aimPoint
            collision = collideDynamicAndStatic(srcPoint, endPoint, (BigWorld.player().playerVehicleID,))
            if collision:
                self.__aimingSystem.aimPoint = collision[0]
                if collision[1]:
                    self.__positionHysteresis.update(aimPoint)
                    self.__timeHysteresis.update(self.__prevTime)
        return distance

    def __calculateIdealState(self, deltaTime):
        aimPoint = self.__aimingSystem.aimPoint
        direction = self.__aimingSystem.direction
        impactPitch = max(direction.pitch, self.__cfg['minimalPitch'])
        self.__rotation = max(self.__rotation, impactPitch)
        distRange = self.__cfg['distRange']
        distanceCurSq = (aimPoint - BigWorld.player().getVehicleAttached().position).lengthSquared
        distanceMinSq = distRange[0] ** 2.0
        forcedPitch = impactPitch
        if distanceCurSq < distanceMinSq:
            forcedPitch = atan2(sqrt(distanceMinSq - distanceCurSq), sqrt(distanceCurSq))
        angularShift = self.__cfg['angularSpeed'] * deltaTime
        angularShift = angularShift if self.__choosePitchLevel(aimPoint) else -angularShift
        minPitch = max(forcedPitch, impactPitch)
        maxPitch = max(forcedPitch, self.__cfg['maximalPitch'])
        self.__rotation = mathUtils.clamp(minPitch, maxPitch, self.__rotation + angularShift)
        desiredDistance = self.__getDesiredCameraDistance()
        cameraDirection = self.__getCameraDirection()
        desiredDistance = self.__resolveCollisions(aimPoint, desiredDistance, cameraDirection)
        desiredDistance = mathUtils.clamp(distRange[0], distRange[1], desiredDistance)
        translation = aimPoint - cameraDirection.scale(desiredDistance)
        rotation = Vector3(cameraDirection.yaw, -cameraDirection.pitch, 0.0)
        return (translation, rotation)

    def __interpolateStates(self, deltaTime, translation, rotation):
        lerpParam = mathUtils.clamp(0.0, 1.0, deltaTime * self.__cfg['interpolationSpeed'])
        self.__sourceMatrix = slerp(self.__sourceMatrix, rotation, lerpParam)
        self.__targetMatrix.translation = mathUtils.lerp(self.__targetMatrix.translation, translation, lerpParam)
        return (self.__sourceMatrix, self.__targetMatrix)

    def __cameraUpdate(self):
        deltaTime = self.__updateTime()
        self.__handleMovement()
        aimPoint = Vector3(self.__aimingSystem.aimPoint)
        self.__aimOffset = self.__calculateAimOffset(aimPoint)
        self.__updateTrajectoryDrawer()
        translation, rotation = self.__calculateIdealState(deltaTime)
        self.__interpolateStates(deltaTime, translation, mathUtils.createRotationMatrix(rotation))
        self.__camDist = aimPoint - self.__targetMatrix.translation
        self.__camDist = self.__camDist.length
        self.__cam.source = self.__sourceMatrix
        self.__cam.target.b = self.__targetMatrix
        self.__cam.pivotPosition = Vector3(0, 0, 0)
        BigWorld.player().positionControl.moveTo(aimPoint)
        self.__updateOscillator(deltaTime)
        self.__aimingSystem.update(deltaTime)
        if not self.__autoUpdatePosition:
            self.__dxdydz = Vector3(0, 0, 0)

    def __updateOscillator(self, deltaTime):
        if ArtyCamera.isCameraDynamic():
            self.__positionOscillator.update(deltaTime)
            self.__positionNoiseOscillator.update(deltaTime)
        else:
            self.__positionOscillator.reset()
            self.__positionNoiseOscillator.reset()
        self.__cam.target.a = mathUtils.createTranslationMatrix(self.__positionOscillator.deviation + self.__positionNoiseOscillator.deviation)

    def __readCfg(self, dataSec):
        if not dataSec:
            LOG_WARNING('Invalid section <artyMode/camera> in avatar_input_handler.xml')
        self.__baseCfg = dict()
        bcfg = self.__baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.005, 10.0, 0.025)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.005, 10.0, 0.025)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0.005, 10.0, 0.025)
        bcfg['angularSpeed'] = readFloat(dataSec, 'angularSpeed', pi / 720.0, pi / 0.5, pi / 360.0)
        bcfg['distRange'] = readVec2(dataSec, 'distRange', (1.0, 1.0), (10000.0, 10000.0), (2.0, 30.0))
        bcfg['minimalPitch'] = readFloat(dataSec, 'minimalPitch', pi / 36.0, pi / 3.0, pi / 18.0)
        bcfg['maximalPitch'] = readFloat(dataSec, 'maximalPitch', pi / 6.0, pi / 3.0, pi / 3.5)
        bcfg['interpolationSpeed'] = readFloat(dataSec, 'interpolationSpeed', 0.0, 10.0, 5.0)
        bcfg['highPitchThreshold'] = readFloat(dataSec, 'highPitchThreshold', 0.1, 10.0, 3.0)
        bcfg['hTimeThreshold'] = readFloat(dataSec, 'hysteresis/timeThreshold', 0.0, 10.0, 0.5)
        bcfg['hPositionThreshold'] = readFloat(dataSec, 'hysteresis/positionThreshold', 0.0, 100.0, 7.0)
        ds = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if ds is not None:
            ds = ds['artyMode/camera']
        self.__userCfg = dict()
        ucfg = self.__userCfg
        ucfg['horzInvert'] = self.settingsCore.getSetting('mouseHorzInvert')
        ucfg['vertInvert'] = self.settingsCore.getSetting('mouseVertInvert')
        ucfg['keySensitivity'] = readFloat(ds, 'keySensitivity', 0.0, 10.0, 1.0)
        ucfg['sensitivity'] = readFloat(ds, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(ds, 'scrollSensitivity', 0.0, 10.0, 1.0)
        ucfg['camDist'] = readFloat(ds, 'camDist', 0.0, 60.0, 0.0)
        self.__cfg = dict()
        cfg = self.__cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['distRange'] = bcfg['distRange']
        cfg['angularSpeed'] = bcfg['angularSpeed']
        cfg['minimalPitch'] = bcfg['minimalPitch']
        cfg['maximalPitch'] = bcfg['maximalPitch']
        cfg['interpolationSpeed'] = bcfg['interpolationSpeed']
        cfg['highPitchThresholdSq'] = bcfg['highPitchThreshold'] * bcfg['highPitchThreshold']
        cfg['hTimeThreshold'] = bcfg['hTimeThreshold']
        cfg['hPositionThresholdSq'] = bcfg['hPositionThreshold'] * bcfg['hPositionThreshold']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['camDist'] = ucfg['camDist']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']
        dynamicSection = dataSec['dynamics']
        self.__dynamicCfg.readImpulsesConfig(dynamicSection)
        self.__positionOscillator = createOscillatorFromSection(dynamicSection['oscillator'], False)
        self.__positionNoiseOscillator = createOscillatorFromSection(dynamicSection['randomNoiseOscillatorFlat'], False)
        return
Example #15
0
class StrategicCamera(CameraWithSettings, CallbackDelayer):
    _DYNAMIC_ENABLED = True
    ABSOLUTE_VERTICAL_FOV = math.radians(60.0)
    _SMOOTHING_PIVOT_DELTA_FACTOR = 6.0

    @staticmethod
    def enableDynamicCamera(enable):
        StrategicCamera._DYNAMIC_ENABLED = enable

    @staticmethod
    def isCameraDynamic():
        return StrategicCamera._DYNAMIC_ENABLED

    camera = property(lambda self: self.__cam)
    aimingSystem = property(lambda self: self.__aimingSystem)
    __aimOffset = aih_global_binding.bindRW(aih_global_binding.BINDING_ID.AIM_OFFSET)

    def __init__(self, dataSec):
        super(StrategicCamera, self).__init__()
        CallbackDelayer.__init__(self)
        self.__positionOscillator = None
        self.__positionNoiseOscillator = None
        self.__activeDistRangeSettings = None
        self.__dynamicCfg = CameraDynamicConfig()
        self.__cameraYaw = 0.0
        self.__switchers = CameraSwitcherCollection(cameraSwitchers=[CameraSwitcher(switchType=SwitchTypes.FROM_TRANSITION_DIST_AS_MIN, switchToName=CTRL_MODE_NAME.ARTY, switchToPos=1.0)], isEnabled=True)
        self._readConfigs(dataSec)
        self.__cam = BigWorld.CursorCamera()
        self.__cam.isHangar = False
        self.__curSense = self._cfg['sensitivity']
        self.__onChangeControlMode = None
        self.__aimingSystem = None
        self.__prevTime = BigWorld.time()
        self.__autoUpdatePosition = False
        self.__dxdydz = Vector3(0, 0, 0)
        self.__needReset = 0
        self.__smoothingPivotDelta = 0
        self.__transitionEnabled = True
        self.__camDist = 0.0
        self.__scrollSmoother = SPGScrollSmoother(0.3)
        self.__saveDist = False
        return

    @staticmethod
    def _getConfigsKey():
        return StrategicCamera.__name__

    def create(self, onChangeControlMode=None):
        aimingSystemClass = StrategicAimingSystemRemote if BigWorld.player().isObserver() else StrategicAimingSystem
        self.__aimingSystem = aimingSystemClass(self._cfg['distRange'][0], self.__cameraYaw)
        super(StrategicCamera, self).create()
        self.__onChangeControlMode = onChangeControlMode
        self.__camDist = self._cfg['camDist']
        self.__cam.pivotMaxDist = 0.0
        self.__cam.maxDistHalfLife = 0.01
        self.__cam.movementHalfLife = 0.0
        self.__cam.turningHalfLife = -1
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        self.__scrollSmoother.setTime(self._cfg['scrollSmoothingTime'])
        self.__enableSwitchers()

    def destroy(self):
        self.__saveDist = False
        self.disable()
        self.__onChangeControlMode = None
        self.__cam = None
        if self.__aimingSystem is not None:
            self.__aimingSystem.destroy()
            self.__aimingSystem = None
        CallbackDelayer.destroy(self)
        CameraWithSettings.destroy(self)
        return

    def enable(self, targetPos, saveDist, switchToPos=None, switchToPlace=None):
        self.__prevTime = BigWorld.time()
        self.__aimingSystem.enable(targetPos)
        self.__activeDistRangeSettings = self.__getActiveDistRangeForArena()
        if self.__activeDistRangeSettings is not None:
            self.__aimingSystem.height = self.__getDistRange()[0]
        minDist, maxDist = self.__getDistRange()
        maxPivotHeight = maxDist - minDist
        self.__updateCameraYaw()
        if switchToPlace == SwitchToPlaces.TO_TRANSITION_DIST:
            self.__camDist = self.__getTransitionCamDist()
        elif switchToPlace == SwitchToPlaces.TO_RELATIVE_POS and switchToPos is not None:
            self.__camDist = maxPivotHeight * switchToPos
        elif switchToPlace == SwitchToPlaces.TO_NEAR_POS and switchToPos is not None:
            switchToPos = math_utils.clamp(minDist, maxDist, switchToPos)
            self.__camDist = switchToPos - minDist
        elif self.settingsCore.getSetting(settings_constants.SPGAim.AUTO_CHANGE_AIM_MODE):
            self.__camDist = math_utils.clamp(self.__getTransitionCamDist(), maxPivotHeight, self.__camDist)
        self.__saveDist = saveDist
        self.__camDist = math_utils.clamp(0, maxPivotHeight, self.__camDist)
        self.__cam.pivotPosition = Math.Vector3(0.0, self.__camDist, 0.0)
        self.__scrollSmoother.start(self.__camDist)
        self.__enableSwitchers()
        camTarget = Math.MatrixProduct()
        camTarget.b = self.__aimingSystem.matrix
        self.__cam.target = camTarget
        self.__cam.forceUpdate()
        BigWorld.camera(self.__cam)
        BigWorld.player().positionControl.moveTo(self.__aimingSystem.matrix.translation)
        BigWorld.player().positionControl.followCamera(True)
        FovExtended.instance().enabled = False
        BigWorld.projection().fov = StrategicCamera.ABSOLUTE_VERTICAL_FOV
        self.__cameraUpdate()
        self.delayCallback(0.0, self.__cameraUpdate)
        self.__needReset = 1
        return

    def disable(self):
        self.__scrollSmoother.stop()
        if self.__aimingSystem is not None:
            self.__aimingSystem.disable()
        self.__switchers.clear()
        self.stopCallback(self.__cameraUpdate)
        positionControl = BigWorld.player().positionControl
        if positionControl is not None:
            positionControl.followCamera(False)
        self.__positionOscillator.reset()
        FovExtended.instance().resetFov()
        FovExtended.instance().enabled = True
        return

    def teleport(self, pos):
        self.moveToPosition(pos)

    def setMaxDist(self):
        distRange = self.__getDistRange()
        if len(distRange) > 1:
            self.__camDist = distRange[1]

    def getDistRatio(self):
        distRange = self.__getDistRange()
        maxPivotHeight = distRange[1] - distRange[0]
        return self.__camDist / maxPivotHeight

    def getCurrentCamDist(self):
        return self.__camDist + self.__getDistRange()[0]

    def getCamDistRange(self):
        return self.__getDistRange()

    def getCamTransitionDist(self):
        return self._cfg['transitionDist']

    def update(self, dx, dy, dz, updatedByKeyboard=False):
        self.__curSense = self._cfg['keySensitivity'] if updatedByKeyboard else self._cfg['sensitivity']
        standardMaxDist = self._cfg['distRange'][1]
        if self.__camDist > standardMaxDist:
            self.__curSense *= self.__getCameraScrollMultiplier() + (self.__camDist - self._cfg['distRange'][1]) * self.__getCameraAcceleration()
        self.__autoUpdatePosition = updatedByKeyboard
        self.__dxdydz = Vector3(dx, dy, dz)

    def moveToPosition(self, pos):
        self.__aimingSystem.enable(pos)
        self.update(0.0, 0.0, 0.0)

    def calcVisibleAreaRatio(self):
        points = [Math.Vector2(1, 1),
         Math.Vector2(1, -1),
         Math.Vector2(-1, -1),
         Math.Vector2(-1, 1)]
        dirsPos = [ getWorldRayAndPoint(point.x, point.y) for point in points ]
        planeXZ = Plane(Math.Vector3(0, 1, 0), 0)
        collisionPoints = []
        for direction, begPos in dirsPos:
            endPos = begPos + direction * 1000
            testResult = BigWorld.wg_collideSegment(BigWorld.player().spaceID, begPos, endPos, 3)
            collPoint = Math.Vector3(0, 0, 0)
            if collPoint is not None:
                collPoint = testResult.closestPoint
            else:
                collPoint = planeXZ.intersectSegment(begPos, endPos)
            collisionPoints.append(collPoint)

        x0 = abs(collisionPoints[1].x - collisionPoints[2].x)
        x1 = abs(collisionPoints[0].x - collisionPoints[3].x)
        z0 = abs(collisionPoints[0].z - collisionPoints[1].z)
        z1 = abs(collisionPoints[3].z - collisionPoints[2].z)
        bb = BigWorld.player().arena.arenaType.boundingBox
        arenaBottomLeft = bb[0]
        arenaUpperRight = bb[1]
        arenaX = arenaUpperRight[0] - arenaBottomLeft[0]
        arenaZ = arenaUpperRight[1] - arenaBottomLeft[1]
        return ((x0 + x1) * 0.5 / arenaX, (z0 + z1) * 0.5 / arenaZ)

    def applyImpulse(self, position, impulse, reason=ImpulseReason.ME_HIT):
        adjustedImpulse, noiseMagnitude = self.__dynamicCfg.adjustImpulse(impulse, reason)
        impulseFlatDir = Vector3(adjustedImpulse.x, 0, adjustedImpulse.z)
        impulseFlatDir.normalise()
        cameraYawMat = math_utils.createRotationMatrix(Vector3(-self.__cameraYaw, 0.0, 0.0))
        impulseLocal = cameraYawMat.applyVector(impulseFlatDir * (-1 * adjustedImpulse.length))
        self.__positionOscillator.applyImpulse(impulseLocal)
        self.__applyNoiseImpulse(noiseMagnitude)

    def applyDistantImpulse(self, position, impulseValue, reason=ImpulseReason.ME_HIT):
        if reason != ImpulseReason.SPLASH and reason != ImpulseReason.PROJECTILE_HIT:
            return
        impulse = BigWorld.player().getOwnVehiclePosition() - position
        distance = impulse.length
        if distance <= 1.0:
            distance = 1.0
        impulse.normalise()
        if reason == ImpulseReason.PROJECTILE_HIT:
            if not cameras.isPointOnScreen(position):
                return
            distance = 1.0
        impulse *= impulseValue / distance
        self.applyImpulse(position, impulse, reason)

    def __applyNoiseImpulse(self, noiseMagnitude):
        noiseImpulse = math_utils.RandomVectors.random3Flat(noiseMagnitude)
        self.__positionNoiseOscillator.applyImpulse(noiseImpulse)

    def writeUserPreferences(self):
        ds = Settings.g_instance.userPrefs
        if not ds.has_key(Settings.KEY_CONTROL_MODE):
            ds.write(Settings.KEY_CONTROL_MODE, '')
        ucfg = self._userCfg
        ds = ds[Settings.KEY_CONTROL_MODE]
        ds.writeFloat('strategicMode/camera/keySensitivity', ucfg['keySensitivity'])
        ds.writeFloat('strategicMode/camera/sensitivity', ucfg['sensitivity'])
        ds.writeFloat('strategicMode/camera/scrollSensitivity', ucfg['scrollSensitivity'])
        ds.writeFloat('strategicMode/camera/camDist', self._cfg['camDist'])

    def __cameraUpdate(self):
        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isPlaying and replayCtrl.isControllingCamera:
            aimOffset = replayCtrl.getAimClipPosition()
        else:
            aimOffset = self.__calcAimOffset()
            if replayCtrl.isRecording:
                replayCtrl.setAimClipPosition(aimOffset)
        self.__aimOffset = aimOffset
        self.__updateCameraYaw()
        shotDescr = BigWorld.player().getVehicleDescriptor().shot
        BigWorld.wg_trajectory_drawer().setParams(shotDescr.maxDistance, Math.Vector3(0, -shotDescr.gravity, 0), aimOffset)
        curTime = BigWorld.time()
        deltaTime = curTime - self.__prevTime
        self.__prevTime = curTime
        self.__aimingSystem.update(deltaTime)
        if replayCtrl.isPlaying:
            if self.__needReset != 0:
                if self.__needReset > 1:
                    from helpers import isPlayerAvatar
                    player = BigWorld.player()
                    if isPlayerAvatar():
                        if player.inputHandler.ctrl is not None:
                            player.inputHandler.ctrl.resetGunMarkers()
                    self.__needReset = 0
                else:
                    self.__needReset += 1
            if replayCtrl.isControllingCamera:
                self.__aimingSystem.updateTargetPos(replayCtrl.getGunRotatorTargetPoint())
            else:
                self.__aimingSystem.handleMovement(self.__dxdydz.x * self.__curSense, -self.__dxdydz.y * self.__curSense)
                if self.__dxdydz.x != 0 or self.__dxdydz.y != 0 or self.__dxdydz.z != 0:
                    self.__needReset = 2
        else:
            self.__aimingSystem.handleMovement(self.__dxdydz.x * self.__curSense, -self.__dxdydz.y * self.__curSense)
        self.__calcSmoothingPivotDelta(deltaTime)
        self.__camDist -= self.__dxdydz.z * float(self.__curSense)
        self.__camDist = self.__aimingSystem.overrideCamDist(self.__camDist)
        distRange = self.__getDistRange()
        maxPivotHeight = distRange[1] - distRange[0]
        transitionDist = self._cfg['transitionDist'] - distRange[0]
        if self.__switchers.isEnabled():
            self.__camDist = math_utils.clamp(transitionDist, maxPivotHeight, self.__camDist)
            scrollLimits = (transitionDist, maxPivotHeight)
        else:
            self.__camDist = math_utils.clamp(0, maxPivotHeight, self.__camDist)
            scrollLimits = (0, maxPivotHeight)
        if self.__saveDist:
            self._cfg['camDist'] = self.__camDist
        self.__scrollSmoother.moveTo(self.__camDist, scrollLimits)
        currentCamDist = self.__scrollSmoother.update(deltaTime)
        camDistWithSmoothing = currentCamDist + self.__smoothingPivotDelta - self.__aimingSystem.heightFromPlane
        self.__cam.pivotPosition = Math.Vector3(0.0, camDistWithSmoothing, 0.0)
        if self.__onChangeControlMode is not None and self.__switchers.needToSwitch(self.__dxdydz.z, self.__camDist, 0, maxPivotHeight, transitionDist):
            self.__onChangeControlMode(*self.__switchers.getSwitchParams())
        if not self.__transitionEnabled and self.__camDist + TRANSITION_DIST_HYSTERESIS >= transitionDist:
            self.__transitionEnabled = True
            self.__enableSwitchers(False)
        self.__updateOscillator(deltaTime)
        if not self.__autoUpdatePosition:
            self.__dxdydz = Vector3(0, 0, 0)
        return 0.0

    def _handleSettingsChange(self, diff):
        if settings_constants.SPGAim.SPG_STRATEGIC_CAM_MODE in diff:
            self.__aimingSystem.setParallaxModeEnabled(diff[settings_constants.SPGAim.SPG_STRATEGIC_CAM_MODE] == 1)
        if settings_constants.SPGAim.AUTO_CHANGE_AIM_MODE in diff:
            self.__enableSwitchers()
        if settings_constants.SPGAim.SCROLL_SMOOTHING_ENABLED in diff:
            self.__scrollSmoother.setIsEnabled(self.settingsCore.getSetting(settings_constants.SPGAim.SCROLL_SMOOTHING_ENABLED))

    def _updateSettingsFromServer(self):
        if self.settingsCore.isReady:
            if self.__aimingSystem is not None:
                self.__aimingSystem.setParallaxModeEnabled(self.settingsCore.getSetting(settings_constants.SPGAim.SPG_STRATEGIC_CAM_MODE) == 1)
            self.__enableSwitchers()
            self.__scrollSmoother.setIsEnabled(self.settingsCore.getSetting(settings_constants.SPGAim.SCROLL_SMOOTHING_ENABLED))
        return

    def __calcSmoothingPivotDelta(self, deltaTime):
        heightsDy = self.__aimingSystem.heightFromPlane - self.__smoothingPivotDelta
        smoothFactor = math_utils.clamp(0, 1, StrategicCamera._SMOOTHING_PIVOT_DELTA_FACTOR * deltaTime)
        smoothingPivotDeltaDy = smoothFactor * heightsDy
        self.__smoothingPivotDelta += smoothingPivotDeltaDy

    def __calcAimOffset(self):
        aimWorldPos = self.__aimingSystem.matrix.applyPoint(Vector3(0, -self.__aimingSystem.height, 0))
        aimOffset = cameras.projectPoint(aimWorldPos)
        return Vector2(math_utils.clamp(-0.95, 0.95, aimOffset.x), math_utils.clamp(-0.95, 0.95, aimOffset.y))

    def __updateOscillator(self, deltaTime):
        if StrategicCamera.isCameraDynamic():
            self.__positionOscillator.update(deltaTime)
            self.__positionNoiseOscillator.update(deltaTime)
        else:
            self.__positionOscillator.reset()
            self.__positionNoiseOscillator.reset()
        self.__cam.target.a = math_utils.createTranslationMatrix(self.__positionOscillator.deviation + self.__positionNoiseOscillator.deviation)

    def __updateCameraYaw(self):
        altModeEnabled = self.settingsCore.getSetting(settings_constants.SPGAim.SPG_STRATEGIC_CAM_MODE) == 1
        pitch = -math.pi * 0.499 if not altModeEnabled else math.radians(-88.0)
        self.__cameraYaw = round(self.aimingSystem.getCamYaw(), _CAM_YAW_ROUND)
        srcMat = math_utils.createRotationMatrix((self.__cameraYaw, pitch, 0.0))
        self.__cam.source = srcMat

    def __getTransitionCamDist(self):
        minDist, maxDist = self.__getDistRange()
        maxPivotHeight = maxDist - minDist
        transitionDist = self._cfg['transitionDist'] - minDist
        return math_utils.clamp(0, maxPivotHeight, transitionDist)

    def reload(self):
        if not constants.IS_DEVELOPMENT:
            return
        import ResMgr
        ResMgr.purge('gui/avatar_input_handler.xml')
        cameraSec = ResMgr.openSection('gui/avatar_input_handler.xml/strategicMode/camera/')
        self._reloadConfigs(cameraSec)

    def _readConfigs(self, dataSec):
        if not dataSec or dataSec['strategic']:
            LOG_WARNING('Invalid section <strategicMode/camera> in avatar_input_handler.xml')
        super(StrategicCamera, self)._readConfigs(dataSec)
        dynamicsSection = dataSec['dynamics']
        self.__dynamicCfg.readImpulsesConfig(dynamicsSection)
        self.__positionOscillator = createOscillatorFromSection(dynamicsSection['oscillator'], False)
        self.__positionNoiseOscillator = createOscillatorFromSection(dynamicsSection['randomNoiseOscillatorFlat'], False)

    def _readBaseCfg(self, dataSec):
        bcfg = self._baseCfg
        bcfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.005, 10, 0.025)
        bcfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.005, 10, 0.025)
        bcfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0.005, 10, 0.025)
        bcfg['distRange'] = readVec2(dataSec, 'distRange', (1, 1), (10000, 10000), (2, 30))
        bcfg['transitionDist'] = readFloat(dataSec, 'transitionDist', 1.0, 10000.0, 60.0)
        bcfg['distRangeForArenaSize'] = self.__readDynamicDistRangeData(dataSec)
        bcfg['scrollSmoothingTime'] = readFloat(dataSec, 'scrollSmoothingTime', 0.0, 1.0, 0.3)

    def _readUserCfg(self):
        ucfg = self._userCfg
        dataSec = Settings.g_instance.userPrefs[Settings.KEY_CONTROL_MODE]
        if dataSec is not None:
            dataSec = dataSec['strategicMode/camera']
        ucfg['horzInvert'] = False
        ucfg['vertInvert'] = False
        ucfg['keySensitivity'] = readFloat(dataSec, 'keySensitivity', 0.0, 10.0, 1.0)
        ucfg['sensitivity'] = readFloat(dataSec, 'sensitivity', 0.0, 10.0, 1.0)
        ucfg['scrollSensitivity'] = readFloat(dataSec, 'scrollSensitivity', 0.0, 10.0, 1.0)
        ucfg['camDist'] = readFloat(dataSec, 'camDist', 0.0, 60.0, 0)
        return

    def _makeCfg(self):
        bcfg = self._baseCfg
        ucfg = self._userCfg
        cfg = self._cfg
        cfg['keySensitivity'] = bcfg['keySensitivity']
        cfg['sensitivity'] = bcfg['sensitivity']
        cfg['scrollSensitivity'] = bcfg['scrollSensitivity']
        cfg['distRange'] = bcfg['distRange']
        cfg['transitionDist'] = bcfg['transitionDist']
        cfg['distRangeForArenaSize'] = bcfg['distRangeForArenaSize']
        cfg['scrollSmoothingTime'] = bcfg['scrollSmoothingTime']
        cfg['camDist'] = ucfg['camDist']
        cfg['keySensitivity'] *= ucfg['keySensitivity']
        cfg['sensitivity'] *= ucfg['sensitivity']
        cfg['scrollSensitivity'] *= ucfg['scrollSensitivity']
        cfg['horzInvert'] = ucfg['horzInvert']
        cfg['vertInvert'] = ucfg['vertInvert']

    def __readDynamicDistRangeData(self, dataSec):
        section = dataSec['dynamicDistRanges']
        dynamicDistRanges = []
        if section is None:
            return dynamicDistRanges
        else:
            value = section['dynamicDistRange']
            minArenaSize = readFloat(value, 'minArenaSize', 0.1, 2000, 2000.0)
            distRange = readVec2(value, 'distRangeOverride', (1, 1), (2000, 2000), (40, 300))
            acceleration = readFloat(value, 'acceleration', 0.0, 100.0, 0.0)
            scrollMultiplier = readFloat(value, 'scrollMultiplier', 0.0, 100.0, 1.0)
            dynamicDistRanges.append(_DistRangeSetting(minArenaSize, distRange, scrollMultiplier, acceleration))
            return dynamicDistRanges

    def __getActiveDistRangeForArena(self):
        bb = BigWorld.player().arena.arenaType.boundingBox
        arenaBottomLeft = bb[0]
        arenaUpperRight = bb[1]
        arenaX = arenaUpperRight[0] - arenaBottomLeft[0]
        arenaZ = arenaUpperRight[1] - arenaBottomLeft[1]
        arenaSize = min(arenaX, arenaZ)
        availableDistRanges = self._cfg['distRangeForArenaSize']
        currentDistRange = None
        choosenArenaMinSize = 0
        for pt in availableDistRanges:
            if arenaSize >= pt.minArenaSize and pt.minArenaSize > choosenArenaMinSize:
                choosenArenaMinSize = pt.minArenaSize
                currentDistRange = pt

        return currentDistRange

    def __getDistRange(self):
        return self._cfg['distRange'] if not self.__activeDistRangeSettings else self.__activeDistRangeSettings.distRange

    def __getCameraAcceleration(self):
        return 0.0 if not self.__activeDistRangeSettings else self.__activeDistRangeSettings.acceleration

    def __getCameraScrollMultiplier(self):
        return 1.0 if not self.__activeDistRangeSettings else self.__activeDistRangeSettings.scrollMultiplier

    def __enableSwitchers(self, updateTransitionEnabled=True):
        minDist, _ = self.__getDistRange()
        if updateTransitionEnabled and self.__camDist + minDist + TRANSITION_DIST_HYSTERESIS <= self._cfg['transitionDist']:
            self.__transitionEnabled = False
        if self.settingsCore.isReady:
            self.__switchers.setIsEnabled(self.settingsCore.getSetting(settings_constants.SPGAim.AUTO_CHANGE_AIM_MODE) and self.__transitionEnabled)