Browse Source

Fixed rotation of the axes model.
Added ray-frustum intersection test.
Added accurate raycasts for point and spot lights. Directional light now never returns a raycast result, as it has an "infinite" bounding box.
Added better debug visualization of a point light.
Reorganization of the editor script code.

Lasse Öörni 14 years ago
parent
commit
a32f0f1c26

BIN
Bin/CoreData/Models/Axes.mdl


+ 1 - 1
Bin/Data/Scripts/Editor/EditorNodeWindow.as

@@ -248,7 +248,7 @@ uint GetAttributeEditorCount(Array<Serializable@>@ serializables)
 
 
     if (!serializables.empty)
     if (!serializables.empty)
     {
     {
-        /// \todo If multi-editing, this only counts the editor count of the first serializable
+        /// \todo When multi-editing, this only counts the editor count of the first serializable
         for (uint i = 0; i < serializables[0].numAttributes; ++i)
         for (uint i = 0; i < serializables[0].numAttributes; ++i)
         {
         {
             AttributeInfo info = serializables[0].attributeInfos[i];
             AttributeInfo info = serializables[0].attributeInfos[i];

+ 163 - 118
Bin/Data/Scripts/Editor/EditorScene.as

@@ -15,10 +15,6 @@ String sceneFileName;
 String sceneResourcePath;
 String sceneResourcePath;
 bool sceneModified = false;
 bool sceneModified = false;
 bool runUpdate = false;
 bool runUpdate = false;
-bool renderingDebug = false;
-bool physicsDebug = false;
-bool octreeDebug = false;
-int pickMode = PICK_GEOMETRIES;
 
 
 Array<Node@> selectedNodes;
 Array<Node@> selectedNodes;
 Array<Component@> selectedComponents;
 Array<Component@> selectedComponents;
@@ -26,11 +22,9 @@ Node@ editNode;
 Array<Node@> editNodes;
 Array<Node@> editNodes;
 Array<Component@> editComponents;
 Array<Component@> editComponents;
 
 
-Array<int> pickModeDrawableFlags = {
-    DRAWABLE_GEOMETRY,
-    DRAWABLE_LIGHT,
-    DRAWABLE_ZONE
-};
+Array<XMLFile@> copyBuffer;
+bool copyBufferLocal = false;
+bool copyBufferExpanded = false;
 
 
 void ClearSelection()
 void ClearSelection()
 {
 {
@@ -66,9 +60,6 @@ void CreateScene()
     UpdateWindowTitle();
     UpdateWindowTitle();
     CreateCamera();
     CreateCamera();
 
 
-    SubscribeToEvent("PostRenderUpdate", "ScenePostRenderUpdate");
-    SubscribeToEvent("UIMouseClick", "SceneMouseClick");
-
     script.defaultScene = editorScene;
     script.defaultScene = editorScene;
 }
 }
 
 
@@ -259,148 +250,202 @@ void EndModify(uint nodeID)
     }
     }
 }
 }
 
 
-void ScenePostRenderUpdate()
+bool SceneDelete()
 {
 {
-    DebugRenderer@ debug = editorScene.debugRenderer;
-    if (debug is null)
-        return;
+    if (!CheckSceneWindowFocus() || (selectedComponents.empty && selectedNodes.empty))
+        return false;
 
 
-    // Visualize the currently selected nodes as their local axes + the first drawable component
-    for (uint i = 0; i < selectedNodes.length; ++i)
+    ListView@ list = sceneWindow.GetChild("NodeList", true);
+
+    // Remove components first
+    for (uint i = 0; i < selectedComponents.length; ++i)
     {
     {
-        Node@ node = selectedNodes[i];
-        debug.AddNode(node, false);
-        for (uint j = 0; j < node.numComponents; ++j)
+        // Do not allow to remove the Octree, PhysicsWorld or DebugRenderer from the root node
+        Component@ component = selectedComponents[i];
+        Node@ node = component.node;
+        
+        uint index = GetComponentListIndex(component);
+        uint nodeIndex = GetNodeListIndex(node);
+        if (index == NO_ITEM || nodeIndex == NO_ITEM)
+            continue;
+
+        if (node is editorScene && (component.typeName == "Octree" || component.typeName == "PhysicsWorld" ||
+            component.typeName == "DebugRenderer"))
+            continue;
+
+        uint id = node.id;
+        BeginModify(id);
+        node.RemoveComponent(component);
+        EndModify(id);
+
+        UpdateSceneWindowNode(nodeIndex, node);
+
+        // If deleting only one component, select the next item in the same index
+        if (selectedComponents.length == 1 && selectedNodes.empty)
         {
         {
-            Drawable@ drawable = cast<Drawable>(node.components[j]);
-            if (drawable !is null)
-            {
-                drawable.DrawDebugGeometry(debug, false);
-                break;
-            }
+            list.selection = index;
+            return true;
         }
         }
     }
     }
 
 
-    // Visualize the currently selected components
-    for (uint i = 0; i < selectedComponents.length; ++i)
+    // Remove (parented) nodes last
+    for (uint i = 0; i < selectedNodes.length; ++i)
     {
     {
-        Drawable@ drawable = cast<Drawable>(selectedComponents[i]);
-        if (drawable !is null)
-            drawable.DrawDebugGeometry(debug, false);
-        else
+        Node@ node = selectedNodes[i];
+        if (node.parent is null)
+            continue;
+
+        uint id = node.id;
+        uint nodeIndex = GetNodeListIndex(node);
+
+        BeginModify(id);
+        node.Remove();
+        EndModify(id);
+
+        UpdateSceneWindowNode(nodeIndex, null);
+
+        // Select the next item in the same index
+
+        // If deleting only one node, select the next item in the same index
+        if (selectedNodes.length == 1 && selectedComponents.empty)
         {
         {
-            CollisionShape@ shape = cast<CollisionShape>(selectedComponents[i]);
-            if (shape !is null)
-                shape.DrawDebugGeometry(debug, false);
+            list.selection = nodeIndex;
+            return true;
         }
         }
     }
     }
 
 
-    if (renderingDebug)
-        renderer.DrawDebugGeometry(false);
-    if (physicsDebug && editorScene.physicsWorld !is null)
-        editorScene.physicsWorld.DrawDebugGeometry(true);
-    if (octreeDebug && editorScene.octree !is null)
-        editorScene.octree.DrawDebugGeometry(true);
-
-    SceneRaycast(false);
+    // If any kind of multi-delete was performed, the list selection should be clear now.
+    // Unfortunately that also means we did not get selection change events, so must update the selection arrays manually.
+    // Otherwise nodes/components may be left in the scene even after delete, as the selection arrays keep them alive.
+    HandleNodeListSelectionChange();
+    return true;
 }
 }
 
 
-void SceneMouseClick()
+bool SceneCut()
 {
 {
-    SceneRaycast(true);
+    if (SceneCopy())
+        return SceneDelete();
+    else
+        return false;
 }
 }
 
 
-void SceneRaycast(bool mouseClick)
+bool SceneCopy()
 {
 {
-    DebugRenderer@ debug = editorScene.debugRenderer;
-    IntVector2 pos = ui.cursorPosition;
-    Component@ selected;
+    if ((selectedNodes.empty && selectedComponents.empty) || !CheckSceneWindowFocus())
+        return false;
 
 
-    if (ui.GetElementAt(pos, true) is null)
-    {
-        Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+    // Must have either only components, or only nodes
+    if (!selectedNodes.empty && !selectedComponents.empty)
+        return false;
 
 
-        if (pickMode != PICK_COLLISIONSHAPES)
-        {
-            if (editorScene.octree is null)
-                return;
+    ListView@ list = sceneWindow.GetChild("NodeList", true);
+    copyBuffer.Clear();
 
 
-            Array<RayQueryResult> result = editorScene.octree.Raycast(cameraRay, RAY_TRIANGLE, camera.farClip,
-                pickModeDrawableFlags[pickMode]);
-            if (!result.empty)
-            {
-                for (uint i = 0; i < result.length; ++i)
-                {
-                    Drawable@ drawable = result[i].drawable;
-                    // Skip directional lights, which always block other selections
-                    Light@ light = cast<Light>(drawable);
-                    if (light !is null && light.lightType == LIGHT_DIRECTIONAL)
-                        continue;
-                    if (debug !is null)
-                    {
-                        debug.AddNode(drawable.node, false);
-                        drawable.DrawDebugGeometry(debug, false);
-                    }
-                    selected = drawable;
-                    break;
-                }
-            }
-        }
-        else
+    // Copy components
+    if (!selectedComponents.empty)
+    {
+        for (uint i = 0; i < selectedComponents.length; ++i)
         {
         {
-            if (editorScene.physicsWorld is null)
-                return;
-
-            Array<PhysicsRaycastResult> result = editorScene.physicsWorld.Raycast(cameraRay, camera.farClip);
-            if (!result.empty)
-            {
-                CollisionShape@ shape = result[0].collisionShape;
-                if (debug !is null)
-                {
-                    debug.AddNode(shape.node, false);
-                    shape.DrawDebugGeometry(debug, false);
-                }
-                selected = shape;
-            }
+            XMLFile@ xml = XMLFile();
+            XMLElement rootElem = xml.CreateRoot("component");
+            selectedComponents[i].SaveXML(rootElem);
+            // Note: component type has to be saved manually
+            rootElem.SetString("type", selectedComponents[i].typeName);
+            rootElem.SetBool("local", selectedComponents[i].id >= FIRST_LOCAL_ID);
+            copyBuffer.Push(xml);
         }
         }
+        return true;
     }
     }
-    
-    if (selected !is null && mouseClick && input.mouseButtonPress[MOUSEB_LEFT])
+    // Copy node. The root node can not be copied
+    else
     {
     {
-        bool multiselect = input.qualifierDown[QUAL_CTRL];
-        if (input.qualifierDown[QUAL_SHIFT])
+        for (uint i = 0; i < selectedNodes.length; ++i)
         {
         {
-            // If we are selecting components, but have nodes in existing selection, do not multiselect to prevent confusion
-            if (!selectedNodes.empty)
-                multiselect = false;
-            SelectComponent(selected, multiselect);
+            if (selectedNodes[i] is editorScene)
+                return false;
         }
         }
-        else
+
+        for (uint i = 0; i < selectedNodes.length; ++i)
         {
         {
-            // If we are selecting nodes, but have components in existing selection, do not multiselect to prevent confusion
-            if (!selectedComponents.empty)
-                multiselect = false;
-            SelectNode(selected.node, multiselect);
+            XMLFile@ xml = XMLFile();
+            XMLElement rootElem = xml.CreateRoot("node");
+            selectedNodes[i].SaveXML(rootElem);
+            rootElem.SetBool("local", selectedNodes[i].id >= FIRST_LOCAL_ID);
+            copyBuffer.Push(xml);
         }
         }
+
+        copyBufferExpanded = SaveExpandedStatus(GetNodeListIndex(selectedNodes[0]));
+        return true;
     }
     }
 }
 }
 
 
-void ToggleRenderingDebug()
+bool ScenePaste()
 {
 {
-    renderingDebug = !renderingDebug;
-}
+    if (editNode is null || !CheckSceneWindowFocus() || copyBuffer.empty)
+        return false;
 
 
-void TogglePhysicsDebug()
-{
-    physicsDebug = !physicsDebug;
-}
+    ListView@ list = sceneWindow.GetChild("NodeList", true);
+    bool pasteComponents = false;
 
 
-void ToggleOctreeDebug()
-{
-    octreeDebug = !octreeDebug;
+    for (uint i = 0; i < copyBuffer.length; ++i)
+    {
+        XMLElement rootElem = copyBuffer[i].root;
+        String mode = rootElem.name;
+        if (mode == "component")
+        {
+            pasteComponents = true;
+
+            // If this is the root node, do not allow to create duplicate scene-global components
+            if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, rootElem.GetAttribute("type")))
+                return false;
+
+            BeginModify(editNode.id);
+
+            // If copied component was local, make the new local too
+            Component@ newComponent = editNode.CreateComponent(rootElem.GetAttribute("type"), rootElem.GetBool("local") ? LOCAL :
+                REPLICATED);
+            if (newComponent is null)
+            {
+                EndModify(editNode.id);
+                return false;
+            }
+            newComponent.LoadXML(rootElem);
+            newComponent.ApplyAttributes();
+            EndModify(editNode.id);
+        }
+        else if (mode == "node")
+        {
+            // Make the paste go always to the root node, no matter of the selected node
+            BeginModify(editorScene.id);
+            // If copied node was local, make the new local too
+            Node@ newNode = editorScene.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED);
+            BeginModify(newNode.id);
+            newNode.LoadXML(rootElem);
+            newNode.ApplyAttributes();
+            EndModify(newNode.id);
+            EndModify(editorScene.id);
+
+            uint addIndex = GetParentAddIndex(newNode);
+            UpdateSceneWindowNode(addIndex, newNode);
+            RestoreExpandedStatus(addIndex, copyBufferExpanded);
+        }
+    }
+
+    if (pasteComponents)
+        UpdateSceneWindowNode(editNode);
+
+    return true;
 }
 }
 
 
-void ToggleUpdate()
+void CalculateNewTransform(Node@ source, Node@ target, Vector3& pos, Quaternion& rot, Vector3& scale)
 {
 {
-    runUpdate  = !runUpdate;
+    Vector3 sourceWorldPos = source.worldPosition;
+    Quaternion sourceWorldRot = source.worldRotation;
+    Vector3 sourceWorldScale = source.worldScale;
+
+    Quaternion inverseTargetWorldRot = target.worldRotation.Inverse();
+    Vector3 inverseTargetWorldScale = Vector3(1, 1, 1) / target.worldScale;
+    scale = inverseTargetWorldScale * sourceWorldScale;
+    rot = inverseTargetWorldRot * sourceWorldRot;
+    pos = inverseTargetWorldScale * (inverseTargetWorldRot * (sourceWorldPos - target.worldPosition));
 }
 }

+ 1 - 204
Bin/Data/Scripts/Editor/EditorSceneWindow.as

@@ -6,9 +6,6 @@ const int ITEM_COMPONENT = 2;
 const uint NO_ITEM = 0xffffffff;
 const uint NO_ITEM = 0xffffffff;
 
 
 Window@ sceneWindow;
 Window@ sceneWindow;
-Array<XMLFile@> copyBuffer;
-bool copyBufferLocal = false;
-bool copyBufferExpanded = false;
 
 
 void CreateSceneWindow()
 void CreateSceneWindow()
 {
 {
@@ -675,19 +672,6 @@ bool TestSceneWindowElements(UIElement@ source, UIElement@ target)
     return true;
     return true;
 }
 }
 
 
-void CalculateNewTransform(Node@ source, Node@ target, Vector3& pos, Quaternion& rot, Vector3& scale)
-{
-    Vector3 sourceWorldPos = source.worldPosition;
-    Quaternion sourceWorldRot = source.worldRotation;
-    Vector3 sourceWorldScale = source.worldScale;
-
-    Quaternion inverseTargetWorldRot = target.worldRotation.Inverse();
-    Vector3 inverseTargetWorldScale = Vector3(1, 1, 1) / target.worldScale;
-    scale = inverseTargetWorldScale * sourceWorldScale;
-    rot = inverseTargetWorldRot * sourceWorldRot;
-    pos = inverseTargetWorldScale * (inverseTargetWorldRot * (sourceWorldPos - target.worldPosition));
-}
-
 void UpdateAndFocusNode(Node@ node)
 void UpdateAndFocusNode(Node@ node)
 {
 {
     UpdateSceneWindowNode(node);
     UpdateSceneWindowNode(node);
@@ -765,193 +749,6 @@ bool CheckForExistingGlobalComponent(Node@ node, const String&in typeName)
         return node.HasComponent(typeName);
         return node.HasComponent(typeName);
 }
 }
 
 
-bool SceneDelete()
-{
-    if (!CheckSceneWindowFocus() || (selectedComponents.empty && selectedNodes.empty))
-        return false;
-
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-
-    // Remove components first
-    for (uint i = 0; i < selectedComponents.length; ++i)
-    {
-        // Do not allow to remove the Octree, PhysicsWorld or DebugRenderer from the root node
-        Component@ component = selectedComponents[i];
-        Node@ node = component.node;
-        
-        uint index = GetComponentListIndex(component);
-        uint nodeIndex = GetNodeListIndex(node);
-        if (index == NO_ITEM || nodeIndex == NO_ITEM)
-            continue;
-
-        if (node is editorScene && (component.typeName == "Octree" || component.typeName == "PhysicsWorld" ||
-            component.typeName == "DebugRenderer"))
-            continue;
-
-        uint id = node.id;
-        BeginModify(id);
-        node.RemoveComponent(component);
-        EndModify(id);
-
-        UpdateSceneWindowNode(nodeIndex, node);
-
-        // If deleting only one component, select the next item in the same index
-        if (selectedComponents.length == 1 && selectedNodes.empty)
-        {
-            list.selection = index;
-            return true;
-        }
-    }
-
-    // Remove (parented) nodes last
-    for (uint i = 0; i < selectedNodes.length; ++i)
-    {
-        Node@ node = selectedNodes[i];
-        if (node.parent is null)
-            continue;
-
-        uint id = node.id;
-        uint nodeIndex = GetNodeListIndex(node);
-
-        BeginModify(id);
-        node.Remove();
-        EndModify(id);
-
-        UpdateSceneWindowNode(nodeIndex, null);
-
-        // Select the next item in the same index
-
-        // If deleting only one node, select the next item in the same index
-        if (selectedNodes.length == 1 && selectedComponents.empty)
-        {
-            list.selection = nodeIndex;
-            return true;
-        }
-    }
-
-    // If any kind of multi-delete was performed, the list selection should be clear now.
-    // Unfortunately that also means we did not get selection change events, so must update the selection arrays manually.
-    // Otherwise nodes/components may be left in the scene even after delete, as the selection arrays keep them alive.
-    HandleNodeListSelectionChange();
-    return true;
-}
-
-bool SceneCut()
-{
-    if (SceneCopy())
-        return SceneDelete();
-    else
-        return false;
-}
-
-bool SceneCopy()
-{
-    if ((selectedNodes.empty && selectedComponents.empty) || !CheckSceneWindowFocus())
-        return false;
-
-    // Must have either only components, or only nodes
-    if (!selectedNodes.empty && !selectedComponents.empty)
-        return false;
-
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    copyBuffer.Clear();
-
-    // Copy components
-    if (!selectedComponents.empty)
-    {
-        for (uint i = 0; i < selectedComponents.length; ++i)
-        {
-            XMLFile@ xml = XMLFile();
-            XMLElement rootElem = xml.CreateRoot("component");
-            selectedComponents[i].SaveXML(rootElem);
-            // Note: component type has to be saved manually
-            rootElem.SetString("type", selectedComponents[i].typeName);
-            rootElem.SetBool("local", selectedComponents[i].id >= FIRST_LOCAL_ID);
-            copyBuffer.Push(xml);
-        }
-        return true;
-    }
-    // Copy node. The root node can not be copied
-    else
-    {
-        for (uint i = 0; i < selectedNodes.length; ++i)
-        {
-            if (selectedNodes[i] is editorScene)
-                return false;
-        }
-
-        for (uint i = 0; i < selectedNodes.length; ++i)
-        {
-            XMLFile@ xml = XMLFile();
-            XMLElement rootElem = xml.CreateRoot("node");
-            selectedNodes[i].SaveXML(rootElem);
-            rootElem.SetBool("local", selectedNodes[i].id >= FIRST_LOCAL_ID);
-            copyBuffer.Push(xml);
-        }
-
-        copyBufferExpanded = SaveExpandedStatus(GetNodeListIndex(selectedNodes[0]));
-        return true;
-    }
-}
-
-bool ScenePaste()
-{
-    if (editNode is null || !CheckSceneWindowFocus() || copyBuffer.empty)
-        return false;
-
-    ListView@ list = sceneWindow.GetChild("NodeList", true);
-    bool pasteComponents = false;
-
-    for (uint i = 0; i < copyBuffer.length; ++i)
-    {
-        XMLElement rootElem = copyBuffer[i].root;
-        String mode = rootElem.name;
-        if (mode == "component")
-        {
-            pasteComponents = true;
-
-            // If this is the root node, do not allow to create duplicate scene-global components
-            if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, rootElem.GetAttribute("type")))
-                return false;
-
-            BeginModify(editNode.id);
-
-            // If copied component was local, make the new local too
-            Component@ newComponent = editNode.CreateComponent(rootElem.GetAttribute("type"), rootElem.GetBool("local") ? LOCAL :
-                REPLICATED);
-            if (newComponent is null)
-            {
-                EndModify(editNode.id);
-                return false;
-            }
-            newComponent.LoadXML(rootElem);
-            newComponent.ApplyAttributes();
-            EndModify(editNode.id);
-        }
-        else if (mode == "node")
-        {
-            // Make the paste go always to the root node, no matter of the selected node
-            BeginModify(editorScene.id);
-            // If copied node was local, make the new local too
-            Node@ newNode = editorScene.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED);
-            BeginModify(newNode.id);
-            newNode.LoadXML(rootElem);
-            newNode.ApplyAttributes();
-            EndModify(newNode.id);
-            EndModify(editorScene.id);
-
-            uint addIndex = GetParentAddIndex(newNode);
-            UpdateSceneWindowNode(addIndex, newNode);
-            RestoreExpandedStatus(addIndex, copyBufferExpanded);
-        }
-    }
-
-    if (pasteComponents)
-        UpdateSceneWindowNode(editNode);
-
-    return true;
-}
-
 bool SaveExpandedStatus(uint itemIndex)
 bool SaveExpandedStatus(uint itemIndex)
 {
 {
     ListView@ list = sceneWindow.GetChild("NodeList", true);
     ListView@ list = sceneWindow.GetChild("NodeList", true);
@@ -967,4 +764,4 @@ void RestoreExpandedStatus(uint itemIndex, bool expanded)
 {
 {
     ListView@ list = sceneWindow.GetChild("NodeList", true);
     ListView@ list = sceneWindow.GetChild("NodeList", true);
     list.SetChildItemsVisible(itemIndex, expanded);
     list.SetChildItemsVisible(itemIndex, expanded);
-}
+}

+ 166 - 1
Bin/Data/Scripts/Editor/EditorView.as

@@ -36,6 +36,16 @@ float scaleStep = 0.1;
 bool moveSnap = false;
 bool moveSnap = false;
 bool rotateSnap = false;
 bool rotateSnap = false;
 bool scaleSnap = false;
 bool scaleSnap = false;
+bool renderingDebug = false;
+bool physicsDebug = false;
+bool octreeDebug = false;
+int pickMode = PICK_GEOMETRIES;
+
+Array<int> pickModeDrawableFlags = {
+    DRAWABLE_GEOMETRY,
+    DRAWABLE_LIGHT,
+    DRAWABLE_ZONE
+};
 
 
 Array<String> moveModeText = {
 Array<String> moveModeText = {
     "Move",
     "Move",
@@ -63,6 +73,9 @@ void CreateCamera()
     ResetCamera();
     ResetCamera();
 
 
     renderer.viewports[0] = Viewport(editorScene, camera);
     renderer.viewports[0] = Viewport(editorScene, camera);
+
+    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+    SubscribeToEvent("UIMouseClick", "ViewMouseClick");
 }
 }
 
 
 void ResetCamera()
 void ResetCamera()
@@ -380,6 +393,138 @@ void SteppedObjectManipulation(int key)
     UpdateAttributes(false);
     UpdateAttributes(false);
 }
 }
 
 
+
+void HandlePostRenderUpdate()
+{
+    DebugRenderer@ debug = editorScene.debugRenderer;
+    if (debug is null)
+        return;
+
+    // Visualize the currently selected nodes as their local axes + the first drawable component
+    for (uint i = 0; i < selectedNodes.length; ++i)
+    {
+        Node@ node = selectedNodes[i];
+        debug.AddNode(node, false);
+        for (uint j = 0; j < node.numComponents; ++j)
+        {
+            Drawable@ drawable = cast<Drawable>(node.components[j]);
+            if (drawable !is null)
+            {
+                drawable.DrawDebugGeometry(debug, false);
+                break;
+            }
+        }
+    }
+
+    // Visualize the currently selected components
+    for (uint i = 0; i < selectedComponents.length; ++i)
+    {
+        Drawable@ drawable = cast<Drawable>(selectedComponents[i]);
+        if (drawable !is null)
+            drawable.DrawDebugGeometry(debug, false);
+        else
+        {
+            CollisionShape@ shape = cast<CollisionShape>(selectedComponents[i]);
+            if (shape !is null)
+                shape.DrawDebugGeometry(debug, false);
+        }
+    }
+
+    if (renderingDebug)
+        renderer.DrawDebugGeometry(false);
+    if (physicsDebug && editorScene.physicsWorld !is null)
+        editorScene.physicsWorld.DrawDebugGeometry(true);
+    if (octreeDebug && editorScene.octree !is null)
+        editorScene.octree.DrawDebugGeometry(true);
+
+    ViewRaycast(false);
+}
+
+void ViewMouseClick()
+{
+    ViewRaycast(true);
+}
+
+void ViewRaycast(bool mouseClick)
+{
+    DebugRenderer@ debug = editorScene.debugRenderer;
+    IntVector2 pos = ui.cursorPosition;
+    Component@ selected;
+
+    if (ui.GetElementAt(pos, true) is null)
+    {
+        Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+
+        if (pickMode != PICK_COLLISIONSHAPES)
+        {
+            if (editorScene.octree is null)
+                return;
+
+            Array<RayQueryResult> result = editorScene.octree.Raycast(cameraRay, RAY_TRIANGLE, camera.farClip,
+                pickModeDrawableFlags[pickMode]);
+            if (!result.empty)
+            {
+                for (uint i = 0; i < result.length; ++i)
+                {
+                    Drawable@ drawable = result[i].drawable;
+                    if (debug !is null)
+                    {
+                        debug.AddNode(drawable.node, false);
+                        drawable.DrawDebugGeometry(debug, false);
+                    }
+                    selected = drawable;
+                    break;
+                }
+            }
+        }
+        else
+        {
+            if (editorScene.physicsWorld is null)
+                return;
+
+            Array<PhysicsRaycastResult> result = editorScene.physicsWorld.Raycast(cameraRay, camera.farClip);
+            if (!result.empty)
+            {
+                CollisionShape@ shape = result[0].collisionShape;
+                if (debug !is null)
+                {
+                    debug.AddNode(shape.node, false);
+                    shape.DrawDebugGeometry(debug, false);
+                }
+                selected = shape;
+            }
+        }
+    }
+    
+    if (mouseClick && input.mouseButtonPress[MOUSEB_LEFT])
+    {
+        bool multiselect = input.qualifierDown[QUAL_CTRL];
+        if (selected !is null)
+        {
+            if (input.qualifierDown[QUAL_SHIFT])
+            {
+                // If we are selecting components, but have nodes in existing selection, do not multiselect to prevent confusion
+                if (!selectedNodes.empty)
+                    multiselect = false;
+                SelectComponent(selected, multiselect);
+            }
+            else
+            {
+                // If we are selecting nodes, but have components in existing selection, do not multiselect to prevent confusion
+                if (!selectedComponents.empty)
+                    multiselect = false;
+                SelectNode(selected.node, multiselect);
+            }
+        }
+        else
+        {
+            // If clicked on emptiness in non-multiselect mode, clear the selection
+            if (!multiselect)
+               SelectComponent(null, false);
+        }
+    }
+}
+
 Vector3 GetNewNodePosition()
 Vector3 GetNewNodePosition()
 {
 {
     return cameraNode.position + cameraNode.worldRotation * Vector3(0, 0, newNodeDistance);
     return cameraNode.position + cameraNode.worldRotation * Vector3(0, 0, newNodeDistance);
@@ -414,4 +559,24 @@ void SetShadowResolution(int level)
         renderer.drawShadows = true;
         renderer.drawShadows = true;
         renderer.shadowMapSize = 256 << level;
         renderer.shadowMapSize = 256 << level;
     }
     }
-}
+}
+
+void ToggleRenderingDebug()
+{
+    renderingDebug = !renderingDebug;
+}
+
+void TogglePhysicsDebug()
+{
+    physicsDebug = !physicsDebug;
+}
+
+void ToggleOctreeDebug()
+{
+    octreeDebug = !octreeDebug;
+}
+
+void ToggleUpdate()
+{
+    runUpdate  = !runUpdate;
+}

+ 2 - 0
Docs/ScriptAPI.dox

@@ -1526,6 +1526,7 @@ Methods:<br>
 - void AddBoundingBox(const BoundingBox&, const Color&, bool arg2 = true)
 - void AddBoundingBox(const BoundingBox&, const Color&, bool arg2 = true)
 - void AddFrustum(const Frustum&, const Color&, bool arg2 = true)
 - void AddFrustum(const Frustum&, const Color&, bool arg2 = true)
 - void AddPolyhedron(const Polyhedron&, const Color&, bool arg2 = true)
 - void AddPolyhedron(const Polyhedron&, const Color&, bool arg2 = true)
+- void AddSphere(const Sphere&, const Color&, bool arg2 = true)
 - void AddSkeleton(Skeleton@, const Color&, bool arg2 = true)
 - void AddSkeleton(Skeleton@, const Color&, bool arg2 = true)
 
 
 Properties:<br>
 Properties:<br>
@@ -3702,6 +3703,7 @@ Properties:<br>
 - float friction
 - float friction
 - float bounce
 - float bounce
 - bool phantom
 - bool phantom
+- BoundingBox worldBoundingBox (readonly)
 
 
 
 
 RigidBody
 RigidBody

+ 1 - 0
Engine/Engine/GraphicsAPI.cpp

@@ -817,6 +817,7 @@ static void RegisterDebugRenderer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("DebugRenderer", "void AddBoundingBox(const BoundingBox&in, const Color&in, bool depthTest = true)", asMETHODPR(DebugRenderer, AddBoundingBox, (const BoundingBox&, const Color&, bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddBoundingBox(const BoundingBox&in, const Color&in, bool depthTest = true)", asMETHODPR(DebugRenderer, AddBoundingBox, (const BoundingBox&, const Color&, bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddFrustum(const Frustum&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddFrustum), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddFrustum(const Frustum&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddFrustum), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddPolyhedron(const Polyhedron&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddPolyhedron), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddPolyhedron(const Polyhedron&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddPolyhedron), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugRenderer", "void AddSphere(const Sphere&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddSphere), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddSkeleton(Skeleton@+, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddSkeleton), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddSkeleton(Skeleton@+, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddSkeleton), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "DebugRenderer@+ get_debugRenderer() const", asFUNCTION(SceneGetDebugRenderer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "DebugRenderer@+ get_debugRenderer() const", asFUNCTION(SceneGetDebugRenderer), asCALL_CDECL_OBJLAST);
     engine->RegisterGlobalFunction("DebugRenderer@+ get_debugRenderer()", asFUNCTION(GetDebugRenderer), asCALL_CDECL);
     engine->RegisterGlobalFunction("DebugRenderer@+ get_debugRenderer()", asFUNCTION(GetDebugRenderer), asCALL_CDECL);

+ 1 - 0
Engine/Engine/PhysicsAPI.cpp

@@ -99,6 +99,7 @@ static void RegisterCollisionShape(asIScriptEngine* engine)
     engine->RegisterObjectMethod("CollisionShape", "float get_bounce() const", asMETHOD(CollisionShape, GetBounce), asCALL_THISCALL);
     engine->RegisterObjectMethod("CollisionShape", "float get_bounce() const", asMETHOD(CollisionShape, GetBounce), asCALL_THISCALL);
     engine->RegisterObjectMethod("CollisionShape", "void set_phantom(bool)", asMETHOD(CollisionShape, SetPhantom), asCALL_THISCALL);
     engine->RegisterObjectMethod("CollisionShape", "void set_phantom(bool)", asMETHOD(CollisionShape, SetPhantom), asCALL_THISCALL);
     engine->RegisterObjectMethod("CollisionShape", "bool get_phantom() const", asMETHOD(CollisionShape, IsPhantom), asCALL_THISCALL);
     engine->RegisterObjectMethod("CollisionShape", "bool get_phantom() const", asMETHOD(CollisionShape, IsPhantom), asCALL_THISCALL);
+    engine->RegisterObjectMethod("CollisionShape", "BoundingBox get_worldBoundingBox() const", asMETHOD(CollisionShape, GetWorldBoundingBox), asCALL_THISCALL);
     
     
     // Register Variant GetPtr() for CollisionShape
     // Register Variant GetPtr() for CollisionShape
     engine->RegisterObjectMethod("Variant", "CollisionShape@+ GetCollisionShape() const", asFUNCTION(GetVariantPtr<CollisionShape>), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Variant", "CollisionShape@+ GetCollisionShape() const", asFUNCTION(GetVariantPtr<CollisionShape>), asCALL_CDECL_OBJLAST);

+ 46 - 18
Engine/Graphics/DebugRenderer.cpp

@@ -172,24 +172,6 @@ void DebugRenderer::AddBoundingBox(const BoundingBox& box, const Matrix3x4& tran
     dest->Push(DebugLine(v3, v6, uintColor));
     dest->Push(DebugLine(v3, v6, uintColor));
 }
 }
 
 
-void DebugRenderer::AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest)
-{
-    unsigned uintColor = color.ToUInt();
-    
-    PODVector<DebugLine>* dest = &lines_;
-    if (!depthTest)
-        dest = &noDepthLines_;
-    
-    for (unsigned i = 0; i < poly.faces_.Size(); ++i)
-    {
-        const Vector<Vector3>& face = poly.faces_[i];
-        if (face.Size() >= 3)
-        {
-            for (unsigned j = 0; j < face.Size(); ++j)
-                dest->Push(DebugLine(face[j], face[(j + 1) % face.Size()], uintColor));
-        }
-    }
-}
 
 
 void DebugRenderer::AddFrustum(const Frustum& frustum, const Color& color, bool depthTest)
 void DebugRenderer::AddFrustum(const Frustum& frustum, const Color& color, bool depthTest)
 {
 {
@@ -214,6 +196,52 @@ void DebugRenderer::AddFrustum(const Frustum& frustum, const Color& color, bool
     dest->Push(DebugLine(vertices[3], vertices[7], uintColor));
     dest->Push(DebugLine(vertices[3], vertices[7], uintColor));
 }
 }
 
 
+void DebugRenderer::AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest)
+{
+    unsigned uintColor = color.ToUInt();
+    
+    PODVector<DebugLine>* dest = &lines_;
+    if (!depthTest)
+        dest = &noDepthLines_;
+    
+    for (unsigned i = 0; i < poly.faces_.Size(); ++i)
+    {
+        const Vector<Vector3>& face = poly.faces_[i];
+        if (face.Size() >= 3)
+        {
+            for (unsigned j = 0; j < face.Size(); ++j)
+                dest->Push(DebugLine(face[j], face[(j + 1) % face.Size()], uintColor));
+        }
+    }
+}
+
+void DebugRenderer::AddSphere(const Sphere& sphere, const Color& color, bool depthTest)
+{
+    const Vector3& center = sphere.center_;
+    float radius = sphere.radius_;
+    unsigned uintColor = color.ToUInt();
+    
+    for (unsigned i = 0; i < 360; i += 45)
+    {
+        unsigned j = i + 45;
+        float a = radius * sinf(i * M_DEGTORAD);
+        float b = radius * cosf(i * M_DEGTORAD);
+        float c = radius * sinf(j * M_DEGTORAD);
+        float d = radius * cosf(j * M_DEGTORAD);
+        Vector3 start, end;
+        
+        start = center + Vector3(a, b, 0.0f);
+        end = center + Vector3(c, d, 0.0f);
+        AddLine(start, end, uintColor, depthTest);
+        start = center + Vector3(a, 0.0f, b);
+        end = center + Vector3(c, 0.0f, d);
+        AddLine(start, end, uintColor, depthTest);
+        start = center + Vector3(0.0f, a, b);
+        end = center + Vector3(0.0f, c, d);
+        AddLine(start, end, uintColor, depthTest);
+    }
+}
+
 void DebugRenderer::AddSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest)
 void DebugRenderer::AddSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest)
 {
 {
     const Vector<Bone>& bones = skeleton.GetBones();
     const Vector<Bone>& bones = skeleton.GetBones();

+ 3 - 0
Engine/Graphics/DebugRenderer.h

@@ -35,6 +35,7 @@ class Light;
 class Matrix3x4;
 class Matrix3x4;
 class Renderer;
 class Renderer;
 class Skeleton;
 class Skeleton;
+class Sphere;
 class VertexBuffer;
 class VertexBuffer;
 
 
 /// Debug rendering line.
 /// Debug rendering line.
@@ -90,6 +91,8 @@ public:
     void AddFrustum(const Frustum& frustum, const Color& color, bool depthTest = true);
     void AddFrustum(const Frustum& frustum, const Color& color, bool depthTest = true);
     /// Add a polyhedron.
     /// Add a polyhedron.
     void AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest = true);
     void AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest = true);
+    /// Add a sphere.
+    void AddSphere(const Sphere& sphere, const Color& color, bool depthTest = true);
     /// Add a skeleton.
     /// Add a skeleton.
     void AddSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest = true);
     void AddSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest = true);
     /// Add a triangle mesh.
     /// Add a triangle mesh.

+ 56 - 1
Engine/Graphics/Light.cpp

@@ -26,6 +26,9 @@
 #include "Context.h"
 #include "Context.h"
 #include "DebugRenderer.h"
 #include "DebugRenderer.h"
 #include "Light.h"
 #include "Light.h"
+#include "Log.h"
+#include "OctreeQuery.h"
+#include "Profiler.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
 #include "Texture2D.h"
 #include "Texture2D.h"
 #include "TextureCube.h"
 #include "TextureCube.h"
@@ -161,6 +164,58 @@ void Light::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
     }
     }
 }
 }
 
 
+void Light::ProcessRayQuery(RayOctreeQuery& query, float initialDistance)
+{
+    PROFILE(RaycastLight);
+    
+    RayQueryLevel level = query.level_;
+    
+    switch (level)
+    {
+    case RAY_AABB_NOSUBOBJECTS:
+    case RAY_AABB:
+    case RAY_OBB:
+        // Do not record a raycast result for a directional light, as they would overwhelm all other results
+        if (lightType_ != LIGHT_DIRECTIONAL)
+        {
+            RayQueryResult result;
+            result.drawable_ = this;
+            result.node_ = GetNode();
+            result.distance_ = initialDistance;
+            query.result_.Push(result);
+        }
+        break;
+        
+    case RAY_TRIANGLE:
+        if (lightType_ == LIGHT_SPOT)
+        {
+            float distance = query.ray_.HitDistance(GetFrustum());
+            if (distance < query.maxDistance_)
+            {
+                LOGINFO("Frustum hitdistance: " + String(distance));
+                RayQueryResult result;
+                result.drawable_ = this;
+                result.node_ = GetNode();
+                result.distance_ = distance;
+                query.result_.Push(result);
+            }
+        }
+        if (lightType_ == LIGHT_POINT)
+        {
+            float distance = query.ray_.HitDistance(Sphere(GetWorldPosition(), range_));
+            if (distance < query.maxDistance_)
+            {
+                RayQueryResult result;
+                result.drawable_ = this;
+                result.node_ = GetNode();
+                result.distance_ = distance;
+                query.result_.Push(result);
+            }
+        }
+        break;
+    }
+}
+
 void Light::UpdateDistance(const FrameInfo& frame)
 void Light::UpdateDistance(const FrameInfo& frame)
 {
 {
     switch (lightType_)
     switch (lightType_)
@@ -185,7 +240,7 @@ void Light::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         break;
         break;
         
         
     case LIGHT_POINT:
     case LIGHT_POINT:
-        debug->AddBoundingBox(GetWorldBoundingBox(), GetColor(), depthTest);
+        debug->AddSphere(Sphere(GetWorldPosition(), range_), GetColor(), depthTest);
         break;
         break;
     }
     }
 }
 }

+ 2 - 0
Engine/Graphics/Light.h

@@ -158,6 +158,8 @@ public:
     
     
     /// Handle attribute change.
     /// Handle attribute change.
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
     virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
+    /// Process renderer raycast.
+    virtual void ProcessRayQuery(RayOctreeQuery& query, float initialDistance);
     /// Calculate distance for rendering.
     /// Calculate distance for rendering.
     virtual void UpdateDistance(const FrameInfo& frame);
     virtual void UpdateDistance(const FrameInfo& frame);
     /// Add debug geometry to the debug graphics.
     /// Add debug geometry to the debug graphics.

+ 64 - 29
Engine/Math/Ray.cpp

@@ -23,6 +23,7 @@
 
 
 #include "Precompiled.h"
 #include "Precompiled.h"
 #include "BoundingBox.h"
 #include "BoundingBox.h"
+#include "Frustum.h"
 #include "Plane.h"
 #include "Plane.h"
 #include "Ray.h"
 #include "Ray.h"
 #include "Sphere.h"
 #include "Sphere.h"
@@ -37,39 +38,17 @@ float Ray::HitDistance(const Plane& plane) const
 {
 {
     float d = plane.normal_.DotProduct(direction_);
     float d = plane.normal_.DotProduct(direction_);
     if (fabsf(d) >= M_EPSILON)
     if (fabsf(d) >= M_EPSILON)
-        return -(plane.normal_.DotProduct(origin_) - plane.intercept_) / d;
+    {
+        float t = -(plane.normal_.DotProduct(origin_) - plane.intercept_) / d;
+        if (t >= 0.0f)
+            return t;
+        else
+            return M_INFINITY;
+    }
     else
     else
         return M_INFINITY;
         return M_INFINITY;
 }
 }
 
 
-float Ray::HitDistance(const Sphere& sphere) const
-{
-    Vector3 centeredOrigin = origin_ - sphere.center_;
-    float squaredRadius = sphere.radius_ * sphere.radius_;
-    
-    // Check if ray originates inside the sphere
-    if (centeredOrigin.LengthSquared() <= squaredRadius)
-        return 0.0f;
-    
-    // Calculate intersection by quadratic equation
-    float a = direction_.DotProduct(direction_);
-    float b = 2.0f * centeredOrigin.DotProduct(direction_);
-    float c = centeredOrigin.DotProduct(centeredOrigin) - squaredRadius;
-    float d = b * b - 4.0f * a * c;
-    
-    // No solution
-    if (d < 0.0f)
-        return M_INFINITY;
-    
-    // Get the nearer solution
-    float dSqrt = sqrtf(d);
-    float dist = (-b - dSqrt) / (2.0f * a);
-    if (dist >= 0.0f)
-        return dist;
-    else
-        return (-b + dSqrt) / (2.0f * a);
-}
-
 float Ray::HitDistance(const BoundingBox& box) const
 float Ray::HitDistance(const BoundingBox& box) const
 {
 {
     // If undefined, no hit (infinite distance)
     // If undefined, no hit (infinite distance)
@@ -149,6 +128,62 @@ float Ray::HitDistance(const BoundingBox& box) const
     return dist;
     return dist;
 }
 }
 
 
+float Ray::HitDistance(const Frustum& frustum) const
+{
+    float maxOutside = 0.0f;
+    float minInside = M_INFINITY;
+    bool allInside = true;
+    
+    for (unsigned i = 0; i < NUM_FRUSTUM_PLANES; ++i)
+    {
+        const Plane& plane = frustum.planes_[i];
+        float distance = HitDistance(frustum.planes_[i]);
+        
+        if (plane.Distance(origin_) < 0.0f)
+        {
+            maxOutside = Max(maxOutside, distance);
+            allInside = false;
+        }
+        else
+            minInside = Min(minInside, distance);
+    }
+    
+    if (allInside)
+        return 0.0f;
+    else if (maxOutside <= minInside)
+        return maxOutside;
+    else
+        return M_INFINITY;
+}
+
+float Ray::HitDistance(const Sphere& sphere) const
+{
+    Vector3 centeredOrigin = origin_ - sphere.center_;
+    float squaredRadius = sphere.radius_ * sphere.radius_;
+    
+    // Check if ray originates inside the sphere
+    if (centeredOrigin.LengthSquared() <= squaredRadius)
+        return 0.0f;
+    
+    // Calculate intersection by quadratic equation
+    float a = direction_.DotProduct(direction_);
+    float b = 2.0f * centeredOrigin.DotProduct(direction_);
+    float c = centeredOrigin.DotProduct(centeredOrigin) - squaredRadius;
+    float d = b * b - 4.0f * a * c;
+    
+    // No solution
+    if (d < 0.0f)
+        return M_INFINITY;
+    
+    // Get the nearer solution
+    float dSqrt = sqrtf(d);
+    float dist = (-b - dSqrt) / (2.0f * a);
+    if (dist >= 0.0f)
+        return dist;
+    else
+        return (-b + dSqrt) / (2.0f * a);
+}
+
 float Ray::HitDistance(const Vector3& v0, const Vector3& v1, const Vector3& v2) const
 float Ray::HitDistance(const Vector3& v0, const Vector3& v1, const Vector3& v2) const
 {
 {
     // Based on Fast, Minimum Storage Ray/Triangle Intersection by Möller & Trumbore
     // Based on Fast, Minimum Storage Ray/Triangle Intersection by Möller & Trumbore

+ 5 - 2
Engine/Math/Ray.h

@@ -26,6 +26,7 @@
 #include "Vector3.h"
 #include "Vector3.h"
 
 
 class BoundingBox;
 class BoundingBox;
+class Frustum;
 class Plane;
 class Plane;
 class Sphere;
 class Sphere;
 
 
@@ -76,10 +77,12 @@ public:
     Vector3 Project(const Vector3& point) const;
     Vector3 Project(const Vector3& point) const;
     /// Return hit distance to a plane, or infinity if no hit.
     /// Return hit distance to a plane, or infinity if no hit.
     float HitDistance(const Plane& plane) const;
     float HitDistance(const Plane& plane) const;
-    /// Return hit distance to a sphere, or infinity if no hit.
-    float HitDistance(const Sphere& sphere) const;
     /// Return hit distance to a bounding box, or infinity if no hit.
     /// Return hit distance to a bounding box, or infinity if no hit.
     float HitDistance(const BoundingBox& box) const;
     float HitDistance(const BoundingBox& box) const;
+    /// Return hit distance to a frustum, or infinity if no hit.
+    float HitDistance(const Frustum& frustum) const;
+    /// Return hit distance to a sphere, or infinity if no hit.
+    float HitDistance(const Sphere& sphere) const;
     /// Return hit distance to a triangle, or infinity if no hit.
     /// Return hit distance to a triangle, or infinity if no hit.
     float HitDistance(const Vector3& v0, const Vector3& v1, const Vector3& v2) const;
     float HitDistance(const Vector3& v0, const Vector3& v1, const Vector3& v2) const;
     /// Return hit distance to a triangle mesh defined by vertex and index data, or infinity if no hit.
     /// Return hit distance to a triangle mesh defined by vertex and index data, or infinity if no hit.

+ 21 - 11
Engine/Physics/CollisionShape.cpp

@@ -544,6 +544,25 @@ void CollisionShape::SetPhantom(bool enable)
     phantom_ = enable;
     phantom_ = enable;
 }
 }
 
 
+BoundingBox CollisionShape::GetWorldBoundingBox() const
+{
+    if (!geometry_)
+        return BoundingBox(0.0f, 0.0f);
+    
+    float aabb[6];
+    dGeomGetAABB(geometry_, aabb);
+    BoundingBox box;
+    box.min_.x_ = aabb[0];
+    box.max_.x_ = aabb[1];
+    box.min_.y_ = aabb[2];
+    box.max_.y_ = aabb[3];
+    box.min_.z_ = aabb[4];
+    box.max_.z_ = aabb[5];
+    box.defined_ = true;
+    
+    return box;
+}
+
 void CollisionShape::UpdateTransform(bool nodeUpdate)
 void CollisionShape::UpdateTransform(bool nodeUpdate)
 {
 {
     if (!geometry_)
     if (!geometry_)
@@ -607,17 +626,8 @@ void CollisionShape::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     unsigned uintColor = color->ToUInt();
     unsigned uintColor = color->ToUInt();
     
     
     // Drawing all the debug geometries of a large world may be expensive (especially triangle meshes)
     // Drawing all the debug geometries of a large world may be expensive (especially triangle meshes)
-    // so check the geometry AABB against the debug geometry frustum first
-    float aabb[6];
-    dGeomGetAABB(geometry_, aabb);
-    BoundingBox box;
-    box.min_.x_ = aabb[0];
-    box.max_.x_ = aabb[1];
-    box.min_.y_ = aabb[2];
-    box.max_.y_ = aabb[3];
-    box.min_.z_ = aabb[4];
-    box.max_.z_ = aabb[5];
-    if (!debug->IsInside(box))
+    // so cull the geometry AABB against the debug geometry frustum first
+    if (!debug->IsInside(GetWorldBoundingBox()))
         return;
         return;
     
     
     const Vector3& position = *reinterpret_cast<const Vector3*>(dGeomGetPosition(geometry_));
     const Vector3& position = *reinterpret_cast<const Vector3*>(dGeomGetPosition(geometry_));

+ 2 - 0
Engine/Physics/CollisionShape.h

@@ -167,6 +167,8 @@ public:
     float GetBounce() const { return bounce_; }
     float GetBounce() const { return bounce_; }
     /// Return phantom flag.
     /// Return phantom flag.
     bool IsPhantom() const { return phantom_; }
     bool IsPhantom() const { return phantom_; }
+    /// Return the world-space bounding box
+    BoundingBox GetWorldBoundingBox() const;
     
     
     /// Update geometry transform and associate with rigid body if available.
     /// Update geometry transform and associate with rigid body if available.
     void UpdateTransform(bool nodeUpdate = false);
     void UpdateTransform(bool nodeUpdate = false);

BIN
SourceAssets/Models/Axes.blend