#include "editor.hpp" #include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h" #include "array.hpp" #include "camera.hpp" #include "ecs.hpp" #include "entities.hpp" #include "game-settings.hpp" #include "game.hpp" #include "imgui.h" #include "level.hpp" #include "math-helpers.hpp" #include "player-input.hpp" #include "plugins.hpp" #include "project.hpp" #include "thread-pool.hpp" #include "vendor-glm-include.hpp" #include "vendor-tinyfiledialogs.h" #include "window.hpp" #include "pk.h" #include #include #include #include #include #ifdef WIN32 // TODO #else #include #include #endif const char* const dbgCtrl_CameraLeft = "debug-camera-left"; const char* const dbgCtrl_CameraRight = "debug-camera-right"; const char* const dbgCtrl_CameraForward = "debug-camera-forward"; const char* const dbgCtrl_CameraBack = "debug-camera-back"; const char* const dbgCtrl_CameraUp = "debug-camera-up"; const char* const dbgCtrl_CameraDown = "debug-camera-down"; const char* const dbgCtrl_CameraRotCC = "debug-camera-rot-counter-clockwise"; const char* const dbgCtrl_CameraRotC = "debug-camera-rot-clockwise"; const char* const dbgCtrl_CameraRot = "debug-camera-rot"; const char* const dbgCtrl_CameraButtonMask = "debug-camera-button-mask"; const char* const dbgCtrl_SelectHovered = "debug-select-hovered"; const char* const dbgCtrl_ClearSelection = "debug-clear-selection"; const char* const dbgCtrl_DeleteSelectedItem = "debug-delete-selected-item"; ThreadPoolHandle threadPoolHandle = ThreadPoolHandle_MAX; InputActionSetHandle debugControlsHandle = InputActionSetHandle_MAX; bool shouldSetupEditor = true; bool shouldDisableEditor = false; bool shouldRebuildProjectDir = true; bool shouldRebuildAssetList = true; DynArray entityInstancesToCreate{16}; CompInstance *selectedEntity = nullptr; CompInstance *hoveredEntity = nullptr; bool shouldCreateEntityType = false; EntityType entityTypeToCreate{}; CameraHandle selectedCamera = CameraHandle_MAX; const char* const newSceneName = "newScene.pstf"; bool shouldOpenLoadSceneDialog = false; bool shouldOpenSaveSceneDialog = false; bool shouldOpenNewScene = false; bool shouldSaveProjectFile = false; bool shouldRunCurrentScene = false; __pid_t subProgramPid = -1; glm::vec3 unproject(glm::vec3 windowCoords) { double xDevNorm = (2.0f * windowCoords.x) / Extent.width - 1.0f; double yDevNorm = 1.0f - (2.0f * windowCoords.y) / Extent.height; yDevNorm *= -1; glm::vec4 clipCoords(xDevNorm, yDevNorm, windowCoords.z, 1.f); glm::vec4 rayEye = glm::inverse(UBO.proj) * clipCoords; glm::vec4 worldCoords = glm::inverse(UBO.model) * glm::inverse(UBO.view) * rayEye; if (std::abs(0.0f - worldCoords.w) > 0.00001f) { worldCoords *= 1.0f / worldCoords.w; } return glm::vec3(worldCoords); } void PkeEditor_Tick(double delta) { if (shouldRunCurrentScene) { shouldRunCurrentScene = false; auto *task = pk_new>(); new (task) std::packaged_task( [] { subProgramPid = fork(); if (subProgramPid == 0) { int status = -1; const char *argv[] = { "pke_runtime", "--plugin", pkeSettings.args.pluginPath == nullptr ? "example.o" : pkeSettings.args.pluginPath, "--project", pkeSettings.args.projectPath == nullptr ? PKE_PROJ_DEFAULT_FILENAME : pkeSettings.args.projectPath, "--scene", pkeSettings.rt.sceneName == nullptr ? PKE_PROJ_DEFAULT_FILENAME : pkeSettings.rt.sceneName, NULL, }; status = execvp("pke_runtime", const_cast(argv)); fprintf(stderr, "pke_runtime (child) exited with a status of %i\n", status); subProgramPid = -1; } else if (subProgramPid == -1) { fprintf(stdout, "pke_runtime failed to fork\n"); subProgramPid = -1; } else { int status = 0xCAFEBABE; waitpid(subProgramPid, &status, 0); fprintf(stdout, "pke_runtime (parent) exited with a status of %i (0x%08X)\n", status, status); subProgramPid = -1; } }); PkeThreads_Enqueue(threadPoolHandle, task); } if (shouldSetupEditor) { PkeEditor_Setup(); } if (shouldDisableEditor) { PkeEditor_Teardown(); } if (shouldOpenNewScene) { shouldOpenNewScene = false; // queues unloading pkeSettings.rt.previousLevel = pkeSettings.rt.activeLevel; // bypasses loading a level by setting a new active one pkeSettings.rt.activeLevel = PkeLevel_Create("editorLevel"); pkeSettings.rt.sceneName = newSceneName; ActiveCamera = &NullCamera; } if (shouldSaveProjectFile) { shouldSaveProjectFile = false; PkeProject_Save(); } if (shouldOpenLoadSceneDialog) { shouldOpenLoadSceneDialog = false; auto *task = pk_new>(); new (task) std::packaged_task( [] { const char * patterns[1] = {"*.pstf"}; char *selectedScene = tinyfd_openFileDialog(nullptr, "cafebabe.pstf", 1, patterns, "Pke Scene Text File", 0); if (selectedScene != nullptr) { pkeSettings.rt.sceneName = selectedScene; pkeSettings.rt.shouldLoadScene = true; ActiveCamera = &NullCamera; } }); PkeThreads_Enqueue(threadPoolHandle, task); } if (shouldOpenSaveSceneDialog) { shouldOpenSaveSceneDialog = false; auto *task = pk_new>(); new (task) std::packaged_task( [] { const char * patterns[1] = {"*.pstf"}; char *selectedScene = tinyfd_saveFileDialog(nullptr, pkeSettings.rt.sceneName, 1, patterns, "Pke Scene Text File"); if (selectedScene != nullptr) { pkeSettings.rt.sceneName = selectedScene; pkeSettings.rt.shouldSaveScene = true; } }); PkeThreads_Enqueue(threadPoolHandle, task); } if (pkeSettings.rt.shouldSaveScene && pkeSettings.rt.sceneName) { pkeSettings.rt.shouldSaveScene = false; Game_SaveSceneFile(pkeSettings.rt.sceneName); shouldRebuildProjectDir = true; } if (selectedEntity && EntitiesToBeRemoved.Has(ECS_GetEntity(selectedEntity->entHandle))) { selectedEntity = nullptr; } bool imGuiHovered = ImGui::GetIO().WantCaptureMouse; if (selectedEntity != nullptr && !imGuiHovered) { auto holder = PkeInput_Query(dbgCtrl_ClearSelection); if (holder.type != InputEventHash{0}) { const PkeMouseButtonEvent *mbEvent = static_cast(holder.ptr); if (mbEvent->isPressed) { selectedEntity = nullptr; } } } // raycast for hovering hoveredEntity = nullptr; if (!imGuiHovered) { double xMousePos, yMousePos; if (pkeSettings.editorSettings.isUsingDebugCamera) { xMousePos = Extent.width / 2.0; yMousePos = Extent.height / 2.0; } else { glfwGetCursorPos(window, &xMousePos, &yMousePos); } glm::vec3 fromCoords{unproject(glm::vec3(xMousePos, yMousePos, -1.f))}; glm::vec3 toCoords{unproject(glm::vec3(xMousePos, yMousePos, 1.f))}; if (fromCoords.x == fromCoords.x && toCoords.x == toCoords.x) { btVector3 rayOrigin{}; btVector3 rayDestination{}; GlmToBullet(fromCoords, rayOrigin); GlmToBullet(toCoords, rayDestination); btCollisionWorld::ClosestRayResultCallback rayResult{rayOrigin, rayDestination}; rayResult.m_flags |= btTriangleRaycastCallback::kF_FilterBackfaces; BtDynamicsWorld->rayTest(rayOrigin, rayDestination, rayResult); if (rayResult.hasHit()) { hoveredEntity = reinterpret_cast(rayResult.m_collisionObject->getUserPointer()); } } } if (hoveredEntity != nullptr) { auto holder = PkeInput_Query(dbgCtrl_SelectHovered); if (holder.type != InputEventHash{0}) { const PkeMouseButtonEvent *mbEvent = static_cast(holder.ptr); if (mbEvent->isPressed) { selectedEntity = hoveredEntity; } } } CompInstance *focusedInst = selectedEntity != nullptr ? selectedEntity : hoveredEntity; bool found = false; if (focusedInst != nullptr) { const auto *grBinds = ECS_GetGrBinds(focusedInst->grBindsHandle); if (grBinds != nullptr) { pkeDebugHitbox.instanceBuffer = grBinds->instanceBuffer; pkeDebugHitbox.instanceStartingIndex = focusedInst->index; found = true; } } if (!found) { pkeDebugHitbox.instanceBuffer = VK_NULL_HANDLE; pkeDebugHitbox.instanceStartingIndex = 0; } if (shouldCreateEntityType) { EntityType *existingEntity = EntityType_FindByTypeCode(entityTypeToCreate.entityTypeCode.val); if (existingEntity == nullptr) { EntityType *newEntType = EntityType_Create(); strncpy(newEntType->modelAssetKey, entityTypeToCreate.modelAssetKey, AssetKeyLength); newEntType->entityTypeCode = entityTypeToCreate.entityTypeCode; if (entityTypeToCreate.createInstanceCallback.name[0] != '\0') { strncpy(newEntType->createInstanceCallback.name, entityTypeToCreate.createInstanceCallback.name, CallbackSignatureLength); PkePlugin_SetSignatureFunc(&newEntType->createInstanceCallback); } for (int64_t i = 0; i < entityTypeToCreate.detailsCount; ++i) { newEntType->details[i] = entityTypeToCreate.details[i]; strncpy(newEntType->details[i].textureAssetKey, entityTypeToCreate.details[i].textureAssetKey, AssetKeyLength); } EntityType_Load(entityTypeToCreate); for (int64_t i = 0; i < entityTypeToCreate.detailsCount; ++i) { strncpy(newEntType->details[i].grBinds->collisionCallback.name, entityTypeToCreate.details[i].grBinds->collisionCallback.name, CallbackSignatureLength); PkePlugin_SetSignatureFunc(&newEntType->details[i].grBinds->collisionCallback); } } else { // TODO leaks entityTypeToCreate.entityTypeCode } entityTypeToCreate = EntityType{}; shouldCreateEntityType = false; } while (entityInstancesToCreate.Count() > 0) { EntityType *et = entityInstancesToCreate.Pop(); if (et->createInstanceCallback.func) { reinterpret_cast(et->createInstanceCallback.func)(); } else { EntityType_CreateGenericInstance(et, PkeLevel_Get(pkeSettings.rt.activeLevel), nullptr); } } PkeInputEventHolder holder = PkeInput_Query(dbgCtrl_CameraButtonMask); if (holder.type != InputEventHash{0}) { PkeMouseButtonEvent *toggleCameraMovement; toggleCameraMovement = static_cast(holder.ptr); if (toggleCameraMovement->thisTick) { if (toggleCameraMovement->isPressed && pkeSettings.editorSettings.isUsingDebugCamera == false) { pkeSettings.editorSettings.isUsingDebugCamera = true; glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); selectedCamera = CameraHandle_MAX; if (ActiveCamera != &NullCamera) { btTransform btfm; ActiveCamera->phys.inst->bt.motionState->getWorldTransform(btfm); NullCamera.phys.inst->bt.motionState->setWorldTransform(btfm); NullCamera.phys.inst->bt.rigidBody->setWorldTransform(btfm); NullCamera.phys.inst->bt.rigidBody->activate(); NullCamera.phys.targetInst = ActiveCamera->phys.targetInst; NullCamera.type = ActiveCamera->type; NullCamera.view = ActiveCamera->view; NullCamera.stale = PKE_CAMERA_STALE_ALL; ActiveCamera = &NullCamera; } } else if (toggleCameraMovement->isPressed && pkeSettings.editorSettings.isUsingDebugCamera == true) { pkeSettings.editorSettings.isUsingDebugCamera = false; glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } } if (pkeSettings.editorSettings.isUsingDebugCamera && ActiveCamera == &NullCamera) { holder = PkeInput_Query(dbgCtrl_CameraRot); btTransform trfm; NullCamera.phys.inst->bt.motionState->getWorldTransform(trfm); if (holder.type != InputEventHash{0}) { const PkeCursorPosEvent *posEvent = static_cast(holder.ptr); if (posEvent->xMotion || posEvent->yMotion) { glm::quat gRot; BulletToGlm(trfm.getRotation(), gRot); glm::vec3 axis1Heading = glm::conjugate(gRot) * glm::vec3(1.f, 0.f, 0.f); glm::vec3 axis2Heading = glm::conjugate(gRot) * glm::vec3(0.f, 1.f, 0.f); glm::quat pitch = glm::angleAxis(float(posEvent->yMotion) * 0.01f, glm::normalize(axis1Heading)); glm::quat yaw = glm::angleAxis(float(posEvent->xMotion) * 0.01f, glm::normalize(axis2Heading)); glm::quat rot = gRot * pitch * yaw; auto eul = glm::eulerAngles(rot); gRot = glm::quat(eul); btQuaternion bRot; GlmToBullet(gRot, bRot); trfm.setRotation(bRot); NullCamera.stale = NullCamera.stale | PKE_CAMERA_STALE_ROT; } } double leftCount = 0; double rightCount = 0; double forwardCount = 0; double backCount = 0; double upCount = 0; double downCount = 0; double rotCCCount = 0; double rotCCount = 0; holder = PkeInput_Query(dbgCtrl_CameraLeft); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyLeft; keyLeft = static_cast(holder.ptr); leftCount = keyLeft->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraRight); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyRight; keyRight = static_cast(holder.ptr); rightCount = keyRight->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraForward); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyForward; keyForward = static_cast(holder.ptr); forwardCount = keyForward->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraBack); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyBack; keyBack = static_cast(holder.ptr); backCount = keyBack->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraUp); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyUp; keyUp = static_cast(holder.ptr); upCount = keyUp->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraDown); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyDown; keyDown = static_cast(holder.ptr); downCount = keyDown->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraRotCC); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyRotCC; keyRotCC = static_cast(holder.ptr); rotCCCount = keyRotCC->isPressed ? 1 : 0; } holder = PkeInput_Query(dbgCtrl_CameraRotC); if (holder.type != InputEventHash{0}) { PkeKeyEvent *keyRotC; keyRotC = static_cast(holder.ptr); rotCCount = keyRotC->isPressed ? 1 : 0; } double accelerated = delta * 10.f; double axis1 = -(leftCount * accelerated) + (rightCount * accelerated); double axis2 = (forwardCount * accelerated) + -(backCount * accelerated); double axis3 = -(upCount * accelerated) + (downCount * accelerated); if (axis1 != 0 || axis2 != 0 || axis3 != 0) { glm::quat gRot; BulletToGlm(trfm.getRotation(), gRot); glm::vec3 axis1Heading = glm::conjugate(gRot) * glm::vec3(-axis1, 0.f, 0.f); glm::vec3 axis2Heading = glm::conjugate(gRot) * glm::vec3(0.f, 0.f, axis2); glm::vec3 axis3Heading = glm::conjugate(gRot) * glm::vec3(0.f, axis3, 0.f); glm::vec3 gPos; BulletToGlm(trfm.getOrigin(), gPos); gPos += glm::vec3(axis1Heading + axis2Heading + axis3Heading); btVector3 bPos; GlmToBullet(gPos, bPos); trfm.setOrigin(bPos); NullCamera.stale = NullCamera.stale | PKE_CAMERA_STALE_POS; } double axis4 = -(rotCCCount * delta) + (rotCCount * delta); if (axis4 != 0.0) { glm::quat gRot; BulletToGlm(trfm.getRotation(), gRot); gRot = glm::quat(glm::vec3(0.f, 0.f, axis4)) * gRot; NullCamera.stale = NullCamera.stale | PKE_CAMERA_STALE_ROT; } NullCamera.phys.inst->bt.motionState->setWorldTransform(trfm); NullCamera.phys.inst->bt.rigidBody->setWorldTransform(trfm); NullCamera.phys.inst->bt.rigidBody->activate(); } holder = PkeInput_Query(dbgCtrl_DeleteSelectedItem); if (holder.type != InputEventHash{0} && selectedEntity != nullptr) { PkeKeyEvent *delSelectedItemEvent; delSelectedItemEvent = static_cast(holder.ptr); if (delSelectedItemEvent->isPressed == true) { ECS_MarkForRemoval(ECS_GetEntity(selectedEntity->entHandle)); selectedEntity = nullptr; } } } void RecordImGui_GLM(const char *label, glm::mat4 &mat) { float min = -1; float max = 1; for (long i = 0; i < 4; ++i) { ImGui::PushID(i); ImGui::SliderFloat4(label, &mat[i][0], min, max, "%.3f", ImGuiSliderFlags_NoRoundToFormat); ImGui::PopID(); } } struct assetLabel { AssetKey key = {'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'}; AssetHandle handle{}; AssetType type{}; }; DynArray assetEntries{0, nullptr}; int assetLabelCmp(const void *a, const void *b) { const auto &tA = *static_cast(a); const auto &tB = *static_cast(b); return strcmp(tA.key, tB.key); } void buildAssetLableList() { } struct AssetPickerSearchStruct { int64_t selectedItem = -1; char source[8]; char safeKey[AssetKeyLength + 1]; AssetType type{PKE_ASSET_TYPE_ALL}; }; void RecordImGui_AssetPicker(AssetPickerSearchStruct &apss) { if (shouldRebuildAssetList == true) { if (assetEntries.Count() == 0) { uint64_t bCount = AM_GetBucketCount(); for (uint64_t b = 0; b < bCount; ++b) { uint64_t iCount = 0; Asset *assets = AM_GetAssets(b, iCount); for (long i = 0; i < iCount; ++i) { const Asset &a = assets[i]; assetLabel &al = assetEntries.Push(); memcpy(al.key, a.key, AssetKeyLength); al.handle = a.handle; al.type = a.type; } } } std::qsort(assetEntries.GetPtr(), assetEntries.Count(), sizeof(assetLabel), assetLabelCmp); } if (ImGui::BeginPopup(apss.source)) { ImGui::SeparatorText("Asset"); const int64_t iCount = assetEntries.Count(); for (int64_t i = 0; i < iCount; ++i) { const assetLabel &al = assetEntries[i]; if ((al.type & apss.type) == AssetType{0}) { continue; } char buf[AssetKeyLength + 1]; snprintf(buf, AssetKeyLength + 1, "%s", al.key); if (ImGui::Selectable(buf, apss.selectedItem == i)) { snprintf(apss.safeKey, AssetKeyLength + 1, "%s", buf); apss.selectedItem = i; ImGui::CloseCurrentPopup(); } } ImGui::EndPopup(); } } void RecordImGuiModalCreateAsset() { if (ImGui::BeginPopupModal("CreateAsset", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { static char assetPath[256]; static char assetKey[AssetKeyLength + 1]; static AssetType type; assetPath[255] = '\0'; assetKey[AssetKeyLength] = '\0'; ImGui::InputText("Asset Key", assetKey, AssetKeyLength); static uint32_t assetTypeMask = 0U; if (ImGui::CheckboxFlags("Shader", &assetTypeMask, 1U << static_cast(PKE_ASSET_TYPE_SHADER))) { assetTypeMask = assetTypeMask & (1U << static_cast(PKE_ASSET_TYPE_SHADER)); type = PKE_ASSET_TYPE_SHADER; } if (ImGui::CheckboxFlags("Model", &assetTypeMask, 1U << static_cast(PKE_ASSET_TYPE_MODEL))) { assetTypeMask = assetTypeMask & (1U << static_cast(PKE_ASSET_TYPE_MODEL)); type = PKE_ASSET_TYPE_MODEL; } if (ImGui::CheckboxFlags("Texture", &assetTypeMask, 1U << static_cast(PKE_ASSET_TYPE_TEXTURE))) { assetTypeMask = assetTypeMask & (1U << static_cast(PKE_ASSET_TYPE_TEXTURE)); type = PKE_ASSET_TYPE_TEXTURE; } if (ImGui::CheckboxFlags("Audio", &assetTypeMask, 1U << static_cast(PKE_ASSET_TYPE_AUDIO))) { assetTypeMask = assetTypeMask & (1U << static_cast(PKE_ASSET_TYPE_AUDIO)); type = PKE_ASSET_TYPE_AUDIO; } if (ImGui::Button("Select File")) { const char * patterns[1] = {"*.*"}; char *selectedPath = tinyfd_openFileDialog(nullptr, "cafebabe.pstf", 1, patterns, "Pke Scene Text File", 0); if (selectedPath != nullptr) { strncpy(assetPath, selectedPath, 255); } } ImGui::SameLine(); ImGui::Text("%s", assetPath); ImGui::Separator(); bool shouldClose = false; if (ImGui::Button("Create")) { AM_Register(assetKey, type, assetPath); shouldRebuildAssetList = true; shouldClose = true; } ImGui::SameLine(); if (ImGui::Button("Cancel")) { shouldClose = true; } if (shouldClose) { type = PKE_ASSET_TYPE_UNSET; assetTypeMask = 0U; assetKey[0] = '\0'; assetPath[0] = '\0'; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } void RecordImGuiEditorWrapper() { ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode); ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New Scene")) { shouldOpenNewScene = true; } /* * comparing pointer locations of c-strings is intentional * - the goal is not to prevent a specific scene name, * I just want to know if they clicked the "New Scene" button */ ImGui::BeginDisabled(pkeSettings.rt.sceneName == newSceneName); if (ImGui::MenuItem("Save")) { shouldSaveProjectFile = true; pkeSettings.rt.shouldSaveScene = true; } if (pkeSettings.rt.sceneName) { ImGui::SameLine(); int offset = 0; const auto *slash = strrchr(pkeSettings.rt.sceneName, '\\'); slash = slash != nullptr ? slash : strrchr(pkeSettings.rt.sceneName, '/'); if (slash != nullptr) { offset = slash - pkeSettings.rt.sceneName; } ImGui::Text("%s", pkeSettings.rt.sceneName + offset); } ImGui::EndDisabled(); if (ImGui::MenuItem("Save As...")) { shouldSaveProjectFile = true; shouldOpenSaveSceneDialog = true; } if (ImGui::MenuItem("Exit")) { glfwSetWindowShouldClose(window, true); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Debug")) { ImGui::Checkbox("Show Debug Hitboxes", &pkeSettings.isRenderingDebug); ImGui::BeginDisabled(); ImGui::Checkbox("Pause Physics Simulation", &pkeSettings.isSimulationPaused); ImGui::EndDisabled(); if (ImGui::Checkbox("Uncap Framerate", &pkeSettings.graphicsSettings.isFramerateUnlocked)) { shouldRecreateSwapchain = true; } if (ImGui::Checkbox("wait for v-sync", &pkeSettings.graphicsSettings.isWaitingForVsync)) { shouldRecreateSwapchain = true; } // ImGui::Checkbox("Uncap Tickrate", &pkeSettings.isTickrateUnlocked); if (ImGui::Checkbox("Use Debug Camera", &pkeSettings.editorSettings.isUsingDebugCamera)) { if (pkeSettings.editorSettings.isUsingDebugCamera) { // shouldUnlockCamera = true; ImGui::CloseCurrentPopup(); } else { // shouldLockCamera = true; } } ImGuiIO &io = ImGui::GetIO(); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); ImGui::EndMenu(); } ImGui::Spacing(); ImGui::BeginDisabled(subProgramPid != -1); if (ImGui::Button("▶️")) { shouldRunCurrentScene = true; } ImGui::EndDisabled(); ImGui::EndMainMenuBar(); } void RecordImGuiEntityTypes() { if (!ImGui::Begin("EntityTypes")) { ImGui::End(); return; } static ImGuiTableFlags tableFlags{ ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg }; if (ImGui::BeginTable("EntityTypes", 5, tableFlags)) { ImGui::TableSetupColumn("EntityHandle"); ImGui::TableSetupColumn("ModelAssetKey"); ImGui::TableSetupColumn("EntityTypeCode"); ImGui::TableSetupColumn("Details"); ImGui::TableSetupColumn("Instances"); ImGui::TableHeadersRow(); pk_handle_bucket_index_T cameraBucketCount = EntityType_GetBucketCount(); for (pk_handle_bucket_index_T b = 0; b < cameraBucketCount; ++b) { pk_handle_item_index_T count; auto *entityTypes = EntityType_GetEntityTypes(b, count); ImGui::PushID(b); for (pk_handle_item_index_T i = 0; i < count; ++i) { const auto &et = entityTypes[i]; if (et.handle == EntityHandle_MAX) continue; ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("0x%08X 0x%08X", et.handle.bucketIndex, et.handle.itemIndex); ImGui::TableSetColumnIndex(1); ImGui::Text("%*.*s", 0, (int)AssetKeyLength, et.modelAssetKey); ImGui::TableSetColumnIndex(2); ImGui::Text("%s", et.entityTypeCode.val); ImGui::TableSetColumnIndex(3); ImGui::Text("count: %li", et.detailsCount); ImGui::TableSetColumnIndex(4); if (ImGui::Button("Add")) { entityInstancesToCreate.Push(const_cast(&et)); } ImGui::SameLine(); ImGui::Text("count: %u", et.details[0].grBinds->instanceCounter); ImGui::PopID(); } ImGui::PopID(); } ImGui::EndTable(); } ImGui::End(); } void RecordImGuiCameras() { if (!ImGui::Begin("Cameras")) { ImGui::End(); return; } if (ImGui::Button("Create")) { InstPos instPos{}; instPos.mass = 1.f; ActiveCamera->phys.inst->bt.motionState->getWorldTransform(instPos.posRot); instPos.scale = ActiveCamera->phys.inst->bt.rigidBody->getCollisionShape()->getLocalScaling(); auto &cam = PkeCamera_Register(instPos); cam.phys.targetInst = ActiveCamera->phys.targetInst; cam.type = ActiveCamera->type; cam.view = ActiveCamera->view; cam.isPrimary = false; PkeLevel_RegisterCamera(pkeSettings.rt.activeLevel, cam.camHandle); } static ImGuiTableFlags tableFlags{ ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg }; if (ImGui::BeginTable("Entities", 8, tableFlags)) { ImGui::TableSetupColumn("Interact"); ImGui::TableSetupColumn("CameraHandle"); ImGui::TableSetupColumn("Target"); ImGui::TableSetupColumn("Type"); ImGui::TableSetupColumn("View"); ImGui::TableSetupColumn("Stale"); ImGui::TableSetupColumn("IsPrimary"); ImGui::TableSetupColumn("Controls"); ImGui::TableHeadersRow(); int64_t cameraBucketCount = PkeCamera_GetBucketCount(); for (long b = 0; b < cameraBucketCount; ++b) { int64_t count; auto *cameras = PkeCamera_GetCameras(b, count); ImGui::PushID(b); for (long i = 0; i < count; ++i) { const auto &cam = cameras[i]; if (cam.handle == CameraHandle_MAX) continue; ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::BeginDisabled(selectedCamera == cam.handle); if (ImGui::Button("Show")) { selectedCamera = cam.camHandle; ActiveCamera = const_cast(&cam); ActiveCamera->stale = PKE_CAMERA_STALE_ALL; } ImGui::SameLine(); if (ImGui::Button("Select")) { selectedEntity = cam.phys.inst; } ImGui::EndDisabled(); ImGui::TableSetColumnIndex(1); ImGui::Text("0x%08X 0x%08X", cam.handle.bucketIndex, cam.handle.itemIndex); ImGui::TableSetColumnIndex(2); if (cam.phys.targetInst != nullptr) { ImGui::Text("0x%08X 0x%08X", cam.phys.targetInst->entHandle.bucketIndex, cam.phys.targetInst->entHandle.itemIndex); } else { ImGui::Text("0x%p", cam.phys.targetInst); } ImGui::TableSetColumnIndex(3); ImGui::Text("%hhu", (unsigned char)cam.type); ImGui::TableSetColumnIndex(4); ImGui::Text("%hhu", (unsigned char)cam.view); ImGui::TableSetColumnIndex(5); ImGui::Text("%hhu", (unsigned char)cam.stale); ImGui::TableSetColumnIndex(6); ImGui::Text("%i", cam.isPrimary); ImGui::TableSetColumnIndex(7); if (ImGui::Button("Update Position")) { InstPos instPos{}; instPos.mass = 1.f; ActiveCamera->phys.inst->bt.motionState->getWorldTransform(instPos.posRot); instPos.scale = ActiveCamera->phys.inst->bt.rigidBody->getCollisionShape()->getLocalScaling(); ECS_UpdateInstance(cam.phys.inst, instPos, true); } ImGui::SameLine(); if (ImGui::Button("Make Primary")) { PkeCamera_SetPrimary(cam.camHandle); } ImGui::PopID(); } ImGui::PopID(); } ImGui::EndTable(); } if (selectedCamera != CameraHandle_MAX) { int inputTextFlags = ImGuiInputTextFlags_ReadOnly; auto *cam = PkeCamera_Get(selectedCamera); if (cam) { bool isOrientTarget{bool(static_cast(cam->view & PKE_CAMERA_VIEW_TARGET))}; bool isOrientFree{bool(static_cast(cam->view & PKE_CAMERA_VIEW_FREE))}; bool isPerspective{bool(static_cast(cam->type & PKE_CAMERA_TYPE_PERSPECTIVE))}; bool isOrthogonal{bool(static_cast(cam->type & PKE_CAMERA_TYPE_ORTHOGONAL))}; ImGui::BeginDisabled(isPerspective); if (ImGui::Button("Perspective")) { cam->type = PKE_CAMERA_TYPE_PERSPECTIVE; } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(isOrthogonal); if (ImGui::Button("Orthogonal")) { cam->type = PKE_CAMERA_TYPE_ORTHOGONAL; } ImGui::EndDisabled(); ImGui::BeginDisabled(isOrientTarget); if (ImGui::Button("Target")) { cam->view = PKE_CAMERA_VIEW_TARGET; } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(isOrientFree); if (ImGui::Button("Free")) { cam->view = PKE_CAMERA_VIEW_FREE; } ImGui::EndDisabled(); } } ImGui::End(); } void RecordImGuiUBO() { if (!ImGui::Begin("UBO", &pkeSettings.editorSettings.isShowingUBO)) { ImGui::End(); return; } ImGui::Text("Model"); RecordImGui_GLM("UBO-model", UBO.model); ImGui::Text("View"); RecordImGui_GLM("UBO-view", UBO.view); ImGui::Text("Proj"); RecordImGui_GLM("UBO-proj", UBO.proj); ImGui::End(); } bool RecordImGui_CallbackSelectModal(long &selectedIndex) { bool returnValue = false; if (ImGui::BeginPopupModal("CallbackSelect", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { long count = 0; auto *signatures = PkePlugin_GetSortedSignatures(count); for (long i = 0; i < count; ++i) { const CallbackSignature &sig = signatures[i]; if (ImGui::Selectable(sig, selectedIndex == i)) { returnValue = true; selectedIndex = i; } } ImGui::EndPopup(); } return returnValue; } void RecordImGuiModalCreateEntityType() { if (ImGui::BeginPopupModal("CreateEntityType", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { static char entityTypeCode[32] = {'\0'}; static char createInstanceSig[CallbackSignatureLength + 1] = {'\0'}; static AssetPickerSearchStruct apssModel{ .source = {"cet_mdl"}, .safeKey = {""}, .type = PKE_ASSET_TYPE_MODEL, }; static AssetPickerSearchStruct apssTexture{ .source = {"cet_txr"}, .safeKey = {""}, .type = PKE_ASSET_TYPE_TEXTURE, }; if (ImGui::Button("Model Key")) { ImGui::OpenPopup(apssModel.source); } ImGui::SameLine(); ImGui::Text("%s", apssModel.safeKey); ImGui::InputText("Entity Type Code", entityTypeCode, 31); static long index = -1; if (ImGui::Button("Clear")) { index = -1; createInstanceSig[0] = '\0'; } ImGui::SameLine(); if (ImGui::Button("Change")) { index = -1; ImGui::OpenPopup("CallbackSelect"); } ImGui::SameLine(); ImGui::Text("Collision Callback: '%s'", createInstanceSig); if (RecordImGui_CallbackSelectModal(index)) { long x = 0; memcpy(createInstanceSig, PkePlugin_GetSortedSignatures(x)[index], CallbackSignatureLength); } ImGui::InputScalar("Sub-Types", ImGuiDataType_S64, &entityTypeToCreate.detailsCount); ImGui::SameLine(); if (ImGui::Button("-")) { entityTypeToCreate.detailsCount -= 1; } entityTypeToCreate.detailsCount = entityTypeToCreate.detailsCount < 1 ? 1 : entityTypeToCreate.detailsCount; ImGui::SameLine(); if (ImGui::Button("+")) { entityTypeToCreate.detailsCount += 1; entityTypeToCreate.detailsCount = entityTypeToCreate.detailsCount > EntityTypeDetails_MAX ? EntityTypeDetails_MAX : entityTypeToCreate.detailsCount; } for (int64_t i = 0; i < entityTypeToCreate.detailsCount; ++i) { auto &etd = entityTypeToCreate.details[i]; ImGui::Separator(); RecordImGui_AssetPicker(apssModel); if (ImGui::Button("Texture Key")) { ImGui::OpenPopup(apssTexture.source); } ImGui::SameLine(); ImGui::Text("%s", apssTexture.safeKey); RecordImGui_AssetPicker(apssTexture); ImGui::InputScalar("Starting Instance Count", ImGuiDataType_U32, &etd.startingInstanceCount); ImGui::InputFloat("Physics - Mass", &etd.bt.startingMass); ImGui::InputScalar("Physics - Collision Layer", ImGuiDataType_U16, &etd.bt.startingCollisionLayer); ImGui::InputScalar("Physics - Collision Mask", ImGuiDataType_U16, &etd.bt.startingCollisionMask); } ImGui::Separator(); bool shouldReset = false; if (ImGui::Button("Create")) { // TODO some type of validation strncpy(entityTypeToCreate.createInstanceCallback.name, createInstanceSig, CallbackSignatureLength); strncpy(entityTypeToCreate.modelAssetKey, apssModel.safeKey, AssetKeyLength); // TODO this needs to be an array strncpy(entityTypeToCreate.details[0].textureAssetKey, apssTexture.safeKey, AssetKeyLength); char *sEntityTypeCode = pk_new(strlen(entityTypeCode) + 1); strncpy(sEntityTypeCode, entityTypeCode, 31); entityTypeToCreate.entityTypeCode.val = sEntityTypeCode; shouldCreateEntityType = true; shouldReset = true; } ImGui::SameLine(); if (ImGui::Button("Cancel")) { shouldReset = true; } if (shouldReset) { createInstanceSig[0] = '\0'; apssModel.safeKey[0] = '\0'; apssTexture.safeKey[0] = '\0'; entityTypeCode[0] = '\0'; createInstanceSig[CallbackSignatureLength] = '\0'; apssModel.safeKey[AssetKeyLength] = '\0'; apssTexture.safeKey[AssetKeyLength] = '\0'; entityTypeCode[31] = '\0'; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } void RecordImGuiLevels() { if (!ImGui::Begin("Levels")) { ImGui::End(); return; } static ImGuiTableFlags tableFlags{ ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg }; if (ImGui::BeginTable("PkeLevels", 3, tableFlags)) { ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Handle"); ImGui::TableSetupColumn("CameraCount"); ImGui::TableHeadersRow(); for (long i = 0; i < MAX_LEVEL_COUNT; ++i) { if (LEVELS[i].levelHandle == LevelHandle_MAX) continue; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("%s", LEVELS[i].name); ImGui::TableSetColumnIndex(1); ImGui::Text("0x%04hx", static_cast(LEVELS[i].levelHandle)); ImGui::TableSetColumnIndex(2); ImGui::Text("%u", LEVELS[i].cameras.next - 1); } ImGui::EndTable(); } ImGui::End(); } void RecordImGui_CompGrBinds(bool readonly, CompGrBinds *component) { if (component == nullptr) return; int inputTextFlags = 0; if (readonly) inputTextFlags |= ImGuiInputTextFlags_ReadOnly; ImGui::Text("CompGRBinds"); ImGui::Separator(); if (component->vkPipelineLayout) ImGui::InputScalar("VkPipelineLayout", ImGuiDataType_U64, &component->vkPipelineLayout, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); if (component->vkDescriptorSets){ ImGui::InputScalarN("VkPipelineDescriptorSets", ImGuiDataType_U64, &component->vkDescriptorSets, MAX_FRAMES_IN_FLIGHT, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); } if (component->vertexBuffer) ImGui::InputScalar("VkVertexBuffer", ImGuiDataType_U64, &component->vertexBuffer, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("VertexFirstBinding", ImGuiDataType_U32, &component->vertexFirstBinding, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("VertexBindingCount", ImGuiDataType_U32, &component->vertexBindingCount, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("VertexOffsets", ImGuiDataType_U64, &component->vertexOffsets, nullptr, nullptr, nullptr, inputTextFlags); if (component->normalsBuffer) ImGui::InputScalar("VkNormalBuffer", ImGuiDataType_U64, &component->normalsBuffer, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("NormalFirstBinding", ImGuiDataType_U32, &component->normalsFirstBinding, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("NormalBindingCount", ImGuiDataType_U32, &component->normalsBindingCount, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("NormalOffsets", ImGuiDataType_U64, &component->normalsOffsets, nullptr, nullptr, nullptr, inputTextFlags); if (component->uvBuffer) ImGui::InputScalar("VkUVBuffer", ImGuiDataType_U64, &component->uvBuffer, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("UVFirstBinding", ImGuiDataType_U32, &component->uvFirstBinding, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("UVBindingCount", ImGuiDataType_U32, &component->uvBindingCount, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("UVOffsets", ImGuiDataType_U64, &component->uvOffsets, nullptr, nullptr, nullptr, inputTextFlags); if (component->indexBuffer) ImGui::InputScalar("VkIndexBuffer", ImGuiDataType_U64, &component->indexBuffer, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("IndexBindingCount", ImGuiDataType_U32, &component->indexBindingCount, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("IndexOffsets", ImGuiDataType_U64, &component->indexOffsets, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("IndexCount", ImGuiDataType_U32, &component->indexCount, nullptr, nullptr, nullptr, inputTextFlags); if (component->instanceBuffer) ImGui::InputScalar("VkInstanceBuffer", ImGuiDataType_U64, &component->instanceBuffer, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("InstanceFirstBinding", ImGuiDataType_U32, &component->instanceFirstBinding, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("InstanceBindingCount", ImGuiDataType_U32, &component->instanceBindingCount, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("InstanceOffsets", ImGuiDataType_U64, &component->instanceOffsets, nullptr, nullptr, nullptr, inputTextFlags); ImGui::InputScalar("InstanceBufferMaxCount", ImGuiDataType_U32, &component->instanceBufferMaxCount, nullptr, nullptr, nullptr, ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("Instance Count", ImGuiDataType_U32, &component->instanceCounter, nullptr, nullptr, nullptr, ImGuiInputTextFlags_ReadOnly); static long index = -1; if (ImGui::Button("Clear")) { index = -1; component->collisionCallback.name[0] = '\0'; } ImGui::SameLine(); if (ImGui::Button("Change")) { index = -1; ImGui::OpenPopup("CallbackSelect"); } ImGui::SameLine(); ImGui::Text("Collision Callback: '%s'", component->collisionCallback.name); if (RecordImGui_CallbackSelectModal(index)) { long x = 0; memcpy(component->collisionCallback.name, PkePlugin_GetSortedSignatures(x)[index], CallbackSignatureLength); PkePlugin_SetSignatureFunc(&component->collisionCallback); } ImGui::Spacing(); } void RecordImGui_CompInstPos(bool readonly, CompInstance *component) { if (component == nullptr) return; int inputTextFlags = 0; if (readonly) inputTextFlags |= ImGuiInputTextFlags_ReadOnly; ImGui::Text("CompInststance + InstPos"); ImGui::Separator(); bool changed = false; InstPos instPos; component->bt.motionState->getWorldTransform(instPos.posRot); instPos.scale = component->bt.rigidBody->getCollisionShape()->getLocalScaling(); instPos.mass = component->bt.rigidBody->getMass(); btVector3 pos = instPos.posRot.getOrigin(); btQuaternion rot = instPos.posRot.getRotation(); glm::vec3 eul; rot.getEulerZYX(eul.z, eul.y, eul.x); eul = glm::degrees(eul); ImGui::Text("InstanceHandle: 0x%08X 0x%08X", component->instanceHandle.bucketIndex, component->instanceHandle.itemIndex); changed = ImGui::InputScalar("Instance Index", ImGuiDataType_U64, &component->index, nullptr, nullptr, nullptr, ImGuiInputTextFlags_ReadOnly) || changed; changed = ImGui::InputScalarN("pos", ImGuiDataType_Float, &pos, 3, nullptr, nullptr, nullptr, inputTextFlags) || changed; changed = ImGui::InputScalarN("rot (eul)", ImGuiDataType_Float, &eul, 3, nullptr, nullptr, nullptr, inputTextFlags) || changed; changed = ImGui::InputScalarN("scale", ImGuiDataType_Float, &instPos.scale, 3, nullptr, nullptr, nullptr, inputTextFlags) || changed; changed = ImGui::InputFloat("mass", &instPos.mass, 0.0, 0.0, "%.3f", inputTextFlags) || changed; changed = ImGui::InputScalar("Phys - Collision Layer", ImGuiDataType_U16, &component->physicsLayer, nullptr, nullptr, nullptr, inputTextFlags) || changed; changed = ImGui::InputScalar("Phys - Collision Mask", ImGuiDataType_U16, &component->physicsMask, nullptr, nullptr, nullptr, inputTextFlags) || changed; static long index = -1; if (ImGui::Button("Clear")) { index = -1; component->collisionCallback.name[0] = '\0'; } ImGui::SameLine(); if (ImGui::Button("Change")) { index = -1; ImGui::OpenPopup("CallbackSelect"); } ImGui::SameLine(); ImGui::Text("Collision Callback: '%s'", component->collisionCallback.name); if (RecordImGui_CallbackSelectModal(index)) { long x = 0; memcpy(component->collisionCallback.name, PkePlugin_GetSortedSignatures(x)[index], 16); PkePlugin_SetSignatureFunc(&component->collisionCallback); } ImGui::InputScalar("Phys - Rigid Body", ImGuiDataType_U64, &component->bt.rigidBody, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); ImGui::InputScalar("Phys - Motion State", ImGuiDataType_U64, &component->bt.motionState, nullptr, nullptr, "0x%016lX", ImGuiInputTextFlags_ReadOnly); // exclude EntityHandle_MAX so you can't attach the NullCamera ImGui::BeginDisabled(ActiveCamera->handle == component->entHandle || ActiveCamera->handle == EntityHandle_MAX || ActiveCamera->phys.inst == nullptr || ActiveCamera->phys.inst == CAFE_BABE(CompInstance) || PkeCamera_Get(selectedEntity->entHandle) != nullptr); if (ImGui::Button("Attach Active Camera")) { PkeCamera_AttachToInstance(ActiveCamera->camHandle, component); } ImGui::EndDisabled(); if (changed) { instPos.posRot.setOrigin(pos); eul = glm::radians(eul); rot.setEulerZYX(eul.z, eul.y, eul.x); instPos.posRot.setRotation(rot); auto *broadphase = component->bt.rigidBody->getBroadphaseProxy(); broadphase->m_collisionFilterGroup = static_cast(component->physicsLayer); broadphase->m_collisionFilterMask = static_cast(component->physicsMask); ECS_UpdateInstance(component, instPos, true); } ImGui::Spacing(); } void RecordImGuiProjectSettingsEditor() { if (!ImGui::Begin("ProjectSettings")) { ImGui::End(); return; } if (ImGui::Button("Save")) { shouldSaveProjectFile = true; } ImGui::End(); } struct fsEntry { int type = 0; char *name = nullptr; char *path = nullptr; DynArray children{0, nullptr}; }; DynArray fsEntries{0, nullptr}; std::regex reg_sceneFile(".+\\.ps[tb]f$", std::regex_constants::icase); int fsEntryComp(const void *a, const void *b) { const auto &fsA = *static_cast(a); const auto &fsB = *static_cast(b); if (fsA.type == fsB.type) { return strcmp(fsA.name, fsB.name); } auto cmp = fsA.type <=> fsB.type; if (cmp == std::strong_ordering::less) { return -1; } if (cmp == std::strong_ordering::greater) { return 1; } return 0; } void SortRecursive(DynArray &arr) { std::qsort(arr.GetPtr(), arr.Count(), sizeof(fsEntry), fsEntryComp); for (long i = 0; i < arr.Count(); ++i) { SortRecursive(arr[i].children); } } void BuildDirRecursive(const std::filesystem::directory_entry &de, fsEntry *dirFsEntry) { auto &entry = dirFsEntry == nullptr ? fsEntries.Push() : dirFsEntry->children.Push(); auto fullPath = std::filesystem::absolute(de.path()); auto len = strlen(fullPath.c_str()); // TODO leaky entry.path = pk_new(len + 1); memset(entry.path, '\0', len + 1); memcpy(entry.path, fullPath.c_str(), len); len = strlen(fullPath.filename().c_str()); // TODO leaky entry.name = pk_new(len + 1); memset(entry.name, '\0', len + 1); memcpy(entry.name, fullPath.filename().c_str(), len); if (de.is_regular_file()) { if (std::regex_search(fullPath.c_str(), reg_sceneFile)) { entry.type = 1; } } else if (de.is_directory()) { entry.type = 0; std::filesystem::directory_iterator di{fullPath}; for (const std::filesystem::directory_entry &sde : di) { if (sde.is_directory() || (sde.is_regular_file() && std::regex_search(sde.path().c_str(), reg_sceneFile)) ) { BuildDirRecursive(sde, &entry); } } } } void BuildProjectMenuRecursive(fsEntry &entry) { if (entry.type == 1) { if (ImGui::Selectable(entry.name, false, ImGuiSelectableFlags_AllowDoubleClick) && ImGui::IsMouseDoubleClicked(0)) { pkeSettings.rt.sceneName = entry.name; pkeSettings.rt.shouldLoadScene = true; ActiveCamera = &NullCamera; } } else if (entry.type == 0) { if (ImGui::TreeNode(entry.name)) { for (long i = 0; i < entry.children.Count(); ++i) { BuildProjectMenuRecursive(entry.children[i]); } ImGui::TreePop(); } } } void RecordImGuiProjectBrowser() { if (!ImGui::Begin("ProjectBrowser")) { ImGui::End(); return; } if (shouldRebuildProjectDir == true) { shouldRebuildProjectDir = false; fsEntries.Resize(0); std::filesystem::directory_iterator di{std::filesystem::current_path()}; for (const std::filesystem::directory_entry &sde : di) { if (sde.is_directory() || (sde.is_regular_file() && std::regex_search(sde.path().c_str(), reg_sceneFile)) ) { BuildDirRecursive(sde, nullptr); } } SortRecursive(fsEntries); } for (long i = 0; i < fsEntries.Count(); ++i) { BuildProjectMenuRecursive(fsEntries[i]); } ImGui::End(); } void RecordImGuiSceneEditor() { if (!ImGui::Begin("SceneEditorEntityTypes", &pkeSettings.editorSettings.isShowingSceneEditor)) { ImGui::End(); return; } if (ImGui::Button("Create Asset")) { ImGui::OpenPopup("CreateAsset"); } if (ImGui::Button("Create Entity Type")) { ImGui::OpenPopup("CreateEntityType"); } if (ImGui::Button("Clear Selection")) { selectedEntity = nullptr; } ImGui::Spacing(); if (selectedEntity != nullptr) { static PkeArray entGrBinds; static PkeArray entInstances; bool reset = false; if (entGrBinds.next > 0) { if (entGrBinds.data[0]->entHandle != selectedEntity->entHandle) { reset = true; } } else if (entInstances.next > 0) { if (entInstances.data[0]->entHandle != selectedEntity->entHandle) { reset = true; } } if (reset) { PkeArray_SoftReset(&entGrBinds); PkeArray_SoftReset(&entInstances); } if (entGrBinds.next == 0) ECS_GetGrBinds(ECS_GetEntity(selectedEntity->entHandle), entGrBinds); if (entInstances.next == 0) ECS_GetInstances(ECS_GetEntity(selectedEntity->entHandle), entInstances); if (entGrBinds.next > 0) { if (ImGui::Button("Create Instance")) { Entity_Base *entity = ECS_GetEntity(selectedEntity->entHandle); auto *entityType = static_cast(entity); entityInstancesToCreate.Push(entityType); } } for (int64_t i = 0; i < entGrBinds.next; ++i) { RecordImGui_CompGrBinds(true, entGrBinds.data[i]); } for (int64_t i = 0; i < entInstances.next; ++i) { RecordImGui_CompInstPos(false, entInstances.data[i]); } } RecordImGuiModalCreateAsset(); RecordImGuiModalCreateEntityType(); ImGui::End(); } void PkeEditor_RecordImGui() { if (pkeSettings.isShowingEditor) { RecordImGuiEditorWrapper(); RecordImGuiProjectSettingsEditor(); RecordImGuiProjectBrowser(); RecordImGuiSceneEditor(); RecordImGuiUBO(); RecordImGuiCameras(); RecordImGuiLevels(); RecordImGuiEntityTypes(); Game_RecordImGui(); } } void PkeEditor_Setup() { shouldSetupEditor = false; PkeInput_ActivateSet(debugControlsHandle); } void PkeEditor_Disable() { shouldDisableEditor = false; PkeInput_DeactivateSet(debugControlsHandle); } void PkeEditor_Teardown() { PkeEditor_Disable(); if (subProgramPid != -1) { kill(subProgramPid, SIGINT); } PkeThreads_Teardown(threadPoolHandle); entityInstancesToCreate.~DynArray(); } void PkeEditor_Init() { PkeInputSet debugControlsSet; debugControlsSet.title = "debug-editor-controls"; debugControlsSet.actionCount = 13; debugControlsSet.actions = pk_new(debugControlsSet.actionCount); debugControlsSet.actions[0].name = dbgCtrl_CameraLeft; debugControlsSet.actions[0].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_A, }; debugControlsSet.actions[1].name = dbgCtrl_CameraRight; debugControlsSet.actions[1].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_D, }; debugControlsSet.actions[2].name = dbgCtrl_CameraForward; debugControlsSet.actions[2].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_W, }; debugControlsSet.actions[3].name = dbgCtrl_CameraBack; debugControlsSet.actions[3].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_S, }; debugControlsSet.actions[4].name = dbgCtrl_CameraUp; debugControlsSet.actions[4].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_R, }; debugControlsSet.actions[5].name = dbgCtrl_CameraDown; debugControlsSet.actions[5].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_F, }; debugControlsSet.actions[6].name = dbgCtrl_CameraRotCC; debugControlsSet.actions[6].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_Q, }; debugControlsSet.actions[7].name = dbgCtrl_CameraRotC; debugControlsSet.actions[7].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_E, }; debugControlsSet.actions[8].name = dbgCtrl_CameraRot; debugControlsSet.actions[8].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_CURSOR_POS_EVENTS, }; debugControlsSet.actions[9].name = dbgCtrl_CameraButtonMask; debugControlsSet.actions[9].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_MOUSE_BUTTON_EVENTS, .button = GLFW_MOUSE_BUTTON_MIDDLE, }; debugControlsSet.actions[10].name = dbgCtrl_SelectHovered; debugControlsSet.actions[10].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_MOUSE_BUTTON_EVENTS, .button = GLFW_MOUSE_BUTTON_LEFT, }; debugControlsSet.actions[11].name = dbgCtrl_ClearSelection; debugControlsSet.actions[11].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_MOUSE_BUTTON_EVENTS, .button = GLFW_MOUSE_BUTTON_RIGHT, }; debugControlsSet.actions[12].name = dbgCtrl_DeleteSelectedItem; debugControlsSet.actions[12].primaryHash = PkeInputEventMask { .computedHash = PKE_INPUT_HASH_ALL_KEY_EVENTS, .button = GLFW_KEY_DELETE, }; debugControlsHandle = PkeInput_RegisterSet(debugControlsSet); btTransform trfm{}; trfm.setIdentity(); trfm.setOrigin(btVector3(-40.f, -40.f, -40.f)); trfm.setRotation(btQuaternion(0.f, 0.f, 0.f, 1.f)); NullCamera.phys.inst->bt.motionState->setWorldTransform(trfm); NullCamera.phys.inst->bt.rigidBody->setWorldTransform(trfm); NullCamera.phys.inst->bt.rigidBody->activate(); NullCamera.type = PKE_CAMERA_TYPE_PERSPECTIVE, NullCamera.view = PKE_CAMERA_VIEW_FREE, NullCamera.stale = PKE_CAMERA_STALE_ALL, threadPoolHandle = PkeThreads_Init(1, 1); }