From 3e6e1fad5f406f0f26ad8866609131d04d6e698f Mon Sep 17 00:00:00 2001 From: maelstrom Date: Thu, 21 Aug 2025 22:09:26 +0200 Subject: [PATCH] feat(editor): use algorithm for handle intersection rather than physics engine --- core/src/handles.cpp | 26 ++----- core/src/handles.h | 2 +- core/src/math_helper.cpp | 145 +++++++++++++++++++++++++++++++++++---- core/src/math_helper.h | 17 ++++- editor/mainglwidget.cpp | 2 +- 5 files changed, 153 insertions(+), 39 deletions(-) diff --git a/core/src/handles.cpp b/core/src/handles.cpp index 950b736..12d1c84 100644 --- a/core/src/handles.cpp +++ b/core/src/handles.cpp @@ -8,10 +8,6 @@ #include #include #include -#include -#include -#include -#include HandleFace HandleFace::XPos(0, glm::vec3(1,0,0)); HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0)); @@ -23,10 +19,6 @@ std::array HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1)); -// Shitty solution -static rp::PhysicsCommon common; -static rp::PhysicsWorld* world = common.createPhysicsWorld(); - std::shared_ptr getHandleAdornee() { std::shared_ptr selection = gDataModel->GetService(); for (std::weak_ptr inst : selection->Get()) { @@ -52,21 +44,17 @@ CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) { return adornee->cframe.Rotation() + newPartPos; } -std::optional raycastHandle(rp::Ray ray) { +std::optional raycastHandle(Vector3 rayStart, Vector3 rayEnd) { for (HandleFace face : HandleFace::Faces) { CFrame cframe = getHandleCFrame(face); - // Implement manual detection via boxes instead of... this shit - // This code also hardly works, and is not good at all... Hooo nope. - rp::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position()); - body->addCollider(common.createBoxShape((cframe.Rotation() * Vector3(handleSize(face) / 2.f)).Abs()), rp::Transform::identity()); - rp::RaycastInfo info; - if (body->raycast(ray, info)) { - world->destroyRigidBody(body); + Vector3 halfSize = (cframe.Rotation() * Vector3(handleSize(face) / 2.f)).Abs(); + Vector3 minB = cframe.Position() - halfSize, maxB = cframe.Position() + halfSize; + + glm::vec3 hitPoint; + bool hit = HitBoundingBox(minB, maxB, rayStart, (rayEnd - rayStart).Unit(), hitPoint); + if (hit) return face; - } - - world->destroyRigidBody(body); } return std::nullopt; diff --git a/core/src/handles.h b/core/src/handles.h index 7f05bee..60c3371 100644 --- a/core/src/handles.h +++ b/core/src/handles.h @@ -40,7 +40,7 @@ std::shared_ptr getHandleAdornee(); CFrame getHandleCFrame(HandleFace face); CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos); Vector3 handleSize(HandleFace face); -std::optional raycastHandle(rp::Ray ray); +std::optional raycastHandle(Vector3 rayStart, Vector3 rayEnd); // Gets the cframe of the handle local to the center of the selected objects CFrame getLocalHandleCFrame(HandleFace face); diff --git a/core/src/math_helper.cpp b/core/src/math_helper.cpp index 6999d00..ce48b3b 100644 --- a/core/src/math_helper.cpp +++ b/core/src/math_helper.cpp @@ -2,9 +2,65 @@ #define CMP_EPSILON 0.00001 + +void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point) { + min = glm::vec3(glm::min(min.x, point.x), glm::min(min.y, point.y), glm::min(min.z, point.z)); + max = glm::vec3(glm::max(max.x, point.x), glm::max(max.y, point.y), glm::max(max.z, point.z)); +} + +void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count) { + if (count == 0) return; + + min = points[0]; + max = points[0]; + + for (int i = 0; i < count; i++) { + min = glm::vec3(glm::min(min.x, points[i].x), glm::min(min.y, points[i].y), glm::min(min.z, points[i].z)); + max = glm::vec3(glm::max(max.x, points[i].x), glm::max(max.y, points[i].y), glm::max(max.z, points[i].z)); + } +} + +void getAABBCoords(glm::vec3 &pos, glm::vec3 &size, glm::vec3 min, glm::vec3 max) { + pos = (max + min) / 2.f; + size = (max - min); +} + + +// ==================== THIRD-PARTY SOURCE CODE ==================== // + // After a long time researching, I was able to use and adapt Godot's implementation of movable handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp) // All thanks goes to them and David Eberly for his algorithm. +/**************************************************************************/ +/* geometry_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt) { // Based on David Eberly's Computation of Distance Between Line Segments algorithm. @@ -102,24 +158,83 @@ void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 r_qt = (1 - t) * p_q0 + t * p_q1; } -void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point) { - min = glm::vec3(glm::min(min.x, point.x), glm::min(min.y, point.y), glm::min(min.z, point.z)); - max = glm::vec3(glm::max(max.x, point.x), glm::max(max.y, point.y), glm::max(max.z, point.z)); -} +// https://github.com/erich666/GraphicsGems/tree/master?tab=License-1-ov-file#readme +// Note: The code below predates open source -void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count) { - if (count == 0) return; +/* + * EULA: The Graphics Gems code is copyright-protected. In other words, you cannot claim the text of the code as your own + * and resell it. Using the code is permitted in any program, product, or library, non-commercial or commercial. Giving + * credit is not required, though is a nice gesture. The code comes as-is, and if there are any flaws or problems with + * any Gems code, nobody involved with Gems - authors, editors, publishers, or webmasters - are to be held responsible. + * Basically, don't be a jerk, and remember that anything free comes with no guarantee. + */ - min = points[0]; - max = points[0]; +/* +Fast Ray-Box Intersection +by Andrew Woo +from "Graphics Gems", Academic Press, 1990 +*/ - for (int i = 0; i < count; i++) { - min = glm::vec3(glm::min(min.x, points[i].x), glm::min(min.y, points[i].y), glm::min(min.z, points[i].z)); - max = glm::vec3(glm::max(max.x, points[i].x), glm::max(max.y, points[i].y), glm::max(max.z, points[i].z)); +#define RIGHT 0 +#define LEFT 1 +#define MIDDLE 2 + +bool HitBoundingBox( + glm::vec3 minB, glm::vec3 maxB, /*box */ + glm::vec3 origin, glm::vec3 dir, /*ray */ + glm::vec3 &coord /* hit point */ +) { + bool inside = true; + glm::vec3 quadrant; + int i; + int whichPlane; + glm::vec3 maxT; + glm::vec3 candidatePlane; + + /* Find candidate planes; this loop can be avoided if + rays cast all from the eye(assume perpsective view) */ + for (i = 0; i < 3; i++) + if(origin[i] < minB[i]) { + quadrant[i] = LEFT; + candidatePlane[i] = minB[i]; + inside = false; + }else if (origin[i] > maxB[i]) { + quadrant[i] = RIGHT; + candidatePlane[i] = maxB[i]; + inside = false; + }else { + quadrant[i] = MIDDLE; + } + + /* Ray origin inside bounding box */ + if (inside) { + coord = origin; + return (true); } -} -void getAABBCoords(glm::vec3 &pos, glm::vec3 &size, glm::vec3 min, glm::vec3 max) { - pos = (max + min) / 2.f; - size = (max - min); + + /* Calculate T distances to candidate planes */ + for (i = 0; i < 3; i++) + if (quadrant[i] != MIDDLE && dir[i] !=0.) + maxT[i] = (candidatePlane[i]-origin[i]) / dir[i]; + else + maxT[i] = -1.; + + /* Get largest of the maxT's for final choice of intersection */ + whichPlane = 0; + for (i = 1; i < 3; i++) + if (maxT[whichPlane] < maxT[i]) + whichPlane = i; + + /* Check final candidate actually inside box */ + if (maxT[whichPlane] < 0.) return (false); + for (i = 0; i < 3; i++) + if (whichPlane != i) { + coord[i] = origin[i] + maxT[whichPlane] * dir[i]; + if (coord[i] < minB[i] || coord[i] > maxB[i]) + return (false); + } else { + coord[i] = candidatePlane[i]; + } + return true; /* ray hits box */ } \ No newline at end of file diff --git a/core/src/math_helper.h b/core/src/math_helper.h index 5d870de..2ee81b7 100644 --- a/core/src/math_helper.h +++ b/core/src/math_helper.h @@ -1,9 +1,20 @@ #pragma once #include +void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point); +void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count); +void getAABBCoords(glm::vec3& pos, glm::vec3& size, glm::vec3 min, glm::vec3 max); + // From godot/editor/plugins/gizmos/gizmo_3d_helper.h void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt); -void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point); -void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count); -void getAABBCoords(glm::vec3& pos, glm::vec3& size, glm::vec3 min, glm::vec3 max); \ No newline at end of file +/* +Fast Ray-Box Intersection +by Andrew Woo +from "Graphics Gems", Academic Press, 1990 +*/ +bool HitBoundingBox( + glm::vec3 minB, glm::vec3 maxB, /*box */ + glm::vec3 origin, glm::vec3 dir, /*ray */ + glm::vec3 &coord /* hit point */ +); \ No newline at end of file diff --git a/editor/mainglwidget.cpp b/editor/mainglwidget.cpp index d178dfb..0f8b27e 100755 --- a/editor/mainglwidget.cpp +++ b/editor/mainglwidget.cpp @@ -323,7 +323,7 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) { std::optional MainGLWidget::raycastHandle(glm::vec3 pointDir) { if (!editorToolHandles.active) return std::nullopt; - return ::raycastHandle(rp::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000)); + return ::raycastHandle(camera.cameraPos, glm::normalize(pointDir) * 50000.f); } void MainGLWidget::handleCursorChange(QMouseEvent* evt) {