Преглед изворни кода

Fixes Camera::project returns [0-1] depth range.
Fixed #1278

seanpaultaylor пре 12 година
родитељ
комит
65c2775808
1 измењених фајлова са 498 додато и 498 уклоњено
  1. 498 498
      gameplay/src/Camera.cpp

+ 498 - 498
gameplay/src/Camera.cpp

@@ -1,498 +1,498 @@
-#include "Base.h"
-#include "Camera.h"
-#include "Game.h"
-#include "Node.h"
-#include "Game.h"
-#include "PhysicsController.h"
-
-// Camera dirty bits
-#define CAMERA_DIRTY_VIEW 1
-#define CAMERA_DIRTY_PROJ 2
-#define CAMERA_DIRTY_VIEW_PROJ 4
-#define CAMERA_DIRTY_INV_VIEW 8
-#define CAMERA_DIRTY_INV_VIEW_PROJ 16
-#define CAMERA_DIRTY_BOUNDS 32
-#define CAMERA_DIRTY_ALL (CAMERA_DIRTY_VIEW | CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS)
-
-// Other misc camera bits
-#define CAMERA_CUSTOM_PROJECTION 64
-
-namespace gameplay
-{
-
-Camera::Camera(float fieldOfView, float aspectRatio, float nearPlane, float farPlane)
-    : _type(PERSPECTIVE), _fieldOfView(fieldOfView), _aspectRatio(aspectRatio), _nearPlane(nearPlane), _farPlane(farPlane),
-    _bits(CAMERA_DIRTY_ALL), _node(NULL), _listeners(NULL)
-{
-}
-
-Camera::Camera(float zoomX, float zoomY, float aspectRatio, float nearPlane, float farPlane)
-    : _type(ORTHOGRAPHIC), _aspectRatio(aspectRatio), _nearPlane(nearPlane), _farPlane(farPlane),
-	_bits(CAMERA_DIRTY_ALL), _node(NULL), _listeners(NULL)
-{
-    // Orthographic camera.
-    _zoom[0] = zoomX;
-    _zoom[1] = zoomY;
-}
-
-Camera::~Camera()
-{
-}
-
-Camera* Camera::createPerspective(float fieldOfView, float aspectRatio, float nearPlane, float farPlane)
-{
-    return new Camera(fieldOfView, aspectRatio, nearPlane, farPlane);
-}
-
-Camera* Camera::createOrthographic(float zoomX, float zoomY, float aspectRatio, float nearPlane, float farPlane)
-{
-    return new Camera(zoomX, zoomY, aspectRatio, nearPlane, farPlane);
-}
-
-Camera* Camera::create(Properties* properties)
-{
-    GP_ASSERT(properties);
-
-    // Read camera type
-    std::string typeStr;
-    if (properties->exists("type"))
-        typeStr = properties->getString("type");
-    Camera::Type type;
-    if (typeStr == "PERSPECTIVE")
-    {
-        type = Camera::PERSPECTIVE;
-    }
-    else if (typeStr == "ORTHOGRAPHIC")
-    {
-        type = Camera::ORTHOGRAPHIC;
-    }
-    else
-    {
-        GP_ERROR("Invalid 'type' parameter for camera definition.");
-        return NULL;
-    }
-
-    // Read common parameters
-    float aspectRatio, nearPlane, farPlane;
-    if (properties->exists("aspectRatio"))
-    {
-        aspectRatio = properties->getFloat("aspectRatio");
-    }
-    else
-    {
-        // Use default aspect ratio
-        aspectRatio = (float)Game::getInstance()->getWidth() / Game::getInstance()->getHeight();
-    }
-
-    if (properties->exists("nearPlane"))
-        nearPlane = properties->getFloat("nearPlane");
-    else
-        nearPlane = 0.2f; // use some reasonable default value
-
-    if (properties->exists("farPlane"))
-        farPlane = properties->getFloat("farPlane");
-    else
-        farPlane = 100; // use some reasonable default value
-
-    Camera* camera = NULL;
-
-    switch (type)
-    {
-    case Camera::PERSPECTIVE:
-        // If field of view is not specified, use a default of 60 degrees
-        camera = createPerspective(
-            properties->exists("fieldOfView") ? properties->getFloat("fieldOfView") : 60.0f,
-            aspectRatio, nearPlane, farPlane);
-        break;
-
-    case Camera::ORTHOGRAPHIC:
-        // If zoomX and zoomY are not specified, use screen width/height
-        camera = createOrthographic(
-            properties->exists("zoomX") ? properties->getFloat("zoomX") : Game::getInstance()->getWidth(),
-            properties->exists("zoomY") ? properties->getFloat("zoomY") : Game::getInstance()->getHeight(),
-            aspectRatio, nearPlane, farPlane);
-        break;
-    }
-
-    return camera;
-}
-
-Camera::Type Camera::getCameraType() const
-{
-    return _type;
-}
-
-float Camera::getFieldOfView() const
-{
-    GP_ASSERT(_type == Camera::PERSPECTIVE);
-
-    return _fieldOfView;
-}
-
-void Camera::setFieldOfView(float fieldOfView)
-{
-    GP_ASSERT(_type == Camera::PERSPECTIVE);
-
-    _fieldOfView = fieldOfView;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-    cameraChanged();
-}
-
-float Camera::getZoomX() const
-{
-    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
-
-    return _zoom[0];
-}
-
-void Camera::setZoomX(float zoomX)
-{
-    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
-
-    _zoom[0] = zoomX;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-    cameraChanged();
-}
-
-float Camera::getZoomY() const
-{
-    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
-
-    return _zoom[1];
-}
-
-void Camera::setZoomY(float zoomY)
-{
-    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
-
-    _zoom[1] = zoomY;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-    cameraChanged();
-}
-
-float Camera::getAspectRatio() const
-{
-    return _aspectRatio;
-}
-
-void Camera::setAspectRatio(float aspectRatio)
-{
-    _aspectRatio = aspectRatio;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-    cameraChanged();
-}
-
-float Camera::getNearPlane() const
-{
-    return _nearPlane;
-}
-
-void Camera::setNearPlane(float nearPlane)
-{
-    _nearPlane = nearPlane;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-    cameraChanged();
-}
-
-float Camera::getFarPlane() const
-{
-    return _farPlane;
-}
-
-void Camera::setFarPlane(float farPlane)
-{
-    _farPlane = farPlane;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-    cameraChanged();
-}
-
-Node* Camera::getNode() const
-{
-    return _node;
-}
-
-void Camera::setNode(Node* node)
-{
-    if (_node != node)
-    {
-        if (_node)
-        {
-            _node->removeListener(this);
-        }
-
-        // Connect the new node.
-        _node = node;
-
-        if (_node)
-        {
-            _node->addListener(this);
-        }
-
-        _bits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-        cameraChanged();
-    }
-}
-
-const Matrix& Camera::getViewMatrix() const
-{
-    if (_bits & CAMERA_DIRTY_VIEW)
-    {
-        if (_node)
-        {
-            // The view matrix is the inverse of our transform matrix.
-            _node->getWorldMatrix().invert(&_view);
-        }
-        else
-        {
-            _view.setIdentity();
-        }
-
-        _bits &= ~CAMERA_DIRTY_VIEW;
-    }
-
-    return _view;
-}
-
-const Matrix& Camera::getInverseViewMatrix() const
-{
-    if (_bits & CAMERA_DIRTY_INV_VIEW)
-    {
-        getViewMatrix().invert(&_inverseView);
-
-        _bits &= ~CAMERA_DIRTY_INV_VIEW;
-    }
-
-    return _inverseView;
-}
-
-const Matrix& Camera::getProjectionMatrix() const
-{
-    if (!(_bits & CAMERA_CUSTOM_PROJECTION) && (_bits & CAMERA_DIRTY_PROJ))
-    {
-        if (_type == PERSPECTIVE)
-        {
-            Matrix::createPerspective(_fieldOfView, _aspectRatio, _nearPlane, _farPlane, &_projection);
-        }
-        else
-        {
-            // Create an ortho projection with the origin at the bottom left of the viewport, +X to the right and +Y up.
-            Matrix::createOrthographic(_zoom[0], _zoom[1], _nearPlane, _farPlane, &_projection);
-        }
-
-        _bits &= ~CAMERA_DIRTY_PROJ;
-    }
-
-    return _projection;
-}
-
-void Camera::setProjectionMatrix(const Matrix& matrix)
-{
-    _projection = matrix;
-    _bits |= CAMERA_CUSTOM_PROJECTION;
-    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-
-    cameraChanged();
-}
-
-void Camera::resetProjectionMatrix()
-{
-    if (_bits & CAMERA_CUSTOM_PROJECTION)
-    {
-        _bits &= ~CAMERA_CUSTOM_PROJECTION;
-        _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-
-        cameraChanged();
-    }
-}
-
-const Matrix& Camera::getViewProjectionMatrix() const
-{
-    if (_bits & CAMERA_DIRTY_VIEW_PROJ)
-    {
-        Matrix::multiply(getProjectionMatrix(), getViewMatrix(), &_viewProjection);
-
-        _bits &= ~CAMERA_DIRTY_VIEW_PROJ;
-    }
-
-    return _viewProjection;
-}
-
-const Matrix& Camera::getInverseViewProjectionMatrix() const
-{
-    if (_bits & CAMERA_DIRTY_INV_VIEW_PROJ)
-    {
-        getViewProjectionMatrix().invert(&_inverseViewProjection);
-
-        _bits &= ~CAMERA_DIRTY_INV_VIEW_PROJ;
-    }
-
-    return _inverseViewProjection;
-}
-
-const Frustum& Camera::getFrustum() const
-{
-    if (_bits & CAMERA_DIRTY_BOUNDS)
-    {
-        // Update our bounding frustum from our view projection matrix.
-        _bounds.set(getViewProjectionMatrix());
-
-        _bits &= ~CAMERA_DIRTY_BOUNDS;
-    }
-
-    return _bounds;
-}
-
-void Camera::project(const Rectangle& viewport, const Vector3& position, float* x, float* y, float* depth) const
-{
-    GP_ASSERT(x);
-    GP_ASSERT(y);
-
-    // Transform the point to clip-space.
-    Vector4 clipPos;
-    getViewProjectionMatrix().transformVector(Vector4(position.x, position.y, position.z, 1.0f), &clipPos);
-
-    // Compute normalized device coordinates.
-    GP_ASSERT(clipPos.w != 0.0f);
-    float ndcX = clipPos.x / clipPos.w;
-    float ndcY = clipPos.y / clipPos.w;
-
-    // Compute screen coordinates by applying our viewport transformation.
-    *x = viewport.x + (ndcX + 1.0f) * 0.5f * viewport.width;
-    *y = viewport.y + (1.0f - (ndcY + 1.0f) * 0.5f) * viewport.height;
-    if (depth)
-    {
-        float ndcZ = clipPos.z / clipPos.w;
-        *depth = ndcZ + 1.0f / 2.0f;
-    }
-}
-
-void Camera::project(const Rectangle& viewport, const Vector3& position, Vector2* out) const
-{
-    GP_ASSERT(out);
-    float x, y;
-    project(viewport, position, &x, &y);
-    out->set(x, y);
-}
-
-void Camera::project(const Rectangle& viewport, const Vector3& position, Vector3* out) const
-{
-    GP_ASSERT(out);
-    float x, y, depth;
-    project(viewport, position, &x, &y, &depth);
-    out->set(x, y, depth);
-}
-
-void Camera::unproject(const Rectangle& viewport, float x, float y, float depth, Vector3* dst) const
-{
-    GP_ASSERT(dst);
-    
-    // Create our screen space position in NDC.
-    GP_ASSERT(viewport.width != 0.0f && viewport.height != 0.0f);
-    Vector4 screen((x - viewport.x) / viewport.width, ((viewport.height - y) - viewport.y) / viewport.height, depth, 1.0f);
-
-    // Map to range -1 to 1.
-    screen.x = screen.x * 2.0f - 1.0f;
-    screen.y = screen.y * 2.0f - 1.0f;
-    screen.z = screen.z * 2.0f - 1.0f;
-
-    // Transform the screen-space NDC by our inverse view projection matrix.
-    getInverseViewProjectionMatrix().transformVector(screen, &screen);
-
-    // Divide by our W coordinate.
-    if (screen.w != 0.0f)
-    {
-        screen.x /= screen.w;
-        screen.y /= screen.w;
-        screen.z /= screen.w;
-    }
-
-    dst->set(screen.x, screen.y, screen.z);
-}
-
-void Camera::pickRay(const Rectangle& viewport, float x, float y, Ray* dst) const
-{
-    GP_ASSERT(dst);
-
-    // Get the world-space position at the near clip plane.
-    Vector3 nearPoint;
-    unproject(viewport, x, y, 0.0f, &nearPoint);
-
-    // Get the world-space position at the far clip plane.
-    Vector3 farPoint;
-    unproject(viewport, x, y, 1.0f, &farPoint);
-
-    // Set the direction of the ray.
-    Vector3 direction;
-    Vector3::subtract(farPoint, nearPoint, &direction);
-    direction.normalize();
-
-    dst->set(nearPoint, direction);
-}
-
-Camera* Camera::clone(NodeCloneContext &context) const
-{
-    Camera* cameraClone = NULL;
-    if (getCameraType() == PERSPECTIVE)
-    {
-        cameraClone = createPerspective(_fieldOfView, _aspectRatio, _nearPlane, _farPlane);
-    }
-    else if (getCameraType() == ORTHOGRAPHIC)
-    {
-        cameraClone = createOrthographic(getZoomX(), getZoomY(), getAspectRatio(), _nearPlane, _farPlane);
-    }
-    GP_ASSERT(cameraClone);
-
-    if (Node* node = context.findClonedNode(getNode()))
-    {
-        cameraClone->setNode(node);
-    }
-    return cameraClone;
-}
-
-void Camera::transformChanged(Transform* transform, long cookie)
-{
-    _bits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
-
-    cameraChanged();
-}
-
-void Camera::cameraChanged()
-{
-    if (_listeners == NULL)
-        return;
-
-    for (std::list<Camera::Listener*>::iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
-    {
-        Camera::Listener* listener = (*itr);
-        listener->cameraChanged(this);
-    }
-}
-
-void Camera::addListener(Camera::Listener* listener)
-{
-    GP_ASSERT(listener);
-
-    if (_listeners == NULL)
-        _listeners = new std::list<Camera::Listener*>();
-
-    _listeners->push_back(listener);
-}
-
-void Camera::removeListener(Camera::Listener* listener)
-{
-    GP_ASSERT(listener);
-
-    if (_listeners)
-    {
-        for (std::list<Camera::Listener*>::iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
-        {
-            if ((*itr) == listener)
-            {
-                _listeners->erase(itr);
-                break;
-            }
-        }
-    }
-}
-
-}
+#include "Base.h"
+#include "Camera.h"
+#include "Game.h"
+#include "Node.h"
+#include "Game.h"
+#include "PhysicsController.h"
+
+// Camera dirty bits
+#define CAMERA_DIRTY_VIEW 1
+#define CAMERA_DIRTY_PROJ 2
+#define CAMERA_DIRTY_VIEW_PROJ 4
+#define CAMERA_DIRTY_INV_VIEW 8
+#define CAMERA_DIRTY_INV_VIEW_PROJ 16
+#define CAMERA_DIRTY_BOUNDS 32
+#define CAMERA_DIRTY_ALL (CAMERA_DIRTY_VIEW | CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS)
+
+// Other misc camera bits
+#define CAMERA_CUSTOM_PROJECTION 64
+
+namespace gameplay
+{
+
+Camera::Camera(float fieldOfView, float aspectRatio, float nearPlane, float farPlane)
+    : _type(PERSPECTIVE), _fieldOfView(fieldOfView), _aspectRatio(aspectRatio), _nearPlane(nearPlane), _farPlane(farPlane),
+    _bits(CAMERA_DIRTY_ALL), _node(NULL), _listeners(NULL)
+{
+}
+
+Camera::Camera(float zoomX, float zoomY, float aspectRatio, float nearPlane, float farPlane)
+    : _type(ORTHOGRAPHIC), _aspectRatio(aspectRatio), _nearPlane(nearPlane), _farPlane(farPlane),
+	_bits(CAMERA_DIRTY_ALL), _node(NULL), _listeners(NULL)
+{
+    // Orthographic camera.
+    _zoom[0] = zoomX;
+    _zoom[1] = zoomY;
+}
+
+Camera::~Camera()
+{
+}
+
+Camera* Camera::createPerspective(float fieldOfView, float aspectRatio, float nearPlane, float farPlane)
+{
+    return new Camera(fieldOfView, aspectRatio, nearPlane, farPlane);
+}
+
+Camera* Camera::createOrthographic(float zoomX, float zoomY, float aspectRatio, float nearPlane, float farPlane)
+{
+    return new Camera(zoomX, zoomY, aspectRatio, nearPlane, farPlane);
+}
+
+Camera* Camera::create(Properties* properties)
+{
+    GP_ASSERT(properties);
+
+    // Read camera type
+    std::string typeStr;
+    if (properties->exists("type"))
+        typeStr = properties->getString("type");
+    Camera::Type type;
+    if (typeStr == "PERSPECTIVE")
+    {
+        type = Camera::PERSPECTIVE;
+    }
+    else if (typeStr == "ORTHOGRAPHIC")
+    {
+        type = Camera::ORTHOGRAPHIC;
+    }
+    else
+    {
+        GP_ERROR("Invalid 'type' parameter for camera definition.");
+        return NULL;
+    }
+
+    // Read common parameters
+    float aspectRatio, nearPlane, farPlane;
+    if (properties->exists("aspectRatio"))
+    {
+        aspectRatio = properties->getFloat("aspectRatio");
+    }
+    else
+    {
+        // Use default aspect ratio
+        aspectRatio = (float)Game::getInstance()->getWidth() / Game::getInstance()->getHeight();
+    }
+
+    if (properties->exists("nearPlane"))
+        nearPlane = properties->getFloat("nearPlane");
+    else
+        nearPlane = 0.2f; // use some reasonable default value
+
+    if (properties->exists("farPlane"))
+        farPlane = properties->getFloat("farPlane");
+    else
+        farPlane = 100; // use some reasonable default value
+
+    Camera* camera = NULL;
+
+    switch (type)
+    {
+    case Camera::PERSPECTIVE:
+        // If field of view is not specified, use a default of 60 degrees
+        camera = createPerspective(
+            properties->exists("fieldOfView") ? properties->getFloat("fieldOfView") : 60.0f,
+            aspectRatio, nearPlane, farPlane);
+        break;
+
+    case Camera::ORTHOGRAPHIC:
+        // If zoomX and zoomY are not specified, use screen width/height
+        camera = createOrthographic(
+            properties->exists("zoomX") ? properties->getFloat("zoomX") : Game::getInstance()->getWidth(),
+            properties->exists("zoomY") ? properties->getFloat("zoomY") : Game::getInstance()->getHeight(),
+            aspectRatio, nearPlane, farPlane);
+        break;
+    }
+
+    return camera;
+}
+
+Camera::Type Camera::getCameraType() const
+{
+    return _type;
+}
+
+float Camera::getFieldOfView() const
+{
+    GP_ASSERT(_type == Camera::PERSPECTIVE);
+
+    return _fieldOfView;
+}
+
+void Camera::setFieldOfView(float fieldOfView)
+{
+    GP_ASSERT(_type == Camera::PERSPECTIVE);
+
+    _fieldOfView = fieldOfView;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+    cameraChanged();
+}
+
+float Camera::getZoomX() const
+{
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
+
+    return _zoom[0];
+}
+
+void Camera::setZoomX(float zoomX)
+{
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
+
+    _zoom[0] = zoomX;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+    cameraChanged();
+}
+
+float Camera::getZoomY() const
+{
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
+
+    return _zoom[1];
+}
+
+void Camera::setZoomY(float zoomY)
+{
+    GP_ASSERT(_type == Camera::ORTHOGRAPHIC);
+
+    _zoom[1] = zoomY;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+    cameraChanged();
+}
+
+float Camera::getAspectRatio() const
+{
+    return _aspectRatio;
+}
+
+void Camera::setAspectRatio(float aspectRatio)
+{
+    _aspectRatio = aspectRatio;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+    cameraChanged();
+}
+
+float Camera::getNearPlane() const
+{
+    return _nearPlane;
+}
+
+void Camera::setNearPlane(float nearPlane)
+{
+    _nearPlane = nearPlane;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+    cameraChanged();
+}
+
+float Camera::getFarPlane() const
+{
+    return _farPlane;
+}
+
+void Camera::setFarPlane(float farPlane)
+{
+    _farPlane = farPlane;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+    cameraChanged();
+}
+
+Node* Camera::getNode() const
+{
+    return _node;
+}
+
+void Camera::setNode(Node* node)
+{
+    if (_node != node)
+    {
+        if (_node)
+        {
+            _node->removeListener(this);
+        }
+
+        // Connect the new node.
+        _node = node;
+
+        if (_node)
+        {
+            _node->addListener(this);
+        }
+
+        _bits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+        cameraChanged();
+    }
+}
+
+const Matrix& Camera::getViewMatrix() const
+{
+    if (_bits & CAMERA_DIRTY_VIEW)
+    {
+        if (_node)
+        {
+            // The view matrix is the inverse of our transform matrix.
+            _node->getWorldMatrix().invert(&_view);
+        }
+        else
+        {
+            _view.setIdentity();
+        }
+
+        _bits &= ~CAMERA_DIRTY_VIEW;
+    }
+
+    return _view;
+}
+
+const Matrix& Camera::getInverseViewMatrix() const
+{
+    if (_bits & CAMERA_DIRTY_INV_VIEW)
+    {
+        getViewMatrix().invert(&_inverseView);
+
+        _bits &= ~CAMERA_DIRTY_INV_VIEW;
+    }
+
+    return _inverseView;
+}
+
+const Matrix& Camera::getProjectionMatrix() const
+{
+    if (!(_bits & CAMERA_CUSTOM_PROJECTION) && (_bits & CAMERA_DIRTY_PROJ))
+    {
+        if (_type == PERSPECTIVE)
+        {
+            Matrix::createPerspective(_fieldOfView, _aspectRatio, _nearPlane, _farPlane, &_projection);
+        }
+        else
+        {
+            // Create an ortho projection with the origin at the bottom left of the viewport, +X to the right and +Y up.
+            Matrix::createOrthographic(_zoom[0], _zoom[1], _nearPlane, _farPlane, &_projection);
+        }
+
+        _bits &= ~CAMERA_DIRTY_PROJ;
+    }
+
+    return _projection;
+}
+
+void Camera::setProjectionMatrix(const Matrix& matrix)
+{
+    _projection = matrix;
+    _bits |= CAMERA_CUSTOM_PROJECTION;
+    _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+
+    cameraChanged();
+}
+
+void Camera::resetProjectionMatrix()
+{
+    if (_bits & CAMERA_CUSTOM_PROJECTION)
+    {
+        _bits &= ~CAMERA_CUSTOM_PROJECTION;
+        _bits |= CAMERA_DIRTY_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+
+        cameraChanged();
+    }
+}
+
+const Matrix& Camera::getViewProjectionMatrix() const
+{
+    if (_bits & CAMERA_DIRTY_VIEW_PROJ)
+    {
+        Matrix::multiply(getProjectionMatrix(), getViewMatrix(), &_viewProjection);
+
+        _bits &= ~CAMERA_DIRTY_VIEW_PROJ;
+    }
+
+    return _viewProjection;
+}
+
+const Matrix& Camera::getInverseViewProjectionMatrix() const
+{
+    if (_bits & CAMERA_DIRTY_INV_VIEW_PROJ)
+    {
+        getViewProjectionMatrix().invert(&_inverseViewProjection);
+
+        _bits &= ~CAMERA_DIRTY_INV_VIEW_PROJ;
+    }
+
+    return _inverseViewProjection;
+}
+
+const Frustum& Camera::getFrustum() const
+{
+    if (_bits & CAMERA_DIRTY_BOUNDS)
+    {
+        // Update our bounding frustum from our view projection matrix.
+        _bounds.set(getViewProjectionMatrix());
+
+        _bits &= ~CAMERA_DIRTY_BOUNDS;
+    }
+
+    return _bounds;
+}
+
+void Camera::project(const Rectangle& viewport, const Vector3& position, float* x, float* y, float* depth) const
+{
+    GP_ASSERT(x);
+    GP_ASSERT(y);
+
+    // Transform the point to clip-space.
+    Vector4 clipPos;
+    getViewProjectionMatrix().transformVector(Vector4(position.x, position.y, position.z, 1.0f), &clipPos);
+
+    // Compute normalized device coordinates.
+    GP_ASSERT(clipPos.w != 0.0f);
+    float ndcX = clipPos.x / clipPos.w;
+    float ndcY = clipPos.y / clipPos.w;
+
+    // Compute screen coordinates by applying our viewport transformation.
+    *x = viewport.x + (ndcX + 1.0f) * 0.5f * viewport.width;
+    *y = viewport.y + (1.0f - (ndcY + 1.0f) * 0.5f) * viewport.height;
+    if (depth)
+    {
+        float ndcZ = clipPos.z / clipPos.w;
+        *depth = (ndcZ + 1.0f) / 2.0f;
+    }
+}
+
+void Camera::project(const Rectangle& viewport, const Vector3& position, Vector2* out) const
+{
+    GP_ASSERT(out);
+    float x, y;
+    project(viewport, position, &x, &y);
+    out->set(x, y);
+}
+
+void Camera::project(const Rectangle& viewport, const Vector3& position, Vector3* out) const
+{
+    GP_ASSERT(out);
+    float x, y, depth;
+    project(viewport, position, &x, &y, &depth);
+    out->set(x, y, depth);
+}
+
+void Camera::unproject(const Rectangle& viewport, float x, float y, float depth, Vector3* dst) const
+{
+    GP_ASSERT(dst);
+    
+    // Create our screen space position in NDC.
+    GP_ASSERT(viewport.width != 0.0f && viewport.height != 0.0f);
+    Vector4 screen((x - viewport.x) / viewport.width, ((viewport.height - y) - viewport.y) / viewport.height, depth, 1.0f);
+
+    // Map to range -1 to 1.
+    screen.x = screen.x * 2.0f - 1.0f;
+    screen.y = screen.y * 2.0f - 1.0f;
+    screen.z = screen.z * 2.0f - 1.0f;
+
+    // Transform the screen-space NDC by our inverse view projection matrix.
+    getInverseViewProjectionMatrix().transformVector(screen, &screen);
+
+    // Divide by our W coordinate.
+    if (screen.w != 0.0f)
+    {
+        screen.x /= screen.w;
+        screen.y /= screen.w;
+        screen.z /= screen.w;
+    }
+
+    dst->set(screen.x, screen.y, screen.z);
+}
+
+void Camera::pickRay(const Rectangle& viewport, float x, float y, Ray* dst) const
+{
+    GP_ASSERT(dst);
+
+    // Get the world-space position at the near clip plane.
+    Vector3 nearPoint;
+    unproject(viewport, x, y, 0.0f, &nearPoint);
+
+    // Get the world-space position at the far clip plane.
+    Vector3 farPoint;
+    unproject(viewport, x, y, 1.0f, &farPoint);
+
+    // Set the direction of the ray.
+    Vector3 direction;
+    Vector3::subtract(farPoint, nearPoint, &direction);
+    direction.normalize();
+
+    dst->set(nearPoint, direction);
+}
+
+Camera* Camera::clone(NodeCloneContext &context) const
+{
+    Camera* cameraClone = NULL;
+    if (getCameraType() == PERSPECTIVE)
+    {
+        cameraClone = createPerspective(_fieldOfView, _aspectRatio, _nearPlane, _farPlane);
+    }
+    else if (getCameraType() == ORTHOGRAPHIC)
+    {
+        cameraClone = createOrthographic(getZoomX(), getZoomY(), getAspectRatio(), _nearPlane, _farPlane);
+    }
+    GP_ASSERT(cameraClone);
+
+    if (Node* node = context.findClonedNode(getNode()))
+    {
+        cameraClone->setNode(node);
+    }
+    return cameraClone;
+}
+
+void Camera::transformChanged(Transform* transform, long cookie)
+{
+    _bits |= CAMERA_DIRTY_VIEW | CAMERA_DIRTY_INV_VIEW | CAMERA_DIRTY_INV_VIEW_PROJ | CAMERA_DIRTY_VIEW_PROJ | CAMERA_DIRTY_BOUNDS;
+
+    cameraChanged();
+}
+
+void Camera::cameraChanged()
+{
+    if (_listeners == NULL)
+        return;
+
+    for (std::list<Camera::Listener*>::iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
+    {
+        Camera::Listener* listener = (*itr);
+        listener->cameraChanged(this);
+    }
+}
+
+void Camera::addListener(Camera::Listener* listener)
+{
+    GP_ASSERT(listener);
+
+    if (_listeners == NULL)
+        _listeners = new std::list<Camera::Listener*>();
+
+    _listeners->push_back(listener);
+}
+
+void Camera::removeListener(Camera::Listener* listener)
+{
+    GP_ASSERT(listener);
+
+    if (_listeners)
+    {
+        for (std::list<Camera::Listener*>::iterator itr = _listeners->begin(); itr != _listeners->end(); ++itr)
+        {
+            if ((*itr) == listener)
+            {
+                _listeners->erase(itr);
+                break;
+            }
+        }
+    }
+}
+
+}