From 3ac4701291a909aa029e11575b4844f66c9b661e Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Tue, 21 Mar 2023 23:35:48 +0100 Subject: [PATCH 01/16] wip --- .../threepp/controls/TransformControls.hpp | 31 ++++ src/CMakeLists.txt | 2 + src/threepp/controls/TransformControls.cpp | 132 ++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 include/threepp/controls/TransformControls.hpp create mode 100644 src/threepp/controls/TransformControls.cpp diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp new file mode 100644 index 00000000..94eb9692 --- /dev/null +++ b/include/threepp/controls/TransformControls.hpp @@ -0,0 +1,31 @@ + +#ifndef THREEPP_TRANSFORMCONTROLS_HPP +#define THREEPP_TRANSFORMCONTROLS_HPP + +#include "threepp/core/Object3D.hpp" + +#include + +namespace threepp { + + class Camera; + class Canvas; + + class TransformControls: public Object3D { + + public: + + bool visible = true; + + TransformControls(Camera& camera, Canvas& canvas); + + + private: + struct Impl; + std::unique_ptr pimpl_; + + }; + +} + +#endif//THREEPP_TRANSFORMCONTROLS_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8726c0ad..68cd6038 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,7 @@ set(publicHeaders "threepp/controls/FlyControls.hpp" "threepp/controls/OrbitControls.hpp" + "threepp/controls/TransformControls.hpp" "threepp/core/BufferAttribute.hpp" "threepp/core/BufferGeometry.hpp" @@ -224,6 +225,7 @@ set(sources "threepp/controls/FlyControls.cpp" "threepp/controls/OrbitControls.cpp" + "threepp/controls/TransformControls.cpp" "threepp/core/BufferGeometry.cpp" "threepp/core/Clock.cpp" diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp new file mode 100644 index 00000000..01046ef0 --- /dev/null +++ b/src/threepp/controls/TransformControls.cpp @@ -0,0 +1,132 @@ + +#include "threepp/controls/TransformControls.hpp" + +#include "threepp/materials/LineBasicMaterial.hpp" +#include "threepp/materials/MeshBasicMaterial.hpp" + +#include "threepp/geometries/CylinderGeometry.hpp" + +#include "threepp/core/BufferGeometry.hpp" +#include "threepp/geometries/BoxGeometry.hpp" + +using namespace threepp; + +namespace { + + std::shared_ptr CircleGeometry(float radius, int arc) { + + auto geometry = BufferGeometry::create(); + std::vector vertices; + + for (unsigned i = 0; i <= 64 * arc; ++i) { + + vertices.emplace_back(0); + vertices.emplace_back(std::cos(static_cast(i) / 32 * math::PI) * radius, std::sin(static_cast(i) / 32 * math::PI) * radius); + } + + geometry->setAttribute("position", FloatBufferAttribute::create(vertices, 3)); + + return geometry; + } + + std::shared_ptr TranslateHelperGeometry() { + + auto geometry = BufferGeometry::create(); + + geometry->setAttribute("position", FloatBufferAttribute::create(std::vector{0, 0, 0, 1, 1, 1}, 3)); + + return geometry; + } + + +}// namespace + +struct TransformControlsGizmo: Object3D { + + TransformControlsGizmo() { + + auto gizmoMaterial = MeshBasicMaterial::create(); + gizmoMaterial->depthTest = false; + gizmoMaterial->depthWrite = false; + gizmoMaterial->transparent = true; + gizmoMaterial->side = DoubleSide; + gizmoMaterial->fog = false; + gizmoMaterial->toneMapped = false; + + auto gizmoLineMaterial = LineBasicMaterial::create(); + gizmoLineMaterial->depthTest = false; + gizmoLineMaterial->depthWrite = false; + gizmoLineMaterial->transparent = true; + gizmoMaterial->fog = false; + gizmoMaterial->toneMapped = false; + + // Make unique material for each axis/color + + const auto matInvisible = gizmoMaterial->clone(); + matInvisible->opacity = 0.15f; + + const auto matHelper = gizmoMaterial->clone(); + matHelper->opacity = 0.33f; + + const auto matRed = gizmoMaterial->clone()->as(); + matRed->color.setHex(0xff0000); + + const auto matGreen = gizmoMaterial->clone()->as(); + matGreen->color.setHex(0x00ff00); + + const auto matBlue = gizmoMaterial->clone()->as(); + matBlue->color.setHex(0x0000ff); + + const auto matWhiteTransparent = gizmoMaterial->clone()->as(); + matWhiteTransparent->opacity = 0.25f; + + const auto matYellowTransparent = matWhiteTransparent->as(); + matYellowTransparent->color.setHex(0xffff00); + + const auto matCyanTransparent = matWhiteTransparent->as(); + matCyanTransparent->color.setHex(0x00ffff); + + const auto matMagentaTransparent = matWhiteTransparent->as(); + matMagentaTransparent->color.setHex(0xff00ff); + + const auto matYellow = gizmoMaterial->clone()->as(); + matYellow->color.setHex(0xffff00); + + const auto matLineRed = gizmoLineMaterial->clone()->as(); + matLineRed->color.setHex(0xff0000); + + const auto matLineGreen = gizmoLineMaterial->clone()->as(); + matLineGreen->color.setHex(0x00ff00); + + const auto matLineBlue = gizmoLineMaterial->clone()->as(); + matLineBlue->color.setHex(0x0000ff); + + const auto matLineCyan = gizmoLineMaterial->clone()->as(); + matLineCyan->color.setHex(0x00ffff); + + const auto matLineMagenta = gizmoLineMaterial->clone()->as(); + matLineMagenta->color.setHex(0xff00ff); + + const auto matLineYellow = gizmoLineMaterial->clone()->as(); + matLineYellow->color.setHex(0xffff00); + + const auto matLineGray = gizmoLineMaterial->clone()->as(); + matLineGray->color.setHex(0x787878); + + const auto matLineYellowTransparent = matLineYellow->clone(); + matLineYellowTransparent->opacity = 0.25f; + + // reusable geometry + + const auto arrowGeometry = CylinderGeometry::create(0, 0.05, 0.2, 12, 1, false); + + const auto scaleHandleGeometry = BoxGeometry::create(0.125, 0.125, 0.125); + + const auto lineGeometry = BufferGeometry::create(); + lineGeometry->setAttribute("position", FloatBufferAttribute::create(std::vector{0, 0, 0, 1, 0, 0}, 3)); + + } +}; + +struct TransformControls::Impl { +}; From d62e50ca81ab03bb4b7dfb70ce836faa63e6fd3f Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Fri, 14 Apr 2023 22:46:15 +0200 Subject: [PATCH 02/16] update --- src/threepp/controls/TransformControls.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 01046ef0..335228f3 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -6,9 +6,10 @@ #include "threepp/geometries/CylinderGeometry.hpp" -#include "threepp/core/BufferGeometry.hpp" #include "threepp/geometries/BoxGeometry.hpp" +#include + using namespace threepp; namespace { @@ -21,7 +22,8 @@ namespace { for (unsigned i = 0; i <= 64 * arc; ++i) { vertices.emplace_back(0); - vertices.emplace_back(std::cos(static_cast(i) / 32 * math::PI) * radius, std::sin(static_cast(i) / 32 * math::PI) * radius); + vertices.emplace_back(std::cos(static_cast(i) / 32 * math::PI) * radius); + vertices.emplace_back(std::sin(static_cast(i) / 32 * math::PI) * radius); } geometry->setAttribute("position", FloatBufferAttribute::create(vertices, 3)); @@ -124,7 +126,6 @@ struct TransformControlsGizmo: Object3D { const auto lineGeometry = BufferGeometry::create(); lineGeometry->setAttribute("position", FloatBufferAttribute::create(std::vector{0, 0, 0, 1, 0, 0}, 3)); - } }; From ca3a9b69cfce2b8f2da2d94c3a23e90e94261069 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Fri, 21 Apr 2023 13:28:19 +0200 Subject: [PATCH 03/16] update --- .../threepp/controls/TransformControls.hpp | 16 ++-- src/threepp/controls/TransformControls.cpp | 74 +++++++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index 94eb9692..b0733463 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -14,18 +14,24 @@ namespace threepp { class TransformControls: public Object3D { public: + bool enabled = true; + std::string mode{"translate"}; + std::string space{"world"}; + float size = 1; + bool dragging = false; + bool showX = true; + bool showY = true; + bool showZ = true; - bool visible = true; - - TransformControls(Camera& camera, Canvas& canvas); + TransformControls(Object3D& object, Canvas& canvas); + ~TransformControls(); private: struct Impl; std::unique_ptr pimpl_; - }; -} +}// namespace threepp #endif//THREEPP_TRANSFORMCONTROLS_HPP diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 335228f3..eab97511 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -1,12 +1,17 @@ #include "threepp/controls/TransformControls.hpp" +#include "threepp/objects/Mesh.hpp" + #include "threepp/materials/LineBasicMaterial.hpp" #include "threepp/materials/MeshBasicMaterial.hpp" #include "threepp/geometries/CylinderGeometry.hpp" #include "threepp/geometries/BoxGeometry.hpp" +#include "threepp/geometries/PlaneGeometry.hpp" + +#include "threepp/core/Raycaster.hpp" #include @@ -14,6 +19,12 @@ using namespace threepp; namespace { + Raycaster _raycaster; + + Vector3 _tmpVector; + Vector2 _tempVector2; + Quaternion _tempQuaternion; + std::shared_ptr CircleGeometry(float radius, int arc) { auto geometry = BufferGeometry::create(); @@ -129,5 +140,68 @@ struct TransformControlsGizmo: Object3D { } }; +struct TransformControlsPlane: Mesh { + + TransformControlsPlane() + : Mesh(PlaneGeometry::create(100000, 100000, 2, 2), + MeshBasicMaterial::create({{"visible", false}, + {"wireframe", true}, + {"side", DoubleSide}, + {"transparent", true}, + {"opacity", 0.1f}, + {"toneMapped", false}})) {} + + void updateMatrixWorld(bool force) override { + } +}; + struct TransformControls::Impl { + + Vector3 _offset; + Vector3 _startNorm; + Vector3 _endNorm; + Vector3 _cameraScale; + + Vector3 _parentPosition; + Quaternion _parentQuaternion; + Quaternion _parentQuaternionInv; + Vector3 _parentScale; + + Vector3 _worldScaleStart; + Quaternion _worldQuaternionInv; + Vector3 _worldScale; + + Vector3 _positionStart; + Quaternion _quaternionStart; + Vector3 _scaleStart; + + std::shared_ptr _gizmo; + std::shared_ptr _plane; + + TransformControls& scope; + Object3D& object; + Canvas& canvas; + + Impl(TransformControls& scope, Object3D& object, Canvas& canvas) + : scope(scope), object(object), canvas(canvas), + _gizmo(std::make_shared()), + _plane(std::make_shared()) {} + + void updateMatrixWorld() { + + object.updateMatrixWorld(); + + object.parent->matrixWorld->decompose(_parentPosition, _parentQuaternion, _parentScale); + } }; + +TransformControls::TransformControls(Object3D& object, Canvas& canvas): pimpl_(std::make_unique(*this, object, canvas)) { + + this->visible = false; + + this->add(pimpl_->_gizmo); + this->add(pimpl_->_plane); +} + + +TransformControls::~TransformControls() = default; From 1c55c30a922ca8db8153f5eb6f5d39205a059adb Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Fri, 21 Apr 2023 14:10:45 +0200 Subject: [PATCH 04/16] update --- .../threepp/controls/TransformControls.hpp | 8 ++++- src/threepp/controls/TransformControls.cpp | 34 +++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index b0733463..32d4b8a4 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -23,7 +23,13 @@ namespace threepp { bool showY = true; bool showZ = true; - TransformControls(Object3D& object, Canvas& canvas); + Vector3 worldPosition; + Quaternion worldQuaternion; + Vector3 cameraPosition; + Quaternion cameraQuaternion; + Vector3 eye; + + TransformControls(Camera& camera, Canvas& canvas); ~TransformControls(); diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index eab97511..4e2cee6e 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -1,6 +1,7 @@ #include "threepp/controls/TransformControls.hpp" +#include "threepp/cameras/Camera.hpp" #include "threepp/objects/Mesh.hpp" #include "threepp/materials/LineBasicMaterial.hpp" @@ -179,28 +180,47 @@ struct TransformControls::Impl { std::shared_ptr _plane; TransformControls& scope; - Object3D& object; Canvas& canvas; + Camera& camera; - Impl(TransformControls& scope, Object3D& object, Canvas& canvas) - : scope(scope), object(object), canvas(canvas), + Object3D* object = nullptr; + + Impl(TransformControls& scope, Camera& camera, Canvas& canvas) + : scope(scope), camera(camera), canvas(canvas), _gizmo(std::make_shared()), - _plane(std::make_shared()) {} + _plane(std::make_shared()) { + + this->camera.updateMatrixWorld(); + this->camera.matrixWorld->decompose(scope.cameraPosition, scope.cameraQuaternion, _cameraScale); + + scope.eye.copy(scope.cameraPosition).sub(scope.worldPosition).normalize(); + } + +private: void updateMatrixWorld() { - object.updateMatrixWorld(); + if (object) { - object.parent->matrixWorld->decompose(_parentPosition, _parentQuaternion, _parentScale); + object->updateMatrixWorld(); + + object->parent->matrixWorld->decompose(_parentPosition, _parentQuaternion, _parentScale); + object->matrixWorld->decompose(scope.worldPosition, scope.worldQuaternion, _worldScale); + + _parentQuaternionInv.copy(_parentQuaternion).invert(); + _worldQuaternionInv.copy(scope.worldQuaternion).invert(); + } } }; -TransformControls::TransformControls(Object3D& object, Canvas& canvas): pimpl_(std::make_unique(*this, object, canvas)) { +TransformControls::TransformControls(Camera& camera, Canvas& canvas): pimpl_(std::make_unique(*this, camera, canvas)) { this->visible = false; this->add(pimpl_->_gizmo); this->add(pimpl_->_plane); + + Object3D::updateMatrixWorld(); } From 9812cf0cf5f991fc5f1299050e2261d59b83b7c5 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Tue, 5 Dec 2023 08:55:29 +0100 Subject: [PATCH 05/16] fix --- src/threepp/controls/TransformControls.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 4e2cee6e..57fcdf2c 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -63,7 +63,7 @@ struct TransformControlsGizmo: Object3D { gizmoMaterial->depthTest = false; gizmoMaterial->depthWrite = false; gizmoMaterial->transparent = true; - gizmoMaterial->side = DoubleSide; + gizmoMaterial->side = Side::Double; gizmoMaterial->fog = false; gizmoMaterial->toneMapped = false; @@ -147,7 +147,7 @@ struct TransformControlsPlane: Mesh { : Mesh(PlaneGeometry::create(100000, 100000, 2, 2), MeshBasicMaterial::create({{"visible", false}, {"wireframe", true}, - {"side", DoubleSide}, + {"side", Side::Double}, {"transparent", true}, {"opacity", 0.1f}, {"toneMapped", false}})) {} From e9b53b3344c21d204c410ee38080ec0c45672913 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Mon, 25 Mar 2024 20:49:46 +0100 Subject: [PATCH 06/16] update --- include/threepp/controls/TransformControls.hpp | 4 ++-- src/threepp/controls/TransformControls.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index 32d4b8a4..70c7eaee 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -9,7 +9,7 @@ namespace threepp { class Camera; - class Canvas; + class PeripheralsEventSource; class TransformControls: public Object3D { @@ -29,7 +29,7 @@ namespace threepp { Quaternion cameraQuaternion; Vector3 eye; - TransformControls(Camera& camera, Canvas& canvas); + TransformControls(Camera& camera, PeripheralsEventSource& canvas); ~TransformControls(); diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 57fcdf2c..9ae505c1 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -14,6 +14,8 @@ #include "threepp/core/Raycaster.hpp" +#include "threepp/input/PeripheralsEventSource.hpp" + #include using namespace threepp; @@ -153,6 +155,7 @@ struct TransformControlsPlane: Mesh { {"toneMapped", false}})) {} void updateMatrixWorld(bool force) override { + } }; @@ -180,12 +183,12 @@ struct TransformControls::Impl { std::shared_ptr _plane; TransformControls& scope; - Canvas& canvas; + PeripheralsEventSource& canvas; Camera& camera; Object3D* object = nullptr; - Impl(TransformControls& scope, Camera& camera, Canvas& canvas) + Impl(TransformControls& scope, Camera& camera, PeripheralsEventSource& canvas) : scope(scope), camera(camera), canvas(canvas), _gizmo(std::make_shared()), _plane(std::make_shared()) { @@ -213,7 +216,7 @@ struct TransformControls::Impl { } }; -TransformControls::TransformControls(Camera& camera, Canvas& canvas): pimpl_(std::make_unique(*this, camera, canvas)) { +TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& canvas): pimpl_(std::make_unique(*this, camera, canvas)) { this->visible = false; From bbdcb93d256a4428af7a3fbdab2df9575ef97dfc Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Tue, 26 Mar 2024 21:18:06 +0100 Subject: [PATCH 07/16] blah --- examples/controls/CMakeLists.txt | 1 + examples/controls/transform.cpp | 43 +++++ .../threepp/controls/TransformControls.hpp | 8 +- src/threepp/controls/TransformControls.cpp | 166 ++++++++++++++++-- 4 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 examples/controls/transform.cpp diff --git a/examples/controls/CMakeLists.txt b/examples/controls/CMakeLists.txt index 14a125b2..a43e1f07 100644 --- a/examples/controls/CMakeLists.txt +++ b/examples/controls/CMakeLists.txt @@ -1,5 +1,6 @@ add_example(NAME "drag" WEB) +add_example(NAME "transform" WEB) add_example(NAME "fly" WEB WEB_EMBED "../data/textures/planets@data/textures/planets" ) diff --git a/examples/controls/transform.cpp b/examples/controls/transform.cpp new file mode 100644 index 00000000..39288f91 --- /dev/null +++ b/examples/controls/transform.cpp @@ -0,0 +1,43 @@ + +#include "threepp/threepp.hpp" + +using namespace threepp; + +int main() { + + Canvas canvas("Transform controls"); + GLRenderer renderer(canvas.size()); + renderer.shadowMap().enabled = true; + renderer.shadowMap().type = threepp::ShadowMap::PFC; + + PerspectiveCamera camera(60, canvas.aspect()); + camera.position.z = 25; + + Scene scene; + scene.background = Color(0xf0f0f0); + + scene.add(AmbientLight::create(0xaaaaaa)); + + auto light = SpotLight::create(0xffffff, 1.f); + light->position.set(0, 25, 50); + light->angle = math::PI / 9; + + light->castShadow = true; + light->shadow->camera->near = 10; + light->shadow->camera->far = 100; + light->shadow->mapSize.x = 1024; + light->shadow->mapSize.y = 1024; + + scene.add(light); + + canvas.onWindowResize([&](WindowSize size) { + camera.aspect = size.aspect(); + camera.updateProjectionMatrix(); + + renderer.setSize(size); + }); + + canvas.animate([&] { + renderer.render(scene, camera); + }); +} diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index 70c7eaee..90d3f724 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -23,14 +23,12 @@ namespace threepp { bool showY = true; bool showZ = true; - Vector3 worldPosition; - Quaternion worldQuaternion; - Vector3 cameraPosition; - Quaternion cameraQuaternion; - Vector3 eye; + TransformControls(Camera& camera, PeripheralsEventSource& canvas); + void updateMatrixWorld(bool force) override; + ~TransformControls(); private: diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 9ae505c1..7bedc98c 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -17,6 +17,7 @@ #include "threepp/input/PeripheralsEventSource.hpp" #include +#include using namespace threepp; @@ -28,6 +29,7 @@ namespace { Vector2 _tempVector2; Quaternion _tempQuaternion; + std::shared_ptr CircleGeometry(float radius, int arc) { auto geometry = BufferGeometry::create(); @@ -188,32 +190,145 @@ struct TransformControls::Impl { Object3D* object = nullptr; + Vector3 worldPosition; + Quaternion worldQuaternion; + Vector3 cameraPosition; + Quaternion cameraQuaternion; + Vector3 eye; + + +// template +// struct Property { +// +// Property(Impl& scope, const std::string& propName, T defaultValue) +// : scope(scope), defaultValue(defaultValue), propName(propName) {} +// +// operator T() const { +// +// return propValue.value_or(defaultValue); +// } +// +// void operator[](T value) { +// +// if (propValue != value) { +// +// propValue = value; +// } +// +// } +// +// private: +// Impl& scope; +// T defaultValue; +// std::string propName; +// std::optional propValue; +// +// }; + Impl(TransformControls& scope, Camera& camera, PeripheralsEventSource& canvas) : scope(scope), camera(camera), canvas(canvas), _gizmo(std::make_shared()), _plane(std::make_shared()) { this->camera.updateMatrixWorld(); - this->camera.matrixWorld->decompose(scope.cameraPosition, scope.cameraQuaternion, _cameraScale); + this->camera.matrixWorld->decompose(this->cameraPosition, this->cameraQuaternion, this->_cameraScale); - scope.eye.copy(scope.cameraPosition).sub(scope.worldPosition).normalize(); + this->eye.copy(this->cameraPosition).sub(this->worldPosition).normalize(); } + static std::optional intersectObjectWithRay( Object3D& object, Raycaster& raycaster, bool includeInvisible ) { -private: - void updateMatrixWorld() { + const auto allIntersections = raycaster.intersectObject( object, true ); - if (object) { + for (const auto & allIntersection : allIntersections) { - object->updateMatrixWorld(); + if ( allIntersection.object->visible || includeInvisible ) { - object->parent->matrixWorld->decompose(_parentPosition, _parentQuaternion, _parentScale); - object->matrixWorld->decompose(scope.worldPosition, scope.worldQuaternion, _worldScale); + return allIntersection; + + } - _parentQuaternionInv.copy(_parentQuaternion).invert(); - _worldQuaternionInv.copy(scope.worldQuaternion).invert(); } + + return std::nullopt; + } + + void pointerHover( Vector2 pointer ) { + + if ( !this->object || scope.dragging ) return; + + _raycaster.setFromCamera( pointer, this->camera ); + + const auto intersect = intersectObjectWithRay( this->_gizmo.picker[ scope.mode ], _raycaster ); + + if ( intersect ) { + + this->axis = intersect.object.name; + + } else { + + this->axis = nullptr; + + } + + } + + void pointerDown(int button, Vector2 pointer ) { + + if ( !this->object || scope.dragging || button != 0 ) return; + + if ( this->axis != nullptr ) { + + _raycaster.setFromCamera( pointer, this->camera ); + + const auto planeIntersect = intersectObjectWithRay( *this->_plane, _raycaster, true ); + + if ( planeIntersect ) { + + auto space = scope.space; + + if ( scope.mode == "scale" ) { + + space = "local"; + + } else if ( this->axis == "E" || this->axis == "XYZE" || this->axis == "XYZ" ) { + + space = "world"; + + } + + if ( space == "local" && scope.mode == "rotate" ) { + + const auto snap = scope.rotationSnap; + + if ( this->axis == "X" && snap ) this->object->rotation.x = std::round( this->object->rotation.x / snap ) * snap; + if ( this->axis == "Y" && snap ) this->object->rotation.y = std::round( this->object->rotation.y / snap ) * snap; + if ( this->axis == "Z" && snap ) this->object->rotation.z = std::round( this->object->rotation.z / snap ) * snap; + + } + + this->object->updateMatrixWorld(); + this->object->parent->updateMatrixWorld(); + + this->_positionStart.copy( this->object->position ); + this->_quaternionStart.copy( this->object->quaternion ); + this->_scaleStart.copy( this->object->scale ); + + this->object->matrixWorld->decompose( this->worldPositionStart, this->worldQuaternionStart, this._worldScaleStart ); + + this->pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + } + + scope.dragging = true; + _mouseDownEvent.mode = this.mode; + scope.dispatchEvent( _mouseDownEvent ); + + } + + } + }; TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& canvas): pimpl_(std::make_unique(*this, camera, canvas)) { @@ -226,5 +341,36 @@ TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& can Object3D::updateMatrixWorld(); } +void TransformControls::updateMatrixWorld(bool force) { + + if ( pimpl_->object ) { + + pimpl_->object->updateMatrixWorld(); + + if ( !pimpl_->object->parent ) { + + std::cerr << "TransformControls: The attached 3D object must be a part of the scene graph." << std::endl; + + } else { + + pimpl_->object->parent->matrixWorld->decompose( pimpl_->_parentPosition, pimpl_->_parentQuaternion, pimpl_->_parentScale ); + + } + + pimpl_->object->matrixWorld->decompose( pimpl_->worldPosition, pimpl_->worldQuaternion, pimpl_->_worldScale ); + + pimpl_->_parentQuaternionInv.copy( pimpl_->_parentQuaternion ).invert(); + pimpl_->_worldQuaternionInv.copy( pimpl_->worldQuaternion ).invert(); + + } + + pimpl_->camera.updateMatrixWorld(); + pimpl_->camera.matrixWorld->decompose( pimpl_->cameraPosition, pimpl_->cameraQuaternion, pimpl_->_cameraScale ); + + pimpl_->eye.copy( pimpl_->cameraPosition ).sub( pimpl_->worldPosition ).normalize(); + + Object3D::updateMatrixWorld(force); +} + TransformControls::~TransformControls() = default; From 3f074f4118328c4159c5d01b0644be5bc6909dc0 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 31 Mar 2024 13:57:22 +0200 Subject: [PATCH 08/16] update --- src/threepp/controls/TransformControls.cpp | 233 +++++++++++++++++++-- 1 file changed, 214 insertions(+), 19 deletions(-) diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index a9b25de6..ce8a741d 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -9,6 +9,8 @@ #include "threepp/materials/MeshBasicMaterial.hpp" #include "threepp/geometries/CylinderGeometry.hpp" +#include "threepp/geometries/SphereGeometry.hpp" +#include "threepp/geometries/TorusGeometry.hpp" #include "threepp/geometries/BoxGeometry.hpp" #include "threepp/geometries/PlaneGeometry.hpp" @@ -34,7 +36,7 @@ namespace { using GizmoMap = std::unordered_map, std::optional, std::optional, std::optional, std::optional>>>; - std::shared_ptr CircleGeometry(float radius, int arc) { + std::shared_ptr CircleGeometry(float radius, float arc) { auto geometry = BufferGeometry::create(); std::vector vertices; @@ -147,6 +149,8 @@ struct TransformControlsGizmo: Object3D { const auto lineGeometry = BufferGeometry::create(); lineGeometry->setAttribute("position", FloatBufferAttribute::create(std::vector{0, 0, 0, 1, 0, 0}, 3)); + // Gizmo definitions - custom hierarchy definitions for setupGizmo() function + // clang-format off GizmoMap gizmoTranslate { {"X", { @@ -166,8 +170,199 @@ struct TransformControlsGizmo: Object3D { }}, {"XYZ", { {Mesh::create(OctahedronGeometry::create(0.1, 0), matWhiteTransparent->clone()), Vector3{0,0,0}, Euler{0,0,0}, std::nullopt, std::nullopt} - }} + }}, + {"XY", { + {Mesh::create(PlaneGeometry::create(0.295, 0.295), matYellowTransparent->clone()), Vector3{0.15,0.15,0}, std::nullopt, std::nullopt, std::nullopt}, + {Line::create(lineGeometry, matLineYellow), Vector3{0.18, 0.3, 0}, std::nullopt, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineYellow), Vector3{0.18, 0.3, 0}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt} + }}, + {"YZ", { + {Mesh::create(PlaneGeometry::create(0.295, 0.295), matCyanTransparent->clone()), Vector3{0, 0.15,0.15}, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, + {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.18, 0.3}, std::nullopt, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.3, 0.18}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} + }}, + {"XZ", { + {Mesh::create(PlaneGeometry::create(0.295, 0.295), matMagentaTransparent->clone()), Vector3{0.15,0,0.15}, Euler{-math::PI/2, 0, 0}, std::nullopt, std::nullopt}, + {Line::create(lineGeometry, matLineMagenta), Vector3{0.18, 0, 0.3}, std::nullopt, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineMagenta), Vector3{0.3, 0, 0.18}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} + }} + }; + + GizmoMap pickerTranslate { + {"X", { + {Mesh::create(CylinderGeometry::create(0.2, 0, 1, 4, 1, false), matInvisible), Vector3{0.6, 0, 0}, Euler{0, 0, -math::PI/2}, std::nullopt, std::nullopt} + }}, + {"Y", { + {Mesh::create(CylinderGeometry::create(0.2, 0, 1, 4, 1, false), matInvisible), Vector3{0, 0.6, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"Z", { + {Mesh::create(CylinderGeometry::create(0.2, 0, 1, 4, 1, false), matInvisible), Vector3{0, 0, 0.6}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt} + }}, + {"XYZ", { + {Mesh::create(OctahedronGeometry::create(0.2, 0), matInvisible), std::nullopt, std::nullopt, std::nullopt, std::nullopt} + }}, + {"XY", { + {Mesh::create(PlaneGeometry::create(0.4, 0.4), matInvisible), Vector3{0.2, 0.2, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"YZ", { + {Mesh::create(PlaneGeometry::create(0.4, 0.4), matInvisible), Vector3{0, 0.2, 0.2}, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt} + }}, + {"XZ", { + {Mesh::create(PlaneGeometry::create(0.4, 0.4), matInvisible), Vector3{0.2, 0, 0.2}, Euler{-math::PI/2, 0, 0}, std::nullopt, std::nullopt} + }} }; + + GizmoMap helperTranslate { + {"START", { + {Mesh::create(OctahedronGeometry::create(0.01, 2), matHelper), std::nullopt, std::nullopt, std::nullopt, "helper"} + }}, + {"END", { + {Mesh::create(OctahedronGeometry::create(0.01, 2), matHelper), std::nullopt, std::nullopt, std::nullopt, "helper"} + }}, + {"DELTA", { + {Line::create(TranslateHelperGeometry(), matHelper), std::nullopt, std::nullopt, std::nullopt, "helper"} + }}, + {"X", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{-1e3, 0, 0}, std::nullopt, Vector3{1e6, 1, 1}, "helper"} + }}, + {"Y", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{0, -1e3, 0}, Euler{0, 0, math::PI/2}, Vector3{1e6, 1, 1}, "helper"} + }}, + {"Z", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{0, 0, -1e3}, Euler{0, -math::PI/2, 0}, Vector3{1e6, 1, 1}, "helper"} + }} + }; + + GizmoMap gizmoRotate { + {"X", { + {Line::create(CircleGeometry(1, 0.5), matLineRed), std::nullopt, std::nullopt, std::nullopt, std::nullopt}, + {Mesh::create(OctahedronGeometry::create(0.04, 0), matRed), Vector3{0, 0, 0.99}, std::nullopt, Vector3{1, 3, 1}, std::nullopt} + }}, + {"Y", { + {Line::create(CircleGeometry(1, 0.5), matLineGreen), std::nullopt, Euler{0, 0, -math::PI/2}, std::nullopt, std::nullopt}, + {Mesh::create(OctahedronGeometry::create(0.04, 0), matGreen), Vector3{0, 0, 0.99}, std::nullopt, Vector3{3, 1, 1}, std::nullopt} + }}, + {"Z", { + {Line::create(TranslateHelperGeometry(), matLineBlue), std::nullopt, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, + {Mesh::create(OctahedronGeometry::create(0.04, 0), matBlue), Vector3{0.99, 0, 0}, std::nullopt, Vector3{1, 3, 1}, std::nullopt}, + }}, + {"E", { + {Line::create(CircleGeometry(1.25, 1), matLineYellowTransparent), std::nullopt, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, + {Mesh::create(CylinderGeometry::create(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), Vector3{1.17, 0, 0}, Euler{0, 0, -math::PI/2}, Vector3{1, 1, 0.001}, std::nullopt}, + {Mesh::create(CylinderGeometry::create(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), Vector3{-1.17, 0, 0}, Euler{0, 0, math::PI/2}, Vector3{1, 1, 0.001}, std::nullopt}, + {Mesh::create(CylinderGeometry::create(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), Vector3{0, -1.17, 0}, Euler{math::PI, 0, 0}, Vector3{1, 1, 0.001}, std::nullopt}, + {Mesh::create(CylinderGeometry::create(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent), Vector3{0, 1.17, 0}, Euler{0, 0, 0}, Vector3{1, 1, 0.001}, std::nullopt}, + }}, + {"XYZE", { + {Line::create(CircleGeometry(1, 1), matLineGray), std::nullopt, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt} + }} + }; + + GizmoMap helperRotate { + {"AXIS", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{-1e3, 0, 0}, std::nullopt, Vector3{1e6, 1, 1}, std::nullopt} + }} + }; + + GizmoMap pickerRotate { + {"X", { + {Mesh::create(TorusGeometry::create(1, 0.1, 4, 24), matInvisible), Vector3{0, 0, 0}, Euler{0, -math::PI/2, -math::PI/2}, std::nullopt, std::nullopt} + }}, + {"Y", { + {Mesh::create(TorusGeometry::create(1, 0.1, 4, 24), matInvisible), Vector3{0, 0, 0}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt} + }}, + {"Z", { + {Mesh::create(TorusGeometry::create(1, 0.1, 4, 24), matInvisible), Vector3{0, 0, 0}, Euler{0, 0, -math::PI/2}, std::nullopt, std::nullopt}, + }}, + {"E", { + {Mesh::create(TorusGeometry::create(1.25, 0.1, 2, 24), matInvisible), std::nullopt, std::nullopt, std::nullopt, std::nullopt}, + }}, + {"XYZE", { + {Mesh::create(SphereGeometry::create(0.7, 10, 8), matInvisible), std::nullopt, std::nullopt, std::nullopt, std::nullopt} + }} + }; + + GizmoMap gizmoScale { + {"X", { + {Mesh::create(scaleHandleGeometry, matRed), Vector3{0.8, 0, 0}, Euler{0, 0, -math::PI/2}, std::nullopt, std::nullopt}, + {Line::create(lineGeometry, matLineRed), std::nullopt, std::nullopt, Vector3{0.8, 1, 1}, std::nullopt} + }}, + {"Y", { + {Mesh::create(scaleHandleGeometry, matGreen), Vector3{0, 0.6, 0}, std::nullopt, std::nullopt, std::nullopt}, + {Line::create(lineGeometry, matLineGreen), std::nullopt, Euler{0, 0, math::PI/2}, Vector3{0.8, 1, 1}, std::nullopt} + }}, + {"Z", { + {Mesh::create(scaleHandleGeometry, matBlue), Vector3{0, 0, 0.6}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt}, + {Line::create(lineGeometry, matLineGreen), std::nullopt, Euler{0, -math::PI/2, 0}, Vector3{0.8, 1, 1}, std::nullopt} + }}, + {"XY", { + {Mesh::create(scaleHandleGeometry, matYellowTransparent), Vector3{0.85, 0.85, 0}, std::nullopt, Vector3{2, 2, 0.2}, std::nullopt}, + {Line::create(lineGeometry, matLineYellow), Vector3{0.855, 0.98, 0}, std::nullopt, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineYellow), Vector3{0.98, 0.855, 0}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt} + }}, + {"YZ", { + {Mesh::create(scaleHandleGeometry, matMagentaTransparent), Vector3{0, 0.85, 0.85}, std::nullopt, Vector3{0.2, 2, 2}, std::nullopt}, + {Line::create(lineGeometry, matLineMagenta), Vector3{0, 0.855, 0.98}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineMagenta), Vector3{0, 0.98, 0.855}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} + }}, + {"XZ", { + {Mesh::create(scaleHandleGeometry, matMagentaTransparent), Vector3{0.85, 0, 0.85}, std::nullopt, Vector3{2, 0.2, 2}, std::nullopt}, + {Line::create(lineGeometry, matLineMagenta), Vector3{0.855, 0, 0.98}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineMagenta), Vector3{0.98, 0, 0.855}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} + }}, + {"XYZX", { + {Mesh::create(BoxGeometry::create(0.125, 0.125, 0.125), matWhiteTransparent->clone()), Vector3{1.1, 0, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"XYZY", { + {Mesh::create(BoxGeometry::create(0.125, 0.125, 0.125), matWhiteTransparent->clone()), Vector3{0, 1.1, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"XYZZ", { + {Mesh::create(BoxGeometry::create(0.125, 0.125, 0.125), matWhiteTransparent->clone()), Vector3{0, 0, 1.1}, std::nullopt, std::nullopt, std::nullopt} + }} + }; + + GizmoMap pickerScale { + {"X", { + {Mesh::create(CylinderGeometry::create(0.2, 0, 0.8, 4, 1, false), matInvisible), Vector3{0.5, 0, 0}, Euler{0, 0, -math::PI/2}, std::nullopt, std::nullopt} + }}, + {"Y", { + {Mesh::create(CylinderGeometry::create(0.2, 0, 0.8, 4, 1, false), matInvisible), Vector3{0, 0.5, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"Z", { + {Mesh::create(CylinderGeometry::create(0.2, 0, 0.8, 4, 1, false), matInvisible), Vector3{0, 0, 0.5}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt} + }}, + {"XY", { + {Mesh::create(scaleHandleGeometry, matInvisible), Vector3{0.85, 0.85, 0}, std::nullopt, Vector3{3, 3, 0.2}, std::nullopt} + }}, + {"YZ", { + {Mesh::create(scaleHandleGeometry, matInvisible), Vector3{0, 0.85, 0.85}, std::nullopt, Vector3{0.2, 3, 3}, std::nullopt} + }}, + {"XZ", { + {Mesh::create(scaleHandleGeometry, matInvisible), Vector3{0.85, 0, 0.85}, std::nullopt, Vector3{3, 0.2, 3}, std::nullopt} + }}, + {"XYZX", { + {Mesh::create(BoxGeometry::create(0.2, 0.2, 0.2), matInvisible), Vector3{1.1, 0, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"XYZY", { + {Mesh::create(BoxGeometry::create(0.2, 0.2, 0.2), matInvisible), Vector3{0, 1.1, 0}, std::nullopt, std::nullopt, std::nullopt} + }}, + {"XYZZ", { + {Mesh::create(BoxGeometry::create(0.2, 0.2, 0.2), matInvisible), Vector3{0, 0, 1.1}, std::nullopt, std::nullopt, std::nullopt} + }} + }; + + GizmoMap helperScale { + {"X", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{-1e3, 0, 0}, std::nullopt, Vector3{1e6, 1, 1}, "helper"} + }}, + {"Y", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{0, -1e3, 0}, Euler{0, 0, math::PI/2}, Vector3{1e6, 1, 1}, "helper"} + }}, + {"Z", { + {Line::create(lineGeometry, matHelper->clone()), Vector3{0, 0, -1e3}, Euler{0, -math::PI/2, 0}, Vector3{1e6, 1, 1}, "helper"} + }} + }; + // clang-format on } }; @@ -185,36 +380,36 @@ struct TransformControlsPlane: Mesh { void updateMatrixWorld(bool force) override { - auto space = this.space; + auto space = this->space; - this.position.copy( this.worldPosition ); + this->position.copy( this->worldPosition ); - if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation + if ( this->mode == "scale" ) space = "local"; // scale always oriented to local rotation - _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); - _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); - _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v1.copy( _unitX ).applyQuaternion( space == "local" ? this->worldQuaternion : _identityQuaternion ); + _v2.copy( _unitY ).applyQuaternion( space == "local" ? this->worldQuaternion : _identityQuaternion ); + _v3.copy( _unitZ ).applyQuaternion( space == "local" ? this->worldQuaternion : _identityQuaternion ); // Align the plane for current transform mode, axis and space. _alignVector.copy( _v2 ); - switch ( this.mode ) { + switch ( this->mode ) { case 'translate': case 'scale': - switch ( this.axis ) { + switch ( this->axis ) { case 'X': - _alignVector.copy( this.eye ).cross( _v1 ); + _alignVector.copy( this->eye ).cross( _v1 ); _dirVector.copy( _v1 ).cross( _alignVector ); break; case 'Y': - _alignVector.copy( this.eye ).cross( _v2 ); + _alignVector.copy( this->eye ).cross( _v2 ); _dirVector.copy( _v2 ).cross( _alignVector ); break; case 'Z': - _alignVector.copy( this.eye ).cross( _v3 ); + _alignVector.copy( this->eye ).cross( _v3 ); _dirVector.copy( _v3 ).cross( _alignVector ); break; case 'XY': @@ -242,16 +437,16 @@ struct TransformControlsPlane: Mesh { } - if ( _dirVector.length() === 0 ) { + if ( _dirVector.length() == 0 ) { // If in rotate mode, make the plane parallel to camera - this.quaternion.copy( this.cameraQuaternion ); + this->quaternion.copy( this->cameraQuaternion ); } else { _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); - this.quaternion.setFromRotationMatrix( _tempMatrix ); + this->quaternion.setFromRotationMatrix( _tempMatrix ); } @@ -406,13 +601,13 @@ struct TransformControls::Impl { this->_quaternionStart.copy(this->object->quaternion); this->_scaleStart.copy(this->object->scale); - this->object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this._worldScaleStart); + this->object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this->_worldScaleStart); - this->pointStart.copy(planeIntersect.point).sub(this.worldPositionStart); + this->pointStart.copy(planeIntersect.point).sub(this->worldPositionStart); } scope.dragging = true; - _mouseDownEvent.mode = this.mode; + _mouseDownEvent.mode = this->mode; scope.dispatchEvent(_mouseDownEvent); } } From eb7f369289776bff2a816d2e6a11f793d059eecb Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 31 Mar 2024 20:09:41 +0200 Subject: [PATCH 09/16] update --- examples/controls/transform.cpp | 10 + .../threepp/controls/TransformControls.hpp | 3 + include/threepp/core/EventDispatcher.hpp | 2 + src/threepp/controls/TransformControls.cpp | 464 ++++++++++++++---- src/threepp/core/EventDispatcher.cpp | 17 +- 5 files changed, 380 insertions(+), 116 deletions(-) diff --git a/examples/controls/transform.cpp b/examples/controls/transform.cpp index 39288f91..8fdfcdf0 100644 --- a/examples/controls/transform.cpp +++ b/examples/controls/transform.cpp @@ -1,5 +1,6 @@ #include "threepp/threepp.hpp" +#include "threepp/controls/TransformControls.hpp" using namespace threepp; @@ -30,6 +31,15 @@ int main() { scene.add(light); + auto material = MeshBasicMaterial::create(); + auto object = Mesh::create(BoxGeometry::create(), material); + scene.add(object); + + TransformControls controls(camera, canvas); + controls.object = object.get(); + + scene.add(controls); + canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); camera.updateProjectionMatrix(); diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index 90d3f724..66fd07f0 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -23,7 +23,10 @@ namespace threepp { bool showY = true; bool showZ = true; + std::optional rotationSnap; + std::optional translationSnap; + Object3D* object = nullptr; TransformControls(Camera& camera, PeripheralsEventSource& canvas); diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index ab457b0b..9f95192f 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -33,6 +33,8 @@ namespace threepp { void removeEventListener(const std::string& type, const EventListener* listener); + void dispatchEvent(Event& event); + void dispatchEvent(const std::string& type, void* target = nullptr); virtual ~EventDispatcher() = default; diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index ce8a741d..5a80bce0 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -30,9 +30,26 @@ namespace { Raycaster _raycaster; - Vector3 _tmpVector; - Vector2 _tempVector2; + Vector3 _tempVector; + Vector3 _tempVector2; + + Quaternion _identityQuaternion; Quaternion _tempQuaternion; + Matrix4 _tempMatrix; + + std::unordered_map _unit{ + {"X", Vector3{1, 0, 0}}, + {"Y", Vector3{}}, + {"Z", Vector3{}}}; + + Vector3 _v1, _v2, _v3; + Vector3 _unitX = Vector3(1, 0, 0), _unitY = Vector3(0, 1, 0), _unitZ = Vector3(0, 0, 1); + Vector3 _dirVector, _alignVector; + + Event _changeEvent{"change"}; + Event _mouseDownEvent{"mouseDown"}; + Event _mouseUpEvent{"mouseUp"}; + Event _objectChangeEvent{"objectChange"}; using GizmoMap = std::unordered_map, std::optional, std::optional, std::optional, std::optional>>>; @@ -63,10 +80,38 @@ namespace { } + template + struct Property { + + Property(const std::string& propName, T defaultValue) + : defaultValue(defaultValue), propName(propName) {} + + operator T() const { + + return propValue.value_or(defaultValue); + } + + void operator[](T value) { + + if (propValue != value) { + + propValue = value; + } + } + + private: + T defaultValue; + std::string propName; + std::optional propValue; + }; + + }// namespace struct TransformControlsGizmo: Object3D { + std::unordered_map picker; + TransformControlsGizmo() { auto gizmoMaterial = MeshBasicMaterial::create(); @@ -369,6 +414,14 @@ struct TransformControlsGizmo: Object3D { struct TransformControlsPlane: Mesh { + std::string* mode = nullptr; + std::string* space = nullptr; + std::string* axis = nullptr; + Vector3* eye = nullptr; + Vector3* worldPosition = nullptr; + Quaternion* worldQuaternion = nullptr; + Quaternion* cameraQuaternion = nullptr; + TransformControlsPlane() : Mesh(PlaneGeometry::create(100000, 100000, 2, 2), MeshBasicMaterial::create({{"visible", false}, @@ -382,72 +435,56 @@ struct TransformControlsPlane: Mesh { auto space = this->space; - this->position.copy( this->worldPosition ); + this->position.copy(*this->worldPosition); - if ( this->mode == "scale" ) space = "local"; // scale always oriented to local rotation + if (*this->mode == "scale") *space = "local";// scale always oriented to local rotation - _v1.copy( _unitX ).applyQuaternion( space == "local" ? this->worldQuaternion : _identityQuaternion ); - _v2.copy( _unitY ).applyQuaternion( space == "local" ? this->worldQuaternion : _identityQuaternion ); - _v3.copy( _unitZ ).applyQuaternion( space == "local" ? this->worldQuaternion : _identityQuaternion ); + _v1.copy(_unitX).applyQuaternion(*space == "local" ? *this->worldQuaternion : _identityQuaternion); + _v2.copy(_unitY).applyQuaternion(*space == "local" ? *this->worldQuaternion : _identityQuaternion); + _v3.copy(_unitZ).applyQuaternion(*space == "local" ? *this->worldQuaternion : _identityQuaternion); // Align the plane for current transform mode, axis and space. - _alignVector.copy( _v2 ); - - switch ( this->mode ) { - - case 'translate': - case 'scale': - switch ( this->axis ) { - - case 'X': - _alignVector.copy( this->eye ).cross( _v1 ); - _dirVector.copy( _v1 ).cross( _alignVector ); - break; - case 'Y': - _alignVector.copy( this->eye ).cross( _v2 ); - _dirVector.copy( _v2 ).cross( _alignVector ); - break; - case 'Z': - _alignVector.copy( this->eye ).cross( _v3 ); - _dirVector.copy( _v3 ).cross( _alignVector ); - break; - case 'XY': - _dirVector.copy( _v3 ); - break; - case 'YZ': - _dirVector.copy( _v1 ); - break; - case 'XZ': - _alignVector.copy( _v3 ); - _dirVector.copy( _v2 ); - break; - case 'XYZ': - case 'E': - _dirVector.set( 0, 0, 0 ); - break; - - } - - break; - case 'rotate': - default: - // special case for rotate - _dirVector.set( 0, 0, 0 ); + _alignVector.copy(_v2); + + if (*this->mode == "translate" || *this->mode == "scale") { + + if (*this->axis == "X") { + + _alignVector.copy(*this->eye).cross(_v1); + _dirVector.copy(_v1).cross(_alignVector); + } else if (*this->axis == "Y") { + _alignVector.copy(*this->eye).cross(_v2); + _dirVector.copy(_v2).cross(_alignVector); + } else if (*this->axis == "Z") { + _alignVector.copy(*this->eye).cross(_v3); + _dirVector.copy(_v3).cross(_alignVector); + } else if (*this->axis == "XY") { + _dirVector.copy(_v3); + } else if (*this->axis == "YZ") { + _dirVector.copy(_v1); + } else if (*this->axis == "XZ") { + _alignVector.copy(_v3); + _dirVector.copy(_v2); + } else if (*this->axis == "XYZ" || *this->axis == "E") { + + _dirVector.set(0, 0, 0); + } + } else { + _dirVector.set(0, 0, 0); } - if ( _dirVector.length() == 0 ) { + if (_dirVector.length() == 0) { // If in rotate mode, make the plane parallel to camera - this->quaternion.copy( this->cameraQuaternion ); + this->quaternion.copy(*this->cameraQuaternion); } else { - _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); - - this->quaternion.setFromRotationMatrix( _tempMatrix ); + _tempMatrix.lookAt(_tempVector.set(0, 0, 0), _dirVector, _alignVector); + this->quaternion.setFromRotationMatrix(_tempMatrix); } Object3D::updateMatrixWorld(force); @@ -474,6 +511,20 @@ struct TransformControls::Impl { Quaternion _quaternionStart; Vector3 _scaleStart; + Vector3 pointStart; + Vector3 pointEnd; + Vector3 worldPositionStart; + Quaternion worldQuaternionStart; + + bool dragging{false}; + + Vector3 rotationAxis; + + float rotationAngle; + + // std::string mode; + std::optional axis; + std::shared_ptr _gizmo; std::shared_ptr _plane; @@ -481,43 +532,12 @@ struct TransformControls::Impl { PeripheralsEventSource& canvas; Camera& camera; - Object3D* object = nullptr; - Vector3 worldPosition; Quaternion worldQuaternion; Vector3 cameraPosition; Quaternion cameraQuaternion; Vector3 eye; - - // template - // struct Property { - // - // Property(Impl& scope, const std::string& propName, T defaultValue) - // : scope(scope), defaultValue(defaultValue), propName(propName) {} - // - // operator T() const { - // - // return propValue.value_or(defaultValue); - // } - // - // void operator[](T value) { - // - // if (propValue != value) { - // - // propValue = value; - // } - // - // } - // - // private: - // Impl& scope; - // T defaultValue; - // std::string propName; - // std::optional propValue; - // - // }; - Impl(TransformControls& scope, Camera& camera, PeripheralsEventSource& canvas) : scope(scope), camera(camera), canvas(canvas), _gizmo(std::make_shared()), @@ -529,7 +549,7 @@ struct TransformControls::Impl { this->eye.copy(this->cameraPosition).sub(this->worldPosition).normalize(); } - static std::optional intersectObjectWithRay(Object3D& object, Raycaster& raycaster, bool includeInvisible) { + static std::optional intersectObjectWithRay(Object3D& object, Raycaster& raycaster, bool includeInvisible = false) { const auto allIntersections = raycaster.intersectObject(object, true); @@ -546,15 +566,15 @@ struct TransformControls::Impl { void pointerHover(Vector2 pointer) { - if (!this->object || scope.dragging) return; + if (!this->scope.object || scope.dragging) return; _raycaster.setFromCamera(pointer, this->camera); - const auto intersect = intersectObjectWithRay(this->_gizmo.picker[scope.mode], _raycaster); + const auto intersect = intersectObjectWithRay(*this->_gizmo->picker[scope.mode], _raycaster); if (intersect) { - this->axis = intersect.object.name; + this->axis = intersect->object->name; } else { @@ -564,9 +584,9 @@ struct TransformControls::Impl { void pointerDown(int button, Vector2 pointer) { - if (!this->object || scope.dragging || button != 0) return; + if (!this->scope.object || scope.dragging || button != 0) return; - if (this->axis != nullptr) { + if (this->axis) { _raycaster.setFromCamera(pointer, this->camera); @@ -587,31 +607,257 @@ struct TransformControls::Impl { if (space == "local" && scope.mode == "rotate") { - const auto snap = scope.rotationSnap; - - if (this->axis == "X" && snap) this->object->rotation.x = std::round(this->object->rotation.x / snap) * snap; - if (this->axis == "Y" && snap) this->object->rotation.y = std::round(this->object->rotation.y / snap) * snap; - if (this->axis == "Z" && snap) this->object->rotation.z = std::round(this->object->rotation.z / snap) * snap; + // const auto snap = scope.rotationSnap; + // + // if (this->axis == "X" && snap) this->object->rotation.x = std::round(this->object->rotation.x / snap) * snap; + // if (this->axis == "Y" && snap) this->object->rotation.y = std::round(this->object->rotation.y / snap) * snap; + // if (this->axis == "Z" && snap) this->object->rotation.z = std::round(this->object->rotation.z / snap) * snap; } - this->object->updateMatrixWorld(); - this->object->parent->updateMatrixWorld(); + this->scope.object->updateMatrixWorld(); + this->scope.object->parent->updateMatrixWorld(); - this->_positionStart.copy(this->object->position); - this->_quaternionStart.copy(this->object->quaternion); - this->_scaleStart.copy(this->object->scale); + this->_positionStart.copy(this->scope.object->position); + this->_quaternionStart.copy(this->scope.object->quaternion); + this->_scaleStart.copy(this->scope.object->scale); - this->object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this->_worldScaleStart); + this->scope.object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this->_worldScaleStart); - this->pointStart.copy(planeIntersect.point).sub(this->worldPositionStart); + this->pointStart.copy(planeIntersect->point).sub(this->worldPositionStart); } scope.dragging = true; - _mouseDownEvent.mode = this->mode; + _mouseDownEvent.target = &this->scope.mode; scope.dispatchEvent(_mouseDownEvent); } } + void pointerMove(int button, Vector2 pointer) { + + const auto axis = this->axis; + const auto mode = this->scope.mode; + const auto object = this->scope.object; + auto space = this->scope.space; + + if (mode == "scale") { + + space = "local"; + + } else if (axis == "E" || axis == "XYZE" || axis == "XYZ") { + + space = "world"; + } + + if (!object || !axis || this->dragging == false || button != -1) return; + + _raycaster.setFromCamera(pointer, this->camera); + + const auto planeIntersect = intersectObjectWithRay(*this->_plane, _raycaster, true); + + if (!planeIntersect) return; + + this->pointEnd.copy(planeIntersect->point).sub(this->worldPositionStart); + + if (mode == "translate") { + + // Apply translate + + this->_offset.copy(this->pointEnd).sub(this->pointStart); + + if (space == "local" && axis != "XYZ") { + + this->_offset.applyQuaternion(this->_worldQuaternionInv); + } + + if (axis->find('X') == std::string::npos) this->_offset.x = 0; + if (axis->find('Y') == std::string::npos) this->_offset.y = 0; + if (axis->find('Z') == std::string::npos) this->_offset.z = 0; + + if (space == "local" && axis != "XYZ") { + + this->_offset.applyQuaternion(this->_quaternionStart).divide(this->_parentScale); + + } else { + + this->_offset.applyQuaternion(this->_parentQuaternionInv).divide(this->_parentScale); + } + + object->position.copy(this->_offset).add(this->_positionStart); + + // Apply translation snap + + if (this->scope.translationSnap) { + + if (space == "local") { + + object->position.applyQuaternion(_tempQuaternion.copy(this->_quaternionStart).invert()); + + if (axis->find('X') != std::string::npos) { + + object->position.x = std::round(object->position.x / *this->scope.translationSnap) * *this->scope.translationSnap; + } + + if (axis->find('Y') != std::string::npos) { + + object->position.y = std::round(object->position.y / *this->scope.translationSnap) * *this->scope.translationSnap; + } + + if (axis->find('Z') != std::string::npos) { + + object->position.z = std::round(object->position.z / *this->scope.translationSnap) * *this->scope.translationSnap; + } + + object->position.applyQuaternion(this->_quaternionStart); + } + + if (space == "world") { + + if (object->parent) { + + object->position.add(_tempVector.setFromMatrixPosition(*object->parent->matrixWorld)); + } + + if (axis->find('X') != std::string::npos) { + + object->position.x = std::round(object->position.x / *this->scope.translationSnap) * *this->scope.translationSnap; + } + + if (axis->find('Y') != std::string::npos) { + + object->position.y = std::round(object->position.y / *this->scope.translationSnap) * *this->scope.translationSnap; + } + + if (axis->find('Z') != std::string::npos) { + + object->position.z = std::round(object->position.z / *this->scope.translationSnap) * *this->scope.translationSnap; + } + + if (object->parent) { + + object->position.sub(_tempVector.setFromMatrixPosition(*object->parent->matrixWorld)); + } + } + } + + } else if (mode == "scale") { + + if (axis->find("XYZ") != std::string::npos) { + + auto d = this->pointEnd.length() / this->pointStart.length(); + + if (this->pointEnd.dot(this->pointStart) < 0) d *= -1; + + _tempVector2.set(d, d, d); + + } else { + + _tempVector.copy(this->pointStart); + _tempVector2.copy(this->pointEnd); + + _tempVector.applyQuaternion(this->_worldQuaternionInv); + _tempVector2.applyQuaternion(this->_worldQuaternionInv); + + _tempVector2.divide(_tempVector); + + if (axis->find('X') == std::string::npos) { + + _tempVector2.x = 1; + } + + if (axis->find('Y') == std::string::npos) { + + _tempVector2.y = 1; + } + + if (axis->find('Z') == std::string::npos) { + + _tempVector2.z = 1; + } + } + + // Apply scale + + object->scale.copy(this->_scaleStart).multiply(_tempVector2); + + // if ( this->scaleSnap ) { + // + // if ( axis.search( 'X' ) !== - 1 ) { + // + // object.scale.x = std::round( object.scale.x / this->scaleSnap ) * this->scaleSnap || this->scaleSnap; + // + // } + // + // if ( axis.search( 'Y' ) !== - 1 ) { + // + // object.scale.y = std::round( object.scale.y / this->scaleSnap ) * this->scaleSnap || this->scaleSnap; + // + // } + // + // if ( axis.search( 'Z' ) !== - 1 ) { + // + // object.scale.z = std::round( object.scale.z / this->scaleSnap ) * this->scaleSnap || this->scaleSnap; + // + // } + // + // } + + } else if (mode == "rotate") { + + this->_offset.copy(this->pointEnd).sub(this->pointStart); + + const auto ROTATION_SPEED = 20.f / this->worldPosition.distanceTo(_tempVector.setFromMatrixPosition(*this->camera.matrixWorld)); + + if (axis == "E") { + + this->rotationAxis.copy(this->eye); + this->rotationAngle = this->pointEnd.angleTo(this->pointStart); + + this->_startNorm.copy(this->pointStart).normalize(); + this->_endNorm.copy(this->pointEnd).normalize(); + + this->rotationAngle *= (this->_endNorm.cross(this->_startNorm).dot(this->eye) < 0 ? 1 : -1); + + } else if (axis == "XYZE") { + + this->rotationAxis.copy(this->_offset).cross(this->eye).normalize(); + this->rotationAngle = this->_offset.dot(_tempVector.copy(this->rotationAxis).cross(this->eye)) * ROTATION_SPEED; + + } else if (axis == "X" || axis == "Y" || axis == "Z") { + + this->rotationAxis.copy(_unit[*axis]); + + _tempVector.copy(_unit[*axis]); + + if (space == "local") { + + _tempVector.applyQuaternion(this->worldQuaternion); + } + + this->rotationAngle = this->_offset.dot(_tempVector.cross(this->eye).normalize()) * ROTATION_SPEED; + } + + // Apply rotation snap + + if (this->scope.rotationSnap) this->rotationAngle = std::round(this->rotationAngle / *this->scope.rotationSnap) * *this->scope.rotationSnap; + + // Apply rotate + if (space == "local" && axis != "E" && axis != "XYZE") { + + object->quaternion.copy(this->_quaternionStart); + object->quaternion.multiply(_tempQuaternion.setFromAxisAngle(this->rotationAxis, this->rotationAngle)).normalize(); + + } else { + + this->rotationAxis.applyQuaternion(this->_parentQuaternionInv); + object->quaternion.copy(_tempQuaternion.setFromAxisAngle(this->rotationAxis, this->rotationAngle)); + object->quaternion.multiply(this->_quaternionStart).normalize(); + } + } + +// this->dispatchEvent(_changeEvent); +// this->dispatchEvent(_objectChangeEvent); + } + std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { const auto gizmo = Object3D::create(); @@ -676,20 +922,20 @@ TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& can void TransformControls::updateMatrixWorld(bool force) { - if (pimpl_->object) { + if (object) { - pimpl_->object->updateMatrixWorld(); + object->updateMatrixWorld(); - if (!pimpl_->object->parent) { + if (!object->parent) { std::cerr << "TransformControls: The attached 3D object must be a part of the scene graph." << std::endl; } else { - pimpl_->object->parent->matrixWorld->decompose(pimpl_->_parentPosition, pimpl_->_parentQuaternion, pimpl_->_parentScale); + object->parent->matrixWorld->decompose(pimpl_->_parentPosition, pimpl_->_parentQuaternion, pimpl_->_parentScale); } - pimpl_->object->matrixWorld->decompose(pimpl_->worldPosition, pimpl_->worldQuaternion, pimpl_->_worldScale); + object->matrixWorld->decompose(pimpl_->worldPosition, pimpl_->worldQuaternion, pimpl_->_worldScale); pimpl_->_parentQuaternionInv.copy(pimpl_->_parentQuaternion).invert(); pimpl_->_worldQuaternionInv.copy(pimpl_->worldQuaternion).invert(); diff --git a/src/threepp/core/EventDispatcher.cpp b/src/threepp/core/EventDispatcher.cpp index 3b3f3ee0..40996be4 100644 --- a/src/threepp/core/EventDispatcher.cpp +++ b/src/threepp/core/EventDispatcher.cpp @@ -32,17 +32,20 @@ void EventDispatcher::removeEventListener(const std::string& type, const EventLi } } -void EventDispatcher::dispatchEvent(const std::string& type, void* target) { - - if (listeners_.count(type)) { +void EventDispatcher::dispatchEvent(Event& event) { + if (listeners_.count(event.type)) { - Event e{type, target}; - - auto listenersOfType = listeners_.at(type);//copy + auto listenersOfType = listeners_.at(event.type);//copy for (auto l : listenersOfType) { if (l) { - l->onEvent(e); + l->onEvent(event); } } } } + +void EventDispatcher::dispatchEvent(const std::string& type, void* target) { + + Event e{type, target}; + dispatchEvent(e); +} From db6469065531f1fc56e275bb4e0ac083650f2f8b Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 31 Mar 2024 21:16:13 +0200 Subject: [PATCH 10/16] update --- examples/controls/transform.cpp | 4 +- src/threepp/controls/TransformControls.cpp | 294 ++++++++++++--------- 2 files changed, 178 insertions(+), 120 deletions(-) diff --git a/examples/controls/transform.cpp b/examples/controls/transform.cpp index 8fdfcdf0..88c340f5 100644 --- a/examples/controls/transform.cpp +++ b/examples/controls/transform.cpp @@ -12,10 +12,10 @@ int main() { renderer.shadowMap().type = threepp::ShadowMap::PFC; PerspectiveCamera camera(60, canvas.aspect()); - camera.position.z = 25; + camera.position.z = 10; Scene scene; - scene.background = Color(0xf0f0f0); +// scene.background = Color(0xf0f0f0); scene.add(AmbientLight::create(0xaaaaaa)); diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 5a80bce0..72209f5d 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -80,37 +80,39 @@ namespace { } - template - struct Property { - - Property(const std::string& propName, T defaultValue) - : defaultValue(defaultValue), propName(propName) {} - - operator T() const { - - return propValue.value_or(defaultValue); - } - - void operator[](T value) { - - if (propValue != value) { - - propValue = value; - } - } - - private: - T defaultValue; - std::string propName; - std::optional propValue; - }; +// template +// struct Property { +// +// Property(const std::string& propName, T defaultValue) +// : defaultValue(defaultValue), propName(propName) {} +// +// operator T() const { +// +// return propValue.value_or(defaultValue); +// } +// +// void operator[](T value) { +// +// if (propValue != value) { +// +// propValue = value; +// } +// } +// +// private: +// T defaultValue; +// std::string propName; +// std::optional propValue; +// }; }// namespace struct TransformControlsGizmo: Object3D { + std::unordered_map gizmo; std::unordered_map picker; + std::unordered_map helper; TransformControlsGizmo() { @@ -409,21 +411,92 @@ struct TransformControlsGizmo: Object3D { }; // clang-format on + + auto translate = setupGizmo(gizmoTranslate); + this->gizmo["translate"] = translate.get(); + add(translate); + + auto rotate = setupGizmo(gizmoRotate); + this->gizmo["rotate"] = rotate.get(); + add(rotate); + + auto scale = setupGizmo(gizmoScale); + this->gizmo["scale"] = scale.get(); + add(scale); + +// this->picker["translate"]->visible = false; +// this->picker["rotate"]->visible = false; +// this->picker["scale"]->visible = false; + } + + std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { + + const auto gizmo = Object3D::create(); + + for (const auto& [name, value] : gizmoMap) { + + for (unsigned i = value.size(); i--;) { + + auto object = std::get<0>(value[i])->clone(); + const auto position = std::get<1>(value[i]); + const auto rotation = std::get<2>(value[i]); + const auto scale = std::get<3>(value[i]); + const auto tag = std::get<4>(value[i]); + + // name and tag properties are essential for picking and updating logic. + object->name = name; + object->userData["tag"] = tag; + + if (position) { + + object->position.copy(*position); + } + + if (rotation) { + + object->rotation.copy(*rotation); + } + + if (scale) { + + object->scale.copy(*scale); + } + + object->updateMatrix(); + + const auto tempGeometry = object->geometry()->clone(); + tempGeometry->applyMatrix4(*object->matrix); + object->setGeometry(tempGeometry); + object->renderOrder = std::numeric_limits::infinity(); + + object->position.set(0, 0, 0); + object->rotation.set(0, 0, 0); + object->scale.set(1, 1, 1); + + gizmo->add(object); + } + } + + return gizmo; } }; +struct State { + std::string mode; + std::string space; + std::string axis; + Vector3 eye; + Vector3 worldPosition; + Quaternion worldQuaternion; + Quaternion cameraQuaternion; +}; + struct TransformControlsPlane: Mesh { - std::string* mode = nullptr; - std::string* space = nullptr; - std::string* axis = nullptr; - Vector3* eye = nullptr; - Vector3* worldPosition = nullptr; - Quaternion* worldQuaternion = nullptr; - Quaternion* cameraQuaternion = nullptr; + State& state; - TransformControlsPlane() - : Mesh(PlaneGeometry::create(100000, 100000, 2, 2), + explicit TransformControlsPlane(State& state) + : state(state), Mesh(PlaneGeometry::create(100000, 100000, 2, 2), MeshBasicMaterial::create({{"visible", false}, {"wireframe", true}, {"side", Side::Double}, @@ -433,40 +506,40 @@ struct TransformControlsPlane: Mesh { void updateMatrixWorld(bool force) override { - auto space = this->space; + auto space = state.space; - this->position.copy(*this->worldPosition); + this->position.copy(state.worldPosition); - if (*this->mode == "scale") *space = "local";// scale always oriented to local rotation + if (state.mode == "scale") space = "local";// scale always oriented to local rotation - _v1.copy(_unitX).applyQuaternion(*space == "local" ? *this->worldQuaternion : _identityQuaternion); - _v2.copy(_unitY).applyQuaternion(*space == "local" ? *this->worldQuaternion : _identityQuaternion); - _v3.copy(_unitZ).applyQuaternion(*space == "local" ? *this->worldQuaternion : _identityQuaternion); + _v1.copy(_unitX).applyQuaternion(space == "local" ? state.worldQuaternion : _identityQuaternion); + _v2.copy(_unitY).applyQuaternion(space == "local" ? state.worldQuaternion : _identityQuaternion); + _v3.copy(_unitZ).applyQuaternion(space == "local" ? state.worldQuaternion : _identityQuaternion); // Align the plane for current transform mode, axis and space. _alignVector.copy(_v2); - if (*this->mode == "translate" || *this->mode == "scale") { + if (state.mode == "translate" || state.mode == "scale") { - if (*this->axis == "X") { + if (state.axis == "X") { - _alignVector.copy(*this->eye).cross(_v1); + _alignVector.copy(state.eye).cross(_v1); _dirVector.copy(_v1).cross(_alignVector); - } else if (*this->axis == "Y") { - _alignVector.copy(*this->eye).cross(_v2); + } else if (state.axis == "Y") { + _alignVector.copy(state.eye).cross(_v2); _dirVector.copy(_v2).cross(_alignVector); - } else if (*this->axis == "Z") { - _alignVector.copy(*this->eye).cross(_v3); + } else if (state.axis == "Z") { + _alignVector.copy(state.eye).cross(_v3); _dirVector.copy(_v3).cross(_alignVector); - } else if (*this->axis == "XY") { + } else if (state.axis == "XY") { _dirVector.copy(_v3); - } else if (*this->axis == "YZ") { + } else if (state.axis == "YZ") { _dirVector.copy(_v1); - } else if (*this->axis == "XZ") { + } else if (state.axis == "XZ") { _alignVector.copy(_v3); _dirVector.copy(_v2); - } else if (*this->axis == "XYZ" || *this->axis == "E") { + } else if (state.axis == "XYZ" || state.axis == "E") { _dirVector.set(0, 0, 0); } @@ -478,7 +551,7 @@ struct TransformControlsPlane: Mesh { if (_dirVector.length() == 0) { // If in rotate mode, make the plane parallel to camera - this->quaternion.copy(*this->cameraQuaternion); + this->quaternion.copy(state.cameraQuaternion); } else { @@ -491,6 +564,19 @@ struct TransformControlsPlane: Mesh { } }; +struct MyMouseListener: MouseListener { + + void onMouseDown(int button, const Vector2& pos) override { + MouseListener::onMouseDown(button, pos); + } + void onMouseUp(int button, const Vector2& pos) override { + MouseListener::onMouseUp(button, pos); + } + void onMouseMove(const Vector2& pos) override { + MouseListener::onMouseMove(pos); + } +}; + struct TransformControls::Impl { Vector3 _offset; @@ -520,9 +606,8 @@ struct TransformControls::Impl { Vector3 rotationAxis; - float rotationAngle; + float rotationAngle{}; - // std::string mode; std::optional axis; std::shared_ptr _gizmo; @@ -532,21 +617,28 @@ struct TransformControls::Impl { PeripheralsEventSource& canvas; Camera& camera; - Vector3 worldPosition; - Quaternion worldQuaternion; +// Vector3 worldPosition; +// Quaternion worldQuaternion; Vector3 cameraPosition; - Quaternion cameraQuaternion; - Vector3 eye; +// Quaternion cameraQuaternion; +// Vector3 eye; + + State state; + + MyMouseListener myMouseListener; Impl(TransformControls& scope, Camera& camera, PeripheralsEventSource& canvas) : scope(scope), camera(camera), canvas(canvas), _gizmo(std::make_shared()), - _plane(std::make_shared()) { + _plane(std::make_shared(state)) { this->camera.updateMatrixWorld(); - this->camera.matrixWorld->decompose(this->cameraPosition, this->cameraQuaternion, this->_cameraScale); + this->camera.matrixWorld->decompose(this->cameraPosition, state.cameraQuaternion, this->_cameraScale); + + state.eye.copy(this->cameraPosition).sub(state.worldPosition).normalize(); + - this->eye.copy(this->cameraPosition).sub(this->worldPosition).normalize(); + canvas.addMouseListener(myMouseListener); } static std::optional intersectObjectWithRay(Object3D& object, Raycaster& raycaster, bool includeInvisible = false) { @@ -805,22 +897,22 @@ struct TransformControls::Impl { this->_offset.copy(this->pointEnd).sub(this->pointStart); - const auto ROTATION_SPEED = 20.f / this->worldPosition.distanceTo(_tempVector.setFromMatrixPosition(*this->camera.matrixWorld)); + const auto ROTATION_SPEED = 20.f / this->state.worldPosition.distanceTo(_tempVector.setFromMatrixPosition(*this->camera.matrixWorld)); if (axis == "E") { - this->rotationAxis.copy(this->eye); + this->rotationAxis.copy(this->state.eye); this->rotationAngle = this->pointEnd.angleTo(this->pointStart); this->_startNorm.copy(this->pointStart).normalize(); this->_endNorm.copy(this->pointEnd).normalize(); - this->rotationAngle *= (this->_endNorm.cross(this->_startNorm).dot(this->eye) < 0 ? 1 : -1); + this->rotationAngle *= (this->_endNorm.cross(this->_startNorm).dot(state.eye) < 0 ? 1 : -1); } else if (axis == "XYZE") { - this->rotationAxis.copy(this->_offset).cross(this->eye).normalize(); - this->rotationAngle = this->_offset.dot(_tempVector.copy(this->rotationAxis).cross(this->eye)) * ROTATION_SPEED; + this->rotationAxis.copy(this->_offset).cross(state.eye).normalize(); + this->rotationAngle = this->_offset.dot(_tempVector.copy(this->rotationAxis).cross(state.eye)) * ROTATION_SPEED; } else if (axis == "X" || axis == "Y" || axis == "Z") { @@ -830,10 +922,10 @@ struct TransformControls::Impl { if (space == "local") { - _tempVector.applyQuaternion(this->worldQuaternion); + _tempVector.applyQuaternion(state.worldQuaternion); } - this->rotationAngle = this->_offset.dot(_tempVector.cross(this->eye).normalize()) * ROTATION_SPEED; + this->rotationAngle = this->_offset.dot(_tempVector.cross(state.eye).normalize()) * ROTATION_SPEED; } // Apply rotation snap @@ -854,70 +946,36 @@ struct TransformControls::Impl { } } -// this->dispatchEvent(_changeEvent); -// this->dispatchEvent(_objectChangeEvent); + this->scope.dispatchEvent(_changeEvent); + this->scope.dispatchEvent(_objectChangeEvent); } - std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { - - const auto gizmo = Object3D::create(); - - for (const auto& [name, value] : gizmoMap) { - - for (unsigned i = value.size(); i--;) { - - auto object = std::get<0>(value[i])->clone(); - const auto position = std::get<1>(value[i]); - const auto rotation = std::get<2>(value[i]); - const auto scale = std::get<3>(value[i]); - const auto tag = std::get<4>(value[i]); - - // name and tag properties are essential for picking and updating logic. - object->name = name; - object->userData["tag"] = tag; - - if (position) { - - object->position.copy(*position); - } - - if (rotation) { - - object->rotation.copy(*rotation); - } - - if (scale) { - - object->scale.copy(*scale); - } + void pointerUp(int button, Vector2 pointer) { - object->updateMatrix(); + if (button != 0) return; - const auto tempGeometry = object->geometry()->clone(); - tempGeometry->applyMatrix4(*object->matrix); - object->setGeometry(tempGeometry); - object->renderOrder = std::numeric_limits::infinity(); + if (this->dragging && this->axis) { - object->position.set(0, 0, 0); - object->rotation.set(0, 0, 0); - object->scale.set(1, 1, 1); + _mouseUpEvent.target = &this->scope.mode; + this->scope.dispatchEvent(_mouseUpEvent); - gizmo->add(object); - } } - return gizmo; + this->dragging = false; + this->axis = std::nullopt; } + }; -TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& canvas): pimpl_(std::make_unique(*this, camera, canvas)) { +TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& canvas) + : pimpl_(std::make_unique(*this, camera, canvas)) { this->visible = false; this->add(pimpl_->_gizmo); this->add(pimpl_->_plane); - Object3D::updateMatrixWorld(); +// Object3D::updateMatrixWorld(); } void TransformControls::updateMatrixWorld(bool force) { @@ -935,16 +993,16 @@ void TransformControls::updateMatrixWorld(bool force) { object->parent->matrixWorld->decompose(pimpl_->_parentPosition, pimpl_->_parentQuaternion, pimpl_->_parentScale); } - object->matrixWorld->decompose(pimpl_->worldPosition, pimpl_->worldQuaternion, pimpl_->_worldScale); + object->matrixWorld->decompose(pimpl_->state.worldPosition, pimpl_->state.worldQuaternion, pimpl_->_worldScale); pimpl_->_parentQuaternionInv.copy(pimpl_->_parentQuaternion).invert(); - pimpl_->_worldQuaternionInv.copy(pimpl_->worldQuaternion).invert(); + pimpl_->_worldQuaternionInv.copy(pimpl_->state.worldQuaternion).invert(); } pimpl_->camera.updateMatrixWorld(); - pimpl_->camera.matrixWorld->decompose(pimpl_->cameraPosition, pimpl_->cameraQuaternion, pimpl_->_cameraScale); + pimpl_->camera.matrixWorld->decompose(pimpl_->cameraPosition, pimpl_->state.cameraQuaternion, pimpl_->_cameraScale); - pimpl_->eye.copy(pimpl_->cameraPosition).sub(pimpl_->worldPosition).normalize(); + pimpl_->state.eye.copy(pimpl_->cameraPosition).sub(pimpl_->state.worldPosition).normalize(); Object3D::updateMatrixWorld(force); } From 02b20ec3b0d7594a27c7bce7ead6c8015110510c Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sun, 31 Mar 2024 23:32:17 +0200 Subject: [PATCH 11/16] update --- examples/controls/transform.cpp | 6 +- .../threepp/controls/TransformControls.hpp | 6 +- src/threepp/controls/TransformControls.cpp | 657 +++++++++++++++--- 3 files changed, 566 insertions(+), 103 deletions(-) diff --git a/examples/controls/transform.cpp b/examples/controls/transform.cpp index 88c340f5..95d5b3f3 100644 --- a/examples/controls/transform.cpp +++ b/examples/controls/transform.cpp @@ -32,14 +32,18 @@ int main() { scene.add(light); auto material = MeshBasicMaterial::create(); + material->transparent = true; + material->opacity = 0.7; auto object = Mesh::create(BoxGeometry::create(), material); scene.add(object); TransformControls controls(camera, canvas); - controls.object = object.get(); + controls.attach(*object); scene.add(controls); + OrbitControls orbitControls(camera, canvas); + canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); camera.updateProjectionMatrix(); diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index 66fd07f0..a22c6481 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -26,10 +26,12 @@ namespace threepp { std::optional rotationSnap; std::optional translationSnap; - Object3D* object = nullptr; - TransformControls(Camera& camera, PeripheralsEventSource& canvas); + TransformControls& attach(Object3D& object); + + TransformControls& detach(); + void updateMatrixWorld(bool force) override; ~TransformControls(); diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 72209f5d..e8704088 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -32,10 +32,16 @@ namespace { Vector3 _tempVector; Vector3 _tempVector2; + Vector3 _zeroVector; + + Euler _tempEuler; Quaternion _identityQuaternion; Quaternion _tempQuaternion; + Quaternion _tempQuaternion2; + Matrix4 _tempMatrix; + Matrix4 _lookAtMatrix; std::unordered_map _unit{ {"X", Vector3{1, 0, 0}}, @@ -79,40 +85,46 @@ namespace { return geometry; } - -// template -// struct Property { -// -// Property(const std::string& propName, T defaultValue) -// : defaultValue(defaultValue), propName(propName) {} -// -// operator T() const { -// -// return propValue.value_or(defaultValue); -// } -// -// void operator[](T value) { -// -// if (propValue != value) { -// -// propValue = value; -// } -// } -// -// private: -// T defaultValue; -// std::string propName; -// std::optional propValue; -// }; + struct GizmoObject: Object3D { + + std::optional _opacity; + std::optional _color; + }; + + + // template + // struct Property { + // + // Property(const std::string& propName, T defaultValue) + // : defaultValue(defaultValue), propName(propName) {} + // + // operator T() const { + // + // return propValue.value_or(defaultValue); + // } + // + // void operator[](T value) { + // + // if (propValue != value) { + // + // propValue = value; + // } + // } + // + // private: + // T defaultValue; + // std::string propName; + // std::optional propValue; + // }; }// namespace struct TransformControlsGizmo: Object3D { - std::unordered_map gizmo; - std::unordered_map picker; - std::unordered_map helper; + std::unordered_map gizmo; + std::unordered_map picker; + std::unordered_map helper; TransformControlsGizmo() { @@ -221,7 +233,7 @@ struct TransformControlsGizmo: Object3D { {"XY", { {Mesh::create(PlaneGeometry::create(0.295, 0.295), matYellowTransparent->clone()), Vector3{0.15,0.15,0}, std::nullopt, std::nullopt, std::nullopt}, {Line::create(lineGeometry, matLineYellow), Vector3{0.18, 0.3, 0}, std::nullopt, Vector3{0.125, 1, 1}, std::nullopt}, - {Line::create(lineGeometry, matLineYellow), Vector3{0.18, 0.3, 0}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt} + {Line::create(lineGeometry, matLineYellow), Vector3{0.3, 0.18, 0}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt} }}, {"YZ", { {Mesh::create(PlaneGeometry::create(0.295, 0.295), matCyanTransparent->clone()), Vector3{0, 0.15,0.15}, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, @@ -412,26 +424,56 @@ struct TransformControlsGizmo: Object3D { // clang-format on - auto translate = setupGizmo(gizmoTranslate); - this->gizmo["translate"] = translate.get(); - add(translate); + { + auto translate = setupGizmo(gizmoTranslate); + this->gizmo["translate"] = translate.get(); + add(translate); + + auto rotate = setupGizmo(gizmoRotate); + this->gizmo["rotate"] = rotate.get(); + add(rotate); - auto rotate = setupGizmo(gizmoRotate); - this->gizmo["rotate"] = rotate.get(); - add(rotate); + auto scale = setupGizmo(gizmoScale); + this->gizmo["scale"] = scale.get(); + add(scale); + } + + { + auto translate = setupGizmo(pickerTranslate); + this->picker["translate"] = translate.get(); + add(translate); - auto scale = setupGizmo(gizmoScale); - this->gizmo["scale"] = scale.get(); - add(scale); + auto rotate = setupGizmo(pickerRotate); + this->picker["rotate"] = rotate.get(); + add(rotate); + + auto scale = setupGizmo(pickerScale); + this->picker["scale"] = scale.get(); + add(scale); + } -// this->picker["translate"]->visible = false; -// this->picker["rotate"]->visible = false; -// this->picker["scale"]->visible = false; + { + auto translate = setupGizmo(helperTranslate); + this->helper["translate"] = translate.get(); + add(translate); + + auto rotate = setupGizmo(helperRotate); + this->helper["rotate"] = rotate.get(); + add(rotate); + + auto scale = setupGizmo(helperScale); + this->helper["scale"] = scale.get(); + add(scale); + } + + this->picker["translate"]->visible = false; + this->picker["rotate"]->visible = false; + this->picker["scale"]->visible = false; } - std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { + std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { - const auto gizmo = Object3D::create(); + const auto gizmo = std::make_shared(); for (const auto& [name, value] : gizmoMap) { @@ -479,6 +521,356 @@ struct TransformControlsGizmo: Object3D { return gizmo; } + + void updateMatrixWorld(bool force) override { + + const auto space = (this->mode == "scale") ? "local" : this->space;// scale always oriented to local rotation + + const auto quaternion = (space == "local") ? this->worldQuaternion : _identityQuaternion; + + // Show only gizmos for current transform mode + + this->gizmo["translate"]->visible = this->mode == "translate"; + this->gizmo["rotate"]->visible = this->mode == "rotate"; + this->gizmo["scale"]->visible = this->mode == "scale"; + + this->helper["translate"]->visible = this->mode == "translate"; + this->helper["rotate"]->visible = this->mode == "rotate"; + this->helper["scale"]->visible = this->mode == "scale"; + + + std::vector handles; + handles = handles.concat(this->picker[this->mode].children); + handles = handles.concat(this->gizmo[this->mode].children); + handles = handles.concat(this->helper[this->mode].children); + + for (unsigned i = 0; i < handles.size(); i++) { + + const auto handle = handles[i]; + + // hide aligned to camera + + handle->visible = true; + handle->rotation.set(0, 0, 0); + handle->position.copy(this->worldPosition); + + float factor; + + if (this->camera.isOrthographicCamera) { + + factor = (this->camera.top - this->camera.bottom) / this->camera.zoom; + + } else { + + factor = this->worldPosition.distanceTo(this->cameraPosition) * std::min(1.9 * std::tan(math::PI * this->camera.fov / 360) / this->camera.zoom, 7); + } + + handle->scale.set(1, 1, 1).multiplyScalar(factor * this->size / 7); + + // TODO: simplify helpers and consider decoupling from gizmo + + if (std::any_cast(handle->userData["tag"]) == "helper") { + + handle->visible = false; + + if (handle->name == "AXIS") { + + handle->position.copy(this->worldPositionStart); + handle->visible = !!this->axis; + + if (this->axis == "X") { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, 0)); + handle->quaternion.copy(quaternion).multiply(_tempQuaternion); + + if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye)) > 0.9) { + + handle->visible = false; + } + } + + if (this->axis == "Y") { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, math::PI / 2)); + handle->quaternion.copy(quaternion).multiply(_tempQuaternion); + + if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye)) > 0.9) { + + handle->visible = false; + } + } + + if (this->axis == "Z") { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, math::PI / 2, 0)); + handle->quaternion.copy(quaternion).multiply(_tempQuaternion); + + if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye)) > 0.9) { + + handle->visible = false; + } + } + + if (this->axis == "XYZE") { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, math::PI / 2, 0)); + _alignVector.copy(this->rotationAxis); + handle->quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(_zeroVector, _alignVector, _unitY)); + handle->quaternion.multiply(_tempQuaternion); + handle->visible = this->dragging; + } + + if (this->axis == "E") { + + handle->visible = false; + } + + + } else if (handle->name == "START") { + + handle->position.copy(this->worldPositionStart); + handle->visible = this->dragging; + + } else if (handle->name == "END") { + + handle->position.copy(this->worldPosition); + handle->visible = this->dragging; + + } else if (handle->name == "DELTA") { + + handle->position.copy(this->worldPositionStart); + handle->quaternion.copy(this->worldQuaternionStart); + _tempVector.set(1e-10, 1e-10, 1e-10).add(this->worldPositionStart).sub(this->worldPosition).multiplyScalar(-1); + _tempVector.applyQuaternion(this->worldQuaternionStart.clone().invert()); + handle->scale.copy(_tempVector); + handle->visible = this->dragging; + + } else { + + handle->quaternion.copy(quaternion); + + if (this->dragging) { + + handle->position.copy(this->worldPositionStart); + + } else { + + handle->position.copy(this->worldPosition); + } + + if (this->axis) { + + handle->visible = this->axis.search(handle->name) != std::string::npos; + } + } + + // If updating helper, skip rest of the loop + continue; + } + + // Align handles to current local or world rotation + + handle->quaternion.copy(quaternion); + + if (this->mode == "translate" || this->mode == "scale") { + + // Hide translate and scale axis facing the camera + + const auto AXIS_HIDE_TRESHOLD = 0.99; + const auto PLANE_HIDE_TRESHOLD = 0.2; + const auto AXIS_FLIP_TRESHOLD = 0.0; + + if (handle->name == "X" || handle->name == "XYZX") { + + if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye)) > AXIS_HIDE_TRESHOLD) { + + handle->scale.set(1e-10, 1e-10, 1e-10); + handle->visible = false; + } + } + + if (handle->name == "Y" || handle->name == "XYZY") { + + if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye)) > AXIS_HIDE_TRESHOLD) { + + handle->scale.set(1e-10, 1e-10, 1e-10); + handle->visible = false; + } + } + + if (handle->name == "Z" || handle->name == "XYZZ") { + + if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye)) > AXIS_HIDE_TRESHOLD) { + + handle->scale.set(1e-10, 1e-10, 1e-10); + handle->visible = false; + } + } + + if (handle->name == "XY") { + + if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye)) < PLANE_HIDE_TRESHOLD) { + + handle->scale.set(1e-10, 1e-10, 1e-10); + handle->visible = false; + } + } + + if (handle->name == "YZ") { + + if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye)) < PLANE_HIDE_TRESHOLD) { + + handle->scale.set(1e-10, 1e-10, 1e-10); + handle->visible = false; + } + } + + if (handle->name == "XZ") { + + if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye)) < PLANE_HIDE_TRESHOLD) { + + handle->scale.set(1e-10, 1e-10, 1e-10); + handle->visible = false; + } + } + + // Flip translate and scale axis ocluded behind another axis + + if (handle->name.find('X') != std::string::npos) { + + if (_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye) < AXIS_FLIP_TRESHOLD) { + + if (std::any_cast(handle->userData["tag"]) == "fwd") { + + handle->visible = false; + + } else { + + handle->scale.x *= -1; + } + + } else if (std::any_cast(handle->userData["tag"]) == "bwd") { + + handle->visible = false; + } + } + + if (handle->name.find('Y') != std::string::npos) { + + if (_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye) < AXIS_FLIP_TRESHOLD) { + + if (std::any_cast(handle->userData["tag"]) == "fwd") { + + handle->visible = false; + + } else { + + handle->scale.y *= -1; + } + + } else if (std::any_cast(handle->userData["tag"]) == "bwd") { + + handle->visible = false; + } + } + + if (handle->name.find('Z') != std::string::npos) { + + if (_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye) < AXIS_FLIP_TRESHOLD) { + + if (std::any_cast(handle->userData["tag"]) == "fwd") { + + handle->visible = false; + + } else { + + handle->scale.z *= -1; + } + + } else if (std::any_cast(handle->userData["tag"]) == "bwd") { + + handle->visible = false; + } + } + + } else if (this->mode == "rotate") { + + // Align handles to current local or world rotation + + _tempQuaternion2.copy(quaternion); + _alignVector.copy(this->eye).applyQuaternion(_tempQuaternion.copy(quaternion).invert()); + + if (handle->name.find('E') != std::string::npos) { + + handle->quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(this->eye, _zeroVector, _unitY)); + } + + if (handle->name == "X") { + + _tempQuaternion.setFromAxisAngle(_unitX, std::atan2(-_alignVector.y, _alignVector.z)); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle->quaternion.copy(_tempQuaternion); + } + + if (handle->name == "Y") { + + _tempQuaternion.setFromAxisAngle(_unitY, std::atan2(_alignVector.x, _alignVector.z)); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle->quaternion.copy(_tempQuaternion); + } + + if (handle->name == "Z") { + + _tempQuaternion.setFromAxisAngle(_unitZ, std::atan2(_alignVector.y, _alignVector.x)); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle->quaternion.copy(_tempQuaternion); + } + } + + // Hide disabled axes + handle->visible = handle->visible && (handle->name.find('X') == std::string::npos || this->showX); + handle->visible = handle->visible && (handle->name.find('Y') == std::string::npos || this->showY); + handle->visible = handle->visible && (handle->name.find('Z') == std::string::npos || this->showZ); + handle->visible = handle->visible && (handle->name.find('E') == std::string::npos || (this->showX && this->showY && this->showZ)); + + // highlight selected axis + + if (!handle->_opacity) handle->_opacity = handle->material()->opacity; + if (!handle->_color) handle->_color = handle->material()->as()->color; + + handle->material()->as()->color.copy(*handle->_color); + handle->material()->opacity = *handle->_opacity; + + if (!this->enabled) { + + handle->material()->opacity *= 0.5; + handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); + + } else if (this->axis) { + + if (handle->name == this->axis) { + + handle->material()->opacity = 1.0; + handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); + + } else if (this->axis.split( '' ).some(function(a) { + return handle->name == = a; + })) { + + handle->material()->opacity = 1.0; + handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); + + } else { + + handle->material()->opacity *= 0.25; + handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); + } + } + } + + + Object3D::updateMatrixWorld(force); + } }; struct State { @@ -497,12 +889,12 @@ struct TransformControlsPlane: Mesh { explicit TransformControlsPlane(State& state) : state(state), Mesh(PlaneGeometry::create(100000, 100000, 2, 2), - MeshBasicMaterial::create({{"visible", false}, - {"wireframe", true}, - {"side", Side::Double}, - {"transparent", true}, - {"opacity", 0.1f}, - {"toneMapped", false}})) {} + MeshBasicMaterial::create({{"visible", false}, + {"wireframe", true}, + {"side", Side::Double}, + {"transparent", true}, + {"opacity", 0.1f}, + {"toneMapped", false}})) {} void updateMatrixWorld(bool force) override { @@ -564,19 +956,6 @@ struct TransformControlsPlane: Mesh { } }; -struct MyMouseListener: MouseListener { - - void onMouseDown(int button, const Vector2& pos) override { - MouseListener::onMouseDown(button, pos); - } - void onMouseUp(int button, const Vector2& pos) override { - MouseListener::onMouseUp(button, pos); - } - void onMouseMove(const Vector2& pos) override { - MouseListener::onMouseMove(pos); - } -}; - struct TransformControls::Impl { Vector3 _offset; @@ -616,19 +995,74 @@ struct TransformControls::Impl { TransformControls& scope; PeripheralsEventSource& canvas; Camera& camera; + Object3D* object = nullptr; -// Vector3 worldPosition; -// Quaternion worldQuaternion; Vector3 cameraPosition; -// Quaternion cameraQuaternion; -// Vector3 eye; State state; + struct MyMouseListener: MouseListener { + + Impl& scope; + bool moveEnabled = false; + + explicit MyMouseListener(Impl& scope): scope(scope) {} + + void onMouseDown(int button, const Vector2& pos) override { + + if (!scope.scope.enabled) return; + + button_ = button; + moveEnabled = true; + + const auto rect = scope.canvas.size(); + + Vector2 _pos; + _pos.x = (pos.x - rect.width) / rect.width * 2 - 1; + _pos.y = -(pos.y - rect.height) / rect.height * 2 - 1; + + // scope.pointerHover(_pos); + scope.pointerDown(button, _pos); + } + + void onMouseMove(const Vector2& pos) override { + if (!moveEnabled) return; + if (!scope.scope.enabled) return; + + const auto rect = scope.canvas.size(); + + Vector2 _pos; + _pos.x = (pos.x - rect.width) / rect.width * 2 - 1; + _pos.y = -(pos.y - rect.height) / rect.height * 2 - 1; + + scope.pointerMove(button_, _pos); + } + + void onMouseUp(int button, const Vector2& pos) override { + if (!scope.scope.enabled) return; + + button_ = -1; + moveEnabled = false; + + const auto rect = scope.canvas.size(); + + Vector2 _pos; + _pos.x = (pos.x - rect.width) / rect.width * 2 - 1; + _pos.y = -(pos.y - rect.height) / rect.height * 2 - 1; + + scope.pointerUp(button, _pos); + } + + + private: + int button_; + }; + + MyMouseListener myMouseListener; Impl(TransformControls& scope, Camera& camera, PeripheralsEventSource& canvas) - : scope(scope), camera(camera), canvas(canvas), + : scope(scope), myMouseListener(*this), camera(camera), canvas(canvas), _gizmo(std::make_shared()), _plane(std::make_shared(state)) { @@ -637,7 +1071,6 @@ struct TransformControls::Impl { state.eye.copy(this->cameraPosition).sub(state.worldPosition).normalize(); - canvas.addMouseListener(myMouseListener); } @@ -656,27 +1089,27 @@ struct TransformControls::Impl { return std::nullopt; } - void pointerHover(Vector2 pointer) { - - if (!this->scope.object || scope.dragging) return; - - _raycaster.setFromCamera(pointer, this->camera); - - const auto intersect = intersectObjectWithRay(*this->_gizmo->picker[scope.mode], _raycaster); - - if (intersect) { - - this->axis = intersect->object->name; - - } else { - - this->axis = nullptr; - } - } + // void pointerHover(Vector2 pointer) { + // + // if (!this->scope.object || scope.dragging) return; + // + // _raycaster.setFromCamera(pointer, this->camera); + // + // const auto intersect = intersectObjectWithRay(*this->_gizmo->picker[scope.mode], _raycaster); + // + // if (intersect) { + // + // this->axis = intersect->object->name; + // + // } else { + // + // this->axis = std::nullopt; + // } + // } void pointerDown(int button, Vector2 pointer) { - if (!this->scope.object || scope.dragging || button != 0) return; + if (!this->object || scope.dragging || button != 0) return; if (this->axis) { @@ -706,14 +1139,14 @@ struct TransformControls::Impl { // if (this->axis == "Z" && snap) this->object->rotation.z = std::round(this->object->rotation.z / snap) * snap; } - this->scope.object->updateMatrixWorld(); - this->scope.object->parent->updateMatrixWorld(); + this->object->updateMatrixWorld(); + this->object->parent->updateMatrixWorld(); - this->_positionStart.copy(this->scope.object->position); - this->_quaternionStart.copy(this->scope.object->quaternion); - this->_scaleStart.copy(this->scope.object->scale); + this->_positionStart.copy(this->object->position); + this->_quaternionStart.copy(this->object->quaternion); + this->_scaleStart.copy(this->object->scale); - this->scope.object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this->_worldScaleStart); + this->object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this->_worldScaleStart); this->pointStart.copy(planeIntersect->point).sub(this->worldPositionStart); } @@ -728,7 +1161,7 @@ struct TransformControls::Impl { const auto axis = this->axis; const auto mode = this->scope.mode; - const auto object = this->scope.object; + const auto object = this->object; auto space = this->scope.space; if (mode == "scale") { @@ -958,13 +1391,24 @@ struct TransformControls::Impl { _mouseUpEvent.target = &this->scope.mode; this->scope.dispatchEvent(_mouseUpEvent); - } this->dragging = false; this->axis = std::nullopt; } + void attach(Object3D& object) { + + scope.visible = true; + this->object = &object; + } + + void detach() { + + this->object = nullptr; + scope.visible = false; + this->axis = std::nullopt; + } }; TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& canvas) @@ -975,25 +1419,25 @@ TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& can this->add(pimpl_->_gizmo); this->add(pimpl_->_plane); -// Object3D::updateMatrixWorld(); + // Object3D::updateMatrixWorld(); } void TransformControls::updateMatrixWorld(bool force) { - if (object) { + if (pimpl_->object) { - object->updateMatrixWorld(); + pimpl_->object->updateMatrixWorld(); - if (!object->parent) { + if (!pimpl_->object->parent) { std::cerr << "TransformControls: The attached 3D object must be a part of the scene graph." << std::endl; } else { - object->parent->matrixWorld->decompose(pimpl_->_parentPosition, pimpl_->_parentQuaternion, pimpl_->_parentScale); + pimpl_->object->parent->matrixWorld->decompose(pimpl_->_parentPosition, pimpl_->_parentQuaternion, pimpl_->_parentScale); } - object->matrixWorld->decompose(pimpl_->state.worldPosition, pimpl_->state.worldQuaternion, pimpl_->_worldScale); + pimpl_->object->matrixWorld->decompose(pimpl_->state.worldPosition, pimpl_->state.worldQuaternion, pimpl_->_worldScale); pimpl_->_parentQuaternionInv.copy(pimpl_->_parentQuaternion).invert(); pimpl_->_worldQuaternionInv.copy(pimpl_->state.worldQuaternion).invert(); @@ -1007,5 +1451,18 @@ void TransformControls::updateMatrixWorld(bool force) { Object3D::updateMatrixWorld(force); } +TransformControls& TransformControls::attach(Object3D& object) { + + pimpl_->attach(object); + + return *this; +} + +TransformControls& TransformControls::detach() { + + pimpl_->detach(); + + return *this; +} TransformControls::~TransformControls() = default; From 4994b1e20d6b3848eded5c393425ac188974bd02 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Fri, 20 Feb 2026 23:35:13 +0100 Subject: [PATCH 12/16] started working --- examples/controls/transform.cpp | 44 +- .../threepp/controls/TransformControls.hpp | 20 +- include/threepp/core/EventDispatcher.hpp | 2 - include/threepp/objects/Line.hpp | 2 +- include/threepp/objects/Mesh.hpp | 2 +- include/threepp/objects/Points.hpp | 2 +- src/threepp/controls/TransformControls.cpp | 577 +++++++++--------- src/threepp/materials/MeshBasicMaterial.cpp | 2 +- 8 files changed, 348 insertions(+), 303 deletions(-) diff --git a/examples/controls/transform.cpp b/examples/controls/transform.cpp index 95d5b3f3..720caee2 100644 --- a/examples/controls/transform.cpp +++ b/examples/controls/transform.cpp @@ -1,6 +1,6 @@ -#include "threepp/threepp.hpp" #include "threepp/controls/TransformControls.hpp" +#include "threepp/threepp.hpp" using namespace threepp; @@ -9,25 +9,25 @@ int main() { Canvas canvas("Transform controls"); GLRenderer renderer(canvas.size()); renderer.shadowMap().enabled = true; - renderer.shadowMap().type = threepp::ShadowMap::PFC; + renderer.shadowMap().type = ShadowMap::PFC; PerspectiveCamera camera(60, canvas.aspect()); camera.position.z = 10; Scene scene; -// scene.background = Color(0xf0f0f0); + scene.background = Color(0xf0f0f0); scene.add(AmbientLight::create(0xaaaaaa)); auto light = SpotLight::create(0xffffff, 1.f); light->position.set(0, 25, 50); light->angle = math::PI / 9; - - light->castShadow = true; - light->shadow->camera->near = 10; - light->shadow->camera->far = 100; - light->shadow->mapSize.x = 1024; - light->shadow->mapSize.y = 1024; + // + // light->castShadow = true; + // light->shadow->camera->as()->nearPlane = 10; + // light->shadow->camera->as()->farPlane = 100; + // light->shadow->mapSize.x = 1024; + // light->shadow->mapSize.y = 1024; scene.add(light); @@ -43,6 +43,32 @@ int main() { scene.add(controls); OrbitControls orbitControls(camera, canvas); + orbitControls.enabled = false; + + KeyAdapter adapter(KeyAdapter::Mode::KEY_PRESSED, [&](KeyEvent evt) { + switch (evt.key) { + case Key::Q: { + controls.setSpace(controls.getSpace() == "local" ? "world" : "local"); + break; + } + case Key::W: { + controls.setMode("translate"); + break; + } + case Key::E: { + controls.setMode("rotate"); + break; + } + case Key::R: { + controls.setMode("scale"); + break; + } + case Key::SPACE: { + orbitControls.enabled = !orbitControls.enabled; + } + } + }); + canvas.addKeyListener(adapter); canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); diff --git a/include/threepp/controls/TransformControls.hpp b/include/threepp/controls/TransformControls.hpp index a22c6481..89b77efc 100644 --- a/include/threepp/controls/TransformControls.hpp +++ b/include/threepp/controls/TransformControls.hpp @@ -1,3 +1,4 @@ +// https://github.com/mrdoob/three.js/blob/r129/examples/jsm/controls/TransformControls.js #ifndef THREEPP_TRANSFORMCONTROLS_HPP #define THREEPP_TRANSFORMCONTROLS_HPP @@ -14,27 +15,24 @@ namespace threepp { class TransformControls: public Object3D { public: - bool enabled = true; - std::string mode{"translate"}; - std::string space{"world"}; - float size = 1; - bool dragging = false; - bool showX = true; - bool showY = true; - bool showZ = true; - std::optional rotationSnap; - std::optional translationSnap; + bool enabled = true; TransformControls(Camera& camera, PeripheralsEventSource& canvas); + void setSpace(const std::string& space); + + [[nodiscard]] std::string getSpace() const; + + void setMode(const std::string& mode); + TransformControls& attach(Object3D& object); TransformControls& detach(); void updateMatrixWorld(bool force) override; - ~TransformControls(); + ~TransformControls() override; private: struct Impl; diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index 6db03348..1b5069ae 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -32,8 +32,6 @@ namespace threepp { void removeEventListener(const std::string& type, const EventListener& listener); - void dispatchEvent(Event& event); - void dispatchEvent(const std::string& type, void* target = nullptr); virtual ~EventDispatcher() = default; diff --git a/include/threepp/objects/Line.hpp b/include/threepp/objects/Line.hpp index 25811464..f651e7e5 100644 --- a/include/threepp/objects/Line.hpp +++ b/include/threepp/objects/Line.hpp @@ -22,7 +22,7 @@ namespace threepp { std::shared_ptr geometry() const override; - void setGeometry(const std::shared_ptr& geometry) override; + void setGeometry(const std::shared_ptr& geometry); virtual void computeLineDistances(); diff --git a/include/threepp/objects/Mesh.hpp b/include/threepp/objects/Mesh.hpp index 23bc9358..46e5d707 100644 --- a/include/threepp/objects/Mesh.hpp +++ b/include/threepp/objects/Mesh.hpp @@ -23,7 +23,7 @@ namespace threepp { [[nodiscard]] std::shared_ptr geometry() const override; - void setGeometry(const std::shared_ptr& geometry) override; + void setGeometry(const std::shared_ptr& geometry); void raycast(const Raycaster& raycaster, std::vector& intersects) override; diff --git a/include/threepp/objects/Points.hpp b/include/threepp/objects/Points.hpp index 5996eca9..255bab30 100644 --- a/include/threepp/objects/Points.hpp +++ b/include/threepp/objects/Points.hpp @@ -20,7 +20,7 @@ namespace threepp { std::shared_ptr geometry() const override; - void setGeometry(const std::shared_ptr& geometry) override; + void setGeometry(const std::shared_ptr& geometry); void raycast(const Raycaster& raycaster, std::vector& intersects) override; diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index e8704088..be1b369b 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -2,6 +2,8 @@ #include "threepp/controls/TransformControls.hpp" #include "threepp/cameras/Camera.hpp" +#include "threepp/cameras/OrthographicCamera.hpp" +#include "threepp/cameras/PerspectiveCamera.hpp" #include "threepp/objects/Line.hpp" #include "threepp/objects/Mesh.hpp" @@ -20,6 +22,7 @@ #include "threepp/geometries/OctahedronGeometry.hpp" #include "threepp/input/PeripheralsEventSource.hpp" +#include #include #include #include @@ -45,18 +48,13 @@ namespace { std::unordered_map _unit{ {"X", Vector3{1, 0, 0}}, - {"Y", Vector3{}}, - {"Z", Vector3{}}}; + {"Y", Vector3{0, 1, 0}}, + {"Z", Vector3{0, 0, 1}}}; Vector3 _v1, _v2, _v3; Vector3 _unitX = Vector3(1, 0, 0), _unitY = Vector3(0, 1, 0), _unitZ = Vector3(0, 0, 1); Vector3 _dirVector, _alignVector; - Event _changeEvent{"change"}; - Event _mouseDownEvent{"mouseDown"}; - Event _mouseUpEvent{"mouseUp"}; - Event _objectChangeEvent{"objectChange"}; - using GizmoMap = std::unordered_map, std::optional, std::optional, std::optional, std::optional>>>; std::shared_ptr CircleGeometry(float radius, float arc) { @@ -64,9 +62,9 @@ namespace { auto geometry = BufferGeometry::create(); std::vector vertices; - for (unsigned i = 0; i <= 64 * arc; ++i) { + for (auto i = 0; i <= 64 * arc; ++i) { - vertices.emplace_back(0); + vertices.emplace_back(0.f); vertices.emplace_back(std::cos(static_cast(i) / 32 * math::PI) * radius); vertices.emplace_back(std::sin(static_cast(i) / 32 * math::PI) * radius); } @@ -91,31 +89,37 @@ namespace { std::optional _color; }; + struct State { + + Vector3 eye; + Vector3 worldPosition; + Quaternion worldQuaternion; + Quaternion cameraQuaternion; + Vector3 cameraPosition; + + Vector3 worldPositionStart; + Quaternion worldQuaternionStart; + + Vector3 rotationAxis; + + bool& enabled; + std::string mode{"translate"}; + std::string space{"world"}; + std::optional axis; + float size{1.f}; + bool dragging{false}; + bool showX = true; + bool showY = true; + bool showZ = true; - // template - // struct Property { - // - // Property(const std::string& propName, T defaultValue) - // : defaultValue(defaultValue), propName(propName) {} - // - // operator T() const { - // - // return propValue.value_or(defaultValue); - // } - // - // void operator[](T value) { - // - // if (propValue != value) { - // - // propValue = value; - // } - // } - // - // private: - // T defaultValue; - // std::string propName; - // std::optional propValue; - // }; + std::optional rotationSnap; + std::optional translationSnap; + std::optional scaleSnap; + + Camera* camera; + + State(bool& enabled): enabled(enabled) {} + }; }// namespace @@ -126,7 +130,9 @@ struct TransformControlsGizmo: Object3D { std::unordered_map picker; std::unordered_map helper; - TransformControlsGizmo() { + State& state; + + explicit TransformControlsGizmo(State& state): state(state) { auto gizmoMaterial = MeshBasicMaterial::create(); gizmoMaterial->depthTest = false; @@ -151,50 +157,50 @@ struct TransformControlsGizmo: Object3D { const auto matHelper = gizmoMaterial->clone(); matHelper->opacity = 0.33f; - const auto matRed = gizmoMaterial->clone()->as_shared(); - matRed->color.setHex(0xff0000); + const auto matRed = gizmoMaterial->clone(); + matRed->as()->color.setHex(0xff0000); - const auto matGreen = gizmoMaterial->clone()->as_shared(); - matGreen->color.setHex(0x00ff00); + const auto matGreen = gizmoMaterial->clone(); + matGreen->as()->color.setHex(0x00ff00); - const auto matBlue = gizmoMaterial->clone()->as_shared(); - matBlue->color.setHex(0x0000ff); + const auto matBlue = gizmoMaterial->clone(); + matBlue->as()->color.setHex(0x0000ff); - const auto matWhiteTransparent = gizmoMaterial->clone()->as_shared(); + const auto matWhiteTransparent = gizmoMaterial->clone(); matWhiteTransparent->opacity = 0.25f; - const auto matYellowTransparent = matWhiteTransparent->clone()->as_shared(); - matYellowTransparent->color.setHex(0xffff00); + const auto matYellowTransparent = matWhiteTransparent->clone(); + matYellowTransparent->as()->color.setHex(0xffff00); - const auto matCyanTransparent = matWhiteTransparent->clone()->as_shared(); - matCyanTransparent->color.setHex(0x00ffff); + const auto matCyanTransparent = matWhiteTransparent->clone(); + matCyanTransparent->as()->color.setHex(0x00ffff); - const auto matMagentaTransparent = matWhiteTransparent->clone()->as_shared(); - matMagentaTransparent->color.setHex(0xff00ff); + const auto matMagentaTransparent = matWhiteTransparent->clone(); + matMagentaTransparent->as()->color.setHex(0xff00ff); - const auto matYellow = gizmoMaterial->clone()->as_shared(); - matYellow->color.setHex(0xffff00); + const auto matYellow = gizmoMaterial->clone(); + matYellow->as()->color.setHex(0xffff00); - const auto matLineRed = gizmoLineMaterial->clone()->as_shared(); - matLineRed->color.setHex(0xff0000); + const auto matLineRed = gizmoLineMaterial->clone(); + matLineRed->as()->color.setHex(0xff0000); - const auto matLineGreen = gizmoLineMaterial->clone()->as_shared(); - matLineGreen->color.setHex(0x00ff00); + const auto matLineGreen = gizmoLineMaterial->clone(); + matLineGreen->as()->color.setHex(0x00ff00); - const auto matLineBlue = gizmoLineMaterial->clone()->as_shared(); - matLineBlue->color.setHex(0x0000ff); + const auto matLineBlue = gizmoLineMaterial->clone(); + matLineBlue->as()->color.setHex(0x0000ff); - const auto matLineCyan = gizmoLineMaterial->clone()->as_shared(); - matLineCyan->color.setHex(0x00ffff); + const auto matLineCyan = gizmoLineMaterial->clone(); + matLineCyan->as()->color.setHex(0x00ffff); - const auto matLineMagenta = gizmoLineMaterial->clone()->as_shared(); - matLineMagenta->color.setHex(0xff00ff); + const auto matLineMagenta = gizmoLineMaterial->clone(); + matLineMagenta->as()->color.setHex(0xff00ff); - const auto matLineYellow = gizmoLineMaterial->clone()->as_shared(); - matLineYellow->color.setHex(0xffff00); + const auto matLineYellow = gizmoLineMaterial->clone(); + matLineYellow->as()->color.setHex(0xffff00); - const auto matLineGray = gizmoLineMaterial->clone()->as_shared(); - matLineGray->color.setHex(0x787878); + const auto matLineGray = gizmoLineMaterial->clone(); + matLineGray->as()->color.setHex(0x787878); const auto matLineYellowTransparent = matLineYellow->clone(); matLineYellowTransparent->opacity = 0.25f; @@ -237,7 +243,7 @@ struct TransformControlsGizmo: Object3D { }}, {"YZ", { {Mesh::create(PlaneGeometry::create(0.295, 0.295), matCyanTransparent->clone()), Vector3{0, 0.15,0.15}, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, - {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.18, 0.3}, std::nullopt, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.18, 0.3}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt}, {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.3, 0.18}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} }}, {"XZ", { @@ -302,7 +308,7 @@ struct TransformControlsGizmo: Object3D { {Mesh::create(OctahedronGeometry::create(0.04, 0), matGreen), Vector3{0, 0, 0.99}, std::nullopt, Vector3{3, 1, 1}, std::nullopt} }}, {"Z", { - {Line::create(TranslateHelperGeometry(), matLineBlue), std::nullopt, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, + {Line::create(CircleGeometry(1, 0.5), matLineBlue), std::nullopt, Euler{0, math::PI/2, 0}, std::nullopt, std::nullopt}, {Mesh::create(OctahedronGeometry::create(0.04, 0), matBlue), Vector3{0.99, 0, 0}, std::nullopt, Vector3{1, 3, 1}, std::nullopt}, }}, {"E", { @@ -319,7 +325,7 @@ struct TransformControlsGizmo: Object3D { GizmoMap helperRotate { {"AXIS", { - {Line::create(lineGeometry, matHelper->clone()), Vector3{-1e3, 0, 0}, std::nullopt, Vector3{1e6, 1, 1}, std::nullopt} + {Line::create(lineGeometry, matHelper->clone()), Vector3{-1e3, 0, 0}, std::nullopt, Vector3{1e6, 1, 1}, "helper"} }} }; @@ -487,7 +493,7 @@ struct TransformControlsGizmo: Object3D { // name and tag properties are essential for picking and updating logic. object->name = name; - object->userData["tag"] = tag; + if (tag) object->userData["tag"] = *tag; if (position) { @@ -508,7 +514,14 @@ struct TransformControlsGizmo: Object3D { const auto tempGeometry = object->geometry()->clone(); tempGeometry->applyMatrix4(*object->matrix); - object->setGeometry(tempGeometry); + if (auto mesh = object->as()) { + mesh->setGeometry(tempGeometry); + } else if (auto line = object->as()) { + line->setGeometry(tempGeometry); + } else { + throw std::runtime_error("GizmoObject::setupGizmo: invalid type"); + } + object->renderOrder = std::numeric_limits::infinity(); object->position.set(0, 0, 0); @@ -524,103 +537,109 @@ struct TransformControlsGizmo: Object3D { void updateMatrixWorld(bool force) override { - const auto space = (this->mode == "scale") ? "local" : this->space;// scale always oriented to local rotation + const auto space = (state.mode == "scale") ? "local" : state.space;// scale always oriented to local rotation - const auto quaternion = (space == "local") ? this->worldQuaternion : _identityQuaternion; + const auto quaternion = (space == "local") ? state.worldQuaternion : _identityQuaternion; // Show only gizmos for current transform mode - this->gizmo["translate"]->visible = this->mode == "translate"; - this->gizmo["rotate"]->visible = this->mode == "rotate"; - this->gizmo["scale"]->visible = this->mode == "scale"; + this->gizmo["translate"]->visible = state.mode == "translate"; + this->gizmo["rotate"]->visible = state.mode == "rotate"; + this->gizmo["scale"]->visible = state.mode == "scale"; - this->helper["translate"]->visible = this->mode == "translate"; - this->helper["rotate"]->visible = this->mode == "rotate"; - this->helper["scale"]->visible = this->mode == "scale"; + this->helper["translate"]->visible = state.mode == "translate"; + this->helper["rotate"]->visible = state.mode == "rotate"; + this->helper["scale"]->visible = state.mode == "scale"; - std::vector handles; - handles = handles.concat(this->picker[this->mode].children); - handles = handles.concat(this->gizmo[this->mode].children); - handles = handles.concat(this->helper[this->mode].children); + std::vector handles; + for (auto obj : this->picker[state.mode]->children) { + handles.emplace_back(obj); + } + for (auto obj : this->gizmo[state.mode]->children) { + handles.emplace_back(obj); + } + for (auto obj : this->helper[state.mode]->children) { + handles.emplace_back(obj); + } - for (unsigned i = 0; i < handles.size(); i++) { - const auto handle = handles[i]; + for (auto handle : handles) { // hide aligned to camera handle->visible = true; handle->rotation.set(0, 0, 0); - handle->position.copy(this->worldPosition); + handle->position.copy(state.worldPosition); float factor; - if (this->camera.isOrthographicCamera) { + if (auto orthoCam = this->state.camera->as()) { - factor = (this->camera.top - this->camera.bottom) / this->camera.zoom; + factor = (orthoCam->top - orthoCam->bottom) / orthoCam->zoom; } else { - factor = this->worldPosition.distanceTo(this->cameraPosition) * std::min(1.9 * std::tan(math::PI * this->camera.fov / 360) / this->camera.zoom, 7); + auto perspCam = this->state.camera->as(); + factor = state.worldPosition.distanceTo(this->state.cameraPosition) * std::min(1.9f * std::tan(math::PI * perspCam->fov / 360.f) / perspCam->zoom, 7.f); } - handle->scale.set(1, 1, 1).multiplyScalar(factor * this->size / 7); + handle->scale.set(1.f, 1.f, 1.f).multiplyScalar(factor * this->state.size / 7); // TODO: simplify helpers and consider decoupling from gizmo - if (std::any_cast(handle->userData["tag"]) == "helper") { + if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "helper") { handle->visible = false; if (handle->name == "AXIS") { - handle->position.copy(this->worldPositionStart); - handle->visible = !!this->axis; + handle->position.copy(this->state.worldPositionStart); + handle->visible = state.axis.has_value(); - if (this->axis == "X") { + if (state.axis == "X") { _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, 0)); handle->quaternion.copy(quaternion).multiply(_tempQuaternion); - if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye)) > 0.9) { + if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->state.eye)) > 0.9) { handle->visible = false; } } - if (this->axis == "Y") { + if (this->state.axis == "Y") { _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, math::PI / 2)); handle->quaternion.copy(quaternion).multiply(_tempQuaternion); - if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye)) > 0.9) { + if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->state.eye)) > 0.9) { handle->visible = false; } } - if (this->axis == "Z") { + if (this->state.axis == "Z") { _tempQuaternion.setFromEuler(_tempEuler.set(0, math::PI / 2, 0)); handle->quaternion.copy(quaternion).multiply(_tempQuaternion); - if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye)) > 0.9) { + if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->state.eye)) > 0.9f) { handle->visible = false; } } - if (this->axis == "XYZE") { + if (this->state.axis == "XYZE") { _tempQuaternion.setFromEuler(_tempEuler.set(0, math::PI / 2, 0)); - _alignVector.copy(this->rotationAxis); + _alignVector.copy(this->state.rotationAxis); handle->quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(_zeroVector, _alignVector, _unitY)); handle->quaternion.multiply(_tempQuaternion); - handle->visible = this->dragging; + handle->visible = this->state.dragging; } - if (this->axis == "E") { + if (this->state.axis == "E") { handle->visible = false; } @@ -628,39 +647,39 @@ struct TransformControlsGizmo: Object3D { } else if (handle->name == "START") { - handle->position.copy(this->worldPositionStart); - handle->visible = this->dragging; + handle->position.copy(this->state.worldPositionStart); + handle->visible = this->state.dragging; } else if (handle->name == "END") { - handle->position.copy(this->worldPosition); - handle->visible = this->dragging; + handle->position.copy(this->state.worldPosition); + handle->visible = this->state.dragging; } else if (handle->name == "DELTA") { - handle->position.copy(this->worldPositionStart); - handle->quaternion.copy(this->worldQuaternionStart); - _tempVector.set(1e-10, 1e-10, 1e-10).add(this->worldPositionStart).sub(this->worldPosition).multiplyScalar(-1); - _tempVector.applyQuaternion(this->worldQuaternionStart.clone().invert()); + handle->position.copy(this->state.worldPositionStart); + handle->quaternion.copy(this->state.worldQuaternionStart); + _tempVector.set(1e-10, 1e-10, 1e-10).add(this->state.worldPositionStart).sub(this->state.worldPosition).multiplyScalar(-1.f); + _tempVector.applyQuaternion(this->state.worldQuaternionStart.clone().invert()); handle->scale.copy(_tempVector); - handle->visible = this->dragging; + handle->visible = this->state.dragging; } else { handle->quaternion.copy(quaternion); - if (this->dragging) { + if (this->state.dragging) { - handle->position.copy(this->worldPositionStart); + handle->position.copy(this->state.worldPositionStart); } else { - handle->position.copy(this->worldPosition); + handle->position.copy(this->state.worldPosition); } - if (this->axis) { + if (this->state.axis) { - handle->visible = this->axis.search(handle->name) != std::string::npos; + handle->visible = this->state.axis->find(handle->name) != std::string::npos; } } @@ -672,7 +691,7 @@ struct TransformControlsGizmo: Object3D { handle->quaternion.copy(quaternion); - if (this->mode == "translate" || this->mode == "scale") { + if (this->state.mode == "translate" || this->state.mode == "scale") { // Hide translate and scale axis facing the camera @@ -682,7 +701,7 @@ struct TransformControlsGizmo: Object3D { if (handle->name == "X" || handle->name == "XYZX") { - if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye)) > AXIS_HIDE_TRESHOLD) { + if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->state.eye)) > AXIS_HIDE_TRESHOLD) { handle->scale.set(1e-10, 1e-10, 1e-10); handle->visible = false; @@ -691,7 +710,7 @@ struct TransformControlsGizmo: Object3D { if (handle->name == "Y" || handle->name == "XYZY") { - if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye)) > AXIS_HIDE_TRESHOLD) { + if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->state.eye)) > AXIS_HIDE_TRESHOLD) { handle->scale.set(1e-10, 1e-10, 1e-10); handle->visible = false; @@ -700,7 +719,7 @@ struct TransformControlsGizmo: Object3D { if (handle->name == "Z" || handle->name == "XYZZ") { - if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye)) > AXIS_HIDE_TRESHOLD) { + if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->state.eye)) > AXIS_HIDE_TRESHOLD) { handle->scale.set(1e-10, 1e-10, 1e-10); handle->visible = false; @@ -709,7 +728,7 @@ struct TransformControlsGizmo: Object3D { if (handle->name == "XY") { - if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye)) < PLANE_HIDE_TRESHOLD) { + if (std::abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->state.eye)) < PLANE_HIDE_TRESHOLD) { handle->scale.set(1e-10, 1e-10, 1e-10); handle->visible = false; @@ -718,7 +737,7 @@ struct TransformControlsGizmo: Object3D { if (handle->name == "YZ") { - if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye)) < PLANE_HIDE_TRESHOLD) { + if (std::abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->state.eye)) < PLANE_HIDE_TRESHOLD) { handle->scale.set(1e-10, 1e-10, 1e-10); handle->visible = false; @@ -727,7 +746,7 @@ struct TransformControlsGizmo: Object3D { if (handle->name == "XZ") { - if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye)) < PLANE_HIDE_TRESHOLD) { + if (std::abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->state.eye)) < PLANE_HIDE_TRESHOLD) { handle->scale.set(1e-10, 1e-10, 1e-10); handle->visible = false; @@ -738,9 +757,9 @@ struct TransformControlsGizmo: Object3D { if (handle->name.find('X') != std::string::npos) { - if (_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->eye) < AXIS_FLIP_TRESHOLD) { + if (_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this->state.eye) < AXIS_FLIP_TRESHOLD) { - if (std::any_cast(handle->userData["tag"]) == "fwd") { + if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "fwd") { handle->visible = false; @@ -749,7 +768,7 @@ struct TransformControlsGizmo: Object3D { handle->scale.x *= -1; } - } else if (std::any_cast(handle->userData["tag"]) == "bwd") { + } else if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "bwd") { handle->visible = false; } @@ -757,9 +776,9 @@ struct TransformControlsGizmo: Object3D { if (handle->name.find('Y') != std::string::npos) { - if (_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->eye) < AXIS_FLIP_TRESHOLD) { + if (_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this->state.eye) < AXIS_FLIP_TRESHOLD) { - if (std::any_cast(handle->userData["tag"]) == "fwd") { + if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "fwd") { handle->visible = false; @@ -768,7 +787,7 @@ struct TransformControlsGizmo: Object3D { handle->scale.y *= -1; } - } else if (std::any_cast(handle->userData["tag"]) == "bwd") { + } else if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "bwd") { handle->visible = false; } @@ -776,9 +795,9 @@ struct TransformControlsGizmo: Object3D { if (handle->name.find('Z') != std::string::npos) { - if (_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->eye) < AXIS_FLIP_TRESHOLD) { + if (_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this->state.eye) < AXIS_FLIP_TRESHOLD) { - if (std::any_cast(handle->userData["tag"]) == "fwd") { + if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "fwd") { handle->visible = false; @@ -787,22 +806,22 @@ struct TransformControlsGizmo: Object3D { handle->scale.z *= -1; } - } else if (std::any_cast(handle->userData["tag"]) == "bwd") { + } else if (handle->userData.contains("tag") && std::any_cast(handle->userData["tag"]) == "bwd") { handle->visible = false; } } - } else if (this->mode == "rotate") { + } else if (this->state.mode == "rotate") { // Align handles to current local or world rotation _tempQuaternion2.copy(quaternion); - _alignVector.copy(this->eye).applyQuaternion(_tempQuaternion.copy(quaternion).invert()); + _alignVector.copy(this->state.eye).applyQuaternion(_tempQuaternion.copy(quaternion).invert()); if (handle->name.find('E') != std::string::npos) { - handle->quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(this->eye, _zeroVector, _unitY)); + handle->quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(this->state.eye, _zeroVector, _unitY)); } if (handle->name == "X") { @@ -828,42 +847,43 @@ struct TransformControlsGizmo: Object3D { } // Hide disabled axes - handle->visible = handle->visible && (handle->name.find('X') == std::string::npos || this->showX); - handle->visible = handle->visible && (handle->name.find('Y') == std::string::npos || this->showY); - handle->visible = handle->visible && (handle->name.find('Z') == std::string::npos || this->showZ); - handle->visible = handle->visible && (handle->name.find('E') == std::string::npos || (this->showX && this->showY && this->showZ)); + handle->visible = handle->visible && (handle->name.find('X') == std::string::npos || this->state.showX); + handle->visible = handle->visible && (handle->name.find('Y') == std::string::npos || this->state.showY); + handle->visible = handle->visible && (handle->name.find('Z') == std::string::npos || this->state.showZ); + handle->visible = handle->visible && (handle->name.find('E') == std::string::npos || (this->state.showX && this->state.showY && this->state.showZ)); // highlight selected axis - if (!handle->_opacity) handle->_opacity = handle->material()->opacity; - if (!handle->_color) handle->_color = handle->material()->as()->color; - - handle->material()->as()->color.copy(*handle->_color); - handle->material()->opacity = *handle->_opacity; + // if (!handle->_opacity) handle->_opacity = handle->material()->opacity; + // if (!handle->_color) handle->_color = handle->material()->as()->color; + // + // handle->material()->as()->color.copy(*handle->_color); + // handle->material()->opacity = *handle->_opacity; - if (!this->enabled) { + if (!this->state.enabled) { handle->material()->opacity *= 0.5; handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); - } else if (this->axis) { + } else if (this->state.axis) { - if (handle->name == this->axis) { + if (handle->name == this->state.axis) { handle->material()->opacity = 1.0; handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); - } else if (this->axis.split( '' ).some(function(a) { - return handle->name == = a; - })) { + } else if (std::ranges::any_of(this->state.axis.value(), + [&](char a) { + return handle->name.size() == 1 && handle->name[0] == a; + })) { handle->material()->opacity = 1.0; - handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); + handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5f); } else { handle->material()->opacity *= 0.25; - handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5); + handle->material()->as()->color.lerp(Color(1, 1, 1), 0.5f); } } } @@ -873,15 +893,6 @@ struct TransformControlsGizmo: Object3D { } }; -struct State { - std::string mode; - std::string space; - std::string axis; - Vector3 eye; - Vector3 worldPosition; - Quaternion worldQuaternion; - Quaternion cameraQuaternion; -}; struct TransformControlsPlane: Mesh { @@ -978,27 +989,16 @@ struct TransformControls::Impl { Vector3 pointStart; Vector3 pointEnd; - Vector3 worldPositionStart; - Quaternion worldQuaternionStart; - - bool dragging{false}; - - Vector3 rotationAxis; float rotationAngle{}; - std::optional axis; - std::shared_ptr _gizmo; std::shared_ptr _plane; TransformControls& scope; PeripheralsEventSource& canvas; - Camera& camera; Object3D* object = nullptr; - Vector3 cameraPosition; - State state; struct MyMouseListener: MouseListener { @@ -1010,7 +1010,7 @@ struct TransformControls::Impl { void onMouseDown(int button, const Vector2& pos) override { - if (!scope.scope.enabled) return; + if (!scope.state.enabled) return; button_ = button; moveEnabled = true; @@ -1018,28 +1018,35 @@ struct TransformControls::Impl { const auto rect = scope.canvas.size(); Vector2 _pos; - _pos.x = (pos.x - rect.width) / rect.width * 2 - 1; - _pos.y = -(pos.y - rect.height) / rect.height * 2 - 1; + _pos.x = (pos.x / static_cast(rect.width())) * 2.f - 1.f; + _pos.y = -(pos.y / static_cast(rect.height())) * 2.f + 1.f; - // scope.pointerHover(_pos); + // clamp to valid NDC range + _pos.x = std::max(-1.f, std::min(1.f, _pos.x)); + _pos.y = std::max(-1.f, std::min(1.f, _pos.y)); + + scope.pointerHover(_pos); scope.pointerDown(button, _pos); } void onMouseMove(const Vector2& pos) override { if (!moveEnabled) return; - if (!scope.scope.enabled) return; + if (!scope.state.enabled) return; const auto rect = scope.canvas.size(); Vector2 _pos; - _pos.x = (pos.x - rect.width) / rect.width * 2 - 1; - _pos.y = -(pos.y - rect.height) / rect.height * 2 - 1; + _pos.x = (pos.x / static_cast(rect.width())) * 2.f - 1.f; + _pos.y = -(pos.y / static_cast(rect.height())) * 2.f + 1.f; + + _pos.x = std::max(-1.f, std::min(1.f, _pos.x)); + _pos.y = std::max(-1.f, std::min(1.f, _pos.y)); scope.pointerMove(button_, _pos); } void onMouseUp(int button, const Vector2& pos) override { - if (!scope.scope.enabled) return; + if (!scope.state.enabled) return; button_ = -1; moveEnabled = false; @@ -1047,31 +1054,38 @@ struct TransformControls::Impl { const auto rect = scope.canvas.size(); Vector2 _pos; - _pos.x = (pos.x - rect.width) / rect.width * 2 - 1; - _pos.y = -(pos.y - rect.height) / rect.height * 2 - 1; + _pos.x = (pos.x / static_cast(rect.width())) * 2.f - 1.f; + _pos.y = -(pos.y / static_cast(rect.height())) * 2.f + 1.f; + + _pos.x = std::max(-1.f, std::min(1.f, _pos.x)); + _pos.y = std::max(-1.f, std::min(1.f, _pos.y)); scope.pointerUp(button, _pos); } private: - int button_; + int button_{-1}; }; MyMouseListener myMouseListener; Impl(TransformControls& scope, Camera& camera, PeripheralsEventSource& canvas) - : scope(scope), myMouseListener(*this), camera(camera), canvas(canvas), - _gizmo(std::make_shared()), + : scope(scope), myMouseListener(*this), canvas(canvas), + state(State(scope.enabled)), + _gizmo(std::make_shared(state)), _plane(std::make_shared(state)) { - this->camera.updateMatrixWorld(); - this->camera.matrixWorld->decompose(this->cameraPosition, state.cameraQuaternion, this->_cameraScale); + camera.updateMatrixWorld(); + camera.matrixWorld->decompose(this->state.cameraPosition, state.cameraQuaternion, this->_cameraScale); - state.eye.copy(this->cameraPosition).sub(state.worldPosition).normalize(); + state.eye.copy(this->state.cameraPosition).sub(state.worldPosition).normalize(); + state.camera = &camera; canvas.addMouseListener(myMouseListener); + + _raycaster.params.lineThreshold = 0.1f; } static std::optional intersectObjectWithRay(Object3D& object, Raycaster& raycaster, bool includeInvisible = false) { @@ -1089,54 +1103,54 @@ struct TransformControls::Impl { return std::nullopt; } - // void pointerHover(Vector2 pointer) { - // - // if (!this->scope.object || scope.dragging) return; - // - // _raycaster.setFromCamera(pointer, this->camera); - // - // const auto intersect = intersectObjectWithRay(*this->_gizmo->picker[scope.mode], _raycaster); - // - // if (intersect) { - // - // this->axis = intersect->object->name; - // - // } else { - // - // this->axis = std::nullopt; - // } - // } + void pointerHover(const Vector2& pointer) { + + if (!this->object || state.dragging) return; + + _raycaster.setFromCamera(pointer, *this->state.camera); + + const auto intersect = intersectObjectWithRay(*this->_gizmo->picker[state.mode], _raycaster); + + if (intersect) { + + this->state.axis = intersect->object->name; + + } else { + + this->state.axis = std::nullopt; + } + } void pointerDown(int button, Vector2 pointer) { - if (!this->object || scope.dragging || button != 0) return; + if (!this->object || state.dragging || button != 0) return; - if (this->axis) { + if (this->state.axis) { - _raycaster.setFromCamera(pointer, this->camera); + _raycaster.setFromCamera(pointer, *this->state.camera); const auto planeIntersect = intersectObjectWithRay(*this->_plane, _raycaster, true); if (planeIntersect) { - auto space = scope.space; + auto space = state.space; - if (scope.mode == "scale") { + if (state.mode == "scale") { space = "local"; - } else if (this->axis == "E" || this->axis == "XYZE" || this->axis == "XYZ") { + } else if (this->state.axis == "E" || this->state.axis == "XYZE" || this->state.axis == "XYZ") { space = "world"; } - if (space == "local" && scope.mode == "rotate") { + if (space == "local" && state.mode == "rotate") { + + const auto snap = state.rotationSnap; - // const auto snap = scope.rotationSnap; - // - // if (this->axis == "X" && snap) this->object->rotation.x = std::round(this->object->rotation.x / snap) * snap; - // if (this->axis == "Y" && snap) this->object->rotation.y = std::round(this->object->rotation.y / snap) * snap; - // if (this->axis == "Z" && snap) this->object->rotation.z = std::round(this->object->rotation.z / snap) * snap; + if (this->state.axis == "X" && snap) this->object->rotation.x = std::round(this->object->rotation.x / *snap) * *snap; + if (this->state.axis == "Y" && snap) this->object->rotation.y = std::round(this->object->rotation.y / *snap) * *snap; + if (this->state.axis == "Z" && snap) this->object->rotation.z = std::round(this->object->rotation.z / *snap) * *snap; } this->object->updateMatrixWorld(); @@ -1146,23 +1160,22 @@ struct TransformControls::Impl { this->_quaternionStart.copy(this->object->quaternion); this->_scaleStart.copy(this->object->scale); - this->object->matrixWorld->decompose(this->worldPositionStart, this->worldQuaternionStart, this->_worldScaleStart); + this->object->matrixWorld->decompose(this->state.worldPositionStart, this->state.worldQuaternionStart, this->_worldScaleStart); - this->pointStart.copy(planeIntersect->point).sub(this->worldPositionStart); + this->pointStart.copy(planeIntersect->point).sub(this->state.worldPositionStart); } - scope.dragging = true; - _mouseDownEvent.target = &this->scope.mode; - scope.dispatchEvent(_mouseDownEvent); + state.dragging = true; + scope.dispatchEvent("mouseDown", &this->state.mode); } } void pointerMove(int button, Vector2 pointer) { - const auto axis = this->axis; - const auto mode = this->scope.mode; + const auto axis = this->state.axis; + const auto mode = this->state.mode; const auto object = this->object; - auto space = this->scope.space; + auto space = this->state.space; if (mode == "scale") { @@ -1173,15 +1186,15 @@ struct TransformControls::Impl { space = "world"; } - if (!object || !axis || this->dragging == false || button != -1) return; + if (!object || !axis || this->state.dragging == false) return; - _raycaster.setFromCamera(pointer, this->camera); + _raycaster.setFromCamera(pointer, *this->state.camera); const auto planeIntersect = intersectObjectWithRay(*this->_plane, _raycaster, true); if (!planeIntersect) return; - this->pointEnd.copy(planeIntersect->point).sub(this->worldPositionStart); + this->pointEnd.copy(planeIntersect->point).sub(this->state.worldPositionStart); if (mode == "translate") { @@ -1211,7 +1224,7 @@ struct TransformControls::Impl { // Apply translation snap - if (this->scope.translationSnap) { + if (this->state.translationSnap) { if (space == "local") { @@ -1219,17 +1232,17 @@ struct TransformControls::Impl { if (axis->find('X') != std::string::npos) { - object->position.x = std::round(object->position.x / *this->scope.translationSnap) * *this->scope.translationSnap; + object->position.x = std::round(object->position.x / *this->state.translationSnap) * *this->state.translationSnap; } if (axis->find('Y') != std::string::npos) { - object->position.y = std::round(object->position.y / *this->scope.translationSnap) * *this->scope.translationSnap; + object->position.y = std::round(object->position.y / *this->state.translationSnap) * *this->state.translationSnap; } if (axis->find('Z') != std::string::npos) { - object->position.z = std::round(object->position.z / *this->scope.translationSnap) * *this->scope.translationSnap; + object->position.z = std::round(object->position.z / *this->state.translationSnap) * *this->state.translationSnap; } object->position.applyQuaternion(this->_quaternionStart); @@ -1244,17 +1257,17 @@ struct TransformControls::Impl { if (axis->find('X') != std::string::npos) { - object->position.x = std::round(object->position.x / *this->scope.translationSnap) * *this->scope.translationSnap; + object->position.x = std::round(object->position.x / *this->state.translationSnap) * *this->state.translationSnap; } if (axis->find('Y') != std::string::npos) { - object->position.y = std::round(object->position.y / *this->scope.translationSnap) * *this->scope.translationSnap; + object->position.y = std::round(object->position.y / *this->state.translationSnap) * *this->state.translationSnap; } if (axis->find('Z') != std::string::npos) { - object->position.z = std::round(object->position.z / *this->scope.translationSnap) * *this->scope.translationSnap; + object->position.z = std::round(object->position.z / *this->state.translationSnap) * *this->state.translationSnap; } if (object->parent) { @@ -1304,52 +1317,51 @@ struct TransformControls::Impl { object->scale.copy(this->_scaleStart).multiply(_tempVector2); - // if ( this->scaleSnap ) { - // - // if ( axis.search( 'X' ) !== - 1 ) { - // - // object.scale.x = std::round( object.scale.x / this->scaleSnap ) * this->scaleSnap || this->scaleSnap; - // - // } - // - // if ( axis.search( 'Y' ) !== - 1 ) { - // - // object.scale.y = std::round( object.scale.y / this->scaleSnap ) * this->scaleSnap || this->scaleSnap; - // - // } - // - // if ( axis.search( 'Z' ) !== - 1 ) { - // - // object.scale.z = std::round( object.scale.z / this->scaleSnap ) * this->scaleSnap || this->scaleSnap; - // - // } - // - // } + if (state.scaleSnap) { + + if (axis->find('X') != std::string::npos) { + + auto snapped = std::round(object->scale.x / state.scaleSnap.value()) * state.scaleSnap.value(); + object->scale.x = (snapped != 0) ? snapped : *state.scaleSnap; + } + + if (axis->find('Y') != std::string::npos) { + + auto snapped = std::round(object->scale.y / state.scaleSnap.value()) * state.scaleSnap.value(); + object->scale.y = (snapped != 0) ? snapped : *state.scaleSnap; + } + + if (axis->find('Z') != std::string::npos) { + + auto snapped = std::round(object->scale.z / state.scaleSnap.value()) * state.scaleSnap.value(); + object->scale.z = (snapped != 0) ? snapped : *state.scaleSnap; + } + } } else if (mode == "rotate") { this->_offset.copy(this->pointEnd).sub(this->pointStart); - const auto ROTATION_SPEED = 20.f / this->state.worldPosition.distanceTo(_tempVector.setFromMatrixPosition(*this->camera.matrixWorld)); + const auto ROTATION_SPEED = 20.f / this->state.worldPosition.distanceTo(_tempVector.setFromMatrixPosition(*this->state.camera->matrixWorld)); if (axis == "E") { - this->rotationAxis.copy(this->state.eye); + this->state.rotationAxis.copy(this->state.eye); this->rotationAngle = this->pointEnd.angleTo(this->pointStart); this->_startNorm.copy(this->pointStart).normalize(); this->_endNorm.copy(this->pointEnd).normalize(); - this->rotationAngle *= (this->_endNorm.cross(this->_startNorm).dot(state.eye) < 0 ? 1 : -1); + this->rotationAngle *= (this->_endNorm.cross(this->_startNorm).dot(state.eye) < 0 ? 1.f : -1.f); } else if (axis == "XYZE") { - this->rotationAxis.copy(this->_offset).cross(state.eye).normalize(); - this->rotationAngle = this->_offset.dot(_tempVector.copy(this->rotationAxis).cross(state.eye)) * ROTATION_SPEED; + this->state.rotationAxis.copy(this->_offset).cross(state.eye).normalize(); + this->rotationAngle = this->_offset.dot(_tempVector.copy(this->state.rotationAxis).cross(state.eye)) * ROTATION_SPEED; } else if (axis == "X" || axis == "Y" || axis == "Z") { - this->rotationAxis.copy(_unit[*axis]); + this->state.rotationAxis.copy(_unit[*axis]); _tempVector.copy(_unit[*axis]); @@ -1363,38 +1375,37 @@ struct TransformControls::Impl { // Apply rotation snap - if (this->scope.rotationSnap) this->rotationAngle = std::round(this->rotationAngle / *this->scope.rotationSnap) * *this->scope.rotationSnap; + if (this->state.rotationSnap) this->rotationAngle = std::round(this->rotationAngle / *this->state.rotationSnap) * *this->state.rotationSnap; // Apply rotate if (space == "local" && axis != "E" && axis != "XYZE") { object->quaternion.copy(this->_quaternionStart); - object->quaternion.multiply(_tempQuaternion.setFromAxisAngle(this->rotationAxis, this->rotationAngle)).normalize(); + object->quaternion.multiply(_tempQuaternion.setFromAxisAngle(this->state.rotationAxis, this->rotationAngle)).normalize(); } else { - this->rotationAxis.applyQuaternion(this->_parentQuaternionInv); - object->quaternion.copy(_tempQuaternion.setFromAxisAngle(this->rotationAxis, this->rotationAngle)); + this->state.rotationAxis.applyQuaternion(this->_parentQuaternionInv); + object->quaternion.copy(_tempQuaternion.setFromAxisAngle(this->state.rotationAxis, this->rotationAngle)); object->quaternion.multiply(this->_quaternionStart).normalize(); } } - this->scope.dispatchEvent(_changeEvent); - this->scope.dispatchEvent(_objectChangeEvent); + this->scope.dispatchEvent("change"); + this->scope.dispatchEvent("objectChange"); } - void pointerUp(int button, Vector2 pointer) { + void pointerUp(int button, Vector2) { if (button != 0) return; - if (this->dragging && this->axis) { + if (this->state.dragging && this->state.axis) { - _mouseUpEvent.target = &this->scope.mode; - this->scope.dispatchEvent(_mouseUpEvent); + this->scope.dispatchEvent("mouseUp", &this->state.mode); } - this->dragging = false; - this->axis = std::nullopt; + this->state.dragging = false; + this->state.axis = std::nullopt; } void attach(Object3D& object) { @@ -1407,7 +1418,7 @@ struct TransformControls::Impl { this->object = nullptr; scope.visible = false; - this->axis = std::nullopt; + this->state.axis = std::nullopt; } }; @@ -1419,7 +1430,19 @@ TransformControls::TransformControls(Camera& camera, PeripheralsEventSource& can this->add(pimpl_->_gizmo); this->add(pimpl_->_plane); - // Object3D::updateMatrixWorld(); + Object3D::updateMatrixWorld(); +} + +void TransformControls::setSpace(const std::string& space) { + pimpl_->state.space = space; +} + +std::string TransformControls::getSpace() const { + return pimpl_->state.space; +} + +void TransformControls::setMode(const std::string& mode) { + pimpl_->state.mode = mode; } void TransformControls::updateMatrixWorld(bool force) { @@ -1443,10 +1466,10 @@ void TransformControls::updateMatrixWorld(bool force) { pimpl_->_worldQuaternionInv.copy(pimpl_->state.worldQuaternion).invert(); } - pimpl_->camera.updateMatrixWorld(); - pimpl_->camera.matrixWorld->decompose(pimpl_->cameraPosition, pimpl_->state.cameraQuaternion, pimpl_->_cameraScale); + pimpl_->state.camera->updateMatrixWorld(); + pimpl_->state.camera->matrixWorld->decompose(pimpl_->state.cameraPosition, pimpl_->state.cameraQuaternion, pimpl_->_cameraScale); - pimpl_->state.eye.copy(pimpl_->cameraPosition).sub(pimpl_->state.worldPosition).normalize(); + pimpl_->state.eye.copy(pimpl_->state.cameraPosition).sub(pimpl_->state.worldPosition).normalize(); Object3D::updateMatrixWorld(force); } diff --git a/src/threepp/materials/MeshBasicMaterial.cpp b/src/threepp/materials/MeshBasicMaterial.cpp index c58d4fe0..0423fb4a 100644 --- a/src/threepp/materials/MeshBasicMaterial.cpp +++ b/src/threepp/materials/MeshBasicMaterial.cpp @@ -134,5 +134,5 @@ bool MeshBasicMaterial::setValue(const std::string& key, const MaterialValue& va std::shared_ptr MeshBasicMaterial::createDefault() const { - return {}; + return std::shared_ptr(new MeshBasicMaterial()); } From f15c9005d48743f235e6257aa9be49e2d87b7a23 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 21 Feb 2026 00:57:18 +0100 Subject: [PATCH 13/16] Working TransformControls! --- examples/controls/drag.cpp | 4 +- examples/controls/transform.cpp | 47 +++++++++++++------ include/threepp/core/EventDispatcher.hpp | 18 +++++++- src/threepp/controls/TransformControls.cpp | 54 ++++++++++++++++------ src/threepp/core/EventDispatcher.cpp | 2 +- src/threepp/renderers/GLRenderer.cpp | 2 +- src/threepp/renderers/gl/GLGeometries.cpp | 5 +- src/threepp/renderers/gl/GLObjects.cpp | 8 ++-- src/threepp/renderers/gl/GLTextures.cpp | 4 +- tests/core/EventDispatcher_test.cpp | 14 +----- 10 files changed, 102 insertions(+), 56 deletions(-) diff --git a/examples/controls/drag.cpp b/examples/controls/drag.cpp index 2ea42c57..8e1a4d7a 100644 --- a/examples/controls/drag.cpp +++ b/examples/controls/drag.cpp @@ -66,10 +66,10 @@ int main() { DragControls controls(objects, camera, canvas); controls.rotateSpeed = 2; - struct HoverListener: public EventListener { + struct HoverListener: EventListener { void onEvent(Event& event) override { - auto target = static_cast(event.target); + auto target = std::any_cast(event.target); auto& color = target->material()->as()->color; if (event.type == "hoveron") { diff --git a/examples/controls/transform.cpp b/examples/controls/transform.cpp index 720caee2..8210ebf6 100644 --- a/examples/controls/transform.cpp +++ b/examples/controls/transform.cpp @@ -2,48 +2,65 @@ #include "threepp/controls/TransformControls.hpp" #include "threepp/threepp.hpp" +#include + using namespace threepp; int main() { - Canvas canvas("Transform controls"); + Canvas canvas(Canvas::Parameters() + .title("Transform controls") + .exitOnKeyEscape(false)); + GLRenderer renderer(canvas.size()); renderer.shadowMap().enabled = true; renderer.shadowMap().type = ShadowMap::PFC; PerspectiveCamera camera(60, canvas.aspect()); - camera.position.z = 10; + camera.position.set(0,5,5); Scene scene; - scene.background = Color(0xf0f0f0); + scene.background = Color::aliceblue; scene.add(AmbientLight::create(0xaaaaaa)); auto light = SpotLight::create(0xffffff, 1.f); light->position.set(0, 25, 50); light->angle = math::PI / 9; - // - // light->castShadow = true; - // light->shadow->camera->as()->nearPlane = 10; - // light->shadow->camera->as()->farPlane = 100; - // light->shadow->mapSize.x = 1024; - // light->shadow->mapSize.y = 1024; + + light->castShadow = true; + light->shadow->camera->as()->nearPlane = 10; + light->shadow->camera->as()->farPlane = 100; + light->shadow->mapSize.x = 1024; + light->shadow->mapSize.y = 1024; scene.add(light); + TextureLoader tl; + auto tex = tl.load(std::string(DATA_FOLDER) + "/textures/crate.gif"); + auto material = MeshBasicMaterial::create(); material->transparent = true; - material->opacity = 0.7; + material->opacity = 0.7f; + material->map = tex; auto object = Mesh::create(BoxGeometry::create(), material); scene.add(object); + + auto grid = GridHelper::create(10, 10); + scene.add(grid); + + OrbitControls orbitControls(camera, canvas); + TransformControls controls(camera, canvas); controls.attach(*object); - scene.add(controls); - OrbitControls orbitControls(camera, canvas); - orbitControls.enabled = false; + LambdaEventListener changeListener([&](Event& event) { + orbitControls.enabled = !std::any_cast(event.target); + }); + + controls.addEventListener("dragging-changed", changeListener); KeyAdapter adapter(KeyAdapter::Mode::KEY_PRESSED, [&](KeyEvent evt) { switch (evt.key) { @@ -64,12 +81,14 @@ int main() { break; } case Key::SPACE: { - orbitControls.enabled = !orbitControls.enabled; + controls.enabled = !controls.enabled; + break; } } }); canvas.addKeyListener(adapter); + canvas.onWindowResize([&](WindowSize size) { camera.aspect = size.aspect(); camera.updateProjectionMatrix(); diff --git a/include/threepp/core/EventDispatcher.hpp b/include/threepp/core/EventDispatcher.hpp index 1b5069ae..bf397fe2 100644 --- a/include/threepp/core/EventDispatcher.hpp +++ b/include/threepp/core/EventDispatcher.hpp @@ -3,9 +3,11 @@ #ifndef THREEPP_EVENTDISPATCHER_HPP #define THREEPP_EVENTDISPATCHER_HPP +#include #include #include #include +#include namespace threepp { @@ -13,7 +15,7 @@ namespace threepp { struct Event { const std::string type; - void* target; + std::any target; }; struct EventListener { @@ -23,6 +25,18 @@ namespace threepp { virtual ~EventListener() = default; }; + struct LambdaEventListener: EventListener { + + explicit LambdaEventListener(std::function f): f_(std::move(f)) {} + + void onEvent(Event& event) override { + f_(event); + } + + private: + std::function f_; + }; + class EventDispatcher { public: @@ -32,7 +46,7 @@ namespace threepp { void removeEventListener(const std::string& type, const EventListener& listener); - void dispatchEvent(const std::string& type, void* target = nullptr); + void dispatchEvent(const std::string& type, std::any target = {}); virtual ~EventDispatcher() = default; diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index be1b369b..ca27fadd 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -83,11 +83,6 @@ namespace { return geometry; } - struct GizmoObject: Object3D { - - std::optional _opacity; - std::optional _color; - }; struct State { @@ -116,9 +111,9 @@ namespace { std::optional translationSnap; std::optional scaleSnap; - Camera* camera; + Camera* camera = nullptr; - State(bool& enabled): enabled(enabled) {} + explicit State(bool& enabled): enabled(enabled) {} }; @@ -126,9 +121,9 @@ namespace { struct TransformControlsGizmo: Object3D { - std::unordered_map gizmo; - std::unordered_map picker; - std::unordered_map helper; + std::unordered_map gizmo; + std::unordered_map picker; + std::unordered_map helper; State& state; @@ -353,11 +348,11 @@ struct TransformControlsGizmo: Object3D { {Line::create(lineGeometry, matLineRed), std::nullopt, std::nullopt, Vector3{0.8, 1, 1}, std::nullopt} }}, {"Y", { - {Mesh::create(scaleHandleGeometry, matGreen), Vector3{0, 0.6, 0}, std::nullopt, std::nullopt, std::nullopt}, + {Mesh::create(scaleHandleGeometry, matGreen), Vector3{0, 0.8, 0}, std::nullopt, std::nullopt, std::nullopt}, {Line::create(lineGeometry, matLineGreen), std::nullopt, Euler{0, 0, math::PI/2}, Vector3{0.8, 1, 1}, std::nullopt} }}, {"Z", { - {Mesh::create(scaleHandleGeometry, matBlue), Vector3{0, 0, 0.6}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt}, + {Mesh::create(scaleHandleGeometry, matBlue), Vector3{0, 0, 0.8}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt}, {Line::create(lineGeometry, matLineGreen), std::nullopt, Euler{0, -math::PI/2, 0}, Vector3{0.8, 1, 1}, std::nullopt} }}, {"XY", { @@ -477,9 +472,9 @@ struct TransformControlsGizmo: Object3D { this->picker["scale"]->visible = false; } - std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { + std::shared_ptr setupGizmo(const GizmoMap& gizmoMap) { - const auto gizmo = std::make_shared(); + const auto gizmo = Object3D::create(); for (const auto& [name, value] : gizmoMap) { @@ -854,6 +849,32 @@ struct TransformControlsGizmo: Object3D { // highlight selected axis + if (auto mat = handle->material()) { + + // Save originals on first encounter + if (!handle->userData.contains("__orig_opacity")) { + handle->userData["__orig_opacity"] = mat->opacity; + } + + if (!handle->userData.contains("__orig_color")) { + if (auto mwc = mat->as()) { + handle->userData["__orig_color"] = mwc->color; // copy stored + } + } + + // Restore original color (if material supports color) + if (auto mwc = mat->as()) { + if (handle->userData.contains("__orig_color")) { + mwc->color.copy(std::any_cast(handle->userData["__orig_color"])); + } + } + + // Restore original opacity + if (handle->userData.contains("__orig_opacity")) { + mat->opacity = std::any_cast(handle->userData["__orig_opacity"]); + } + } + // if (!handle->_opacity) handle->_opacity = handle->material()->opacity; // if (!handle->_color) handle->_color = handle->material()->as()->color; // @@ -1166,7 +1187,8 @@ struct TransformControls::Impl { } state.dragging = true; - scope.dispatchEvent("mouseDown", &this->state.mode); + scope.dispatchEvent("dragging-changed", this->state.dragging); + scope.dispatchEvent("mouseDown", this->state.mode); } } @@ -1406,6 +1428,8 @@ struct TransformControls::Impl { this->state.dragging = false; this->state.axis = std::nullopt; + + this->scope.dispatchEvent("dragging-changed", this->state.dragging); } void attach(Object3D& object) { diff --git a/src/threepp/core/EventDispatcher.cpp b/src/threepp/core/EventDispatcher.cpp index 5b288d23..6300db6b 100644 --- a/src/threepp/core/EventDispatcher.cpp +++ b/src/threepp/core/EventDispatcher.cpp @@ -31,7 +31,7 @@ void EventDispatcher::removeEventListener(const std::string& type, const EventLi } } -void EventDispatcher::dispatchEvent(const std::string& type, void* target) { +void EventDispatcher::dispatchEvent(const std::string& type, std::any target) { if (listeners_.contains(type)) { diff --git a/src/threepp/renderers/GLRenderer.cpp b/src/threepp/renderers/GLRenderer.cpp index b32b7839..c460eb0f 100644 --- a/src/threepp/renderers/GLRenderer.cpp +++ b/src/threepp/renderers/GLRenderer.cpp @@ -57,7 +57,7 @@ struct GLRenderer::Impl { void onEvent(Event& event) override { - auto material = static_cast(event.target); + const auto material = std::any_cast(event.target); material->removeEventListener("dispose", *this); diff --git a/src/threepp/renderers/gl/GLGeometries.cpp b/src/threepp/renderers/gl/GLGeometries.cpp index f8e41517..ead0412c 100644 --- a/src/threepp/renderers/gl/GLGeometries.cpp +++ b/src/threepp/renderers/gl/GLGeometries.cpp @@ -11,6 +11,7 @@ #include #endif +#include #include using namespace threepp; @@ -25,14 +26,14 @@ struct GLGeometries::Impl { void onEvent(Event& event) override { - auto geometry = static_cast(event.target); + const auto geometry = std::any_cast(event.target); if (geometry->hasIndex()) { scope_->attributes_.remove(geometry->getIndex()); } - for (const auto& [name, value] : geometry->getAttributes()) { + for (const auto& value : geometry->getAttributes() | std::views::values) { scope_->attributes_.remove(value.get()); } diff --git a/src/threepp/renderers/gl/GLObjects.cpp b/src/threepp/renderers/gl/GLObjects.cpp index 6e63d4a9..f2bca50c 100644 --- a/src/threepp/renderers/gl/GLObjects.cpp +++ b/src/threepp/renderers/gl/GLObjects.cpp @@ -18,12 +18,12 @@ using namespace threepp::gl; struct GLObjects::Impl { - struct OnInstancedMeshDispose: public EventListener { + struct OnInstancedMeshDispose: EventListener { - explicit OnInstancedMeshDispose(GLObjects::Impl* scope): scope(scope) {} + explicit OnInstancedMeshDispose(Impl* scope): scope(scope) {} void onEvent(Event& event) override { - auto instancedMesh = static_cast(event.target); + auto instancedMesh = std::any_cast(event.target); instancedMesh->removeEventListener("dispose", *this); @@ -33,7 +33,7 @@ struct GLObjects::Impl { } private: - GLObjects::Impl* scope; + Impl* scope; }; GLInfo& info_; diff --git a/src/threepp/renderers/gl/GLTextures.cpp b/src/threepp/renderers/gl/GLTextures.cpp index 9fa454c3..e23618cd 100644 --- a/src/threepp/renderers/gl/GLTextures.cpp +++ b/src/threepp/renderers/gl/GLTextures.cpp @@ -522,7 +522,7 @@ std::optional gl::GLTextures::getGlTexture(Texture& texture) const void gl::GLTextures::TextureEventListener::onEvent(Event& event) { - auto texture = static_cast(event.target); + const auto texture = std::any_cast(event.target); texture->removeEventListener("dispose", *this); @@ -533,7 +533,7 @@ void gl::GLTextures::TextureEventListener::onEvent(Event& event) { void gl::GLTextures::RenderTargetEventListener::onEvent(Event& event) { - auto renderTarget = static_cast(event.target); + const auto renderTarget = std::any_cast(event.target); renderTarget->removeEventListener("dispose", *this); diff --git a/tests/core/EventDispatcher_test.cpp b/tests/core/EventDispatcher_test.cpp index f2544520..be9f8de8 100644 --- a/tests/core/EventDispatcher_test.cpp +++ b/tests/core/EventDispatcher_test.cpp @@ -10,18 +10,6 @@ using namespace threepp; namespace { - struct LambdaEventListener: EventListener { - - explicit LambdaEventListener(std::function f): f_(std::move(f)) {} - - void onEvent(Event& event) override { - f_(event); - } - - private: - std::function f_; - }; - struct MyEventListener: EventListener { @@ -35,7 +23,7 @@ namespace { struct OnMaterialDispose: EventListener { void onEvent(Event& event) override { - auto* material = static_cast(event.target); + auto* material = std::any_cast(event.target); material->removeEventListener("dispose", *this); } }; From 3bafdad6c2e5d43c71a32a4f1a6b8a49c70a3d69 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 21 Feb 2026 02:07:13 +0100 Subject: [PATCH 14/16] fix colors --- src/threepp/controls/TransformControls.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index ca27fadd..c399a70a 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -353,7 +353,7 @@ struct TransformControlsGizmo: Object3D { }}, {"Z", { {Mesh::create(scaleHandleGeometry, matBlue), Vector3{0, 0, 0.8}, Euler{math::PI/2, 0, 0}, std::nullopt, std::nullopt}, - {Line::create(lineGeometry, matLineGreen), std::nullopt, Euler{0, -math::PI/2, 0}, Vector3{0.8, 1, 1}, std::nullopt} + {Line::create(lineGeometry, matLineBlue), std::nullopt, Euler{0, -math::PI/2, 0}, Vector3{0.8, 1, 1}, std::nullopt} }}, {"XY", { {Mesh::create(scaleHandleGeometry, matYellowTransparent), Vector3{0.85, 0.85, 0}, std::nullopt, Vector3{2, 2, 0.2}, std::nullopt}, @@ -361,9 +361,9 @@ struct TransformControlsGizmo: Object3D { {Line::create(lineGeometry, matLineYellow), Vector3{0.98, 0.855, 0}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt} }}, {"YZ", { - {Mesh::create(scaleHandleGeometry, matMagentaTransparent), Vector3{0, 0.85, 0.85}, std::nullopt, Vector3{0.2, 2, 2}, std::nullopt}, - {Line::create(lineGeometry, matLineMagenta), Vector3{0, 0.855, 0.98}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt}, - {Line::create(lineGeometry, matLineMagenta), Vector3{0, 0.98, 0.855}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} + {Mesh::create(scaleHandleGeometry, matCyanTransparent), Vector3{0, 0.85, 0.85}, std::nullopt, Vector3{0.2, 2, 2}, std::nullopt}, + {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.855, 0.98}, Euler{0, 0, math::PI/2}, Vector3{0.125, 1, 1}, std::nullopt}, + {Line::create(lineGeometry, matLineCyan), Vector3{0, 0.98, 0.855}, Euler{0, -math::PI/2, 0}, Vector3{0.125, 1, 1}, std::nullopt} }}, {"XZ", { {Mesh::create(scaleHandleGeometry, matMagentaTransparent), Vector3{0.85, 0, 0.85}, std::nullopt, Vector3{2, 0.2, 2}, std::nullopt}, @@ -858,7 +858,7 @@ struct TransformControlsGizmo: Object3D { if (!handle->userData.contains("__orig_color")) { if (auto mwc = mat->as()) { - handle->userData["__orig_color"] = mwc->color; // copy stored + handle->userData["__orig_color"] = mwc->color;// copy stored } } From 2d7495f4ae3ae0d62135952e6044d5bc381303a3 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 21 Feb 2026 02:07:25 +0100 Subject: [PATCH 15/16] cleanup --- src/threepp/controls/TransformControls.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index c399a70a..019cb605 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -875,12 +875,6 @@ struct TransformControlsGizmo: Object3D { } } - // if (!handle->_opacity) handle->_opacity = handle->material()->opacity; - // if (!handle->_color) handle->_color = handle->material()->as()->color; - // - // handle->material()->as()->color.copy(*handle->_color); - // handle->material()->opacity = *handle->_opacity; - if (!this->state.enabled) { handle->material()->opacity *= 0.5; From 79d982cfc2f71af38543ab7dc6241184e41ba588 Mon Sep 17 00:00:00 2001 From: Lars Ivar Hatledal Date: Sat, 21 Feb 2026 10:41:37 +0100 Subject: [PATCH 16/16] fix highlight on hover --- src/threepp/controls/TransformControls.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/threepp/controls/TransformControls.cpp b/src/threepp/controls/TransformControls.cpp index 019cb605..591f306f 100644 --- a/src/threepp/controls/TransformControls.cpp +++ b/src/threepp/controls/TransformControls.cpp @@ -676,13 +676,11 @@ struct TransformControlsGizmo: Object3D { handle->visible = this->state.axis->find(handle->name) != std::string::npos; } - } + }// If updating helper, skip rest of the loop - // If updating helper, skip rest of the loop continue; - } - // Align handles to current local or world rotation + }// Align handles to current local or world rotation handle->quaternion.copy(quaternion); @@ -1045,7 +1043,6 @@ struct TransformControls::Impl { } void onMouseMove(const Vector2& pos) override { - if (!moveEnabled) return; if (!scope.state.enabled) return; const auto rect = scope.canvas.size(); @@ -1057,7 +1054,11 @@ struct TransformControls::Impl { _pos.x = std::max(-1.f, std::min(1.f, _pos.x)); _pos.y = std::max(-1.f, std::min(1.f, _pos.y)); - scope.pointerMove(button_, _pos); + scope.pointerHover(_pos); + + if (moveEnabled) { + scope.pointerMove(button_, _pos); + } } void onMouseUp(int button, const Vector2& pos) override { @@ -1103,6 +1104,10 @@ struct TransformControls::Impl { _raycaster.params.lineThreshold = 0.1f; } + ~Impl() { + canvas.removeMouseListener(myMouseListener); + } + static std::optional intersectObjectWithRay(Object3D& object, Raycaster& raycaster, bool includeInvisible = false) { const auto allIntersections = raycaster.intersectObject(object, true); @@ -1489,7 +1494,7 @@ void TransformControls::updateMatrixWorld(bool force) { pimpl_->state.eye.copy(pimpl_->state.cameraPosition).sub(pimpl_->state.worldPosition).normalize(); - Object3D::updateMatrixWorld(force); + Object3D::updateMatrixWorld(true); } TransformControls& TransformControls::attach(Object3D& object) {