Browse Source

Engine & script API improvements.
Multiple viewport support.
Refactoring of scene updates & debug geometry handling.
Urho3D Shell can now either load a script, or load a scene.
More helper macros for script-safe exceptions.
Fixed InstancedModel::setMaterial().
Optimized functions that return a std::vector by value to take a reference instead.
Close button UI graphics.

Lasse Öörni 15 years ago
parent
commit
20b23ced83
68 changed files with 1130 additions and 1019 deletions
  1. BIN
      Bin/Data/GraphicsTestScene.scn
  2. 18 16
      Bin/Data/Scripts/GraphicsTest.as
  3. 15 19
      Bin/Data/Scripts/NinjaSnowWar.as
  4. BIN
      Bin/Data/Textures/UI.png
  5. 14 5
      Bin/Data/UI/DefaultStyle.xml
  6. 14 14
      Bin/Data/UI/TestLayout.xml
  7. 1 1
      Bin/GraphicsTest.bat
  8. 1 1
      Bin/NSWScript.bat
  9. 2 0
      Engine/Common/Exception.h
  10. 14 14
      Engine/Common/File.cpp
  11. 2 2
      Engine/Common/File.h
  12. 4 2
      Engine/Engine/Client.cpp
  13. 21 55
      Engine/Engine/Engine.cpp
  14. 3 18
      Engine/Engine/Engine.h
  15. 5 19
      Engine/Engine/RegisterCommon.cpp
  16. 2 15
      Engine/Engine/RegisterEngine.cpp
  17. 6 25
      Engine/Engine/RegisterPhysics.cpp
  18. 54 74
      Engine/Engine/RegisterRenderer.cpp
  19. 4 16
      Engine/Engine/RegisterResource.cpp
  20. 78 104
      Engine/Engine/RegisterScene.cpp
  21. 4 32
      Engine/Engine/RegisterTemplates.h
  22. 1 9
      Engine/Engine/RegisterUI.cpp
  23. 2 2
      Engine/Engine/Server.cpp
  24. 15 11
      Engine/Physics/PhysicsWorld.cpp
  25. 5 7
      Engine/Physics/PhysicsWorld.h
  26. 1 1
      Engine/Physics/RigidBody.h
  27. 1 1
      Engine/Renderer/AnimatedModel.h
  28. 32 19
      Engine/Renderer/Camera.cpp
  29. 6 0
      Engine/Renderer/Camera.h
  30. 16 23
      Engine/Renderer/DebugRenderer.cpp
  31. 10 11
      Engine/Renderer/DebugRenderer.h
  32. 11 2
      Engine/Renderer/DeferredView.cpp
  33. 3 0
      Engine/Renderer/ForwardView.cpp
  34. 3 6
      Engine/Renderer/InstancedModel.cpp
  35. 1 1
      Engine/Renderer/Light.h
  36. 93 150
      Engine/Renderer/Octree.cpp
  37. 24 15
      Engine/Renderer/Octree.h
  38. 22 23
      Engine/Renderer/OctreeQuery.h
  39. 211 82
      Engine/Renderer/Pipeline.cpp
  40. 47 8
      Engine/Renderer/Pipeline.h
  41. 1 0
      Engine/Renderer/Renderer.cpp
  42. 63 46
      Engine/Renderer/View.cpp
  43. 10 4
      Engine/Renderer/View.h
  44. 1 1
      Engine/Renderer/VolumeNode.h
  45. 21 8
      Engine/Resource/ResourceCache.cpp
  46. 14 12
      Engine/Resource/ResourceCache.h
  47. 11 4
      Engine/Scene/Component.cpp
  48. 2 0
      Engine/Scene/Component.h
  49. 3 6
      Engine/Scene/Entity.cpp
  50. 13 22
      Engine/Scene/Entity.h
  51. 5 0
      Engine/Scene/Node.cpp
  52. 4 0
      Engine/Scene/Node.h
  53. 56 33
      Engine/Scene/Scene.cpp
  54. 27 9
      Engine/Scene/Scene.h
  55. 6 0
      Engine/Scene/SceneEvents.h
  56. 6 1
      Engine/Scene/SceneExtension.cpp
  57. 12 0
      Engine/Scene/SceneExtension.h
  58. 16 20
      Engine/UI/FileSelector.cpp
  59. 6 4
      Examples/NinjaSnowWar/AIController.cpp
  60. 24 24
      Examples/NinjaSnowWar/Game.cpp
  61. 1 1
      Examples/NinjaSnowWar/Game.h
  62. 39 17
      Examples/Urho3D/Application.cpp
  63. 3 0
      Examples/Urho3D/Application.h
  64. 5 2
      Examples/Urho3D/Readme.txt
  65. 6 0
      ThirdParty/AngelScript/source/as_compiler.cpp
  66. 6 0
      ThirdParty/AngelScript/source/as_config.h
  67. 1 1
      Tools/AssetImporter/AssetImporter.cpp
  68. 2 1
      Tools/PackageTool/PackageTool.cpp

BIN
Bin/Data/GraphicsTestScene.scn


+ 18 - 16
Bin/Data/Scripts/GraphicsTest.as

@@ -51,12 +51,6 @@ void start()
     subscribeToEvent("MouseButtonDown", "handleMouseButtonDown");
     subscribeToEvent("MouseButtonUp", "handleMouseButtonUp");
     subscribeToEvent("PostRenderUpdate", "handlePostRenderUpdate");
-    subscribeToEvent("WindowResized", "handleWindowResized");
-}
-
-void runFrame()
-{
-    engine.runFrame(testScene, camera, true);
 }
 
 void initScene()
@@ -370,7 +364,6 @@ void createCamera()
 {
     Entity@ cameraEntity = testScene.createEntity("Camera");
     @camera = cameraEntity.createComponent("Camera");
-    camera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
     camera.setPosition(Vector3(-50.0, 2.0, -50.0));
 
     @cameraLight = cameraEntity.createComponent("Light");
@@ -385,6 +378,9 @@ void createCamera()
     cameraLight.setRampTexture(cache.getResource("Texture2D", "Textures/RampWide.png"));
     cameraLight.setSpotTexture(cache.getResource("Texture2D", "Textures/SpotWide.png"));
     camera.addChild(cameraLight);
+    
+    // Set zero screen rect -> follow the window size
+    pipeline.setViewport(0, testScene, camera, IntRect(0, 0, 0, 0));
 }
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
@@ -500,7 +496,6 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
         {
             drawdebug++;
             if (drawdebug > 2) drawdebug = 0;
-            engine.setDebugDrawMode(drawdebug);
         }
 
         if (input.getKeyPress('P'))
@@ -606,18 +601,25 @@ void handlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
     if (ui.getElementAt(pos, true) is null)
     {
         Ray cameraRay = camera.getScreenRay(float(pos.x) / renderer.getWidth(), float(pos.y) / renderer.getHeight());
-        array<RayQueryResult> result = testScene.getOctree().raycast(cameraRay, NODE_GEOMETRY, NODE_BILLBOARDSET, 250.0f, RAY_TRIANGLE);
+        array<RayQueryResult> result = testScene.getOctree().raycast(cameraRay, NODE_STATICMODEL | NODE_ANIMATEDMODEL |
+            NODE_INSTANCEDMODEL, 250.0f, RAY_TRIANGLE);
         if (result.length() > 0)
         {
             VolumeNode@ node = result[0].node;
             Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result[0].distance;
-            debugRenderer.addBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos + Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
+            debugRenderer.addBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
+                Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
         }
     }
+    
+    // Draw either renderer or physics debug geometry
+    switch (drawdebug)
+    {
+    case 1:
+        pipeline.drawDebugGeometry();
+        break;
+    case 2:
+        testScene.getPhysicsWorld().drawDebugGeometry();
+        break;
+    }
 }
-
-void handleWindowResized(StringHash eventType, VariantMap& eventData)
-{
-    camera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
-}
-

+ 15 - 19
Bin/Data/Scripts/NinjaSnowWar.as

@@ -29,8 +29,8 @@ BorderImage@ sight;
 
 Controls playerControls;
 Controls prevPlayerControls;
-bool paused = false;
 bool gameOn = false;
+bool drawDebug = false;
 int score = 0;
 int hiscore = 0;
 int maxEnemies = 0;
@@ -48,7 +48,7 @@ void start()
     startGame();
 
     subscribeToEvent("Update", "handleUpdate");
-    subscribeToEvent("PhysicsPreStep", "handleFixedUpdate");
+    subscribeToEvent(gameScene.getPhysicsWorld(), "PhysicsPreStep", "handleFixedUpdate");
     subscribeToEvent("PostUpdate", "handlePostUpdate");
     subscribeToEvent("Points", "handlePoints");
     subscribeToEvent("Kill", "handleKill");
@@ -56,11 +56,6 @@ void start()
     subscribeToEvent("WindowResized", "handleWindowResized");
 }
 
-void runFrame()
-{
-    engine.runFrame(gameScene, gameCamera, !paused);
-}
-
 void initAudio()
 {
     // Lower mastervolumes slightly
@@ -102,11 +97,13 @@ void createCamera()
 {
     Entity@ cameraEntity = gameScene.createEntity("Camera");
     @gameCamera = cameraEntity.createComponent("Camera");
-    if (!engine.isHeadless())
-        gameCamera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
     gameCamera.setNearClip(10.0);
     gameCamera.setFarClip(16000.0);
     gameCamera.setPosition(Vector3(0, 200, -1000));
+
+    // Set zero screen rect -> follow the window size
+    if (!engine.isHeadless())
+        pipeline.setViewport(0, gameScene, gameCamera, IntRect(0, 0, 0, 0));
 }
 
 void createOverlays()
@@ -197,15 +194,17 @@ void startGame()
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
 {
+    float timeStep = eventData["TimeStep"].getFloat();
+
     if (input.getKeyPress(KEY_F1))
         debugHud.toggleAll();
     if (input.getKeyPress(KEY_F2))
-        engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
+        drawDebug = !drawDebug;
 
     if ((!console.isVisible()) && (input.getKeyPress('P')) && (gameOn))
     {
-        paused = !paused;
-        if (paused)
+        gameScene.setPaused(!gameScene.isPaused());
+        if (gameScene.isPaused())
             messageText.setText("PAUSED");
         else
             messageText.setText("");
@@ -219,16 +218,12 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
         console.setVisible(false);
     }
 
-    if (!paused)
+    if (!gameScene.isPaused())
         updateControls();
 }
 
 void handleFixedUpdate(StringHash eventType, VariantMap& eventData)
 {
-    // Check that scene being updated matches (we have only one scene, but for completeness...)
-    if (eventData["Scene"].getScene() !is gameScene)
-        return;
-
     float timeStep = eventData["TimeStep"].getFloat();
 
     // Spawn new objects and check for end/restart of game
@@ -240,6 +235,9 @@ void handlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
     updateCamera();
     updateStatus();
+    
+    if (drawDebug)
+        gameScene.getPhysicsWorld().drawDebugGeometry();
 }
 
 void handlePoints(StringHash eventType, VariantMap& eventData)
@@ -430,8 +428,6 @@ void handleKeyDown(StringHash eventType, VariantMap& eventData)
 
 void handleWindowResized(StringHash eventType, VariantMap& eventData)
 {
-    gameCamera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
-    
     int height = renderer.getHeight() / 22;
     if (height > 64)
         height = 64;

BIN
Bin/Data/Textures/UI.png


+ 14 - 5
Bin/Data/UI/DefaultStyle.xml

@@ -16,6 +16,15 @@
         <checkedoffset value="16 0" />
         <hoveroffset value="0 16" />
     </element>
+    <element type="CloseButton">
+        <size value="16 16" />
+        <texture name="Textures/UI.png" />
+        <imagerect value="128 0 144 16" />
+        <border value="4 4 4 4" />
+        <pressedoffset value="16 0" />
+        <hoveroffset value="0 16" />
+        <labeloffset value="-1 1" />
+    </element>
     <element type="Cursor">
         <shape name="normal" texture="Textures/UI.png" imagerect="0 0 12 24" hotspot="0 0" />
         <shape name="resizevertical" texture="Textures/UI.png" imagerect="0 64 20 84" hotspot="9 9" />
@@ -108,8 +117,8 @@
         <text>
             <position value="1 1" />
             <font name="Cour.ttf" size="12" />
-            <hovercolor value="0.5 0.75 0.5" />
-            <selectioncolor value="0.75 0.75 0.75" />
+            <hovercolor value="0.45 0.70 0.45" />
+            <selectioncolor value="0.70 0.70 0.70" />
         </text>
         <cursor>
             <size value="4 16" />
@@ -351,15 +360,15 @@
     </element>
     <element type="FileSelectorFilterText">
         <font name="Cour.ttf" size="12" />
-        <hovercolor value="0.5 0.75 0.5" />
+        <hovercolor value="0.45 0.70 0.45" />
     </element>
     <element type="FileSelectorLayout">
         <layout spacing="4" />
     </element>
     <element type="FileSelectorListText">
         <font name="Cour.ttf" size="12" />
-        <hovercolor value="0.5 0.75 0.5" />
-        <selectioncolor value="0.75 0.75 0.75" />
+        <hovercolor value="0.45 0.70 0.45" />
+        <selectioncolor value="0.70 0.70 0.70" />
     </element>
     <element type="FileSelectorTitleText">
         <font name="Cour.ttf" size="15" />

+ 14 - 14
Bin/Data/UI/TestLayout.xml

@@ -27,17 +27,17 @@
     <element type="Text" name="PopupItem1">
         <font name="cour.ttf" size="12" />
         <text value="Deferred" />
-        <hovercolor value="0.5 0.75 0.5" />
+        <hovercolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="PopupItem2">
         <font name="cour.ttf" size="12" />
         <text value="Prepass" />
-        <hovercolor value="0.5 0.75 0.5" />
+        <hovercolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="PopupItem3">
         <font name="cour.ttf" size="12" />
         <text value="Forward" />
-        <hovercolor value="0.5 0.75 0.5" />
+        <hovercolor value="0.45 0.70 0.45" />
     </element>
     <element type="DropDownList">
         <position value="10 160" />
@@ -55,57 +55,57 @@
     <element type="Text" name="Item1">
         <font name="cour.ttf" size="12" />
         <text value="Audio" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item2">
         <font name="cour.ttf" size="12" />
         <text value="Common" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item3">
         <font name="cour.ttf" size="12" />
         <text value="Engine" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item4">
         <font name="cour.ttf" size="12" />
         <text value="Input" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item5">
         <font name="cour.ttf" size="12" />
         <text value="Math" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item6">
         <font name="cour.ttf" size="12" />
         <text value="Network" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item7">
         <font name="cour.ttf" size="12" />
         <text value="Physics" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item8">
         <font name="cour.ttf" size="12" />
         <text value="Renderer" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item9">
         <font name="cour.ttf" size="12" />
         <text value="Resource" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item10">
         <font name="cour.ttf" size="12" />
         <text value="Scene" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="Text" name="Item11">
         <font name="cour.ttf" size="12" />
         <text value="UI" />
-        <selectioncolor value="0.5 0.75 0.5" />
+        <selectioncolor value="0.45 0.70 0.45" />
     </element>
     <element type="ListView">
         <position value="150 100" />

+ 1 - 1
Bin/GraphicsTest.bat

@@ -1 +1 @@
-Urho3D Scripts/GraphicsTest.as %1 %2 %3 %4 %5 %6 %7 %8
+Urho3D Data/Scripts/GraphicsTest.as %1 %2 %3 %4 %5 %6 %7 %8

+ 1 - 1
Bin/NSWScript.bat

@@ -1 +1 @@
-Urho3D Scripts/NinjaSnowWar.as %1 %2 %3 %4 %5 %6 %7 %8
+Urho3D Data/Scripts/NinjaSnowWar.as %1 %2 %3 %4 %5 %6 %7 %8

+ 2 - 0
Engine/Common/Exception.h

@@ -38,6 +38,8 @@
 #endif
 #define SAFE_RETHROW(e) { checkAndRethrowException(e); return; }
 #define SAFE_RETHROW_RET(e, ret) { checkAndRethrowException(e); return ret; }
+#define TRY_SAFE_RETHROW(operation) try { operation; } catch (Exception& e) { checkAndRethrowException(e); return; }
+#define TRY_CONSTRUCT(operation) try { return operation; } catch (Exception& e) { checkAndRethrowException(e); return 0; }
 
 //! Urho3D exception class
 class Exception : public std::exception

+ 14 - 14
Engine/Common/File.cpp

@@ -221,23 +221,23 @@ std::string getWorkingDirectory()
     return fixPath(std::string(currentDir));
 }
 
-std::vector<std::string> scanDirectory(const std::string& pathName, const std::string& filter, unsigned flags, bool recursive)
+void scanDirectory(std::vector<std::string>& result, const std::string& pathName, const std::string& filter, unsigned flags, bool recursive)
 {
-    std::vector<std::string> ret;
+    result.clear();
     
     if (!checkDirectoryAccess(pathName))
-        SAFE_EXCEPTION_RET("Access denied to " + pathName, ret);
-    
-    // Go into the directory to scan the files; this way the file names will be relative to the start path
-    char oldDir[MAX_PATH];
-    GetCurrentDirectory(MAX_PATH, oldDir);
-    if (SetCurrentDirectory(getOSPath(pathName, true).c_str()) == FALSE)
-        return ret;
-    
-    scanDirectoryInternal(ret, "", filter, flags, recursive);
-    SetCurrentDirectory(oldDir);
-    
-    return ret;
+        LOGERROR("Access denied to " + pathName);
+    else
+    {
+        // Go into the directory to scan the files; this way the file names will be relative to the start path
+        char oldDir[MAX_PATH];
+        GetCurrentDirectory(MAX_PATH, oldDir);
+        if (SetCurrentDirectory(getOSPath(pathName, true).c_str()) == FALSE)
+            return;
+        
+        scanDirectoryInternal(result, "", filter, flags, recursive);
+        SetCurrentDirectory(oldDir);
+    }
 }
 
 void registerDirectory(const std::string& pathName)

+ 2 - 2
Engine/Common/File.h

@@ -97,12 +97,12 @@ private:
 bool fileExists(const std::string& fileName);
 //! Check if a directory exists
 bool directoryExists(const std::string& pathName);
-//! Create a directory
+//! Create a directory. Throw exception on failure
 void createDirectory(const std::string& pathName);
 //! Return the absolute working directory
 std::string getWorkingDirectory();
 //! Scan a directory for specified files
-std::vector<std::string> scanDirectory(const std::string& pathName, const std::string& filter, unsigned flags, bool recursive);
+void scanDirectory(std::vector<std::string>& result, const std::string& pathName, const std::string& filter, unsigned flags, bool recursive);
 //! Register a path as being allowed to access
 void registerDirectory(const std::string& pathName);
 //! Check if a path is allowed to be accessed. If no paths defined, all are allowed

+ 4 - 2
Engine/Engine/Client.cpp

@@ -574,7 +574,7 @@ void Client::handleServerUpdate(VectorBuffer& packet, bool initial)
     else
     {
         // If initial/full update, remove all old proxy entities
-        mScene->removeAllEntities(NET_PROXY);
+        mScene->removeEntities(NET_PROXY);
         LOGINFO("Initial scene: " + toString(packet.getSize()) + " bytes");
     }
     
@@ -724,8 +724,10 @@ unsigned Client::checkPackages()
     
     // To avoid resource version conflicts and to keep the amount of open packages reasonable, remove all existing
     // downloaded packages from the resource cache first
-    std::vector<std::string> downloadedPackages = scanDirectory(mDownloadDirectory, "*.pak", SCAN_FILES, false);
+    std::vector<std::string> downloadedPackages;
     std::vector<SharedPtr<PackageFile> > registeredPackages = mCache->getPackageFiles();
+    scanDirectory(downloadedPackages, mDownloadDirectory, "*.pak", SCAN_FILES, false);
+    
     for (std::vector<SharedPtr<PackageFile> >::iterator i = registeredPackages.begin(); i != registeredPackages.end();)
     {
         if ((*i)->getName().find(mDownloadDirectory) != std::string::npos)

+ 21 - 55
Engine/Engine/Engine.cpp

@@ -49,9 +49,11 @@
 #include "RegisterLibraries.h"
 #include "Renderer.h"
 #include "RendererComponentFactory.h"
+#include "RendererEvents.h"
 #include "RendererResourceFactory.h"
 #include "ResourceCache.h"
 #include "Scene.h"
+#include "SceneEvents.h"
 #include "ScriptComponentFactory.h"
 #include "ScriptEngine.h"
 #include "ScriptFile.h"
@@ -73,7 +75,6 @@ Engine::Engine(const std::string& windowTitle, const std::string& logFileName, b
     mMinFps(10),
     mMaxFps(200),
     mMaxInactiveFps(50),
-    mDebugDrawMode(DEBUGDRAW_NONE),
     mFlushGPU(true),
     mInitialized(false),
     mExiting(false),
@@ -237,7 +238,6 @@ void Engine::init(const std::vector<std::string>& arguments)
     if (!mHeadless)
     {
         mPipeline = new Pipeline(mRenderer, mCache);
-        mDebugRenderer = new DebugRenderer(mRenderer, mCache);
         mUI = new UI(mRenderer, mCache);
         if (!shadows)
             mPipeline->setDrawShadows(false);
@@ -248,7 +248,7 @@ void Engine::init(const std::vector<std::string>& arguments)
     mInitialized = true;
 }
 
-void Engine::runFrame(Scene* scene, Camera* camera, bool updateScene)
+void Engine::runFrame()
 {
     if ((!mInitialized) || (mExiting))
         return;
@@ -264,9 +264,8 @@ void Engine::runFrame(Scene* scene, Camera* camera, bool updateScene)
     {
         PROFILE(Engine_RunFrame);
         
-        // Get frame timestep / update / render / garbage collect one step
         float timeStep = getNextTimeStep();
-        update(timeStep, scene, camera, updateScene);
+        update(timeStep);
         render();
         if (mScriptEngine)
             mScriptEngine->garbageCollect(false);
@@ -292,11 +291,18 @@ SharedPtr<Scene> Engine::createScene(const std::string& name, const BoundingBox&
     // Enable physics as necessary
     if (usePhysics)
     {
-        PhysicsWorld* world = new PhysicsWorld(scene);
+        PhysicsWorld* world = new PhysicsWorld();
         scene->addExtension(world);
         scene->addComponentFactory(new PhysicsComponentFactory(world));
     }
     
+    // Add debug rendering if not headless
+    if (!mHeadless)
+    {
+        DebugRenderer* debug = new DebugRenderer(mRenderer, mCache);
+        scene->addExtension(debug);
+    }
+    
     return scene;
 }
 
@@ -388,11 +394,6 @@ void Engine::setMaxInactiveFps(int fps)
     mMaxInactiveFps = max(fps, 0);
 }
 
-void Engine::setDebugDrawMode(int mode)
-{
-    mDebugDrawMode = mode;
-}
-
 void Engine::setFlushGPU(bool enable)
 {
     mFlushGPU = enable;
@@ -494,28 +495,13 @@ float Engine::getNextTimeStep()
     return timeAcc / 1000.0f;
 }
 
-void Engine::update(float timeStep, Scene* scene, Camera* camera, bool updateScene)
+void Engine::update(float timeStep)
 {
     PROFILE(Engine_Update);
     
     if (!mInitialized)
         return;
     
-    // If client or server exist, they are assumed to update the scene on their own.
-    // In those cases set updateScene = false
-    if ((mClient) && (mClient->getScene() == scene))
-        updateScene = false;
-    if ((mServer) && (mServer->hasScene(scene)))
-        updateScene = false;
-    
-    // Make weak pointers of the scene & camera, in case update logic destroys them
-    WeakPtr<Scene> sceneWeak(scene);
-    WeakPtr<Camera> cameraWeak(camera);
-    
-    // Clear debug rendering output from last frame
-    if (mDebugRenderer)
-        mDebugRenderer->clear();
-    
     // Input update
     mInput->update();
     
@@ -536,37 +522,15 @@ void Engine::update(float timeStep, Scene* scene, Camera* camera, bool updateSce
     if (mServer)
         mServer->update(timeStep);
     
-    // Scene update
-    if ((sceneWeak) && (updateScene))
-        scene->update(timeStep);
+    // Non-networked scenes update
+    sendEvent(EVENT_UPDATESCENES, updateData);
     
     // Application post-update
     sendEvent(EVENT_POSTUPDATE, updateData);
     
-    // Rendering and debug geometry update
+    // Rendering update
     if (mPipeline)
-    {
-        // Do not render if the scene is loading asynchronously, and thus incomplete
-        if ((sceneWeak) && (cameraWeak) && (!sceneWeak->isAsyncLoading()))
-        {
-            mPipeline->update(timeStep, scene->getExtension<Octree>(), camera);
-            
-            if (mDebugRenderer)
-            {
-                mDebugRenderer->setView(camera);
-                if (mDebugDrawMode & DEBUGDRAW_RENDERING)
-                    mPipeline->drawDebugGeometry(mDebugRenderer);
-                if (mDebugDrawMode & DEBUGDRAW_PHYSICS)
-                {
-                    PhysicsWorld* world = scene->getExtension<PhysicsWorld>();
-                    if (world)
-                        world->drawDebugGeometry(mDebugRenderer);
-                }
-            }
-        }
-        else
-            mPipeline->update(timeStep, 0, 0);
-    }
+        mPipeline->update(timeStep);
     
     if (mDebugHud)
         mDebugHud->update(timeStep);
@@ -589,12 +553,14 @@ void Engine::render()
     if (!mRenderer)
         return;
     
-    // Do not render if device lost
+    // Do not render if device lost. However send end frame event so that debug geometry is cleared
     if (!mRenderer->beginFrame())
+    {
+        sendEvent(EVENT_ENDFRAME);
         return;
+    }
     
     mPipeline->render();
-    mDebugRenderer->render();
     mUI->render();
     
     mRenderer->endFrame(mFlushGPU);

+ 3 - 18
Engine/Engine/Engine.h

@@ -32,17 +32,12 @@
 #include <string>
 #include <vector>
 
-static const int DEBUGDRAW_NONE = 0;
-static const int DEBUGDRAW_RENDERING = 1;
-static const int DEBUGDRAW_PHYSICS = 2;
-
 class Audio;
 class Camera;
 class Client;
 class Console;
 class Cursor;
 class DebugHud;
-class DebugRenderer;
 class Input;
 class Log;
 class Network;
@@ -70,8 +65,8 @@ public:
     
     //! Initialize and show the application window
     void init(const std::vector<std::string>& arguments = std::vector<std::string>());
-    //! Run one frame with the scene and camera given
-    void runFrame(Scene* scene, Camera* camera, bool updateScene = true);
+    //! Run one frame
+    void runFrame();
     //! Create a scene
     SharedPtr<Scene> createScene(const std::string& name = "Default", const BoundingBox& octreeSize =
         BoundingBox(-1000.0f, 1000.0f), unsigned octreeLevels = 8, bool usePhysics = true);
@@ -101,8 +96,6 @@ public:
     void setMaxFps(int fps);
     //! Set maximum frames per second when the application is inactive
     void setMaxInactiveFps(int fps);
-    //! Set debug draw mode: whether to draw rendering or physics debug information, or both
-    void setDebugDrawMode(int mode);
     //! Set whether the GPU is flushed after each frame. Can prevent mouse latency and frame rate jitter, but reduces CPU/GPU parallelism
     void setFlushGPU(bool enable);
     //! Close the application window and set the exit flag
@@ -120,8 +113,6 @@ public:
     Console* getConsole() const { return mConsole; }
     //! Return debug hud if created
     DebugHud* getDebugHud() const { return mDebugHud; }
-    //! Return debug renderer
-    DebugRenderer* getDebugRenderer() const { return mDebugRenderer; }
     //! Return input subsystem
     Input* getInput() const { return mInput; }
     //! Return log
@@ -154,8 +145,6 @@ public:
     int getMaxFps() const { return mMaxFps; }
     //! Return the maximum frames per second when the application is inactive
     int getMaxInactiveFps() const { return mMaxInactiveFps; }
-    //! Return the debug draw mode
-    int getDebugDrawMode() const { return mDebugDrawMode; }
     //! Return the GPU flush flag
     bool getFlushGPU() const { return mFlushGPU; }
     //! Return whether engine has been initialized
@@ -168,7 +157,7 @@ public:
     //! Return the timestep for the next frame and sleep for frame limiting if necessary
     float getNextTimeStep();
     //! Run the update for one frame
-    void update(float timeStep, Scene* scene, Camera* camera, bool updateScene = true);
+    void update(float timeStep);
     //! Run the rendering for one frame
     void render();
     
@@ -190,8 +179,6 @@ private:
     SharedPtr<ResourceCache> mCache;
     //! High-level rendering pipeline
     SharedPtr<Pipeline> mPipeline;
-    //! Debug renderer
-    SharedPtr<DebugRenderer> mDebugRenderer;
     //! UI subsystem
     SharedPtr<UI> mUI;
     //! Network subsystem
@@ -218,8 +205,6 @@ private:
     unsigned mMaxFps;
     //! Maximum frames per second when the application is inactive
     unsigned mMaxInactiveFps;
-    //! Debug draw mode
-    unsigned mDebugDrawMode;
     //! Flush GPU flag
     bool mFlushGPU;
     //! Initialized flag

+ 5 - 19
Engine/Engine/RegisterCommon.cpp

@@ -457,15 +457,7 @@ static void registerStringUtils(asIScriptEngine* engine)
 
 static File* ConstructFile(const std::string& fileName, FileMode mode)
 {
-    try
-    {
-        return new File(fileName, mode);
-    }
-    catch (Exception& e)
-    {
-        // Rethrow after File has been deallocated
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(new File(fileName, mode));
 }
 
 static void ConstructVectorBuffer(VectorBuffer* ptr)
@@ -503,6 +495,7 @@ static unsigned char* VectorBufferAt(unsigned index, VectorBuffer* ptr)
 {
     if (index >= ptr->getSize())
         SAFE_EXCEPTION_RET("Index out of bounds", 0);
+    
     return ptr->getModifiableData() + index;
 }
 
@@ -524,7 +517,8 @@ static bool VariantEqualsBuffer(const VectorBuffer& buffer, Variant* ptr)
 
 static CScriptArray* ScanDirectory(const std::string& pathName, const std::string& filter, unsigned flags, bool recursive)
 {
-    std::vector<std::string> result = scanDirectory(pathName, filter, flags, recursive);
+    std::vector<std::string> result;
+    scanDirectory(result, pathName, filter, flags, recursive);
     return vectorToArray<std::string>(result, "array<string>");
 }
 
@@ -592,15 +586,7 @@ static void registerSerialization(asIScriptEngine* engine)
 
 static PackageFile* ConstructPackageFile(const std::string& fileName)
 {
-    try
-    {
-        return new PackageFile(fileName);
-    }
-    catch (Exception& e)
-    {
-        // Rethrow after PackageFile has been deallocated
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(new PackageFile(fileName));
 }
 
 static void registerPackageFile(asIScriptEngine* engine)

+ 2 - 15
Engine/Engine/RegisterEngine.cpp

@@ -296,14 +296,7 @@ static void registerServer(asIScriptEngine* engine)
 
 static void ParticleEmitterLoadParameters(XMLFile* file, ParticleEmitter* ptr)
 {
-    try
-    {
-        ptr->loadParameters(file, getEngine()->getResourceCache());
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    ptr->loadParameters(file, getEngine()->getResourceCache());
 }
 
 static void registerAnimationController(asIScriptEngine* engine)
@@ -431,14 +424,10 @@ static Scene* EngineCreateScene(const std::string& name, const BoundingBox& octr
 
 static void registerEngine(asIScriptEngine* engine)
 {
-    engine->RegisterGlobalProperty("const int DEBUGDRAW_NONE", (void*)&DEBUGDRAW_NONE);
-    engine->RegisterGlobalProperty("const int DEBUGDRAW_RENDERING", (void*)&DEBUGDRAW_RENDERING);
-    engine->RegisterGlobalProperty("const int DEBUGDRAW_PHYSICS", (void*)&DEBUGDRAW_PHYSICS);
-    
     engine->RegisterObjectType("Engine", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("Engine", asBEHAVE_ADDREF, "void f()", asMETHOD(Engine, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Engine", asBEHAVE_RELEASE, "void f()", asMETHOD(Engine, releaseRef), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Engine", "void runFrame(Scene@+, Camera@+, bool)", asMETHOD(Engine, runFrame), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Engine", "void runFrame()", asMETHOD(Engine, runFrame), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "Scene@ createScene(const string& in, const BoundingBox& in, uint, bool)", asFUNCTION(EngineCreateScene), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Engine", "Client@+ createClient(const string& in)", asMETHOD(Engine, createClient), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "Server@+ createServer()", asMETHOD(Engine, createServer), asCALL_THISCALL);
@@ -451,7 +440,6 @@ static void registerEngine(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Engine", "void setMinFps(int)", asMETHOD(Engine, setMinFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setMaxFps(int)", asMETHOD(Engine, setMaxFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setMaxInactiveFps(int)", asMETHOD(Engine, setMaxInactiveFps), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Engine", "void setDebugDrawMode(int)", asMETHOD(Engine, setDebugDrawMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setFlushGPU(bool)", asMETHOD(Engine, setFlushGPU), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void exit()", asMETHOD(Engine, exit), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void dumpProfilingData()", asMETHOD(Engine, dumpProfilingData), asCALL_THISCALL);
@@ -460,7 +448,6 @@ static void registerEngine(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Engine", "int getMinFps() const", asMETHOD(Engine, getMinFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "int getMaxFps() const", asMETHOD(Engine, getMaxFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "int getMaxInactiveFps() const", asMETHOD(Engine, getMaxInactiveFps), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Engine", "int getDebugDrawMode() const", asMETHOD(Engine, getDebugDrawMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "bool getFlushGPU() const", asMETHOD(Engine, getFlushGPU), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "bool isInitialized() const", asMETHOD(Engine, isInitialized), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "bool isExiting() const", asMETHOD(Engine, isExiting), asCALL_THISCALL);

+ 6 - 25
Engine/Engine/RegisterPhysics.cpp

@@ -58,7 +58,7 @@ static RigidBody* PhysicsRaycastResultGetBody(PhysicsRaycastResult* ptr)
 static CScriptArray* PhysicsWorldRaycast(const Ray& ray, float maxDistance, unsigned collisionMask, PhysicsWorld* ptr)
 {
     static std::vector<PhysicsRaycastResult> result;
-    ptr->raycast(ray, result, maxDistance, collisionMask);
+    ptr->raycast(result, ray, maxDistance, collisionMask);
     return vectorToArray<PhysicsRaycastResult>(result, "array<PhysicsRaycastResult>");
 }
 
@@ -74,6 +74,7 @@ static void registerPhysicsWorld(asIScriptEngine* engine)
     engine->RegisterObjectMethod("PhysicsRaycastResult", "RigidBody@+ get_body() const", asFUNCTION(PhysicsRaycastResultGetBody), asCALL_CDECL_OBJLAST);
     
     registerHashedType<PhysicsWorld>(engine, "PhysicsWorld");
+    engine->RegisterObjectMethod("PhysicsWorld", "void update(float)", asMETHOD(PhysicsWorld, update), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void setGravity(const Vector3& in)", asMETHOD(PhysicsWorld, setGravity), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void setFps(int)", asMETHOD(PhysicsWorld, setFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void setMaxContacts(uint)", asMETHOD(PhysicsWorld, setMaxContacts), asCALL_THISCALL);
@@ -87,6 +88,7 @@ static void registerPhysicsWorld(asIScriptEngine* engine)
     engine->RegisterObjectMethod("PhysicsWorld", "void setCFM(float)", asMETHOD(PhysicsWorld, setCFM), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void setContactSurfaceLayer(float)", asMETHOD(PhysicsWorld, setContactSurfaceLayer), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "array<PhysicsRaycastResult>@ raycast(const Ray& in, float, uint)", asFUNCTION(PhysicsWorldRaycast), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("PhysicsWorld", "void drawDebugGeometry()", asMETHOD(PhysicsWorld, drawDebugGeometry), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "Vector3 getGravity() const", asMETHOD(PhysicsWorld, getGravity), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "int getFps() const", asMETHOD(PhysicsWorld, getFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "uint getMaxContacts() const", asMETHOD(PhysicsWorld, getMaxContacts), asCALL_THISCALL);
@@ -118,38 +120,17 @@ static CollisionShape* ConstructCollisionShape(const std::string& name)
 
 static void CollisionShapeAddTriangleMesh(const Model* model, unsigned lodLevel, const Vector3& position, const Quaternion& rotation, CollisionShape* ptr)
 {
-    try
-    {
-        ptr->addTriangleMesh(model, lodLevel, position, rotation);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->addTriangleMesh(model, lodLevel, position, rotation));
 }
 
 static void CollisionShapeAddHeightfield(const Model* model, unsigned xPoints, unsigned zPoints, float thickness, unsigned lodLevel, const Vector3& position, const Quaternion& rotation, CollisionShape* ptr)
 {
-    try
-    {
-        ptr->addHeightfield(model, xPoints, zPoints, thickness, lodLevel, position, rotation);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->addHeightfield(model, xPoints, zPoints, thickness, lodLevel, position, rotation));
 }
 
 static void CollisionShapeAddConvexHull(const Model* model, float skinWidth, unsigned lodLevel, const Vector3& position, const Quaternion& rotation, CollisionShape* ptr)
 {
-    try
-    {
-        ptr->addConvexHull(model, skinWidth, lodLevel, position, rotation);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->addConvexHull(model, skinWidth, lodLevel, position, rotation));
 }
 
 static void registerCollisionShape(asIScriptEngine* engine)

+ 54 - 74
Engine/Engine/RegisterRenderer.cpp

@@ -45,6 +45,9 @@
 void FakeAddRef(void* ptr);
 void FakeReleaseRef(void* ptr);
 
+static std::vector<RayQueryResult> rayQueryResult;
+static std::vector<VolumeNode*> nodeResult;
+
 static void registerCamera(asIScriptEngine* engine)
 {
     engine->RegisterGlobalProperty("const uint VIEW_NONE", (void*)&VIEW_NONE);
@@ -63,6 +66,7 @@ static void registerCamera(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Camera", "void setZoom(float)", asMETHOD(Camera, setZoom), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "void setLodBias(float)", asMETHOD(Camera, setLodBias), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "void setOrthographic(bool)", asMETHOD(Camera, setOrthographic), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Camera", "void setAutoAspectRatio(bool)", asMETHOD(Camera, setAutoAspectRatio), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "void setViewMask(uint)", asMETHOD(Camera, setViewMask), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "void setDrawShadowsOverride(bool)", asMETHOD(Camera, setDrawShadowsOverride), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "void setLightDetailLevelOverride(int)", asMETHOD(Camera, setLightDetailLevelOverride), asCALL_THISCALL);
@@ -76,6 +80,7 @@ static void registerCamera(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Camera", "float getZoom() const", asMETHOD(Camera, getZoom), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "float getLodBias() const", asMETHOD(Camera, getLodBias), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "bool isOrthographic() const", asMETHOD(Camera, isOrthographic), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Camera", "bool getAutoAspectRatio() const", asMETHOD(Camera, getAutoAspectRatio), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "uint getViewMask() const", asMETHOD(Camera, getViewMask), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "bool getDrawShadowsOverride() const", asMETHOD(Camera, getDrawShadowsOverride), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "int getLightDetailLevelOverride() const", asMETHOD(Camera, getLightDetailLevelOverride), asCALL_THISCALL);
@@ -124,74 +129,32 @@ static void registerSkeleton(asIScriptEngine* engine)
 
 static Texture2D* ConstructTexture2D(TextureUsage usage, const std::string& name)
 {
-    try
-    {
-        return new Texture2D(getEngine()->getRenderer(), usage, name);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(new Texture2D(getEngine()->getRenderer(), usage, name));
 }
 
 static void Texture2DSetSize(int width, int height, unsigned format, Texture2D* ptr)
 {
-    try
-    {
-        ptr->setSize(width, height, format);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->setSize(width, height, format));
 }
 
 static void Texture2DLoad(Image* image, Texture2D* ptr)
 {
-    try
-    {
-        ptr->load(SharedPtr<Image>(image));
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->load(SharedPtr<Image>(image)));
 }
 
 static TextureCube* ConstructTextureCube(TextureUsage usage, const std::string& name)
 {
-    try
-    {
-        return new TextureCube(getEngine()->getRenderer(), usage, name);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(new TextureCube(getEngine()->getRenderer(), usage, name));
 }
 
 static void TextureCubeSetSize(int size, unsigned format, TextureCube* ptr)
 {
-    try
-    {
-        ptr->setSize(size, format);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->setSize(size, format));
 }
 
 static void TextureCubeLoad(CubeMapFace face, Image* image, TextureCube* ptr)
 {
-    try
-    {
-        ptr->load(face, SharedPtr<Image>(image));
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->load(face, SharedPtr<Image>(image)));
 }
 
 static void registerTexture(asIScriptEngine* engine)
@@ -839,6 +802,10 @@ static void registerPipeline(asIScriptEngine* engine)
     engine->RegisterObjectType("Pipeline", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("Pipeline", asBEHAVE_ADDREF, "void f()", asMETHOD(Pipeline, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Pipeline", asBEHAVE_RELEASE, "void f()", asMETHOD(Pipeline, releaseRef), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "void setNumViewports(uint)", asMETHOD(Pipeline, setNumViewports), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "void setViewport(uint, Scene@+, Camera@+, const IntRect& in)", asMETHOD(Pipeline, setViewport), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "void setViewportCamera(uint, Camera@+)", asMETHOD(Pipeline, setViewportCamera), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "void setViewportScreenRect(uint, Camera@+)", asMETHOD(Pipeline, setViewportScreenRect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "void setSpecularLighting(bool)", asMETHOD(Pipeline, setSpecularLighting), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "void setDrawShadows(bool)", asMETHOD(Pipeline, setDrawShadows), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "void setTextureAnisotropy(int)", asMETHOD(Pipeline, setTextureAnisotropy), asCALL_THISCALL);
@@ -852,6 +819,11 @@ static void registerPipeline(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Pipeline", "void setOcclusionBufferSize(int)", asMETHOD(Pipeline, setOcclusionBufferSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "void setOccluderSizeThreshold(float)", asMETHOD(Pipeline, setOccluderSizeThreshold), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "void setEdgeFilter(const EdgeFilterParameters& in)", asMETHOD(Pipeline, setEdgeFilter), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "void drawDebugGeometry()", asMETHOD(Pipeline, drawDebugGeometry), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "uint getNumViewports() const", asMETHOD(Pipeline, getNumViewports), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "Scene@+ getViewportScene(uint) const", asMETHOD(Pipeline, getViewportScene), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "Camera@+ getViewportCamera(uint) const", asMETHOD(Pipeline, getViewportCamera), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Pipeline", "IntRect getViewportScreenRect(uint) const", asMETHOD(Pipeline, getViewportScreenRect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "uint getFrameNumber() const", asMETHOD(Pipeline, getFrameNumber), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "float getElapsedTime() const", asMETHOD(Pipeline, getElapsedTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("Pipeline", "bool getSpecularLighting() const", asMETHOD(Pipeline, getSpecularLighting), asCALL_THISCALL);
@@ -879,7 +851,16 @@ static void registerPipeline(asIScriptEngine* engine)
 
 static DebugRenderer* GetDebugRenderer()
 {
-    return getEngine()->getDebugRenderer();
+    Scene* scene = getScriptContextScene();
+    if (scene)
+        return scene->getExtension<DebugRenderer>();
+    else
+        return 0;
+}
+
+static DebugRenderer* SceneGetDebugRenderer(Scene* ptr)
+{
+    return ptr->getExtension<DebugRenderer>();
 }
 
 static void registerDebugRenderer(asIScriptEngine* engine)
@@ -891,6 +872,7 @@ static void registerDebugRenderer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("DebugRenderer", "void addBoundingBox(const BoundingBox& in, const Color& in, bool)", asMETHODPR(DebugRenderer, addBoundingBox, (const BoundingBox&, const Color&, bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void addFrustum(const Frustum& in, const Color& in, bool)", asMETHOD(DebugRenderer, addFrustum), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void addSkeleton(Skeleton@+, const Color& in, bool)", asMETHOD(DebugRenderer, addSkeleton), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "DebugRenderer@+ getDebugRenderer() const", asFUNCTION(SceneGetDebugRenderer), asCALL_CDECL_OBJLAST);
     
     engine->RegisterGlobalFunction("DebugRenderer@+ getDebugRenderer()", asFUNCTION(GetDebugRenderer), asCALL_CDECL);
     engine->RegisterGlobalFunction("DebugRenderer@+ get_debugRenderer()", asFUNCTION(GetDebugRenderer), asCALL_CDECL);
@@ -909,44 +891,39 @@ static Node* RayQueryResultGetNode(RayQueryResult* ptr)
     return ptr->mNode;
 }
 
-static CScriptArray* OctreeRaycast(const Ray& ray, unsigned includeFlags, unsigned excludeFlags, float maxDistance, RayQueryLevel level, Octree* ptr)
+static CScriptArray* OctreeRaycast(const Ray& ray, unsigned nodeFlags, float maxDistance, RayQueryLevel level, Octree* ptr)
 {
-    static std::vector<RayQueryResult> result;
-    RayOctreeQuery query(ray, result, includeFlags, excludeFlags, false, false, maxDistance, level);
+    RayOctreeQuery query(rayQueryResult, ray, nodeFlags, false, false, maxDistance, level);
     ptr->getNodes(query);
-    return vectorToArray<RayQueryResult>(result, "array<RayQueryResult>");
+    return vectorToArray<RayQueryResult>(rayQueryResult, "array<RayQueryResult>");
 }
 
-static CScriptArray* OctreeGetNodesPoint(const Vector3& point, unsigned includeFlags, unsigned excludeFlags, Octree* ptr)
+static CScriptArray* OctreeGetNodesPoint(const Vector3& point, unsigned nodeFlags, Octree* ptr)
 {
-    static std::vector<VolumeNode*> result;
-    PointOctreeQuery query(point, result, includeFlags, excludeFlags);
+    PointOctreeQuery query(nodeResult, point, nodeFlags);
     ptr->getNodes(query);
-    return vectorToHandleArray<VolumeNode>(result, "array<Node@>");
+    return vectorToHandleArray<VolumeNode>(nodeResult, "array<Node@>");
 }
 
-static CScriptArray* OctreeGetNodesBox(const BoundingBox& box, unsigned includeFlags, unsigned excludeFlags, Octree* ptr)
+static CScriptArray* OctreeGetNodesBox(const BoundingBox& box, unsigned nodeFlags, Octree* ptr)
 {
-    static std::vector<VolumeNode*> result;
-    BoxOctreeQuery query(box, result, includeFlags, excludeFlags);
+    BoxOctreeQuery query(nodeResult, box, nodeFlags);
     ptr->getNodes(query);
-    return vectorToHandleArray<VolumeNode>(result, "array<Node@>");
+    return vectorToHandleArray<VolumeNode>(nodeResult, "array<Node@>");
 }
 
-static CScriptArray* OctreeGetNodesFrustum(const Frustum& frustum, unsigned includeFlags, unsigned excludeFlags, Octree* ptr)
+static CScriptArray* OctreeGetNodesFrustum(const Frustum& frustum, unsigned nodeFlags, Octree* ptr)
 {
-    static std::vector<VolumeNode*> result;
-    FrustumOctreeQuery query(frustum, result, includeFlags, excludeFlags);
+    FrustumOctreeQuery query(nodeResult, frustum, nodeFlags);
     ptr->getNodes(query);
-    return vectorToHandleArray<VolumeNode>(result, "array<Node@>");
+    return vectorToHandleArray<VolumeNode>(nodeResult, "array<Node@>");
 }
 
-static CScriptArray* OctreeGetNodesSphere(const Sphere& sphere, unsigned includeFlags, unsigned excludeFlags, Octree* ptr)
+static CScriptArray* OctreeGetNodesSphere(const Sphere& sphere, unsigned nodeFlags, Octree* ptr)
 {
-    static std::vector<VolumeNode*> result;
-    SphereOctreeQuery query(sphere, result, includeFlags, excludeFlags);
+    SphereOctreeQuery query(nodeResult, sphere, nodeFlags);
     ptr->getNodes(query);
-    return vectorToHandleArray<VolumeNode>(result, "array<Node@>");
+    return vectorToHandleArray<VolumeNode>(nodeResult, "array<Node@>");
 }
 
 static Octree* SceneGetOctree(Scene* ptr)
@@ -981,14 +958,17 @@ static void registerOctree(asIScriptEngine* engine)
     engine->RegisterObjectProperty("RayQueryResult", "uint subObject", offsetof(RayQueryResult, mSubObject));
     
     registerHashedType<Octree>(engine, "Octree");
+    engine->RegisterObjectMethod("Octree", "void resize(const BoundingBox& in, uint)", asMETHOD(Octree, resize), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Octree", "void setExcludeFlags(uint)", asMETHOD(Octree, getExcludeFlags), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "const BoundingBox& getWorldBoundingBox() const", asMETHODPR(Octree, getWorldBoundingBox, () const, const BoundingBox&), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "uint getNumLevels() const", asMETHOD(Octree, getNumLevels), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Octree", "uint getExcludeFlags() const", asMETHOD(Octree, getExcludeFlags), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "bool isHeadless() const", asMETHOD(Octree, isHeadless), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Octree", "array<RayQueryResult>@ raycast(const Ray& in, uint, uint, float, RayQueryLevel)", asFUNCTION(OctreeRaycast), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Vector3& in, uint, uint)", asFUNCTION(OctreeGetNodesPoint), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const BoundingBox& in, uint, uint)", asFUNCTION(OctreeGetNodesBox), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Frustum& in, uint, uint)", asFUNCTION(OctreeGetNodesFrustum), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Sphere& in, uint, uint)", asFUNCTION(OctreeGetNodesSphere), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Octree", "array<RayQueryResult>@ raycast(const Ray& in, uint, float, RayQueryLevel)", asFUNCTION(OctreeRaycast), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Vector3& in, uint)", asFUNCTION(OctreeGetNodesPoint), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const BoundingBox& in, uint)", asFUNCTION(OctreeGetNodesBox), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Frustum& in, uint)", asFUNCTION(OctreeGetNodesFrustum), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Sphere& in, uint)", asFUNCTION(OctreeGetNodesSphere), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Octree@+ getOctree() const", asFUNCTION(SceneGetOctree), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool isHeadless() const", asFUNCTION(SceneIsHeadless), asCALL_CDECL_OBJLAST);
     

+ 4 - 16
Engine/Engine/RegisterResource.cpp

@@ -202,28 +202,16 @@ static void XMLFileLoad(File* file, XMLFile* ptr)
 {
     if (!file)
         SAFE_EXCEPTION("Null source file");
-    try
-    {
-        ptr->load(*file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    
+    TRY_SAFE_RETHROW(ptr->load(*file));
 }
 
 static void XMLFileSave(File* file, XMLFile* ptr)
 {
     if (!file)
         SAFE_EXCEPTION("Null destination file");
-    try
-    {
-        ptr->save(*file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    
+    TRY_SAFE_RETHROW(ptr->save(*file));
 }
 
 static void registerXMLFile(asIScriptEngine* engine)

+ 78 - 104
Engine/Engine/RegisterScene.cpp

@@ -27,6 +27,9 @@
 #include "ScriptFile.h"
 #include "ScriptInstance.h"
 
+static std::vector<Component*> componentResult;
+static std::vector<Entity*> entityResult;
+
 static void ConstructComponentRef(ComponentRef* ptr)
 {
     new(ptr) ComponentRef();
@@ -87,26 +90,12 @@ static void registerComponentRef(asIScriptEngine* engine)
 
 Component* EntityCreateComponent(const std::string& type, Entity* ptr)
 {
-    try
-    {
-        return ptr->createComponent(ShortStringHash(type));
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(ptr->createComponent(ShortStringHash(type)));
 }
 
 static Component* EntityCreateComponentWithName(const std::string& type, const std::string& name, Entity* ptr)
 {
-    try
-    {
-        return ptr->createComponent(ShortStringHash(type), name);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(ptr->createComponent(ShortStringHash(type), name));
 }
 
 static void EntityRemoveComponent(const std::string& type, Entity* ptr)
@@ -175,6 +164,12 @@ static CScriptArray* EntityGetComponents(Entity* ptr)
     return sharedPtrVectorToHandleArray<Component>(components, "array<Component@>");
 }
 
+static CScriptArray* EntityGetComponentsWithType(const std::string& type, Entity* ptr)
+{
+    ptr->getComponents(componentResult, ShortStringHash(type));
+    return vectorToHandleArray<Component>(componentResult, "array<Component@>");
+}
+
 static asIScriptObject* EntityGetScriptObject(Entity* ptr)
 {
     const std::vector<SharedPtr<Component> >& components = ptr->getComponents();
@@ -256,6 +251,7 @@ static void registerEntity(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Entity", "ScriptObject@+ getScriptObject(const string& in) const", asFUNCTION(EntityGetScriptObjectWithClass), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Entity", "uint getNumComponents() const", asMETHOD(Entity, getNumComponents), asCALL_THISCALL);
     engine->RegisterObjectMethod("Entity", "array<Component@>@ getComponents() const", asFUNCTION(EntityGetComponents), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Entity", "array<Component@>@ getComponents(const string& in) const", asFUNCTION(EntityGetComponentsWithType), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Entity", "bool isAuthority() const", asMETHOD(Entity, isAuthority), asCALL_THISCALL);
     engine->RegisterObjectMethod("Entity", "bool isProxy() const", asMETHOD(Entity, isProxy), asCALL_THISCALL);
     engine->RegisterObjectMethod("Entity", "bool isOwnerPredicted() const", asMETHOD(Entity, isOwnerPredicted), asCALL_THISCALL);
@@ -277,21 +273,28 @@ static Scene* GetScene()
 
 static Entity* SceneCreateEntity(Scene* ptr)
 {
-    try
-    {
-        return ptr->createEntity();
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(ptr->createEntity());
 }
 
 static Entity* SceneCreateEntityWithName(const std::string& name, Scene* ptr)
+{
+    TRY_CONSTRUCT(ptr->createEntity(name));
+}
+
+static Entity* SceneCreateEntityWithNameAndLocalFlag(const std::string& name, bool local, Scene* ptr)
+{
+    TRY_CONSTRUCT(ptr->createEntity(name, local));
+}
+
+static Component* SceneCreateComponent(const std::string& type, Scene* ptr)
 {
     try
     {
-        return ptr->createEntity(name);
+        SharedPtr<Component> component = ptr->createComponent(ShortStringHash(type));
+        // The shared pointer will go out of scope, so have to increment the reference count
+        // (here an auto handle can not be used)
+        component->addRef();
+        return component.getPtr();
     }
     catch (Exception& e)
     {
@@ -299,11 +302,13 @@ static Entity* SceneCreateEntityWithName(const std::string& name, Scene* ptr)
     }
 }
 
-static Entity* SceneCreateEntityWithNameAndLocalFlag(const std::string& name, bool local, Scene* ptr)
+static Component* SceneCreateComponentWithName(const std::string& type, const std::string& name, Scene* ptr)
 {
     try
     {
-        return ptr->createEntity(name, local);
+        SharedPtr<Component> component = ptr->createComponent(ShortStringHash(type), name);
+        component->addRef();
+        return component.getPtr();
     }
     catch (Exception& e)
     {
@@ -315,121 +320,80 @@ static void SceneSave(File* file, Scene* ptr)
 {
     if (!file)
         SAFE_EXCEPTION("Null scene destination file");
-    try
-    {
-        ptr->save(*file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    
+    TRY_SAFE_RETHROW(ptr->save(*file));
 }
 
 static void SceneLoad(File* file, Scene* ptr)
 {
     if (!file)
         SAFE_EXCEPTION("Null scene source file");
-    try
-    {
-        ptr->load(*file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    
+    TRY_SAFE_RETHROW(ptr->load(*file));
 }
 
 static void SceneSaveXML(File* file, Scene* ptr)
 {
     if (!file)
         SAFE_EXCEPTION("Null scene destination XML file");
-    try
-    {
-        ptr->saveXML(*file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    
+    TRY_SAFE_RETHROW(ptr->saveXML(*file));
 }
 
 static void SceneLoadXML(File* file, Scene* ptr)
 {
     if (!file)
         SAFE_EXCEPTION("Null scene source XML file");
-    try
-    {
-        ptr->loadXML(*file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    
+    TRY_SAFE_RETHROW(ptr->loadXML(*file));
 }
 
 static void SceneLoadAsync(File* file, Scene* ptr)
 {
-    try
-    {
-        ptr->loadAsync(file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->loadAsync(file));
 }
 
 static void SceneLoadAsyncXML(File* file, Scene* ptr)
 {
-    try
-    {
-        ptr->loadAsyncXML(file);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->loadAsyncXML(file));
 }
 
-static void SceneRemoveAllEntitiesDefault(Scene* ptr)
+static CScriptArray* SceneGetAllEntities(Scene* ptr)
 {
-    ptr->removeAllEntities();
+    const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getAllEntities();
+    entityResult.clear();
+    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
+        entityResult.push_back(i->second);
+    return vectorToHandleArray<Entity>(entityResult, "array<Entity@>");
 }
 
-static CScriptArray* SceneGetEntities(Scene* ptr)
+static CScriptArray* SceneGetEntities(unsigned char netFlags, unsigned groupFlags, Scene* ptr)
 {
-    const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getEntities();
-    std::vector<Entity*> result;
-    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
-        result.push_back(i->second);
-    return vectorToHandleArray<Entity>(result, "array<Entity@>");
+    ptr->getEntities(entityResult, netFlags, groupFlags);
+    return vectorToHandleArray<Entity>(entityResult, "array<Entity@>");
 }
 
-static CScriptArray* SceneGetEntitiesWithFlags(unsigned includeFlags, unsigned excludeFlags, Scene* ptr)
+static CScriptArray* SceneGetEntitiesWithComponent(const std::string& type, Scene* ptr)
 {
-    std::vector<Entity*> result = ptr->getEntities(includeFlags, excludeFlags);
-    return vectorToHandleArray<Entity>(result, "array<Entity@>");
+    ptr->getEntitiesWithComponent(entityResult, ShortStringHash(type));
+    return vectorToHandleArray<Entity>(entityResult, "array<Entity@>");
 }
 
 static CScriptArray* SceneGetScriptedEntities(Scene* ptr)
 {
-    const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getEntities();
-    std::vector<Entity*> result;
-    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
-    {
-        if (i->second->hasComponent<ScriptInstance>())
-            result.push_back(i->second);
-    }
-    return vectorToHandleArray<Entity>(result, "array<Entity@>");
+    ptr->getEntitiesWithComponent<ScriptInstance>(entityResult);
+    return vectorToHandleArray<Entity>(entityResult, "array<Entity@>");
 }
 
 static CScriptArray* SceneGetScriptedEntitiesWithClass(const std::string& className, Scene* ptr)
 {
-    const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getEntities();
-    std::vector<Entity*> result;
-    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
+    static std::vector<Entity*> tempEntityResult;
+    entityResult.clear();
+    
+    ptr->getEntitiesWithComponent<ScriptInstance>(tempEntityResult);
+    for (std::vector<Entity*>::const_iterator i = tempEntityResult.begin(); i != tempEntityResult.end(); ++i)
     {
-        const std::vector<SharedPtr<Component> >& components = i->second->getComponents();
+        const std::vector<SharedPtr<Component> >& components = (*i)->getComponents();
         for (std::vector<SharedPtr<Component> >::const_iterator j = components.begin(); j != components.end(); ++j)
         {
             if ((*j)->getType() == ScriptInstance::getTypeStatic())
@@ -437,46 +401,55 @@ static CScriptArray* SceneGetScriptedEntitiesWithClass(const std::string& classN
                 ScriptInstance* instance = static_cast<ScriptInstance*>(j->getPtr());
                 if (instance->getClassName() == className)
                 {
-                    result.push_back(i->second);
+                    entityResult.push_back(*i);
                     break;
                 }
             }
         }
     }
-    return vectorToHandleArray<Entity>(result, "array<Entity@>");
+    return vectorToHandleArray<Entity>(entityResult, "array<Entity@>");
 }
 
 static void registerScene(asIScriptEngine* engine)
 {
     engine->RegisterObjectBehaviour("Scene", asBEHAVE_ADDREF, "void f()", asMETHOD(Scene, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Scene", asBEHAVE_RELEASE, "void f()", asMETHOD(Scene, releaseRef), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void update(float)", asMETHOD(Scene, update), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void save(File@+)", asFUNCTION(SceneSave), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void load(File@+)", asFUNCTION(SceneLoad), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void saveXML(File@+)", asFUNCTION(SceneSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void loadXML(File@+)", asFUNCTION(SceneLoadXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void loadAsync(File@+)", asFUNCTION(SceneLoadAsync), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void loadAsyncXML(File@+)", asFUNCTION(SceneLoadAsyncXML), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "void stopAsyncLoading()", asMETHOD(Scene, stopAsyncLoading), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void setName(const string& in)", asMETHOD(Scene, setName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void setNetFlags(uint8)", asMETHOD(Scene, setNetFlags), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void setPaused(bool)", asMETHOD(Scene, setPaused), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Entity@+ createEntity()", asFUNCTION(SceneCreateEntity), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Entity@+ createEntity(const string& in)", asFUNCTION(SceneCreateEntityWithName), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Entity@+ createEntity(const string& in, bool)", asFUNCTION(SceneCreateEntityWithNameAndLocalFlag), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Component@ createComponent(const string& in)", asFUNCTION(SceneCreateComponent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Component@ createComponent(const string& in, const string& in)", asFUNCTION(SceneCreateComponentWithName), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void addEntity(Entity@+)", asMETHOD(Scene, addEntity), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void removeEntity(uint)", asMETHODPR(Scene, removeEntity, (EntityID), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void removeEntity(Entity@+)", asMETHODPR(Scene, removeEntity, (Entity*), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void removeEntity(const string& in)", asMETHODPR(Scene, removeEntity, (const std::string&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Scene", "void removeAllEntities()", asFUNCTION(SceneRemoveAllEntitiesDefault), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Scene", "void removeAllEntities(uint8)", asMETHOD(Scene, removeAllEntities), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void removeEntities(uint8, uint)", asMETHOD(Scene, removeEntities), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void removeAllEntities()", asMETHOD(Scene, removeAllEntities), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void setTransientPredictionTime(float)", asMETHOD(Scene, setTransientPredictionTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void setInterpolationConstant(float)", asMETHOD(Scene, setInterpolationConstant), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void setInterpolationSnapThreshold(float)", asMETHOD(Scene, setInterpolationSnapThreshold), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "const string& getName() const", asMETHOD(Scene, getName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "uint8 getNetFlags() const", asMETHOD(Scene, getNetFlags), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool hasEntity(uint) const", asMETHODPR(Scene, hasEntity, (EntityID) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool hasEntity(Entity@+) const", asMETHODPR(Scene, hasEntity, (Entity*) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool hasEntity(const string& in) const", asMETHODPR(Scene, hasEntity, (const std::string&) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Entity@+ getEntity(uint) const", asMETHODPR(Scene, getEntity, (EntityID) const, Entity*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "Entity@+ getEntity(const string& in) const", asMETHODPR(Scene, getEntity, (const std::string&) const, Entity*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "uint getNumEntities() const", asMETHOD(Scene, getNumEntities), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getEntities() const", asFUNCTION(SceneGetEntities), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getEntities(uint, uint) const", asFUNCTION(SceneGetEntitiesWithFlags), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getAllEntities() const", asFUNCTION(SceneGetAllEntities), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getEntities(uint8, uint) const", asFUNCTION(SceneGetEntities), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getEntitiesWithComponent(const string& in) const", asFUNCTION(SceneGetEntitiesWithComponent), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "array<Entity@>@ getScriptedEntities() const", asFUNCTION(SceneGetScriptedEntities), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "array<Entity@>@ getScriptedEntities(const string& in) const", asFUNCTION(SceneGetScriptedEntitiesWithClass), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Vector3 getEntityPosition(Entity@+) const", asMETHOD(Scene, getEntityPosition), asCALL_THISCALL);
@@ -488,6 +461,7 @@ static void registerScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "float getAsyncLoadProgress() const", asMETHOD(Scene, getAsyncLoadProgress), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool isAuthority() const", asMETHOD(Scene, isAuthority), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool isProxy() const", asMETHOD(Scene, isProxy), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "bool isPaused() const", asMETHOD(Scene, isPaused), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool isPlayback() const", asMETHOD(Scene, isPlayback), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool isAsyncLoading() const", asMETHOD(Scene, isAsyncLoading), asCALL_THISCALL);
     registerRefCasts<EventListener, Scene>(engine, "EventListener", "Scene");

+ 4 - 32
Engine/Engine/RegisterTemplates.h

@@ -250,27 +250,13 @@ template <class T> void registerHashedType(asIScriptEngine* engine, const char*
 //! Template function for saving a component or an entity to XML
 template <class T> void objectSaveXML(XMLElement& element, T* ptr)
 {
-    try
-    {
-        ptr->saveXML(element);
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->saveXML(element));
 }
 
 //! Template function for loading a component or an entity from XML
 template <class T> void objectLoadXML(const XMLElement& element, T* ptr)
 {
-    try
-    {
-        ptr->loadXML(element, getEngine()->getResourceCache());
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    TRY_SAFE_RETHROW(ptr->loadXML(element, getEngine()->getResourceCache()));
 }
 
 //! Template function for registering a class derived from Component
@@ -426,27 +412,13 @@ template <class T> T* ConstructUIElementWithName(const std::string& name)
 //! Template function for setting UI element style from an XML element
 template <class T> void UIElementSetStyle(const XMLElement& element, T* ptr)
 {
-    try
-    {
-        ptr->setStyle(element, getEngine()->getResourceCache());
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    ptr->setStyle(element, getEngine()->getResourceCache());
 }
 
 //! Template function for setting UI element style from an XML file
 template <class T> void UIElementSetStyleAuto(XMLFile* file, T* ptr)
 {
-    try
-    {
-        ptr->setStyleAuto(file, getEngine()->getResourceCache());
-    }
-    catch (Exception& e)
-    {
-        SAFE_RETHROW(e);
-    }
+    ptr->setStyleAuto(file, getEngine()->getResourceCache());
 }
 
 //! Template function for registering a class derived from UIElement

+ 1 - 9
Engine/Engine/RegisterUI.cpp

@@ -384,15 +384,7 @@ static void registerWindow(asIScriptEngine* engine)
 
 static FileSelector* ConstructFileSelector()
 {
-    try
-    {
-        return new FileSelector(getEngine()->getUI());
-    }
-    catch (Exception& e)
-    {
-        // Rethrow after FileSelector has been deallocated
-        SAFE_RETHROW_RET(e, 0);
-    }
+    TRY_CONSTRUCT(new FileSelector(getEngine()->getUI()));
 }
 
 static void FileSelectorSetFilters(CScriptArray* filters, unsigned defaultIndex, FileSelector* ptr)

+ 2 - 2
Engine/Engine/Server.cpp

@@ -794,7 +794,7 @@ void Server::writeNetUpdate(Connection* connection, Serializer& dest)
     
     {
         // Go through the scene and see which entities are new and which have been removed
-        const std::map<EntityID, SharedPtr<Entity> >& entities = scene->getEntities();
+        const std::map<EntityID, SharedPtr<Entity> >& entities = scene->getAllEntities();
         std::set<EntityID> processedEntities;
         for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
         {
@@ -1067,7 +1067,7 @@ void Server::getRelevantEntities(Connection* connection, std::set<EntityID>& des
     dest.clear();
     
     Scene* scene = connection->getScene();
-    const std::map<EntityID, SharedPtr<Entity> >& entities = scene->getEntities();
+    const std::map<EntityID, SharedPtr<Entity> >& entities = scene->getAllEntities();
     const Vector3& clientPos = connection->getPosition();
     
     for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)

+ 15 - 11
Engine/Physics/PhysicsWorld.cpp

@@ -30,6 +30,7 @@
 #include "Profiler.h"
 #include "Ray.h"
 #include "RigidBody.h"
+#include "Scene.h"
 #include "StringUtils.h"
 #include "VectorBuffer.h"
 #include "XMLElement.h"
@@ -46,8 +47,7 @@ static bool compareRaycastResults(const PhysicsRaycastResult& lhs, const Physics
     return lhs.mDistance < rhs.mDistance;
 }
 
-PhysicsWorld::PhysicsWorld(Scene* scene) :
-    mScene(scene),
+PhysicsWorld::PhysicsWorld() :
     mWorld(0),
     mSpace(0),
     mRayGeometry(0),
@@ -360,7 +360,7 @@ void PhysicsWorld::setRandomSeed(unsigned seed)
     dRandSetSeed(seed);
 }
 
-void PhysicsWorld::raycast(const Ray& ray, std::vector<PhysicsRaycastResult>& result, float maxDistance, unsigned collisionMask)
+void PhysicsWorld::raycast(std::vector<PhysicsRaycastResult>& result, const Ray& ray, float maxDistance, unsigned collisionMask)
 {
     PROFILE(Physics_Raycast);
     
@@ -373,6 +373,18 @@ void PhysicsWorld::raycast(const Ray& ray, std::vector<PhysicsRaycastResult>& re
     std::sort(result.begin(), result.end(), compareRaycastResults);
 }
 
+void PhysicsWorld::drawDebugGeometry()
+{
+    DebugRenderer* debug = mScene->getExtension<DebugRenderer>();
+    if (!debug)
+        return;
+    
+    PROFILE(Physics_DrawDebugGeometry);
+    
+    for (std::vector<RigidBody*>::iterator i = mRigidBodies.begin(); i != mRigidBodies.end(); ++i)
+        (*i)->drawDebugGeometry(debug);
+}
+
 unsigned PhysicsWorld::getRandomSeed() const
 {
     return dRandGetSeed();
@@ -447,14 +459,6 @@ void PhysicsWorld::removeRigidBody(RigidBody* body)
     }
 }
 
-void PhysicsWorld::drawDebugGeometry(DebugRenderer* debug)
-{
-    PROFILE(Physics_DrawDebugGeometry);
-    
-    for (std::vector<RigidBody*>::iterator i = mRigidBodies.begin(); i != mRigidBodies.end(); ++i)
-        (*i)->drawDebugGeometry(debug);
-}
-
 void PhysicsWorld::sendCollisionEvents()
 {
     PROFILE(Physics_SendCollisionEvents);

+ 5 - 7
Engine/Physics/PhysicsWorld.h

@@ -92,8 +92,8 @@ class PhysicsWorld : public SceneExtension, public EventListener
     DEFINE_TYPE(PhysicsWorld);
     
 public:
-    //! Construct with scene pointer
-    PhysicsWorld(Scene* scene);
+    //! Construct
+    PhysicsWorld();
     //! Destruct
     virtual ~PhysicsWorld();
     
@@ -139,8 +139,10 @@ public:
     //! Set simulation random seed
     void setRandomSeed(unsigned seed);
     //! Perform a physics world raycast
-    void raycast(const Ray& ray, std::vector<PhysicsRaycastResult>& result, float maxDistance, unsigned collisionMask =
+    void raycast(std::vector<PhysicsRaycastResult>& result, const Ray& ray, float maxDistance, unsigned collisionMask =
         M_MAX_UNSIGNED);
+    //! Add debug geometry to the debug renderer
+    void drawDebugGeometry();
     
     //! Return ODE world ID
     dWorldID getWorld() const { return mWorld; }
@@ -183,8 +185,6 @@ public:
     void addRigidBody(RigidBody* body);
     //! Remove a rigid body. Called by RigidBody
     void removeRigidBody(RigidBody* body);
-    //! Draw debug geometry. Called by Engine
-    void drawDebugGeometry(DebugRenderer* debug);
     //! Send accumulated collision events
     void sendCollisionEvents();
     
@@ -194,8 +194,6 @@ private:
     //! ODE raycast callback
     static void raycastCallback(void *userData, dGeomID geomA, dGeomID geomB);
     
-    //! Scene
-    Scene* mScene;
     //! ODE world ID
     dWorldID mWorld;
     //! ODE space ID

+ 1 - 1
Engine/Physics/RigidBody.h

@@ -173,7 +173,7 @@ public:
     //! Return whether body is active
     bool isActive() const;
     
-    //! Draw debug geometry
+    //! Add debug geometry to the debug renderer
     void drawDebugGeometry(DebugRenderer* debug);
     
 private:

+ 1 - 1
Engine/Renderer/AnimatedModel.h

@@ -75,7 +75,7 @@ public:
     virtual GeometryType getGeometryType() { return GEOM_SKINNED; }
     //! Return vertex shader parameter
     virtual bool getVertexShaderParameter(unsigned batchIndex, VSParameter parameter, const float** data, unsigned* count);
-    //! Draw debug geometry
+    //! Add debug geometry to the debug renderer
     virtual void drawDebugGeometry(DebugRenderer* debug);
     
     //! Set model

+ 32 - 19
Engine/Renderer/Camera.cpp

@@ -46,6 +46,7 @@ Camera::Camera(const std::string& name) :
     mZoom(1.0f),
     mLodBias(1.0f),
     mOrthographic(false),
+    mAutoAspectRatio(true),
     mViewMask(VIEW_MAIN),
     mDrawShadowsOverride(true),
     mLightDetailLevelOverride(QUALITY_MAX),
@@ -77,6 +78,7 @@ void Camera::save(Serializer& dest)
     dest.writeFloat(mZoom);
     dest.writeFloat(mLodBias);
     dest.writeBool(mOrthographic);
+    dest.writeBool(mAutoAspectRatio);
     dest.writeUInt(mViewMask);
     dest.writeBool(mDrawShadowsOverride);
     dest.writeUByte(mLightDetailLevelOverride);
@@ -98,6 +100,7 @@ void Camera::load(Deserializer& source, ResourceCache* cache)
     mZoom = source.readFloat();
     mLodBias = source.readFloat();
     mOrthographic = source.readBool();
+    mAutoAspectRatio = source.readBool();
     mViewMask = source.readUInt();
     mDrawShadowsOverride = source.readBool();
     mLightDetailLevelOverride = source.readUByte();
@@ -120,6 +123,7 @@ void Camera::saveXML(XMLElement& dest)
     projectionElem.setFloat("fov", mFov);
     projectionElem.setFloat("orthosize", mOrthoSize);
     projectionElem.setFloat("aspectratio", mAspectRatio);
+    projectionElem.setBool("autoaspect", mAutoAspectRatio);
     projectionElem.setFloat("zoom", mZoom);
     
     XMLElement lodElem = dest.createChildElement("lod");
@@ -146,6 +150,7 @@ void Camera::loadXML(const XMLElement& source, ResourceCache* cache)
     mFov = projectionElem.getFloat("fov");
     mOrthoSize = projectionElem.getFloat("orthosize");
     mAspectRatio = projectionElem.getFloat("aspectratio");
+    mAutoAspectRatio = projectionElem.getFloat("autoaspectratio");
     mZoom = projectionElem.getFloat("zoom");
     
     XMLElement lodElem = source.getChildElement("lod");
@@ -171,14 +176,15 @@ bool Camera::writeNetUpdate(Serializer& dest, Serializer& destRevision, Deserial
     checkFloat(mFarClip, DEFAULT_FARCLIP, baseRevision, bits, 1);
     checkFloat(mFov, DEFAULT_FOV, baseRevision, bits, 2);
     checkFloat(mOrthoSize, DEFAULT_ORTHOSIZE, baseRevision, bits, 4);
-    checkFloat(mAspectRatio, 1.0f, baseRevision, bits, 4);
     checkBool(mOrthographic, false, baseRevision, bits, 4);
-    checkFloat(mZoom, 1.0f, baseRevision, bits, 8);
-    checkFloat(mLodBias, 1.0f, baseRevision, bits, 16);
-    checkUInt(mViewMask, VIEW_ALL, baseRevision, bits, 32);
-    checkBool(mDrawShadowsOverride, true, baseRevision, bits, 64);
-    checkUByte(qualityOverrides, QUALITY_MAX | (QUALITY_MAX << 4), baseRevision, bits, 64);
-    checkInt(mMaxOccluderTrianglesOverride, M_MAX_INT, baseRevision, bits, 64);
+    checkFloat(mAspectRatio, 1.0f, baseRevision, bits, 8);
+    checkBool(mAutoAspectRatio, true, baseRevision, bits, 8);
+    checkFloat(mZoom, 1.0f, baseRevision, bits, 16);
+    checkFloat(mLodBias, 1.0f, baseRevision, bits, 32);
+    checkUInt(mViewMask, VIEW_ALL, baseRevision, bits, 64);
+    checkBool(mDrawShadowsOverride, true, baseRevision, bits, 128);
+    checkUByte(qualityOverrides, QUALITY_MAX | (QUALITY_MAX << 4), baseRevision, bits, 128);
+    checkInt(mMaxOccluderTrianglesOverride, M_MAX_INT, baseRevision, bits, 128);
     
     // Update replication state fully, and network stream by delta
     dest.writeUByte(bits);
@@ -186,14 +192,15 @@ bool Camera::writeNetUpdate(Serializer& dest, Serializer& destRevision, Deserial
     writeFloatDelta(mFarClip, dest, destRevision, bits & 1);
     writeFloatDelta(mFov, dest, destRevision, bits & 2);
     writeFloatDelta(mOrthoSize, dest, destRevision, bits & 4);
-    writeFloatDelta(mAspectRatio, dest, destRevision, bits & 4);
     writeBoolDelta(mOrthographic, dest, destRevision, bits & 4);
-    writeFloatDelta(mZoom, dest, destRevision, bits & 8);
-    writeFloatDelta(mLodBias, dest, destRevision, bits & 16);
-    writeUIntDelta(mViewMask, dest, destRevision, bits & 32);
-    writeBoolDelta(mDrawShadowsOverride, dest, destRevision, bits & 64);
-    writeUByteDelta(qualityOverrides, dest, destRevision, bits & 64);
-    writeIntDelta(mMaxOccluderTrianglesOverride, dest, destRevision, bits & 64);
+    writeFloatDelta(mAspectRatio, dest, destRevision, bits & 8);
+    writeBoolDelta(mAutoAspectRatio, dest, destRevision, bits & 8);
+    writeFloatDelta(mZoom, dest, destRevision, bits & 16);
+    writeFloatDelta(mLodBias, dest, destRevision, bits & 32);
+    writeUIntDelta(mViewMask, dest, destRevision, bits & 64);
+    writeBoolDelta(mDrawShadowsOverride, dest, destRevision, bits & 128);
+    writeUByteDelta(qualityOverrides, dest, destRevision, bits & 128);
+    writeIntDelta(mMaxOccluderTrianglesOverride, dest, destRevision, bits & 128);
     
     return prevBits || (bits != 0);
 }
@@ -208,12 +215,13 @@ void Camera::readNetUpdate(Deserializer& source, ResourceCache* cache, const Net
     readFloatDelta(mFarClip, source, bits & 1);
     readFloatDelta(mFov, source, bits & 2);
     readFloatDelta(mOrthoSize, source, bits & 4);
-    readFloatDelta(mAspectRatio, source, bits & 4);
     readBoolDelta(mOrthographic, source, bits & 4);
-    readFloatDelta(mZoom, source, bits & 8);
-    readFloatDelta(mLodBias, source, bits & 16);
-    readUIntDelta(mViewMask, source, bits & 32);
-    if (bits & 64)
+    readFloatDelta(mAspectRatio, source, bits & 8);
+    readBoolDelta(mAutoAspectRatio, source, bits & 8);
+    readFloatDelta(mZoom, source, bits & 16);
+    readFloatDelta(mLodBias, source, bits & 32);
+    readUIntDelta(mViewMask, source, bits & 64);
+    if (bits & 128)
     {
         mDrawShadowsOverride = source.readBool();
         unsigned char qualityOverrides = source.readUByte();
@@ -281,6 +289,11 @@ void Camera::setOrthographic(bool enable)
     markProjectionDirty();
 }
 
+void Camera::setAutoAspectRatio(bool enable)
+{
+    mAutoAspectRatio = enable;
+}
+
 void Camera::setViewMask(unsigned mask)
 {
     mViewMask = mask;

+ 6 - 0
Engine/Renderer/Camera.h

@@ -76,6 +76,8 @@ public:
     void setLodBias(float bias);
     //! Set orthographic mode enabled/disabled
     void setOrthographic(bool enable);
+    //! Set automatic aspect ratio based on viewport dimensions
+    void setAutoAspectRatio(bool enable);
     //! Set view mask bits
     void setViewMask(unsigned mask);
     //! Override drawing of shadows
@@ -103,6 +105,8 @@ public:
     float getLodBias() const { return mLodBias; }
     //! Return orthographic flag
     bool isOrthographic() const { return mOrthographic; }
+    //! Return auto aspect ratio flag
+    bool getAutoAspectRatio() const { return mAutoAspectRatio; }
     //! Return view mask bits
     unsigned getViewMask() const { return mViewMask; }
     //! Return draw shadows override
@@ -185,6 +189,8 @@ private:
     float mLodBias;
     //! Orthographic mode flag
     bool mOrthographic;
+    //! Auto aspect ratio flag
+    bool mAutoAspectRatio;
     //! View mask bits
     unsigned mViewMask;
     //! Draw shadows override

+ 16 - 23
Engine/Renderer/DebugRenderer.cpp

@@ -24,11 +24,13 @@
 #include "Precompiled.h"
 #include "AnimatedModel.h"
 #include "DebugRenderer.h"
+#include "Exception.h"
 #include "Light.h"
 #include "Log.h"
 #include "PixelShader.h"
 #include "Profiler.h"
 #include "Renderer.h"
+#include "RendererEvents.h"
 #include "RendererImpl.h"
 #include "ResourceCache.h"
 #include "VertexShader.h"
@@ -39,40 +41,25 @@ DebugRenderer::DebugRenderer(Renderer* renderer, ResourceCache* cache) :
     mRenderer(renderer),
     mCache(cache)
 {
-    LOGINFO("Debug renderer created");
+    if (!mRenderer)
+        EXCEPTION("Null renderer for DebugRenderer");
     
     mDebugVS = mCache->getResource<VertexShader>("Shaders/SM2/Basic_VCol.vs2");
     mDebugPS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_VCol.ps2");
+    
+    subscribeToEvent(EVENT_ENDFRAME, EVENT_HANDLER(DebugRenderer, handleEndFrame));
 }
 
 DebugRenderer::~DebugRenderer()
 {
-    LOGINFO("Debug renderer shut down");
-}
-
-void DebugRenderer::clear()
-{
-    mLines.clear();
-    mNoDepthLines.clear();
-}
-
-void DebugRenderer::setView(Camera* camera)
-{
-    if (camera)
-        mViewProj = camera->getProjection() * camera->getInverseWorldTransform();
-    else
-        mViewProj = Matrix4::sIdentity;
 }
 
-void DebugRenderer::render()
+void DebugRenderer::render(Camera* camera)
 {
-    PROFILE(DebugGeometry_Render);
-    
-    if (!mRenderer)
+    if ((!camera) || ((!mLines.size()) && (!mNoDepthLines.size())))
         return;
     
-    if ((!mLines.size()) && (!mNoDepthLines.size()))
-        return;
+    PROFILE(DebugGeometry_Render);
     
     mRenderer->setAlphaTest(false);
     mRenderer->setBlendMode(BLEND_REPLACE);
@@ -85,7 +72,7 @@ void DebugRenderer::render()
     mRenderer->setStencilTest(false);
     mRenderer->setVertexShader(mDebugVS);
     mRenderer->setPixelShader(mDebugPS);
-    mRenderer->setVertexShaderConstant(getVSRegister(VSP_MODELVIEWPROJ), mViewProj);
+    mRenderer->setVertexShaderConstant(getVSRegister(VSP_MODELVIEWPROJ), camera->getProjection() * camera->getInverseWorldTransform());
     mRenderer->setPixelShaderConstant(getPSRegister(PSP_MATDIFFCOLOR), Color(1.0f, 1.0f, 1.0f, 1.0f));
     
     // Draw all line geometry with depth testing
@@ -257,3 +244,9 @@ void DebugRenderer::addSkeleton(const Skeleton& skeleton, const Color& color, bo
         dest->push_back(newLine);
     }
 }
+
+void DebugRenderer::handleEndFrame(StringHash eventType, VariantMap& eventData)
+{
+    mLines.clear();
+    mNoDepthLines.clear();
+}

+ 10 - 11
Engine/Renderer/DebugRenderer.h

@@ -25,7 +25,9 @@
 #define RENDERER_DEBUGRENDERER_H
 
 #include "Color.h"
+#include "EventListener.h"
 #include "Matrix4.h"
+#include "SceneExtension.h"
 #include "SharedPtr.h"
 
 #include <vector>
@@ -68,18 +70,16 @@ struct DebugLine
 };
 
 //! Debug geometry renderer
-class DebugRenderer : public RefCounted
+class DebugRenderer : public SceneExtension, public EventListener
 {
+    DEFINE_TYPE(DebugRenderer);
+    
 public:
     //! Construct with Renderer and ResourceCache pointers
     DebugRenderer(Renderer* renderer, ResourceCache* cache);
     //! Destruct
     virtual ~DebugRenderer();
     
-    //! Clear debug geometry
-    void clear();
-    //! Set camera view to render from
-    void setView(Camera* camera);
     //! Add a line
     void addLine(const Vector3& start, const Vector3& end, const Color& color, bool depthTest = true);
     //! Add a bounding box
@@ -90,10 +90,13 @@ public:
     void addFrustum(const Frustum& frustum, const Color& color, bool depthTest = true);
     //! Add a skeleton
     void addSkeleton(const Skeleton& skeleton, const Color& color, bool depthTest = true);
-    //! Render all debug lines
-    void render();
+    //! Render all debug lines from a specific camera. The viewport and rendertarget should be set before
+    void render(Camera* camera);
     
 private:
+    //! Handle end of frame. Clear debug geometry
+    void handleEndFrame(StringHash eventType, VariantMap& eventData);
+    
     //! Renderer subsystem
     SharedPtr<Renderer> mRenderer;
     //! Resource cache
@@ -103,14 +106,10 @@ private:
     //! Debug geometry pixel shader
     SharedPtr<PixelShader> mDebugPS;
     
-    //! View transform matrix
-    Matrix4 mViewProj;
     //! Lines rendered with depth test
     std::vector<DebugLine> mLines;
     //! Lines rendered without depth test
     std::vector<DebugLine> mNoDepthLines;
 };
 
-DebugRenderer* getDebugRenderer();
-
 #endif // RENDERR_DEBUGGEOMETRY_H

+ 11 - 2
Engine/Renderer/DeferredView.cpp

@@ -378,7 +378,8 @@ void View::renderBatchesDeferred()
     int gBufferHeight = diffBuffer->getHeight();
     float widthRange = 0.5f * mWidth / gBufferWidth;
     float heightRange = 0.5f * mHeight / gBufferHeight;
-    Vector4 bufferUVOffset(0.5f / gBufferWidth, 0.5f / gBufferHeight, widthRange, heightRange);
+    Vector4 bufferUVOffset((0.5f + (float)mScreenRect.mLeft) / gBufferWidth, (0.5f + (float)mScreenRect.mTop) / gBufferHeight,
+        widthRange, heightRange);
     renderer->setVertexShaderConstant(getVSRegister(VSP_GBUFFEROFFSETS), bufferUVOffset);
     
     {
@@ -394,7 +395,6 @@ void View::renderBatchesDeferred()
         renderer->setScissorTest(false);
         renderer->setStencilTest(false);
         renderer->resetDepthStencil();
-        renderer->setViewport(IntRect(0, 0, mWidth, mHeight));
         if (deferred)
         {
             renderer->setRenderTarget(0, diffBuffer);
@@ -408,6 +408,7 @@ void View::renderBatchesDeferred()
             if (!hwDepth)
                 renderer->setRenderTarget(1, depthBuffer);
         }
+        renderer->setViewport(mScreenRect);
         
         // Clear only depth and stencil at first, render the G-buffer batches
         renderer->clear(CLEAR_DEPTH | CLEAR_STENCIL);
@@ -434,6 +435,7 @@ void View::renderBatchesDeferred()
         {
             renderer->resetRenderTarget(1);
             renderer->setRenderTarget(0, depthBuffer);
+            renderer->setViewport(mScreenRect);
             
             // The stencil shader writes color 1.0, which equals far depth
             mPipeline->drawFullScreenQuad(*mCamera, mPipeline->getVertexShader("Stencil"),
@@ -452,6 +454,7 @@ void View::renderBatchesDeferred()
         renderer->resetRenderTarget(2);
         renderer->setTexture(TU_DIFFBUFFER, diffBuffer);
         renderer->setTexture(TU_DEPTHBUFFER, depthBuffer);
+        renderer->setViewport(mScreenRect);
         
         // Use depth reconstruction only if necessary
         bool linear = mCamera->isOrthographic() || (!hwDepth);
@@ -463,6 +466,7 @@ void View::renderBatchesDeferred()
         // Light prepass: reset the light accumulation buffer with ambient light (half intensity to allow 2x "overburn")
         renderer->setRenderTarget(0, diffBuffer);
         renderer->resetRenderTarget(1);
+        renderer->setViewport(mScreenRect);
         renderer->clear(CLEAR_COLOR, mZone->getAmbientColor() * 0.5f);
     }
     
@@ -550,6 +554,7 @@ void View::renderBatchesDeferred()
                 renderer->setTexture(TU_NORMALBUFFER, normalBuffer);
                 renderer->setTexture(TU_DEPTHBUFFER, depthBuffer);
                 renderer->resetDepthStencil();
+                renderer->setViewport(mScreenRect);
                 
                 for (unsigned j = 0; j < queue.mBatches.size(); ++j)
                 {
@@ -579,6 +584,7 @@ void View::renderBatchesDeferred()
             renderer->setTexture(TU_NORMALBUFFER, normalBuffer);
             renderer->setTexture(TU_DEPTHBUFFER, depthBuffer);
             renderer->resetDepthStencil();
+            renderer->setViewport(mScreenRect);
             
             for (unsigned i = 0; i < mNoShadowLightQueueSorted.size(); ++i)
             {
@@ -598,6 +604,8 @@ void View::renderBatchesDeferred()
         renderer->setTexture(TU_DIFFBUFFER, 0);
         renderer->setTexture(TU_NORMALBUFFER, 0);
         renderer->setTexture(TU_DEPTHBUFFER, 0);
+        renderer->setViewport(mScreenRect);
+        
         if (!deferred)
             renderer->clear(CLEAR_COLOR, mZone->getFogColor());
         
@@ -654,6 +662,7 @@ void View::renderBatchesDeferred()
         renderer->setPixelShaderConstant(getPSRegister(PSP_SAMPLEOFFSETS), Vector4(invWidth, -invWidth, invHeight, -invHeight));
         renderer->setPixelShaderConstant(getPSRegister(PSP_EDGEFILTERPARAMS), Vector4(filterParams.mThreshold,
             filterParams.mFilterStep, filterParams.mMaxFilter, filterParams.mMaxScale));
+        renderer->setViewport(mScreenRect);
         
         mPipeline->drawFullScreenQuad(*mCamera, mPipeline->getVertexShader("EdgeFilter"),
             mPipeline->getPixelShader("EdgeFilter"), false);

+ 3 - 0
Engine/Renderer/ForwardView.cpp

@@ -290,6 +290,7 @@ void View::renderBatchesForward()
         renderer->setStencilTest(false);
         renderer->setRenderTarget(0, mRenderTarget);
         renderer->setDepthStencil(mDepthStencil);
+        renderer->setViewport(mScreenRect);
         renderer->clear(CLEAR_COLOR | CLEAR_DEPTH | CLEAR_STENCIL, mZone->getFogColor());
         
         for (unsigned i = 0; i < mAmbientQueueSorted.size(); ++i)
@@ -359,6 +360,7 @@ void View::renderBatchesForward()
                 
                 renderer->setRenderTarget(0, mRenderTarget);
                 renderer->setDepthStencil(mDepthStencil);
+                renderer->setViewport(mScreenRect);
                 optimizeLightByScissor(queue.mLight);
                 
                 // If this is a split point or dir. light, mark the split volume to the stencil
@@ -387,6 +389,7 @@ void View::renderBatchesForward()
         renderer->setScissorTest(false);
         renderer->setStencilTest(false);
         renderer->setRenderTarget(0, mRenderTarget);
+        renderer->setViewport(mScreenRect);
         renderer->setDepthStencil(mDepthStencil);
         
         for (unsigned i = 0; i < mPostOpaqueQueueSorted.size(); ++i)

+ 3 - 6
Engine/Renderer/InstancedModel.cpp

@@ -557,15 +557,12 @@ void InstancedModel::setModel(Model* model)
     
     mModel = model;
     mOriginalGeometries.clear();
-    mOriginalMaterials.clear();
     
     // Copy the subgeometry & LOD level structure
     const std::vector<std::vector<SharedPtr<Geometry> > >& geometries = model->getGeometries();
+    mOriginalMaterials.resize(geometries.size());
     for (unsigned i = 0; i < geometries.size(); ++i)
-    {
         mOriginalGeometries.push_back(geometries[i]);
-        mOriginalMaterials.push_back(SharedPtr<Material>());
-    }
     
     // Set the bounding box
     setBoundingBox(model->getBoundingBox());
@@ -582,13 +579,13 @@ void InstancedModel::setMaterial(Material* material)
 
 bool InstancedModel::setMaterial(unsigned index, Material* material)
 {
-    if (index >= mMaterials.size())
+    if (index >= mOriginalMaterials.size())
     {
         LOGERROR("Illegal material index");
         return false;
     }
     
-    mMaterials[index] = material;
+    mOriginalMaterials[index] = material;
     markInstancesDirty();
     return true;
 }

+ 1 - 1
Engine/Renderer/Light.h

@@ -163,7 +163,7 @@ public:
     virtual void updateDistance(const FrameInfo& frame);
     //! Override model and view transforms for rendering
     virtual void overrideTransforms(unsigned batchIndex, Camera& camera, const Matrix4x3** model, const Matrix4x3** view);
-    //! Draw debug geometry
+    //! Add debug geometry to the debug renderer
     virtual void drawDebugGeometry(DebugRenderer* debug);
     
     //! Set light type

+ 93 - 150
Engine/Renderer/Octree.cpp

@@ -22,13 +22,12 @@
 //
 
 #include "Precompiled.h"
+#include "DebugRenderer.h"
 #include "Deserializer.h"
-#include "Log.h"
 #include "Profiler.h"
 #include "Octree.h"
 #include "OctreeQuery.h"
 #include "Serializer.h"
-#include "StringUtils.h"
 #include "XMLElement.h"
 
 #include <algorithm>
@@ -39,6 +38,8 @@
 #pragma warning(disable:4355)
 #endif
 
+static unsigned excludeFlags = 0;
+
 inline static bool compareRayQueryResults(const RayQueryResult& lhs, const RayQueryResult& rhs)
 {
     return lhs.mDistance < rhs.mDistance;
@@ -54,16 +55,8 @@ Octant::Octant(const BoundingBox& box, unsigned level, Octant* parent, Octree* r
     Vector3 halfSize = mWorldBoundingBox.getSize() * 0.5f;
     mCullingBox = BoundingBox(mWorldBoundingBox.mMin - halfSize, mWorldBoundingBox.mMax + halfSize);
     
-    for (unsigned x = 0; x < 2; ++x)
-    {
-        for (unsigned y = 0; y < 2; ++y)
-        {
-            for (unsigned z = 0; z < 2; ++z)
-            {
-                mChildren[x][y][z] = 0;
-            }
-        }
-    }
+    for (unsigned i = 0; i < NUM_OCTANTS; ++i)
+        mChildren[i] = 0;
 }
 
 Octant::~Octant()
@@ -71,55 +64,49 @@ Octant::~Octant()
     release();
 }
 
-Octant* Octant::getOrCreateChild(unsigned x, unsigned y, unsigned z)
+Octant* Octant::getOrCreateChild(unsigned index)
 {
-    if (mChildren[x][y][z])
-        return mChildren[x][y][z];
+    if (mChildren[index])
+        return mChildren[index];
     
     Vector3 newMin = mWorldBoundingBox.mMin;
     Vector3 newMax = mWorldBoundingBox.mMax;
     Vector3 oldCenter = mWorldBoundingBox.getCenter();
     
-    if (!x)
-        newMax.mX = oldCenter.mX;
-    else
+    if (index & 1)
         newMin.mX = oldCenter.mX;
-    
-    if (!y)
-        newMax.mY = oldCenter.mY;
     else
-        newMin.mY = oldCenter.mY;
+        newMax.mX = oldCenter.mX;
     
-    if (!z)
-        newMax.mZ = oldCenter.mZ;
+    if (index & 2)
+        newMin.mY = oldCenter.mY;
     else
+        newMax.mY = oldCenter.mY;
+    
+    if (index & 4)
         newMin.mZ = oldCenter.mZ;
+    else
+        newMax.mZ = oldCenter.mZ;
     
-    mChildren[x][y][z] = new Octant(BoundingBox(newMin, newMax), mLevel + 1, this, mRoot);
-    return mChildren[x][y][z];
+    mChildren[index] = new Octant(BoundingBox(newMin, newMax), mLevel + 1, this, mRoot);
+    return mChildren[index];
 }
 
-void Octant::deleteChild(unsigned x, unsigned y, unsigned z)
+void Octant::deleteChild(unsigned index)
 {
-    delete mChildren[x][y][z];
-    mChildren[x][y][z] = 0;
+    delete mChildren[index];
+    mChildren[index] = 0;
 }
 
 void Octant::deleteChild(Octant* octant)
 {
-    for (unsigned x = 0; x < 2; ++x)
+    for (unsigned i = 0; i < NUM_OCTANTS; ++i)
     {
-        for (unsigned y = 0; y < 2; ++y)
+        if (mChildren[i] == octant)
         {
-            for (unsigned z = 0; z < 2; ++z)
-            {
-                if (mChildren[x][y][z] == octant)
-                {
-                    delete octant;
-                    mChildren[x][y][z] = 0;
-                    break;
-                }
-            }
+            delete octant;
+            mChildren[i] = 0;
+            return;
         }
     }
 }
@@ -140,24 +127,10 @@ void Octant::insertNode(VolumeNode* node)
     
     Vector3 octantCenter = mWorldBoundingBox.getCenter();
     Vector3 nodeCenter = node->getWorldBoundingBox().getCenter();
-    unsigned x, y, z;
-    
-    if (nodeCenter.mX < octantCenter.mX)
-        x = 0;
-    else
-        x = 1;
-    
-    if (nodeCenter.mY < octantCenter.mY)
-        y = 0;
-    else
-        y = 1;
-    
-    if (nodeCenter.mZ < octantCenter.mZ)
-        z = 0;
-    else
-        z = 1;
-    
-    getOrCreateChild(x, y, z)->insertNode(node);
+    unsigned x = nodeCenter.mX < octantCenter.mX ? 0 : 1;
+    unsigned y = nodeCenter.mY < octantCenter.mY ? 0 : 2;
+    unsigned z = nodeCenter.mZ < octantCenter.mZ ? 0 : 4;
+    getOrCreateChild(x + y + z)->insertNode(node);
 }
 
 bool Octant::checkNodeSize(VolumeNode* node) const
@@ -169,40 +142,17 @@ bool Octant::checkNodeSize(VolumeNode* node) const
     Vector3 octantHalfSize = mWorldBoundingBox.getSize() * 0.5;
     Vector3 nodeSize = node->getWorldBoundingBox().getSize();
     
-    return (nodeSize.mX >= octantHalfSize.mX) ||(nodeSize.mY >= octantHalfSize.mY) || (nodeSize.mZ >= octantHalfSize.mZ);
-}
-
-void Octant::getNodes(OctreeQuery& query) const
-{
-    PROFILE(Octree_GetNodes);
-    
-    query.mResult.clear();
-    getNodesInternal(query, 0);
-}
-
-void Octant::getNodes(RayOctreeQuery& query) const
-{
-    PROFILE(Octree_Raycast);
-    
-    query.mResult.clear();
-    getNodesInternal(query);
-    std::sort(query.mResult.begin(), query.mResult.end(), compareRayQueryResults);
+    return (nodeSize.mX >= octantHalfSize.mX) || (nodeSize.mY >= octantHalfSize.mY) || (nodeSize.mZ >= octantHalfSize.mZ);
 }
 
 void Octant::resetRoot()
 {
     mRoot = 0;
     
-    for (unsigned x = 0; x < 2; ++x)
+    for (unsigned i = 0; i < NUM_OCTANTS; ++i)
     {
-        for (unsigned y = 0; y < 2; ++y)
-        {
-            for (unsigned z = 0; z < 2; ++z)
-            {
-                if (mChildren[x][y][z])
-                    mChildren[x][y][z]->resetRoot();
-            }
-        }
+        if (mChildren[i])
+            mChildren[i]->resetRoot();
     }
 }
 
@@ -228,7 +178,7 @@ void Octant::getNodesInternal(OctreeQuery& query, unsigned mask) const
         VolumeNode* node = *i;
         unsigned nodeFlags = node->getNodeFlags();
         
-        if ((!(nodeFlags & query.mIncludeFlags)) || (nodeFlags & query.mExcludeFlags))
+        if ((!(nodeFlags & query.mNodeFlags)) || (nodeFlags & excludeFlags))
             continue;
         if (!node->isVisible())
             continue;
@@ -241,22 +191,11 @@ void Octant::getNodesInternal(OctreeQuery& query, unsigned mask) const
             query.mResult.push_back(node);
     }
     
-    if (mChildren[0][0][0])
-        mChildren[0][0][0]->getNodesInternal(query, mask);
-    if (mChildren[0][0][1])
-        mChildren[0][0][1]->getNodesInternal(query, mask);
-    if (mChildren[0][1][0])
-        mChildren[0][1][0]->getNodesInternal(query, mask);
-    if (mChildren[0][1][1])
-        mChildren[0][1][1]->getNodesInternal(query, mask);
-    if (mChildren[1][0][0])
-        mChildren[1][0][0]->getNodesInternal(query, mask);
-    if (mChildren[1][0][1])
-        mChildren[1][0][1]->getNodesInternal(query, mask);
-    if (mChildren[1][1][0])
-        mChildren[1][1][0]->getNodesInternal(query, mask);
-    if (mChildren[1][1][1])
-        mChildren[1][1][1]->getNodesInternal(query, mask);
+    for (unsigned i = 0; i < NUM_OCTANTS; ++i)
+    {
+        if (mChildren[i])
+            mChildren[i]->getNodesInternal(query, mask);
+    }
 }
 
 void Octant::getNodesInternal(RayOctreeQuery& query) const
@@ -273,7 +212,7 @@ void Octant::getNodesInternal(RayOctreeQuery& query) const
         VolumeNode* node = *i;
         unsigned nodeFlags = node->getNodeFlags();
         
-        if ((!(nodeFlags & query.mIncludeFlags)) || (nodeFlags & query.mExcludeFlags))
+        if ((!(nodeFlags & query.mNodeFlags)) || (nodeFlags & excludeFlags))
             continue;
         if (!node->isVisible())
             continue;
@@ -288,22 +227,11 @@ void Octant::getNodesInternal(RayOctreeQuery& query) const
             node->processRayQuery(query, nodeDist);
     }
     
-    if (mChildren[0][0][0])
-        mChildren[0][0][0]->getNodesInternal(query);
-    if (mChildren[0][0][1])
-        mChildren[0][0][1]->getNodesInternal(query);
-    if (mChildren[0][1][0])
-        mChildren[0][1][0]->getNodesInternal(query);
-    if (mChildren[0][1][1])
-        mChildren[0][1][1]->getNodesInternal(query);
-    if (mChildren[1][0][0])
-        mChildren[1][0][0]->getNodesInternal(query);
-    if (mChildren[1][0][1])
-        mChildren[1][0][1]->getNodesInternal(query);
-    if (mChildren[1][1][0])
-        mChildren[1][1][0]->getNodesInternal(query);
-    if (mChildren[1][1][1])
-        mChildren[1][1][1]->getNodesInternal(query);
+    for (unsigned i = 0; i < NUM_OCTANTS; ++i)
+    {
+        if (mChildren[i])
+            mChildren[i]->getNodesInternal(query);
+    }
 }
 
 void Octant::release()
@@ -327,22 +255,14 @@ void Octant::release()
     mNodes.clear();
     mNumNodes = 0;
     
-    for (unsigned x = 0; x < 2; ++x)
-    {
-        for (unsigned y = 0; y < 2; ++y)
-        {
-            for (unsigned z = 0; z < 2; ++z)
-            {
-                delete mChildren[x][y][z];
-                mChildren[x][y][z] = 0;
-            }
-        }
-    }
+    for (unsigned i = 0; i < NUM_OCTANTS; ++i)
+        deleteChild(i);
 }
 
 Octree::Octree(const BoundingBox& box, unsigned numLevels, bool headless) :
     Octant(box, 0, 0, this),
-    mNumLevels(numLevels),
+    mNumLevels(max((int)numLevels, 1)),
+    mExcludeFlags(0),
     mHeadless(headless)
 {
 }
@@ -401,6 +321,7 @@ void Octree::update(float timeStep)
 
 void Octree::resize(const BoundingBox& box, unsigned numLevels)
 {
+    numLevels = max((int)numLevels, 1);
     if ((numLevels == mNumLevels) && (box.mMin == mWorldBoundingBox.mMin) && (box.mMax == mWorldBoundingBox.mMax))
         return;
     
@@ -412,28 +333,11 @@ void Octree::resize(const BoundingBox& box, unsigned numLevels)
     
     Vector3 halfSize = mWorldBoundingBox.getSize() * 0.5f;
     mCullingBox = BoundingBox(mWorldBoundingBox.mMin - halfSize, mWorldBoundingBox.mMax + halfSize);
-    
-    LOGINFO("Resized octree to " + toString(box.getSize()) + " with " + toString(numLevels) + " levels");
-}
-
-void Octree::markNodeForUpdate(VolumeNode* node)
-{
-    mNodeUpdates.insert(node);
-}
-
-void Octree::clearNodeUpdate(VolumeNode* node)
-{
-    mNodeUpdates.erase(node);
 }
 
-void Octree::markNodeForReinsertion(VolumeNode* node)
+void Octree::setExcludeFlags(unsigned nodeFlags)
 {
-    mNodeReinsertions.insert(node);
-}
-
-void Octree::clearNodeReinsertion(VolumeNode* node)
-{
-    mNodeReinsertions.erase(node);
+    mExcludeFlags = nodeFlags;
 }
 
 void Octree::updateOctree(const FrameInfo& frame)
@@ -493,3 +397,42 @@ void Octree::updateOctree(const FrameInfo& frame)
     mNodeUpdates.clear();
     mNodeReinsertions.clear();
 }
+
+void Octree::getNodes(OctreeQuery& query) const
+{
+    PROFILE(Octree_GetNodes);
+    
+    excludeFlags = mExcludeFlags;
+    query.mResult.clear();
+    getNodesInternal(query, 0);
+}
+
+void Octree::getNodes(RayOctreeQuery& query) const
+{
+    PROFILE(Octree_Raycast);
+    
+    excludeFlags = mExcludeFlags;
+    query.mResult.clear();
+    getNodesInternal(query);
+    std::sort(query.mResult.begin(), query.mResult.end(), compareRayQueryResults);
+}
+
+void Octree::markNodeForUpdate(VolumeNode* node)
+{
+    mNodeUpdates.insert(node);
+}
+
+void Octree::clearNodeUpdate(VolumeNode* node)
+{
+    mNodeUpdates.erase(node);
+}
+
+void Octree::markNodeForReinsertion(VolumeNode* node)
+{
+    mNodeReinsertions.insert(node);
+}
+
+void Octree::clearNodeReinsertion(VolumeNode* node)
+{
+    mNodeReinsertions.erase(node);
+}

+ 24 - 15
Engine/Renderer/Octree.h

@@ -33,6 +33,8 @@ class Octree;
 class OctreeQuery;
 class RayOctreeQuery;
 
+static const unsigned NUM_OCTANTS = 8;
+
 //! Octree octant
 class Octant
 {
@@ -43,9 +45,9 @@ public:
     virtual ~Octant();
     
     //! Return or create a child octant
-    Octant* getOrCreateChild(unsigned x, unsigned y, unsigned z);
+    Octant* getOrCreateChild(unsigned index);
     //! Delete child octant
-    void deleteChild(unsigned x, unsigned y, unsigned z);
+    void deleteChild(unsigned index);
     //! Delete child octant by pointer
     void deleteChild(Octant* octant);
     //! Insert scene node by checking for fit recursively
@@ -76,10 +78,6 @@ public:
         }
     }
     
-    //! Return scene nodes by a query
-    void getNodes(OctreeQuery& query) const;
-    //! Return scene nodes by a ray query
-    void getNodes(RayOctreeQuery& query) const;
     //! Return world bounding box
     const BoundingBox& getWorldBoundingBox() const { return mWorldBoundingBox; }
     //! Return bounding box used for fitting nodes
@@ -129,7 +127,7 @@ protected:
     //! Parent octant
     Octant* mParent;
     //! Child octants
-    Octant* mChildren[2][2][2];
+    Octant* mChildren[NUM_OCTANTS];
     //! Octree root
     Octree* mRoot;
     //! Scene nodes
@@ -164,8 +162,24 @@ public:
     //! Scene extension update. Update octree when in headless mode
     virtual void update(float timeStep);
     
-    //! Resize octree. Should be empty at this point; any nodes will be removed
+    //! Resize octree. If octree is not empty, scene nodes will be temporarily moved to the root
     void resize(const BoundingBox& box, unsigned numLevels);
+    //! Set global exclude node flags for queries. For debugging/editing purposes only, will not be saved
+    void setExcludeFlags(unsigned nodeFlags);
+    //! Update and reinsert scene nodes. Called by Pipeline, or by update() if in headless mode
+    void updateOctree(const FrameInfo& frame);
+    
+    //! Return scene nodes by a query
+    void getNodes(OctreeQuery& query) const;
+    //! Return scene nodes by a ray query
+    void getNodes(RayOctreeQuery& query) const;
+    //! Return subdivision levels
+    unsigned getNumLevels() const { return mNumLevels; }
+    //! Return global exclude node flags
+    unsigned getExcludeFlags() const { return mExcludeFlags; }
+    //! Return whether is in headless mode
+    bool isHeadless() const { return mHeadless; }
+    
     //! Mark scene node as requiring an update
     void markNodeForUpdate(VolumeNode* node);
     //! Remove scene node from update list
@@ -174,13 +188,6 @@ public:
     void markNodeForReinsertion(VolumeNode* node);
     //! Remove scene node from reinsertion list
     void clearNodeReinsertion(VolumeNode* node);
-    //! Update and reinsert scene nodes
-    void updateOctree(const FrameInfo& frame);
-    
-    //! Return subdivision levels
-    unsigned getNumLevels() const { return mNumLevels; }
-    //! Return whether is in headless mode
-    bool isHeadless() const { return mHeadless; }
     
 private:
     //! Set of scene nodes that require update
@@ -189,6 +196,8 @@ private:
     std::set<VolumeNode*> mNodeReinsertions;
     //! Subdivision level
     unsigned mNumLevels;
+    //! Global query exclude flags
+    unsigned mExcludeFlags;
     //! Headless mode flag
     bool mHeadless;
 };

+ 22 - 23
Engine/Renderer/OctreeQuery.h

@@ -39,10 +39,9 @@ class OctreeQuery
 {
 public:
     //! Construct with result vector, include/exclude flags and whether to get only occluders or shadowcasters
-    OctreeQuery(std::vector<VolumeNode*>& result, unsigned includeFlags, unsigned excludeFlags, bool occludersOnly, bool shadowCastersOnly) :
+    OctreeQuery(std::vector<VolumeNode*>& result, unsigned nodeFlags, bool occludersOnly, bool shadowCastersOnly) :
         mResult(result),
-        mIncludeFlags(includeFlags),
-        mExcludeFlags(excludeFlags),
+        mNodeFlags(nodeFlags),
         mOccludersOnly(occludersOnly),
         mShadowCastersOnly(shadowCastersOnly)
     {
@@ -61,9 +60,7 @@ public:
     //! Result vector reference
     std::vector<VolumeNode*>& mResult;
     //! Scene node flags to include
-    unsigned mIncludeFlags;
-    //! Scene node flags to exclude
-    unsigned mExcludeFlags;
+    unsigned mNodeFlags;
     //! Get occluders only flag
     bool mOccludersOnly;
     //! Get shadowcasters only flag
@@ -75,8 +72,9 @@ class PointOctreeQuery : public OctreeQuery
 {
 public:
     //! Construct with point and query parameters
-    PointOctreeQuery(const Vector3& point, std::vector<VolumeNode*>& result, unsigned includeFlags, unsigned excludeFlags = 0, bool occludersOnly = false, bool shadowCastersOnly = false) :
-        OctreeQuery(result, includeFlags, excludeFlags, occludersOnly, shadowCastersOnly),
+    PointOctreeQuery(std::vector<VolumeNode*>& result, const Vector3& point, unsigned nodeFlags, bool occludersOnly = false,
+        bool shadowCastersOnly = false) :
+        OctreeQuery(result, nodeFlags, occludersOnly, shadowCastersOnly),
         mPoint(point)
     {
     }
@@ -95,8 +93,9 @@ class SphereOctreeQuery : public OctreeQuery
 {
 public:
     //! Construct with sphere and query parameters
-    SphereOctreeQuery(const Sphere& sphere, std::vector<VolumeNode*>& result, unsigned includeFlags, unsigned excludeFlags = 0, bool occludersOnly = false, bool shadowCastersOnly = false) :
-        OctreeQuery(result, includeFlags, excludeFlags, occludersOnly, shadowCastersOnly),
+    SphereOctreeQuery(std::vector<VolumeNode*>& result, const Sphere& sphere, unsigned nodeFlags, bool occludersOnly = false,
+        bool shadowCastersOnly = false) :
+        OctreeQuery(result, nodeFlags, occludersOnly, shadowCastersOnly),
         mSphere(sphere)
     {
     }
@@ -115,8 +114,9 @@ class BoxOctreeQuery : public OctreeQuery
 {
 public:
     //! Construct with bounding box and query parameters
-    BoxOctreeQuery(const BoundingBox& box, std::vector<VolumeNode*>& result, unsigned includeFlags, unsigned excludeFlags = 0, bool occludersOnly = false, bool shadowCastersOnly = false) :
-        OctreeQuery(result, includeFlags, excludeFlags, occludersOnly, shadowCastersOnly),
+    BoxOctreeQuery(std::vector<VolumeNode*>& result, const BoundingBox& box, unsigned nodeFlags, bool occludersOnly = false,
+        bool shadowCastersOnly = false) :
+        OctreeQuery(result, nodeFlags, occludersOnly, shadowCastersOnly),
         mBox(box)
     {
     }
@@ -135,8 +135,9 @@ class FrustumOctreeQuery : public OctreeQuery
 {
 public:
     //! Construct with frustum and query parameters
-    FrustumOctreeQuery(const Frustum& frustum, std::vector<VolumeNode*>& result, unsigned includeFlags, unsigned excludeFlags = 0, bool occludersOnly = false, bool shadowCastersOnly = false) :
-        OctreeQuery(result, includeFlags, excludeFlags, occludersOnly, shadowCastersOnly),
+    FrustumOctreeQuery(std::vector<VolumeNode*>& result, const Frustum& frustum, unsigned nodeFlags, bool occludersOnly = false,
+        bool shadowCastersOnly = false) :
+        OctreeQuery(result, nodeFlags, occludersOnly, shadowCastersOnly),
         mFrustum(frustum)
     {
     }
@@ -155,9 +156,9 @@ class OccludedFrustumOctreeQuery : public OctreeQuery
 {
 public:
     //! Construct with frustum, occlusion buffer pointer and query parameters
-    OccludedFrustumOctreeQuery(const Frustum& frustum, OcclusionBuffer* buffer, std::vector<VolumeNode*>& result,
-            unsigned includeFlags, unsigned excludeFlags = 0, bool occludersOnly = false, bool shadowCastersOnly = false) :
-        OctreeQuery(result, includeFlags, excludeFlags, occludersOnly, shadowCastersOnly),
+    OccludedFrustumOctreeQuery(std::vector<VolumeNode*>& result, const Frustum& frustum, OcclusionBuffer* buffer,
+            unsigned nodeFlags, bool occludersOnly = false, bool shadowCastersOnly = false) :
+        OctreeQuery(result, nodeFlags, occludersOnly, shadowCastersOnly),
         mFrustum(frustum),
         mBuffer(buffer)
     {
@@ -205,11 +206,11 @@ class RayOctreeQuery
 {
 public:
     //! Construct with ray and query parameters
-    RayOctreeQuery(const Ray& ray, std::vector<RayQueryResult>& result, unsigned includeFlags, unsigned excludeFlags = 0, bool occludersOnly = false, bool shadowCastersOnly = false, float maxDistance = M_INFINITY, RayQueryLevel level = RAY_TRIANGLE) :
+    RayOctreeQuery(std::vector<RayQueryResult>& result, const Ray& ray, unsigned nodeFlags, bool occludersOnly = false,
+        bool shadowCastersOnly = false, float maxDistance = M_INFINITY, RayQueryLevel level = RAY_TRIANGLE) :
         mRay(ray),
         mResult(result),
-        mIncludeFlags(includeFlags),
-        mExcludeFlags(excludeFlags),
+        mNodeFlags(nodeFlags),
         mOccludersOnly(occludersOnly),
         mShadowCastersOnly(shadowCastersOnly),
         mMaxDistance(maxDistance),
@@ -222,9 +223,7 @@ public:
     //! Result vector reference
     std::vector<RayQueryResult>& mResult;
     //! Scene node flags to include
-    unsigned mIncludeFlags;
-    //! Scene node flags to exclude
-    unsigned mExcludeFlags;
+    unsigned mNodeFlags;
     //! Get occluders only flag
     bool mOccludersOnly;
     //! Get shadowcasters only flag

+ 211 - 82
Engine/Renderer/Pipeline.cpp

@@ -39,6 +39,7 @@
 #include "RendererEvents.h"
 #include "RendererImpl.h"
 #include "ResourceCache.h"
+#include "Scene.h"
 #include "StringUtils.h"
 #include "Texture2D.h"
 #include "TextureCube.h"
@@ -315,6 +316,8 @@ Pipeline::Pipeline(Renderer* renderer, ResourceCache* cache) :
     if (!createShadowMaps())
         mDrawShadows = false;
     
+    mViewports.resize(1);
+    
     resetViews();
 }
 
@@ -323,6 +326,48 @@ Pipeline::~Pipeline()
     LOGINFO("Rendering pipeline shut down");
 }
 
+void Pipeline::setNumViewports(unsigned num)
+{
+    mViewports.resize(num);
+}
+
+void Pipeline::setViewport(unsigned index, Scene* scene, Camera* camera, const IntRect& screenRect)
+{
+    if (index >= mViewports.size())
+    {
+        LOGERROR("Illegal viewport index");
+        return;
+    }
+    
+    Viewport& viewport = mViewports[index];
+    viewport.mScene = scene;
+    viewport.mCamera = camera;
+    viewport.mScreenRect = screenRect;
+}
+
+void Pipeline::setViewportCamera(unsigned index, Camera* camera)
+{
+    if (index >= mViewports.size())
+    {
+        LOGERROR("Illegal viewport index");
+        return;
+    }
+    
+    mViewports[index].mCamera = camera;
+}
+
+void Pipeline::setViewportScreenRect(unsigned index, const IntRect& screenRect)
+{
+    if (index >= mViewports.size())
+    {
+        LOGERROR("Illegal viewport index");
+        return;
+    }
+    
+    mViewports[index].mScreenRect = screenRect;
+}
+
+
 void Pipeline::setSpecularLighting(bool enable)
 {
     mSpecularLighting = enable;
@@ -413,91 +458,75 @@ void Pipeline::setEdgeFilter(const EdgeFilterParameters& parameters)
     mEdgeFilter = parameters;
 }
 
-bool Pipeline::update(float timeStep, Octree* octree, Camera* camera)
+void Pipeline::drawDebugGeometry()
 {
-    PROFILE(Pipeline_Update);
+    PROFILE(Pipeline_DrawDebugGeometry);
     
-    // If device lost, do not perform update. This is because any dynamic vertex/index buffer updates happen already here,
-    // and if the device is lost, the updates queue up, causing memory use to rise constantly
-    if ((mRenderer->isDeviceLost()) || (!camera) || (!octree))
+    static std::set<GeometryNode*> processedGeometries;
+    static std::set<Light*> processedLights;
+    processedGeometries.clear();
+    processedLights.clear();
+    
+    for (unsigned i = 0; i < mNumViews; ++i)
     {
-        mNumViews = 0;
-        return false;
+        // Make sure it's a main view, and process each node only once
+        View* view = mViews[i];
+        if ((!view) || (view->getRenderTarget()))
+            continue;
+        Octree* octree = view->getOctree();
+        if (!octree)
+            continue;
+        Scene* scene = octree->getScene();
+        if (!scene)
+            continue;
+        DebugRenderer* debug = scene->getExtension<DebugRenderer>();
+        if (!debug)
+            continue;
+        
+        const std::vector<GeometryNode*>& geometries = view->getGeometries();
+        const std::vector<Light*>& lights = view->getLights();
+        
+        for (unsigned i = 0; i < geometries.size(); ++i)
+        {
+            if (processedGeometries.find(geometries[i]) == processedGeometries.end())
+            {
+                geometries[i]->drawDebugGeometry(debug);
+                processedGeometries.insert(geometries[i]);
+            }
+        }
+        for (unsigned i = 0; i < lights.size(); ++i)
+        {
+            if (processedLights.find(lights[i]) == processedLights.end())
+            {
+                lights[i]->drawDebugGeometry(debug);
+                processedLights.insert(lights[i]);
+            }
+        }
     }
-    
-    // Advance frame number & time, set up the frameinfo structure, and reset stats
-    beginFrame(timeStep);
-    
-    // Reload shaders if needed
-    if (mShadersDirty)
-        loadShaders();
-    
-    // Update octree. Perform early update for nodes which need that, and reinsert moved nodes
-    mFrame.mCamera = camera;
-    octree->updateOctree(mFrame);
-    
-    // Add the main view
-    addView(octree, camera, 0);
-    
-    // Get light and geometry nodes for the main view
-    mViews[0]->update(mFrame);
-    
-    // Process any auxiliary views that were found during the main view processing
-    for (unsigned i = 1; i < mNumViews; ++i)
-        mViews[i]->update(mFrame);
-    
-    return true;
 }
 
-bool Pipeline::render()
+Scene* Pipeline::getViewportScene(unsigned index) const
 {
-    PROFILE(Pipeline_Render);
-    
-    mRenderer->setDefaultTextureFilterMode(mTextureFilterMode);
-    mRenderer->setTextureAnisotropy(mTextureAnisotropy);
-    
-    // If no views, just clear the screen
-    if (!mNumViews)
-    {
-        mRenderer->setAlphaTest(false);
-        mRenderer->setBlendMode(BLEND_REPLACE);
-        mRenderer->setColorWrite(true);
-        mRenderer->setDepthWrite(true);
-        mRenderer->setFillMode(FILL_SOLID);
-        mRenderer->setScissorTest(false);
-        mRenderer->setStencilTest(false);
-        mRenderer->clear(CLEAR_COLOR | CLEAR_DEPTH | CLEAR_STENCIL);
-        return false;
-    }
-    
-    // Render views from last to first (main view is rendered last)
-    for (unsigned i = mNumViews - 1; i < mNumViews; --i)
-        mViews[i]->render();
-    
-    // Disable scissor/stencil tests if left on by lights, and reset stream frequencies
-    mRenderer->setScissorTest(false);
-    mRenderer->setStencilTest(false);
-    mRenderer->resetStreamFrequencies();
-    
-    return true;
+    if (index >= mViewports.size())
+        return 0;
+    else
+        return mViewports[index].mScene;
 }
 
-void Pipeline::drawDebugGeometry(DebugRenderer* debug)
+Camera* Pipeline::getViewportCamera(unsigned index) const
 {
-    PROFILE(Pipeline_DrawDebugGeometry);
-    
-    if (!mNumViews)
-        return;
-    
-    // Only use the main view
-    const std::vector<GeometryNode*>& geometries = mViews[0]->getGeometries();
-    const std::vector<Light*>& lights = mViews[0]->getLights();
-    
-    for (unsigned i = 0; i < geometries.size(); ++i)
-        geometries[i]->drawDebugGeometry(debug);
-    
-    for (unsigned i = 0; i < lights.size(); ++i)
-        lights[i]->drawDebugGeometry(debug);
+    if (index >= mViewports.size())
+        return 0;
+    else
+        return mViewports[index].mCamera;
+}
+
+IntRect Pipeline::getViewportScreenRect(unsigned index) const
+{
+    if (index >= mViewports.size())
+        return IntRect::sZero;
+    else
+        return mViewports[index].mScreenRect;
 }
 
 VertexShader* Pipeline::getVertexShader(const std::string& name, bool checkExists) const
@@ -607,6 +636,101 @@ const OcclusionBuffer* Pipeline::getOcclusionBuffer(float aspectRatio, bool half
         return 0;
 }
 
+bool Pipeline::update(float timeStep)
+{
+    PROFILE(Pipeline_Update);
+    
+    // If device lost, do not perform update. This is because any dynamic vertex/index buffer updates happen already here,
+    // and if the device is lost, the updates queue up, causing memory use to rise constantly
+    if (mRenderer->isDeviceLost())
+    {
+        mNumViews = 0;
+        return false;
+    }
+    
+    // Advance frame number & time, set up the frameinfo structure, and reset views & stats
+    beginFrame(timeStep);
+    
+    // Reload shaders if needed
+    if (mShadersDirty)
+        loadShaders();
+    
+    // Process all viewports. Use reverse order, because during rendering the order will be reversed again to handle auxiliary
+    // view dependencies correctly
+    for (unsigned i = mViewports.size() - 1; i < mViewports.size(); --i)
+    {
+        Viewport& viewport = mViewports[i];
+        
+        // Check for valid scene, camera and octree, and that the scene is not asynchronously loading and thus incomplete
+        Scene* scene = viewport.mScene;
+        Camera* camera = viewport.mCamera;
+        if ((!scene) || (!camera) || (scene->isAsyncLoading()))
+            continue;
+        Octree* octree = scene->getExtension<Octree>();
+        if (!octree)
+            continue;
+        
+        // If viewport has zero size defined, follow the window size
+        IntRect screenRect = viewport.mScreenRect;
+        if (screenRect == IntRect::sZero)
+            screenRect = IntRect(0, 0, mRenderer->getWidth(), mRenderer->getHeight());
+        
+        mFrame.mCamera = camera;
+        
+        // Update octree. Perform early update for nodes which need that, and reinsert moved nodes
+        // However, if the same scene is viewed from multiple cameras, update the octree only once
+        if (mUpdatedOctrees.find(octree) == mUpdatedOctrees.end())
+        {
+            octree->updateOctree(mFrame);
+            mUpdatedOctrees.insert(octree);
+        }
+        
+        // Add and process this viewport's main view
+        unsigned mainView = mNumViews;
+        addView(octree, camera, 0, screenRect);
+        mViews[mainView]->update(mFrame);
+        
+        // Process any auxiliary views that were found during the main view processing
+        for (unsigned i = mainView + 1; i < mNumViews; ++i)
+            mViews[i]->update(mFrame);
+    }
+    
+    return true;
+}
+
+bool Pipeline::render()
+{
+    PROFILE(Pipeline_Render);
+    
+    mRenderer->setDefaultTextureFilterMode(mTextureFilterMode);
+    mRenderer->setTextureAnisotropy(mTextureAnisotropy);
+    
+    // If no views, just clear the screen
+    if (!mNumViews)
+    {
+        mRenderer->setAlphaTest(false);
+        mRenderer->setBlendMode(BLEND_REPLACE);
+        mRenderer->setColorWrite(true);
+        mRenderer->setDepthWrite(true);
+        mRenderer->setFillMode(FILL_SOLID);
+        mRenderer->setScissorTest(false);
+        mRenderer->setStencilTest(false);
+        mRenderer->clear(CLEAR_COLOR | CLEAR_DEPTH | CLEAR_STENCIL);
+        return false;
+    }
+    
+    // Render views from last to first (each main view is rendered after the auxiliary views it depends on)
+    for (unsigned i = mNumViews - 1; i < mNumViews; --i)
+        mViews[i]->render();
+    
+    // Disable scissor/stencil tests if left on by lights, and reset stream frequencies
+    mRenderer->setScissorTest(false);
+    mRenderer->setStencilTest(false);
+    mRenderer->resetStreamFrequencies();
+    
+    return true;
+}
+
 void Pipeline::beginFrame(float timeStep)
 {
     mElapsedTime += timeStep;
@@ -622,6 +746,8 @@ void Pipeline::beginFrame(float timeStep)
     
     mNumViews = 0;
     mNumSplitLights = 0;
+    
+    mUpdatedOctrees.clear();
 }
 
 void Pipeline::resetViews()
@@ -630,14 +756,14 @@ void Pipeline::resetViews()
     mNumViews = 0;
 }
 
-void Pipeline::addView(Octree* octree, Camera* camera, RenderSurface* renderTarget)
+void Pipeline::addView(Octree* octree, Camera* camera, RenderSurface* renderTarget, const IntRect& screenRect)
 {
     if (mViews.size() <= mNumViews)
         mViews.resize(mNumViews + 1);
     if (!mViews[mNumViews])
         mViews[mNumViews] = new View(this);
     
-    mViews[mNumViews]->define(octree, camera, renderTarget);
+    mViews[mNumViews]->define(octree, camera, renderTarget, screenRect);
     ++mNumViews;
 }
 
@@ -1017,7 +1143,8 @@ void Pipeline::loadMaterialPassShaders(MaterialTechnique* technique, PassType pa
 
 void Pipeline::releaseMaterialShaders()
 {
-    std::vector<Material*> materials = mCache->getResources<Material>();
+    std::vector<Material*> materials;
+    mCache->getResources<Material>(materials);
     
     for (unsigned i = 0; i < materials.size(); ++i)
     {
@@ -1028,13 +1155,15 @@ void Pipeline::releaseMaterialShaders()
 
 void Pipeline::reloadTextures()
 {
-    std::vector<Texture2D*> textures = mCache->getResources<Texture2D>();
-    std::vector<TextureCube*> cubeTextures = mCache->getResources<TextureCube>();
+    std::vector<Resource*> textures;
+    
+    mCache->getResources(textures, Texture2D::getTypeStatic());
+    for (unsigned i = 0; i < textures.size(); ++i)
+        mCache->reloadResource(textures[i]);
     
+    mCache->getResources(textures, TextureCube::getTypeStatic());
     for (unsigned i = 0; i < textures.size(); ++i)
         mCache->reloadResource(textures[i]);
-    for (unsigned i = 0; i < cubeTextures.size(); ++i)
-        mCache->reloadResource(cubeTextures[i]);
 }
 
 void Pipeline::createGeometries()

+ 47 - 8
Engine/Renderer/Pipeline.h

@@ -34,6 +34,7 @@
 #include "Zone.h"
 
 #include <map>
+#include <set>
 #include <string>
 
 class DebugRenderer;
@@ -47,6 +48,7 @@ class Octree;
 class Renderer;
 class RenderSurface;
 class ResourceCache;
+class Scene;
 class Skeleton;
 class OcclusionBuffer;
 class Texture;
@@ -88,6 +90,23 @@ struct EdgeFilterParameters
     float mMaxScale;
 };
 
+//! Viewport definition
+struct Viewport
+{
+    //! Construct
+    Viewport() :
+        mScreenRect(IntRect::sZero)
+    {
+    }
+    
+    //! Scene pointer
+    WeakPtr<Scene> mScene;
+    //! Camera pointer
+    WeakPtr<Camera> mCamera;
+    //! Viewport screen rectangle
+    IntRect mScreenRect;
+};
+
 //! High-level rendering pipeline
 class Pipeline : public RefCounted, public EventListener
 {
@@ -99,13 +118,14 @@ public:
     //! Destruct
     virtual ~Pipeline();
     
-    //! Update for rendering
-    bool update(float timeStep, Octree* octree, Camera* camera);
-    //! Render
-    bool render();
-    //! Render debug geometry
-    void drawDebugGeometry(DebugRenderer* debug);
-    
+    //! Set number of viewports to render
+    void setNumViewports(unsigned num);
+    //! Set a viewport
+    void setViewport(unsigned index, Scene* scene, Camera* camera, const IntRect& screenRect = IntRect::sZero);
+    //! Set a viewport's camera only
+    void setViewportCamera(unsigned index, Camera* camera);
+    //! Set a viewport's screen rect only
+    void setViewportScreenRect(unsigned index, const IntRect& screenRect);
     //! Set specular lighting on/off
     void setSpecularLighting(bool enable);
     //! Set shadows on/off
@@ -132,7 +152,17 @@ public:
     void setOccluderSizeThreshold(float screenSize);
     //! Set deferred rendering edge filter parameters. Only has effect if nonzero multisample level is set in Renderer::setMode().
     void setEdgeFilter(const EdgeFilterParameters& parameters);
+    //! Add debug geometry to the debug renderer(s). Call during EVENT_POSTRENDER to get most accurate results
+    void drawDebugGeometry();
     
+    //! Return number of viewports
+    unsigned getNumViewports() const { return mViewports.size(); }
+    //! Return viewport scene
+    Scene* getViewportScene(unsigned index) const;
+    //! Return viewport camera
+    Camera* getViewportCamera(unsigned index) const;
+    //! Return viewport screen rectangle
+    IntRect getViewportScreenRect(unsigned index) const;
     //! Return current frame number
     unsigned getFrameNumber() const { return mFrameNumber; }
     //! Return elapsed time
@@ -190,13 +220,18 @@ public:
     //! Return the renderer subsystem
     Renderer* getRenderer() const { return mRenderer; }
     
+    //! Update for rendering. Called by Engine
+    bool update(float timeStep);
+    //! Render. Called by Engine
+    bool render();
+    
 private:
     //! Begin new frame
     void beginFrame(float timeStep);
     //! Clear views from previous frame
     void resetViews();
     //! Add a view
-    void addView(Octree* octree, Camera* camera, RenderSurface* renderTarget);
+    void addView(Octree* octree, Camera* camera, RenderSurface* renderTarget, const IntRect& screenRect);
     //! Return an occlusion buffer for use
     OcclusionBuffer* getOrCreateOcclusionBuffer(Camera& camera, int maxOccluderTriangles, bool halfResolution = false);
     //! Return a material technique for a scene node, considering material LOD
@@ -326,10 +361,14 @@ private:
     std::string mShaderPath;
     //! Light shader base name (deferred and prepass have different light shaders)
     std::string mLightShaderName;
+    //! Viewports
+    std::vector<Viewport> mViewports;
     //! Views
     std::vector<SharedPtr<View> > mViews;
     //! Frame info for rendering
     FrameInfo mFrame;
+    //! Octrees that have been updated during the frame
+    std::set<Octree*> mUpdatedOctrees;
 };
 
 #endif // RENDERER_PIPELINE_H

+ 1 - 0
Engine/Renderer/Renderer.cpp

@@ -1113,6 +1113,7 @@ void Renderer::resetRenderTargets()
     for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
         setRenderTarget(i, (RenderSurface*)0);
     setDepthStencil((RenderSurface*)0);
+    setViewport(IntRect(0, 0, mWidth, mHeight));
 }
 
 void Renderer::resetRenderTarget(unsigned index)

+ 63 - 46
Engine/Renderer/View.cpp

@@ -35,6 +35,7 @@
 #include "PixelShader.h"
 #include "Profiler.h"
 #include "Renderer.h"
+#include "Scene.h"
 #include "Texture2D.h"
 #include "TextureCube.h"
 #include "VertexShader.h"
@@ -47,6 +48,8 @@ Light* View::sSplitLights[MAX_LIGHT_SPLITS];
 std::vector<GeometryNode*> View::sLitGeometries[MAX_LIGHT_SPLITS];
 std::vector<GeometryNode*> View::sShadowCasters[MAX_LIGHT_SPLITS];
 
+static std::vector<VolumeNode*> tempNodes;
+
 static bool compareNodes(const VolumeNode* lhs, const VolumeNode* rhs)
 {
     return lhs->getSortValue() < rhs->getSortValue();
@@ -68,7 +71,7 @@ View::~View()
 {
 }
 
-void View::define(Octree* octree, Camera* camera, RenderSurface* renderTarget)
+void View::define(Octree* octree, Camera* camera, RenderSurface* renderTarget, IntRect screenRect)
 {
     if ((!octree) || (!camera))
     {
@@ -104,17 +107,14 @@ void View::define(Octree* octree, Camera* camera, RenderSurface* renderTarget)
         mDepthStencil = 0;
     mZone = &sDefaultZone;
     
-    // If there is a specified render texture, use its dimensions, else the main view size
-    if (renderTarget)
-    {
-        mWidth = renderTarget->getWidth();
-        mHeight = renderTarget->getHeight();
-    }
-    else
-    {
-        mWidth = renderer->getWidth();
-        mHeight = renderer->getHeight();
-    }
+    // Validate the rect and calculate size
+    screenRect.mLeft = clamp(screenRect.mLeft, 0, renderer->getWidth() - 1);
+    screenRect.mTop = clamp(screenRect.mTop, 0, renderer->getHeight() - 1);
+    screenRect.mRight = clamp(screenRect.mRight, screenRect.mLeft + 1, renderer->getWidth());
+    screenRect.mBottom = clamp(screenRect.mBottom, screenRect.mTop + 1, renderer->getHeight());
+    mScreenRect = screenRect;
+    mWidth = screenRect.mRight - screenRect.mLeft;
+    mHeight = screenRect.mBottom - screenRect.mTop;
     
     // Set possible quality overrides from the camera
     mDrawShadows = mPipeline->getDrawShadows() && (camera->getDrawShadowsOverride());
@@ -145,10 +145,16 @@ void View::update(const FrameInfo& frame)
     if ((!mCamera) || (!mOctree))
         return;
     
+    PROFILE(Pipeline_UpdateView);
+    
     mFrame.mCamera = mCamera;
     mFrame.mTimeStep = frame.mTimeStep;
     mFrame.mFrameNumber = frame.mFrameNumber;
     
+    // Set automatic aspect ratio if required
+    if (mCamera->getAutoAspectRatio())
+        mCamera->setAspectRatio((float)(mScreenRect.mRight - mScreenRect.mLeft) / (float)(mScreenRect.mBottom - mScreenRect.mTop));
+    
     mCamera->markInView(mFrame.mFrameNumber);
     getNodes();
     
@@ -165,6 +171,11 @@ void View::render()
     
     Renderer* renderer = mPipeline->getRenderer();
     
+    // It is possible, though not recommended, that the same camera is used for multiple main views. Set automatic aspect ratio
+    // again to ensure the correct projection will be used
+    if (mCamera->getAutoAspectRatio())
+        mCamera->setAspectRatio((float)(mScreenRect.mRight - mScreenRect.mLeft) / (float)(mScreenRect.mBottom - mScreenRect.mTop));
+    
     // Set the "view texture" to ensure the rendertarget will not be bound as a texture during G-buffer rendering
     if (mRenderTarget)
         renderer->setViewTexture(mRenderTarget->getParentTexture());
@@ -201,6 +212,18 @@ void View::render()
     else
         renderBatchesForward();
     
+    // If this is a main view, draw the associated debug geometry
+    if (!mRenderTarget)
+    {
+        Scene* scene = mOctree->getScene();
+        if (scene)
+        {
+            DebugRenderer* debug = mOctree->getScene()->getExtension<DebugRenderer>();
+            if (debug)
+                debug->render(mCamera);
+        }
+    }
+    
     // "Forget" the camera, octree and zone after rendering
     mCamera = 0;
     mOctree = 0;
@@ -216,7 +239,7 @@ void View::getNodes()
     
     // Get zones & find the zone camera is in
     static std::vector<Zone*> zones;
-    PointOctreeQuery query(mCamera->getWorldPosition(), reinterpret_cast<std::vector<VolumeNode*>& >(zones), NODE_ZONE);
+    PointOctreeQuery query(reinterpret_cast<std::vector<VolumeNode*>& >(zones), mCamera->getWorldPosition(), NODE_ZONE);
     mOctree->getNodes(query);
     
     int highestZonePriority = M_MIN_INT;
@@ -237,8 +260,8 @@ void View::getNodes()
     
     if (mMaxOccluderTriangles > 0)
     {
-        FrustumOctreeQuery query(mCamera->getFrustum(), reinterpret_cast<std::vector<VolumeNode*>& >(mOccluders),
-            NODE_GEOMETRY, 0, true, false);
+        FrustumOctreeQuery query(reinterpret_cast<std::vector<VolumeNode*>& >(mOccluders), mCamera->getFrustum(),
+            NODE_GEOMETRY, true, false);
         
         mOctree->getNodes(query);
         updateOccluders(mOccluders, *mCamera);
@@ -253,18 +276,16 @@ void View::getNodes()
         }
     }
     
-    static std::vector<VolumeNode*> tempNodes;
-    
     if (!useOcclusion)
     {
         // Get geometries & lights without occlusion
-        FrustumOctreeQuery query(mCamera->getFrustum(), tempNodes, NODE_GEOMETRY | NODE_LIGHT);
+        FrustumOctreeQuery query(tempNodes, mCamera->getFrustum(), NODE_GEOMETRY | NODE_LIGHT);
         mOctree->getNodes(query);
     }
     else
     {
         // Get geometries & lights using occlusion
-        OccludedFrustumOctreeQuery query(mCamera->getFrustum(), buffer, tempNodes, NODE_GEOMETRY | NODE_LIGHT);
+        OccludedFrustumOctreeQuery query(tempNodes, mCamera->getFrustum(), buffer, NODE_GEOMETRY | NODE_LIGHT);
         mOctree->getNodes(query);
     }
     
@@ -462,8 +483,8 @@ unsigned View::processLight(Light* light)
         Camera& shadowCamera = light->getShadowCamera();
         
         // Get occluders, which must be shadow-casting themselves
-        FrustumOctreeQuery query(shadowCamera.getFrustum(), reinterpret_cast<std::vector<VolumeNode*>& >(mShadowOccluders),
-            NODE_GEOMETRY, 0, true, true);
+        FrustumOctreeQuery query(reinterpret_cast<std::vector<VolumeNode*>& >(mShadowOccluders), shadowCamera.getFrustum(),
+            NODE_GEOMETRY, true, true);
         mOctree->getNodes(query);
         
         updateOccluders(mShadowOccluders, shadowCamera);
@@ -503,8 +524,6 @@ unsigned View::processLight(Light* light)
         BoundingBox geometryBox;
         BoundingBox shadowCasterBox;
         
-        static std::vector<VolumeNode*> tempLitNodes;
-        
         switch (type)
         {
         case LIGHT_DIRECTIONAL:
@@ -561,38 +580,36 @@ unsigned View::processLight(Light* light)
                 if (!useOcclusion)
                 {
                     // Get potential shadow casters without occlusion
-                    FrustumOctreeQuery query(shadowCamera.getFrustum(), tempLitNodes, NODE_GEOMETRY);
+                    FrustumOctreeQuery query(tempNodes, shadowCamera.getFrustum(), NODE_GEOMETRY);
                     mOctree->getNodes(query);
                 }
                 else
                 {
                     // Get potential shadow casters with occlusion
-                    OccludedFrustumOctreeQuery query(shadowCamera.getFrustum(), buffer, tempLitNodes,
+                    OccludedFrustumOctreeQuery query(tempNodes, shadowCamera.getFrustum(), buffer, 
                         NODE_GEOMETRY);
                     mOctree->getNodes(query);
                 }
                 
-                processLightQuery(i, tempLitNodes, false, isSplitShadowed, geometryBox, shadowCasterBox);
+                processLightQuery(i, tempNodes, geometryBox, shadowCasterBox, false, isSplitShadowed);
             }
             break;
             
         case LIGHT_POINT:
             {
-                SphereOctreeQuery query(Sphere(sSplitLights[i]->getWorldPosition(), sSplitLights[i]->getRange()),
-                    tempLitNodes, NODE_GEOMETRY);
+                SphereOctreeQuery query(tempNodes, Sphere(sSplitLights[i]->getWorldPosition(), sSplitLights[i]->getRange()),
+                    NODE_GEOMETRY);
                 mOctree->getNodes(query);
-                
-                processLightQuery(i, tempLitNodes, true, false, geometryBox, shadowCasterBox);
+                processLightQuery(i, tempNodes, geometryBox, shadowCasterBox, true, false);
             }
             break;
             
         case LIGHT_SPOT:
         case LIGHT_SPLITPOINT:
             {
-                FrustumOctreeQuery query(sSplitLights[i]->getFrustum(), tempLitNodes, NODE_GEOMETRY);
+                FrustumOctreeQuery query(tempNodes, sSplitLights[i]->getFrustum(), NODE_GEOMETRY);
                 mOctree->getNodes(query);
-                
-                processLightQuery(i, tempLitNodes, true, isSplitShadowed, geometryBox, shadowCasterBox);
+                processLightQuery(i, tempNodes, geometryBox, shadowCasterBox, true, isSplitShadowed);
             }
             break;
         }
@@ -651,13 +668,14 @@ unsigned View::processLight(Light* light)
     return splitLights;
 }
 
-void View::processLightQuery(unsigned index, const std::vector<VolumeNode*>& result,
-    bool getLitGeometries, bool getShadowCasters, BoundingBox& geometryBox, BoundingBox& shadowCasterBox)
+void View::processLightQuery(unsigned splitIndex, const std::vector<VolumeNode*>& result, BoundingBox& geometryBox,
+    BoundingBox& shadowCasterBox, bool getLitGeometries, bool getShadowCasters)
 {
     Renderer* renderer = mPipeline->getRenderer();
+    Light* light = sSplitLights[splitIndex];
     
     // Transform scene frustum into shadow camera's view space for shadow caster visibility check
-    Camera& shadowCamera = sSplitLights[index]->getShadowCamera();
+    Camera& shadowCamera = light->getShadowCamera();
     const Matrix4x3& lightView = shadowCamera.getInverseWorldTransform();
     const Matrix4& lightProj = shadowCamera.getProjection();
     
@@ -665,7 +683,7 @@ void View::processLightQuery(unsigned index, const std::vector<VolumeNode*>& res
     // intersection of the scene frustum and the split frustum, so that shadow casters do not get
     // rendered into unnecessary splits
     Frustum lightViewFrustum;
-    if (sSplitLights[index]->getLightType() != LIGHT_DIRECTIONAL)
+    if (light->getLightType() != LIGHT_DIRECTIONAL)
     {
         lightViewFrustum = mCamera->getSplitFrustum(
             mSceneViewBox.mMin.mZ, mSceneViewBox.mMax.mZ).getTransformed(lightView);
@@ -673,8 +691,8 @@ void View::processLightQuery(unsigned index, const std::vector<VolumeNode*>& res
     else
     {
         lightViewFrustum = mCamera->getSplitFrustum(
-            max(mSceneViewBox.mMin.mZ, sSplitLights[index]->getNearSplit() - sSplitLights[index]->getNearFadeRange()),
-            min(mSceneViewBox.mMax.mZ, sSplitLights[index]->getFarSplit())
+            max(mSceneViewBox.mMin.mZ, light->getNearSplit() - light->getNearFadeRange()),
+            min(mSceneViewBox.mMax.mZ, light->getFarSplit())
         ).getTransformed(lightView);
     }
     BoundingBox lightViewFrustumBox;
@@ -685,13 +703,12 @@ void View::processLightQuery(unsigned index, const std::vector<VolumeNode*>& res
         getShadowCasters = false;
     
     // Generate merged light view geometry/shadowcaster bounding boxes box for shadow focusing
-    bool mergeBoxes = (sSplitLights[index]->getLightType() != LIGHT_SPLITPOINT) && (sSplitLights[index]->getShadowMap()) &&
-        (sSplitLights[index]->getShadowFocus().mFocus);
+    bool mergeBoxes = (light->getLightType() != LIGHT_SPLITPOINT) && (light->getShadowMap()) && (light->getShadowFocus().mFocus);
     bool projectBoxes = !shadowCamera.isOrthographic();
     
     BoundingBox lightViewBox;
     BoundingBox lightProjBox;
-    unsigned lightMask = sSplitLights[index]->getLightMask();
+    unsigned lightMask = light->getLightMask();
     
     for (unsigned i = 0; i < result.size(); ++i)
     {
@@ -729,7 +746,7 @@ void View::processLightQuery(unsigned index, const std::vector<VolumeNode*>& res
                     boxGenerated = true;
                 }
                 
-                sLitGeometries[index].push_back(geom);
+                sLitGeometries[splitIndex].push_back(geom);
             }
         }
         
@@ -773,7 +790,7 @@ void View::processLightQuery(unsigned index, const std::vector<VolumeNode*>& res
                             geom->markInShadowView(mFrame);
                             geom->updateGeometry(mFrame, renderer);
                         }
-                        sShadowCasters[index].push_back(geom);
+                        sShadowCasters[splitIndex].push_back(geom);
                     }
                     break;
                 }
@@ -1201,7 +1218,7 @@ void View::checkTechniqueForAuxView(MaterialTechnique* technique)
                     // Add the view only if it has not been added already
                     Camera* camera = target->getCamera();
                     if ((camera) && (!camera->isInView(mFrame.mFrameNumber)))
-                        mPipeline->addView(mOctree, camera, target);
+                        mPipeline->addView(mOctree, camera, target, IntRect(0, 0, tex2D->getWidth(), tex2D->getHeight()));
                 }
             }
             else if (texture->getType() == TextureCube::getTypeStatic())
@@ -1215,7 +1232,7 @@ void View::checkTechniqueForAuxView(MaterialTechnique* technique)
                         // Add the view only if it has not been added already
                         Camera* camera = target->getCamera();
                         if ((camera) && (!camera->isInView(mFrame.mFrameNumber)))
-                            mPipeline->addView(mOctree, camera, target);
+                            mPipeline->addView(mOctree, camera, target, IntRect(0, 0, texCube->getWidth(), texCube->getHeight()));
                     }
                 }
             }

+ 10 - 4
Engine/Renderer/View.h

@@ -61,7 +61,7 @@ struct LightBatchQueue
     bool mLastSplit;
 };
 
-//! A rendering view. Includes the main view and any auxiliary views, but not shadow cameras
+//! A rendering view. Includes the main view(s) and any auxiliary views, but not shadow cameras
 class View : public RefCounted
 {
 public:
@@ -70,8 +70,8 @@ public:
     //! Destruct
     virtual ~View();
     
-    //! Define with octree, camera and render target pointers
-    void define(Octree* octree, Camera* camera, RenderSurface* renderTarget);
+    //! Define with octree, camera and render target pointers and a viewport rectangle
+    void define(Octree* octree, Camera* camera, RenderSurface* renderTarget, IntRect screenRect);
     //! Update culling and construct rendering batches
     void update(const FrameInfo& frame);
     //! Render batches
@@ -83,6 +83,10 @@ public:
     Camera* getCamera() const { return mCamera; }
     //! Return zone
     Zone* getZone() const { return mZone; }
+    //! Return the render target. 0 if using the backbuffer
+    RenderSurface* getRenderTarget() const { return mRenderTarget; }
+    //! Return the depth stencil. 0 if using the backbuffer's depth stencil
+    RenderSurface* getDepthStencil() const { return mDepthStencil; }
     //! Return geometry scene nodes
     const std::vector<GeometryNode*>& getGeometries() const { return mGeometries; }
     //! Return occluder scene nodes
@@ -116,7 +120,7 @@ private:
     //! Query for lit geometries and shadow casters for a light
     unsigned processLight(Light* light);
     //! Generate combined bounding boxes for lit geometries and shadow casters and check shadow caster visibility
-    void processLightQuery(unsigned index, const std::vector<VolumeNode*>& result, bool getLitGeometries, bool getShadowCasters, BoundingBox& geometryBox, BoundingBox& shadowSpaceBox);
+    void processLightQuery(unsigned splitIndex, const std::vector<VolumeNode*>& result, BoundingBox& geometryBox, BoundingBox& shadowSpaceBox, bool getLitGeometries, bool getShadowCasters);
     //! Check visibility of one shadow caster
     bool isShadowCasterVisible(GeometryNode* geom, BoundingBox lightViewBox, const Camera& shadowCamera, const Matrix4x3& lightView, const Frustum& lightViewFrustum, const BoundingBox& lightViewFrustumBox);
     //! Set up initial shadow camera view
@@ -151,6 +155,8 @@ private:
     RenderSurface* mRenderTarget;
     //! Depth buffer to use
     RenderSurface* mDepthStencil;
+    //! Screen rectangle. For texture render targets it is the full texture size
+    IntRect mScreenRect;
     //! Render target width
     int mWidth;
     //! Render target height

+ 1 - 1
Engine/Renderer/VolumeNode.h

@@ -76,7 +76,7 @@ public:
     virtual void updateDistance(const FrameInfo& frame);
     //! Override model and view transforms for rendering
     virtual void overrideTransforms(unsigned batchIndex, Camera& camera, const Matrix4x3** model, const Matrix4x3** view);
-    //! Draw debug geometry
+    //! Add debug geometry to the debug renderer
     virtual void drawDebugGeometry(DebugRenderer* debug);
     
     //! Set whether casts shadows

+ 21 - 8
Engine/Resource/ResourceCache.cpp

@@ -55,13 +55,29 @@ void ResourceCache::addResourceFactory(ResourceFactory* factory)
 
 void ResourceCache::addResourcePath(const std::string& path)
 {
+    std::string fixedPath = fixPath(path);
+    std::string pathLower = toLower(fixedPath);
+    // Check that the same path does not already exist
+    for (unsigned i = 0; i < mResourcePaths.size(); ++i)
+    {
+        if (toLower(mResourcePaths[i]) == pathLower)
+            return;
+    }
+    
+    if (!directoryExists(path))
+    {
+        LOGERROR("Could not open directory " + path);
+        return;
+    }
+    
     checkDirectoryAccess(path);
     
-    std::string fixedPath = fixPath(path);
+
     mResourcePaths.push_back(fixedPath);
     
     // Scan the path for files recursively and add their hash-to-name mappings
-    std::vector<std::string> fileNames = scanDirectory(fixedPath, "*.*", SCAN_FILES, true);
+    std::vector<std::string> fileNames;
+    scanDirectory(fileNames, fixedPath, "*.*", SCAN_FILES, true);
     for (unsigned i = 0; i < fileNames.size(); ++i)
         registerHash(fileNames[i]);
     
@@ -327,19 +343,16 @@ Resource* ResourceCache::getResource(ShortStringHash type, StringHash nameHash)
     }
 }
 
-std::vector<Resource*> ResourceCache::getResources(ShortStringHash type)
+void ResourceCache::getResources(std::vector<Resource*>& result, ShortStringHash type) const
 {
-    std::vector<Resource*> ret;
-    
+    result.clear();
     std::map<ShortStringHash, ResourceGroup>::const_iterator i = mResourceGroups.find(type);
     if (i != mResourceGroups.end())
     {
         for (std::map<StringHash, SharedPtr<Resource> >::const_iterator j = i->second.mResources.begin();
             j != i->second.mResources.end(); ++j)
-            ret.push_back(j->second);
+            result.push_back(j->second);
     }
-    
-    return ret;
 }
 
 bool ResourceCache::exists(const std::string& name) const

+ 14 - 12
Engine/Resource/ResourceCache.h

@@ -95,9 +95,9 @@ public:
     Resource* getResource(ShortStringHash type, const std::string& name);
     //! Return a resource by type and name hash. Load if not loaded yet. Return null if fails
     Resource* getResource(ShortStringHash type, StringHash nameHash);
-    //! Return all resources of a specific type
-    std::vector<Resource*> getResources(ShortStringHash type);
-    //! Return all resources
+    //! Return all loaded resources of a specific type
+    void getResources(std::vector<Resource*>& result, ShortStringHash type) const;
+    //! Return all loaded resources
     const std::map<ShortStringHash, ResourceGroup>& getAllResources() const { return mResourceGroups; }
     //! Return added resource load paths
     const std::vector<std::string>& getResourcePaths() const { return mResourcePaths; }
@@ -107,8 +107,8 @@ public:
     template <class T> T* getResource(const std::string& name);
     //! Template version of returning a resource by name hash
     template <class T> T* getResource(StringHash nameHash);
-    //! Template version of returning resources of a specific type
-    template <class T> std::vector<T*> getResources();
+    //! Template version of returning loaded resources of a specific type
+    template <class T> void getResources(std::vector<T*>& result) const;
     //! Return whether a file exists by name
     bool exists(const std::string& name) const;
     //! Return whether a file exists by name hash
@@ -150,16 +150,18 @@ template <class T> T* ResourceCache::getResource(StringHash nameHash)
     return static_cast<T*>(getResource(type, nameHash));
 }
 
-template <class T> std::vector<T*> ResourceCache::getResources()
+template <class T> void ResourceCache::getResources(std::vector<T*>& result) const
 {
+    std::vector<Resource*>& resources = reinterpret_cast<std::vector<Resource*>&>(result);
     ShortStringHash type = T::getTypeStatic();
-    std::vector<Resource*> resources = getResources(type);
-    std::vector<T*> ret;
+    getResources(resources, type);
     
-    for (std::vector<Resource*>::const_iterator i = resources.begin(); i != resources.end(); ++i)
-        ret.push_back(static_cast<T*>(*i));
-    
-    return ret;
+    // Perform conversion of the returned pointers
+    for (unsigned i = 0; i < result.size(); ++i)
+    {
+        Resource* resource = resources[i];
+        result[i] = static_cast<T*>(resource);
+    }
 }
 
 #endif // RESOURCE_RESOURCECACHE_H

+ 11 - 4
Engine/Scene/Component.cpp

@@ -110,19 +110,25 @@ void Component::setNetFlags(unsigned char flags)
         mNetFlags = flags;
 }
 
+unsigned Component::getGroupFlags() const
+{
+    if (!mEntity)
+        return 0;
+    
+    return mEntity->getGroupFlags();
+}
+
 bool Component::isPlayback() const
 {
     if (!mEntity)
         return false;
+    
     return mEntity->isPlayback();
 }
 
 bool Component::checkSync(Connection* connection) const
 {
-    if (!mEntity)
-        return false;
-    
-    if ((mNetFlags & NET_SYNCTONONE) || (mEntity->getNetFlags() & NET_SYNCTONONE))
+    if ((!mEntity) || (mNetFlags & NET_SYNCTONONE) || (mEntity->getNetFlags() & NET_SYNCTONONE))
         return false;
     
     if ((mNetFlags & NET_SYNCTOOWNER) || (mEntity->getNetFlags() & NET_SYNCTOOWNER))
@@ -135,6 +141,7 @@ bool Component::checkPrediction(Connection* connection) const
 {
     if ((!mEntity) || (!(mNetFlags & NET_PREDICTIONFLAGS)))
         return false;
+    
     return mEntity->checkPrediction(connection);
 }
 

+ 2 - 0
Engine/Scene/Component.h

@@ -113,6 +113,8 @@ public:
     ShortStringHash getCombinedHash() const { return getType() + ShortStringHash(mNameHash); }
     //! Return network replication flags
     unsigned char getNetFlags() const { return mNetFlags; }
+    //! Return the entity's groupflags, or 0 if no entity
+    unsigned getGroupFlags() const;
     //! Return entity
     Entity* getEntity() const { return mEntity; }
     //! Return whether is in a server scene

+ 3 - 6
Engine/Scene/Entity.cpp

@@ -624,17 +624,14 @@ Component* Entity::getComponent(unsigned short combinedHash) const
     return 0;
 }
 
-std::vector<Component*> Entity::getComponents(ShortStringHash type) const
+void Entity::getComponents(std::vector<Component*>& result, ShortStringHash type) const
 {
-    std::vector<Component*> ret;
-    
+    result.clear();
     for (std::vector<SharedPtr<Component> >::const_iterator i = mComponents.begin(); i != mComponents.end(); ++i)
     {
         if ((*i)->getType() == type)
-            ret.push_back(*i);
+            result.push_back(*i);
     }
-    
-    return ret;
 }
 
 bool Entity::isPlayback() const

+ 13 - 22
Engine/Scene/Entity.h

@@ -86,9 +86,9 @@ public:
     //! Let each component perform network client-side visual smoothing
     void interpolate(bool snapToEnd = false);
     //! Let each component list component references
-    void getComponentRefs(std::vector<ComponentRef>& dest);
+    void getComponentRefs(std::vector<ComponentRef>& result);
     //! Let each component list resource references
-    void getResourceRefs(std::vector<Resource*>& dest);
+    void getResourceRefs(std::vector<Resource*>& result);
     //! Set name
     void setName(const std::string& name);
    //! Set group flags
@@ -189,7 +189,7 @@ public:
     //! Return all components
     const std::vector<SharedPtr<Component> >& getComponents() const { return mComponents; }
     //! Return components of a specific type
-    std::vector<Component*> getComponents(ShortStringHash type) const;
+    void getComponents(std::vector<Component*>& result, ShortStringHash type) const;
     //! Return whether is in a server scene
     bool isAuthority() const { return (mNetFlags & NET_AUTHORITY) != 0; }
     //! Return whether is in a client scene
@@ -230,9 +230,9 @@ public:
     //! Return component derived from class with specific name hash
     template <class T> T* getDerivedComponent(StringHash nameHash) const;
     //! Template version of returning components of specific type
-    template <class T> std::vector<T*> getComponents() const;
+    template <class T> void getComponents(std::vector<T*>& result) const;
     //! Return all components derived from class
-    template <class T> std::vector<T*> getDerivedComponents() const;
+    template <class T> void getDerivedComponents(std::vector<T*>& result) const;
     
 private:
     //! Remove a component by iterator
@@ -411,34 +411,25 @@ template <class T> T* Entity::getDerivedComponent(StringHash nameHash) const
     return 0;
 }
 
-template <class T> std::vector<T*> Entity::getComponents() const
+template <class T> void Entity::getComponents(std::vector<T*>& result) const
 {
-    std::vector<T*> components;
-    ShortStringHash type = T::getTypeStatic();
-    
+    result.clear();
     for (std::vector<SharedPtr<Component> >::const_iterator i = mComponents.begin(); i != mComponents.end(); ++i)
     {
-        if ((*i)->getType() == type)
-            components.push_back(static_cast<T*>(i->getPtr()));
+        if ((*i)->getType() == T::getTypeStatic())
+            result.push_back(static_cast<T*>(i->getPtr()));
     }
-    
-    return components;
 }
 
-template <class T> std::vector<T*> Entity::getDerivedComponents() const
+template <class T> void Entity::getDerivedComponents(std::vector<T*>& result) const
 {
-    std::vector<T*> components;
-    
-    std::vector<SharedPtr<Component> >::const_iterator i = mComponents.begin();
-    while (i != mComponents.end())
+    result.clear();
+    for (std::vector<SharedPtr<Component> >::const_iterator i = mComponents.begin(); i != mComponents.end(); ++i)
     {
         T* ptr = dynamic_cast<T*>(i->getPtr());
         if (ptr)
-            components.push_back(ptr);
-        ++i;
+            result.push_back(ptr);
     }
-    
-    return components;
 }
 
 #endif // SCENE_ENTITY_H

+ 5 - 0
Engine/Scene/Node.cpp

@@ -465,6 +465,11 @@ void Node::scale(const Vector3& scale)
         markDirty();
 }
 
+void Node::setNodeFlags(unsigned flags)
+{
+    mNodeFlags = (mNodeFlags & NODE_PREDEFINEDFLAGS) | (flags & NODE_CUSTOMFLAGS);
+}
+
 void Node::addChild(Node* node)
 {
     if ((!node) || (node->mParent == this) || (mParent == node))

+ 4 - 0
Engine/Scene/Node.h

@@ -48,6 +48,8 @@ static const unsigned NODE_ZONE = 0x800;
 static const unsigned NODE_RIGIDBODY = 0x1000;
 static const unsigned NODE_POSITIONALCHANNEL = 0x2000;
 static const unsigned NODE_ANY = 0xffffffff;
+static const unsigned NODE_PREDEFINEDFLAGS = 0x0000ffff;
+static const unsigned NODE_CUSTOMFLAGS = 0xffff0000;
 
 static const unsigned INTERP_NONE = 0x0;
 static const unsigned INTERP_POS = 0x1;
@@ -118,6 +120,8 @@ public:
     void scale(float scale);
     //! Modify scale
     void scale(const Vector3& scale);
+    //! Set custom node flags (high 16 bits.) For editing use only, these will not be saved or replicated
+    void setNodeFlags(unsigned flags);
     //! Add a child scene node
     void addChild(Node* node);
     //! Remove a child scene node. Optionally set its transform to match the last world position it was in

+ 56 - 33
Engine/Scene/Scene.cpp

@@ -50,10 +50,13 @@ Scene::Scene(ResourceCache* cache, const std::string& name) :
     mInterpolationConstant(50.0f),
     mInterpolationSnapThreshold(1.0f),
     mInterpolationLerpFactor(1.0f),
+    mPaused(false),
     mPlayback(false),
     mAsyncLoading(false)
 {
     LOGINFO("Scene " + mName + " created");
+    
+    subscribeToEvent(EVENT_UPDATESCENES, EVENT_HANDLER(Scene, handleUpdateScenes));
 }
 
 Scene::~Scene()
@@ -72,6 +75,9 @@ void Scene::update(float timeStep)
         return;
     }
     
+    if (mPaused)
+        return;
+    
     PROFILE(Scene_Update);
     
     using namespace SceneUpdate;
@@ -241,9 +247,6 @@ void Scene::saveProperties(Serializer& dest)
 
 void Scene::loadProperties(Deserializer& source)
 {
-    if (!mEntities.empty())
-        EXCEPTION("Scene must be empty when loading properties");
-    
     // Read network interpolation properties
     mTransientPredictionTime = source.readFloat();
     mInterpolationConstant = source.readFloat();
@@ -269,9 +272,6 @@ void Scene::savePropertiesXML(XMLElement& dest)
 
 void Scene::loadPropertiesXML(const XMLElement& source)
 {
-    if (!mEntities.empty())
-        EXCEPTION("Scene must be empty when loading properties");
-    
     // Read network interpolation properties
     if (source.hasChildElement("interpolation"))
     {
@@ -412,14 +412,11 @@ void Scene::loadAsync(File* file)
     stopAsyncLoading();
     removeAllEntities();
     
-    // Read scene name
+    // Read scene name and extension properties, then begin async loading of entities
     file->seek(0);
     mName = file->readString();
-    
-    // Read extension properties
     loadProperties(*file);
     
-    // Begin async loading
     mAsyncTotalEntities = file->readUInt();
     mAsyncLoadedEntities = 0;
     mAsyncFile = file;
@@ -445,10 +442,8 @@ void Scene::loadAsyncXML(File* file)
     stopAsyncLoading();
     removeAllEntities();
     
-    // Read scene name
+    // Read scene name and extension properties
     mName = sceneElem.getString("name");
-    
-    // Read extension properties
     loadPropertiesXML(sceneElem);
     
     // Count entities
@@ -461,7 +456,7 @@ void Scene::loadAsyncXML(File* file)
         mAsyncXMLElement = mAsyncXMLElement.getNextElement("entity");
     }
     
-    // Begin async loading
+    // Begin async loading of entities
     mAsyncFile = file;
     mAsyncXMLFile = xml;
     mAsyncXMLElement = sceneElem.getChildElement("entity");
@@ -488,11 +483,17 @@ void Scene::setNetFlags(unsigned char flags)
     mNetFlags = flags & NET_MODEFLAGS;
 }
 
+void Scene::setPaused(bool enable)
+{
+    mPaused = enable;
+}
+
 void Scene::addExtension(SceneExtension* extension)
 {
     if (!extension)
         return;
     
+    extension->mScene = this;
     mExtensions[extension->getType()] = extension;
 }
 
@@ -658,20 +659,23 @@ void Scene::removeEntity(StringHash nameHash)
     LOGWARNING("Entity " + toString(nameHash) + " not found in scene");
 }
 
-void Scene::removeAllEntities(unsigned char netFlagsMask)
+void Scene::removeEntities(unsigned char netFlags, unsigned groupFlags)
 {
-    // Remove all entities with matching netflags
     for (std::map<EntityID, SharedPtr<Entity> >::iterator i = mEntities.begin(); i != mEntities.end();)
     {
-        std::map<EntityID, SharedPtr<Entity> >::iterator entity = i;
-        ++i;
-        if (!netFlagsMask)
-            removeEntity(entity);
-        else
-        {
-            if (entity->second->getNetFlags() & netFlagsMask)
-                removeEntity(entity);
-        }
+        std::map<EntityID, SharedPtr<Entity> >::iterator current = i++;
+        if (((!netFlags) || (current->second->getNetFlags() & netFlags)) && ((!groupFlags) ||
+            (current->second->getGroupFlags() & groupFlags)))
+            removeEntity(current);
+    }
+}
+
+void Scene::removeAllEntities()
+{
+    for (std::map<EntityID, SharedPtr<Entity> >::iterator i = mEntities.begin(); i != mEntities.end();)
+    {
+        std::map<EntityID, SharedPtr<Entity> >::iterator current = i++;
+        removeEntity(current);
     }
 }
 
@@ -871,18 +875,25 @@ Entity* Scene::getEntity(StringHash nameHash) const
     return 0;
 }
 
-std::vector<Entity*> Scene::getEntities(unsigned includeFlags, unsigned excludeFlags) const
+void Scene::getEntities(std::vector<Entity*>& result, unsigned char netFlags, unsigned groupFlags) const
 {
-    std::vector<Entity*> ret;
-    
+    result.clear();
     for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = mEntities.begin(); i != mEntities.end(); ++i)
     {
-        unsigned flags = i->second->getGroupFlags();
-        if ((flags & includeFlags) && (!(flags & excludeFlags)))
-            ret.push_back(i->second);
+        if (((!netFlags) || (i->second->getNetFlags() & netFlags)) && ((!groupFlags) || (i->second->getGroupFlags() &
+            groupFlags)))
+            result.push_back(i->second);
+    }
+}
+
+void Scene::getEntitiesWithComponent(std::vector<Entity*>& result, ShortStringHash type) const
+{
+    result.clear();
+    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = mEntities.begin(); i != mEntities.end(); ++i)
+    {
+        if (i->second->hasComponent(type))
+            result.push_back(i->second);
     }
-    
-    return ret;
 }
 
 Component* Scene::getComponent(const ComponentRef& ref) const
@@ -1103,3 +1114,15 @@ void Scene::finishLoading(Deserializer& source)
     else
         mChecksum = 0;
 }
+
+void Scene::handleUpdateScenes(StringHash eventType, VariantMap& eventData)
+{
+    // Update only if not networked. Otherwise the Client or Server should handle the updating
+    if ((!isAuthority()) && (!isProxy()))
+    {
+        using namespace UpdateScenes;
+        
+        float timeStep = eventData[P_TIMESTEP].getFloat();
+        update(timeStep);
+    }
+}

+ 27 - 9
Engine/Scene/Scene.h

@@ -51,7 +51,7 @@ public:
     //! Destruct
     virtual ~Scene();
     
-    //! Run update. Send update events and update scene extensions
+    //! Run update. Send update events and update scene extensions. Will not update if paused
     void update(float timeStep);
     //! Perform client-side visual smoothing
     void interpolate(float timeStep);
@@ -81,18 +81,19 @@ public:
     void loadAsyncXML(File* file);
     //! Stop asynchronous loading
     void stopAsyncLoading();
-    
     //! Set scene name
     void setName(const std::string& name);
     //! Set network replication flags (only NET_AUTHORITY or NET_PROXY)
     void setNetFlags(unsigned char flags);
+    //! Set pause mode
+    void setPaused(bool enable);
     //! Add a scene extension
     void addExtension(SceneExtension* extension);
     //! Add a component factory
     void addComponentFactory(ComponentFactory* factory);
     //! Create a component. Throw exception if no factory can create
     SharedPtr<Component> createComponent(ShortStringHash type, const std::string& name = std::string());
-    //! Create an entity and assign an ID automatically
+    //! Create an entity and assign an ID automatically. Throw exception if ID range exhausted
     Entity* createEntity(const std::string& name = std::string(), bool local = false);
     //! Create an entity with manually defined ID
     Entity* createEntity(EntityID id, const std::string& name = std::string());
@@ -106,8 +107,10 @@ public:
     void removeEntity(const std::string& name);
     //! Remove an entity by name hash. Needs to search through all entities
     void removeEntity(StringHash nameHash);
+    //! Remove entities based on netflags and groupflags
+    void removeEntities(unsigned char netFlags = NET_NONE, unsigned groupFlags = 0);
     //! Remove all entities
-    void removeAllEntities(unsigned char netFlagsMask = NET_NONE);
+    void removeAllEntities();
     //! Reset a specific network owner from entities. Optionally remove the owned entities
     void resetOwner(Connection* owner, bool removeEntities = false);
     //! Set client-side transient prediction time
@@ -160,9 +163,11 @@ public:
     //! Return number of entities
     unsigned getNumEntities() const { return mEntities.size(); }
     //! Return all entities
-    const std::map<EntityID, SharedPtr<Entity> >& getEntities() const { return mEntities; }
-    //! Return entities by group flags
-    std::vector<Entity*> getEntities(unsigned includeFlags, unsigned excludeFlags) const;
+    const std::map<EntityID, SharedPtr<Entity> >& getAllEntities() const { return mEntities; }
+    //! Return entities by netflags and group flags
+    void getEntities(std::vector<Entity*>& result, unsigned char netFlags = 0, unsigned groupFlags = 0) const;
+    //! Return entities with a specific component
+    void getEntitiesWithComponent(std::vector<Entity*>& result, ShortStringHash type) const;
     //! Return a component by component reference, or null if not found
     Component* getComponent(const ComponentRef& ref) const;
     //! Return entity position by going through scene node components it has (RigidBody is preferred)
@@ -187,14 +192,18 @@ public:
     float getInterpolationSnapThresholdSquared() { return mInterpolationSnapThreshold; }
     //! Return asynchronous loading progress between 0.0 - 1.0
     float getAsyncLoadProgress() const;
-    //! Return whether scene is performing client-side prediction by replaying controls
-    bool isPlayback() const { return mPlayback; }
     //! Return whether is a server scene
     bool isAuthority() const { return (mNetFlags & NET_AUTHORITY) != 0; }
     //! Return whether is a client scene
     bool isProxy() const { return (mNetFlags & NET_PROXY) != 0; }
+    //! Return whether scene is paused
+    bool isPaused() const { return mPaused; }
+    //! Return whether scene is performing client-side prediction by replaying controls
+    bool isPlayback() const { return mPlayback; }
     //! Return whether is loading asynchronously
     bool isAsyncLoading() const { return mAsyncLoading; }
+    //! Template function for returning entities with a specific component
+    template <class T> void getEntitiesWithComponent(std::vector<Entity*>& result) const;
     //! Template function that returns a specific scene extension
     template <class T> T* getExtension() const;
     
@@ -214,6 +223,8 @@ private:
     void updateNextEntityID(EntityID loadedID);
     //! Perform post-load after scene loading has finished
     void finishLoading(Deserializer& source);
+    //! Handle update scenes event
+    void handleUpdateScenes(StringHash eventType, VariantMap& eventData);
     
     //! Scene name
     std::string mName;
@@ -251,6 +262,8 @@ private:
     float mInterpolationSnapThreshold;
     //! Visual smoothing lerp factor, valid only during update()
     float mInterpolationLerpFactor;
+    //! Pause flag
+    bool mPaused;
     //! Client-side prediction controls playback flag
     bool mPlayback;
     
@@ -273,6 +286,11 @@ template <class T> SharedPtr<T> Scene::createComponent(const std::string& name)
     return staticCast<T>(createComponent(T::getTypeStatic(), name));
 }
 
+template <class T> void Scene::getEntitiesWithComponent(std::vector<Entity*>& result) const
+{
+    return getEntitiesWithComponent(result, T::getTypeStatic());
+}
+
 template <class T> T* Scene::getExtension() const
 {
     ShortStringHash type = T::getTypeStatic();

+ 6 - 0
Engine/Scene/SceneEvents.h

@@ -26,6 +26,12 @@
 
 #include "Event.h"
 
+//! Update all non-networked scenes. Sent by Engine
+DEFINE_EVENT(EVENT_UPDATESCENES, UpdateScenes)
+{
+    EVENT_PARAM(P_TIMESTEP, TimeStep);          // float
+}
+
 //! Variable timestep scene update
 DEFINE_EVENT(EVENT_SCENEUPDATE, SceneUpdate)
 {

+ 6 - 1
Engine/Scene/SceneExtension.cpp

@@ -27,6 +27,11 @@
 #include "SceneExtension.h"
 #include "Serializer.h"
 
+SceneExtension::SceneExtension() :
+    mScene(0)
+{
+}
+
 void SceneExtension::writeExtensionType(Serializer& dest)
 {
     dest.writeShortStringHash(getType());
@@ -36,5 +41,5 @@ void SceneExtension::checkExtensionType(Deserializer& source)
 {
     ShortStringHash type = source.readShortStringHash();
     if (type != getType())
-        SAFE_EXCEPTION("Scene extension data mismatch in " + source.getName());
+        EXCEPTION("Scene extension data mismatch in " + source.getName());
 }

+ 12 - 0
Engine/Scene/SceneExtension.h

@@ -27,13 +27,19 @@
 #include "HashedType.h"
 
 class Deserializer;
+class Scene;
 class Serializer;
 class XMLElement;
 
 //! Base class for extending Scene functionality
 class SceneExtension : public HashedType
 {
+    friend class Scene;
+    
 public:
+    //! Construct with defaults
+    SceneExtension();
+    
     //! Write properties to a stream
     virtual void save(Serializer& dest) {}
     //! Read properties from a stream
@@ -47,11 +53,17 @@ public:
     //! Set client-side prediction controls playback mode
     virtual void setPlayback(bool enable) {}
     
+    //! Return scene
+    Scene* getScene() const { return mScene; }
+    
 protected:
     //! Write extension type to a stream
     void writeExtensionType(Serializer& dest);
     //! Read extension type from a stream. Throw an extension in case of mismatch
     void checkExtensionType(Deserializer& source);
+    
+    //! Scene
+    Scene* mScene;
 };
 
 #endif // SCENE_SCENEEXTENSION_H

+ 16 - 20
Engine/UI/FileSelector.cpp

@@ -293,30 +293,26 @@ void FileSelector::refreshFiles()
     
     mFileList->removeAllItems();
     mFileEntries.clear();
+
+    std::vector<std::string> directories;
+    std::vector<std::string> files;
+    scanDirectory(directories, mPath, "*.*", SCAN_DIRECTORIES, false);
+    scanDirectory(files, mPath, getFilter(), SCAN_FILES, false);
     
-    try
+    for (unsigned i = 0; i < directories.size(); ++i)
     {
-        std::vector<std::string> directories = scanDirectory(mPath, "*.*", SCAN_DIRECTORIES, false);
-        std::vector<std::string> files = scanDirectory(mPath, getFilter(), SCAN_FILES, false);
-        
-        for (unsigned i = 0; i < directories.size(); ++i)
-        {
-            FileSelectorEntry newEntry;
-            newEntry.mName = directories[i];
-            newEntry.mDirectory = true;
-            mFileEntries.push_back(newEntry);
-        }
-        
-        for (unsigned i = 0; i < files.size(); ++i)
-        {
-            FileSelectorEntry newEntry;
-            newEntry.mName = files[i];
-            newEntry.mDirectory = false;
-            mFileEntries.push_back(newEntry);
-        }
+        FileSelectorEntry newEntry;
+        newEntry.mName = directories[i];
+        newEntry.mDirectory = true;
+        mFileEntries.push_back(newEntry);
     }
-    catch (...)
+    
+    for (unsigned i = 0; i < files.size(); ++i)
     {
+        FileSelectorEntry newEntry;
+        newEntry.mName = files[i];
+        newEntry.mDirectory = false;
+        mFileEntries.push_back(newEntry);
     }
     
     // Sort and add to the list view

+ 6 - 4
Examples/NinjaSnowWar/AIController.cpp

@@ -101,13 +101,15 @@ void AIController::makeHarder()
 void AIController::control(Ninja* ninja, float time)
 {
     // Get closest ninja on the players' side
+    static std::vector<Entity*> entities;
+    ninja->getScene()->getEntitiesWithComponent<Ninja>(entities);
+    
     Ninja* target = 0;
     float closestDistance = M_INFINITY;
-    Scene* scene = ninja->getScene();
-    const std::map<EntityID, SharedPtr<Entity> >& entities = scene->getEntities();
-    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
+    
+    for (unsigned i = 0; i < entities.size(); ++i)
     {
-        Entity* entity = i->second;
+        Entity* entity = entities[i];
         Ninja* testTarget = entity->getComponent<Ninja>();
         if (testTarget)
         {

+ 24 - 24
Examples/NinjaSnowWar/Game.cpp

@@ -91,9 +91,9 @@ Game::Game() :
     mCameraMaxDist(0.0f),
     mCameraSafetyDist(0.0f),
     mCameraRayLength(0.0f),
+    mDrawDebug(false),
     mGameOn(false),
     mFirstFrame(false),
-    mPaused(false),
     mClientEntityID(0)
 {
     std::string userDir = getUserDocumentsDirectory();
@@ -135,7 +135,7 @@ void Game::run()
     
     while (!mEngine->isExiting())
     {
-        mEngine->runFrame(mScene, mCamera, !mPaused);
+        mEngine->runFrame();
         
         // Save/load/exit
         // Check these outside the frame update, so that the engine does not render a black screen
@@ -240,6 +240,10 @@ void Game::init()
         mClient->connect(address, 1234, mUserName);
     }
     
+    // Configure the viewport
+    if (!runServer)
+        mEngine->getPipeline()->setViewport(0, mScene, mCamera);
+    
     startGame();
 }
 
@@ -326,15 +330,12 @@ void Game::createCamera()
     // Note: camera is local, so it will not be affected by net replication
     Entity* cameraEntity = mScene->createEntity("Camera", true);
     mCamera = cameraEntity->createComponent<Camera>();
-    Camera* camera = mCamera.getPtr();
     Renderer* renderer = mEngine->getRenderer();
-    if (renderer)
-        camera->setAspectRatio((float)renderer->getWidth() / (float)renderer->getHeight());
     // View distance is optional
     try
     {
-        camera->setNearClip(GameConfig::getReal("Engine/ViewStart"));
-        camera->setFarClip(GameConfig::getReal("Engine/ViewEnd"));
+        mCamera->setNearClip(GameConfig::getReal("Engine/ViewStart"));
+        mCamera->setFarClip(GameConfig::getReal("Engine/ViewEnd"));
     }
     catch (...) {}
 }
@@ -359,7 +360,7 @@ void Game::startGame()
     AIController::setup();
     
     // Clear all previous game objects
-    std::map<EntityID, SharedPtr<Entity> > entities = mScene->getEntities();
+    std::map<EntityID, SharedPtr<Entity> > entities = mScene->getAllEntities();
     for (std::map<EntityID, SharedPtr<Entity> >::iterator i = entities.begin(); i != entities.end(); ++i)
     {
         if (i->second->getName().find("Obj") != std::string::npos)
@@ -402,7 +403,7 @@ void Game::loadGame()
     if ((mClient) || (mServer))
         return;
     
-    if (mPaused)
+    if (mScene->isPaused())
         return;
     
     if (!fileExists(applicationDir + "Save.dat"))
@@ -449,7 +450,7 @@ void Game::saveGame()
     if ((mClient) || (mServer))
         return;
     
-    if ((mPaused) || (!mGameOn))
+    if ((mScene->isPaused()) || (!mGameOn))
         return;
     
     // Save the scene
@@ -491,9 +492,12 @@ void Game::handlePostUpdate(StringHash eventType, VariantMap& eventData)
     
     // Update camera to player position
     updateCamera();
-    
     // Update status panel
     updateStatus(timeStep);
+    
+    // Draw physics debug geometry if necessary
+    if (mDrawDebug)
+        mScene->getExtension<PhysicsWorld>()->drawDebugGeometry();
 }
 
 void Game::handlePreStep(StringHash eventType, VariantMap& eventData)
@@ -816,7 +820,7 @@ void Game::getControls()
     if (!mServer)
     {
         mControls.set(CTRL_ALL, false);
-        if ((!mFirstFrame) && (!mPaused))
+        if ((!mFirstFrame) && (!mScene->isPaused()))
         {
             if (input->getKeyDown('W')) mControls.set(CTRL_UP);
             if (input->getKeyDown('S')) mControls.set(CTRL_DOWN);
@@ -840,7 +844,7 @@ void Game::toggleDebugOverlay()
 
 void Game::toggleDebugGeometry()
 {
-    mEngine->setDebugDrawMode(mEngine->getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
+    mDrawDebug = !mDrawDebug;
 }
 
 void Game::togglePause()
@@ -849,11 +853,11 @@ void Game::togglePause()
     if ((mClient) || (mServer))
         return;
     
-    mPaused = !mPaused;
+    mScene->setPaused(!mScene->isPaused());
     
     if (mGameOn)
     {
-        if (mPaused)
+        if (mScene->isPaused())
             setMessage("PAUSED");
         else
             setMessage("");
@@ -1074,13 +1078,6 @@ void Game::updateCamera()
         {
             mCamera = cameraEntity->getComponent<Camera>();
             camera = mCamera.getPtr();
-            if (camera)
-            {
-                // Reset aspect ratio, might have changed
-                Renderer* renderer = mEngine->getRenderer();
-                if (renderer)
-                    camera->setAspectRatio((float)renderer->getWidth() / (float)renderer->getHeight());
-            }
         }
     }
     else
@@ -1089,6 +1086,9 @@ void Game::updateCamera()
     if (!camera)
         return;
     
+    // Make sure the pipeline has the updated camera pointer
+    mEngine->getPipeline()->setViewportCamera(0, camera);
+    
     // Player tracking
     Entity* playerEntity = 0;
     if ((!mClient) && (!mServer))
@@ -1128,7 +1128,7 @@ void Game::updateCamera()
     Vector3 rayDir = (maxDist - minDist).getNormalized();
     float rayDistance = mCameraMaxDist - mCameraMinDist + mCameraSafetyDist;
     std::vector<PhysicsRaycastResult> result;
-    mScene->getExtension<PhysicsWorld>()->raycast(Ray(minDist, rayDir), result, rayDistance, 2);
+    mScene->getExtension<PhysicsWorld>()->raycast(result, Ray(minDist, rayDir), rayDistance, 2);
     if (result.size())
         rayDistance = min(rayDistance, result[0].mDistance - mCameraSafetyDist);
     
@@ -1147,7 +1147,7 @@ int Game::getObjectCount(ShortStringHash type, int side)
 {
     int count = 0;
     
-    const std::map<EntityID, SharedPtr<Entity> >& entities = mScene->getEntities();
+    const std::map<EntityID, SharedPtr<Entity> >& entities = mScene->getAllEntities();
     for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
     {
         GameObject* object = i->second->getDerivedComponent<GameObject>();

+ 1 - 1
Examples/NinjaSnowWar/Game.h

@@ -138,7 +138,7 @@ private:
     float mCameraSafetyDist;
     float mCameraRayLength;
     float mSensitivity;
-    bool mPaused;
+    bool mDrawDebug;
     bool mGameOn;
     bool mFirstFrame;
     int mEnemies;

+ 39 - 17
Examples/Urho3D/Application.cpp

@@ -30,6 +30,7 @@
 #include "PackageFile.h"
 #include "ProcessUtils.h"
 #include "ResourceCache.h"
+#include "Scene.h"
 #include "ScriptEngine.h"
 #include "ScriptFile.h"
 #include "StringUtils.h"
@@ -56,19 +57,21 @@ void Application::run()
     std::string applicationDir = getUserDocumentsDirectory() + "Urho3D";
     createDirectory(applicationDir);
     
-    // Parse script file name from the command line
+    // Parse scene or script file name from the command line
     const std::vector<std::string>& arguments = getArguments();
-    std::string scriptFileName;
+    std::string fullName;
     if ((arguments.size()) && (arguments[0][0] != '-'))
-        scriptFileName = replace(arguments[0], '\\', '/');
-    if (scriptFileName.empty())
-        EXCEPTION("Usage: Urho3D <scriptfile> [options]\n\n"
-            "The script file should implement the functions void start() and void runFrame(). See the readme for options.\n");
+        fullName = replace(arguments[0], '\\', '/');
+    if (fullName.empty())
+        EXCEPTION("Usage: Urho3D <scriptfile | scenefile> [options]\n\n"
+            "Either a script file or a scene file can be specified. The script file should implement the function void start(), "
+            "which should in turn subscribe to all necessary events, such as the application update. If a scene is loaded, it "
+            "should contain script objects to implement the application logic.");
     
     // Instantiate the engine
     mEngine = new Engine();
     
-    // Add resources
+    // Add default resources
     mCache = mEngine->getResourceCache();
     if (fileExists("Data.pak"))
         mCache->addPackageFile(new PackageFile("Data.pak"));
@@ -77,21 +80,40 @@ void Application::run()
     
     mCache->addResourcePath(getSystemFontDirectory());
     
+    // Try to open the file before setting screen mode
+    File file(fullName);
+    
     // Initialize engine & scripting
     mEngine->init(arguments);
     mEngine->createScriptEngine();
     
-    // Execute the rest of initialization, including scene creation, in script
-    mScriptFile = mCache->getResource<ScriptFile>(scriptFileName);
-    if (!mScriptFile)
-        throw Exception(getLog()->getLastMessage(), false);
-    if (!mScriptFile->execute("void start()"))
-        EXCEPTION("Failed to execute the start() function");
+    // If the file has a pathname, add it also as a resource path
+    std::string pathName, fileName, extension;
+    splitPath(fullName, pathName, fileName, extension);
+    if (!pathName.empty())
+        mCache->addResourcePath(pathName);
     
-    // Run until exited
-    while (!mEngine->isExiting())
+    // Script mode: execute the rest of initialization, including scene creation, in script
+    if ((extension != ".xml") && (extension != ".scn"))
     {
-        if (!mScriptFile->execute("void runFrame()"))
-            EXCEPTION("Failed to execute the runFrame() function");
+        mScriptFile = new ScriptFile(mEngine->getScriptEngine(), fileName + extension);
+        mScriptFile->load(file, mCache);
+        if (!mScriptFile->execute("void start()"))
+            EXCEPTION("Failed to execute the start() function");
     }
+    // Scene mode: create and load a scene, then let it run
+    else
+    {
+        mScene = mEngine->createScene("Urho3D");
+        File sceneFile(fullName);
+        
+        if (extension == ".xml")
+            mScene->loadXML(sceneFile);
+        else
+            mScene->load(sceneFile);
+    }
+    
+    // Run until exited
+    while (!mEngine->isExiting())
+        mEngine->runFrame();
 }

+ 3 - 0
Examples/Urho3D/Application.h

@@ -29,6 +29,7 @@
 
 class Engine;
 class ResourceCache;
+class Scene;
 class ScriptFile;
 
 //! Urho3D Shell application
@@ -50,6 +51,8 @@ private:
     SharedPtr<ResourceCache> mCache;
     //! Script file
     SharedPtr<ScriptFile> mScriptFile;
+    //! Scene
+    SharedPtr<Scene> mScene;
 };
 
 #endif // URHO3D_APPLICATION_H

+ 5 - 2
Examples/Urho3D/Readme.txt

@@ -1,7 +1,10 @@
 This is a shell for running script-based Urho3D examples. Usage:
-Urho3D <scriptfile> [options]
+Urho3D <scriptfile | scenefile> [options]
 
-The script file should implement the functions void start() and void runFrame().
+Either a script file or a scene file can be specified. The script file should
+implement the function void start(), which should in turn subscribe to all 
+necessary events, such as the application update. If a scene is loaded, it 
+should contain script objects to implement the application logic.
 
 
 Commandline options:

+ 6 - 0
ThirdParty/AngelScript/source/as_compiler.cpp

@@ -28,6 +28,7 @@
    [email protected]
 */
 
+// Modified by Lasse Öörni for Urho3D
 
 //
 // as_compiler.cpp
@@ -6050,7 +6051,12 @@ int asCCompiler::CompileExpressionValue(asCScriptNode *node, asSExprContext *ctx
 			size_t numScanned;
 			float v = float(asStringScanDouble(value.AddressOf(), &numScanned));
 			ctx->type.SetConstantF(asCDataType::CreatePrimitive(ttFloat, true), v);
+			// Urho3D: fix assert if always using floats
+			#ifdef AS_USE_DOUBLE_AS_FLOAT
+			asASSERT((numScanned == vnode->tokenLength - 1) || (numScanned == vnode->tokenLength));
+			#else
 			asASSERT(numScanned == vnode->tokenLength - 1);
+			#endif
 		}
 		else if( vnode->tokenType == ttDoubleConstant )
 		{

+ 6 - 0
ThirdParty/AngelScript/source/as_config.h

@@ -28,6 +28,7 @@
    [email protected]
 */
 
+// Modified by Lasse Öörni for Urho3D
 
 //
 // as_config.h
@@ -830,6 +831,11 @@
 #endif
 
 
+// Urho3D: disable threading, always use float type
+#define AS_NO_THREADS
+#define AS_USE_DOUBLE_AS_FLOAT
+
+
 // The assert macro
 #if defined(ANDROID)
 	#if defined(AS_DEBUG)

+ 1 - 1
Tools/AssetImporter/AssetImporter.cpp

@@ -1064,7 +1064,7 @@ void buildAndSaveScene(ExportScene& scene)
     if (!scene.mNoExtensions)
     {
         //! \todo Make the physics properties configurable
-        PhysicsWorld* physicsWorld = new PhysicsWorld(outScene);
+        PhysicsWorld* physicsWorld = new PhysicsWorld();
         physicsWorld->setGravity(Vector3(0.0f, -9.81f, 0.0f));
         outScene->addExtension(physicsWorld);
         

+ 2 - 1
Tools/PackageTool/PackageTool.cpp

@@ -86,7 +86,8 @@ void run(const std::vector<std::string>& arguments)
         gBasePath = fixPath(arguments[2]);
     
    // Get the file list recursively
-    std::vector<std::string> fileNames = scanDirectory(dirName, "*.*", SCAN_FILES, true);
+    std::vector<std::string> fileNames;
+    scanDirectory(fileNames, dirName, "*.*", SCAN_FILES, true);
     if (!fileNames.size())
         errorExit("No files found");
     for (unsigned i = 0; i < fileNames.size(); ++i)