class Camera: """class controlling a 3d camera for use with opengl""" def __init__(self, mode=1, position=glm.vec3(1, 0, 0), target=glm.vec3(0), worldUp=glm.vec3(0, 1, 0)): # settings self.fpsRotationSpeed = 0.005 # speed with which the camera rotates in fps mode self.moveSpeed = 0.125 # speed with which the camera moves in fps mode self.tbRotationSpeed = 0.015 # speed with which the camera rotates in trackball mode self.panSpeed = 0.006 # speed with which the camera pans in trackball mode self.zoomSpeed = 0.25 # speed with which the camera zooms (used for Z axis in trackball mode) self.enableAllControls = False # enable movement while in trackball mode and pan/zoom while in fps mode self.movementSmoothing = 0.25 # higher numbers result in stronger smoothing and delayed movement self.rotationSmoothing = 0.3 # higher numbers result in stronger smoothing and delayed rotation self.worldUp = worldUp # worlds up vector self.mode = mode # 0 = trackball, 1 = first person self.movementSpeedMod = 1.0 # temporary modified movement speed, will be reset every frame # cameras internal state (DO NOT WRITE) self._movementInput = glm.vec3(0) self._rotationInput = glm.vec2(0) self._desiredTransform = Transform( ) # Transform(position) # desired transform based on inputs self._desiredTargetDistance = 0 # desired distance along the front vector where the center for trackball mode lives self._currentTransform = Transform( ) #Transform(position) # transform object describing orientation and position of camera self._currentTargetDistance = 0 # distance along the front vector where the center for trackball mode lives self.setPosition(position) self.setTarget(target) # variables depending on state, they are automatically updated when calling the Camera.update() (DO NOT WRITE, only read) self.modelMatrix = self._currentTransform.mat4() self.viewMatrix = glm.inverse(self.modelMatrix) def setTarget(self, target, interpolate=False): """Use to set a point where the camera should look. Target is expected to be a glm.vec3, interpolate is a bool. Set interpolate to true produces an animated camera movement""" self._desiredTransform.lookAt(target, self.worldUp) self._desiredTargetDistance = glm.length( target - self._desiredTransform.position) if not interpolate: self._currentTransform.orientation = self._desiredTransform.orientation self._currentTargetDistance = self._desiredTargetDistance def setPosition(self, position, interpolate=False): """Use to set the position of the camera. Position is expected to be a glm.vec3, interpolate is a bool. Set interpolate to true produces an animated camera movement""" newPos = glm.vec3( position.x, position.y, position.z) # avoid python binding a reference to the position self._desiredTransform.position = newPos if not interpolate: self._currentTransform.position = newPos def rotateH(self, dPhi): self._rotationInput.x += dPhi * (self.fpsRotationSpeed if self.mode == 1 else self.tbRotationSpeed) def rotateV(self, dTheta): self._rotationInput.y += dTheta * (self.fpsRotationSpeed if self.mode == 1 else self.tbRotationSpeed) def moveX(self, dx): if self.mode == 1 or self.enableAllControls: self._movementInput.x += dx * self.moveSpeed def moveY(self, dy): if self.mode == 1 or self.enableAllControls: self._movementInput.y += dy * self.moveSpeed def moveZ(self, dz): if self.mode == 1 or self.enableAllControls: self._movementInput.z += dz * self.moveSpeed def panH(self, dx): if self.mode == 0 or self.enableAllControls: self._movementInput.x += dx * self.panSpeed def panV(self, dy): if self.mode == 0 or self.enableAllControls: self._movementInput.y += dy * self.panSpeed def zoom(self, dz): if self.mode == 0 or self.enableAllControls: self._movementInput.z -= dz * self.zoomSpeed def update(self, deltaTime): # movement in camera coordinates movement = self._currentTransform.orientation * (self._movementInput * self.movementSpeedMod) if self.mode == 0: # rotate position around target # figure out where the old target is oldTarget = self._desiredTransform.position \ + self._desiredTransform.orientation * glm.vec3(0, 0, -1) * self._desiredTargetDistance # rotate the camera self._desiredTransform.orientation = glm.normalize( glm.angleAxis(self._rotationInput.x, self.worldUp) * self._desiredTransform.orientation * glm.angleAxis(self._rotationInput.y, glm.vec3(1, 0, 0))) # move so old target matches new target newTarget = self._desiredTransform.position \ + self._desiredTransform.orientation * glm.vec3(0, 0, -1) * self._desiredTargetDistance self._desiredTransform.position += oldTarget - newTarget # pan # zooming, make sure distance to the target does not become negative if self._desiredTargetDistance + self._movementInput.z > 0.01 * self.zoomSpeed: self._desiredTargetDistance += self._movementInput.z # now just apply movement self._desiredTransform.position += movement # interpolate # interpolate distance to target self._currentTargetDistance = glm.mix( self._currentTargetDistance, self._desiredTargetDistance, glm.pow(deltaTime, self.movementSmoothing)) # interpolate current target position desiredTarget = self._desiredTransform.position + self._desiredTransform.orientation * glm.vec3( 0, 0, -1) * self._desiredTargetDistance oldActualTarget = self._currentTransform.position + self._currentTransform.orientation * glm.vec3( 0, 0, -1) * self._currentTargetDistance oldActualTarget = glm.mix( oldActualTarget, desiredTarget, glm.pow(deltaTime, self.movementSmoothing)) # interpolate orientation self._currentTransform.orientation = glm.slerp( self._currentTransform.orientation, self._desiredTransform.orientation, glm.pow(deltaTime, self.rotationSmoothing)) # calculate proper position using difference that was created by rotation and moving the target newActualTarget = self._currentTransform.position + self._currentTransform.orientation * glm.vec3( 0, 0, -1) * self._currentTargetDistance self._currentTransform.position += oldActualTarget - newActualTarget elif self.mode == 1: # movement self._desiredTransform.position += movement # rotation self._desiredTransform.orientation = glm.normalize( glm.angleAxis(self._rotationInput.x, self.worldUp) * self._desiredTransform.orientation * glm.angleAxis(self._rotationInput.y, glm.vec3(1, 0, 0))) # interpolate between transform and desired transform self._currentTransform.position = glm.mix( self._currentTransform.position, self._desiredTransform.position, glm.pow(deltaTime, self.movementSmoothing)) self._currentTransform.orientation = glm.slerp( self._currentTransform.orientation, self._desiredTransform.orientation, glm.pow(deltaTime, self.rotationSmoothing)) self.modelMatrix = self._currentTransform.mat4() self.viewMatrix = glm.inverse(self.modelMatrix) self._rotationInput.x = 0 self._rotationInput.y = 0 self._movementInput.x = 0 self._movementInput.y = 0 self._movementInput.z = 0 self.movementSpeedMod = 1.0