154 lines
6.0 KiB
C++
154 lines
6.0 KiB
C++
#include <sophia/application.hpp>
|
|
#include <sophia/components.hpp>
|
|
#include <sophia/entity.hpp>
|
|
#include <sophia/primitives.hpp>
|
|
#include <vector>
|
|
|
|
#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::RenderTarget>();
|
|
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::RenderTarget>();
|
|
sophia::SceneViewHandle gameView =
|
|
sceneViews().create({.target = gameTarget, .enabled = false});
|
|
|
|
panels().emplace<SceneHierarchyPanel>(context, cubeMesh);
|
|
panels().emplace<InspectorPanel>(context, textures);
|
|
panels().emplace<AssetBrowserPanel>();
|
|
viewPanels.push_back(
|
|
{panels().emplace<ScenePanel>(ui(), resources(), context, sceneViews(),
|
|
sceneView, sceneTarget, editorCamera),
|
|
sceneView});
|
|
viewPanels.push_back(
|
|
{panels().emplace<GamePanel>(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<sophia::TransformComponent>().position = {-1.5f, 0.f,
|
|
0.f};
|
|
checker.addComponent<sophia::SpriteComponent>(sophia::SpriteComponent{
|
|
.texture = textures[0].second, .size = {2.f, 2.f}});
|
|
|
|
sophia::Entity red = scene.createEntity("Red");
|
|
red.getComponent<sophia::TransformComponent>().position = {1.f, -0.5f, 0.f};
|
|
red.addComponent<sophia::SpriteComponent>(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<sophia::TransformComponent>().position = {1.75f, 0.25f,
|
|
0.f};
|
|
blue.addComponent<sophia::SpriteComponent>(
|
|
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<sophia::TransformComponent>();
|
|
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>(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<sophia::Mesh>(data);
|
|
}
|
|
|
|
sophia::TextureHandle createCheckerTexture() {
|
|
constexpr sophia::u32 SIZE = 8;
|
|
std::vector<sophia::u8> 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<sophia::Texture>(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<ViewPanel> viewPanels;
|
|
InspectorPanel::TextureList textures;
|
|
sophia::MeshHandle cubeMesh;
|
|
};
|
|
|
|
SOPHIA_MAIN(Editor)
|