#include #include #include #include #include #include "editor_context.hpp" #include "panels/asset_browser_panel.hpp" #include "panels/game_panel.hpp" #include "panels/inspector_panel.hpp" #include "panels/scene_hierarchy_panel.hpp" #include "panels/scene_panel.hpp" #include "utilities/logger.hpp" class Editor : public sophia::Application { public: Editor() : Application{{.width = 750, .height = 1000, .name = "Editor"}} {} void onCreate() override { log("Editor created"); debugUI().show(); textures.emplace_back("Checker", createCheckerTexture()); cubeMesh = createShadedCube(); sophia::SceneHandle sceneHandle = context.createScene("Demo"); createDemoScene(scenes().get(sceneHandle)); context.setScene(sceneHandle); // The editor's own scene, never edited or rendered. It holds the camera // the Scene panel looks through, which a SceneView may take from any // scene. sophia::SceneHandle editorSceneHandle = scenes().create(); sophia::Entity editorCamera = scenes().get(editorSceneHandle).createCamera("Editor Camera"); // Views start without a scene; their panels point them at the active // scene each frame. sophia::RenderTargetHandle sceneTarget = resources().create(); sophia::SceneViewHandle sceneView = sceneViews().create( {.target = sceneTarget, .camera = editorCamera, .enabled = false}); // No camera: the Game panel shows the scene through its active one. sophia::RenderTargetHandle gameTarget = resources().create(); sophia::SceneViewHandle gameView = sceneViews().create({.target = gameTarget, .enabled = false}); panels().emplace(context, cubeMesh); panels().emplace(context, textures); panels().emplace(); viewPanels.push_back( {panels().emplace(ui(), resources(), context, sceneViews(), sceneView, sceneTarget, editorCamera), sceneView}); viewPanels.push_back( {panels().emplace(ui(), context, sceneViews(), gameView, gameTarget), gameView}); panels().setDefaultLayout( {.left = {"Scene Hierarchy"}, .right = {"Inspector", "Frame Stats", "PanelRegistry"}, .bottom = {"Asset Browser", "Console"}, .center = {"Scene", "Game"}}); } void onUpdate(const sophia::FrameInfo& frameInfo) override { // A closed panel's onUpdate() doesn't run, so it can't stop its view // rendering itself. for (const ViewPanel& viewPanel : viewPanels) { if (!panels().get(viewPanel.panel).isVisible() && sceneViews().peek(viewPanel.view).enabled) { sceneViews().get(viewPanel.view).enabled = false; } } } private: // Adds to a new scene, which already has a camera. void createDemoScene(sophia::Scene& scene) { sophia::Entity checker = scene.createEntity("Checker"); checker.getComponent().position = {-1.5f, 0.f, 0.f}; checker.addComponent(sophia::SpriteComponent{ .texture = textures[0].second, .size = {2.f, 2.f}}); sophia::Entity red = scene.createEntity("Red"); red.getComponent().position = {1.f, -0.5f, 0.f}; red.addComponent(sophia::SpriteComponent{ .color = {0.9f, 0.25f, 0.25f, 1.f}, .size = {2.f, 2.f}}); // Drawn over "Red" because of its higher sort order. sophia::Entity blue = scene.createEntity("Blue (translucent)"); blue.getComponent().position = {1.75f, 0.25f, 0.f}; blue.addComponent( sophia::SpriteComponent{.color = {0.25f, 0.45f, 0.95f, 0.6f}, .size = {2.f, 2.f}, .sortOrder = 1}); sophia::Entity cube = scene.createEntity("Cube"); auto& cubeTransform = cube.getComponent(); cubeTransform.position = {0.f, 2.5f, 0.f}; cubeTransform.rotation = glm::quat{glm::radians(glm::vec3{30.f, 45.f, 0.f})}; cube.addComponent(sophia::MeshComponent{ .handle = cubeMesh, .color = {0.4f, 0.8f, 0.5f, 1.f}}); } // Meshes are unlit, so a plain cube is a flat silhouette. Bake a fixed // light into its vertex colors so its faces read in 3D until there's real // lighting; MeshComponent::color still tints it. sophia::MeshHandle createShadedCube() { const glm::vec3 lightDirection = glm::normalize(glm::vec3{0.4f, 1.f, 0.7f}); sophia::MeshData data = sophia::primitives::cube(); for (sophia::MeshVertex& vertex : data.vertices) { float light = glm::dot(vertex.normal, lightDirection) * 0.5f + 0.5f; vertex.color = glm::vec4{glm::vec3{0.35f + 0.65f * light}, 1.f}; } return resources().create(data); } sophia::TextureHandle createCheckerTexture() { constexpr sophia::u32 SIZE = 8; std::vector pixels(SIZE * SIZE * 4); for (sophia::u32 y = 0; y < SIZE; y++) { for (sophia::u32 x = 0; x < SIZE; x++) { sophia::u8 value = ((x + y) % 2 == 0) ? 230 : 60; sophia::u8* pixel = &pixels[(y * SIZE + x) * 4]; pixel[0] = pixel[1] = pixel[2] = value; pixel[3] = 255; } } return resources().create(SIZE, SIZE, std::move(pixels)); } // A panel showing a scene view, which should only render while the panel // is on screen. struct ViewPanel { sophia::PanelHandle panel; sophia::SceneViewHandle view; }; EditorContext context{scenes()}; std::vector viewPanels; InspectorPanel::TextureList textures; sophia::MeshHandle cubeMesh; }; SOPHIA_MAIN(Editor)