#include "stdafx.h" #include "V8DataModel/Camera.h" #include "V8DataModel/Workspace.h" #include "V8DataModel/GameBasicSettings.h" #include "V8DataModel/PartInstance.h" #include "v8datamodel/GuiService.h" #include "v8datamodel/UserInputService.h" #include "Tool/ToolsArrow.h" // only needed for STUDIO_CAMERA_CONTROL_SHORTCUTS #include "Humanoid/Humanoid.h" #include "V8World/ContactManager.h" #include "V8World/World.h" #include "V8World/Tolerance.h" #include "V8Kernel/Constants.h" #include "Util/Math.h" #include "Util/CameraSubject.h" #include "V8DataModel/ICharacterSubject.h" #include "Util/NavKeys.h" #include "Network/Players.h" #include "FastLog.h" #include "v8datamodel/UserController.h" #include "Util/UserInputBase.h" #include "v8datamodel/GameBasicSettings.h" #include "FastLog.h" #include FASTFLAG(FlyCamOnRenderStep) FASTFLAG(UserBetterInertialScrolling) FASTFLAGVARIABLE(UserAllCamerasInLua, false) FASTFLAGVARIABLE(CameraInterpolateMethodEnhancement, true) FASTFLAGVARIABLE(CameraVR, true) namespace RBX { const char* const sCamera = "Camera"; const char *const category_Camera = "Camera"; REFLECTION_BEGIN(); static Reflection::EnumPropDescriptor desc_cameraType("CameraType", category_Camera, &Camera::getCameraType, &Camera::setCameraType); static Reflection::BoundFuncDesc func_viewportToWorldRay(&Camera::worldRayViewportLua, "ViewportPointToRay", "x","y","depth",0, Security::None); static Reflection::BoundFuncDesc func_screenToWorldRay(&Camera::worldRayLua, "ScreenPointToRay", "x","y","depth",0, Security::None); static Reflection::BoundFuncDesc(Vector3)> func_worldToViewportPoint(&Camera::projectViewportLua, "WorldToViewportPoint", "worldPoint", Security::None); static Reflection::BoundFuncDesc(Vector3)> func_worldToScreenPoint(&Camera::projectLua, "WorldToScreenPoint", "worldPoint", Security::None); static Reflection::PropDescriptor desc_viewport("ViewportSize", category_Data, &Camera::getViewport, NULL); static Reflection::PropDescriptor desc_CFrame("CFrame", category_Data, &Camera::getCameraCoordinateFrame, &Camera::setCameraCoordinateFrame); static Reflection::PropDescriptor desc_CoordFrame("CoordinateFrame", category_Data, &Camera::getCameraCoordinateFrame, &Camera::setCameraCoordinateFrame, Reflection::PropertyDescriptor::Attributes::deprecated(desc_CFrame, Reflection::PropertyDescriptor::LEGACY_SCRIPTING)); static Reflection::PropDescriptor desc_Focus("Focus", category_Data, &Camera::getCameraFocus, &Camera::setCameraFocus); static Reflection::PropDescriptor desc_focus("focus", category_Data, &Camera::getCameraFocus, &Camera::setCameraFocus, Reflection::PropertyDescriptor::Attributes::deprecated(desc_Focus)); static Reflection::PropDescriptor desc_FieldOfView("FieldOfView", category_Data, &Camera::getFieldOfViewDegrees, &Camera::setFieldOfViewDegrees); static Reflection::RefPropDescriptor cameraSubjectProp("CameraSubject", category_Camera, &Camera::getCameraSubjectInstanceDangerous, &Camera::setCameraSubject); static Reflection::BoundFuncDesc func_setroll(&Camera::setRoll, "SetRoll", "rollAngle", Security::None); static Reflection::BoundFuncDesc func_getroll(&Camera::getRollSlow, "GetRoll", Security::None); static Reflection::BoundFuncDesc func_getTiltSpeed(&Camera::getTiltSpeed, "GetTiltSpeed", Security::None); static Reflection::BoundFuncDesc func_getPanSpeed(&Camera::getPanSpeed, "GetPanSpeed", Security::None); static Reflection::BoundFuncDesc func_setCameraPanMode(&Camera::setCameraPanMode, "SetCameraPanMode", "mode", Camera::CAMERAPANMODE_CLASSIC, Security::None); static Reflection::BoundFuncDesc func_zoom(&Camera::zoom, "Zoom", "distance", Security::RobloxScript); static Reflection::BoundFuncDesc func_panUnits(&Camera::panUnits, "PanUnits", "units", Security::None); static Reflection::BoundFuncDesc func_tiltUnits(&Camera::tiltUnits, "TiltUnits", "units", Security::None); static Reflection::BoundFuncDesc func_interpolateCamera(&Camera::beginCameraInterpolation, "Interpolate", "endPos", "endFocus", "duration", Security::None); static Reflection::EventDesc event_doneInterpolating(&Camera::interpolationFinishedSignal, "InterpolationFinished"); static Reflection::EventDesc event_firstPersonTransition(&Camera::firstPersonTransitionSignal, "FirstPersonTransition", "entering", Security::RobloxPlace); static Reflection::PropDescriptor desc_HeadLocked("HeadLocked", category_Data, &Camera::getHeadLocked, &Camera::setHeadLocked); static Reflection::BoundFuncDesc func_GetRenderCFrame(&Camera::getRenderingCoordinateFrameLua, "GetRenderCFrame", Security::None); REFLECTION_END(); namespace Reflection { template<> EnumDesc::EnumDesc() :EnumDescriptor("CameraType") { addPair(Camera::FIXED_CAMERA, "Fixed"); addPair(Camera::WATCH_CAMERA, "Watch"); addPair(Camera::ATTACH_CAMERA, "Attach"); addPair(Camera::TRACK_CAMERA, "Track"); addPair(Camera::FOLLOW_CAMERA, "Follow"); addPair(Camera::CUSTOM_CAMERA, "Custom"); addPair(Camera::LOCKED_CAMERA, "Scriptable"); } template<> EnumDesc::EnumDesc() :EnumDescriptor("CameraMode") { addPair(Camera::CAMERAMODE_CLASSIC, "Classic"); addPair(Camera::CAMERAMODE_LOCKFIRSTPERSON, "LockFirstPerson"); } template<> EnumDesc::EnumDesc() :EnumDescriptor("CameraPanMode") { addPair(RBX::Camera::CAMERAPANMODE_CLASSIC, "Classic"); addPair(RBX::Camera::CAMERAPANMODE_EDGEBUMP, "EdgeBump"); } template<> RBX::Camera::CameraPanMode& Variant::convert(void) { return genericConvert(); } }//namespace Reflection template<> bool RBX::StringConverter::convertToValue(const std::string& text, RBX::Camera::CameraPanMode& value) { if(text.find("Classic") != std::string::npos){ value = RBX::Camera::CAMERAPANMODE_CLASSIC; return true; } if(text.find("EdgeBump") != std::string::npos){ value = RBX::Camera::CAMERAPANMODE_EDGEBUMP; return true; } return false; } //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// static const float defaultFieldOfView = G3D::toRadians(70.0f); float Camera::CameraKeyMoveFactor = 1.5f; float Camera::CameraMouseWheelMoveFactor = 15.0f; float Camera::CameraShiftKeyMoveFactor = .2f; Camera::Camera() : camInterpolation(CAM_INTERPOLATION_NONE), interpolationDuration(0.f), interpolationTime(-1.f), cameraType(Camera::FIXED_CAMERA), cameraFocus(Vector3(0.0f, 0.0f, -5.0f)), fieldOfView(defaultFieldOfView), roll(0.0f), panSpeed(0.0f), tiltSpeed(0.0f), cameraPanMode(Camera::CAMERAPANMODE_CLASSIC), imagePlaneDepth(1.0f / (2.0f * tanf(defaultFieldOfView / 2.0f))), cameraHistoryStack(), currentCameraHistoryPosition(-1), lastHistoryPushTime(0), hasFocalObject(false), viewport(Vector2(0,0)), headLocked(true) { setName("Camera"); CoordinateFrame defaultC(Vector3(0.0f, 20.0f, 20.0f)); defaultC.lookAt(Vector3::zero()); cameraCoord = defaultC; } bool Camera::askSetParent(const Instance* instance) const { return Instance::fastDynamicCast(instance)!=NULL; } // static float Camera::getNewZoomDistance(float currentDistance, float in) { static const float ZOOM_FACTOR = 0.25f; // unitless float answer; if (in > 0.0f) { answer = std::max((currentDistance / (1.0f + ZOOM_FACTOR*in)), Camera::distanceMin()); } else if (in < 0.0f) { answer = std::min((currentDistance * (1.0f - ZOOM_FACTOR*in)), Camera::distanceMax()); } else { answer = currentDistance; } return answer; } bool Camera::isCharacterCamera() const { return ( (cameraSubject.get() != NULL) && ((cameraType == Camera::FOLLOW_CAMERA) || (cameraType == Camera::ATTACH_CAMERA) || (cameraType == Camera::TRACK_CAMERA) || (cameraType == Camera::CUSTOM_CAMERA)) ); } bool Camera::isFirstPersonCamera() const { if(ICharacterSubject* charSubject = dynamic_cast(cameraSubject.get())) return charSubject->isFirstPerson(); return false; } bool Camera::isPartVisibleFast(const PartInstance& part, const ContactManager& contactManager, const HitTestFilter* filter) const { Vector3 hitPoint; std::vector ignorePrims; CoordinateFrame cframe = FFlag::CameraVR ? getRenderingCoordinateFrame() : cameraCoord; Vector3 direction = (part.getCoordinateFrame().translation - cframe.translation) * 2; RbxRay ray = RbxRay::fromOriginAndDirection(cframe.translation, direction); if (Primitive* hitPrim = contactManager.getHit(ray, &ignorePrims, filter, hitPoint) ) return hitPrim == part.getConstPartPrimitive(); return false; } bool Camera::isPartInFrustum(const PartInstance& part) const { RBX::Frustum fr( frustum() ); if(!part.containedByFrustum(fr)) return false; return true; } bool Camera::isLockedToFirstPerson() const { if(ICharacterSubject* charSubject = dynamic_cast(cameraSubject.get())) return charSubject->getCameraMode() == CAMERAMODE_LOCKFIRSTPERSON; return false; } void Camera::onHeartbeat(const Heartbeat& event) { if (interpolationTime >= interpolationDuration) signalInterpolationDone(); CameraSubject* subject = getCameraSubject(); if (subject != NULL && cameraType != Camera::LOCKED_CAMERA && cameraType != Camera::CUSTOM_CAMERA) subject->onCameraHeartbeat(cameraCoord.translation, cameraFocus.translation); else if(RBX::GameBasicSettings::singleton().inStudioMode()) // only do camera interpolation if we are currently using studio { if( (cameraCoordGoal != cameraCoord) && (cameraFocus != cameraFocusGoal) && camInterpolation == CAM_INTERPOLATION_CONSTANT_SPEED ) { fixedSpeedInterpolateCamera(event.wallStep); } else if (FFlag::CameraInterpolateMethodEnhancement && camInterpolation != CAM_INTERPOLATION_CONSTANT_SPEED) signalInterpolationDone(); } } void Camera::signalInterpolationDone() { camInterpolation = CAM_INTERPOLATION_NONE; interpolationTime = -1.f; interpolationFinishedSignal(); } ICameraOwner* Camera::getCameraOwner() { Instance* parent = this; while ((parent = parent->getParent())) { if (ICameraOwner* owner = dynamic_cast(parent)) { return owner; } } return NULL; } // this method will push the current camera data onto a stack, so we can have a camera history! (this will only work in studio with [] keys) void Camera::pushCameraHistoryStack() { if(RBX::Time::nowFastSec() - lastHistoryPushTime < 0.5f) // don't update history too fast return; std::pair newPair(cameraCoord,cameraFocus); if(currentCameraHistoryPosition >= 0 && (unsigned)currentCameraHistoryPosition < cameraHistoryStack.size() && cameraHistoryStack.at(currentCameraHistoryPosition) == newPair) // don't want to push the same thing in history twice return; lastHistoryPushTime = RBX::Time::nowFastSec(); if(currentCameraHistoryPosition >= 0 ) { currentCameraHistoryPosition++; cameraHistoryStack.insert(cameraHistoryStack.begin() + currentCameraHistoryPosition,newPair); } else { currentCameraHistoryPosition = 0; cameraHistoryStack.push_back(newPair); } if(cameraHistoryStack.size() > 50) // can't maintain all history forever, need to remove oldest { cameraHistoryStack.erase(cameraHistoryStack.begin() + cameraHistoryStack.size() - 1); currentCameraHistoryPosition--; } } std::pair Camera::popCameraHistoryStack(bool backward) { if(cameraHistoryStack.size() > 0) { std::pair cameraFrameReturn; if(currentCameraHistoryPosition < 0) { cameraFrameReturn = cameraHistoryStack.back(); currentCameraHistoryPosition = cameraHistoryStack.size() - 1; } else { if(backward && currentCameraHistoryPosition > 0) currentCameraHistoryPosition--; else if(!backward && (unsigned)currentCameraHistoryPosition < (cameraHistoryStack.size() - 1) ) currentCameraHistoryPosition++; cameraFrameReturn = cameraHistoryStack.at(currentCameraHistoryPosition); } return cameraFrameReturn; } return std::pair(CoordinateFrame(),CoordinateFrame()); } void Camera::stepCameraHistoryForward() { if(isCharacterCamera()) return; std::pair newerCameraData = popCameraHistoryStack(false); if(newerCameraData != std::pair(CoordinateFrame(),CoordinateFrame())) { setCameraCoordinateFrame(newerCameraData.first); setCameraFocus(newerCameraData.second); } } void Camera::stepCameraHistoryBackward() { if(isCharacterCamera()) return; std::pair olderCameraData = popCameraHistoryStack(true); if(olderCameraData != std::pair(CoordinateFrame(),CoordinateFrame())) { setCameraCoordinateFrame(olderCameraData.first); setCameraFocus(olderCameraData.second); } } void Camera::updateFocus() { if (cameraSubject.get()) { setCameraFocusWithoutPropertyChange( getCameraSubject()->getRenderLocation() ); } } // when camera goals are different than the actual camera, this function will attempt to // move the camera smoothly thru the space // TODO: Expose this somehow to lua // moves the camera from one position to another at a constant rate, not over a constant time period void Camera::fixedSpeedInterpolateCamera(double elapsedTime) { RBXASSERT(camInterpolation == CAM_INTERPOLATION_CONSTANT_SPEED); double percentOfDist = elapsedTime * interpolationSpeed(); if((cameraCoordGoal.translation - cameraCoord.translation).magnitude() > 0.1) { if(percentOfDist < 1.0f) { Vector3 cameraCoordDiff = cameraCoordGoal.translation - cameraCoord.translation; cameraCoord.translation += (cameraCoordDiff * percentOfDist); } else setCameraCoordinateFrame(cameraCoordGoal); } else if(cameraCoord != cameraCoordGoal) setCameraCoordinateFrame(cameraCoordGoal); if((cameraFocusGoal.translation - cameraFocus.translation).magnitude() > 0.1) { if(percentOfDist < 1.0f) { Vector3 cameraFocusDiff = cameraFocusGoal.translation - cameraFocus.translation; cameraFocus.translation += (cameraFocusDiff * percentOfDist); } else setCameraFocusOnly(cameraFocusGoal); } else if(cameraFocus != cameraFocusGoal) setCameraFocusOnly(cameraFocusGoal); } // begin moving the camera from the current coordinates to endPos and focused on endFocus while taking duration seconds to move there void Camera::beginCameraInterpolation(CoordinateFrame endPos, CoordinateFrame endFocus, float duration) { if (FFlag::CameraInterpolateMethodEnhancement) { RBXASSERT(duration >= 0.f); RBXASSERT(cameraType == Camera::LOCKED_CAMERA || GameBasicSettings::singleton().inStudioMode()); // camera must be scriptable, if not used from Studio if (duration < 0.f) { throw std::runtime_error("Interpolation time must be positive or 0."); } if (cameraType != Camera::LOCKED_CAMERA && !GameBasicSettings::singleton().inStudioMode()) { throw std::runtime_error("Attempted to use interpolation with a camera mode other than scriptable."); } if (duration > 0.f) { camInterpolation = CAM_INTERPOLATION_CONSTANT_TIME; interpolationDuration = duration; interpolationTime = 0.f; } else { camInterpolation = CAM_INTERPOLATION_CONSTANT_SPEED; } if (endPos != cameraCoord) { cameraCoordGoal = endPos; cameraFocusGoal = endFocus; cameraCoordPrev = cameraCoord; cameraFocusPrev = cameraFocus; cameraUpDirPrev = cameraCoord.upVector(); } else { lookAt(endFocus.translation, true); } } else { RBXASSERT(duration > 0.f); RBXASSERT(cameraType == Camera::LOCKED_CAMERA); // camera must be scriptable if (duration <= 0.f) { throw std::runtime_error("Interpolation time must be positive."); } if (cameraType != Camera::LOCKED_CAMERA) { throw std::runtime_error("Attempted to use interpolation with a camera mode other than scriptable."); } camInterpolation = CAM_INTERPOLATION_CONSTANT_TIME; interpolationDuration = duration; interpolationTime = 0.f; cameraCoordGoal = endPos; cameraFocusGoal = endFocus; cameraCoordPrev = cameraCoord; cameraFocusPrev = cameraFocus; cameraUpDirPrev = cameraCoord.upVector(); } } void Camera::step(double elapsedTime) { if (FFlag::UserAllCamerasInLua && hasClientPlayer()) { return; } switch (cameraType) { case Camera::LOCKED_CAMERA: { G3D::Vector3 lookDir = cameraCoord.lookVector(); G3D::Vector3 upDir = cameraCoord.upVector(); if (camInterpolation == CAM_INTERPOLATION_CONSTANT_TIME) { interpolationTime = interpolationTime + elapsedTime; float interpolationVal = std::min(interpolationTime / interpolationDuration, 1.f); cameraCoord.translation = cameraCoordPrev.translation * (1.f - interpolationVal) + cameraCoordGoal.translation * interpolationVal; G3D::Vector3 focusTranslation = cameraFocusPrev.translation * (1.f - interpolationVal) + cameraFocusGoal.translation * interpolationVal; G3D::Vector3 lookVec = focusTranslation - cameraCoord.translation; G3D::Vector3 upTranslation = cameraUpDirPrev * (1.f - interpolationVal) + cameraCoordGoal.upVector() * interpolationVal; upDir = upTranslation.direction(); lookDir = lookVec.direction(); } G3D::Vector3 focusCoord = cameraCoord.translation + 20.0f*lookDir; // We don't have DM write access here, so lets not trigger property changed setCameraFocusOnlyWithoutPropertyChange(focusCoord); // always look straight ahead! if (camInterpolation == CAM_INTERPOLATION_CONSTANT_TIME) { if (cameraCoord.translation == cameraFocus.translation) { //cameraCoord.rotation = cameraFocus.rotation; // scenario when we're panning / tilting (translation is the same but we've rotated) cameraCoord.lookAt(cameraCoord.lookVector(), upDir); } else // otherwise look towards our focal point cameraCoord.lookAt(cameraFocus.translation, upDir); } return; } case Camera::FIXED_CAMERA: { break; } case Camera::WATCH_CAMERA: { // ******** Now - update focus ************ updateFocus(); break; } case Camera::ATTACH_CAMERA: { Vector3 delta = cameraCoord.translation - cameraFocus.translation; float distance = delta.xz().length(); // ******** Now - update focus ************ updateFocus(); Vector2 newDeltaXZ = -cameraFocus.lookVector().xz().direction() * distance; Vector3 newDelta(newDeltaXZ.x, delta.y, newDeltaXZ.y); setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation,cameraFocus.translation + newDelta)); break; } case Camera::TRACK_CAMERA: { Vector3 oldFocusPt = cameraFocus.translation; // ******** Now - update focus ************ updateFocus(); setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation,cameraCoord.translation + (cameraFocus.translation - oldFocusPt)) ); break; } case Camera::FOLLOW_CAMERA: { // Note - distance lags and follows, height follows immediately // Get the desired Y-plane distance Vector3 delta = cameraFocus.translation - cameraCoord.translation; float distance = delta.xz().length(); // ******** Now - update focus ************ updateFocus(); // Get the new look vector const Vector2 newDxz = (cameraFocus.translation.xz() - cameraCoord.translation.xz()).direction() * distance; const Vector3 newDelta(newDxz.x, delta.y, newDxz.y); // Move towards/away from the cameraFocus setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation,cameraFocus.translation - newDelta) ); break; } case Camera::CUSTOM_CAMERA: return; default: break; } if (panSpeed != 0.0f) { panRadians(panSpeed * elapsedTime); } if (tiltSpeed != 0.0f) { tiltRadians(tiltSpeed * elapsedTime); } //If cameraFocus == camera position, just look in the direction we're looking if (cameraCoord.translation == cameraFocus.translation) { //cameraCoord.rotation = cameraFocus.rotation; // scenario when we're panning / tilting (translation is the same but we've rotated) cameraCoord.lookAt(cameraCoord.lookVector()); } else // otherwise look towards our focal point cameraCoord.lookAt(cameraFocus.translation); } void Camera::stepSubject() { if (CameraSubject* subject = getCameraSubject()) { subject->stepRotationalVelocity(cameraCoord.translation, cameraFocus.translation); } } void Camera::zoomOut(CoordinateFrame& cameraPos, CoordinateFrame& cameraFocus, float currentFocusToCameraDistance) { currentFocusToCameraDistance *= 2.0f; currentFocusToCameraDistance = std::max(Camera::distanceMin(), currentFocusToCameraDistance); currentFocusToCameraDistance = std::min(Camera::distanceMax(), currentFocusToCameraDistance); setDistanceFromTarget(currentFocusToCameraDistance, cameraPos, cameraFocus); } void Camera::lerpToExtents(const Extents& extents) { // first, make sure camera lerp goals are stopped, lerp goals are changing stopInterpolation(); // make camera look at the extents if (cameraType == Camera::FIXED_CAMERA) { Vector3 newFocus = extents.center(); Vector3 delta = newFocus - cameraFocus.translation; cameraFocusGoal = CoordinateFrame(cameraFocus.rotation,cameraFocus.translation + delta); cameraCoordGoal = CoordinateFrame(cameraCoord.rotation,cameraCoord.translation + delta); } cameraCoordGoal.lookAt(cameraFocusGoal.translation); bool cameraInExtents = extents.contains(cameraCoordGoal.translation); const Vector3 initialCameraToFocus = (cameraFocusGoal.translation - cameraCoordGoal.translation); float goalFocusToCamera = initialCameraToFocus.magnitude(); CoordinateFrame pos = cameraCoordGoal; CoordinateFrame focus = cameraFocusGoal; if ( RBX::ServiceProvider::findServiceProvider(this) != NULL && ( RBX::Network::Players::getGameMode(this) == RBX::Network::EDIT || RBX::Network::Players::getGameMode(this) == RBX::Network::DPHYS_GAME_SERVER || RBX::Network::Players::getGameMode(this) == RBX::Network::GAME_SERVER ) ) { const Vector3 focusToCameraUnit = -initialCameraToFocus.unit(); float distNeeded = (extents.longestSide()) - (pos.translation - extents.center()).magnitude(); if(distNeeded > 0.0f) { distNeeded += 4.0f; pos.translation += focusToCameraUnit * distNeeded; focus.translation = extents.center(); } } else { // make sure camera is not inside extents, if so, push out camera while(cameraInExtents && goalFocusToCamera > Camera::distanceMin() && goalFocusToCamera < Camera::distanceMax()) { float oldGoal = goalFocusToCamera; zoomOut(pos, focus, goalFocusToCamera); // if we can no longer zoom out, quit if ( goalFocusToCamera == oldGoal ) break; cameraInExtents = extents.contains(pos.translation); } } setCameraLerpGoals(pos,focus); hasFocalObject = true; camInterpolation = CAM_INTERPOLATION_CONSTANT_SPEED; } void Camera::tryZoomExtents(const Extents& extents) { Vector3 extentsSize = extents.size(); float largestSize = extentsSize.x; if ( extentsSize.y > largestSize ) largestSize = extentsSize.y; if( extentsSize.z > largestSize ) largestSize = extentsSize.z; // we are looking at an infinitely small point in space // camera will display white if we don't give it some // volume (otherwise focus == camera position) if(largestSize <= 0) largestSize = 1; // do this before getting lookdir, otherwise if // focus.translation == coord.translation we get a nan vector setCameraFocus(CoordinateFrame(cameraFocus.rotation,extents.center())); Vector3 lookDir = (cameraCoord.translation - cameraFocus.translation).unit(); setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation,extents.center() + (lookDir * largestSize))); } void Camera::zoomExtents(const ModelInstance* model, ZoomType zoomType) { Extents extents = model->computeExtentsWorld(); extents = extents.clampInsideOf(Tolerance::maxExtents()); zoomExtents(extents, zoomType); } void Camera::zoomExtents(const Extents& extents, ZoomType zoomType) { stopInterpolation(); Vector3 delta = Vector3::zero(); if (cameraType == Camera::FIXED_CAMERA) { Vector3 newFocus = extents.center(); delta = newFocus - cameraFocus.translation; setCameraFocus(CoordinateFrame(cameraFocus.rotation,cameraFocus.translation + delta) ); setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation,cameraCoord.translation + delta) ); } float min = distanceMin(); float max = distanceMax(); float cameraToFocus = cameraToFocusDistance(); if (zoomType == ZOOM_OUT_ONLY) { min = std::max(min, cameraToFocus); max = std::max(max, cameraToFocus); } const float current = std::min(max, std::max(min, cameraToFocus)); RBXASSERT(G3D::isFinite(current)); if (G3D::isFinite(min) && G3D::isFinite(current) && G3D::isFinite(max)) { tryZoomExtents(extents); hasFocalObject = true; } pushCameraHistoryStack(); } bool Camera::zoomExtents() { if (ICameraOwner* owner = getCameraOwner()) { zoomExtents(owner->getCameraOwnerModel(), ZOOM_IN_OR_OUT); return true; } else return false; } bool Camera::canZoom(bool inwards) const { if (cameraType == Camera::LOCKED_CAMERA) return false; if (!hasFocalObject) // If we're not focusing on a specific object, we should be able to zoom anywhere return true; return inwards ? (cameraToFocusDistance() > Camera::distanceMin()) : (cameraToFocusDistance() < Camera::distanceMax()); } // only updates the goal // bool Camera::setDistanceFromTarget(float newDistance) { return setDistanceFromTarget(newDistance, cameraCoord, cameraFocus); } bool Camera::setDistanceFromTarget(float newDistance, CoordinateFrame& newCameraPos, const CoordinateFrame& newCameraFocus) { const Vector3 coordToFocus = newCameraFocus.translation - newCameraPos.translation; const float currentDistance = coordToFocus.magnitude(); const float min = Camera::distanceMin(); const float max = Camera::distanceMax(); if ((newDistance < min) && (currentDistance == min)) { return false; } if ((newDistance > max) && (currentDistance == max)) { return false; } newDistance = std::max(min, newDistance); newDistance = std::min(max, newDistance); newCameraPos = CoordinateFrame(newCameraPos.rotation, newCameraFocus.translation - (newDistance * (coordToFocus / currentDistance)) ); return true; } void Camera::onMousePan(const Vector2& wrapMouseDelta) { if (cameraType != Camera::LOCKED_CAMERA) { tiltRadians(Math::degreesToRadians(-0.3f * wrapMouseDelta.y)); panRadians(Math::degreesToRadians(-0.4f * wrapMouseDelta.x)); } } void Camera::onMouseTrack(const Vector2& wrapMouseDelta) { if (cameraType != Camera::LOCKED_CAMERA) { // Track camera by 5% of total pixels moved by the mouse. const float kCameraTrackModifer = 0.05f; // Translate by the Camera y-direction. Vector3 vec = cameraCoord.rotation.column(1); vec.unitize(); cameraCoord.translation += vec * wrapMouseDelta.y * kCameraTrackModifer; cameraFocus.translation += vec * wrapMouseDelta.y * kCameraTrackModifer; // Translate by the Camera x-direction. vec = cameraCoord.rotation.column(0); vec.unitize(); cameraCoord.translation -= vec * wrapMouseDelta.x * kCameraTrackModifer; cameraFocus.translation -= vec * wrapMouseDelta.x * kCameraTrackModifer; } } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// void Camera::setCameraType(Camera::CameraType value) { if (cameraType != value) { stopInterpolation(); cameraType = value; raisePropertyChanged(desc_cameraType); } if (cameraType != Camera::LOCKED_CAMERA) roll = 0; // no rolling for most camera types for now } void Camera::setCameraSubject(Instance* newSubject) { if ( (newSubject != cameraSubject.get()) && dynamic_cast(newSubject) ) { if(ICharacterSubject* charSubject = dynamic_cast(cameraSubject.get())) charSubject->tellCameraSubjectDidChange(cameraSubject,shared_from(newSubject)); cameraSubject = shared_from(newSubject); // this is to help when people set the camera back to the character, instead of the humanoid (control schemes can't interface with camera otherwise) shared_ptr potentialHuman = shared_from(cameraSubject->findFirstChildByName("Humanoid")); if(potentialHuman && Instance::fastDynamicCast(potentialHuman.get())) cameraSubject = potentialHuman; raisePropertyChanged(cameraSubjectProp); } } Instance* Camera::getCameraSubjectInstanceDangerous() const // for reflection { return cameraSubject.get(); } const CameraSubject* Camera::getConstCameraSubject() const { const Instance* i = cameraSubject.get(); if (i) { const CameraSubject* answer = dynamic_cast(i); RBXASSERT(answer); return answer; } else { return NULL; } } CameraSubject* Camera::getCameraSubject() { return const_cast(getConstCameraSubject()); } // Would rather provide an overloaded function but the compiler can't disambiguate for our Reflection PropDescriptor void Camera::setCameraFocusAndMaintainFocus(const CoordinateFrame& value, bool maintainFocusOnPoint) { setCameraFocus(value); // If there is an object that we're focusing on and we want it to stay our focal point, assign here (thus allowing us to rotate around) hasFocalObject = maintainFocusOnPoint; } void Camera::setCameraFocusOnly(const CoordinateFrame& value) { if (value != cameraFocus) { cameraFocus = value; raisePropertyChanged(desc_Focus); } } void Camera::setCameraFocusOnlyWithoutPropertyChange(const CoordinateFrame& value) { if (value != cameraFocus) { cameraFocus = value; } } void Camera::setCameraFocus(const CoordinateFrame& value) { if (value != cameraFocus) { interpolationDuration = 0.f; camInterpolation = CAM_INTERPOLATION_NONE; cameraFocus = value; cameraFocusGoal = value; raisePropertyChanged(desc_Focus); } } void Camera::setCameraFocusWithoutPropertyChange(const CoordinateFrame& value) { if (value != cameraFocus) { interpolationDuration = 0.f; camInterpolation = CAM_INTERPOLATION_NONE; cameraFocus = value; cameraFocusGoal = value; } } void Camera::setFieldOfViewDegrees(float value) { float clampedValue = G3D::clamp(value,1.0f,120.0f); // anything above or below this range is not compatible with the camera if(clampedValue != value) StandardOut::singleton()->printf(MESSAGE_WARNING, "FieldOfView set out of range, should be between %f and %f, setting to %f",1.0f,120.0f,clampedValue); clampedValue = G3D::toRadians(clampedValue); if(clampedValue != fieldOfView) { fieldOfView = clampedValue; imagePlaneDepth = 1.0f / (2.0f * tanf(fieldOfView / 2.0f)); raisePropertyChanged(desc_FieldOfView); } } float Camera::getRollSlow() { return roll; } void Camera::setCameraLerpGoals(const CoordinateFrame& cameraCoordValue, const CoordinateFrame& cameraFocusValue) { cameraCoordGoal = cameraCoordValue; cameraFocusGoal = cameraFocusValue; } void Camera::stopInterpolation() { camInterpolation = CAM_INTERPOLATION_NONE; setCameraLerpGoals(cameraCoord,cameraFocus); interpolationTime = -1.f; } void Camera::setRoll(float value) { if (cameraType == Camera::LOCKED_CAMERA) roll = value; else { // print a warning and disallow for now StandardOut::singleton()->printf(MESSAGE_WARNING, "SetRoll can only be used on Camera objects with a CameraType of Scriptable"); roll = 0; } } void Camera::setCameraCoordinateFrame(const CoordinateFrame& value) { if ((cameraCoord != value) && legalCameraCoord(value)) { cameraCoord = value; cameraCoordGoal = value; raisePropertyChanged(desc_CFrame); raisePropertyChanged(desc_CoordFrame); cframeChangedSignal(cameraCoord); } else if(cameraCoordGoal != value) cameraCoordGoal = value; } //////////////////////////////////////////////////////////////////////// bool Camera::zoom(float in) // in zoom percentage { if(isEditMode()) return nonCharacterZoom(in); if (cameraType == Camera::CUSTOM_CAMERA) { return false; } return isCharacterCamera() ? characterZoom(in) : nonCharacterZoom(in); } bool Camera::nonCharacterZoom(float in) { if (!hasClientPlayer()) { const Vector3 lookVector = cameraCoord.lookVector(); Vector3 zoomVector = lookVector; if ( ControllerService* service = ServiceProvider::create(this) ) { if ( const UserInputBase* hardwareDevice = service->getHardwareDevice() ) { if(DataModel* dm = DataModel::get(this)) { NavKeys navKeys; hardwareDevice->getNavKeys(navKeys, dm->getSharedSuppressNavKeys()); if ( navKeys.shiftKeyDown()) zoomVector *= Camera::CameraShiftKeyMoveFactor; else zoomVector *= (FFlag::UserBetterInertialScrolling ? G3D::abs(in/CameraMouseWheelMoveFactor) : CameraMouseWheelMoveFactor); } } } if(in <= 0.0f) { setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation, cameraCoord.translation - zoomVector)); if(!hasFocalObject) setCameraFocus(CoordinateFrame(cameraFocus.rotation, cameraFocus.translation - zoomVector)); } else { if(!hasFocalObject) { setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation, cameraCoord.translation + zoomVector)); setCameraFocus(CoordinateFrame(cameraFocus.rotation, cameraFocus.translation + zoomVector)); } else { // If our zoom distance is less than the distance to the part, zoom in 1 increment if(cameraToFocusDistance() > zoomVector.magnitude()) setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation, cameraCoord.translation + zoomVector)); // Otherwise zoom to the center of the object else setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation, cameraFocus.translation)); } } pushCameraHistoryStack(); return true; } const Vector3 lookVector = cameraFocus.translation - cameraCoord.translation; const float currentDistance = lookVector.magnitude(); const float newDistance = getNewZoomDistance(currentDistance, in); if (newDistance == currentDistance) return false; else { setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation, cameraCoord.translation - (lookVector * (newDistance / currentDistance - 1.0f))) ); pushCameraHistoryStack(); return true; } } bool Camera::isEditMode() const { return RBX::ServiceProvider::findServiceProvider(this) != NULL && (RBX::Network::Players::getGameMode(this) == RBX::Network::EDIT || RBX::Network::Players::isCloudEdit(this)); } bool Camera::hasClientPlayer() const { RBX::Network::Players* players = ServiceProvider::create(this); return players && players->getLocalPlayer() && !Network::Players::isCloudEdit(this); } bool Camera::characterZoom(float in) // in zoom percentage { // This method is poorly named! This zoom method is used for camera zooming // on a known target, but is not used for zooming in on players, the // ICharacterSubject is instead used for zooming in on players. Vector3 focusToGoal = cameraCoord.translation - cameraFocus.translation; float currentDistance = focusToGoal.magnitude(); float newDistance = std::min( getNewZoomDistance(currentDistance, in) , distanceMaxCharacter() ); if (newDistance == currentDistance) { return false; } else { // This is duplicated in ICharacterSubject. The code is not currently // shared because ICharacterSubject does occlusion adjustments, but the // camera should not account for occlusion when characterZoom is called. focusToGoal.y = 0.0f; focusToGoal.unitize(); // don't allow the camera to get within pi/20 of directly overhead, // the camera's orientation will be lost. tan(pi/20) ~= 1/6. // manToCamera was unitized so the magnitude of the x and z components is // 1, so make sure that the y value is less than or equal to 6. focusToGoal.y = std::min(6.0f, 0.025f * newDistance); setCameraCoordinateFrame(CoordinateFrame(cameraFocus.rotation, cameraFocus.translation + (focusToGoal.unit() * newDistance)) ); return true; } } void Camera::lookAt(const Vector3& point, bool lerpCamera) { if(lerpCamera) // for lerping camera in studio { CoordinateFrame cameraCoordCopy = cameraCoord; cameraCoordCopy.lookAt(point); setCameraLerpGoals(cameraCoordCopy, CoordinateFrame(cameraFocus.rotation, point)); } else { setCameraFocus(CoordinateFrame(cameraFocus.rotation, point)); cameraCoord.lookAt(point); setCameraCoordinateFrame(cameraCoord); } } bool Camera::canTilt(int up) const { if (cameraType == Camera::LOCKED_CAMERA) return false; const Vector3 look = cameraCoord.lookVector(); if (look.y != look.y) // guard against #INV return false; float angle = Math::elevationAngle(look); return (up < 0) ? (angle >= -Math::piHalf()) : (angle <= Math::piHalf()); } void Camera::getHeadingElevationDistance(float& heading, float& elevation, float& distance) { Math::getHeadingElevation(cameraCoord, heading, elevation); distance = cameraToFocusDistance(); } void Camera::setHeadingElevationDistance(float heading, float elevation, float distance) { Math::setHeadingElevation(cameraCoord, heading, elevation); if (distance == 0.0f) // Hack to allow us to tilt / pan when we're at our focal point distance = 1.0f; setCameraCoordinateFrame(CoordinateFrame(cameraCoord.rotation, cameraFocus.translation - distance * cameraCoord.lookVector()) ); } void Camera::tiltSpeedRadians(float tilt) { tiltSpeed = tilt; } void Camera::panSpeedRadians(float angle) { panSpeed = angle; } bool Camera::tiltRadians(float tilt) { if (tilt!=0.0f) { float heading, elevation, distance; getHeadingElevationDistance(heading, elevation, distance); static const float almost90Degrees = Math::pif() * (9.0f / 20.0f); float lookUpMax = almost90Degrees; if(ICharacterSubject* charSubject = dynamic_cast(cameraSubject.get())) { if (charSubject->getCustomCameraMode() == GameBasicSettings::CAMERA_MODE_FOLLOW) { lookUpMax = 60 * Math::pif() / 180.0f; } } float newElevation = G3D::clamp(elevation + tilt, -almost90Degrees, lookUpMax ); if (newElevation != elevation) { if(RBX::GameBasicSettings::singleton().inHybridMode() && !isFirstPersonCamera() && isCharacterCamera()) // don't allow hybrid mode to tilt so much newElevation = G3D::clamp(newElevation,-0.44f,0.22f); setHeadingElevationDistance(heading, newElevation, distance); return true; } } return false; } void Camera::panRadians(float angle) { RBXASSERT(angle > -100.0f); RBXASSERT(angle < 100.0f); // catch weird numbers here if (angle != 0.0f) { float heading, elevation, distance; // from the camera, looking away getHeadingElevationDistance(heading, elevation, distance); heading = static_cast(Math::radWrap(heading + angle)); FASTLOG3F(FLog::UserInputProfile, "Panning camera, heading: %f, elevation: %f, distance: %f", heading, elevation, distance); setHeadingElevationDistance(heading, elevation, distance); } } void Camera::setCameraPanMode(Camera::CameraPanMode mode) { cameraPanMode = mode; } bool Camera::tiltUnits(int up) { const Vector3 look = cameraCoord.lookVector(); float angle = Math::elevationAngle(look); float angleD = Math::radiansToDegrees(angle); int angleId = Math::iRound(angleD * 0.1f); // to 10 degree slots float newAngle = Math::degreesToRadians(10.0f * (angleId + up)); return tiltRadians(newAngle - angle); } void Camera::panUnits(int units) { const Vector3 look = cameraCoord.lookVector(); float angle = atan2(-look.z, -look.x); float newAngle = Math::iRound(angle * 4.0f / Math::pif() + units) * Math::pif() / 4.0f; panRadians(angle - newAngle); } void Camera::setImageServerViewNoLerp(const CoordinateFrame& modelCoord) { Vector3 look = modelCoord.lookVector(); // 1. Clip the look vector to the plane if (std::abs(look.y) > 0.95f) { look = -Vector3::unitZ(); } else { look.y = 0.0f; look = look.direction(); } CoordinateFrame lookCoord; lookCoord.lookAt(look); // goal -z == look vector // 2. Now rotate the look vector to give us a near-"isometric" view lookCoord.rotation *= Matrix3::fromEulerAnglesZXY(45.0f*G3D::pif()/180.0f, 35.0f*G3D::pi()/180.0f, 0.0f); // changed from 40.0, 30.0 on 3/22/07 look = lookCoord.lookVector(); // ok, now clipped and rotated lookCoord.translation = modelCoord.translation + (10.0f * look); // looking AT - reverse direction lookCoord.lookAt(modelCoord.translation); setCameraType(Camera::FIXED_CAMERA); setCameraFocus(modelCoord.translation); setCameraCoordinateFrame(lookCoord); zoomExtents(); } void Camera::doFly(const NavKeys& nav, int steps) { if (getCameraType() != Camera::FIXED_CAMERA) return; if (nav.navKeyDown()) { bool shiftFly = nav.shiftKeyDown(); float accelerationMultiplier = 1.0f; if (FFlag::FlyCamOnRenderStep) { float framesPerSecond = 60.0f; accelerationMultiplier = 0.5f; if (!shiftFly && steps > 2 * framesPerSecond) accelerationMultiplier = std::min(15.0f, ((float)(steps)) / (4.0f * framesPerSecond)); } else { if (!shiftFly && steps > 60) accelerationMultiplier = std::min(30.0f, ((float)(steps))/60.0f); } // If we don't have an avatar, or mouselock mode is off, shift key modifies our camera bool isInMouseLockMode = hasClientPlayer() && GameBasicSettings::singleton().inMouseLockMode(); if ( nav.shiftKeyDown() && !isInMouseLockMode ) accelerationMultiplier *= CameraShiftKeyMoveFactor; CoordinateFrame current = this->getCameraCoordinateFrame(); //Vector3 focus = this->getCameraFocus().translation; Vector3 look = current.lookVector(); Vector3 right = current.rightVector(); Vector3 up = current.upVector(); Vector3 delta; accelerationMultiplier *= CameraKeyMoveFactor; if (nav.forward()) delta += look * accelerationMultiplier; if (nav.backward()) delta -= look * accelerationMultiplier; if (nav.right()) delta += right * accelerationMultiplier; if (nav.left()) delta -= right * accelerationMultiplier; if (nav.up()) delta -= up * accelerationMultiplier; if (nav.down()) delta += up * accelerationMultiplier; current.translation += delta; setCameraCoordinateFrame(current); if (isEditMode()) { hasFocalObject = false; setCameraFocus(current.translation + look*2.0f); } else { // TODO: Hmmm... we're changing the camera type based on a key press setCameraType(Camera::FIXED_CAMERA); setCameraFocus(current.translation + 20.0f*look); } } } float Camera::nearPlaneZ() const { return -0.5f; } float Camera::getImagePlaneDepth() const { // The image plane depth has been pre-computed for // a 1x1 image. Now that the image is width x height, // we need to scale appropriately. return imagePlaneDepth * viewport.y; } float Camera::getViewportWidth() const { return viewport.x; } float Camera::getViewportHeight() const { return viewport.y; } void Camera::setViewport(Vector2int16 newViewport) { if (newViewport != viewport) { viewport = newViewport; raisePropertyChanged(desc_viewport); } } Vector4 Camera::projectPointToScreen(const Vector3& point) const { int screenWidth = viewport.x; int screenHeight = viewport.y; // Find where it hits an image plane of these dimensions const float zImagePlane = getImagePlaneDepth(); const Matrix4 projection = getProjectionPerspective(); const Vector4 out4 = projection * Vector4(point, 1.0f); const Vector3 q = out4.xyz() / out4.w; return Vector4((screenWidth / 2.0) + ((screenWidth / 2.0) * q.x), (screenHeight / 2.0) - ((screenHeight / 2.0) * q.y), zImagePlane * 2 * q.z, out4.w); } shared_ptr makeProjectionArgs(const Vector3& vectorIn2D, const Vector2& viewport) { shared_ptr args = rbx::make_shared(); args->values.push_back(vectorIn2D); const Vector2 clampedPosition = G3D::clamp(Vector2(vectorIn2D.x, vectorIn2D.y),Vector2(0,0), viewport); bool isOnScreen = vectorIn2D.z > 0 && (clampedPosition == Vector2(vectorIn2D.x, vectorIn2D.y)); args->values.push_back(isOnScreen); return args; } shared_ptr Camera::projectLua(Vector3 point) { const Vector4 projection = projectPointToScreen(point); Vector3 offsetVector = Vector3(projection.x, projection.y, projection.w); if (GuiService* guiService = RBX::ServiceProvider::find(this)) { Vector4 guiInset = guiService->getGlobalGuiInset(); offsetVector = Vector3(offsetVector.x - guiInset.x, offsetVector.y - guiInset.y, offsetVector.z); } return makeProjectionArgs(offsetVector, viewport); } shared_ptr Camera::projectViewportLua(Vector3 point) { const Vector4 projection = projectPointToScreen(point); return makeProjectionArgs(Vector3(projection.x, projection.y, projection.w), viewport); } Vector3 Camera::project(const Vector3& point) const { const Vector4 projection = projectPointToScreen(point); if (projection.w <= 0.0f) { // provide at least basic quadrant information. // (helps with clipping) return Vector3(((projection.x < 0.0f) ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()), ((projection.y > 0.0f) ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()), std::numeric_limits::infinity()); } return Vector3(projection.x, projection.y, projection.z); } RbxRay Camera::worldRayLua(float x, float y, float depth) { if (GuiService* guiService = RBX::ServiceProvider::find(this)) { Vector4 guiInset = guiService->getGlobalGuiInset(); return worldRayViewportLua(x + guiInset.x, y + guiInset.y, depth); } return worldRayViewportLua(x, y, depth); } RbxRay Camera::worldRayViewportLua(float x, float y, float depth) { return worldRay(x, y, depth); } RBX::RbxRay Camera::worldRay(float x, float y, float depth) const { int screenWidth = viewport.x; int screenHeight = viewport.y; CoordinateFrame cameraFrame = getRenderingCoordinateFrame(); Vector3 origin = cameraFrame.translation; float cx = screenWidth / 2.0f; float cy = screenHeight / 2.0f; Vector3 point = Vector3((x / cx) - 1.0f, 1.0f - (y / cy), imagePlaneDepth); Matrix4 projection = getProjectionPerspective(); Vector4 projectedPoint = projection.inverse() * Vector4(point, 1.0f); Vector3 projectedPointAdjusted = projectedPoint.xyz() / projectedPoint.w; Vector3 direction = projectedPointAdjusted - origin; // Normalize the direction (we didn't do it before) direction = direction.direction(); float theta = acos(std::min(1.0f, direction.dot(cameraFrame.lookVector()))); float depthToNearClipPlane = imagePlaneDepth / sin((Math::pif() / 2) - theta); return RBX::RbxRay::fromOriginAndDirection(origin + (direction * depthToNearClipPlane) + (direction * depth), direction); } const CoordinateFrame& Camera::coordinateFrame() const { return getCameraCoordinateFrame(); } float Camera::dot(const Vector3& point) const { Vector3 toPoint = point - cameraCoord.translation; return cameraCoord.lookVector().dot(toPoint); } RBX::Frustum Camera::frustum() const { RBX::Frustum f; frustum(farPlaneZ(), f); return f; } void Camera::frustum(const float farPlaneZ, RBX::Frustum& fr) const { fr.faceArray.fastClear(); // The volume is the convex hull of the vertices defining the view // frustum and the light source point at infinity. const CoordinateFrame& cframe = getRenderingCoordinateFrame(); if( !Math::hasNanOrInf(cframe) ) { float fovx; fovx = 2 * atan(tan(fieldOfView * 0.5f) * viewport.x / viewport.y); fr = Frustum(cframe.translation, -cframe.rotation.column(2), cframe.rotation.column(1), -nearPlaneZ(), -farPlaneZ, fovx, fieldOfView); } } bool Camera::legalCameraCoord(const CoordinateFrame& c) { if(Math::hasNanOrInf(c)) return false; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { float r = c.rotation[i][j]; if (!((r > -1.2f) && (r < 1.2f))) { return false; } } float t = c.translation[i]; if (!((t > -1e6f) && (t < 1e6f))) { return false; } } return true; } Matrix4 Camera::getProjectionPerspective() const { Matrix4 view; if (FFlag::CameraVR) { CoordinateFrame cframe = getRenderingCoordinateFrame(); view = cframe.inverse().toMatrix4(); } else { CoordinateFrame cframe = getCameraCoordinateFrame(); view = Matrix4::rollDegrees(G3D::toDegrees(roll)) * cframe.inverse().toMatrix4(); } int screenWidth = viewport.x; int screenHeight = viewport.y; float aspect = (float) screenWidth / (float) screenHeight; float h = 1 / tanf(getFieldOfView() / 2); float w = h / aspect; float zfar = - nearPlaneZ(); float znear = - farPlaneZ(); // Note: this maps to [0..1] Z range float q = -zfar / (zfar - znear); float qn = znear * q; Matrix4 projection( w, 0, 0, 0, 0, h, 0, 0, 0, 0, q, qn, 0, 0, -1, 0); Matrix4 viewProjection = projection * view; return viewProjection; } CoordinateFrame Camera::getRenderingCoordinateFrame() const { CoordinateFrame result = cameraCoord; if (roll != 0) result.rotation *= Matrix3::fromAxisAngle(Vector3::unitZ(), -roll); UserInputService* uis = ServiceProvider::find(this); if (uis && headLocked) result = result * uis->getUserHeadCFrame(); return result; } void Camera::setHeadLocked(bool value) { if (headLocked != value) { headLocked = value; raisePropertyChanged(desc_HeadLocked); } } } // namespace RBX