Browse Source

Add cubemap generation capability to the Urho3D editor

JSandusky 10 years ago
parent
commit
da0463ec49

+ 18 - 0
Source/Urho3D/Script/GraphicsAPI.cpp

@@ -175,6 +175,23 @@ static Viewport* ConstructViewportSceneCameraRect(Scene* scene, Camera* camera,
     return new Viewport(GetScriptContext(), scene, camera, rect, renderPath);
 }
 
+static Image* Texture2DGetImage(Texture2D* tex2d)
+{
+    Image* rawImage = new Image(tex2d->GetContext());
+    const unsigned texSize = tex2d->GetDataSize(tex2d->GetWidth(), tex2d->GetHeight());
+    const unsigned format = tex2d->GetFormat();
+
+    if (format == Graphics::GetRGBAFormat() || format == Graphics::GetRGBA16Format() || format == Graphics::GetRGBAFloat32Format())
+        rawImage->SetSize(tex2d->GetWidth(), tex2d->GetHeight(), 4);
+    else if (format == Graphics::GetRGBFormat())
+        rawImage->SetSize(tex2d->GetWidth(), tex2d->GetHeight(), 3);
+    else
+        return SharedPtr<Image>();
+
+    tex2d->GetData(0, rawImage->GetData());
+    return rawImage;
+}
+
 static bool Texture2DSetData(Image* image, bool useAlpha, Texture2D* ptr)
 {
     return ptr->SetData(SharedPtr<Image>(image), useAlpha);
@@ -483,6 +500,7 @@ static void RegisterTextures(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Texture2D", "bool SetSize(int, int, uint, TextureUsage usage = TEXTURE_STATIC)", asMETHOD(Texture2D, SetSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("Texture2D", "bool SetData(Image@+, bool useAlpha = false)", asFUNCTION(Texture2DSetData), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Texture2D", "RenderSurface@+ get_renderSurface() const", asMETHOD(Texture2D, GetRenderSurface), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Texture2D", "Image@+ GetImage() const", asFUNCTION(Texture2DGetImage), asCALL_CDECL_OBJLAST);
 
     RegisterTexture<Texture3D>(engine, "Texture3D");
     engine->RegisterObjectMethod("Texture3D", "bool SetSize(int, int, uint, TextureUsage usage = TEXTURE_STATIC)", asMETHOD(Texture3D, SetSize), asCALL_THISCALL);

+ 18 - 0
bin/Data/Scripts/Editor.as

@@ -177,6 +177,7 @@ void LoadConfig()
     XMLElement consoleElem = configElem.GetChild("console");
     XMLElement varNamesElem = configElem.GetChild("varnames");
     XMLElement soundTypesElem = configElem.GetChild("soundtypes");
+    XMLElement cubeMapElem = configElem.GetChild("cubegen");
 
     if (!cameraElem.isNull)
     {
@@ -295,6 +296,18 @@ void LoadConfig()
     if (!soundTypesElem.isNull)
         LoadSoundTypes(soundTypesElem);
 
+    if (!cubeMapElem.isNull)
+    {
+        cubeMapGen_Name = cubeMapElem.HasAttribute("name") ? cubeMapElem.GetAttribute("name") : "";
+        cubeMapGen_Path = cubeMapElem.HasAttribute("path") ? cubeMapElem.GetAttribute("path") : "Data/Textures/Cubemaps";
+        cubeMapGen_Size = cubeMapElem.HasAttribute("size") ? cubeMapElem.GetInt("size") : 128;
+    }
+    else
+    {
+        cubeMapGen_Name = "";
+        cubeMapGen_Path = "Data/Textures/Cubemaps";
+        cubeMapGen_Size = 128;
+    }
 }
 
 void SaveConfig()
@@ -312,6 +325,7 @@ void SaveConfig()
     XMLElement consoleElem = configElem.CreateChild("console");
     XMLElement varNamesElem = configElem.CreateChild("varnames");
     XMLElement soundTypesElem = configElem.CreateChild("soundtypes");
+    XMLElement cubeGenElem = configElem.CreateChild("cubegen");
 
     cameraElem.SetFloat("nearclip", viewNearClip);
     cameraElem.SetFloat("farclip", viewFarClip);
@@ -387,6 +401,10 @@ void SaveConfig()
     consoleElem.SetAttribute("commandinterpreter", console.commandInterpreter);
 
     varNamesElem.SetVariantMap(globalVarNames);
+    
+    cubeGenElem.SetAttribute("name", cubeMapGen_Name);
+    cubeGenElem.SetAttribute("path", cubeMapGen_Path);
+    cubeGenElem.SetAttribute("size", cubeMapGen_Size);
 
     SaveSoundTypes(soundTypesElem);
 

+ 262 - 0
bin/Data/Scripts/Editor/EditorCubeCapture.as

@@ -0,0 +1,262 @@
+
+/// Settings
+String cubeMapGen_Name;
+String cubeMapGen_Path;
+int cubeMapGen_Size;
+
+Array<EditorCubeCapture@> activeCubeCapture; // Editor capture tasks in progress
+Array<Zone@> cloneZones; // Duplicate zones constructed to prevent IBL doubling
+Array<Zone@> disabledZones; // Zones that were disabled to prevent IBL doubling
+String cubemapOutputPath = "Data/Textures/Cubemaps";
+
+void PrepareZonesForCubeRendering()
+{
+    // Only clone zones when we aren't actively processing
+    if (cloneZones.length > 0)
+        return;
+        
+    Array<Component@>@ zones = editorScene.GetComponents("Zone", true);
+    for (int i = 0; i < zones.length; ++i)
+    {
+        Zone@ srcZone = cast<Zone>(zones[i]);
+        if (zones[i].enabled)
+        {
+            Zone@ cloneZone = srcZone.node.CreateComponent("Zone");
+            cloneZone.zoneMask = srcZone.zoneMask;
+            cloneZone.priority = srcZone.priority;
+            cloneZone.boundingBox = srcZone.boundingBox;
+            
+            cloneZone.ambientColor = srcZone.ambientColor;
+            cloneZone.ambientGradient = srcZone.ambientGradient;
+            
+            cloneZone.fogColor = srcZone.fogColor;
+            cloneZone.fogStart = srcZone.fogStart;
+            cloneZone.fogEnd = srcZone.fogEnd;
+            cloneZone.fogHeight = srcZone.fogHeight;
+            cloneZone.fogHeightScale = srcZone.fogHeightScale;
+            cloneZone.heightFog = srcZone.heightFog;
+            
+            srcZone.enabled = false;
+            
+            // Add the zones to our temporary lists
+            cloneZones.Push(cloneZone);
+            disabledZones.Push(srcZone);
+        }
+    }
+}
+
+void UnprepareZonesForCubeRendering()
+{
+    // Clean up the clones
+    for (int i = 0; i < cloneZones.length; ++i)
+        cloneZones[i].Remove();
+    cloneZones.Clear();
+        
+    // Reenable anyone we disabled
+    for (int i = 0; i < disabledZones.length; ++i)
+        disabledZones[i].enabled = true;
+    disabledZones.Clear();
+}
+
+class EditorCubeCapture : ScriptObject // script object in order to get events
+{
+    private String name_;
+    private String path_;
+    private Node@ camNode_;
+    private Camera@ camera_;
+    private Zone@ target_;
+    private Viewport@ viewport_;
+    private Texture2D@ renderImage_;
+    private RenderSurface@ renderSurface_;
+    int updateCycle_;
+    String imagePath_;
+    
+    EditorCubeCapture(Zone@ forZone)
+    {
+        PrepareZonesForCubeRendering();
+        
+        // Store name and path because if we have a lot of zones it could take long enough to queue another go
+        name_ = cubeMapGen_Name;
+        path_ = cubeMapGen_Path;
+    
+        updateCycle_ = 0;
+    
+        target_ = forZone;
+    
+        camNode_ = scene.CreateChild("RenderCamera");
+        camera_ = camNode_.GetOrCreateComponent("Camera");
+        camera_.fov = 90.0f;
+        camNode_.worldPosition = forZone.node.worldPosition;
+        
+        viewport_ = Viewport(scene, camera_);
+        viewport_.renderPath = renderer.viewports[0].renderPath;
+        
+        renderImage_ = Texture2D();
+        renderImage_.SetSize(cubeMapGen_Size, cubeMapGen_Size, GetRGBAFormat(), TEXTURE_RENDERTARGET);
+        
+        renderSurface_ = renderImage_.renderSurface;
+        renderSurface_.viewports[0] = viewport_;
+        renderSurface_.updateMode = SURFACE_UPDATEALWAYS;
+        
+        updateCycle_ = 0;
+    }
+    
+    ~EditorCubeCapture()
+    {
+        camNode_.Remove();
+    }
+    
+    void Start()
+    {
+        SubscribeToEvent("BeginFrame", "HandlePreRender");
+        SubscribeToEvent("EndFrame", "HandlePostRender");
+    }
+    
+    private void Stop()
+    {
+        camNode_.Remove();
+        camNode_ = null;
+        viewport_ = null;
+        renderSurface_ = null;
+        
+        UnsubscribeFromEvent("BeginFrame");
+        UnsubscribeFromEvent("EndFrame");
+        
+        WriteXML();
+        
+        // Remove ourselves from the processing list and if necessary clean things up
+        activeCubeCapture.Erase(activeCubeCapture.FindByRef(this));
+        if (activeCubeCapture.length == 0)
+            UnprepareZonesForCubeRendering();
+        else
+            activeCubeCapture[0].Start();
+    }
+    
+    // Position camera accordingly
+    void HandlePreRender(StringHash eventType, VariantMap& eventData)
+    {
+        if (camNode_ !is null)
+        {
+            ++updateCycle_;
+        
+            if (updateCycle_ < 7)
+                camNode_.rotation = RotationOf(GetFaceForCycle(updateCycle_));
+            else
+                Stop();
+        }
+    }
+    
+    // Save our image
+    void HandlePostRender(StringHash eventType, VariantMap& eventData)
+    {
+        Image@ img = renderImage_.GetImage();
+        String sceneName = editorScene.name.length > 0 ? editorScene.name + "/" : "";
+        String path = path_ + "/" + sceneName;
+        fileSystem.CreateDir(path);
+        path = path + "/" + String(target_.id) + "_" + GetFaceName(GetFaceForCycle(updateCycle_)) + ".png";
+        img.SavePNG(path);
+    }
+    
+    private void WriteXML()
+    {
+        String sceneName = editorScene.name.length > 0 ? editorScene.name + "/" : "";
+        String basePath = path_ + "/" + sceneName;
+        String cubeName = name_.length > 0 ? (name_ + "_") : "";
+        String xmlPath = basePath + "/" + name_ + String(target_.id) + ".xml";
+        XMLFile@ file = XMLFile();
+        XMLElement rootElem = file.CreateRoot("cubemap");
+        
+        XMLElement posXElem = rootElem.CreateChild("face");
+        posXElem.SetAttribute("name", basePath + "/" + cubeName + String(target_.id) + "_" + GetFaceName(FACE_POSITIVE_X) + ".png");
+        
+        XMLElement negXElem = rootElem.CreateChild("face");
+        negXElem.SetAttribute("name", basePath + "/" + cubeName + String(target_.id) + "_" + GetFaceName(FACE_NEGATIVE_X) + ".png");
+        
+        XMLElement posYElem = rootElem.CreateChild("face");
+        posYElem.SetAttribute("name", basePath + "/" + cubeName + String(target_.id) + "_" + GetFaceName(FACE_POSITIVE_Y) + ".png");
+        
+        XMLElement negYElem = rootElem.CreateChild("face");
+        negYElem.SetAttribute("name", basePath + "/" + cubeName + String(target_.id) + "_" + GetFaceName(FACE_NEGATIVE_Y) + ".png");
+        
+        XMLElement posZElem = rootElem.CreateChild("face");
+        posZElem.SetAttribute("name", basePath + "/" + cubeName + String(target_.id) + "_" + GetFaceName(FACE_POSITIVE_Z) + ".png");
+        
+        XMLElement negZElem = rootElem.CreateChild("face");
+        negZElem.SetAttribute("name", basePath + "/" + cubeName + String(target_.id) + "_" + GetFaceName(FACE_NEGATIVE_Z) + ".png");
+        
+        file.Save(File(xmlPath, FILE_WRITE), "    ");
+        
+        ResourceRef ref;
+        ref.type = StringHash("TextureCube");
+        ref.name = xmlPath;
+        target_.SetAttribute("Zone Texture", Variant(ref));
+    }
+    
+    private CubeMapFace GetFaceForCycle(int cycle)
+    {
+        switch (updateCycle_)
+        {
+        case 1:
+            return FACE_POSITIVE_X;
+        case 2:                                                
+            return FACE_POSITIVE_Y;                                   
+        case 3:                                                
+            return FACE_POSITIVE_Z;
+        case 4:
+            return FACE_NEGATIVE_X;
+        case 5:
+            return FACE_NEGATIVE_Y;
+        case 6:
+            return FACE_NEGATIVE_Z;
+        }
+        return FACE_POSITIVE_X;
+    }
+    
+    private String GetFaceName(CubeMapFace face)
+    {
+        switch (updateCycle_)
+        {
+        case 1:
+            return "PosX";
+        case 2:                                  
+            return "PosY";                                   
+        case 3:                                  
+            return "PosZ";
+        case 4:
+            return "NegX";
+        case 5:
+            return "NegY";
+        case 6:
+            return "NegZ";
+        }
+        return "PosX";
+    }
+    
+    private Quaternion RotationOf(CubeMapFace face)
+    {
+        Quaternion result;
+        switch (face)
+        {
+            //  Rotate camera according to probe rotation
+            case FACE_POSITIVE_X:
+                result = Quaternion(0, 90, 0);
+                break;
+            case FACE_NEGATIVE_X:
+                result = Quaternion(0, -90, 0);
+                break;
+            case FACE_POSITIVE_Y:
+                result = Quaternion(-90, 0, 0);
+                break;
+            case FACE_NEGATIVE_Y:
+                result = Quaternion(90, 0, 0);
+                break;
+            case FACE_POSITIVE_Z:
+                result = Quaternion(0, 0, 0);
+                break;
+            case FACE_NEGATIVE_Z:
+                result = Quaternion(0, 180, 0);
+                break;
+        }
+        return result;
+    }
+}

+ 43 - 0
bin/Data/Scripts/Editor/EditorScene.as

@@ -2,6 +2,7 @@
 
 #include "Scripts/Editor/EditorHierarchyWindow.as"
 #include "Scripts/Editor/EditorInspectorWindow.as"
+#include "Scripts/Editor/EditorCubeCapture.as"
 
 const int PICK_GEOMETRIES = 0;
 const int PICK_LIGHTS = 1;
@@ -1206,6 +1207,48 @@ bool SceneRebuildNavigation()
     return success;
 }
 
+bool SceneRenderZoneCubemaps()
+{
+    bool success = false;
+    Array<Zone@> capturedThisCall;
+    
+    for (int i = 0; i < selectedNodes.length; ++i)
+    {
+        Array<Component@>@ zones = selectedNodes[i].GetComponents("Zone", true);
+        for (int z = 0; z < zones.length; ++z)
+        {
+            Zone@ zone = cast<Zone>(zones[z]);
+            if (zone !is null)
+            {
+                activeCubeCapture.Push(EditorCubeCapture(zone));
+                capturedThisCall.Push(zone);
+            }
+        }
+    }
+    
+    for (int i = 0; i < selectedComponents.length; ++i)
+    {
+        Zone@ zone = cast<Zone>(selectedComponents[i]);
+        if (zone !is null)
+        {
+            if (capturedThisCall.FindByRef(zone) < 0)
+            {
+                activeCubeCapture.Push(EditorCubeCapture(zone));
+                capturedThisCall.Push(zone);
+            }
+        }
+    }
+    
+    if (activeCubeCapture.length > 0)
+        activeCubeCapture[0].Start();
+
+    if (capturedThisCall.length <= 0)
+    {
+        MessageBox("No zones selected to render cubemaps for/");
+    }
+    return capturedThisCall.length > 0;
+}
+
 bool SceneAddChildrenStaticModelGroup()
 {
     StaticModelGroup@ smg = cast<StaticModelGroup>(editComponents.length > 0 ? editComponents[0] : null);

+ 33 - 0
bin/Data/Scripts/Editor/EditorSettings.as

@@ -110,6 +110,13 @@ void UpdateEditorSettingsDialog()
 
     CheckBox@ frameLimiterToggle = settingsDialog.GetChild("FrameLimiterToggle", true);
     frameLimiterToggle.checked = engine.maxFps > 0;
+    
+    LineEdit@ cubemapPath = settingsDialog.GetChild("CubeMapGenPath", true);
+    cubemapPath.text = cubeMapGen_Path;
+    LineEdit@ cubemapName = settingsDialog.GetChild("CubeMapGenKey", true);
+    cubemapName.text = cubeMapGen_Name;
+    LineEdit@ cubemapSize = settingsDialog.GetChild("CubeMapGenSize", true);
+    cubemapSize.text = String(cubeMapGen_Size);
 
     if (!subscribedToEditorSettings)
     {
@@ -155,6 +162,14 @@ void UpdateEditorSettingsDialog()
         SubscribeToEvent(dynamicInstancingToggle, "Toggled", "EditDynamicInstancing");
         SubscribeToEvent(frameLimiterToggle, "Toggled", "EditFrameLimiter");
         SubscribeToEvent(settingsDialog.GetChild("CloseButton", true), "Released", "HideEditorSettingsDialog");
+        
+        SubscribeToEvent(cubemapPath, "TextChanged",  "EditCubemapPath");
+        SubscribeToEvent(cubemapPath, "TextFinished", "EditCubemapPath");
+        SubscribeToEvent(cubemapName, "TextChanged",  "EditCubemapName");
+        SubscribeToEvent(cubemapName, "TextFinished", "EditCubemapName");
+        SubscribeToEvent(cubemapSize, "TextChanged",  "EditCubemapSize");
+        SubscribeToEvent(cubemapSize, "TextFinished", "EditCubemapSize");
+        
         subscribedToEditorSettings = true;
     }
 }
@@ -390,3 +405,21 @@ void EditFrameLimiter(StringHash eventType, VariantMap& eventData)
     CheckBox@ edit = eventData["Element"].GetPtr();
     engine.maxFps = edit.checked ? 200 : 0;
 }
+
+void EditCubemapPath(StringHash eventType, VariantMap& eventData)
+{
+    LineEdit@ edit = eventData["Element"].GetPtr();
+    cubeMapGen_Path = edit.text;
+}
+
+void EditCubemapName(StringHash eventType, VariantMap& eventData)
+{
+    LineEdit@ edit = eventData["Element"].GetPtr();
+    cubeMapGen_Name = edit.text;
+}
+
+void EditCubemapSize(StringHash eventType, VariantMap& eventData)
+{
+    LineEdit@ edit = eventData["Element"].GetPtr();
+    cubeMapGen_Size = edit.text.ToInt();
+}

+ 1 - 0
bin/Data/Scripts/Editor/EditorUI.as

@@ -404,6 +404,7 @@ void CreateMenuBar()
         popup.AddChild(CreateMenuItem("Stop test animation", @StopTestAnimation));
         CreateChildDivider(popup);
         popup.AddChild(CreateMenuItem("Rebuild navigation data", @SceneRebuildNavigation));
+        popup.AddChild(CreateMenuItem("Render Zone Cubemap", @SceneRenderZoneCubemaps));
         popup.AddChild(CreateMenuItem("Add children to SM-group", @SceneAddChildrenStaticModelGroup));
         Menu@ childMenu = CreateMenuItem("Set children as spline path", null, SHOW_POPUP_INDICATOR);
         Window@ childPopup = CreatePopup(childMenu);

+ 36 - 0
bin/Data/UI/EditorSettingsDialog.xml

@@ -499,6 +499,42 @@
                         <attribute name="Text" value="Frame limiter" />
                     </element>
                 </element>
+                
+                <element style="ListRow">
+                    <attribute name="Layout Spacing" value="20" />
+                    <element type="Text">
+                        <attribute name="Text" value="Cubemap Gen Path" />
+                    </element>
+                    <element type="LineEdit">
+                        <attribute name="Name" value="CubeMapGenPath" />
+                        <attribute name="Min Size" value="100 0" />
+                        <attribute name="Max Size" value="100 2147483647" />
+                     </element>
+                </element>
+                
+                <element style="ListRow">
+                    <attribute name="Layout Spacing" value="20" />
+                    <element type="Text">
+                        <attribute name="Text" value="Cubemap Key" />
+                    </element>
+                    <element type="LineEdit">
+                        <attribute name="Name" value="CubeMapGenKey" />
+                        <attribute name="Min Size" value="100 0" />
+                        <attribute name="Max Size" value="100 2147483647" />
+                     </element>
+                </element>
+                
+                <element style="ListRow">
+                    <attribute name="Layout Spacing" value="20" />
+                    <element type="Text">
+                        <attribute name="Text" value="Cubemap Size" />
+                    </element>
+                    <element type="LineEdit">
+                        <attribute name="Name" value="CubeMapGenSize" />
+                        <attribute name="Min Size" value="100 0" />
+                        <attribute name="Max Size" value="100 2147483647" />
+                     </element>
+                </element>
             </element>
         </element>
     </element>