Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Axiom/Renderer/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ void Camera::SetPerspective(float VerticalFovDegrees, float AspectRatio,
m_Projection = glm::perspective(glm::radians(VerticalFovDegrees), AspectRatio,
NearPlane, FarPlane);
m_Projection[1][1] *= -1.0f;
m_ProjectionType = CameraProjectionType::Perspective;
}

void Camera::SetOrthographic(float OrthoHeight, float AspectRatio,
float NearPlane, float FarPlane) {
const float HalfH = OrthoHeight * 0.5f;
const float HalfW = HalfH * AspectRatio;
m_Projection = glm::ortho(-HalfW, HalfW, -HalfH, HalfH, NearPlane, FarPlane);
m_Projection[1][1] *= -1.0f; // Vulkan Y-flip
m_ProjectionType = CameraProjectionType::Orthographic;
}

void Camera::SetPosition(const glm::vec3 &Position) {
Expand Down
9 changes: 9 additions & 0 deletions Axiom/Renderer/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <glm/vec3.hpp>

namespace Axiom {
enum class CameraProjectionType { Perspective, Orthographic };

class Camera {
public:
Camera() = default;
Expand All @@ -12,6 +14,8 @@ class Camera {
const glm::vec3 &Up = glm::vec3(0.0f, 1.0f, 0.0f));
void SetPerspective(float VerticalFovDegrees, float AspectRatio,
float NearPlane, float FarPlane);
void SetOrthographic(float OrthoHeight, float AspectRatio,
float NearPlane, float FarPlane);
void SetPosition(const glm::vec3 &Position);
void SetRotation(float YawDegrees, float PitchDegrees);
void MoveLocal(const glm::vec3 &LocalOffset);
Expand All @@ -26,6 +30,10 @@ class Camera {
const glm::mat4 &GetViewMatrix() const { return m_View; }
const glm::mat4 &GetProjectionMatrix() const { return m_Projection; }
glm::mat4 GetViewProjectionMatrix() const { return m_Projection * m_View; }
CameraProjectionType GetProjectionType() const { return m_ProjectionType; }
bool IsOrthographic() const {
return m_ProjectionType == CameraProjectionType::Orthographic;
}

private:
void UpdateOrientationVectors();
Expand All @@ -39,5 +47,6 @@ class Camera {
float m_PitchDegrees{0.0f};
glm::mat4 m_View{1.0f};
glm::mat4 m_Projection{1.0f};
CameraProjectionType m_ProjectionType{CameraProjectionType::Perspective};
};
} // namespace Axiom
3 changes: 2 additions & 1 deletion Axiom/Renderer/Vulkan/VulkanRendererBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,8 @@ void VulkanRendererBackend::DrawBackground(VkCommandBuffer CommandBuffer) {
const bool UseHDR = m_LoadedHDRSkyboxData != nullptr &&
m_HDRSkyboxDescriptorSet != VK_NULL_HANDLE &&
m_ActiveScene != nullptr &&
m_ActiveScene->ActiveCamera != nullptr;
m_ActiveScene->ActiveCamera != nullptr &&
!m_ActiveScene->ActiveCamera->IsOrthographic();

if (UseHDR) {
vkCmdBindPipeline(CommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE,
Expand Down
15 changes: 14 additions & 1 deletion Axiom/Session/EditorCommand.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "Renderer/Camera.h"
#include "Session/SessionTypes.h"

#include <glm/vec2.hpp>
Expand Down Expand Up @@ -128,12 +129,23 @@ struct ResumeSessionCommand {};

struct StopSessionCommand {};

struct SetCameraProjectionCommand {
CameraProjectionType ProjectionType{CameraProjectionType::Perspective};
};

struct SetWorldSettingsCommand {
EditorWorldSettings Settings;
};

struct PlaceActorCommand {
std::string ChildTemplateId; // empty = bare Actor, no child
std::string ChildMeshAssetPath; // if set, assigns this asset to the child mesh after creation
glm::vec3 Location{0.0f};
};

using EditorCommandPayload =
std::variant<UpdateViewportCameraCommand, SetViewportCameraPoseCommand,
SetCameraProjectionCommand,
SetLookActiveCommand, SelectObjectCommand,
RenameObjectCommand, SetObjectVisibilityCommand,
CreateObjectCommand, CreateMeshObjectCommand,
Expand All @@ -146,7 +158,8 @@ using EditorCommandPayload =
SetMaterialTextureCommand, SetPhysicsPropertiesCommand,
PlaySessionCommand,
PauseSessionCommand, ResumeSessionCommand,
StopSessionCommand, SetWorldSettingsCommand>;
StopSessionCommand, SetWorldSettingsCommand,
PlaceActorCommand>;

struct EditorCommand {
EditorCommandPayload Payload;
Expand Down
130 changes: 127 additions & 3 deletions Axiom/Session/EditorSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ std::string CommandTypeName(const EditorCommandPayload &Payload) {
if (std::holds_alternative<SetViewportCameraPoseCommand>(Payload)) {
return "set_viewport_camera_pose";
}
if (std::holds_alternative<SetCameraProjectionCommand>(Payload)) {
return "set_camera_projection";
}
if (std::holds_alternative<SetLookActiveCommand>(Payload)) {
return "set_look_active";
}
Expand Down Expand Up @@ -215,6 +218,9 @@ std::string CommandTypeName(const EditorCommandPayload &Payload) {
if (std::holds_alternative<SetWorldSettingsCommand>(Payload)) {
return "set_world_settings";
}
if (std::holds_alternative<PlaceActorCommand>(Payload)) {
return "place_actor";
}
return "set_transform";
}

Expand All @@ -234,7 +240,8 @@ bool IsAuthoringMutationCommand(const EditorCommandPayload &Payload) {
std::holds_alternative<SetMaterialPropertiesCommand>(Payload) ||
std::holds_alternative<SetMaterialTextureCommand>(Payload) ||
std::holds_alternative<SetPhysicsPropertiesCommand>(Payload) ||
std::holds_alternative<SetWorldSettingsCommand>(Payload);
std::holds_alternative<SetWorldSettingsCommand>(Payload) ||
std::holds_alternative<PlaceActorCommand>(Payload);
}

EditorSceneItemKind KindForClassName(std::string_view ClassName) {
Expand Down Expand Up @@ -1691,6 +1698,23 @@ void EditorSession::HandleCommand(
}});
}

void EditorSession::HandleCommand(const QueuedEditorCommand &QueuedCommand,
const SetCameraProjectionCommand &Command) {
EditorViewportState &Viewport = EnsureViewport(QueuedCommand.Context.User);
Viewport.ProjectionType = Command.ProjectionType;
if (Command.ProjectionType == CameraProjectionType::Orthographic) {
Viewport.Camera.SetOrthographic(Viewport.OrthoHeight,
m_Config.CameraAspectRatio,
m_Config.CameraNearPlane,
m_Config.CameraFarPlane);
} else {
Viewport.Camera.SetPerspective(m_Config.CameraVerticalFovDegrees,
m_Config.CameraAspectRatio,
m_Config.CameraNearPlane,
m_Config.CameraFarPlane);
}
}

void EditorSession::HandleCommand(const QueuedEditorCommand &QueuedCommand,
const SetLookActiveCommand &Command) {
EditorViewportState &Viewport = EnsureViewport(QueuedCommand.Context.User);
Expand Down Expand Up @@ -2011,8 +2035,22 @@ void EditorSession::HandleCommand(const QueuedEditorCommand &QueuedCommand,
return;
}

CookMeshAssetBestEffort(m_ContentDir, Command.AssetPath);
const std::filesystem::path FullPath = m_ContentDir / Command.AssetPath;
// Resolve "Engine/" prefix to the engine content directory.
const std::filesystem::path AssetRelative{Command.AssetPath};
const bool IsEngineAsset =
!AssetRelative.empty() && *AssetRelative.begin() == "Engine";
std::filesystem::path EffectiveContentDir = m_ContentDir;
std::filesystem::path EffectiveRelative = AssetRelative;
if (IsEngineAsset && !m_EngineContentDir.empty()) {
EffectiveContentDir = m_EngineContentDir;
auto It = AssetRelative.begin();
++It; // skip "Engine"
EffectiveRelative.clear();
for (; It != AssetRelative.end(); ++It) EffectiveRelative /= *It;
}

CookMeshAssetBestEffort(EffectiveContentDir, EffectiveRelative.string());
const std::filesystem::path FullPath = EffectiveContentDir / EffectiveRelative;
const auto SceneData = Assets::LoadBasicMeshAsset(FullPath);
if (!SceneData.has_value() || SceneData->Instances.empty()) {
A_CORE_WARN("SetMeshAsset: failed to load '{}' for object '{}'",
Expand Down Expand Up @@ -2291,10 +2329,96 @@ void EditorSession::HandleCommand(const QueuedEditorCommand &QueuedCommand,
}
}

void EditorSession::HandleCommand(const QueuedEditorCommand &QueuedCommand,
const PlaceActorCommand &Command) {
EnsurePresence(QueuedCommand.Context.User);
Instance *WorldFolder = EnsureWorldFolder();
if (!WorldFolder) return;

// Create the Actor parent
const std::string ActorId = BuildUniqueObjectId("Actor");
const std::string ActorDisplayName = BuildUniqueDisplayName("Actor");
const EditorTransformDetails ActorTransform{.Location = Command.Location};
m_State.Scene.ObjectDetailsById.emplace(
ActorId,
EditorObjectDetails{
.ObjectId = ActorId,
.DisplayName = ActorDisplayName,
.Kind = EditorSceneItemKind::Actor,
.Visible = true,
.SupportsTransform = true,
.TransformReadOnly = false,
.Transform = ActorTransform,
.WorldTransform = ActorTransform,
});
Instance *ActorNode = CreateInstanceForTemplate("Actor", ActorId);
if (ActorNode) ActorNode->SetParent(WorldFolder);

// Create the child object (if a template was specified)
std::string ChildId;
std::string ChildDisplayName;
if (!Command.ChildTemplateId.empty()) {
const EditorSceneItemKind ChildKind = KindForTemplateId(Command.ChildTemplateId);
ChildId = BuildUniqueObjectId(Command.ChildTemplateId);
ChildDisplayName = BuildUniqueDisplayName(Command.ChildTemplateId);
const bool ChildTransformable = SupportsTransformForKind(ChildKind);
m_State.Scene.ObjectDetailsById.emplace(
ChildId,
EditorObjectDetails{
.ObjectId = ChildId,
.DisplayName = ChildDisplayName,
.Kind = ChildKind,
.Visible = true,
.SupportsTransform = ChildTransformable,
.TransformReadOnly = false,
.Transform = ChildTransformable
? std::optional{EditorTransformDetails{}}
: std::nullopt,
.WorldTransform = ChildTransformable
? std::optional{EditorTransformDetails{}}
: std::nullopt,
});
if (Instance *ChildNode =
CreateInstanceForTemplate(Command.ChildTemplateId, ChildId)) {
ChildNode->SetParent(ActorNode ? ActorNode : WorldFolder);
}
}

SyncItemsFromTree();
PublishEvent({.Payload = ObjectCreatedEvent{
.User = QueuedCommand.Context.User,
.ObjectId = ActorId,
.DisplayName = ActorDisplayName,
}});
if (!ChildId.empty()) {
PublishEvent({.Payload = ObjectCreatedEvent{
.User = QueuedCommand.Context.User,
.ObjectId = ChildId,
.DisplayName = ChildDisplayName,
}});
if (!Command.ChildMeshAssetPath.empty()) {
HandleCommand(QueuedCommand, SetMeshAssetCommand{
.ObjectId = ChildId,
.AssetPath = Command.ChildMeshAssetPath,
});
}
}

// Apply world-space location to the actor
HandleCommand(QueuedCommand, SetTransformCommand{
.ObjectId = ActorId,
.Location = Command.Location,
});
}

void EditorSession::SetContentDir(std::filesystem::path ContentDir) {
m_ContentDir = std::move(ContentDir);
}

void EditorSession::SetEngineContentDir(std::filesystem::path EngineContentDir) {
m_EngineContentDir = std::move(EngineContentDir);
}

void EditorSession::PublishScriptError(const std::string &ObjectId,
const std::string &Message) {
PublishEvent({ScriptErrorEvent{.ObjectId = ObjectId, .Message = Message}});
Expand Down
11 changes: 11 additions & 0 deletions Axiom/Session/EditorSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ struct EditorSessionConfig {

struct EditorViewportState {
Camera Camera;
CameraProjectionType ProjectionType{CameraProjectionType::Perspective};
float OrthoHeight{20.0f};
bool IsLooking{false};
glm::dvec2 LastCursorPosition{0.0, 0.0};
bool HasLastCursorPosition{false};
Expand Down Expand Up @@ -170,6 +172,10 @@ class EditorSession final : public IEditorCommandSink {
void SetContentDir(std::filesystem::path ContentDir);
const std::filesystem::path &GetContentDir() const { return m_ContentDir; }

// Optional fallback for engine-bundled assets (paths prefixed with "Engine/").
void SetEngineContentDir(std::filesystem::path EngineContentDir);
const std::filesystem::path &GetEngineContentDir() const { return m_EngineContentDir; }

void EnsureViewportState(SessionUserId User);
void SetPresenceState(SessionUserId User, EditorUserPresenceState State);
void SetSceneState(EditorSceneState SceneState);
Expand Down Expand Up @@ -257,6 +263,8 @@ class EditorSession final : public IEditorCommandSink {
const UpdateViewportCameraCommand &Command);
void HandleCommand(const QueuedEditorCommand &QueuedCommand,
const SetViewportCameraPoseCommand &Command);
void HandleCommand(const QueuedEditorCommand &QueuedCommand,
const SetCameraProjectionCommand &Command);
void HandleCommand(const QueuedEditorCommand &QueuedCommand,
const SetLookActiveCommand &Command);
void HandleCommand(const QueuedEditorCommand &QueuedCommand,
Expand Down Expand Up @@ -301,6 +309,8 @@ class EditorSession final : public IEditorCommandSink {
const StopSessionCommand &Command);
void HandleCommand(const QueuedEditorCommand &QueuedCommand,
const SetWorldSettingsCommand &Command);
void HandleCommand(const QueuedEditorCommand &QueuedCommand,
const PlaceActorCommand &Command);
void ApplyWorldTransform(std::string_view ObjectId,
const EditorTransformDetails &WorldTransform,
SessionUserId User, bool PublishEvent);
Expand All @@ -321,6 +331,7 @@ class EditorSession final : public IEditorCommandSink {
EditorMessageBus m_MessageBus;
std::unique_ptr<DataModel> m_SceneRoot;
std::filesystem::path m_ContentDir;
std::filesystem::path m_EngineContentDir;
std::optional<RuntimeSceneSnapshot> m_RuntimeSceneSnapshot;
std::unique_ptr<PhysicsWorld> m_PhysicsWorld;
};
Expand Down
19 changes: 18 additions & 1 deletion Axiom/Session/MeshPicking.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ BuildViewportRay(const Camera &Cam, uint32_t VpWidth, uint32_t VpHeight,
return std::nullopt;
}

const glm::vec3 RayOrigin = Cam.GetPosition();
const float NdcX = (MousePixel.x / static_cast<float>(VpWidth)) * 2.0f - 1.0f;
const float NdcY = (MousePixel.y / static_cast<float>(VpHeight)) * 2.0f - 1.0f;
const glm::vec4 WorldH =
Expand All @@ -47,6 +46,17 @@ BuildViewportRay(const Camera &Cam, uint32_t VpWidth, uint32_t VpHeight,
}

const glm::vec3 WorldPt = glm::vec3(WorldH) / WorldH.w;

if (Cam.IsOrthographic()) {
const glm::vec3 RayDir = glm::normalize(Cam.GetForward());
if (!std::isfinite(RayDir.x) || !std::isfinite(RayDir.y) ||
!std::isfinite(RayDir.z)) {
return std::nullopt;
}
return ViewportRay{.Origin = WorldPt, .Direction = RayDir};
}

const glm::vec3 RayOrigin = Cam.GetPosition();
const glm::vec3 RayDir = glm::normalize(WorldPt - RayOrigin);
if (!std::isfinite(RayDir.x) || !std::isfinite(RayDir.y) ||
!std::isfinite(RayDir.z)) {
Expand Down Expand Up @@ -148,6 +158,13 @@ inline float ComputeBillboardHalfSizeWorld(const Camera &Cam,
return 0.1f;
}

if (Cam.IsOrthographic()) {
// For ortho: |Projection[1][1]| = 1/HalfH, so world units per pixel = 2/(ProjectionY * VpHeight)
const float WorldUnitsPerPixel =
2.0f / (ProjectionY * static_cast<float>(VpHeight));
return std::max(0.01f, PixelSize * 0.5f * WorldUnitsPerPixel);
}

const float TanHalfFov = 1.0f / ProjectionY;
const float WorldUnitsPerPixel =
(2.0f * Distance * TanHalfFov) / static_cast<float>(VpHeight);
Expand Down
14 changes: 14 additions & 0 deletions Content/Engine/Cooked/AssetCookManifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"entries": [
{"assetId":17869987657431928246,"kind":"material","relativePath":"Generated/MeshMaterials/Cube__0","cookedPath":"Cooked/Generated/MeshMaterials/Cube__0.wmat","formatVersion":1,"sourceHash":5263286426569856486},
{"assetId":17473500555079411416,"kind":"mesh","relativePath":"Primitives/Cube.glb","cookedPath":"Cooked/Primitives/Cube.wmesh","formatVersion":2,"sourceHash":14886726218825273740},
{"assetId":9525971645921137078,"kind":"material","relativePath":"Generated/MeshMaterials/Sphere__0","cookedPath":"Cooked/Generated/MeshMaterials/Sphere__0.wmat","formatVersion":1,"sourceHash":5263286426569856486},
{"assetId":2642413918879317334,"kind":"mesh","relativePath":"Primitives/Sphere.glb","cookedPath":"Cooked/Primitives/Sphere.wmesh","formatVersion":2,"sourceHash":6741214548514332},
{"assetId":13522777309111790592,"kind":"material","relativePath":"Generated/MeshMaterials/Cylinder__0","cookedPath":"Cooked/Generated/MeshMaterials/Cylinder__0.wmat","formatVersion":1,"sourceHash":5263286426569856486},
{"assetId":6692227360735420864,"kind":"mesh","relativePath":"Primitives/Cylinder.glb","cookedPath":"Cooked/Primitives/Cylinder.wmesh","formatVersion":2,"sourceHash":15044282560946633958},
{"assetId":17271692768927002155,"kind":"material","relativePath":"Generated/MeshMaterials/Cone__0","cookedPath":"Cooked/Generated/MeshMaterials/Cone__0.wmat","formatVersion":1,"sourceHash":5263286426569856486},
{"assetId":8791640745736304361,"kind":"mesh","relativePath":"Primitives/Cone.glb","cookedPath":"Cooked/Primitives/Cone.wmesh","formatVersion":2,"sourceHash":18189565898652062489},
{"assetId":11105005092375462530,"kind":"material","relativePath":"Generated/MeshMaterials/Plane__0","cookedPath":"Cooked/Generated/MeshMaterials/Plane__0.wmat","formatVersion":1,"sourceHash":5263286426569856486},
{"assetId":13441880797815026060,"kind":"mesh","relativePath":"Primitives/Plane.glb","cookedPath":"Cooked/Primitives/Plane.wmesh","formatVersion":2,"sourceHash":14317949110025125343}
]
}
Binary file added Content/Engine/Primitives/Cone.glb
Binary file not shown.
Binary file added Content/Engine/Primitives/Cube.glb
Binary file not shown.
Binary file added Content/Engine/Primitives/Cylinder.glb
Binary file not shown.
Binary file added Content/Engine/Primitives/Plane.glb
Binary file not shown.
Binary file added Content/Engine/Primitives/Sphere.glb
Binary file not shown.
Loading
Loading