oh my fucking god that parent with positions following thing took far too long 😭 anyway, yey! better icons and hierarchy changes!
This commit is contained in:
199
Scripts/StandaloneMovementController.cpp
Normal file
199
Scripts/StandaloneMovementController.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "ScriptRuntime.h"
|
||||
#include "SceneObject.h"
|
||||
#include "ThirdParty/imgui/imgui.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
struct ControllerState {
|
||||
float pitch = 0.0f;
|
||||
float yaw = 0.0f;
|
||||
float verticalVelocity = 0.0f;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
std::unordered_map<int, ControllerState> g_states;
|
||||
|
||||
glm::vec3 moveTuning = glm::vec3(4.5f, 7.5f, 6.5f); // walk speed, run speed, jump
|
||||
glm::vec3 lookTuning = glm::vec3(0.12f, 200.0f, 0.0f); // sensitivity, max delta clamp, reserved
|
||||
glm::vec3 capsuleTuning = glm::vec3(1.8f, 0.4f, 0.2f); // height, radius, ground snap
|
||||
glm::vec3 gravityTuning = glm::vec3(-9.81f, 0.4f, 30.0f); // gravity, probe extra, max fall speed
|
||||
bool enableMouseLook = true;
|
||||
bool requireMouseButton = false;
|
||||
bool enforceCollider = true;
|
||||
bool enforceRigidbody = true;
|
||||
bool showDebug = false;
|
||||
|
||||
ControllerState& getState(int id) {
|
||||
return g_states[id];
|
||||
}
|
||||
|
||||
void ensureComponents(ScriptContext& ctx, float height, float radius) {
|
||||
if (!ctx.object) return;
|
||||
if (enforceCollider) {
|
||||
ctx.object->hasCollider = true;
|
||||
ctx.object->collider.enabled = true;
|
||||
ctx.object->collider.type = ColliderType::Capsule;
|
||||
ctx.object->collider.convex = true;
|
||||
ctx.object->collider.boxSize = glm::vec3(radius * 2.0f, height, radius * 2.0f);
|
||||
}
|
||||
if (enforceRigidbody) {
|
||||
ctx.object->hasRigidbody = true;
|
||||
ctx.object->rigidbody.enabled = true;
|
||||
ctx.object->rigidbody.useGravity = true;
|
||||
ctx.object->rigidbody.isKinematic = false;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
||||
ctx.AutoSetting("moveTuning", moveTuning);
|
||||
ctx.AutoSetting("lookTuning", lookTuning);
|
||||
ctx.AutoSetting("capsuleTuning", capsuleTuning);
|
||||
ctx.AutoSetting("gravityTuning", gravityTuning);
|
||||
ctx.AutoSetting("enableMouseLook", enableMouseLook);
|
||||
ctx.AutoSetting("requireMouseButton", requireMouseButton);
|
||||
ctx.AutoSetting("enforceCollider", enforceCollider);
|
||||
ctx.AutoSetting("enforceRigidbody", enforceRigidbody);
|
||||
ctx.AutoSetting("showDebug", showDebug);
|
||||
|
||||
ImGui::TextUnformatted("Standalone Movement Controller");
|
||||
ImGui::Separator();
|
||||
|
||||
bool changed = false;
|
||||
changed |= ImGui::DragFloat3("Walk/Run/Jump", &moveTuning.x, 0.05f, 0.0f, 25.0f, "%.2f");
|
||||
changed |= ImGui::DragFloat2("Look Sens/Clamp", &lookTuning.x, 0.01f, 0.0f, 500.0f, "%.2f");
|
||||
changed |= ImGui::DragFloat3("Height/Radius/Snap", &capsuleTuning.x, 0.02f, 0.0f, 5.0f, "%.2f");
|
||||
changed |= ImGui::DragFloat3("Gravity/Probe/MaxFall", &gravityTuning.x, 0.05f, -50.0f, 50.0f, "%.2f");
|
||||
changed |= ImGui::Checkbox("Enable Mouse Look", &enableMouseLook);
|
||||
changed |= ImGui::Checkbox("Hold RMB to Look", &requireMouseButton);
|
||||
changed |= ImGui::Checkbox("Force Collider", &enforceCollider);
|
||||
changed |= ImGui::Checkbox("Force Rigidbody", &enforceRigidbody);
|
||||
changed |= ImGui::Checkbox("Show Debug", &showDebug);
|
||||
|
||||
if (changed) {
|
||||
ctx.SaveAutoSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Begin(ScriptContext& ctx, float /*deltaTime*/) {
|
||||
if (!ctx.object) return;
|
||||
ControllerState& state = getState(ctx.object->id);
|
||||
if (!state.initialized) {
|
||||
state.pitch = ctx.object->rotation.x;
|
||||
state.yaw = ctx.object->rotation.y;
|
||||
state.verticalVelocity = 0.0f;
|
||||
state.initialized = true;
|
||||
}
|
||||
ensureComponents(ctx, capsuleTuning.x, capsuleTuning.y);
|
||||
}
|
||||
|
||||
void TickUpdate(ScriptContext& ctx, float deltaTime) {
|
||||
if (!ctx.object) return;
|
||||
|
||||
ControllerState& state = getState(ctx.object->id);
|
||||
ensureComponents(ctx, capsuleTuning.x, capsuleTuning.y);
|
||||
|
||||
const float walkSpeed = moveTuning.x;
|
||||
const float runSpeed = moveTuning.y;
|
||||
const float jumpStrength = moveTuning.z;
|
||||
const float lookSensitivity = lookTuning.x;
|
||||
const float maxMouseDelta = glm::max(5.0f, lookTuning.y);
|
||||
const float height = capsuleTuning.x;
|
||||
const float radius = capsuleTuning.y;
|
||||
const float groundSnap = capsuleTuning.z;
|
||||
const float gravity = gravityTuning.x;
|
||||
const float probeExtra = gravityTuning.y;
|
||||
const float maxFall = glm::max(1.0f, gravityTuning.z);
|
||||
|
||||
if (enableMouseLook) {
|
||||
bool allowLook = !requireMouseButton || ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
if (allowLook) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
glm::vec2 delta(io.MouseDelta.x, io.MouseDelta.y);
|
||||
float len = glm::length(delta);
|
||||
if (len > maxMouseDelta) {
|
||||
delta *= (maxMouseDelta / len);
|
||||
}
|
||||
state.yaw -= delta.x * 50.0f * lookSensitivity * deltaTime;
|
||||
state.pitch -= delta.y * 50.0f * lookSensitivity * deltaTime;
|
||||
state.pitch = std::clamp(state.pitch, -89.0f, 89.0f);
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat q = glm::quat(glm::radians(glm::vec3(state.pitch, state.yaw, 0.0f)));
|
||||
glm::vec3 forward = glm::normalize(q * glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
glm::vec3 right = glm::normalize(q * glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::vec3 planarForward = glm::normalize(glm::vec3(forward.x, 0.0f, forward.z));
|
||||
glm::vec3 planarRight = glm::normalize(glm::vec3(right.x, 0.0f, right.z));
|
||||
if (!std::isfinite(planarForward.x) || glm::length(planarForward) < 1e-3f) {
|
||||
planarForward = glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
}
|
||||
if (!std::isfinite(planarRight.x) || glm::length(planarRight) < 1e-3f) {
|
||||
planarRight = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 move(0.0f);
|
||||
if (ImGui::IsKeyDown(ImGuiKey_W)) move += planarForward;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_S)) move -= planarForward;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_D)) move += planarRight;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_A)) move -= planarRight;
|
||||
if (glm::length(move) > 0.001f) move = glm::normalize(move);
|
||||
|
||||
bool sprint = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
|
||||
float targetSpeed = sprint ? runSpeed : walkSpeed;
|
||||
glm::vec3 velocity = move * targetSpeed;
|
||||
|
||||
float capsuleHalf = std::max(0.1f, height * 0.5f);
|
||||
glm::vec3 physVel;
|
||||
bool havePhysVel = ctx.GetRigidbodyVelocity(physVel);
|
||||
if (havePhysVel) state.verticalVelocity = physVel.y;
|
||||
|
||||
glm::vec3 hitPos(0.0f);
|
||||
glm::vec3 hitNormal(0.0f, 1.0f, 0.0f);
|
||||
float hitDist = 0.0f;
|
||||
float probeDist = capsuleHalf + probeExtra;
|
||||
glm::vec3 rayStart = ctx.object->position + glm::vec3(0.0f, 0.1f, 0.0f);
|
||||
bool hitGround = ctx.RaycastClosest(rayStart, glm::vec3(0.0f, -1.0f, 0.0f), probeDist,
|
||||
&hitPos, &hitNormal, &hitDist);
|
||||
bool grounded = hitGround && hitNormal.y > 0.25f &&
|
||||
hitDist <= capsuleHalf + groundSnap &&
|
||||
state.verticalVelocity <= 0.35f;
|
||||
if (!hitGround) {
|
||||
grounded = ctx.object->position.y <= capsuleHalf + 0.12f && state.verticalVelocity <= 0.35f;
|
||||
}
|
||||
|
||||
if (grounded) {
|
||||
state.verticalVelocity = 0.0f;
|
||||
if (!havePhysVel) {
|
||||
if (hitGround) {
|
||||
ctx.object->position.y = std::max(ctx.object->position.y, hitPos.y + capsuleHalf);
|
||||
} else {
|
||||
ctx.object->position.y = capsuleHalf;
|
||||
}
|
||||
}
|
||||
if (ImGui::IsKeyDown(ImGuiKey_Space)) {
|
||||
state.verticalVelocity = jumpStrength;
|
||||
}
|
||||
} else {
|
||||
state.verticalVelocity += gravity * deltaTime;
|
||||
}
|
||||
|
||||
state.verticalVelocity = std::clamp(state.verticalVelocity, -maxFall, maxFall);
|
||||
velocity.y = state.verticalVelocity;
|
||||
|
||||
glm::vec3 rotation(state.pitch, state.yaw, 0.0f);
|
||||
ctx.object->rotation = rotation;
|
||||
ctx.SetRigidbodyRotation(rotation);
|
||||
|
||||
if (!ctx.SetRigidbodyVelocity(velocity)) {
|
||||
ctx.object->position += velocity * deltaTime;
|
||||
}
|
||||
|
||||
if (showDebug) {
|
||||
ImGui::Text("Move (%.2f, %.2f, %.2f)", velocity.x, velocity.y, velocity.z);
|
||||
ImGui::Text("Grounded: %s", grounded ? "yes" : "no");
|
||||
}
|
||||
}
|
||||
|
||||
void Spec(ScriptContext& /*ctx*/, float /*deltaTime*/) {}
|
||||
void TestEditor(ScriptContext& /*ctx*/, float /*deltaTime*/) {}
|
||||
@@ -159,7 +159,7 @@ void AudioSystem::update(const std::vector<SceneObject>& objects, const Camera&
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioSystem::playPreview(const std::string& path, float volume) {
|
||||
bool AudioSystem::playPreview(const std::string& path, float volume, bool loop) {
|
||||
if (path.empty()) return false;
|
||||
if (!initialized && !init()) return false;
|
||||
|
||||
@@ -169,6 +169,7 @@ bool AudioSystem::playPreview(const std::string& path, float volume) {
|
||||
std::cerr << "AudioSystem: preview load failed for " << path << " (" << res << ")\n";
|
||||
return false;
|
||||
}
|
||||
ma_sound_set_looping(&previewSound, loop ? MA_TRUE : MA_FALSE);
|
||||
ma_sound_set_volume(&previewSound, volume);
|
||||
ma_sound_set_spatialization_enabled(&previewSound, MA_FALSE);
|
||||
previewPath = path;
|
||||
@@ -217,6 +218,12 @@ bool AudioSystem::seekPreview(const std::string& path, double seconds) {
|
||||
return res == MA_SUCCESS;
|
||||
}
|
||||
|
||||
bool AudioSystem::setPreviewLoop(bool loop) {
|
||||
if (!previewActive) return false;
|
||||
ma_sound_set_looping(&previewSound, loop ? MA_TRUE : MA_FALSE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioSystem::playObjectSound(const SceneObject& obj) {
|
||||
if (!obj.hasAudioSource || obj.audioSource.clipPath.empty() || !obj.audioSource.enabled) return false;
|
||||
if (!ensureSoundFor(obj)) return false;
|
||||
@@ -268,12 +275,18 @@ AudioClipPreview AudioSystem::loadPreview(const std::string& path) {
|
||||
|
||||
const ma_uint64 framesPerBucket = std::max<ma_uint64>(1, totalFrames / kPreviewBuckets);
|
||||
preview.waveform.assign(static_cast<size_t>(kPreviewBuckets), 0.0f);
|
||||
if (preview.channels >= 2) {
|
||||
preview.waveformLeft.assign(static_cast<size_t>(kPreviewBuckets), 0.0f);
|
||||
preview.waveformRight.assign(static_cast<size_t>(kPreviewBuckets), 0.0f);
|
||||
}
|
||||
|
||||
std::vector<float> temp(kPreviewChunkFrames * preview.channels);
|
||||
ma_uint64 frameCursor = 0;
|
||||
size_t bucketIndex = 0;
|
||||
ma_uint64 bucketCursor = 0;
|
||||
float bucketMax = 0.0f;
|
||||
float bucketMaxLeft = 0.0f;
|
||||
float bucketMaxRight = 0.0f;
|
||||
|
||||
while (frameCursor < totalFrames && bucketIndex < preview.waveform.size()) {
|
||||
ma_uint64 framesToRead = std::min<ma_uint64>(kPreviewChunkFrames, totalFrames - frameCursor);
|
||||
@@ -285,26 +298,43 @@ AudioClipPreview AudioSystem::loadPreview(const std::string& path) {
|
||||
if (framesRead == 0) break;
|
||||
|
||||
for (ma_uint64 f = 0; f < framesRead; ++f) {
|
||||
size_t frameOffset = static_cast<size_t>(f * preview.channels);
|
||||
for (ma_uint32 c = 0; c < preview.channels; ++c) {
|
||||
float sample = temp[static_cast<size_t>(f * preview.channels + c)];
|
||||
float sample = temp[frameOffset + c];
|
||||
bucketMax = std::max(bucketMax, std::fabs(sample));
|
||||
}
|
||||
if (preview.channels >= 2) {
|
||||
float leftSample = temp[frameOffset];
|
||||
float rightSample = temp[frameOffset + 1];
|
||||
bucketMaxLeft = std::max(bucketMaxLeft, std::fabs(leftSample));
|
||||
bucketMaxRight = std::max(bucketMaxRight, std::fabs(rightSample));
|
||||
}
|
||||
bucketCursor++;
|
||||
frameCursor++;
|
||||
|
||||
if (bucketCursor >= framesPerBucket) {
|
||||
if (bucketIndex < preview.waveform.size()) {
|
||||
preview.waveform[bucketIndex] = std::clamp(bucketMax, 0.0f, 1.0f);
|
||||
if (preview.channels >= 2) {
|
||||
preview.waveformLeft[bucketIndex] = std::clamp(bucketMaxLeft, 0.0f, 1.0f);
|
||||
preview.waveformRight[bucketIndex] = std::clamp(bucketMaxRight, 0.0f, 1.0f);
|
||||
}
|
||||
bucketIndex++;
|
||||
}
|
||||
bucketCursor = 0;
|
||||
bucketMax = 0.0f;
|
||||
bucketMaxLeft = 0.0f;
|
||||
bucketMaxRight = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bucketIndex < preview.waveform.size() && bucketMax > 0.0f) {
|
||||
preview.waveform[bucketIndex] = std::clamp(bucketMax, 0.0f, 1.0f);
|
||||
if (preview.channels >= 2) {
|
||||
preview.waveformLeft[bucketIndex] = std::clamp(bucketMaxLeft, 0.0f, 1.0f);
|
||||
preview.waveformRight[bucketIndex] = std::clamp(bucketMaxRight, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ma_decoder_uninit(&decoder);
|
||||
|
||||
@@ -14,6 +14,8 @@ struct AudioClipPreview {
|
||||
uint32_t sampleRate = 0;
|
||||
double durationSeconds = 0.0;
|
||||
std::vector<float> waveform; // Normalized 0..1 amplitude envelope for drawing
|
||||
std::vector<float> waveformLeft; // Left channel envelope (if available)
|
||||
std::vector<float> waveformRight; // Right channel envelope (if available)
|
||||
};
|
||||
|
||||
class AudioSystem {
|
||||
@@ -26,12 +28,13 @@ public:
|
||||
void onPlayStop();
|
||||
void update(const std::vector<SceneObject>& objects, const Camera& listenerCamera, bool playing);
|
||||
|
||||
bool playPreview(const std::string& path, float volume = 1.0f);
|
||||
bool playPreview(const std::string& path, float volume = 1.0f, bool loop = false);
|
||||
void stopPreview();
|
||||
bool isPreviewing(const std::string& path) const;
|
||||
const AudioClipPreview* getPreview(const std::string& path);
|
||||
bool getPreviewTime(const std::string& path, double& cursorSeconds, double& durationSeconds) const;
|
||||
bool seekPreview(const std::string& path, double seconds);
|
||||
bool setPreviewLoop(bool loop);
|
||||
|
||||
// Scene audio control (runtime)
|
||||
bool playObjectSound(const SceneObject& obj);
|
||||
|
||||
103
src/EditorUI.cpp
103
src/EditorUI.cpp
@@ -188,81 +188,82 @@ void applyModernTheme() {
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
ImVec4 slate = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
|
||||
ImVec4 panel = ImVec4(0.11f, 0.12f, 0.14f, 1.00f);
|
||||
ImVec4 overlay = ImVec4(0.07f, 0.08f, 0.10f, 0.98f);
|
||||
ImVec4 accent = ImVec4(0.33f, 0.63f, 0.98f, 1.00f);
|
||||
ImVec4 accentMuted = ImVec4(0.25f, 0.46f, 0.78f, 1.00f);
|
||||
ImVec4 highlight = ImVec4(0.18f, 0.23f, 0.30f, 1.00f);
|
||||
ImVec4 slate = ImVec4(0.10f, 0.11f, 0.16f, 1.00f);
|
||||
ImVec4 panel = ImVec4(0.14f, 0.15f, 0.21f, 1.00f);
|
||||
ImVec4 overlay = ImVec4(0.09f, 0.10f, 0.14f, 0.98f);
|
||||
ImVec4 accent = ImVec4(0.58f, 0.34f, 0.78f, 1.00f);
|
||||
ImVec4 accentMuted = ImVec4(0.46f, 0.30f, 0.68f, 1.00f);
|
||||
ImVec4 highlight = ImVec4(0.20f, 0.22f, 0.30f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(0.87f, 0.89f, 0.92f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.52f, 0.56f, 0.62f, 1.00f);
|
||||
colors[ImGuiCol_Text] = ImVec4(0.90f, 0.91f, 0.94f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.56f, 0.60f, 0.66f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_WindowBg] = panel;
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.11f, 0.13f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.13f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_ChildBg] = panel;
|
||||
colors[ImGuiCol_PopupBg] = overlay;
|
||||
colors[ImGuiCol_Border] = ImVec4(0.21f, 0.24f, 0.28f, 0.60f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.20f, 0.22f, 0.30f, 0.70f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.06f, 0.07f, 0.08f, 1.00f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.09f, 0.10f, 0.14f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Header] = highlight;
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.22f, 0.28f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.44f, 0.72f, 1.00f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.24f, 0.28f, 0.38f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.44f, 0.30f, 0.68f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Button] = ImVec4(0.17f, 0.19f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.23f, 0.27f, 0.33f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.36f, 0.46f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.24f, 0.26f, 0.34f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.32f, 0.36f, 0.48f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.40f, 0.28f, 0.60f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.14f, 0.15f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.32f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.19f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.24f, 0.26f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.32f, 0.26f, 0.44f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.06f, 0.07f, 0.08f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.10f, 0.12f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.04f, 0.05f, 0.06f, 1.00f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.10f, 0.11f, 0.16f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.14f, 0.15f, 0.21f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.08f, 0.09f, 0.13f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.12f, 0.13f, 0.15f, 1.00f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.20f, 0.28f, 0.38f, 1.00f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.16f, 0.19f, 0.23f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocused] = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.13f, 0.15f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.13f, 0.14f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.30f, 0.26f, 0.42f, 1.00f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.18f, 0.20f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocused] = ImVec4(0.10f, 0.11f, 0.16f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.15f, 0.16f, 0.22f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.18f, 0.20f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.24f, 0.32f, 0.42f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.30f, 0.44f, 0.60f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.22f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.30f, 0.28f, 0.40f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.50f, 0.32f, 0.70f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.07f, 0.08f, 0.10f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.21f, 0.23f, 0.27f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.26f, 0.30f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.30f, 0.36f, 0.44f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.11f, 0.15f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.22f, 0.24f, 0.32f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.30f, 0.32f, 0.44f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.40f, 0.32f, 0.54f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_CheckMark] = accent;
|
||||
colors[ImGuiCol_SliderGrab] = accent;
|
||||
colors[ImGuiCol_SliderGrabActive] = accentMuted;
|
||||
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.22f, 0.26f, 0.33f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.32f, 0.46f, 0.62f, 0.80f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.28f, 0.38f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.42f, 0.32f, 0.62f, 0.80f);
|
||||
colors[ImGuiCol_ResizeGripActive] = accent;
|
||||
|
||||
colors[ImGuiCol_DockingPreview] = ImVec4(0.33f, 0.63f, 0.98f, 0.60f);
|
||||
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.05f, 0.06f, 0.07f, 1.00f);
|
||||
colors[ImGuiCol_DockingPreview] = ImVec4(0.58f, 0.34f, 0.78f, 0.55f);
|
||||
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.09f, 0.13f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.33f, 0.63f, 0.98f, 0.25f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.58f, 0.34f, 0.78f, 0.25f);
|
||||
colors[ImGuiCol_NavHighlight] = accent;
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.15f, 0.17f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.18f, 0.20f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.05f, 0.06f, 0.08f, 0.70f);
|
||||
|
||||
style.WindowRounding = 5.0f;
|
||||
style.ChildRounding = 5.0f;
|
||||
style.FrameRounding = 6.0f;
|
||||
style.PopupRounding = 6.0f;
|
||||
style.WindowRounding = 10.0f;
|
||||
style.ChildRounding = 12.0f;
|
||||
style.FrameRounding = 10.0f;
|
||||
style.PopupRounding = 12.0f;
|
||||
style.ScrollbarRounding = 10.0f;
|
||||
style.GrabRounding = 6.0f;
|
||||
style.TabRounding = 6.0f;
|
||||
style.GrabRounding = 8.0f;
|
||||
style.TabRounding = 10.0f;
|
||||
|
||||
style.WindowPadding = ImVec2(10.0f, 10.0f);
|
||||
style.FramePadding = ImVec2(9.0f, 5.0f);
|
||||
style.ItemSpacing = ImVec2(10.0f, 6.0f);
|
||||
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
|
||||
style.WindowPadding = ImVec2(12.0f, 12.0f);
|
||||
style.FramePadding = ImVec2(10.0f, 6.0f);
|
||||
style.ItemSpacing = ImVec2(10.0f, 8.0f);
|
||||
style.ItemInnerSpacing = ImVec2(8.0f, 6.0f);
|
||||
style.IndentSpacing = 18.0f;
|
||||
|
||||
style.WindowBorderSize = 1.0f;
|
||||
|
||||
@@ -21,268 +21,367 @@
|
||||
#endif
|
||||
|
||||
namespace FileIcons {
|
||||
namespace {
|
||||
ImU32 BlendColor(ImU32 a, ImU32 b, float t) {
|
||||
int ar = a & 0xFF;
|
||||
int ag = (a >> 8) & 0xFF;
|
||||
int ab = (a >> 16) & 0xFF;
|
||||
int aa = (a >> 24) & 0xFF;
|
||||
int br = b & 0xFF;
|
||||
int bg = (b >> 8) & 0xFF;
|
||||
int bb = (b >> 16) & 0xFF;
|
||||
int ba = (b >> 24) & 0xFF;
|
||||
int r = ar + static_cast<int>((br - ar) * t);
|
||||
int g = ag + static_cast<int>((bg - ag) * t);
|
||||
int bch = ab + static_cast<int>((bb - ab) * t);
|
||||
int aout = aa + static_cast<int>((ba - aa) * t);
|
||||
return IM_COL32(r, g, bch, aout);
|
||||
}
|
||||
|
||||
void DrawPaperSpeckles(ImDrawList* drawList, ImVec2 min, ImVec2 max, ImU32 color) {
|
||||
const ImVec2 points[] = {
|
||||
ImVec2(0.18f, 0.22f), ImVec2(0.42f, 0.36f), ImVec2(0.66f, 0.28f),
|
||||
ImVec2(0.28f, 0.62f), ImVec2(0.52f, 0.68f), ImVec2(0.74f, 0.58f),
|
||||
ImVec2(0.36f, 0.82f), ImVec2(0.82f, 0.78f)
|
||||
};
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
float r = std::min(w, h) * 0.015f;
|
||||
for (const auto& p : points) {
|
||||
ImVec2 dot(min.x + w * p.x, min.y + h * p.y);
|
||||
drawList->AddCircleFilled(dot, r, color);
|
||||
}
|
||||
}
|
||||
|
||||
struct PaperFrame {
|
||||
ImVec2 min;
|
||||
ImVec2 max;
|
||||
};
|
||||
|
||||
PaperFrame DrawPaperFileBase(ImDrawList* drawList, ImVec2 pos, float size, ImU32 accentColor) {
|
||||
const ImU32 kPaperBase = IM_COL32(205, 185, 128, 255);
|
||||
const ImU32 kPaperEdge = IM_COL32(122, 106, 72, 200);
|
||||
const ImU32 kPaperFold = IM_COL32(223, 206, 150, 230);
|
||||
const ImU32 kPaperShadow = IM_COL32(110, 95, 60, 80);
|
||||
const ImU32 kPaperSpeck = IM_COL32(120, 110, 80, 55);
|
||||
|
||||
float w = size * 0.78f;
|
||||
float h = size * 0.95f;
|
||||
float offsetX = (size - w) * 0.5f;
|
||||
float offsetY = (size - h) * 0.5f;
|
||||
float cornerSize = w * 0.22f;
|
||||
float rounding = size * 0.08f;
|
||||
float shadowOffset = size * 0.04f;
|
||||
|
||||
ImVec2 min = ImVec2(pos.x + offsetX, pos.y + offsetY);
|
||||
ImVec2 max = ImVec2(pos.x + offsetX + w, pos.y + offsetY + h);
|
||||
|
||||
drawList->AddRectFilled(ImVec2(min.x + shadowOffset, min.y + shadowOffset),
|
||||
ImVec2(max.x + shadowOffset, max.y + shadowOffset),
|
||||
kPaperShadow, rounding);
|
||||
|
||||
drawList->AddRectFilled(min, max, kPaperBase, rounding);
|
||||
drawList->AddRect(min, max, kPaperEdge, rounding, 0, 1.2f);
|
||||
|
||||
ImVec2 foldA(max.x - cornerSize, min.y);
|
||||
ImVec2 foldB(max.x, min.y + cornerSize);
|
||||
drawList->AddTriangleFilled(foldA, foldB, ImVec2(max.x - cornerSize, min.y + cornerSize), kPaperFold);
|
||||
|
||||
ImU32 band = BlendColor(accentColor, kPaperBase, 0.65f);
|
||||
float bandH = h * 0.14f;
|
||||
drawList->AddRectFilled(ImVec2(min.x, max.y - bandH), max, band, rounding);
|
||||
|
||||
DrawPaperSpeckles(drawList, min, ImVec2(max.x, max.y - bandH), kPaperSpeck);
|
||||
|
||||
ImVec2 contentMin(min.x + w * 0.12f, min.y + h * 0.16f);
|
||||
ImVec2 contentMax(max.x - w * 0.12f, max.y - h * 0.22f);
|
||||
return {contentMin, contentMax};
|
||||
}
|
||||
|
||||
PaperFrame DrawSheetFileBase(ImDrawList* drawList, ImVec2 pos, float size, ImU32 accentColor) {
|
||||
const ImU32 kSheetBase = IM_COL32(248, 248, 248, 255);
|
||||
const ImU32 kSheetBack = IM_COL32(238, 238, 238, 235);
|
||||
const ImU32 kSheetEdge = IM_COL32(185, 185, 185, 200);
|
||||
const ImU32 kSheetFold = IM_COL32(232, 232, 232, 255);
|
||||
const ImU32 kSheetShadow = IM_COL32(0, 0, 0, 55);
|
||||
const ImU32 kSheetInner = IM_COL32(255, 255, 255, 80);
|
||||
const ImU32 kFoldShadow = IM_COL32(0, 0, 0, 35);
|
||||
|
||||
float w = size * 0.78f;
|
||||
float h = size * 0.95f;
|
||||
float offsetX = (size - w) * 0.5f;
|
||||
float offsetY = (size - h) * 0.5f;
|
||||
float cornerSize = w * 0.24f;
|
||||
float rounding = size * 0.075f;
|
||||
float shadowOffset = size * 0.04f;
|
||||
float backOffset = size * 0.02f;
|
||||
|
||||
ImVec2 min = ImVec2(pos.x + offsetX, pos.y + offsetY);
|
||||
ImVec2 max = ImVec2(pos.x + offsetX + w, pos.y + offsetY + h);
|
||||
|
||||
ImVec2 backMin = ImVec2(min.x + backOffset, min.y + backOffset * 0.6f);
|
||||
ImVec2 backMax = ImVec2(max.x + backOffset, max.y + backOffset * 0.6f);
|
||||
drawList->AddRectFilled(backMin, backMax, kSheetBack, rounding);
|
||||
|
||||
drawList->AddRectFilled(ImVec2(min.x + shadowOffset, min.y + shadowOffset),
|
||||
ImVec2(max.x + shadowOffset, max.y + shadowOffset),
|
||||
kSheetShadow, rounding);
|
||||
|
||||
drawList->AddRectFilled(min, max, kSheetBase, rounding);
|
||||
drawList->AddRect(min, max, kSheetEdge, rounding, 0, 1.2f);
|
||||
drawList->AddRect(ImVec2(min.x + 1.0f, min.y + 1.0f),
|
||||
ImVec2(max.x - 1.0f, max.y - 1.0f),
|
||||
kSheetInner, rounding, 0, 1.0f);
|
||||
|
||||
ImVec2 foldA(max.x - cornerSize, min.y);
|
||||
ImVec2 foldB(max.x, min.y + cornerSize);
|
||||
ImVec2 foldC(max.x - cornerSize, min.y + cornerSize);
|
||||
drawList->AddTriangleFilled(ImVec2(foldA.x + 1.0f, foldA.y + 1.0f),
|
||||
ImVec2(foldB.x + 1.0f, foldB.y + 1.0f),
|
||||
ImVec2(foldC.x + 1.0f, foldC.y + 1.0f),
|
||||
kFoldShadow);
|
||||
drawList->AddTriangleFilled(foldA, foldB, ImVec2(max.x - cornerSize, min.y + cornerSize), kSheetFold);
|
||||
drawList->AddTriangle(foldA, foldB, foldC, kSheetEdge, 1.0f);
|
||||
|
||||
ImU32 band = BlendColor(accentColor, kSheetBase, 0.55f);
|
||||
float bandH = h * 0.16f;
|
||||
drawList->AddRectFilled(ImVec2(min.x, max.y - bandH), max, band, rounding);
|
||||
|
||||
ImVec2 contentMin(min.x + w * 0.12f, min.y + h * 0.16f);
|
||||
ImVec2 contentMax(max.x - w * 0.12f, max.y - h * 0.24f);
|
||||
return {contentMin, contentMax};
|
||||
}
|
||||
|
||||
ImU32 InkColor() {
|
||||
return IM_COL32(92, 78, 52, 220);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a folder icon
|
||||
void DrawFolderIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
const ImU32 kFolderBase = IM_COL32(198, 178, 120, 255);
|
||||
const ImU32 kFolderTab = IM_COL32(215, 197, 140, 255);
|
||||
const ImU32 kFolderEdge = IM_COL32(120, 104, 70, 200);
|
||||
const ImU32 kFolderShadow = IM_COL32(110, 95, 60, 70);
|
||||
const ImU32 kFolderSpeck = IM_COL32(120, 110, 80, 55);
|
||||
|
||||
float w = size;
|
||||
float h = size * 0.75f;
|
||||
float tabW = w * 0.4f;
|
||||
float tabH = h * 0.15f;
|
||||
|
||||
// Folder body
|
||||
drawList->AddRectFilled(
|
||||
ImVec2(pos.x, pos.y + tabH),
|
||||
ImVec2(pos.x + w, pos.y + h),
|
||||
color, 3.0f
|
||||
);
|
||||
// Folder tab
|
||||
drawList->AddRectFilled(
|
||||
ImVec2(pos.x, pos.y),
|
||||
ImVec2(pos.x + tabW, pos.y + tabH + 2),
|
||||
color, 2.0f
|
||||
);
|
||||
float h = size * 0.72f;
|
||||
float tabW = w * 0.46f;
|
||||
float tabH = h * 0.28f;
|
||||
float rounding = size * 0.08f;
|
||||
float shadowOffset = size * 0.04f;
|
||||
|
||||
ImVec2 bodyMin(pos.x, pos.y + tabH * 0.55f);
|
||||
ImVec2 bodyMax(pos.x + w, pos.y + h);
|
||||
ImVec2 tabMin(pos.x + w * 0.06f, pos.y);
|
||||
ImVec2 tabMax(pos.x + tabW, pos.y + tabH);
|
||||
|
||||
drawList->AddRectFilled(ImVec2(bodyMin.x + shadowOffset, bodyMin.y + shadowOffset),
|
||||
ImVec2(bodyMax.x + shadowOffset, bodyMax.y + shadowOffset),
|
||||
kFolderShadow, rounding);
|
||||
|
||||
drawList->AddRectFilled(bodyMin, bodyMax, kFolderBase, rounding);
|
||||
drawList->AddRect(bodyMin, bodyMax, kFolderEdge, rounding, 0, 1.2f);
|
||||
|
||||
drawList->AddRectFilled(tabMin, tabMax, kFolderTab, rounding);
|
||||
drawList->AddRect(tabMin, tabMax, kFolderEdge, rounding, 0, 1.0f);
|
||||
|
||||
ImU32 band = BlendColor(color, kFolderBase, 0.7f);
|
||||
float bandH = h * 0.14f;
|
||||
drawList->AddRectFilled(ImVec2(bodyMin.x, bodyMax.y - bandH), bodyMax, band, rounding);
|
||||
|
||||
DrawPaperSpeckles(drawList, bodyMin, ImVec2(bodyMax.x, bodyMax.y - bandH), kFolderSpeck);
|
||||
}
|
||||
|
||||
// Draw a scene/document icon
|
||||
void DrawSceneIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float w = size * 0.8f;
|
||||
float h = size;
|
||||
float cornerSize = w * 0.25f;
|
||||
|
||||
// Main document body
|
||||
ImVec2 p1 = ImVec2(pos.x, pos.y);
|
||||
ImVec2 p2 = ImVec2(pos.x + w - cornerSize, pos.y);
|
||||
ImVec2 p3 = ImVec2(pos.x + w, pos.y + cornerSize);
|
||||
ImVec2 p4 = ImVec2(pos.x + w, pos.y + h);
|
||||
ImVec2 p5 = ImVec2(pos.x, pos.y + h);
|
||||
|
||||
drawList->AddQuadFilled(p1, ImVec2(pos.x + w - cornerSize, pos.y), ImVec2(pos.x + w - cornerSize, pos.y + h), p5, color);
|
||||
drawList->AddTriangleFilled(p2, p3, ImVec2(pos.x + w - cornerSize, pos.y + cornerSize), color);
|
||||
drawList->AddRectFilled(ImVec2(pos.x + w - cornerSize, pos.y + cornerSize), p4, color);
|
||||
|
||||
// Corner fold
|
||||
drawList->AddTriangleFilled(p2, ImVec2(pos.x + w - cornerSize, pos.y + cornerSize), p3,
|
||||
IM_COL32(255, 255, 255, 60));
|
||||
|
||||
// Scene icon indicator (play triangle)
|
||||
float cx = pos.x + w * 0.5f;
|
||||
float cy = pos.y + h * 0.55f;
|
||||
float triSize = size * 0.25f;
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
float w = frame.max.x - frame.min.x;
|
||||
float h = frame.max.y - frame.min.y;
|
||||
ImVec2 center(frame.min.x + w * 0.55f, frame.min.y + h * 0.55f);
|
||||
float tri = std::min(w, h) * 0.35f;
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(cx - triSize * 0.4f, cy - triSize * 0.5f),
|
||||
ImVec2(cx - triSize * 0.4f, cy + triSize * 0.5f),
|
||||
ImVec2(cx + triSize * 0.5f, cy),
|
||||
IM_COL32(255, 255, 255, 180)
|
||||
ImVec2(center.x - tri * 0.4f, center.y - tri * 0.55f),
|
||||
ImVec2(center.x - tri * 0.4f, center.y + tri * 0.55f),
|
||||
ImVec2(center.x + tri * 0.6f, center.y),
|
||||
ink
|
||||
);
|
||||
}
|
||||
|
||||
// Draw a 3D model icon (cube wireframe)
|
||||
void DrawModelIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float s = size * 0.8f;
|
||||
float offset = size * 0.1f;
|
||||
float depth = s * 0.3f;
|
||||
|
||||
// Front face
|
||||
ImVec2 f1 = ImVec2(pos.x + offset, pos.y + offset + depth);
|
||||
ImVec2 f2 = ImVec2(pos.x + offset + s, pos.y + offset + depth);
|
||||
ImVec2 f3 = ImVec2(pos.x + offset + s, pos.y + offset + s);
|
||||
ImVec2 f4 = ImVec2(pos.x + offset, pos.y + offset + s);
|
||||
|
||||
// Back face
|
||||
ImVec2 b1 = ImVec2(f1.x + depth, f1.y - depth);
|
||||
ImVec2 b2 = ImVec2(f2.x + depth, f2.y - depth);
|
||||
ImVec2 b3 = ImVec2(f3.x + depth, f3.y - depth);
|
||||
|
||||
// Fill front face
|
||||
drawList->AddQuadFilled(f1, f2, f3, f4, color);
|
||||
|
||||
// Fill top face
|
||||
drawList->AddQuadFilled(f1, f2, b2, b1, IM_COL32(
|
||||
(color & 0xFF) * 0.7f,
|
||||
((color >> 8) & 0xFF) * 0.7f,
|
||||
((color >> 16) & 0xFF) * 0.7f,
|
||||
(color >> 24) & 0xFF
|
||||
));
|
||||
|
||||
// Fill right face
|
||||
drawList->AddQuadFilled(f2, b2, b3, f3, IM_COL32(
|
||||
(color & 0xFF) * 0.5f,
|
||||
((color >> 8) & 0xFF) * 0.5f,
|
||||
((color >> 16) & 0xFF) * 0.5f,
|
||||
(color >> 24) & 0xFF
|
||||
));
|
||||
|
||||
// Edges
|
||||
ImU32 edgeColor = IM_COL32(255, 255, 255, 100);
|
||||
drawList->AddLine(f1, f2, edgeColor, 1.0f);
|
||||
drawList->AddLine(f2, f3, edgeColor, 1.0f);
|
||||
drawList->AddLine(f3, f4, edgeColor, 1.0f);
|
||||
drawList->AddLine(f4, f1, edgeColor, 1.0f);
|
||||
drawList->AddLine(f1, b1, edgeColor, 1.0f);
|
||||
drawList->AddLine(f2, b2, edgeColor, 1.0f);
|
||||
drawList->AddLine(b1, b2, edgeColor, 1.0f);
|
||||
drawList->AddLine(f3, b3, edgeColor, 1.0f);
|
||||
drawList->AddLine(b2, b3, edgeColor, 1.0f);
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
ImVec2 min = frame.min;
|
||||
ImVec2 max = frame.max;
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
float s = std::min(w, h) * 0.6f;
|
||||
ImVec2 origin(min.x + w * 0.25f, min.y + h * 0.25f);
|
||||
float depth = s * 0.35f;
|
||||
|
||||
ImVec2 f1(origin.x, origin.y + depth);
|
||||
ImVec2 f2(origin.x + s, origin.y + depth);
|
||||
ImVec2 f3(origin.x + s, origin.y + s + depth);
|
||||
ImVec2 f4(origin.x, origin.y + s + depth);
|
||||
|
||||
ImVec2 b1(f1.x + depth, f1.y - depth);
|
||||
ImVec2 b2(f2.x + depth, f2.y - depth);
|
||||
ImVec2 b3(f3.x + depth, f3.y - depth);
|
||||
ImVec2 b4(f4.x + depth, f4.y - depth);
|
||||
|
||||
drawList->AddLine(f1, f2, ink, 1.4f);
|
||||
drawList->AddLine(f2, f3, ink, 1.4f);
|
||||
drawList->AddLine(f3, f4, ink, 1.4f);
|
||||
drawList->AddLine(f4, f1, ink, 1.4f);
|
||||
drawList->AddLine(b1, b2, ink, 1.2f);
|
||||
drawList->AddLine(b2, b3, ink, 1.2f);
|
||||
drawList->AddLine(b3, b4, ink, 1.2f);
|
||||
drawList->AddLine(b4, b1, ink, 1.2f);
|
||||
drawList->AddLine(f1, b1, ink, 1.2f);
|
||||
drawList->AddLine(f2, b2, ink, 1.2f);
|
||||
drawList->AddLine(f3, b3, ink, 1.2f);
|
||||
drawList->AddLine(f4, b4, ink, 1.2f);
|
||||
}
|
||||
|
||||
// Draw a texture/image icon
|
||||
void DrawTextureIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float padding = size * 0.1f;
|
||||
ImVec2 tl = ImVec2(pos.x + padding, pos.y + padding);
|
||||
ImVec2 br = ImVec2(pos.x + size - padding, pos.y + size - padding);
|
||||
|
||||
// Frame
|
||||
drawList->AddRectFilled(tl, br, color, 2.0f);
|
||||
|
||||
// Mountain landscape
|
||||
float midY = pos.y + size * 0.6f;
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
ImVec2 min = frame.min;
|
||||
ImVec2 max = frame.max;
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
|
||||
ImVec2 frameMin(min.x + w * 0.08f, min.y + h * 0.12f);
|
||||
ImVec2 frameMax(max.x - w * 0.08f, max.y - h * 0.1f);
|
||||
drawList->AddRect(frameMin, frameMax, ink, 3.0f, 0, 1.2f);
|
||||
|
||||
float midY = frameMax.y - h * 0.28f;
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(pos.x + size * 0.2f, br.y - padding),
|
||||
ImVec2(pos.x + size * 0.45f, midY),
|
||||
ImVec2(pos.x + size * 0.7f, br.y - padding),
|
||||
IM_COL32(60, 60, 60, 255)
|
||||
ImVec2(frameMin.x + w * 0.1f, frameMax.y),
|
||||
ImVec2(frameMin.x + w * 0.35f, midY),
|
||||
ImVec2(frameMin.x + w * 0.6f, frameMax.y),
|
||||
ink
|
||||
);
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(pos.x + size * 0.5f, br.y - padding),
|
||||
ImVec2(pos.x + size * 0.7f, midY + size * 0.1f),
|
||||
ImVec2(pos.x + size * 0.9f, br.y - padding),
|
||||
IM_COL32(80, 80, 80, 255)
|
||||
ImVec2(frameMin.x + w * 0.45f, frameMax.y),
|
||||
ImVec2(frameMin.x + w * 0.7f, midY + h * 0.1f),
|
||||
ImVec2(frameMin.x + w * 0.9f, frameMax.y),
|
||||
ink
|
||||
);
|
||||
|
||||
// Sun
|
||||
float sunR = size * 0.1f;
|
||||
drawList->AddCircleFilled(ImVec2(pos.x + size * 0.75f, pos.y + size * 0.35f), sunR, IM_COL32(255, 220, 100, 255));
|
||||
|
||||
float sunR = std::min(w, h) * 0.12f;
|
||||
drawList->AddCircleFilled(ImVec2(frameMax.x - w * 0.18f, frameMin.y + h * 0.2f), sunR, ink);
|
||||
}
|
||||
|
||||
// Draw a shader icon (code brackets)
|
||||
void DrawShaderIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float padding = size * 0.15f;
|
||||
ImVec2 tl = ImVec2(pos.x + padding, pos.y + padding);
|
||||
ImVec2 br = ImVec2(pos.x + size - padding, pos.y + size - padding);
|
||||
|
||||
// Background
|
||||
drawList->AddRectFilled(tl, br, color, 3.0f);
|
||||
|
||||
// Code lines
|
||||
ImU32 lineColor = IM_COL32(255, 255, 255, 180);
|
||||
float lineY = pos.y + size * 0.35f;
|
||||
float lineH = size * 0.08f;
|
||||
float lineSpacing = size * 0.15f;
|
||||
|
||||
drawList->AddRectFilled(ImVec2(pos.x + size * 0.25f, lineY), ImVec2(pos.x + size * 0.7f, lineY + lineH), lineColor);
|
||||
lineY += lineSpacing;
|
||||
drawList->AddRectFilled(ImVec2(pos.x + size * 0.3f, lineY), ImVec2(pos.x + size * 0.8f, lineY + lineH), lineColor);
|
||||
lineY += lineSpacing;
|
||||
drawList->AddRectFilled(ImVec2(pos.x + size * 0.25f, lineY), ImVec2(pos.x + size * 0.55f, lineY + lineH), lineColor);
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
ImVec2 min = frame.min;
|
||||
ImVec2 max = frame.max;
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
|
||||
float lineH = h * 0.12f;
|
||||
float lineY = min.y + h * 0.2f;
|
||||
drawList->AddRectFilled(ImVec2(min.x + w * 0.15f, lineY), ImVec2(min.x + w * 0.75f, lineY + lineH), ink);
|
||||
lineY += h * 0.22f;
|
||||
drawList->AddRectFilled(ImVec2(min.x + w * 0.2f, lineY), ImVec2(min.x + w * 0.85f, lineY + lineH), ink);
|
||||
lineY += h * 0.22f;
|
||||
drawList->AddRectFilled(ImVec2(min.x + w * 0.15f, lineY), ImVec2(min.x + w * 0.55f, lineY + lineH), ink);
|
||||
}
|
||||
|
||||
// Draw an audio icon (speaker/waveform)
|
||||
void DrawAudioIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
// Speaker body
|
||||
float spkW = size * 0.25f;
|
||||
float spkH = size * 0.3f;
|
||||
float cx = pos.x + size * 0.35f;
|
||||
float cy = pos.y + size * 0.5f;
|
||||
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
ImVec2 min = frame.min;
|
||||
ImVec2 max = frame.max;
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
float spkW = w * 0.25f;
|
||||
float spkH = h * 0.28f;
|
||||
float cx = min.x + w * 0.35f;
|
||||
float cy = min.y + h * 0.55f;
|
||||
|
||||
drawList->AddRectFilled(
|
||||
ImVec2(cx - spkW * 0.5f, cy - spkH * 0.5f),
|
||||
ImVec2(cx + spkW * 0.5f, cy + spkH * 0.5f),
|
||||
color
|
||||
ink
|
||||
);
|
||||
|
||||
// Speaker cone
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(cx + spkW * 0.5f, cy - spkH * 0.5f),
|
||||
ImVec2(cx + spkW * 0.5f, cy + spkH * 0.5f),
|
||||
ImVec2(cx + spkW * 1.2f, cy + spkH),
|
||||
color
|
||||
ImVec2(cx + spkW * 1.1f, cy + spkH * 0.9f),
|
||||
ink
|
||||
);
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(cx + spkW * 0.5f, cy - spkH * 0.5f),
|
||||
ImVec2(cx + spkW * 1.2f, cy - spkH),
|
||||
ImVec2(cx + spkW * 1.2f, cy + spkH),
|
||||
color
|
||||
ImVec2(cx + spkW * 1.1f, cy - spkH * 0.9f),
|
||||
ImVec2(cx + spkW * 1.1f, cy + spkH * 0.9f),
|
||||
ink
|
||||
);
|
||||
|
||||
// Sound waves
|
||||
ImU32 waveColor = IM_COL32(255, 255, 255, 150);
|
||||
float waveX = cx + spkW * 1.5f;
|
||||
|
||||
float waveX = cx + spkW * 1.4f;
|
||||
drawList->AddBezierQuadratic(
|
||||
ImVec2(waveX, cy - size * 0.15f),
|
||||
ImVec2(waveX + size * 0.1f, cy),
|
||||
ImVec2(waveX, cy + size * 0.15f),
|
||||
waveColor, 2.0f
|
||||
ImVec2(waveX, cy - h * 0.12f),
|
||||
ImVec2(waveX + w * 0.12f, cy),
|
||||
ImVec2(waveX, cy + h * 0.12f),
|
||||
ink, 1.6f
|
||||
);
|
||||
waveX += size * 0.12f;
|
||||
waveX += w * 0.12f;
|
||||
drawList->AddBezierQuadratic(
|
||||
ImVec2(waveX, cy - size * 0.22f),
|
||||
ImVec2(waveX + size * 0.12f, cy),
|
||||
ImVec2(waveX, cy + size * 0.22f),
|
||||
waveColor, 2.0f
|
||||
ImVec2(waveX, cy - h * 0.2f),
|
||||
ImVec2(waveX + w * 0.14f, cy),
|
||||
ImVec2(waveX, cy + h * 0.2f),
|
||||
ink, 1.6f
|
||||
);
|
||||
}
|
||||
|
||||
// Draw a generic file icon
|
||||
void DrawFileIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float w = size * 0.7f;
|
||||
float h = size * 0.9f;
|
||||
float offsetX = (size - w) * 0.5f;
|
||||
float offsetY = (size - h) * 0.5f;
|
||||
float cornerSize = w * 0.25f;
|
||||
|
||||
ImVec2 p1 = ImVec2(pos.x + offsetX, pos.y + offsetY);
|
||||
ImVec2 p2 = ImVec2(pos.x + offsetX + w - cornerSize, pos.y + offsetY);
|
||||
ImVec2 p3 = ImVec2(pos.x + offsetX + w, pos.y + offsetY + cornerSize);
|
||||
ImVec2 p4 = ImVec2(pos.x + offsetX + w, pos.y + offsetY + h);
|
||||
ImVec2 p5 = ImVec2(pos.x + offsetX, pos.y + offsetY + h);
|
||||
|
||||
// Main body
|
||||
drawList->AddQuadFilled(p1, p2, ImVec2(p2.x, p4.y), p5, color);
|
||||
drawList->AddTriangleFilled(p2, p3, ImVec2(p2.x, p3.y), color);
|
||||
drawList->AddRectFilled(ImVec2(p2.x, p3.y), p4, color);
|
||||
|
||||
// Corner fold
|
||||
drawList->AddTriangleFilled(p2, ImVec2(p2.x, p3.y), p3, IM_COL32(255, 255, 255, 50));
|
||||
DrawSheetFileBase(drawList, pos, size, color);
|
||||
}
|
||||
|
||||
// Draw a script/code icon
|
||||
void DrawScriptIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float padding = size * 0.12f;
|
||||
ImVec2 tl = ImVec2(pos.x + padding, pos.y + padding);
|
||||
ImVec2 br = ImVec2(pos.x + size - padding, pos.y + size - padding);
|
||||
|
||||
// Background
|
||||
drawList->AddRectFilled(tl, br, color, 3.0f);
|
||||
|
||||
// Brackets < >
|
||||
ImU32 bracketColor = IM_COL32(255, 255, 255, 200);
|
||||
float cx = pos.x + size * 0.5f;
|
||||
float cy = pos.y + size * 0.5f;
|
||||
float bSize = size * 0.2f;
|
||||
|
||||
// Left bracket <
|
||||
drawList->AddLine(ImVec2(cx - bSize * 0.5f, cy - bSize), ImVec2(cx - bSize * 1.5f, cy), bracketColor, 2.5f);
|
||||
drawList->AddLine(ImVec2(cx - bSize * 1.5f, cy), ImVec2(cx - bSize * 0.5f, cy + bSize), bracketColor, 2.5f);
|
||||
|
||||
// Right bracket >
|
||||
drawList->AddLine(ImVec2(cx + bSize * 0.5f, cy - bSize), ImVec2(cx + bSize * 1.5f, cy), bracketColor, 2.5f);
|
||||
drawList->AddLine(ImVec2(cx + bSize * 1.5f, cy), ImVec2(cx + bSize * 0.5f, cy + bSize), bracketColor, 2.5f);
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
ImVec2 min = frame.min;
|
||||
ImVec2 max = frame.max;
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
float cx = min.x + w * 0.5f;
|
||||
float cy = min.y + h * 0.5f;
|
||||
float bSize = std::min(w, h) * 0.28f;
|
||||
|
||||
drawList->AddLine(ImVec2(cx - bSize * 0.5f, cy - bSize), ImVec2(cx - bSize * 1.3f, cy), ink, 1.8f);
|
||||
drawList->AddLine(ImVec2(cx - bSize * 1.3f, cy), ImVec2(cx - bSize * 0.5f, cy + bSize), ink, 1.8f);
|
||||
drawList->AddLine(ImVec2(cx + bSize * 0.5f, cy - bSize), ImVec2(cx + bSize * 1.3f, cy), ink, 1.8f);
|
||||
drawList->AddLine(ImVec2(cx + bSize * 1.3f, cy), ImVec2(cx + bSize * 0.5f, cy + bSize), ink, 1.8f);
|
||||
}
|
||||
|
||||
// Draw a text icon
|
||||
void DrawTextIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
DrawFileIcon(drawList, pos, size, color);
|
||||
|
||||
// Text lines
|
||||
ImU32 lineColor = IM_COL32(255, 255, 255, 150);
|
||||
float startX = pos.x + size * 0.25f;
|
||||
float endX = pos.x + size * 0.65f;
|
||||
float lineY = pos.y + size * 0.4f;
|
||||
float lineH = size * 0.06f;
|
||||
float spacing = size * 0.12f;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float w = (i == 1) ? (endX - startX) * 0.7f : (endX - startX);
|
||||
drawList->AddRectFilled(ImVec2(startX, lineY), ImVec2(startX + w, lineY + lineH), lineColor);
|
||||
PaperFrame frame = DrawSheetFileBase(drawList, pos, size, color);
|
||||
ImU32 ink = IM_COL32(90, 90, 90, 230);
|
||||
ImVec2 min = frame.min;
|
||||
ImVec2 max = frame.max;
|
||||
float w = max.x - min.x;
|
||||
float h = max.y - min.y;
|
||||
float lineY = min.y + h * 0.25f;
|
||||
float lineH = h * 0.12f;
|
||||
float spacing = h * 0.2f;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
float lineW = (i == 1) ? w * 0.55f : w * 0.75f;
|
||||
drawList->AddRectFilled(ImVec2(min.x + w * 0.12f, lineY),
|
||||
ImVec2(min.x + w * 0.12f + lineW, lineY + lineH),
|
||||
ink);
|
||||
lineY += spacing;
|
||||
}
|
||||
}
|
||||
@@ -647,23 +746,7 @@ void Engine::renderFileBrowserPanel() {
|
||||
fileBrowser.viewMode = isGridMode ? FileBrowserViewMode::List : FileBrowserViewMode::Grid;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip(isGridMode ? "Switch to List View" : "Switch to Grid View");
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refresh", ImVec2(68, 0))) {
|
||||
fileBrowser.needsRefresh = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("New Mat", ImVec2(78, 0))) {
|
||||
fs::path target = fileBrowser.currentPath / "NewMaterial.mat";
|
||||
int counter = 1;
|
||||
while (fs::exists(target)) {
|
||||
target = fileBrowser.currentPath / ("NewMaterial" + std::to_string(counter++) + ".mat");
|
||||
}
|
||||
SceneObject temp("Material", ObjectType::Cube, -1);
|
||||
temp.materialPath = target.string();
|
||||
saveMaterialToFile(temp);
|
||||
fileBrowser.needsRefresh = true;
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
@@ -125,9 +125,11 @@ void Engine::renderLauncher() {
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(24.0f, 24.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 18.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.09f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
@@ -143,27 +145,65 @@ void Engine::renderLauncher() {
|
||||
|
||||
if (ImGui::Begin("Launcher", nullptr, flags))
|
||||
{
|
||||
float leftPanelWidth = 280.0f;
|
||||
const float leftPanelWidth = 300.0f;
|
||||
const float heroHeight = 120.0f;
|
||||
const ImVec4 bgTopLeft = ImVec4(0.10f, 0.11f, 0.16f, 1.0f);
|
||||
const ImVec4 bgTopRight = ImVec4(0.15f, 0.16f, 0.22f, 1.0f);
|
||||
const ImVec4 bgBottomRight = ImVec4(0.07f, 0.08f, 0.12f, 1.0f);
|
||||
const ImVec4 bgBottomLeft = ImVec4(0.08f, 0.09f, 0.13f, 1.0f);
|
||||
const ImVec4 cardBg = ImVec4(0.14f, 0.15f, 0.21f, 0.98f);
|
||||
const ImVec4 cardOutline = ImVec4(0.20f, 0.22f, 0.30f, 0.70f);
|
||||
const ImVec4 accent = ImVec4(0.91f, 0.42f, 0.78f, 1.0f);
|
||||
const ImVec4 accentCool = ImVec4(0.42f, 0.72f, 0.96f, 1.0f);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.06f, 0.06f, 0.07f, 1.0f));
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
ImVec2 windowPos = ImGui::GetWindowPos();
|
||||
ImVec2 windowSize = ImGui::GetWindowSize();
|
||||
drawList->AddRectFilledMultiColor(
|
||||
windowPos,
|
||||
ImVec2(windowPos.x + windowSize.x, windowPos.y + windowSize.y),
|
||||
ImGui::GetColorU32(bgTopLeft),
|
||||
ImGui::GetColorU32(bgTopRight),
|
||||
ImGui::GetColorU32(bgBottomRight),
|
||||
ImGui::GetColorU32(bgBottomLeft)
|
||||
);
|
||||
|
||||
ImGui::BeginChild("LauncherHero", ImVec2(0, heroHeight), true,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground);
|
||||
|
||||
ImDrawList* heroDraw = ImGui::GetWindowDrawList();
|
||||
ImVec2 heroPos = ImGui::GetWindowPos();
|
||||
ImVec2 heroSize = ImGui::GetWindowSize();
|
||||
heroDraw->AddRectFilled(heroPos, ImVec2(heroPos.x + heroSize.x, heroPos.y + heroSize.y),
|
||||
ImGui::GetColorU32(cardBg), 18.0f);
|
||||
heroDraw->AddRect(heroPos, ImVec2(heroPos.x + heroSize.x, heroPos.y + heroSize.y),
|
||||
ImGui::GetColorU32(cardBg), 18.0f);
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(28.0f, 24.0f));
|
||||
ImGui::TextDisabled("Project Manager");
|
||||
ImGui::SetWindowFontScale(1.4f);
|
||||
ImGui::TextColored(ImVec4(0.95f, 0.96f, 0.98f, 1.0f), "Modularity");
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::TextColored(ImVec4(0.70f, 0.73f, 0.80f, 1.0f), "Modularity | Debug Build V0.7.0");
|
||||
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, cardBg);
|
||||
ImGui::BeginChild("LauncherLeft", ImVec2(leftPanelWidth, 0), true);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(0.45f, 0.72f, 0.95f, 1.0f), "MODULARITY");
|
||||
ImGui::TextDisabled("Game Engine");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(0.78f, 0.80f, 0.86f, 1.0f), "GET STARTED");
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.78f, 1.0f), "GET STARTED");
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.32f, 0.22f, 0.54f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.43f, 0.30f, 0.70f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.38f, 0.26f, 0.60f, 1.0f));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.18f, 0.38f, 0.55f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.24f, 0.48f, 0.68f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.20f, 0.42f, 0.60f, 1.0f));
|
||||
|
||||
if (ImGui::Button("New Project", ImVec2(-1, 36.0f)))
|
||||
if (ImGui::Button("New Project", ImVec2(-1, 40.0f)))
|
||||
{
|
||||
projectManager.showNewProjectDialog = true;
|
||||
projectManager.errorMessage.clear();
|
||||
@@ -186,7 +226,7 @@ void Engine::renderLauncher() {
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Open Project", ImVec2(-1, 36.0f)))
|
||||
if (ImGui::Button("Open Project", ImVec2(-1, 40.0f)))
|
||||
{
|
||||
projectManager.showOpenProjectDialog = true;
|
||||
projectManager.errorMessage.clear();
|
||||
@@ -194,44 +234,48 @@ void Engine::renderLauncher() {
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.78f, 1.0f), "QUICK ACTIONS");
|
||||
ImGui::TextColored(ImVec4(0.78f, 0.80f, 0.86f, 1.0f), "QUICK ACTIONS");
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Documentation", ImVec2(-1, 30.0f)))
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20f, 0.22f, 0.32f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.26f, 0.30f, 0.42f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.24f, 0.28f, 0.40f, 1.0f));
|
||||
|
||||
if (ImGui::Button("Documentation", ImVec2(-1, 34.0f)))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
system("start https://github.com");
|
||||
system("start https://docs.shockinteractive.xyz");
|
||||
#else
|
||||
system("xdg-open https://github.com &");
|
||||
system("xdg-open https://docs.shockinteractive.xyz &");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ImGui::Button("Exit", ImVec2(-1, 30.0f)))
|
||||
if (ImGui::Button("Exit", ImVec2(-1, 34.0f)))
|
||||
{
|
||||
glfwSetWindowShouldClose(editorWindow, GLFW_TRUE);
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.10f, 0.10f, 0.11f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, cardBg);
|
||||
ImGui::BeginChild("LauncherRight", ImVec2(0, 0), true);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.78f, 1.0f), "RECENT PROJECTS");
|
||||
ImGui::TextColored(ImVec4(0.78f, 0.80f, 0.86f, 1.0f), "RECENT PROJECTS");
|
||||
ImGui::Spacing();
|
||||
|
||||
if (projectManager.recentProjects.empty())
|
||||
{
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("No recent projects");
|
||||
ImGui::TextDisabled("Create a new project to get started!");
|
||||
ImGui::TextDisabled("There are no recent projects yet.\nCreate or open a project to get started.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -241,30 +285,12 @@ void Engine::renderLauncher() {
|
||||
const auto& rp = projectManager.recentProjects[i];
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
|
||||
char label[512];
|
||||
std::snprintf(label, sizeof(label), "%s\n%s",
|
||||
rp.name.c_str(), rp.path.c_str());
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.20f, 0.30f, 0.45f, 0.40f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.25f, 0.38f, 0.55f, 0.70f));
|
||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.20f, 0.35f, 0.60f, 0.90f));
|
||||
|
||||
bool selected = ImGui::Selectable(
|
||||
label,
|
||||
false,
|
||||
ImGuiSelectableFlags_AllowDoubleClick,
|
||||
ImVec2(availWidth, 48.0f)
|
||||
);
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Dummy to extend window bounds properly
|
||||
ImGui::Dummy(ImVec2(0, 0));
|
||||
|
||||
if (selected || ImGui::IsItemClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
OpenProjectPath(rp.path);
|
||||
}
|
||||
const ImVec2 cardSize(availWidth, 72.0f);
|
||||
const ImVec2 cardPos = ImGui::GetCursorScreenPos();
|
||||
ImGui::InvisibleButton("RecentCard", cardSize);
|
||||
bool hovered = ImGui::IsItemHovered();
|
||||
bool clicked = ImGui::IsItemClicked();
|
||||
ImVec2 afterPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
if (ImGui::BeginPopupContextItem("RecentProjectContext"))
|
||||
{
|
||||
@@ -287,6 +313,34 @@ void Engine::renderLauncher() {
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImU32 cardCol = ImGui::GetColorU32(hovered ? ImVec4(0.18f, 0.19f, 0.27f, 1.0f)
|
||||
: ImVec4(0.16f, 0.17f, 0.24f, 1.0f));
|
||||
ImDrawList* list = ImGui::GetWindowDrawList();
|
||||
list->AddRectFilled(cardPos, ImVec2(cardPos.x + cardSize.x, cardPos.y + cardSize.y), cardCol, 14.0f);
|
||||
list->AddRect(cardPos, ImVec2(cardPos.x + cardSize.x, cardPos.y + cardSize.y),
|
||||
ImGui::GetColorU32(cardOutline), 14.0f);
|
||||
|
||||
ImVec2 textPos = ImVec2(cardPos.x + 16.0f, cardPos.y + 14.0f);
|
||||
ImGui::SetCursorScreenPos(textPos);
|
||||
ImGui::TextColored(ImVec4(0.92f, 0.93f, 0.96f, 1.0f), "%s", rp.name.c_str());
|
||||
ImGui::SetCursorScreenPos(ImVec2(textPos.x, textPos.y + 22.0f));
|
||||
ImGui::TextDisabled("%s", rp.path.c_str());
|
||||
|
||||
const float buttonWidth = 88.0f;
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardPos.x + cardSize.x - buttonWidth - 16.0f, cardPos.y + 20.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.24f, 0.28f, 0.40f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.32f, 0.38f, 0.55f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.30f, 0.35f, 0.50f, 1.0f));
|
||||
bool openClicked = ImGui::Button("Open", ImVec2(buttonWidth, 30.0f));
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
if ((clicked && !openClicked) || openClicked)
|
||||
{
|
||||
OpenProjectPath(rp.path);
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(afterPos);
|
||||
|
||||
ImGui::PopID();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
@@ -303,7 +357,7 @@ void Engine::renderLauncher() {
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleVar(5);
|
||||
|
||||
if (projectManager.showNewProjectDialog)
|
||||
renderNewProjectDialog();
|
||||
|
||||
@@ -18,6 +18,83 @@
|
||||
#include <shlobj.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
ImU32 GetHierarchyTypeColor(ObjectType type) {
|
||||
switch (type) {
|
||||
case ObjectType::Camera: return IM_COL32(110, 175, 235, 220);
|
||||
case ObjectType::DirectionalLight:
|
||||
case ObjectType::PointLight:
|
||||
case ObjectType::SpotLight:
|
||||
case ObjectType::AreaLight: return IM_COL32(255, 200, 90, 220);
|
||||
case ObjectType::PostFXNode: return IM_COL32(200, 140, 230, 220);
|
||||
case ObjectType::OBJMesh:
|
||||
case ObjectType::Model: return IM_COL32(120, 200, 150, 220);
|
||||
case ObjectType::Mirror: return IM_COL32(180, 200, 210, 220);
|
||||
case ObjectType::Plane: return IM_COL32(170, 180, 190, 220);
|
||||
case ObjectType::Torus: return IM_COL32(155, 215, 180, 220);
|
||||
default: return IM_COL32(140, 190, 235, 220);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawFileOutlineIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float w = size * 0.78f;
|
||||
float h = size * 0.95f;
|
||||
float offsetX = (size - w) * 0.5f;
|
||||
float offsetY = (size - h) * 0.5f;
|
||||
float corner = w * 0.28f;
|
||||
ImVec2 min(pos.x + offsetX, pos.y + offsetY);
|
||||
ImVec2 max(pos.x + offsetX + w, pos.y + offsetY + h);
|
||||
ImVec2 foldA(max.x - corner, min.y);
|
||||
ImVec2 foldB(max.x, min.y + corner);
|
||||
ImVec2 foldC(max.x - corner, min.y + corner);
|
||||
drawList->AddRect(min, max, color, size * 0.12f, 0, 1.2f);
|
||||
drawList->AddTriangle(foldA, foldB, foldC, color, 1.2f);
|
||||
drawList->AddLine(ImVec2(foldA.x, foldA.y), ImVec2(foldC.x, foldC.y), color, 1.2f);
|
||||
}
|
||||
|
||||
void DrawCubeOutlineIcon(ImDrawList* drawList, ImVec2 pos, float size, ImU32 color) {
|
||||
float inset = size * 0.18f;
|
||||
float backOffset = size * 0.16f;
|
||||
ImVec2 frontMin(pos.x + inset, pos.y + inset + backOffset);
|
||||
ImVec2 frontMax(pos.x + size - inset, pos.y + size - inset + backOffset);
|
||||
ImVec2 backMin(pos.x + inset + backOffset, pos.y + inset);
|
||||
ImVec2 backMax(pos.x + size - inset + backOffset, pos.y + size - inset);
|
||||
drawList->AddRect(frontMin, frontMax, color, 0.0f, 0, 1.2f);
|
||||
drawList->AddRect(backMin, backMax, color, 0.0f, 0, 1.2f);
|
||||
drawList->AddLine(frontMin, backMin, color, 1.2f);
|
||||
drawList->AddLine(ImVec2(frontMax.x, frontMin.y), ImVec2(backMax.x, backMin.y), color, 1.2f);
|
||||
drawList->AddLine(ImVec2(frontMin.x, frontMax.y), ImVec2(backMin.x, backMax.y), color, 1.2f);
|
||||
drawList->AddLine(frontMax, backMax, color, 1.2f);
|
||||
}
|
||||
|
||||
void DrawHierarchyLines(ImDrawList* drawList, const ImVec2& itemMin, const ImVec2& itemMax,
|
||||
const std::vector<bool>& ancestorHasNext, int depth, bool isLast) {
|
||||
if (depth <= 0) {
|
||||
return;
|
||||
}
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec4 base = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled);
|
||||
ImU32 lineColor = ImGui::ColorConvertFloat4ToU32(ImVec4(base.x, base.y, base.z, 0.6f));
|
||||
float indent = style.IndentSpacing;
|
||||
float rowTop = itemMin.y;
|
||||
float rowBottom = itemMax.y;
|
||||
float rowMid = (rowTop + rowBottom) * 0.5f;
|
||||
float baseX = itemMin.x - indent * depth;
|
||||
|
||||
for (int i = 0; i < depth && i < static_cast<int>(ancestorHasNext.size()); ++i) {
|
||||
if (ancestorHasNext[i]) {
|
||||
float x = baseX + indent * (i + 0.5f);
|
||||
drawList->AddLine(ImVec2(x, rowTop), ImVec2(x, rowBottom), lineColor, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
float connectorX = baseX + indent * (depth - 0.5f);
|
||||
float vertEnd = isLast ? rowMid : rowBottom;
|
||||
drawList->AddLine(ImVec2(connectorX, rowTop), ImVec2(connectorX, vertEnd), lineColor, 1.0f);
|
||||
drawList->AddLine(ImVec2(connectorX, rowMid), ImVec2(itemMin.x + 6.0f, rowMid), lineColor, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::renderHierarchyPanel() {
|
||||
ImGui::Begin("Hierarchy", &showHierarchy);
|
||||
|
||||
@@ -96,11 +173,18 @@ void Engine::renderHierarchyPanel() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 2.0f));
|
||||
ImGui::BeginChild("HierarchyList", ImVec2(0, 0), true);
|
||||
|
||||
std::vector<size_t> rootIndices;
|
||||
rootIndices.reserve(sceneObjects.size());
|
||||
for (size_t i = 0; i < sceneObjects.size(); i++) {
|
||||
if (sceneObjects[i].parentId != -1)
|
||||
continue;
|
||||
if (sceneObjects[i].parentId == -1) {
|
||||
rootIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
renderObjectNode(sceneObjects[i], filter);
|
||||
std::vector<bool> ancestorHasNext;
|
||||
for (size_t i = 0; i < rootIndices.size(); ++i) {
|
||||
bool isLastRoot = (i + 1 == rootIndices.size());
|
||||
renderObjectNode(sceneObjects[rootIndices[i]], filter, ancestorHasNext, isLastRoot, 0);
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("HierarchyBackground",
|
||||
@@ -115,6 +199,8 @@ void Engine::renderHierarchyPanel() {
|
||||
if (ImGui::MenuItem("Cube")) addObject(ObjectType::Cube, "Cube");
|
||||
if (ImGui::MenuItem("Sphere")) addObject(ObjectType::Sphere, "Sphere");
|
||||
if (ImGui::MenuItem("Capsule")) addObject(ObjectType::Capsule, "Capsule");
|
||||
if (ImGui::MenuItem("Plane")) addObject(ObjectType::Plane, "Plane");
|
||||
if (ImGui::MenuItem("Torus")) addObject(ObjectType::Torus, "Torus");
|
||||
if (ImGui::MenuItem("Mirror")) addObject(ObjectType::Mirror, "Mirror");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@@ -149,7 +235,8 @@ void Engine::renderHierarchyPanel() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
|
||||
void Engine::renderObjectNode(SceneObject& obj, const std::string& filter,
|
||||
std::vector<bool>& ancestorHasNext, bool isLast, int depth) {
|
||||
std::string nameLower = obj.name;
|
||||
std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
|
||||
|
||||
@@ -164,23 +251,23 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
|
||||
if (isSelected) flags |= ImGuiTreeNodeFlags_Selected;
|
||||
if (!hasChildren) flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
|
||||
const char* icon = "";
|
||||
switch (obj.type) {
|
||||
case ObjectType::Cube: icon = "[#]"; break;
|
||||
case ObjectType::Sphere: icon = "(O)"; break;
|
||||
case ObjectType::Capsule: icon = "[|]"; break;
|
||||
case ObjectType::OBJMesh: icon = "[M]"; break;
|
||||
case ObjectType::Model: icon = "[A]"; break;
|
||||
case ObjectType::Camera: icon = "(C)"; break;
|
||||
case ObjectType::DirectionalLight: icon = "(D)"; break;
|
||||
case ObjectType::PointLight: icon = "(P)"; break;
|
||||
case ObjectType::SpotLight: icon = "(S)"; break;
|
||||
case ObjectType::AreaLight: icon = "(L)"; break;
|
||||
case ObjectType::PostFXNode: icon = "(FX)"; break;
|
||||
case ObjectType::Mirror: icon = "[R]"; break;
|
||||
}
|
||||
std::string label = " " + obj.name;
|
||||
bool nodeOpen = ImGui::TreeNodeEx((void*)(intptr_t)obj.id, flags, "%s", label.c_str());
|
||||
|
||||
bool nodeOpen = ImGui::TreeNodeEx((void*)(intptr_t)obj.id, flags, "%s %s", icon, obj.name.c_str());
|
||||
ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||
ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||
DrawHierarchyLines(ImGui::GetWindowDrawList(), itemMin, itemMax, ancestorHasNext, depth, isLast);
|
||||
|
||||
float lineHeight = itemMax.y - itemMin.y;
|
||||
float iconSize = std::max(8.0f, lineHeight - 6.0f);
|
||||
float labelStart = itemMin.x + ImGui::GetTreeNodeToLabelSpacing();
|
||||
ImVec2 iconPos(labelStart, itemMin.y + (lineHeight - iconSize) * 0.5f);
|
||||
ImU32 iconColor = GetHierarchyTypeColor(obj.type);
|
||||
if (obj.parentId == -1) {
|
||||
DrawCubeOutlineIcon(ImGui::GetWindowDrawList(), iconPos, iconSize, iconColor);
|
||||
} else {
|
||||
DrawFileOutlineIcon(ImGui::GetWindowDrawList(), iconPos, iconSize, iconColor);
|
||||
}
|
||||
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
|
||||
bool additive = ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeyShift;
|
||||
@@ -290,13 +377,26 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) {
|
||||
}
|
||||
|
||||
if (nodeOpen) {
|
||||
std::vector<SceneObject*> visibleChildren;
|
||||
visibleChildren.reserve(obj.childIds.size());
|
||||
for (int childId : obj.childIds) {
|
||||
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
||||
[childId](const SceneObject& o) { return o.id == childId; });
|
||||
if (it != sceneObjects.end()) {
|
||||
renderObjectNode(*it, filter);
|
||||
std::string childLower = it->name;
|
||||
std::transform(childLower.begin(), childLower.end(), childLower.begin(), ::tolower);
|
||||
if (filter.empty() || childLower.find(filter) != std::string::npos) {
|
||||
visibleChildren.push_back(&(*it));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ancestorHasNext.push_back(!isLast);
|
||||
for (size_t i = 0; i < visibleChildren.size(); ++i) {
|
||||
bool childLast = (i + 1 == visibleChildren.size());
|
||||
renderObjectNode(*visibleChildren[i], filter, ancestorHasNext, childLast, depth + 1);
|
||||
}
|
||||
ancestorHasNext.pop_back();
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -348,8 +448,23 @@ void Engine::renderInspectorPanel() {
|
||||
inspectedMaterialValid = false;
|
||||
}
|
||||
|
||||
if (browserHasAudio) {
|
||||
std::string selectedAudio = selectedAudioPath.string();
|
||||
if (selectedAudio != audioPreviewSelectedPath) {
|
||||
audioPreviewSelectedPath = selectedAudio;
|
||||
if (audioPreviewAutoPlay) {
|
||||
audio.playPreview(selectedAudio, 1.0f, audioPreviewLoop);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
audioPreviewSelectedPath.clear();
|
||||
}
|
||||
|
||||
auto drawWaveform = [&](const char* id, const AudioClipPreview* preview, const ImVec2& size, float progressRatio, float* seekRatioOut) {
|
||||
if (!preview || preview->waveform.empty()) {
|
||||
bool hasStereo = preview && preview->channels >= 2
|
||||
&& !preview->waveformLeft.empty()
|
||||
&& !preview->waveformRight.empty();
|
||||
if (!preview || (!hasStereo && preview->waveform.empty())) {
|
||||
ImGui::Dummy(size);
|
||||
return;
|
||||
}
|
||||
@@ -360,14 +475,33 @@ void Engine::renderInspectorPanel() {
|
||||
dl->AddRectFilled(start, end, IM_COL32(30, 35, 45, 180), 4.0f);
|
||||
float midY = (start.y + end.y) * 0.5f;
|
||||
float usableHeight = size.y * 0.45f;
|
||||
size_t count = preview->waveform.size();
|
||||
size_t count = hasStereo
|
||||
? std::min(preview->waveformLeft.size(), preview->waveformRight.size())
|
||||
: preview->waveform.size();
|
||||
float step = count > 1 ? size.x / static_cast<float>(count - 1) : size.x;
|
||||
ImU32 color = IM_COL32(255, 180, 100, 200);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float amp = std::clamp(preview->waveform[i], 0.0f, 1.0f);
|
||||
float x = start.x + step * static_cast<float>(i);
|
||||
float yOff = amp * usableHeight;
|
||||
dl->AddLine(ImVec2(x, midY - yOff), ImVec2(x, midY + yOff), color, 1.2f);
|
||||
if (hasStereo) {
|
||||
ImU32 leftColor = IM_COL32(255, 190, 90, 200);
|
||||
ImU32 rightColor = IM_COL32(100, 200, 255, 200);
|
||||
float topMidY = start.y + size.y * 0.25f;
|
||||
float bottomMidY = start.y + size.y * 0.75f;
|
||||
float stereoHeight = size.y * 0.22f;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float leftAmp = std::clamp(preview->waveformLeft[i], 0.0f, 1.0f);
|
||||
float rightAmp = std::clamp(preview->waveformRight[i], 0.0f, 1.0f);
|
||||
float x = start.x + step * static_cast<float>(i);
|
||||
float leftOff = leftAmp * stereoHeight;
|
||||
float rightOff = rightAmp * stereoHeight;
|
||||
dl->AddLine(ImVec2(x, topMidY - leftOff), ImVec2(x, topMidY + leftOff), leftColor, 1.2f);
|
||||
dl->AddLine(ImVec2(x, bottomMidY - rightOff), ImVec2(x, bottomMidY + rightOff), rightColor, 1.2f);
|
||||
}
|
||||
} else {
|
||||
ImU32 color = IM_COL32(255, 180, 100, 200);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float amp = std::clamp(preview->waveform[i], 0.0f, 1.0f);
|
||||
float x = start.x + step * static_cast<float>(i);
|
||||
float yOff = amp * usableHeight;
|
||||
dl->AddLine(ImVec2(x, midY - yOff), ImVec2(x, midY + yOff), color, 1.2f);
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRatio >= 0.0f && progressRatio <= 1.0f) {
|
||||
@@ -648,7 +782,19 @@ void Engine::renderInspectorPanel() {
|
||||
if (isPlayingPreview) {
|
||||
audio.stopPreview();
|
||||
} else {
|
||||
audio.playPreview(selectedAudioPath.string());
|
||||
audio.playPreview(selectedAudioPath.string(), 1.0f, audioPreviewLoop);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Loop##AudioPreview", &audioPreviewLoop)) {
|
||||
if (isPlayingPreview) {
|
||||
audio.setPreviewLoop(audioPreviewLoop);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Auto Play##AudioPreview", &audioPreviewAutoPlay)) {
|
||||
if (audioPreviewAutoPlay && !selectedAudioPath.empty() && !isPlayingPreview) {
|
||||
audio.playPreview(selectedAudioPath.string(), 1.0f, audioPreviewLoop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,6 +933,8 @@ void Engine::renderInspectorPanel() {
|
||||
case ObjectType::AreaLight: typeLabel = "Area Light"; break;
|
||||
case ObjectType::PostFXNode: typeLabel = "Post FX Node"; break;
|
||||
case ObjectType::Mirror: typeLabel = "Mirror"; break;
|
||||
case ObjectType::Plane: typeLabel = "Plane"; break;
|
||||
case ObjectType::Torus: typeLabel = "Torus"; break;
|
||||
}
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "%s", typeLabel);
|
||||
|
||||
@@ -837,6 +985,7 @@ void Engine::renderInspectorPanel() {
|
||||
ImGui::Text("Position");
|
||||
ImGui::PushItemWidth(-1);
|
||||
if (ImGui::DragFloat3("##Position", &obj.position.x, 0.1f)) {
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
@@ -847,6 +996,7 @@ void Engine::renderInspectorPanel() {
|
||||
ImGui::PushItemWidth(-1);
|
||||
if (ImGui::DragFloat3("##Rotation", &obj.rotation.x, 1.0f, -360.0f, 360.0f)) {
|
||||
obj.rotation = NormalizeEulerDegrees(obj.rotation);
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
@@ -856,6 +1006,7 @@ void Engine::renderInspectorPanel() {
|
||||
ImGui::Text("Scale");
|
||||
ImGui::PushItemWidth(-1);
|
||||
if (ImGui::DragFloat3("##Scale", &obj.scale.x, 0.05f, 0.01f, 100.0f)) {
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
@@ -866,6 +1017,7 @@ void Engine::renderInspectorPanel() {
|
||||
obj.position = glm::vec3(0.0f);
|
||||
obj.rotation = glm::vec3(0.0f);
|
||||
obj.scale = glm::vec3(1.0f);
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
@@ -1110,7 +1262,7 @@ void Engine::renderInspectorPanel() {
|
||||
if (previewPlaying) {
|
||||
audio.stopPreview();
|
||||
} else if (!src.clipPath.empty()) {
|
||||
audio.playPreview(src.clipPath, src.volume);
|
||||
audio.playPreview(src.clipPath, src.volume, src.loop);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@@ -1234,7 +1386,7 @@ void Engine::renderInspectorPanel() {
|
||||
if (ImGui::SliderFloat("Threshold", &obj.postFx.bloomThreshold, 0.0f, 3.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::SliderFloat("Intensity", &obj.postFx.bloomIntensity, 0.0f, 3.0f, "%.2f")) {
|
||||
if (ImGui::SliderFloat("Intensity##Bloom", &obj.postFx.bloomIntensity, 0.0f, 3.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::SliderFloat("Spread", &obj.postFx.bloomRadius, 0.5f, 3.5f, "%.2f")) {
|
||||
@@ -1279,7 +1431,7 @@ void Engine::renderInspectorPanel() {
|
||||
changed = true;
|
||||
}
|
||||
ImGui::BeginDisabled(!obj.postFx.vignetteEnabled);
|
||||
if (ImGui::SliderFloat("Intensity", &obj.postFx.vignetteIntensity, 0.0f, 1.5f, "%.2f")) {
|
||||
if (ImGui::SliderFloat("Intensity##Vignette", &obj.postFx.vignetteIntensity, 0.0f, 1.5f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
if (ImGui::SliderFloat("Smoothness", &obj.postFx.vignetteSmoothness, 0.05f, 1.0f, "%.2f")) {
|
||||
@@ -1912,6 +2064,7 @@ void Engine::renderInspectorPanel() {
|
||||
obj.rigidbody.useGravity = true;
|
||||
obj.rigidbody.isKinematic = false;
|
||||
obj.scale = glm::vec3(obj.playerController.radius * 2.0f, obj.playerController.height, obj.playerController.radius * 2.0f);
|
||||
syncLocalTransform(obj);
|
||||
componentChanged = true;
|
||||
}
|
||||
if (!obj.hasAudioSource && ImGui::MenuItem("Audio Source")) {
|
||||
|
||||
@@ -525,6 +525,9 @@ void Engine::renderMainMenuBar() {
|
||||
if (ImGui::MenuItem("Cube")) addObject(ObjectType::Cube, "Cube");
|
||||
if (ImGui::MenuItem("Sphere")) addObject(ObjectType::Sphere, "Sphere");
|
||||
if (ImGui::MenuItem("Capsule")) addObject(ObjectType::Capsule, "Capsule");
|
||||
if (ImGui::MenuItem("Plane")) addObject(ObjectType::Plane, "Plane");
|
||||
if (ImGui::MenuItem("Torus")) addObject(ObjectType::Torus, "Torus");
|
||||
if (ImGui::MenuItem("Mirror")) addObject(ObjectType::Mirror, "Mirror");
|
||||
if (ImGui::MenuItem("Camera")) addObject(ObjectType::Camera, "Camera");
|
||||
if (ImGui::MenuItem("Directional Light")) addObject(ObjectType::DirectionalLight, "Directional Light");
|
||||
if (ImGui::MenuItem("Point Light")) addObject(ObjectType::PointLight, "Point Light");
|
||||
@@ -1066,6 +1069,14 @@ void Engine::renderViewport() {
|
||||
gizmoBoundsMin = glm::vec3(-0.35f, -0.9f, -0.35f);
|
||||
gizmoBoundsMax = glm::vec3(0.35f, 0.9f, 0.35f);
|
||||
break;
|
||||
case ObjectType::Plane:
|
||||
gizmoBoundsMin = glm::vec3(-0.5f, -0.5f, -0.02f);
|
||||
gizmoBoundsMax = glm::vec3(0.5f, 0.5f, 0.02f);
|
||||
break;
|
||||
case ObjectType::Torus:
|
||||
gizmoBoundsMin = glm::vec3(-0.5f);
|
||||
gizmoBoundsMax = glm::vec3(0.5f);
|
||||
break;
|
||||
case ObjectType::OBJMesh: {
|
||||
const auto* info = g_objLoader.getMeshInfo(selectedObj->meshId);
|
||||
if (info && info->boundsMin.x < info->boundsMax.x) {
|
||||
@@ -1167,12 +1178,33 @@ void Engine::renderViewport() {
|
||||
o.position = t;
|
||||
o.rotation = NormalizeEulerDegrees(glm::degrees(r));
|
||||
o.scale = s;
|
||||
syncLocalTransform(o);
|
||||
};
|
||||
|
||||
if (selectedObjectIds.size() <= 1) {
|
||||
applyDelta(*selectedObj);
|
||||
} else {
|
||||
std::unordered_set<int> selectedSet(selectedObjectIds.begin(), selectedObjectIds.end());
|
||||
auto getParentId = [&](int id) -> int {
|
||||
auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
||||
[id](const SceneObject& o){ return o.id == id; });
|
||||
if (it == sceneObjects.end()) return -1;
|
||||
return it->parentId;
|
||||
};
|
||||
auto hasSelectedAncestor = [&](int id) -> bool {
|
||||
int parentId = getParentId(id);
|
||||
while (parentId != -1) {
|
||||
if (selectedSet.count(parentId)) {
|
||||
return true;
|
||||
}
|
||||
parentId = getParentId(parentId);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (int id : selectedObjectIds) {
|
||||
if (hasSelectedAncestor(id)) {
|
||||
continue;
|
||||
}
|
||||
auto itObj = std::find_if(sceneObjects.begin(), sceneObjects.end(),
|
||||
[id](const SceneObject& o){ return o.id == id; });
|
||||
if (itObj != sceneObjects.end()) {
|
||||
@@ -1597,9 +1629,15 @@ void Engine::renderViewport() {
|
||||
case ObjectType::Capsule:
|
||||
hit = rayAabb(localOrigin, localDir, glm::vec3(-0.35f, -0.9f, -0.35f), glm::vec3(0.35f, 0.9f, 0.35f), hitT);
|
||||
break;
|
||||
case ObjectType::Plane:
|
||||
hit = rayAabb(localOrigin, localDir, glm::vec3(-0.5f, -0.5f, -0.02f), glm::vec3(0.5f, 0.5f, 0.02f), hitT);
|
||||
break;
|
||||
case ObjectType::Mirror:
|
||||
hit = rayAabb(localOrigin, localDir, glm::vec3(-0.5f, -0.5f, -0.02f), glm::vec3(0.5f, 0.5f, 0.02f), hitT);
|
||||
break;
|
||||
case ObjectType::Torus:
|
||||
hit = raySphere(localOrigin, localDir, 0.5f, hitT);
|
||||
break;
|
||||
case ObjectType::OBJMesh: {
|
||||
const auto* info = g_objLoader.getMeshInfo(obj.meshId);
|
||||
if (info && info->boundsMin.x < info->boundsMax.x) {
|
||||
|
||||
241
src/Engine.cpp
241
src/Engine.cpp
@@ -2,6 +2,7 @@
|
||||
#include "ModelLoader.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -153,6 +154,15 @@ glm::vec3 ExtractEulerXYZ(const glm::mat3& m) {
|
||||
// GLM's extractEulerAngleXYZ returns (-T1, -T2, -T3)
|
||||
return glm::vec3(-T1, -T2, -T3);
|
||||
}
|
||||
|
||||
glm::quat QuatFromEulerXYZ(const glm::vec3& deg) {
|
||||
glm::vec3 r = glm::radians(deg);
|
||||
glm::mat4 m(1.0f);
|
||||
m = glm::rotate(m, r.x, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
m = glm::rotate(m, r.y, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
m = glm::rotate(m, r.z, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
return glm::quat_cast(glm::mat3(m));
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot, glm::vec3& scale) {
|
||||
@@ -165,11 +175,27 @@ void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3&
|
||||
if (scale.x != 0.0f) rotMat[0] /= scale.x;
|
||||
if (scale.y != 0.0f) rotMat[1] /= scale.y;
|
||||
if (scale.z != 0.0f) rotMat[2] /= scale.z;
|
||||
// Orthonormalize to reduce shear-induced rotation jitter.
|
||||
rotMat[0] = glm::normalize(rotMat[0]);
|
||||
rotMat[1] = glm::normalize(rotMat[1] - rotMat[0] * glm::dot(rotMat[0], rotMat[1]));
|
||||
rotMat[2] = glm::normalize(glm::cross(rotMat[0], rotMat[1]));
|
||||
|
||||
// Use explicit XYZ extraction so yaw isn't clamped to [-90, 90] like glm::yaw/pitch/roll.
|
||||
rot = ExtractEulerXYZ(rotMat);
|
||||
}
|
||||
|
||||
glm::mat4 Engine::ComposeTransform(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& scale) {
|
||||
glm::mat4 m(1.0f);
|
||||
m = glm::translate(m, position);
|
||||
m *= glm::mat4_cast(rotation);
|
||||
m = glm::scale(m, scale);
|
||||
return m;
|
||||
}
|
||||
|
||||
glm::mat4 Engine::ComposeTransform(const glm::vec3& position, const glm::vec3& rotationDeg, const glm::vec3& scale) {
|
||||
return ComposeTransform(position, QuatFromEulerXYZ(rotationDeg), scale);
|
||||
}
|
||||
|
||||
void Engine::recordState(const char* /*reason*/) {
|
||||
SceneSnapshot snap;
|
||||
snap.objects = sceneObjects;
|
||||
@@ -327,11 +353,15 @@ void Engine::run() {
|
||||
updatePlayerController(deltaTime);
|
||||
}
|
||||
|
||||
updateHierarchyWorldTransforms();
|
||||
|
||||
bool simulatePhysics = physics.isReady() && ((isPlaying && !isPaused) || (!isPlaying && specMode));
|
||||
if (simulatePhysics) {
|
||||
physics.simulate(deltaTime, sceneObjects);
|
||||
}
|
||||
|
||||
updateHierarchyWorldTransforms();
|
||||
|
||||
bool audioShouldPlay = isPlaying || specMode || testMode;
|
||||
Camera listenerCamera = camera;
|
||||
for (const auto& obj : sceneObjects) {
|
||||
@@ -901,6 +931,162 @@ void Engine::updatePlayerController(float delta) {
|
||||
if (!physics.setLinearVelocity(player->id, velocity)) {
|
||||
player->position += velocity * delta;
|
||||
}
|
||||
syncLocalTransform(*player);
|
||||
}
|
||||
|
||||
void Engine::updateLocalFromWorld(SceneObject& obj, const glm::vec3& parentPos, const glm::quat& parentRot, const glm::vec3& parentScale) {
|
||||
auto safeDiv = [](float v, float d) { return (std::abs(d) > 1e-6f) ? (v / d) : 0.0f; };
|
||||
auto unwrapNear = [](float angle, float reference) {
|
||||
float result = angle;
|
||||
while (result - reference > 180.0f) result -= 360.0f;
|
||||
while (reference - result > 180.0f) result += 360.0f;
|
||||
return result;
|
||||
};
|
||||
|
||||
glm::quat invParent = glm::inverse(parentRot);
|
||||
glm::vec3 localPos = invParent * (obj.position - parentPos);
|
||||
localPos.x = safeDiv(localPos.x, parentScale.x);
|
||||
localPos.y = safeDiv(localPos.y, parentScale.y);
|
||||
localPos.z = safeDiv(localPos.z, parentScale.z);
|
||||
|
||||
glm::quat worldRot = QuatFromEulerXYZ(obj.rotation);
|
||||
glm::quat localRot = invParent * worldRot;
|
||||
glm::vec3 localRotDeg = glm::degrees(ExtractEulerXYZ(glm::mat3_cast(localRot)));
|
||||
glm::vec3 refRot = obj.localInitialized ? obj.localRotation : obj.rotation;
|
||||
localRotDeg.x = unwrapNear(localRotDeg.x, refRot.x);
|
||||
localRotDeg.y = unwrapNear(localRotDeg.y, refRot.y);
|
||||
localRotDeg.z = unwrapNear(localRotDeg.z, refRot.z);
|
||||
|
||||
glm::vec3 localScale(1.0f);
|
||||
localScale.x = safeDiv(obj.scale.x, parentScale.x);
|
||||
localScale.y = safeDiv(obj.scale.y, parentScale.y);
|
||||
localScale.z = safeDiv(obj.scale.z, parentScale.z);
|
||||
|
||||
obj.localPosition = localPos;
|
||||
obj.localRotation = localRotDeg;
|
||||
obj.localScale = localScale;
|
||||
obj.localInitialized = true;
|
||||
}
|
||||
|
||||
void Engine::initializeLocalTransformsFromWorld(int sceneVersion) {
|
||||
if (sceneObjects.empty()) return;
|
||||
|
||||
if (sceneVersion >= 10) {
|
||||
for (auto& obj : sceneObjects) {
|
||||
if (!obj.localInitialized) {
|
||||
obj.localPosition = obj.position;
|
||||
obj.localRotation = NormalizeEulerDegrees(obj.rotation);
|
||||
obj.localScale = obj.scale;
|
||||
obj.localInitialized = true;
|
||||
}
|
||||
}
|
||||
updateHierarchyWorldTransforms();
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<int, glm::mat4> worldById;
|
||||
worldById.reserve(sceneObjects.size());
|
||||
for (const auto& obj : sceneObjects) {
|
||||
worldById[obj.id] = ComposeTransform(obj.position, obj.rotation, obj.scale);
|
||||
}
|
||||
|
||||
for (auto& obj : sceneObjects) {
|
||||
if (obj.parentId == -1) {
|
||||
obj.localPosition = obj.position;
|
||||
obj.localRotation = NormalizeEulerDegrees(obj.rotation);
|
||||
obj.localScale = obj.scale;
|
||||
obj.localInitialized = true;
|
||||
continue;
|
||||
}
|
||||
auto itParent = worldById.find(obj.parentId);
|
||||
if (itParent == worldById.end()) {
|
||||
obj.localPosition = obj.position;
|
||||
obj.localRotation = NormalizeEulerDegrees(obj.rotation);
|
||||
obj.localScale = obj.scale;
|
||||
obj.localInitialized = true;
|
||||
continue;
|
||||
}
|
||||
glm::vec3 pPos, pRotDeg, pScale;
|
||||
DecomposeMatrix(itParent->second, pPos, pRotDeg, pScale);
|
||||
updateLocalFromWorld(obj, pPos, QuatFromEulerXYZ(pRotDeg), pScale);
|
||||
}
|
||||
|
||||
updateHierarchyWorldTransforms();
|
||||
}
|
||||
|
||||
void Engine::updateHierarchyWorldTransforms() {
|
||||
if (sceneObjects.empty()) return;
|
||||
|
||||
std::unordered_map<int, size_t> indexById;
|
||||
indexById.reserve(sceneObjects.size());
|
||||
for (size_t i = 0; i < sceneObjects.size(); ++i) {
|
||||
indexById[sceneObjects[i].id] = i;
|
||||
}
|
||||
|
||||
auto unwrapNear = [](float angle, float reference) {
|
||||
float result = angle;
|
||||
while (result - reference > 180.0f) result -= 360.0f;
|
||||
while (reference - result > 180.0f) result += 360.0f;
|
||||
return result;
|
||||
};
|
||||
|
||||
std::unordered_set<int> visiting;
|
||||
std::unordered_set<int> visited;
|
||||
visiting.reserve(sceneObjects.size());
|
||||
visited.reserve(sceneObjects.size());
|
||||
|
||||
std::function<void(int, const glm::vec3&, const glm::quat&, const glm::vec3&)> processNode =
|
||||
[&](int id, const glm::vec3& parentPos, const glm::quat& parentRot, const glm::vec3& parentScale) {
|
||||
if (visited.count(id)) return;
|
||||
if (visiting.count(id)) return;
|
||||
auto itIndex = indexById.find(id);
|
||||
if (itIndex == indexById.end()) return;
|
||||
|
||||
visiting.insert(id);
|
||||
SceneObject& obj = sceneObjects[itIndex->second];
|
||||
if (!obj.localInitialized) {
|
||||
obj.localPosition = obj.position;
|
||||
obj.localRotation = NormalizeEulerDegrees(obj.rotation);
|
||||
obj.localScale = obj.scale;
|
||||
obj.localInitialized = true;
|
||||
}
|
||||
|
||||
bool useWorldAuthoritative = obj.hasRigidbody && obj.rigidbody.enabled && !obj.rigidbody.isKinematic;
|
||||
glm::vec3 worldPos = obj.position;
|
||||
glm::quat worldRot = QuatFromEulerXYZ(obj.rotation);
|
||||
glm::vec3 worldScale = obj.scale;
|
||||
if (useWorldAuthoritative) {
|
||||
updateLocalFromWorld(obj, parentPos, parentRot, parentScale);
|
||||
worldPos = obj.position;
|
||||
worldRot = QuatFromEulerXYZ(obj.rotation);
|
||||
worldScale = obj.scale;
|
||||
} else {
|
||||
glm::quat localRot = QuatFromEulerXYZ(obj.localRotation);
|
||||
worldRot = parentRot * localRot;
|
||||
worldScale = parentScale * obj.localScale;
|
||||
worldPos = parentPos + parentRot * (parentScale * obj.localPosition);
|
||||
glm::vec3 worldRotDeg = glm::degrees(ExtractEulerXYZ(glm::mat3_cast(worldRot)));
|
||||
worldRotDeg.x = unwrapNear(worldRotDeg.x, obj.rotation.x);
|
||||
worldRotDeg.y = unwrapNear(worldRotDeg.y, obj.rotation.y);
|
||||
worldRotDeg.z = unwrapNear(worldRotDeg.z, obj.rotation.z);
|
||||
obj.position = worldPos;
|
||||
obj.rotation = worldRotDeg;
|
||||
obj.scale = worldScale;
|
||||
}
|
||||
|
||||
for (int childId : obj.childIds) {
|
||||
processNode(childId, worldPos, worldRot, worldScale);
|
||||
}
|
||||
|
||||
visiting.erase(id);
|
||||
visited.insert(id);
|
||||
};
|
||||
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (obj.parentId == -1 || indexById.find(obj.parentId) == indexById.end()) {
|
||||
processNode(obj.id, glm::vec3(0.0f), glm::quat(1.0f, 0.0f, 0.0f, 0.0f), glm::vec3(1.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
void Engine::OpenProjectPath(const std::string& path) {
|
||||
try {
|
||||
@@ -999,7 +1185,9 @@ void Engine::loadRecentScenes() {
|
||||
|
||||
fs::path scenePath = projectManager.currentProject.getSceneFilePath(projectManager.currentProject.currentSceneName);
|
||||
if (fs::exists(scenePath)) {
|
||||
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
|
||||
int sceneVersion = 9;
|
||||
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId, sceneVersion)) {
|
||||
initializeLocalTransformsFromWorld(sceneVersion);
|
||||
addConsoleMessage("Loaded scene: " + projectManager.currentProject.currentSceneName, ConsoleMessageType::Success);
|
||||
} else {
|
||||
addConsoleMessage("Warning: Failed to load scene, starting fresh", ConsoleMessageType::Warning);
|
||||
@@ -1037,7 +1225,9 @@ void Engine::loadScene(const std::string& sceneName) {
|
||||
}
|
||||
|
||||
fs::path scenePath = projectManager.currentProject.getSceneFilePath(sceneName);
|
||||
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId)) {
|
||||
int sceneVersion = 9;
|
||||
if (SceneSerializer::loadScene(scenePath, sceneObjects, nextObjectId, sceneVersion)) {
|
||||
initializeLocalTransformsFromWorld(sceneVersion);
|
||||
undoStack.clear();
|
||||
redoStack.clear();
|
||||
projectManager.currentProject.currentSceneName = sceneName;
|
||||
@@ -1112,7 +1302,13 @@ void Engine::addObject(ObjectType type, const std::string& baseName) {
|
||||
sceneObjects.back().material.textureMix = 1.0f;
|
||||
sceneObjects.back().material.color = glm::vec3(1.0f);
|
||||
sceneObjects.back().scale = glm::vec3(2.0f, 2.0f, 0.05f);
|
||||
} else if (type == ObjectType::Plane) {
|
||||
sceneObjects.back().scale = glm::vec3(2.0f, 2.0f, 0.05f);
|
||||
}
|
||||
sceneObjects.back().localPosition = sceneObjects.back().position;
|
||||
sceneObjects.back().localRotation = NormalizeEulerDegrees(sceneObjects.back().rotation);
|
||||
sceneObjects.back().localScale = sceneObjects.back().scale;
|
||||
sceneObjects.back().localInitialized = true;
|
||||
setPrimarySelection(id);
|
||||
if (projectManager.currentProject.isLoaded) {
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
@@ -1150,6 +1346,10 @@ void Engine::duplicateSelected() {
|
||||
newObj.collider = it->collider;
|
||||
newObj.hasPlayerController = it->hasPlayerController;
|
||||
newObj.playerController = it->playerController;
|
||||
newObj.localPosition = newObj.position;
|
||||
newObj.localRotation = NormalizeEulerDegrees(newObj.rotation);
|
||||
newObj.localScale = newObj.scale;
|
||||
newObj.localInitialized = true;
|
||||
newObj.hasAudioSource = it->hasAudioSource;
|
||||
newObj.audioSource = it->audioSource;
|
||||
|
||||
@@ -1202,6 +1402,19 @@ void Engine::setParent(int childId, int parentId) {
|
||||
newParentIt->childIds.push_back(childId);
|
||||
}
|
||||
}
|
||||
{
|
||||
glm::vec3 parentPos(0.0f);
|
||||
glm::quat parentRot(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 parentScale(1.0f);
|
||||
if (parentId != -1) {
|
||||
if (SceneObject* parent = findObjectById(parentId)) {
|
||||
parentPos = parent->position;
|
||||
parentRot = QuatFromEulerXYZ(parent->rotation);
|
||||
parentScale = parent->scale;
|
||||
}
|
||||
}
|
||||
updateLocalFromWorld(*childIt, parentPos, parentRot, parentScale);
|
||||
}
|
||||
|
||||
if (projectManager.currentProject.isLoaded) {
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
@@ -1309,6 +1522,30 @@ bool Engine::addRigidbodyAngularImpulseFromScript(int id, const glm::vec3& impul
|
||||
return physics.addAngularImpulse(id, impulse);
|
||||
}
|
||||
|
||||
bool Engine::setRigidbodyYawFromScript(int id, float yawDegrees) {
|
||||
return physics.setActorYaw(id, yawDegrees);
|
||||
}
|
||||
|
||||
bool Engine::raycastClosestFromScript(const glm::vec3& origin, const glm::vec3& dir, float distance,
|
||||
int ignoreId, glm::vec3* hitPos, glm::vec3* hitNormal,
|
||||
float* hitDistance) const {
|
||||
return physics.raycastClosest(origin, dir, distance, ignoreId, hitPos, hitNormal, hitDistance);
|
||||
}
|
||||
|
||||
void Engine::syncLocalTransform(SceneObject& obj) {
|
||||
glm::vec3 parentPos(0.0f);
|
||||
glm::quat parentRot(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 parentScale(1.0f);
|
||||
if (obj.parentId != -1) {
|
||||
if (SceneObject* parent = findObjectById(obj.parentId)) {
|
||||
parentPos = parent->position;
|
||||
parentRot = QuatFromEulerXYZ(parent->rotation);
|
||||
parentScale = parent->scale;
|
||||
}
|
||||
}
|
||||
updateLocalFromWorld(obj, parentPos, parentRot, parentScale);
|
||||
}
|
||||
|
||||
bool Engine::playAudioFromScript(int id) {
|
||||
SceneObject* obj = findObjectById(id);
|
||||
if (!obj || !obj->hasAudioSource) return false;
|
||||
|
||||
17
src/Engine.h
17
src/Engine.h
@@ -102,6 +102,9 @@ private:
|
||||
bool hierarchyShowTexturePreview = false;
|
||||
bool hierarchyPreviewNearest = false;
|
||||
std::unordered_map<std::string, bool> texturePreviewFilterOverrides;
|
||||
bool audioPreviewLoop = false;
|
||||
bool audioPreviewAutoPlay = false;
|
||||
std::string audioPreviewSelectedPath;
|
||||
bool isPlaying = false;
|
||||
bool isPaused = false;
|
||||
bool showViewOutput = true;
|
||||
@@ -136,13 +139,17 @@ private:
|
||||
bool specMode = false;
|
||||
bool testMode = false;
|
||||
bool collisionWireframe = false;
|
||||
|
||||
// Private methods
|
||||
SceneObject* getSelectedObject();
|
||||
glm::vec3 getSelectionCenterWorld(bool worldSpace) const;
|
||||
void setPrimarySelection(int id, bool additive = false);
|
||||
void clearSelection();
|
||||
static void DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot, glm::vec3& scale);
|
||||
static glm::mat4 ComposeTransform(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& scale);
|
||||
static glm::mat4 ComposeTransform(const glm::vec3& position, const glm::vec3& rotationDeg, const glm::vec3& scale);
|
||||
void updateHierarchyWorldTransforms();
|
||||
void updateLocalFromWorld(SceneObject& obj, const glm::vec3& parentPos, const glm::quat& parentRot, const glm::vec3& parentScale);
|
||||
void initializeLocalTransformsFromWorld(int sceneVersion);
|
||||
|
||||
void importOBJToScene(const std::string& filepath, const std::string& objectName);
|
||||
void importModelToScene(const std::string& filepath, const std::string& objectName); // Assimp import
|
||||
@@ -161,7 +168,8 @@ private:
|
||||
void renderEnvironmentWindow();
|
||||
void renderCameraWindow();
|
||||
void renderHierarchyPanel();
|
||||
void renderObjectNode(SceneObject& obj, const std::string& filter);
|
||||
void renderObjectNode(SceneObject& obj, const std::string& filter,
|
||||
std::vector<bool>& ancestorHasNext, bool isLast, int depth);
|
||||
void renderFileBrowserPanel();
|
||||
void renderMeshBuilderPanel();
|
||||
void renderInspectorPanel();
|
||||
@@ -246,10 +254,15 @@ public:
|
||||
bool addRigidbodyImpulseFromScript(int id, const glm::vec3& impulse);
|
||||
bool addRigidbodyTorqueFromScript(int id, const glm::vec3& torque);
|
||||
bool addRigidbodyAngularImpulseFromScript(int id, const glm::vec3& impulse);
|
||||
bool setRigidbodyYawFromScript(int id, float yawDegrees);
|
||||
bool raycastClosestFromScript(const glm::vec3& origin, const glm::vec3& dir, float distance,
|
||||
int ignoreId, glm::vec3* hitPos, glm::vec3* hitNormal,
|
||||
float* hitDistance) const;
|
||||
// Audio control exposed to scripts
|
||||
bool playAudioFromScript(int id);
|
||||
bool stopAudioFromScript(int id);
|
||||
bool setAudioLoopFromScript(int id, bool loop);
|
||||
bool setAudioVolumeFromScript(int id, float volume);
|
||||
bool setAudioClipFromScript(int id, const std::string& path);
|
||||
void syncLocalTransform(SceneObject& obj);
|
||||
};
|
||||
|
||||
@@ -203,6 +203,20 @@ bool PhysicsSystem::attachPrimitiveShape(PxRigidActor* actor, const SceneObject&
|
||||
tuneShape(shape, std::min(radius * 2.0f, halfHeight * 2.0f), isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Plane: {
|
||||
glm::vec3 halfExtents = glm::max(obj.scale * 0.5f, glm::vec3(0.01f));
|
||||
halfExtents.z = std::max(halfExtents.z, 0.01f);
|
||||
shape = mPhysics->createShape(PxBoxGeometry(ToPxVec3(halfExtents)), *mDefaultMaterial, true);
|
||||
tuneShape(shape, std::min({halfExtents.x, halfExtents.y, halfExtents.z}) * 2.0f, isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Torus: {
|
||||
float radius = std::max({obj.scale.x, obj.scale.y, obj.scale.z}) * 0.5f;
|
||||
radius = std::max(radius, 0.01f);
|
||||
shape = mPhysics->createShape(PxSphereGeometry(radius), *mDefaultMaterial, true);
|
||||
tuneShape(shape, radius * 2.0f, isDynamic);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
|
||||
if (!file.is_open()) return false;
|
||||
|
||||
file << "# Scene File\n";
|
||||
file << "version=9\n";
|
||||
file << "version=10\n";
|
||||
file << "nextId=" << nextId << "\n";
|
||||
file << "objectCount=" << objects.size() << "\n";
|
||||
file << "\n";
|
||||
@@ -272,9 +272,9 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
|
||||
file << "layer=" << obj.layer << "\n";
|
||||
file << "tag=" << obj.tag << "\n";
|
||||
file << "parentId=" << obj.parentId << "\n";
|
||||
file << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n";
|
||||
file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.z << "\n";
|
||||
file << "scale=" << obj.scale.x << "," << obj.scale.y << "," << obj.scale.z << "\n";
|
||||
file << "position=" << obj.localPosition.x << "," << obj.localPosition.y << "," << obj.localPosition.z << "\n";
|
||||
file << "rotation=" << obj.localRotation.x << "," << obj.localRotation.y << "," << obj.localRotation.z << "\n";
|
||||
file << "scale=" << obj.localScale.x << "," << obj.localScale.y << "," << obj.localScale.z << "\n";
|
||||
file << "hasRigidbody=" << (obj.hasRigidbody ? 1 : 0) << "\n";
|
||||
if (obj.hasRigidbody) {
|
||||
file << "rbEnabled=" << (obj.rigidbody.enabled ? 1 : 0) << "\n";
|
||||
@@ -409,7 +409,8 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
|
||||
|
||||
bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
std::vector<SceneObject>& objects,
|
||||
int& nextId) {
|
||||
int& nextId,
|
||||
int& outVersion) {
|
||||
try {
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) return false;
|
||||
@@ -417,6 +418,7 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
objects.clear();
|
||||
std::string line;
|
||||
SceneObject* currentObj = nullptr;
|
||||
int sceneVersion = 9;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
size_t first = line.find_first_not_of(" \t\r\n");
|
||||
@@ -445,7 +447,9 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
std::string key = line.substr(0, eqPos);
|
||||
std::string value = line.substr(eqPos + 1);
|
||||
|
||||
if (key == "nextId") {
|
||||
if (key == "version") {
|
||||
sceneVersion = std::stoi(value);
|
||||
} else if (key == "nextId") {
|
||||
nextId = std::stoi(value);
|
||||
} else if (currentObj) {
|
||||
if (key == "id") {
|
||||
@@ -474,17 +478,23 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
¤tObj->position.x,
|
||||
¤tObj->position.y,
|
||||
¤tObj->position.z);
|
||||
currentObj->localPosition = currentObj->position;
|
||||
currentObj->localInitialized = true;
|
||||
} else if (key == "rotation") {
|
||||
sscanf(value.c_str(), "%f,%f,%f",
|
||||
¤tObj->rotation.x,
|
||||
¤tObj->rotation.y,
|
||||
¤tObj->rotation.z);
|
||||
currentObj->rotation = NormalizeEulerDegrees(currentObj->rotation);
|
||||
currentObj->localRotation = currentObj->rotation;
|
||||
currentObj->localInitialized = true;
|
||||
} else if (key == "scale") {
|
||||
sscanf(value.c_str(), "%f,%f,%f",
|
||||
¤tObj->scale.x,
|
||||
¤tObj->scale.y,
|
||||
¤tObj->scale.z);
|
||||
currentObj->localScale = currentObj->scale;
|
||||
currentObj->localInitialized = true;
|
||||
} else if (key == "hasRigidbody") {
|
||||
currentObj->hasRigidbody = std::stoi(value) != 0;
|
||||
} else if (key == "rbEnabled") {
|
||||
@@ -752,6 +762,7 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
}
|
||||
|
||||
file.close();
|
||||
outVersion = sceneVersion;
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to load scene: " << e.what() << std::endl;
|
||||
|
||||
@@ -59,5 +59,6 @@ public:
|
||||
|
||||
static bool loadScene(const fs::path& filePath,
|
||||
std::vector<SceneObject>& objects,
|
||||
int& nextId);
|
||||
int& nextId,
|
||||
int& outVersion);
|
||||
};
|
||||
|
||||
@@ -232,6 +232,58 @@ std::vector<float> generateCapsule(int segments, int rings) {
|
||||
return triangulated;
|
||||
}
|
||||
|
||||
std::vector<float> generateTorus(int segments, int sides) {
|
||||
std::vector<float> vertices;
|
||||
float majorRadius = 0.35f;
|
||||
float minorRadius = 0.15f;
|
||||
|
||||
for (int seg = 0; seg <= segments; ++seg) {
|
||||
float u = seg * 2.0f * PI / segments;
|
||||
float cosU = cos(u);
|
||||
float sinU = sin(u);
|
||||
|
||||
for (int side = 0; side <= sides; ++side) {
|
||||
float v = side * 2.0f * PI / sides;
|
||||
float cosV = cos(v);
|
||||
float sinV = sin(v);
|
||||
|
||||
float x = (majorRadius + minorRadius * cosV) * cosU;
|
||||
float y = minorRadius * sinV;
|
||||
float z = (majorRadius + minorRadius * cosV) * sinU;
|
||||
|
||||
glm::vec3 normal = glm::normalize(glm::vec3(cosU * cosV, sinV, sinU * cosV));
|
||||
|
||||
vertices.push_back(x);
|
||||
vertices.push_back(y);
|
||||
vertices.push_back(z);
|
||||
vertices.push_back(normal.x);
|
||||
vertices.push_back(normal.y);
|
||||
vertices.push_back(normal.z);
|
||||
vertices.push_back((float)seg / segments);
|
||||
vertices.push_back((float)side / sides);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> triangulated;
|
||||
int stride = sides + 1;
|
||||
for (int seg = 0; seg < segments; ++seg) {
|
||||
for (int side = 0; side < sides; ++side) {
|
||||
int current = seg * stride + side;
|
||||
int next = current + stride;
|
||||
|
||||
for (int i = 0; i < 8; ++i) triangulated.push_back(vertices[current * 8 + i]);
|
||||
for (int i = 0; i < 8; ++i) triangulated.push_back(vertices[next * 8 + i]);
|
||||
for (int i = 0; i < 8; ++i) triangulated.push_back(vertices[(current + 1) * 8 + i]);
|
||||
|
||||
for (int i = 0; i < 8; ++i) triangulated.push_back(vertices[(current + 1) * 8 + i]);
|
||||
for (int i = 0; i < 8; ++i) triangulated.push_back(vertices[next * 8 + i]);
|
||||
for (int i = 0; i < 8; ++i) triangulated.push_back(vertices[(next + 1) * 8 + i]);
|
||||
}
|
||||
}
|
||||
|
||||
return triangulated;
|
||||
}
|
||||
|
||||
// Mesh implementation
|
||||
Mesh::Mesh(const float* vertexData, size_t dataSizeBytes) {
|
||||
vertexCount = dataSizeBytes / (8 * sizeof(float));
|
||||
@@ -435,6 +487,7 @@ Renderer::~Renderer() {
|
||||
delete sphereMesh;
|
||||
delete capsuleMesh;
|
||||
delete planeMesh;
|
||||
delete torusMesh;
|
||||
delete skybox;
|
||||
delete postShader;
|
||||
delete brightShader;
|
||||
@@ -458,7 +511,6 @@ Renderer::~Renderer() {
|
||||
releaseRenderTarget(entry.second);
|
||||
}
|
||||
mirrorTargets.clear();
|
||||
mirrorSmooth.clear();
|
||||
if (framebuffer) glDeleteFramebuffers(1, &framebuffer);
|
||||
if (viewportTexture) glDeleteTextures(1, &viewportTexture);
|
||||
if (rbo) glDeleteRenderbuffers(1, &rbo);
|
||||
@@ -555,6 +607,8 @@ void Renderer::initialize() {
|
||||
auto capsuleVerts = generateCapsule();
|
||||
capsuleMesh = new Mesh(capsuleVerts.data(), capsuleVerts.size() * sizeof(float));
|
||||
planeMesh = new Mesh(mirrorPlaneVertices, sizeof(mirrorPlaneVertices));
|
||||
auto torusVerts = generateTorus();
|
||||
torusMesh = new Mesh(torusVerts.data(), torusVerts.size() * sizeof(float));
|
||||
|
||||
skybox = new Skybox();
|
||||
|
||||
@@ -732,14 +786,6 @@ void Renderer::updateMirrorTargets(const Camera& camera, const std::vector<Scene
|
||||
}
|
||||
return glm::normalize(n);
|
||||
};
|
||||
auto planeUp = [](const SceneObject& obj) {
|
||||
glm::quat q = glm::quat(glm::radians(obj.rotation));
|
||||
glm::vec3 u = q * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
if (!std::isfinite(u.x) || glm::length(u) < 1e-3f) {
|
||||
u = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
return glm::normalize(u);
|
||||
};
|
||||
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (!obj.enabled || obj.type != ObjectType::Mirror) continue;
|
||||
@@ -756,30 +802,20 @@ void Renderer::updateMirrorTargets(const Camera& camera, const std::vector<Scene
|
||||
|
||||
glm::vec3 n = planeNormal(obj);
|
||||
glm::vec3 planePoint = obj.position;
|
||||
glm::vec3 upVec = planeUp(obj);
|
||||
glm::vec3 tangent = glm::normalize(glm::cross(upVec, n));
|
||||
if (!std::isfinite(tangent.x) || glm::length(tangent) < 1e-3f) {
|
||||
tangent = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
glm::vec3 bitangent = glm::cross(n, tangent);
|
||||
|
||||
auto reflectPoint = [&](const glm::vec3& p) {
|
||||
float dist = glm::dot(p - planePoint, n);
|
||||
return p - 2.0f * dist * n;
|
||||
};
|
||||
auto reflectDir = [&](const glm::vec3& v) {
|
||||
float dist = glm::dot(v, n);
|
||||
return v - 2.0f * dist * n;
|
||||
};
|
||||
|
||||
Camera mirrorCam = camera;
|
||||
glm::vec3 relToPlane = camera.position - planePoint;
|
||||
float alongT = glm::dot(relToPlane, tangent);
|
||||
float alongB = glm::dot(relToPlane, bitangent);
|
||||
MirrorSmoothing& sm = mirrorSmooth[obj.id];
|
||||
if (!sm.initialized) {
|
||||
sm.planar = glm::vec2(alongT, alongB);
|
||||
sm.initialized = true;
|
||||
} else {
|
||||
float lerp = 0.2f; // slow the planar tracking slightly
|
||||
sm.planar = glm::mix(sm.planar, glm::vec2(alongT, alongB), lerp);
|
||||
}
|
||||
|
||||
float fixedDepth = 0.05f; // keep a small offset off the plane; ignore viewer local Z movement
|
||||
mirrorCam.position = planePoint + tangent * sm.planar.x + bitangent * sm.planar.y + n * fixedDepth;
|
||||
mirrorCam.front = n; // Look straight out from the mirror face
|
||||
mirrorCam.up = upVec;
|
||||
mirrorCam.position = reflectPoint(camera.position);
|
||||
mirrorCam.front = glm::normalize(reflectDir(camera.front));
|
||||
mirrorCam.up = glm::normalize(reflectDir(camera.up));
|
||||
if (!std::isfinite(mirrorCam.front.x) || glm::length(mirrorCam.front) < 1e-3f) {
|
||||
mirrorCam.front = glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
}
|
||||
@@ -795,7 +831,6 @@ void Renderer::updateMirrorTargets(const Camera& camera, const std::vector<Scene
|
||||
for (auto it = mirrorTargets.begin(); it != mirrorTargets.end(); ) {
|
||||
if (active.find(it->first) == active.end()) {
|
||||
releaseRenderTarget(it->second);
|
||||
mirrorSmooth.erase(it->first);
|
||||
it = mirrorTargets.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
@@ -972,9 +1007,15 @@ void Renderer::renderObject(const SceneObject& obj) {
|
||||
case ObjectType::Capsule:
|
||||
capsuleMesh->draw();
|
||||
break;
|
||||
case ObjectType::Plane:
|
||||
if (planeMesh) planeMesh->draw();
|
||||
break;
|
||||
case ObjectType::Mirror:
|
||||
if (planeMesh) planeMesh->draw();
|
||||
break;
|
||||
case ObjectType::Torus:
|
||||
if (torusMesh) torusMesh->draw();
|
||||
break;
|
||||
case ObjectType::OBJMesh:
|
||||
if (obj.meshId >= 0) {
|
||||
Mesh* objMesh = g_objLoader.getMesh(obj.meshId);
|
||||
@@ -1192,7 +1233,9 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
if (obj.type == ObjectType::Cube) meshToDraw = cubeMesh;
|
||||
else if (obj.type == ObjectType::Sphere) meshToDraw = sphereMesh;
|
||||
else if (obj.type == ObjectType::Capsule) meshToDraw = capsuleMesh;
|
||||
else if (obj.type == ObjectType::Plane) meshToDraw = planeMesh;
|
||||
else if (obj.type == ObjectType::Mirror) meshToDraw = planeMesh;
|
||||
else if (obj.type == ObjectType::Torus) meshToDraw = torusMesh;
|
||||
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
|
||||
meshToDraw = g_objLoader.getMesh(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId != -1) {
|
||||
@@ -1259,6 +1302,8 @@ unsigned int Renderer::applyPostProcessing(const std::vector<SceneObject>& scene
|
||||
if (allowHistory) {
|
||||
ensureRenderTarget(historyTarget, width, height);
|
||||
}
|
||||
ensureRenderTarget(bloomTargetA, width, height);
|
||||
ensureRenderTarget(bloomTargetB, width, height);
|
||||
if (target.fbo == 0 || target.texture == 0) {
|
||||
if (allowHistory) {
|
||||
displayTexture = sourceTexture;
|
||||
|
||||
@@ -13,6 +13,7 @@ extern float vertices[];
|
||||
// Primitive generation functions
|
||||
std::vector<float> generateSphere(int segments = 32, int rings = 16);
|
||||
std::vector<float> generateCapsule(int segments = 16, int rings = 8);
|
||||
std::vector<float> generateTorus(int segments = 32, int sides = 16);
|
||||
|
||||
class Mesh {
|
||||
private:
|
||||
@@ -73,11 +74,6 @@ private:
|
||||
RenderTarget historyTarget;
|
||||
RenderTarget bloomTargetA;
|
||||
RenderTarget bloomTargetB;
|
||||
struct MirrorSmoothing {
|
||||
glm::vec2 planar = glm::vec2(0.0f);
|
||||
bool initialized = false;
|
||||
};
|
||||
std::unordered_map<int, MirrorSmoothing> mirrorSmooth;
|
||||
Shader* shader = nullptr;
|
||||
Shader* defaultShader = nullptr;
|
||||
Shader* postShader = nullptr;
|
||||
@@ -108,6 +104,7 @@ private:
|
||||
Mesh* sphereMesh = nullptr;
|
||||
Mesh* capsuleMesh = nullptr;
|
||||
Mesh* planeMesh = nullptr;
|
||||
Mesh* torusMesh = nullptr;
|
||||
Skybox* skybox = nullptr;
|
||||
unsigned int quadVAO = 0;
|
||||
unsigned int quadVBO = 0;
|
||||
|
||||
@@ -14,7 +14,9 @@ enum class ObjectType {
|
||||
AreaLight,
|
||||
Camera,
|
||||
PostFXNode,
|
||||
Mirror
|
||||
Mirror,
|
||||
Plane,
|
||||
Torus
|
||||
};
|
||||
|
||||
struct MaterialProperties {
|
||||
@@ -161,6 +163,10 @@ public:
|
||||
glm::vec3 position;
|
||||
glm::vec3 rotation;
|
||||
glm::vec3 scale;
|
||||
glm::vec3 localPosition;
|
||||
glm::vec3 localRotation;
|
||||
glm::vec3 localScale;
|
||||
bool localInitialized = false;
|
||||
int id;
|
||||
int parentId = -1;
|
||||
std::vector<int> childIds;
|
||||
@@ -190,5 +196,14 @@ public:
|
||||
AudioSourceComponent audioSource;
|
||||
|
||||
SceneObject(const std::string& name, ObjectType type, int id)
|
||||
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(id) {}
|
||||
: name(name),
|
||||
type(type),
|
||||
position(0.0f),
|
||||
rotation(0.0f),
|
||||
scale(1.0f),
|
||||
localPosition(0.0f),
|
||||
localRotation(0.0f),
|
||||
localScale(1.0f),
|
||||
localInitialized(true),
|
||||
id(id) {}
|
||||
};
|
||||
|
||||
@@ -87,6 +87,12 @@ bool ScriptContext::IsInLayer(int layer) const {
|
||||
void ScriptContext::SetPosition(const glm::vec3& pos) {
|
||||
if (object) {
|
||||
object->position = pos;
|
||||
if (engine) {
|
||||
engine->syncLocalTransform(*object);
|
||||
} else {
|
||||
object->localPosition = object->position;
|
||||
object->localInitialized = true;
|
||||
}
|
||||
MarkDirty();
|
||||
}
|
||||
}
|
||||
@@ -94,6 +100,12 @@ void ScriptContext::SetPosition(const glm::vec3& pos) {
|
||||
void ScriptContext::SetRotation(const glm::vec3& rot) {
|
||||
if (object) {
|
||||
object->rotation = NormalizeEulerDegrees(rot);
|
||||
if (engine) {
|
||||
engine->syncLocalTransform(*object);
|
||||
} else {
|
||||
object->localRotation = object->rotation;
|
||||
object->localInitialized = true;
|
||||
}
|
||||
MarkDirty();
|
||||
if (engine && HasRigidbody()) {
|
||||
engine->teleportPhysicsActorFromScript(object->id, object->position, object->rotation);
|
||||
@@ -104,6 +116,12 @@ void ScriptContext::SetRotation(const glm::vec3& rot) {
|
||||
void ScriptContext::SetScale(const glm::vec3& scl) {
|
||||
if (object) {
|
||||
object->scale = scl;
|
||||
if (engine) {
|
||||
engine->syncLocalTransform(*object);
|
||||
} else {
|
||||
object->localScale = object->scale;
|
||||
object->localInitialized = true;
|
||||
}
|
||||
MarkDirty();
|
||||
}
|
||||
}
|
||||
@@ -152,9 +170,22 @@ bool ScriptContext::AddRigidbodyAngularImpulse(const glm::vec3& impulse) {
|
||||
return engine->addRigidbodyAngularImpulseFromScript(object->id, impulse);
|
||||
}
|
||||
|
||||
bool ScriptContext::SetRigidbodyYaw(float yawDegrees) {
|
||||
if (!engine || !object || !HasRigidbody()) return false;
|
||||
return engine->setRigidbodyYawFromScript(object->id, yawDegrees);
|
||||
}
|
||||
|
||||
bool ScriptContext::RaycastClosest(const glm::vec3& origin, const glm::vec3& dir, float distance,
|
||||
glm::vec3* hitPos, glm::vec3* hitNormal, float* hitDistance) const {
|
||||
if (!engine) return false;
|
||||
int ignoreId = object ? object->id : -1;
|
||||
return engine->raycastClosestFromScript(origin, dir, distance, ignoreId, hitPos, hitNormal, hitDistance);
|
||||
}
|
||||
|
||||
bool ScriptContext::SetRigidbodyRotation(const glm::vec3& rotDeg) {
|
||||
if (!engine || !object || !HasRigidbody()) return false;
|
||||
object->rotation = NormalizeEulerDegrees(rotDeg);
|
||||
engine->syncLocalTransform(*object);
|
||||
MarkDirty();
|
||||
return engine->teleportPhysicsActorFromScript(object->id, object->position, object->rotation);
|
||||
}
|
||||
@@ -163,6 +194,7 @@ bool ScriptContext::TeleportRigidbody(const glm::vec3& pos, const glm::vec3& rot
|
||||
if (!engine || !object) return false;
|
||||
object->position = pos;
|
||||
object->rotation = NormalizeEulerDegrees(rotDeg);
|
||||
engine->syncLocalTransform(*object);
|
||||
MarkDirty();
|
||||
return engine->teleportPhysicsActorFromScript(object->id, pos, object->rotation);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ struct ScriptContext {
|
||||
bool AddRigidbodyImpulse(const glm::vec3& impulse);
|
||||
bool AddRigidbodyTorque(const glm::vec3& torque);
|
||||
bool AddRigidbodyAngularImpulse(const glm::vec3& impulse);
|
||||
bool SetRigidbodyYaw(float yawDegrees);
|
||||
bool RaycastClosest(const glm::vec3& origin, const glm::vec3& dir, float distance,
|
||||
glm::vec3* hitPos = nullptr, glm::vec3* hitNormal = nullptr,
|
||||
float* hitDistance = nullptr) const;
|
||||
bool SetRigidbodyRotation(const glm::vec3& rotDeg);
|
||||
bool TeleportRigidbody(const glm::vec3& pos, const glm::vec3& rotDeg);
|
||||
// Audio helpers
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "../../include/Window/Window.h"
|
||||
#include "../../include/ThirdParty/stb_image.h"
|
||||
#include <cstdlib>
|
||||
|
||||
int width, height, channels;
|
||||
|
||||
@@ -7,7 +8,17 @@ GLFWwindow *Window::makeWindow() {
|
||||
unsigned char *pixels = stbi_load("Resources/Engine-Root/Modu-Logo.png",
|
||||
&width, &height, &channels, 4);
|
||||
#if defined(__linux__)
|
||||
setenv("XDG_SESSION_TYPE", "x11", 1);
|
||||
const char *wayland_display = std::getenv("WAYLAND_DISPLAY");
|
||||
const char *x11_display = std::getenv("DISPLAY");
|
||||
if (wayland_display && *wayland_display &&
|
||||
glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) {
|
||||
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND);
|
||||
} else if (x11_display && *x11_display &&
|
||||
glfwPlatformSupported(GLFW_PLATFORM_X11)) {
|
||||
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
|
||||
} else {
|
||||
glfwInitHint(GLFW_PLATFORM, GLFW_ANY_PLATFORM);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!glfwInit()) {
|
||||
|
||||
Reference in New Issue
Block a user