diff options
| author | MegaMech <MegaMech@users.noreply.github.com> | 2025-05-14 18:30:32 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-14 18:30:32 -0600 |
| commit | 32632cacdb316d7f2d3b6ecdb634f0ef9aebadf0 (patch) | |
| tree | d374a84675fc0f49100c275aa34828bc9db538f8 /src/engine/editor/EditorMath.cpp | |
| parent | 70f049cb6c326dede27e0ba7e88ac0d67b7651a4 (diff) | |
Impl new intro (#193)
* Update menus
* Update CMakeLists.txt
* Add Ship
* Impl hm ship actors
* Update HM course
* Impl new intro
* Finish intro scene
* Rename
* Start Editor Work
* raycast works
* Fix ScreenRayTrace in widescreen
* Basic Actor Picking
* wip
* Editor use vtx collision
* gizmo work
* otr works for object picking
* Impl objects for editor
* actor init
* Update
* Add all axis move (freemove)
* Docking Windows works here
* Setup imgui layout for editor
* Editor Snap to Ground works
* Basic Scene Explorer Works
* Editor get actor names
* Impl editor object names
* impl Editor Play and Pause buttons
* Editor translate works while paused
* Fix freecam lighting
* Added adjustable track properties to editor
* Editor matrix, icons, rotation, impl light
* Setup Track Properties 1
* Editor tooling wip
* Load modded o2rs
* Don't enable hud if editor is enabled
* Updates
* SceneManager nearly working
* Fix mario kart 64 intro logo sizing
* Fix Rotator
* Finish new matrix translation code
* Cleanup headers
* Cleanup
* Cleanup 2
* Cleanup 3
* Prevent divize by zero crash
* Add visible circle for translate in all axis
* Editor scaling/rot works properly now
* Scale All_Axis evenly
* Fixes to includes to work on Linux.
* Removed overfilled arguments in gfx_create_framebuffer()
* Added missing function definitions to Game.h
* Editor sun face the camera
* Add rotation model to gizmo
* Add new handles
* Failed attempt at transforming collision
* Impl water volume
* Import fast64 paths
* water surface
* Scene Setup 1
* Custom Track O2R almost working needs testing
* Custom Track Load path O2r
* Render custom track. Wip collision
* Add missing function
* Debug Spawning Custom O2R Track
* Import courses working now
* Fix memory leak
* Remove New Track Button
* Engine.cpp more consistent with sf64
* Fix Editor Enable Button
* Editor Accurate mouse click drag objects
* Editor selects closest object and cleanup
* Gizmo rot and scale collision working
* Remove constexpr from IRotator
* Impl properties for location/rot/scale
* Better Properties display, swap rot handles
* Fix content browser dock and editor now disabled by default
* Remove GameInfoWindow, Multiplayer Button, and FPS Slider
* Disable Editor when its disabled
* Add new logo to hm intro
* Fix pause menu item box cursor
* Remove minimap from Course::from_json and to_json
* Impl Import Minimap
* Fix custom minimap rendering
* minimap uses extension .png
* Refactor minimap
* Freecam only for player 1
* GrandPrix Balloons work in custom track
* Track Id is now std::string and outside of Props
* Moved editor assets to be included in ship.o2r
* Fixed GenerateO2R to package the correct folder and save to the correct filename
* Linux specific changes.
* Added "#include <stdio.h>" that required them
* Changed how the "ship.o2r" file is loaded to allow it to load the file from within appimages.
* Changed the Linuxdeploy version to avoid errors later when the Github Actions creates appimages(same fix applied to other ports.)
* Revert "Moved editor assets to be included in ship.o2r"
This reverts commit 05704c01f761baa67a83e357d9765f5c6b5e3fdb.
* Added back files(this time without LUS changes)
* Changed workflow file to use correct filename for assets file.
* Missed a few spots in the workflow file.
* Added .desktop file and made corrections to the main workflow.
* Added the rest of upstream CMakeLists.txt
* disabled USE_NETWORKING
* New InverseMatrix
* Renamed both .o2r files to be more accurate to its contents.
* Reverted CmakeList.txt
---------
Co-authored-by: MegaMech <7255464+MegaMech@users.noreply.github.com>
Co-authored-by: sitton76 <58642183+sitton76@users.noreply.github.com>
Diffstat (limited to 'src/engine/editor/EditorMath.cpp')
| -rw-r--r-- | src/engine/editor/EditorMath.cpp | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/src/engine/editor/EditorMath.cpp b/src/engine/editor/EditorMath.cpp new file mode 100644 index 000000000..533c0c2b5 --- /dev/null +++ b/src/engine/editor/EditorMath.cpp @@ -0,0 +1,427 @@ +#include "EditorMath.h" + +#include <libultraship/libultraship.h> +#include "port/Game.h" +#include "port/Engine.h" +#include <libultra/types.h> +#include "GameObject.h" + +#include <vector> +#include <limits> +#include <cmath> + +extern "C" { +#include "common_structs.h" +#include "main.h" +#include "defines.h" +#include "actors.h" +#include "math_util.h" +#include "math_util_2.h" +#include "camera.h" +} + +std::vector<Mtx> EditorMatrix; + +bool IsInGameScreen() { + auto wnd = GameEngine::Instance->context->GetWindow(); + Ship::Coords mouse = wnd->GetMousePos(); + + // Define viewport boundaries + int left = gfx_current_game_window_viewport.x; + int right = left + OTRGetGameRenderWidth(); + int top = gfx_current_game_window_viewport.y; + int bottom = top + OTRGetGameRenderHeight(); + + // Check if the mouse is within the game render area + return (mouse.x >= left && mouse.x < right) && (mouse.y >= top && mouse.y < bottom); +} + +FVector ScreenRayTrace() { + auto wnd = GameEngine::Instance->context->GetWindow(); + Camera* camera = &cameras[0]; + + Ship::Coords mouse = wnd->GetMousePos(); + mouse.x -= gfx_current_game_window_viewport.x; + mouse.y -= gfx_current_game_window_viewport.y; + // Get screen dimensions + uint32_t width = OTRGetGameViewportWidth(); + uint32_t height = OTRGetGameViewportHeight(); + + // Convert mouse to NDS screen coordinates + float x = (2.0f * mouse.x) / width - 1.0f; // Normalized X: -1 to 1 + float y = 1.0f - (2.0f * mouse.y) / height; // Normalized Y: -1 to 1 + float z = 1.0f; // z is typically 1.0 for the near plane + + FVector4 rayClip = {x, y, z, 1.0f}; + + Mat4 perspMtx; + u16 perspNorm; + guPerspectiveF(perspMtx, &perspNorm, gCameraZoom[0], OTRGetAspectRatio(), CM_GetProps()->NearPersp, CM_GetProps()->FarPersp, 1.0f); + + Mat4 inversePerspMtx; + if (InverseMatrix((float*)&perspMtx, (float*)&inversePerspMtx) != 2) { + FVector4 rayEye = MultiplyMatrixVector(inversePerspMtx, (float*)&rayClip.x); + + Mat4 lookAtMtx; + guLookAtF(lookAtMtx, camera->pos[0], camera->pos[1], camera->pos[2], camera->lookAt[0], camera->lookAt[1], camera->lookAt[2], camera->up[0], camera->up[1], camera->up[2]); + Mat4 inverseViewMtx; + if (InverseMatrix((float*)&lookAtMtx, (float*)&inverseViewMtx[0][0]) != 2) { + rayEye.w = 0; + FVector4 invRayWor = MultiplyMatrixVector(inverseViewMtx, (float*)&rayEye.x); + + FVector direction; + direction = FVector(invRayWor.x, invRayWor.y, invRayWor.z); + + return direction; + } + } + return FVector(0, 0, 0); +} + +bool QueryCollisionRayActor(Vec3f rayOrigin, Vec3f rayDir, Vec3f actorMin, Vec3f actorMax, float* t) { + float tmin = -FLT_MAX, tmax = FLT_MAX; + + for (int i = 0; i < 3; i++) { + if (fabs(rayDir[i]) > 1e-6f) { // Avoid division by zero + float t1 = (actorMin[i] - rayOrigin[i]) / rayDir[i]; + float t2 = (actorMax[i] - rayOrigin[i]) / rayDir[i]; + + if (t1 > t2) { float temp = t1; t1 = t2; t2 = temp; } + + tmin = fmax(tmin, t1); + tmax = fmin(tmax, t2); + + if (tmax < tmin) return false; // No intersection + } else if (rayOrigin[i] < actorMin[i] || rayOrigin[i] > actorMax[i]) { + return false; // Ray is outside the slab + } + } + + *t = tmin; // Distance to first intersection + return true; +} + +FVector4 MultiplyMatrixVector(float matrix[4][4], float vector[4]) { + FVector4 result; + float* resultPtr = &result.x; + for (int i = 0; i < 4; i++) { + resultPtr[i] = 0; + for (int j = 0; j < 4; j++) { + resultPtr[i] += matrix[j][i] * vector[j]; // Swap [i][j] → [j][i] for column order + } + } + return result; +} + +// https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix +static bool InverseMatrix(const float m[16], float invOut[16]) { + float inv[16], det; + int i; + + inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + m[12] * m[6] * m[9]; + + inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if (det == 0) { + return false; + } + + det = 1.0 / det; + + for (i = 0; i < 16; i++) { + invOut[i] = inv[i] * det; + } + + return true; +} + +FVector TransformVecByMatrix(const FVector& vec, const float mtx[4][4]) { + FVector result; + result.x = vec.x * mtx[0][0] + vec.y * mtx[1][0] + vec.z * mtx[2][0] + mtx[3][0]; + result.y = vec.x * mtx[0][1] + vec.y * mtx[1][1] + vec.z * mtx[2][1] + mtx[3][1]; + result.z = vec.x * mtx[0][2] + vec.y * mtx[1][2] + vec.z * mtx[2][2] + mtx[3][2]; + return result; +} + +FVector TransformVecDirection(const FVector& dir, const float mtx[4][4]) { + FVector result; + result.x = dir.x * mtx[0][0] + dir.y * mtx[1][0] + dir.z * mtx[2][0]; + result.y = dir.x * mtx[0][1] + dir.y * mtx[1][1] + dir.z * mtx[2][1]; + result.z = dir.x * mtx[0][2] + dir.y * mtx[1][2] + dir.z * mtx[2][2]; + return result; +} + +Ray RayToLocalSpace(MtxF mtx, const Ray& ray) { + MtxF inverse; + + if (InverseMatrix((float*)&mtx, (float*)&inverse) != 2) { + FVector localRayOrigin = TransformVecByMatrix(ray.Origin, (float(*)[4])&inverse); + FVector localRayDir = TransformVecDirection(ray.Direction, (float(*)[4])&inverse); + return Ray{localRayOrigin, localRayDir.Normalize()}; + } + return Ray{}; // Fail. Return empty ray +} + +bool IntersectRayTriangle(const Ray& ray, const Triangle& tri, float& t) { + constexpr float EPSILON = 1e-6f; + + // Adjust the triangle vertices by the object's position + FVector v0 = tri.v0; + FVector v1 = tri.v1; + FVector v2 = tri.v2; + + FVector edge1 = v1 - v0; + FVector edge2 = v2 - v0; + FVector h = ray.Direction.Cross(edge2); + float a = edge1.Dot(h); + + if (std::abs(a) < EPSILON) + return false; // Ray is parallel to triangle + + float f = 1.0f / a; + FVector s = ray.Origin - v0; + float u = f * s.Dot(h); + + if (u < 0.0f || u > 1.0f) + return false; + + FVector q = s.Cross(edge1); + float v = f * ray.Direction.Dot(q); + + if (v < 0.0f || u + v > 1.0f) + return false; + + t = f * edge2.Dot(q); + return t > EPSILON; +} + +bool IntersectRayTriangleAndTransform(const Ray& ray, FVector pos, const Triangle& tri, float& t) { + constexpr float EPSILON = 1e-6f; + + // Adjust the triangle vertices by the object's position + FVector v0 = tri.v0 + pos; + FVector v1 = tri.v1 + pos; + FVector v2 = tri.v2 + pos; + + FVector edge1 = v1 - v0; + FVector edge2 = v2 - v0; + FVector h = ray.Direction.Cross(edge2); + float a = edge1.Dot(h); + + if (std::abs(a) < EPSILON) + return false; // Ray is parallel to triangle + + float f = 1.0f / a; + FVector s = ray.Origin - v0; + float u = f * s.Dot(h); + + if (u < 0.0f || u > 1.0f) + return false; + + FVector q = s.Cross(edge1); + float v = f * ray.Direction.Dot(q); + + if (v < 0.0f || u + v > 1.0f) + return false; + + t = f * edge2.Dot(q); + return t > EPSILON; +} + +std::optional<FVector> QueryHandleIntersection(MtxF mtx, Ray ray, const Triangle& tri) { + float t; + Ray localRay = RayToLocalSpace(mtx, ray); + if (IntersectRayTriangle(localRay, tri, t)) { + FVector localClickPosition = localRay.Origin + localRay.Direction * t; + FVector worldClickPosition = TransformVecByMatrix(localClickPosition, (float(*)[4])&mtx); + + return worldClickPosition; // Stop checking objects if we selected a Gizmo handle + } + return std::nullopt; +} + +bool IntersectRaySphere(const Ray& ray, const FVector& sphereCenter, float radius, float& t) { + const float EPSILON = 1e-6f; + + // Vector from ray origin to sphere center + FVector oc = ray.Origin - sphereCenter; + + // Quadratic equation coefficients + float a = ray.Direction.Dot(ray.Direction); + float b = 2.0f * oc.Dot(ray.Direction); + float c = oc.Dot(oc) - (radius * radius); + + // Compute discriminant + float discriminant = (b * b) - (4 * a * c); + + // No intersection if discriminant is negative + if (discriminant < 0) { + return false; + } + + // Compute nearest intersection point + float sqrtD = sqrtf(discriminant); + float t0 = (-b - sqrtD) / (2.0f * a); + float t1 = (-b + sqrtD) / (2.0f * a); + + // Select the closest valid intersection + if (t0 > EPSILON) { + t = t0; + return true; + } else if (t1 > EPSILON) { + t = t1; + return true; + } + + return false; // Sphere is behind the ray origin +} + +// bool FindClosestObject(const Ray& ray, const std::vector<GameObject*>& objects, GameObject* outObject, float& outDistance) { +// float closestDist = std::numeric_limits<float>::max(); +// bool found = false; + +// for (const auto& obj : objects) { +// for (const auto& tri : obj.Triangles) { +// float t; +// if (IntersectRayTriangle(ray, tri, *obj.Pos, t) && t < closestDist) { +// closestDist = t; +// outObject = obj; +// found = true; +// } +// } +// } + +// if (found) { +// outDistance = closestDist; +// return true; +// } + +// return false; +// } + +// Transform a matrix to a matrix identity +void Editor_MatrixIdentity(Mat4 mtx) { + register s32 i; + register s32 k; + + for (i = 0; i < 4; i++) { + for (k = 0; k < 4; k++) { + mtx[i][k] = (i == k) ? 1.0f : 0.0f; + } + } +} + +void Editor_AddMatrix(Mat4 mtx, int32_t flags) { + EditorMatrix.emplace_back(); + guMtxF2L(mtx, &EditorMatrix.back()); + gSPMatrix(gDisplayListHead++, &EditorMatrix.back(), flags); +} + +float CalculateAngle(const FVector& start, const FVector& end) { + float dot = start.Dot(end); + + float magStart = start.Magnitude(); + float magEnd = end.Magnitude(); + + float cosAngle = dot / (magStart * magEnd); + cosAngle = std::min(1.0f, std::max(-1.0f, cosAngle)); + + return acos(cosAngle); +} + +void SetDirectionFromRotator(IRotator rot, s8 direction[3]) { + float yaw = (rot.yaw) * (M_PI / 32768.0f); // Convert from n64 binary angles 0-0xFFFF 0-360 degrees to radians + float pitch = rot.pitch * (M_PI / 32768.0f); + + // Compute unit direction vector + float x = cosf(yaw) * cosf(pitch); + float y = -sinf(pitch); + float z = -sinf(yaw) * cosf(pitch); + + // Scale into -127 to 127 range (not 128 to avoid overflow) + direction[0] = static_cast<s8>(x * 127.0f); + direction[1] = static_cast<s8>(y * 127.0f); + direction[2] = static_cast<s8>(z * 127.0f); + + //printf("Light dir %d %d %d (from rot 0x%X 0x%X 0x%X)\n", direction[0], direction[1], direction[2], rotator[0], rotator[1], rotator[2]); +} + +void SetRotatorFromDirection(FVector direction, IRotator* rot) { + // Compute pitch (inverse of -sinf(pitch)) + float pitch = -asinf(direction.y); + + // Compute yaw (inverse of cosf(yaw) * cosf(pitch)) + float yaw = atan2f(-direction.z, direction.x); + + // Convert back to N64 angles (0-0xFFFF range) + rot->pitch = (s16)(pitch * (32768.0f / M_PI)); + rot->yaw = (s16)(yaw * (32768.0f / M_PI)); + rot->roll = 0; // Assume no roll, since it's undefined from direction alone +} + +FVector GetPositionAheadOfCamera(f32 dist) { + FVector pos = FVector(cameras[0].pos[0], cameras[0].pos[1], cameras[0].pos[2]); + + f32 pitch = (cameras[0].rot[2] / 65535.0f) * 360.0f; + f32 yaw = (cameras[0].rot[1] / 65535.0f) * 360.0f; + + // Convert degrees to radians + pitch = pitch * M_PI / 180.0f; + yaw = yaw * M_PI / 180.0f; + + // Compute forward vector + FVector forward( + -sinf(yaw), // X + -sinf(pitch), // Y + cosf(yaw) // Z (vertical component) + ); + + // Move 1000 units forward from the camera position + return pos + (forward * dist); +} |
