浏览代码

Improved asset import directly invokable from the editor.
Fixed octree octants not always getting deleted when empty.
Fixed memory corruption when closing the file selector with the buttons.
API improvements.
Doxygen documentation fixes.

Lasse Öörni 14 年之前
父节点
当前提交
23152b4d58
共有 41 个文件被更改,包括 812 次插入426 次删除
  1. 1 1
      Bin/CoreData/Scripts/Editor.as
  2. 2 0
      Bin/CoreData/Scripts/EditorCamera.as
  3. 3 3
      Bin/CoreData/Scripts/EditorComponentWindow.as
  4. 81 14
      Bin/CoreData/Scripts/EditorImport.as
  5. 33 21
      Bin/CoreData/Scripts/EditorScene.as
  6. 37 21
      Bin/CoreData/Scripts/EditorSceneWindow.as
  7. 45 12
      Bin/CoreData/Scripts/EditorUI.as
  8. 10 0
      Bin/CoreData/UI/SceneSettingsDialog.xml
  9. 1 1
      Bin/Editor.bat
  10. 86 0
      Editor.txt
  11. 54 4
      Engine/Common/File.cpp
  12. 7 1
      Engine/Common/File.h
  13. 7 0
      Engine/Common/Serializer.cpp
  14. 2 0
      Engine/Common/Serializer.h
  15. 19 0
      Engine/Engine/RegisterCommon.cpp
  16. 1 2
      Engine/Engine/RegisterRenderer.cpp
  17. 2 1
      Engine/Engine/RegisterResource.cpp
  18. 1 0
      Engine/Engine/RegisterTemplates.h
  19. 2 1
      Engine/Engine/RegisterUI.cpp
  20. 1 1
      Engine/Renderer/BillboardSet.h
  21. 6 28
      Engine/Renderer/Octree.cpp
  22. 12 10
      Engine/Renderer/Octree.h
  23. 5 0
      Engine/Renderer/Renderer.cpp
  24. 2 0
      Engine/Renderer/Renderer.h
  25. 50 50
      Engine/Resource/ResourceCache.cpp
  26. 3 2
      Engine/Resource/ResourceCache.h
  27. 0 5
      Engine/Scene/Node.cpp
  28. 0 4
      Engine/Scene/Node.h
  29. 1 0
      Engine/Script/ScriptEventListener.h
  30. 1 2
      Engine/UI/Button.cpp
  31. 1 0
      Engine/UI/Cursor.h
  32. 1 3
      Engine/UI/FileSelector.cpp
  33. 1 1
      Engine/UI/FileSelector.h
  34. 3 3
      Engine/UI/LineEdit.cpp
  35. 1 0
      Engine/UI/LineEdit.h
  36. 11 3
      Engine/UI/Menu.cpp
  37. 24 1
      Engine/UI/UI.cpp
  38. 3 1
      Engine/UI/UI.h
  39. 0 3
      Engine/UI/UIElement.h
  40. 1 3
      Examples/Urho3D/Application.cpp
  41. 291 224
      Tools/AssetImporter/AssetImporter.cpp

+ 1 - 1
Bin/CoreData/Scripts/Editor.as

@@ -3,7 +3,7 @@
 #include "Scripts/EditorCamera.as"
 #include "Scripts/EditorScene.as"
 #include "Scripts/EditorUI.as"
-#include "Scripts/EditorImportTundra.as"
+#include "Scripts/EditorImport.as"
 
 void start()
 {

+ 2 - 0
Bin/CoreData/Scripts/EditorCamera.as

@@ -62,6 +62,8 @@ void resetCamera()
 {
     camera.setPosition(Vector3(0, 10, 0));
     camera.setRotation(Quaternion());
+    cameraPitch = 0;
+    cameraYaw = 0;
 }
 
 void createCameraDialog()

+ 3 - 3
Bin/CoreData/Scripts/EditorComponentWindow.as

@@ -152,8 +152,8 @@ void editEntityName()
     beginModify(selectedEntity.getID());
     selectedEntity.setName(nameEdit.getText());
     endModify(selectedEntity.getID());
-    
-    updateSceneWindowEntity(selectedEntity);
+
+    updateSceneWindowEntityOnly(selectedEntity);
 }
 
 void editComponentName()
@@ -368,7 +368,7 @@ void openResource(StringHash eventType, VariantMap& eventData)
 {
     UIElement@ button = eventData["Element"].getUIElement();
     LineEdit@ attrEdit = button.getParent().getChild(1);
-    systemOpenFile(sceneResourcePath + attrEdit.getText(), "edit");
+    systemOpenFile(sceneResourcePath + attrEdit.getText(), "");
 }
 
 void pickResourceDone(StringHash eventType, VariantMap& eventData)

+ 81 - 14
Bin/CoreData/Scripts/EditorImportTundra.as → Bin/CoreData/Scripts/EditorImport.as

@@ -1,9 +1,73 @@
-//Import realXtend Tundra scene file (Much missing features. Handles only everything in the same path)
+// Urho3D editor import functions
+
+void importModel(const string& in fileName)
+{
+    string modelName = "Models/" + getFileName(fileName) + ".mdl";
+    string outFileName = sceneResourcePath + modelName;
+    string materialListName = sceneResourcePath + "_tempmatlist_.txt";
+
+    array<string> args;
+    args.push("model");
+    args.push("\"" + fileName + "\"");
+    args.push("\"" + outFileName + "\"");
+    args.push("-p\"" + sceneResourcePath + "\"");
+    args.push("-m\"" + materialListName + "\"");
+
+    if (systemRun(getExecutableDirectory() + "AssetImporter.exe", args) == 0)
+    {
+        Entity@ newEntity = editorScene.createEntity(getFileName(fileName));
+        StaticModel@ newModel = newEntity.createComponent("StaticModel");
+        newModel.setPosition(camera.getWorldPosition() + camera.getWorldRotation() * Vector3(0, 0, newNodeDistance));
+        newModel.setModel(cache.getResource("Model", modelName));
+
+        if (fileExists(materialListName))
+        {
+            File list(materialListName, FILE_READ);
+            for (uint i = 0; i < newModel.getNumGeometries(); ++i)
+            {
+                if (!list.isEof())
+                    newModel.setMaterial(i, cache.getResource("Material", list.readLine()));
+                else
+                    break;
+            }
+            list.close();
+            deleteFile(materialListName);
+        }
+
+        updateAndFocusNewEntity(newEntity);
+    }
+}
+
+void importScene(const string& in fileName)
+{
+    // Handle Tundra scene files here in code, otherwise via AssetImporter
+    if (getExtension(fileName) == ".txml")
+        importTundraScene(fileName);
+    else
+    {
+        // Export scene to a temp file, then load and delete it if successful
+        string tempSceneName = sceneResourcePath + "_tempscene_.xml";
+        array<string> args;
+        args.push("scene");
+        args.push("\"" + fileName + "\"");
+        args.push("\"" + tempSceneName + "\"");
+        args.push("-p\"" + sceneResourcePath + "\"");
+        
+        if (systemRun(getExecutableDirectory() + "AssetImporter.exe", args) == 0)
+        {
+            string currentFileName = sceneFileName;
+            loadScene(tempSceneName);
+            deleteFile(tempSceneName);
+            sceneFileName = currentFileName;
+            updateWindowTitle();
+        }
+    }
+}
 
 void autoCollisionMesh()
 {
     array<string> createdCollisions;
-    
+
     array<Entity@> entities = editorScene.getAllEntities();
     for (uint i = 0; i < entities.size(); ++i)
     {
@@ -52,7 +116,7 @@ void autoCollisionMesh()
     }
 }
 
-void importTundraScene(string fileName)
+void importTundraScene(const string& in fileName)
 {
     createDirectory(sceneResourcePath + "Materials");
     createDirectory(sceneResourcePath + "Models");
@@ -70,7 +134,8 @@ void importTundraScene(string fileName)
     array<string> convertedMeshes;
     
     // Clear old scene, then create a zone and a directional light first
-    editorScene.removeAllEntities();
+    createScene();
+
     Entity@ zoneEntity = editorScene.createEntity();
     Zone@ zone = zoneEntity.createComponent("Zone");
     Light@ sunLight = zoneEntity.createComponent("Light");
@@ -177,7 +242,7 @@ void importTundraScene(string fileName)
     }
 }
 
-string getComponentAttribute(XMLElement compElem, string name)
+string getComponentAttribute(XMLElement compElem, const string& in name)
 {
     XMLElement attrElem = compElem.getChildElement("attribute");
     while (attrElem.notNull())
@@ -204,13 +269,16 @@ void processRef(string& ref)
 
 void convertModel(const string& in modelName, const string& in filePath)
 {
-    string cmdLine1 = "ogrexmlconverter.exe \"" + filePath + modelName + "\" \"" + filePath + modelName + ".xml\"";
-    string cmdLine2 = getExecutableDirectory() + "OgreImporter.exe \"" + filePath + modelName + ".xml\" \"" + sceneResourcePath + "Models/" + modelName.replace(".mesh", ".mdl") + "\" -a -t";
-    
-    if (!fileExists(filePath + modelName + ".xml"))
-        systemCommand(cmdLine1.replace('/', '\\'));
-    if (!fileExists(sceneResourcePath + "Models/" + modelName.replace(".mesh", ".mdl")))
-        systemCommand(cmdLine2.replace('/', '\\'));
+    // Convert .mesh to .mesh.xml
+    string cmdLine = "ogrexmlconverter.exe \"" + filePath + modelName + "\" \"" + filePath + modelName + ".xml\"";
+    systemCommand(cmdLine.replace('/', '\\'));
+
+    // Convert .mesh.xml to .mdl
+    array<string> args;
+    args.push("\"" + filePath + modelName + ".xml\"");
+    args.push("\"" + sceneResourcePath + "Models/" + modelName.replace(".mesh", ".mdl") + "\"");
+    args.push("-a");
+    systemRun(getExecutableDirectory() + "OgreImporter.exe", args);
 }
 
 void convertMaterial(const string& in materialName, const string& in filePath)
@@ -248,8 +316,7 @@ void convertMaterial(const string& in materialName, const string& in filePath)
     if (twoSided)
         baseName = baseName.replace(".xml", "TS.xml");
     baseElem.setAttribute("name", baseName);
-    
-    
+
     XMLElement techniqueElem = rootElem.createChildElement("technique");
     
     if (!textureName.empty())

+ 33 - 21
Bin/CoreData/Scripts/EditorScene.as

@@ -8,11 +8,13 @@ Window@ sceneSettingsDialog;
 
 string sceneFileName;
 string sceneResourcePath;
+float newNodeDistance = 20;
 bool sceneModified = false;
 bool runPhysics = false;
-bool subscribedToSceneSettingsEdits = false;
+bool renderingDebug = false;
+bool physicsDebug = false;
 bool octreeDebug = false;
-int debugGeometryMode = 0;
+bool subscribedToSceneSettingsEdits = false;
 
 Component@ selectedComponent;
 Entity@ selectedEntity;
@@ -41,7 +43,7 @@ void createScene()
 
     subscribeToEvent("PostRenderUpdate", "scenePostRenderUpdate");
     subscribeToEvent("UIMouseClick", "sceneMouseClick");
-    
+
     engine.setDefaultScene(editorScene);
     
     updateSceneSettingsDialog();
@@ -76,13 +78,17 @@ void updateSceneSettingsDialog()
     
     LineEdit@ gravityEdit = sceneSettingsDialog.getChild("GravityEdit", true);
     gravityEdit.setText(editorScene.getPhysicsWorld().getGravity().toString());
-    
+
+    LineEdit@ physicsFPSEdit = sceneSettingsDialog.getChild("PhysicsFPSEdit", true);
+    physicsFPSEdit.setText(toString(editorScene.getPhysicsWorld().getFps()));
+
     if (!subscribedToSceneSettingsEdits)
     {
         subscribeToEvent(octreeMinEdit, "TextFinished", "editOctreeMin");
         subscribeToEvent(octreeMaxEdit, "TextFinished", "editOctreeMax");
         subscribeToEvent(octreeLevelsEdit, "TextFinished", "editOctreeLevels");
         subscribeToEvent(gravityEdit, "TextFinished", "editGravity");
+        subscribeToEvent(physicsFPSEdit, "TextFinished", "editPhysicsFPS");
         subscribeToEvent(sceneSettingsDialog.getChild("CloseButton", true), "Released", "hideSceneSettingsDialog");
         subscribedToSceneSettingsEdits = true;
     }
@@ -136,8 +142,17 @@ void editGravity(StringHash eventType, VariantMap& eventData)
     edit.setText(editorScene.getPhysicsWorld().getGravity().toString());
 }
 
+void editPhysicsFPS(StringHash eventType, VariantMap& eventData)
+{
+    LineEdit@ edit = eventData["Element"].getUIElement();
+    editorScene.getPhysicsWorld().setFps(edit.getText().toInt());
+    edit.setText(toString(editorScene.getPhysicsWorld().getFps()));
+}
+
 void setResourcePath(string newPath)
 {
+    newPath = fixPath(newPath);
+
     if (newPath == sceneResourcePath)
         return;
 
@@ -149,6 +164,10 @@ void setResourcePath(string newPath)
 
     cache.addResourcePath(newPath);
     sceneResourcePath = newPath;
+    
+    // If scenes were not loaded yet, default load/save to the resource path
+    if (uiScenePath.empty())
+        uiScenePath = newPath;
 }
 
 void reloadResources()
@@ -212,7 +231,7 @@ void loadScene(string fileName)
     editorScene.removeAllEntities();
 
     // Add the new resource path
-    setResourcePath(cache.getPreferredResourcePath(getPath(fileName)));
+    setResourcePath(getPreferredResourcePath(getPath(fileName)));
 
     File file(fileName, FILE_READ);
     string extension = getExtension(fileName);
@@ -307,20 +326,10 @@ void scenePostRenderUpdate()
         }
     }
     
-    switch (debugGeometryMode)
-    {
-        // Draw all renderer debug geometry
-    case 1:
+    if (renderingDebug)
         pipeline.drawDebugGeometry(false);
-        break;
-        
-        // Draw all physics debug geometry
-    case 2:
+    if (physicsDebug)
         editorScene.getPhysicsWorld().drawDebugGeometry(true);
-        break;
-    }
-    
-    // Additionally, draw octree debug
     if (octreeDebug)
         editorScene.getOctree().drawDebugGeometry(true);
     
@@ -353,11 +362,14 @@ void sceneRaycast(bool mouseClick)
     }
 }
 
-void toggleDebugGeometry()
+void toggleRenderingDebug()
+{
+    renderingDebug = !renderingDebug;
+}
+
+void togglePhysicsDebug()
 {
-    ++debugGeometryMode;
-    if (debugGeometryMode == 3)
-        debugGeometryMode = 0;
+    physicsDebug = !physicsDebug;
 }
 
 void toggleOctreeDebug()

+ 37 - 21
Bin/CoreData/Scripts/EditorSceneWindow.as

@@ -147,12 +147,28 @@ void updateSceneWindowEntity(uint itemIndex, Entity@ entity)
     }
 }
 
+void updateSceneWindowEntityOnly(uint itemIndex, Entity@ entity)
+{
+    ListView@ list = sceneWindow.getChild("EntityList", true);
+
+    Text@ text = list.getItem(itemIndex);
+    if (text is null)
+        return;
+    text.setText(getEntityTitle(entity));
+}
+
 void updateSceneWindowEntity(Entity@ entity)
 {
     uint index = getEntityListIndex(entity);
     updateSceneWindowEntity(index, entity);
 }
 
+void updateSceneWindowEntityOnly(Entity@ entity)
+{
+    uint index = getEntityListIndex(entity);
+    updateSceneWindowEntityOnly(index, entity);
+}
+
 void addComponentToSceneWindow(Entity@ entity, array<Component@>@ components, uint componentID, int indent)
 {
     ListView@ list = sceneWindow.getChild("EntityList", true);
@@ -534,6 +550,14 @@ void calculateNewTransform(Node@ source, Node@ target, Vector3& pos, Quaternion&
     pos = inverseTargetWorldScale * (inverseTargetWorldRot * (sourceWorldPos - target.getWorldPosition()));
 }
 
+void updateAndFocusNewEntity(Entity@ newEntity)
+{
+    updateSceneWindowEntity(newEntity);
+    uint index = getEntityListIndex(newEntity);
+    ListView@ list = sceneWindow.getChild("EntityList", true);
+    list.setSelection(index);
+}
+
 void handleCreateEntity(StringHash eventType, VariantMap& eventData)
 {
     DropDownList@ list = eventData["Element"].getUIElement();
@@ -543,11 +567,7 @@ void handleCreateEntity(StringHash eventType, VariantMap& eventData)
     bool local = (mode == 1);
     
     Entity@ newEntity = editorScene.createEntity("", local);
-    updateSceneWindowEntity(newEntity);
-    uint index = getEntityListIndex(newEntity);
-
-    ListView@ entityList = sceneWindow.getChild("EntityList", true);
-    entityList.setSelection(index);
+    updateAndFocusNewEntity(newEntity);
 }
 
 void handleCreateComponent(StringHash eventType, VariantMap& eventData)
@@ -600,29 +620,29 @@ void sceneDelete()
         
         @selectedComponent = null;
         
-        updateSceneWindowEntity(entityIndex, entity);
+        updateSceneWindowEntity(entityIndex, selectedEntity);
         
         // Select the next item in the same index
         list.setSelection(index);
     }
     // Remove entity
-    if ((selectedEntity !is null) && (selectedComponent is null))
+    else if ((selectedEntity !is null) && (selectedComponent is null))
     {
         // Entity operations are dangerous. Require the scene hierarchy to be focused
         if (!checkSceneWindowFocus(false))
             return;
-         
+
         uint id = selectedEntity.getID();
-        
+
         beginModify(id);
         editorScene.removeEntity(selectedEntity);
         endModify(id);
-        
+
         @selectedComponent = null;
         @selectedEntity = null;
-        
+
         updateSceneWindowEntity(entityIndex, null);
-        
+
         // Select the next item in the same index
         list.setSelection(index);
     }
@@ -659,7 +679,7 @@ void sceneCopy()
         copyBufferEntityID = selectedEntity.getID();
     }
     // Copy entity
-    if ((selectedEntity !is null) && (selectedComponent is null))
+    else if ((selectedEntity !is null) && (selectedComponent is null))
     {
         if (!checkSceneWindowFocus(false))
             return;
@@ -705,8 +725,7 @@ void scenePaste()
         
         updateSceneWindowEntity(selectedEntity);
     }
-    
-    if (mode == "entity")
+    else if (mode == "entity")
     {
         if (!checkSceneWindowFocus(false))
             return;
@@ -716,7 +735,7 @@ void scenePaste()
         uint newEntityID = newEntity.getID();
         
         beginModify(newEntityID);
-        
+
         // Before loading, rewrite scene node references to the copied entity
         XMLElement compElem = rootElem.getChildElement("component");
         bool rewrite = false;
@@ -740,10 +759,7 @@ void scenePaste()
         
         endModify(newEntityID);
         
-        updateSceneWindowEntity(newEntity);
-        uint index = getEntityListIndex(newEntity);
-        
-        ListView@ list = sceneWindow.getChild("EntityList", true);
-        list.setSelection(index);
+        updateAndFocusNewEntity(newEntity);
     }
 }
+

+ 45 - 12
Bin/CoreData/Scripts/EditorUI.as

@@ -5,10 +5,10 @@ UIElement@ uiMenuBar;
 FileSelector@ uiFileSelector;
 
 array<string> uiSceneFilters = {"*.xml", "*.bin", "*.dat", "*.*"};
-array<string> tundraSceneFilter = {"*.txml"};
 array<string> uiAllFilter = {"*.*"};
 uint uiSceneFilter = 0;
 string uiScenePath;
+string uiImportPath;
 
 void createUI()
 {
@@ -57,12 +57,13 @@ void createMenuBar()
         filePopup.addChild(createMenuItem("Save scene", 'S', QUAL_CTRL));
         filePopup.addChild(createMenuItem("Save scene as", 'S', QUAL_SHIFT | QUAL_CTRL));
         filePopup.addChild(createMenuDivider());
-        filePopup.addChild(createMenuItem("Import Tundra scene", 0, 0));
+        filePopup.addChild(createMenuItem("Import model", 0, 0));
+        filePopup.addChild(createMenuItem("Import scene", 0, 0));
         filePopup.addChild(createMenuDivider());
         filePopup.addChild(createMenuItem("Set resource path", 0, 0));
         filePopup.addChild(createMenuItem("Reload resources", 'R', QUAL_CTRL));
         filePopup.addChild(createMenuDivider());
-        filePopup.addChild(createMenuItem("Exit", 'X', QUAL_CTRL));
+        filePopup.addChild(createMenuItem("Exit", 0, 0));
         uiMenuBar.addChild(fileMenu);
     }
 
@@ -81,8 +82,8 @@ void createMenuBar()
     {
         Menu@ fileMenu = createMenu("View");
         Window@ filePopup = fileMenu.getPopup();
-        filePopup.addChild(createMenuItem("Scene hierarchy", 0, 0));
-        filePopup.addChild(createMenuItem("Entity / component edit", 0, 0));
+        filePopup.addChild(createMenuItem("Scene hierarchy", 'H', QUAL_CTRL));
+        filePopup.addChild(createMenuItem("Entity / component edit", 'E', QUAL_CTRL));
         filePopup.addChild(createMenuItem("Global scene settings", 0, 0));
         filePopup.addChild(createMenuItem("Camera settings", 0, 0));
         uiMenuBar.addChild(fileMenu);
@@ -219,10 +220,16 @@ void handleMenuSelected(StringHash eventType, VariantMap& eventData)
             subscribeToEvent(uiFileSelector, "FileSelected", "handleSaveSceneFile");
         }
 
-        if (action == "Import Tundra scene")
+        if (action == "Import model")
         {
-            createFileSelector("Import Tundra scene", "Import", "Cancel", "", tundraSceneFilter, 0);
-            subscribeToEvent(uiFileSelector, "FileSelected", "handleImportTundraFile");
+            createFileSelector("Import model", "Import", "Cancel", uiImportPath, uiAllFilter, 0);
+            subscribeToEvent(uiFileSelector, "FileSelected", "handleImportModel");
+        }
+
+        if (action == "Import scene")
+        {
+            createFileSelector("Import scene", "Import", "Cancel", uiImportPath, uiAllFilter, 0);
+            subscribeToEvent(uiFileSelector, "FileSelected", "handleImportScene");
         }
         
         if (action == "Set resource path")
@@ -297,8 +304,10 @@ void handleSaveSceneFile(StringHash eventType, VariantMap& eventData)
     saveScene(fileName);
 }
 
-void handleImportTundraFile(StringHash eventType, VariantMap& eventData)
+void handleImportModel(StringHash eventType, VariantMap& eventData)
 {
+    // Save path for next time
+    uiImportPath = uiFileSelector.getPath();
     closeFileSelector();
 
     // Check for cancel
@@ -306,7 +315,21 @@ void handleImportTundraFile(StringHash eventType, VariantMap& eventData)
         return;
 
     string fileName = eventData["FileName"].getString();
-    importTundraScene(fileName);
+    importModel(fileName);
+}
+
+void handleImportScene(StringHash eventType, VariantMap& eventData)
+{
+    // Save path for next time
+    uiImportPath = uiFileSelector.getPath();
+    closeFileSelector();
+
+    // Check for cancel
+    if (!eventData["OK"].getBool())
+        return;
+
+    string fileName = eventData["FileName"].getString();
+    importScene(fileName);
 }
 
 void handleResourcePath(StringHash eventType, VariantMap& eventData)
@@ -329,9 +352,19 @@ void handleKeyDown(StringHash eventType, VariantMap& eventData)
         console.toggle();
         input.suppressNextChar();
     }
-    
+    if (key == KEY_ESC)
+    {
+        UIElement@ front = ui.getFrontElement();
+        if ((uiFileSelector !is null) && (front is uiFileSelector.getWindow()))
+            closeFileSelector();
+        else if ((front is sceneSettingsDialog) || (front is cameraDialog) || (front is sceneWindow) || (front is componentWindow))
+            front.setVisible(false);
+    }
+
     if (key == KEY_F1)
-        toggleDebugGeometry();
+        toggleRenderingDebug();
     if (key == KEY_F2)
+        togglePhysicsDebug();
+    if (key == KEY_F3)
         toggleOctreeDebug();
 }

+ 10 - 0
Bin/CoreData/UI/SceneSettingsDialog.xml

@@ -52,4 +52,14 @@
             <fixedwidth value="160" />
        </element>
     </element>
+    <element>
+        <fixedheight value="17" />
+        <layout mode="horizontal" spacing="20" />
+        <element type="Text">
+            <text value="Physics FPS" />
+        </element>
+       <element type="LineEdit" name="PhysicsFPSEdit">
+            <fixedwidth value="160" />
+       </element>
+    </element>
 </element>

+ 1 - 1
Bin/Editor.bat

@@ -1 +1 @@
-Urho3D Scripts/Editor.as -w -x1024 -y600 %1 %2 %3 %4 %5 %6 %7 %8
+Urho3D Scripts/Editor.as -w -x1024 -y768 %1 %2 %3 %4 %5 %6 %7 %8

+ 86 - 0
Editor.txt

@@ -0,0 +1,86 @@
+Urho3D scene editor
+-------------------
+
+The Urho3D scene editor is a script that can be executed through the Urho3D Shell.
+To run, execute either of these commands: (from the Bin directory)
+Editor.bat
+Urho3D.exe Scripts/Editor.as
+
+
+Controls
+--------
+
+Left mouse     - Select renderable components
+Right mouse    - Hold down and move mouse to rotate camera
+
+WSAD or arrows - Move
+Shift+WSAD     - Move faster
+Ctrl+O         - Open scene
+Ctrl+S         - Save scene
+Ctrl+Shift+S   - Save scene as
+Ctrl+X,C,V     - Cut/copy/paste entity or component
+Ctrl+E	       - Open the Entity / component edit window
+Ctrl+H	       - Open the Scene hierarchy window
+Ctrl+P         - Toggle physics on/off
+Ctrl+R         - Reload resources
+ESC            - Close the topmost window
+DEL            - Delete entity or component
+F1             - Toggle rendering debug geometry
+F2             - Toggle physics debug geometry
+F3             - Toggle octree debug geometry
+
+
+Workflow
+--------
+
+When you start with an empty scene, set the resource path first (File -> Set 
+resource path). This is the base directory, under which the subdirectories 
+Models, Materials & Textures will be created as you import assets.
+
+Scenes should be saved either into this base directory, or into its immediate
+subdirectory, named for example Scenes or Levels. When loading a scene, the
+resource path will be set automatically.
+
+Check the Global scene settings & Camera settings so that they match the
+size of the objects you are using.
+
+
+Editing
+-------
+
+Editing components is accomplished through the Entity / component edit window,
+where the attributes of the selected component will be displayed. For scene
+nodes, note that the transform shown is the local transform (offset from parent)
+
+To reparent scene nodes, drag and drop them onto the new parent scene node in 
+the Scene hierarchy window. They will also be automatically moved into the same
+entity as where the parent node resides. To make a child node unparented, drag
+it onto its entity. Reparenting should retain the effective world transform, so
+check afterwards from the component window that the local transform is what you 
+expect it to be.
+
+Currently, when you edit for example a material or texture, you need to manually 
+reload resources (Ctrl+R) to make the changes visible.
+
+
+Importing
+---------
+
+The editor can import models or scenes from all the formats that the Open Asset 
+Import Library supports, see 
+http://assimp.sourceforge.net/main_features_formats.html
+
+Model and scene import work differently: model import will take everything in
+the source file (for example a Collada scene), and combine it into a single
+model, with possibly many subgeometries. Scene import on the other hand will 
+export each source scene node separately, creating multiple models as necessary.
+
+When a model is imported, it will also be instantiated into the scene as a
+new entity with a StaticModel component.
+
+To do the actual importing, the editor will invoke AssetImporter.exe from the
+same directory as where Urho3D.exe resides, so be sure both are built.
+
+Importing lights is not properly supported yet. Instead, when a scene is
+imported, a zone for ambient lighting and a single directional light are created,
+so that you can at least see something.

+ 54 - 4
Engine/Common/File.cpp

@@ -30,6 +30,7 @@
 
 #include <cstdlib>
 #include <direct.h>
+#include <process.h>
 #include <windows.h>
 #include <shellapi.h>
 
@@ -247,7 +248,7 @@ bool createDirectory(const std::string& pathName)
         return false;
     }
     
-    bool success = (CreateDirectory(getOSPath(pathName, true).c_str(), 0) == TRUE) || (GetLastError() == ERROR_ALREADY_EXISTS);
+    bool success = (CreateDirectory(getOSPath(unfixPath(pathName), true).c_str(), 0) == TRUE) || (GetLastError() == ERROR_ALREADY_EXISTS);
     if (success)
         LOGDEBUG("Created directory " + pathName);
     else
@@ -259,9 +260,27 @@ bool createDirectory(const std::string& pathName)
 int systemCommand(const std::string& commandLine)
 {
     if (allowedDirectories.empty())
-    {
-        LOGINFO("Executing system command: " + commandLine);
         return system(commandLine.c_str());
+    else
+    {
+        LOGERROR("Executing an external command is not allowed");
+        return -1;
+    }
+}
+
+int systemRun(const std::string& fileName, const std::vector<std::string>& arguments)
+{
+    if (allowedDirectories.empty())
+    {
+        std::string fixedFileName = getOSPath(fileName, true);
+        
+        std::vector<const char*> argPtrs;
+        argPtrs.push_back(fixedFileName.c_str());
+        for (unsigned i = 0; i < arguments.size(); ++i)
+            argPtrs.push_back(arguments[i].c_str());
+        argPtrs.push_back(0);
+        
+        return _spawnv(_P_WAIT, fixedFileName.c_str(), &argPtrs[0]);
     }
     else
     {
@@ -280,7 +299,10 @@ bool systemOpenFile(const std::string& fileName, const std::string& mode)
             return false;
         }
         
-        return (int)ShellExecute(0, !mode.empty() ? (char*)mode.c_str() : 0, (char*)getOSPath(fileName, true).c_str(), 0, 0, SW_SHOW) > 32;
+        bool success = (int)ShellExecute(0, !mode.empty() ? (char*)mode.c_str() : 0, (char*)getOSPath(fileName, true).c_str(), 0, 0, SW_SHOW) > 32;
+        if (!success)
+            LOGERROR("Failed to open " + fileName + " externally");
+        return success;
     }
     else
     {
@@ -301,6 +323,7 @@ bool copyFile(const std::string& srcFileName, const std::string& destFileName)
         LOGERROR("Access denied to " + destFileName);
         return false;
     }
+    
     try
     {
         File srcFile(srcFileName, FILE_READ);
@@ -320,6 +343,33 @@ bool copyFile(const std::string& srcFileName, const std::string& destFileName)
     return true;
 }
 
+bool renameFile(const std::string& srcFileName, const std::string& destFileName)
+{
+    if (!checkDirectoryAccess(getPath(srcFileName)))
+    {
+        LOGERROR("Access denied to " + srcFileName);
+        return false;
+    }
+    if (!checkDirectoryAccess(getPath(destFileName)))
+    {
+        LOGERROR("Access denied to " + destFileName);
+        return false;
+    }
+    
+    return rename(getOSPath(srcFileName).c_str(), getOSPath(destFileName).c_str()) == 0;
+}
+
+bool deleteFile(const std::string& fileName)
+{
+    if (!checkDirectoryAccess(getPath(fileName)))
+    {
+        LOGERROR("Access denied to " + fileName);
+        return false;
+    }
+    
+    return remove(getOSPath(fileName).c_str()) == 0;
+}
+
 void registerDirectory(const std::string& pathName)
 {
     if (pathName.empty())

+ 7 - 1
Engine/Common/File.h

@@ -105,12 +105,18 @@ std::string getCurrentDirectory();
 bool setCurrentDirectory(const std::string& pathName);
 //! Create a directory
 bool createDirectory(const std::string& pathName);
-//! Execute an external command, block until it exists and return the exit code. Will fail if any allowed paths are defined
+//! Run a program using the command interpreter, block until it exits and return the exit code. Will fail if any allowed paths are defined
 int systemCommand(const std::string& commandLine);
+//! Run a specific program, block until it exists and return the exit code. Will fail if any allowed paths are defined
+int systemRun(const std::string& fileName, const std::vector<std::string>& arguments);
 //! Open a file in an external program, with mode such as "edit" optionally specified. Will fail if any allowed paths are defined
 bool systemOpenFile(const std::string& fileName, const std::string& mode = std::string());
 //! Copy a file. Return true if successful
 bool copyFile(const std::string& srcFileName, const std::string& destFileName);
+//! Rename a file. Return true if successful
+bool renameFile(const std::string& srcFileName, const std::string& destFileName);
+//! Delete a file. Return true if successful
+bool deleteFile(const std::string& fileName);
 //! 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

+ 7 - 0
Engine/Common/Serializer.cpp

@@ -216,3 +216,10 @@ void Serializer::writeVLE(unsigned value)
         writeUByte(value >> 21);
     }
 }
+
+void Serializer::writeLine(const std::string& value)
+{
+    write(value.c_str(), value.length());
+    writeUByte(13);
+    writeUByte(10);
+}

+ 2 - 0
Engine/Common/Serializer.h

@@ -106,6 +106,8 @@ public:
     void writeVariantMap(const VariantMap& value);
     //! Write a variable-length encoded unsigned integer, which can use 29 bits maximum
     void writeVLE(unsigned value);
+    //! Write a text line. Char codes 13 & 10 will be automatically appended.
+    void writeLine(const std::string& value);
 };
 
 #endif // COMMON_SERIALIZER_H

+ 19 - 0
Engine/Engine/RegisterCommon.cpp

@@ -563,6 +563,22 @@ static CScriptArray* ScanDirectory(const std::string& pathName, const std::strin
     return vectorToArray<std::string>(result, "array<string>");
 }
 
+static int SystemRun(const std::string& fileName, CScriptArray* srcArguments)
+{
+    if (!srcArguments)
+        return -1;
+    
+    unsigned numArguments = srcArguments->GetSize();
+    std::vector<std::string> destArguments(numArguments);
+    for (unsigned i = 0; i < numArguments; ++i)
+        destArguments[i] = *(static_cast<std::string*>(srcArguments->At(i)));
+    
+    LOGINFO("Systemrun: " + fileName);
+    for (unsigned i = 0; i < destArguments.size(); ++i)
+        LOGINFO(destArguments[i]);
+    return systemRun(fileName, destArguments);
+}
+
 static void registerSerialization(asIScriptEngine* engine)
 {
     engine->RegisterObjectType("Serializer", 0, asOBJ_REF);
@@ -601,8 +617,11 @@ static void registerSerialization(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("void setCurrentDirectory(const string& in)", asFUNCTION(setCurrentDirectory), asCALL_CDECL);
     engine->RegisterGlobalFunction("bool createDirectory(const string& in)", asFUNCTION(createDirectory), asCALL_CDECL);
     engine->RegisterGlobalFunction("int systemCommand(const string& in)", asFUNCTION(systemCommand), asCALL_CDECL);
+    engine->RegisterGlobalFunction("int systemRun(const string& in, array<string>@+)", asFUNCTION(SystemRun), asCALL_CDECL);
     engine->RegisterGlobalFunction("bool systemOpenFile(const string& in, const string& in)", asFUNCTION(systemOpenFile), asCALL_CDECL);
     engine->RegisterGlobalFunction("bool copyFile(const string& in, const string& in)", asFUNCTION(copyFile), asCALL_CDECL);
+    engine->RegisterGlobalFunction("bool renameFile(const string& in, const string& in)", asFUNCTION(renameFile), asCALL_CDECL);
+    engine->RegisterGlobalFunction("bool deleteFile(const string& in)", asFUNCTION(deleteFile), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getPath(const string& in)", asFUNCTION(getPath), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getFileName(const string& in)", asFUNCTION(getFileName), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getExtension(const string& in)", asFUNCTION(getExtension), asCALL_CDECL);

+ 1 - 2
Engine/Engine/RegisterRenderer.cpp

@@ -817,6 +817,7 @@ static void registerRenderer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Renderer", "void setWindowTitle(const string& in)", asMETHOD(Renderer, setWindowTitle), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void setMode(RenderMode, int, int, bool, bool, int)", asMETHODPR(Renderer, setMode, (RenderMode, int, int, bool, bool, int), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void setMode(int, int)", asMETHODPR(Renderer, setMode, (int, int), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Renderer", "void setMode(RenderMode)", asMETHODPR(Renderer, setMode, (RenderMode), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void toggleFullscreen()", asMETHOD(Renderer, toggleFullscreen), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "void close()", asMETHOD(Renderer, close), asCALL_THISCALL);
     engine->RegisterObjectMethod("Renderer", "bool takeScreenShot(Image@+)", asMETHOD(Renderer, takeScreenShot), asCALL_THISCALL);
@@ -1034,11 +1035,9 @@ static void registerOctree(asIScriptEngine* engine)
     
     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, setExcludeFlags), asCALL_THISCALL);
     engine->RegisterObjectMethod("Octree", "void drawDebugGeometry(bool) const", asMETHOD(Octree, drawDebugGeometry), 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, float, RayQueryLevel)", asFUNCTION(OctreeRaycast), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Octree", "array<Node@>@ getNodes(const Vector3& in, uint)", asFUNCTION(OctreeGetNodesPoint), asCALL_CDECL_OBJLAST);

+ 2 - 1
Engine/Engine/RegisterResource.cpp

@@ -114,10 +114,11 @@ static void registerResourceCache(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ResourceCache", "uint getMemoryBudget(const string& in) const", asFUNCTION(ResourceCacheGetMemoryBudget), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ResourceCache", "uint getMemoryUse(const string& in) const", asFUNCTION(ResourceCacheGetMemoryUse), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("ResourceCache", "uint getTotalMemoryUse() const", asMETHOD(ResourceCache, getTotalMemoryUse), asCALL_THISCALL);
-    engine->RegisterObjectMethod("ResourceCache", "string getPreferredResourcePath(const string& in) const", asMETHOD(ResourceCache, getPreferredResourcePath), asCALL_THISCALL);
     engine->RegisterGlobalFunction("ResourceCache@+ getResourceCache()", asFUNCTION(GetResourceCache), asCALL_CDECL);
     engine->RegisterGlobalFunction("ResourceCache@+ get_resourceCache()", asFUNCTION(GetResourceCache), asCALL_CDECL);
     engine->RegisterGlobalFunction("ResourceCache@+ get_cache()", asFUNCTION(GetResourceCache), asCALL_CDECL);
+    
+    engine->RegisterGlobalFunction("string getPreferredResourcePath(const string& in)", asFUNCTION(getPreferredResourcePath), asCALL_CDECL);
 }
 
 static Image* ConstructImage(const std::string& name)

+ 1 - 0
Engine/Engine/RegisterTemplates.h

@@ -202,6 +202,7 @@ template <class T> void registerSerializer(asIScriptEngine* engine, const char*
     engine->RegisterObjectMethod(className, "void writeVariant(const Variant& in)", asMETHODPR(T, writeVariant, (const Variant&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void writeVariantMap(const VariantMap& in)", asMETHODPR(T, writeVariantMap, (const VariantMap&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void writeVLE(uint)", asMETHODPR(T, writeVLE, (unsigned), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void writeLine(const string& in)", asMETHODPR(T, writeLine, (const std::string&), void), asCALL_THISCALL);
 }
 
 //! Template function for registering a class derived from Deserializer

+ 2 - 1
Engine/Engine/RegisterUI.cpp

@@ -511,7 +511,8 @@ static void registerUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "Cursor@+ getCursor() const", asMETHOD(UI, getCursor), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ getElementAt(const IntVector2& in, bool)", asMETHODPR(UI, getElementAt, (const IntVector2&, bool), UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ getElementAt(int, int, bool)", asMETHODPR(UI, getElementAt, (int, int, bool), UIElement*), asCALL_THISCALL);
-    engine->RegisterObjectMethod("UI", "UIElement@+ getFocusElement()", asMETHOD(UI, getFocusElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "UIElement@+ getFocusElement() const", asMETHOD(UI, getFocusElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "UIElement@+ getFrontElement() const", asMETHOD(UI, getFrontElement), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "IntVector2 getCursorPosition()", asMETHOD(UI, getCursorPosition), asCALL_THISCALL);
     registerRefCasts<EventListener, UI>(engine, "EventListener", "UI");
     

+ 1 - 1
Engine/Renderer/BillboardSet.h

@@ -32,7 +32,7 @@ class IndexBuffer;
 class Renderer;
 class VertexBuffer;
 
-//! Billboard state
+//! One billboard in the billboard set
 struct Billboard
 {
     //! Position

+ 6 - 28
Engine/Renderer/Octree.cpp

@@ -40,8 +40,6 @@
 #pragma warning(disable:4355)
 #endif
 
-static unsigned excludeFlags = 0;
-
 inline static bool compareRayQueryResults(const RayQueryResult& lhs, const RayQueryResult& rhs)
 {
     return lhs.mDistance < rhs.mDistance;
@@ -120,9 +118,11 @@ void Octant::insertNode(VolumeNode* node)
     {
         if (node->mOctant != this)
         {
-            if (node->mOctant)
-                node->mOctant->removeNode(node);
+            // Add first, then remove, because node count going to zero deletes the octree branch in question
+            Octant* oldOctant = node->mOctant;
             addNode(node);
+            if (oldOctant)
+                oldOctant->removeNode(node);
         }
         return;
     }
@@ -191,9 +191,7 @@ void Octant::getNodesInternal(OctreeQuery& query, unsigned mask) const
         VolumeNode* node = *i;
         unsigned nodeFlags = node->getNodeFlags();
         
-        if ((!(nodeFlags & query.mNodeFlags)) || (nodeFlags & excludeFlags))
-            continue;
-        if (!node->isVisible())
+        if ((!(nodeFlags & query.mNodeFlags)) || (!node->isVisible()))
             continue;
         if ((query.mOccludersOnly) && (!node->isOccluder()))
             continue;
@@ -225,9 +223,7 @@ void Octant::getNodesInternal(RayOctreeQuery& query) const
         VolumeNode* node = *i;
         unsigned nodeFlags = node->getNodeFlags();
         
-        if ((!(nodeFlags & query.mNodeFlags)) || (nodeFlags & excludeFlags))
-            continue;
-        if (!node->isVisible())
+        if ((!(nodeFlags & query.mNodeFlags)) || (!node->isVisible()))
             continue;
         if ((query.mOccludersOnly) && (!node->isOccluder()))
             continue;
@@ -275,7 +271,6 @@ void Octant::release()
 Octree::Octree(const BoundingBox& box, unsigned numLevels, bool headless) :
     Octant(box, 0, 0, this),
     mNumLevels(max((int)numLevels, 1)),
-    mExcludeFlags(0),
     mDrawDebugGeometry(false),
     mHeadless(headless)
 {
@@ -350,11 +345,6 @@ void Octree::resize(const BoundingBox& box, unsigned numLevels)
     mCullingBox = BoundingBox(mWorldBoundingBox.mMin - halfSize, mWorldBoundingBox.mMax + halfSize);
 }
 
-void Octree::setExcludeFlags(unsigned nodeFlags)
-{
-    mExcludeFlags = nodeFlags;
-}
-
 void Octree::updateOctree(const FrameInfo& frame)
 {
     {
@@ -392,17 +382,7 @@ void Octree::updateOctree(const FrameInfo& frame)
                 }
                 
                 if (reinsert)
-                {
                     insertNode(node);
-                    
-                    // If old octant (not root) has become empty, delete it
-                    while ((octant != this) && (octant->isEmpty()))
-                    {
-                        Octant* parent = octant->getParent();
-                        parent->deleteChild(octant);
-                        octant = parent;
-                    }
-                }
             }
             else
                 insertNode(node);
@@ -417,7 +397,6 @@ void Octree::getNodes(OctreeQuery& query) const
 {
     PROFILE(Octree_GetNodes);
     
-    excludeFlags = mExcludeFlags;
     query.mResult.clear();
     getNodesInternal(query, 0);
 }
@@ -426,7 +405,6 @@ 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);

+ 12 - 10
Engine/Renderer/Octree.h

@@ -72,7 +72,6 @@ public:
         {
             if (*i == node)
             {
-                node->mOctant = 0;
                 mNodes.erase(i);
                 decNodeCount();
                 return;
@@ -114,12 +113,20 @@ protected:
             mParent->incNodeCount();
     }
     
-    //! Decrease scene node count recursively
+    //! Decrease scene node count recursively and remove octant if it became empty
     void decNodeCount()
     {
+        Octant* parent = mParent;
+        
         mNumNodes--;
-        if (mParent)
-            mParent->decNodeCount();
+        if (!mNumNodes)
+        {
+            if (parent)
+                parent->deleteChild(this);
+        }
+        
+        if (parent)
+            parent->decNodeCount();
     }
     
     //! World bounding box
@@ -168,8 +175,7 @@ public:
     
     //! 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);
     
@@ -179,8 +185,6 @@ public:
     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; }
     
@@ -202,8 +206,6 @@ private:
     std::set<VolumeNode*> mNodeReinsertions;
     //! Subdivision level
     unsigned mNumLevels;
-    //! Global query exclude flags
-    unsigned mExcludeFlags;
     //! Headless mode flag
     bool mHeadless;
     //! Debug draw flag

+ 5 - 0
Engine/Renderer/Renderer.cpp

@@ -356,6 +356,11 @@ void Renderer::setMode(int width, int height)
     setMode(mMode, width, height, mFullscreen, mVsync, mMultiSample);
 }
 
+void Renderer::setMode(RenderMode mode)
+{
+    setMode(mode, mWidth, mHeight, mFullscreen, mVsync, mMultiSample);
+}
+
 void Renderer::toggleFullscreen()
 {
     setMode(mMode, mWidth, mHeight, !mFullscreen, mVsync, mMultiSample);

+ 2 - 0
Engine/Renderer/Renderer.h

@@ -71,6 +71,8 @@ public:
     void setMode(RenderMode mode, int width, int height, bool fullscreen, bool vsync, int multiSample);
     //! Set screen resolution only
     void setMode(int width, int height);
+    //! Set rendering mode only
+    void setMode(RenderMode mode);
     //! Toggle between full screen and windowed mode
     void toggleFullscreen();
     //! Close the window

+ 50 - 50
Engine/Resource/ResourceCache.cpp

@@ -430,56 +430,6 @@ unsigned ResourceCache::getTotalMemoryUse() const
     return total;
 }
 
-std::string ResourceCache::getPreferredResourcePath(const std::string& path)
-{
-    std::string fixedPath = fixPath(path);
-    
-    // Check for the existence of some known resource subdirectories to decide if we should add the parent directory instead
-    static const std::string checkDirs[] = {
-        "Fonts",
-        "Materials",
-        "Models",
-        "Music",
-        "Particle",
-        "Physics",
-        "Scripts",
-        "Sounds",
-        "Shaders",
-        "Textures",
-        "UI",
-        ""
-    };
-    
-    bool pathHasKnownDirs = false;
-    bool parentHasKnownDirs = false;
-    
-    for (unsigned i = 0; !checkDirs[i].empty(); ++i)
-    {
-        if (directoryExists(fixedPath + checkDirs[i]))
-        {
-            pathHasKnownDirs = true;
-            break;
-        }
-    }
-    if (!pathHasKnownDirs)
-    {
-        std::string parentPath = getParentPath(fixedPath);
-        for (unsigned i = 0; !checkDirs[i].empty(); ++i)
-        {
-            if (directoryExists(parentPath + checkDirs[i]))
-            {
-                parentHasKnownDirs = true;
-                break;
-            }
-        }
-        // If path does not have known subdirectories, but the parent path has, use the parent instead
-        if (parentHasKnownDirs)
-            fixedPath = parentPath;
-    }
-    
-    return fixedPath;
-}
-
 const SharedPtr<Resource>& ResourceCache::findResource(ShortStringHash type, StringHash nameHash)
 {
     static const SharedPtr<Resource> noResource;
@@ -564,3 +514,53 @@ void ResourceCache::updateResourceGroup(ShortStringHash type)
             break;
     }
 }
+
+std::string getPreferredResourcePath(const std::string& path)
+{
+    std::string fixedPath = fixPath(path);
+    
+    // Check for the existence of known resource subdirectories to decide if we should add the parent directory instead
+    static const std::string checkDirs[] = {
+        "Fonts",
+        "Materials",
+        "Models",
+        "Music",
+        "Particle",
+        "Physics",
+        "Scripts",
+        "Sounds",
+        "Shaders",
+        "Textures",
+        "UI",
+        ""
+    };
+    
+    bool pathHasKnownDirs = false;
+    bool parentHasKnownDirs = false;
+    
+    for (unsigned i = 0; !checkDirs[i].empty(); ++i)
+    {
+        if (directoryExists(fixedPath + checkDirs[i]))
+        {
+            pathHasKnownDirs = true;
+            break;
+        }
+    }
+    if (!pathHasKnownDirs)
+    {
+        std::string parentPath = getParentPath(fixedPath);
+        for (unsigned i = 0; !checkDirs[i].empty(); ++i)
+        {
+            if (directoryExists(parentPath + checkDirs[i]))
+            {
+                parentHasKnownDirs = true;
+                break;
+            }
+        }
+        // If path does not have known subdirectories, but the parent path has, use the parent instead
+        if (parentHasKnownDirs)
+            fixedPath = parentPath;
+    }
+    
+    return fixedPath;
+}

+ 3 - 2
Engine/Resource/ResourceCache.h

@@ -121,8 +121,6 @@ public:
     unsigned getMemoryUse(ShortStringHash type) const;
     //! Return total memory use for all resources
     unsigned getTotalMemoryUse() const;
-    //! Return a "preferred resource path" ie. either the path itself or its parent, based on which of them has known subdirectories
-    std::string getPreferredResourcePath(const std::string& path);
     
 private:
     //! Find a resource
@@ -168,4 +166,7 @@ template <class T> void ResourceCache::getResources(std::vector<T*>& result) con
     }
 }
 
+//! Return either the path itself or its parent, based on which of them has recognized resource subdirectories
+std::string getPreferredResourcePath(const std::string& path);
+
 #endif // RESOURCE_RESOURCECACHE_H

+ 0 - 5
Engine/Scene/Node.cpp

@@ -467,11 +467,6 @@ 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 == this) || (node->mParent == this) || (mParent == node))

+ 0 - 4
Engine/Scene/Node.h

@@ -48,8 +48,6 @@ 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;
@@ -122,8 +120,6 @@ 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

+ 1 - 0
Engine/Script/ScriptEventListener.h

@@ -28,6 +28,7 @@
 
 class asIScriptFunction;
 
+//! Base class for event listeners that forward events to script
 class ScriptEventListener : public EventListener
 {
 public:

+ 1 - 2
Engine/UI/Button.cpp

@@ -96,6 +96,7 @@ void Button::onHover(const IntVector2& position, const IntVector2& screenPositio
 {
     bool oldPressed = mPressed;
     setPressed((buttons & MOUSEB_LEFT) != 0);
+    mHovering = true;
     
     if ((oldPressed) && (!mPressed))
     {
@@ -105,8 +106,6 @@ void Button::onHover(const IntVector2& position, const IntVector2& screenPositio
         eventData[P_ELEMENT] = (void*)this;
         sendEvent(EVENT_RELEASED, eventData);
     }
-    
-    mHovering = true;
 }
 
 void Button::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)

+ 1 - 0
Engine/UI/Cursor.h

@@ -38,6 +38,7 @@ enum CursorShape
     CS_MAX_SHAPES
 };
 
+//! Cursor image and hotspot information
 struct CursorShapeData
 {
     SharedPtr<Texture> mTexture;

+ 1 - 3
Engine/UI/FileSelector.cpp

@@ -55,7 +55,7 @@ FileSelector::FileSelector(UI* ui) :
     if (!mUI)
         EXCEPTION("Null UI for FileSelector");
     
-    mWindow = new Window();
+    mWindow = new Window("FileSelector");
     mWindow->setLayout(LM_VERTICAL);
     
     mTitleLayout = new UIElement();
@@ -133,8 +133,6 @@ FileSelector::FileSelector(UI* ui) :
 FileSelector::~FileSelector()
 {
     UIElement* root = mUI->getRootElement();
-    //! \todo This should not be necessary
-    root->removeChild(mFilterList->getPopup());
     root->removeChild(mWindow);
 }
 

+ 1 - 1
Engine/UI/FileSelector.h

@@ -36,7 +36,7 @@ class UIElement;
 class Window;
 class XMLFile;
 
-//! File selector entry
+//! File selector's list entry (file or directory)
 struct FileSelectorEntry
 {
     //! Name

+ 3 - 3
Engine/UI/LineEdit.cpp

@@ -321,7 +321,7 @@ void LineEdit::onKey(int key, int buttons, int qualifiers)
             eventData[P_QUALIFIERS] = qualifiers;
             sendEvent(EVENT_UNHANDLEDKEY, eventData);
         }
-        break;
+        return;
     }
     
     if (changed)
@@ -375,8 +375,8 @@ void LineEdit::onChar(unsigned char c, int buttons, int qualifiers)
         VariantMap eventData;
         eventData[P_ELEMENT] = (void*)this;
         eventData[P_TEXT] = mLine;
-        // This event may potentially cause deletion
-        SAFE_SEND_EVENT(EVENT_TEXTFINISHED, eventData);
+        sendEvent(EVENT_TEXTFINISHED, eventData);
+        return;
     }
     else if ((c >= 0x20) && ((!mMaxLength) || (mLine.length() < mMaxLength)))
     {

+ 1 - 0
Engine/UI/LineEdit.h

@@ -29,6 +29,7 @@
 class Font;
 class Text;
 
+//! A single-line text editor
 class LineEdit : public BorderImage
 {
     DEFINE_TYPE(LineEdit);

+ 11 - 3
Engine/UI/Menu.cpp

@@ -46,7 +46,17 @@ Menu::Menu(const std::string& name) :
 Menu::~Menu()
 {
     if (mPopup)
+    {
         showPopup(false);
+        
+        // Make sure the popup is removed from hierarchy if still visible
+        UIElement* parent = mPopup->getParent();
+        if (parent)
+        {
+            mPopup->getUserData()[originHash].clear();
+            parent->removeChild(mPopup);
+        }
+    }
 }
 
 void Menu::setStyle(const XMLElement& element, ResourceCache* cache)
@@ -103,10 +113,8 @@ void Menu::showPopup(bool enable)
     if (!mPopup)
         return;
     
-    // Find the UI root element for showing the popup. If we are already detached, try to find it through the popup
+    // Find the UI root element for showing the popup
     UIElement* root = getRootElement();
-    if (!root)
-        root = mPopup->getRootElement();
     if (!root)
         return;
     

+ 24 - 1
Engine/UI/UI.cpp

@@ -358,7 +358,7 @@ UIElement* UI::getElementAt(int x, int y, bool enabledOnly)
     return getElementAt(IntVector2(x, y), enabledOnly);
 }
 
-UIElement* UI::getFocusElement()
+UIElement* UI::getFocusElement() const
 {
     std::vector<UIElement*> allChildren = mRootElement->getChildren(true);
     for (std::vector<UIElement*>::iterator i = allChildren.begin(); i != allChildren.end(); ++i)
@@ -370,6 +370,29 @@ UIElement* UI::getFocusElement()
     return 0;
 }
 
+UIElement* UI::getFrontElement() const
+{
+    std::vector<UIElement*> rootChildren = mRootElement->getChildren(false);
+    int maxPriority = M_MIN_INT;
+    UIElement* front = 0;
+    
+    for (unsigned i = 0; i < rootChildren.size(); ++i)
+    {
+        // Do not take into account input-disabled elements, hidden elements or those that are always in the front
+        if ((!rootChildren[i]->isEnabled()) || (!rootChildren[i]->isVisible()) || (!rootChildren[i]->getBringToBack()))
+            continue;
+        
+        int priority = rootChildren[i]->getPriority();
+        if (priority > maxPriority)
+        {
+            maxPriority = priority;
+            front = rootChildren[i];
+        }
+    }
+    
+    return front;
+}
+
 IntVector2 UI::getCursorPosition()
 {
     if (!mCursor)

+ 3 - 1
Engine/UI/UI.h

@@ -74,7 +74,9 @@ public:
     //! Return UI element at screen coordinates
     UIElement* getElementAt(int x, int y, bool enabledOnly = true);
     //! Return focused element
-    UIElement* getFocusElement();
+    UIElement* getFocusElement() const;
+    //! Return topmost enabled root-level element
+    UIElement* getFrontElement() const;
     //! Return cursor position
     IntVector2 getCursorPosition();
     //! Return UI element factories

+ 0 - 3
Engine/UI/UIElement.h

@@ -31,9 +31,6 @@
 #include "Vector2.h"
 #include "XMLFile.h"
 
-//! Helper macro to send an event and check if we were deleted as a result
-#define SAFE_SEND_EVENT(type, data) { WeakPtr<UIElement> self(static_cast<UIElement*>(this)); sendEvent(type, data); if (!self) return; }
-
 //! UI element horizontal alignment
 enum HorizontalAlignment
 {

+ 1 - 3
Examples/Urho3D/Application.cpp

@@ -24,8 +24,6 @@
 #include "Application.h"
 #include "Engine.h"
 #include "Exception.h"
-#include "Font.h"
-#include "Input.h"
 #include "Log.h"
 #include "PackageFile.h"
 #include "ProcessUtils.h"
@@ -83,7 +81,7 @@ void Application::run()
     {
         fileName = getAbsoluteFileName(fileName);
         file = new File(fileName);
-        mCache->addResourcePath(mCache->getPreferredResourcePath(getPath(fileName)));
+        mCache->addResourcePath(getPreferredResourcePath(getPath(fileName)));
     }
     
     // Initialize engine & scripting. Render once first to avoid a white screen in case init takes a long time

文件差异内容过多而无法显示
+ 291 - 224
Tools/AssetImporter/AssetImporter.cpp


部分文件因为文件数量过多而无法显示