feat: rotation gizmo improvements

This commit is contained in:
Nick Fisher
2024-12-12 16:58:30 +08:00
parent 771f851784
commit 62cd85c148
7 changed files with 7752 additions and 3450 deletions

Binary file not shown.

View File

@@ -1,16 +1,16 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
class _Gizmo { class _Gizmo {
bool isVisible = false; bool isVisible = false;
final ThermionViewer viewer; final ThermionViewer viewer;
final GizmoAsset _gizmo; final GizmoAsset _gizmo;
ThermionEntity? _attachedTo; ThermionEntity? _attachedTo;
final GizmoType type;
_Gizmo(this._gizmo, this.viewer); _Gizmo(this._gizmo, this.viewer, this.type);
final _onEntityTransformUpdated = StreamController< final _onEntityTransformUpdated = StreamController<
({ThermionEntity entity, Matrix4 transform})>.broadcast(); ({ThermionEntity entity, Matrix4 transform})>.broadcast();
@@ -18,7 +18,21 @@ class _Gizmo {
Axis? _active; Axis? _active;
Axis? get active => _active; Axis? get active => _active;
Vector3? _activeCords;
double _getAngleBetweenVectors(Vector2 v1, Vector2 v2) {
// Normalize vectors to ensure consistent rotation regardless of distance from center
v1.normalize();
v2.normalize();
// Calculate angle using atan2
double angle = atan2(v2.y, v2.x) - atan2(v1.y, v1.x);
// Ensure angle is between -π and π
if (angle > pi) angle -= 2 * pi;
if (angle < -pi) angle += 2 * pi;
return angle;
}
void checkHover(int x, int y) async { void checkHover(int x, int y) async {
_gizmo.pick(x, y, handler: (result, coords) async { _gizmo.pick(x, y, handler: (result, coords) async {
@@ -29,16 +43,10 @@ class _Gizmo {
break; break;
case GizmoPickResultType.AxisX: case GizmoPickResultType.AxisX:
_active = Axis.X; _active = Axis.X;
_activeCords = coords;
case GizmoPickResultType.AxisY: case GizmoPickResultType.AxisY:
_active = Axis.Y; _active = Axis.Y;
_activeCords = coords;
case GizmoPickResultType.AxisZ: case GizmoPickResultType.AxisZ:
_active = Axis.Z; _active = Axis.Z;
_activeCords = coords;
default: default:
} }
}); });
@@ -79,11 +87,20 @@ class _Gizmo {
return; return;
} }
if (type == GizmoType.translation) {
await _updateTranslation(currentPosition, delta);
} else if (type == GizmoType.rotation) {
await _updateRotation(currentPosition, delta);
}
_onEntityTransformUpdated
.add((entity: _attachedTo!, transform: gizmoTransform!));
}
Future<void> _updateTranslation(Vector2 currentPosition, Vector2 delta) async {
var view = await viewer.getViewAt(0); var view = await viewer.getViewAt(0);
var camera = await viewer.getActiveCamera(); var camera = await viewer.getActiveCamera();
var viewport = await view.getViewport(); var viewport = await view.getViewport();
var projectionMatrix = await viewer.getCameraProjectionMatrix(); var projectionMatrix = await viewer.getCameraProjectionMatrix();
var viewMatrix = await camera.getViewMatrix(); var viewMatrix = await camera.getViewMatrix();
var inverseViewMatrix = await camera.getModelMatrix(); var inverseViewMatrix = await camera.getModelMatrix();
@@ -98,13 +115,17 @@ class _Gizmo {
var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w; var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w;
var gizmoScreenSpace = Vector2(((gizmoNdc.x / 2) + 0.5) * viewport.width, var gizmoScreenSpace = Vector2(
((gizmoNdc.x / 2) + 0.5) * viewport.width,
viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height)); viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height));
gizmoScreenSpace += delta; gizmoScreenSpace += delta;
gizmoNdc = Vector4(((gizmoScreenSpace.x / viewport.width) - 0.5) * 2, gizmoNdc = Vector4(
(((gizmoScreenSpace.y / viewport.height)) - 0.5) * -2, gizmoNdc.z, 1.0); ((gizmoScreenSpace.x / viewport.width) - 0.5) * 2,
(((gizmoScreenSpace.y / viewport.height)) - 0.5) * -2,
gizmoNdc.z,
1.0);
var gizmoViewSpace = inverseProjectionMatrix * gizmoNdc; var gizmoViewSpace = inverseProjectionMatrix * gizmoNdc;
gizmoViewSpace /= gizmoViewSpace.w; gizmoViewSpace /= gizmoViewSpace.w;
@@ -118,12 +139,79 @@ class _Gizmo {
.setTranslation(gizmoTransform!.getTranslation() + worldSpaceDelta); .setTranslation(gizmoTransform!.getTranslation() + worldSpaceDelta);
await viewer.setTransform(_attachedTo!, gizmoTransform!); await viewer.setTransform(_attachedTo!, gizmoTransform!);
}
_onEntityTransformUpdated Future<void> _updateRotation(Vector2 currentPosition, Vector2 delta) async {
.add((entity: _attachedTo!, transform: gizmoTransform!));
var view = await viewer.getViewAt(0);
var camera = await viewer.getActiveCamera();
var viewport = await view.getViewport();
var projectionMatrix = await viewer.getCameraProjectionMatrix();
var viewMatrix = await camera.getViewMatrix();
// Get gizmo center in screen space
var gizmoPositionWorldSpace = gizmoTransform!.getTranslation();
Vector4 gizmoClipSpace = projectionMatrix *
viewMatrix *
Vector4(gizmoPositionWorldSpace.x, gizmoPositionWorldSpace.y,
gizmoPositionWorldSpace.z, 1.0);
var gizmoNdc = gizmoClipSpace / gizmoClipSpace.w;
var gizmoScreenSpace = Vector2(
((gizmoNdc.x / 2) + 0.5) * viewport.width,
viewport.height - (((gizmoNdc.y / 2) + 0.5) * viewport.height));
// Calculate vectors from gizmo center to previous and current mouse positions
var prevVector = (currentPosition - delta) - gizmoScreenSpace;
var currentVector = currentPosition - gizmoScreenSpace;
// Calculate rotation angle based on the active axis
double rotationAngle = 0.0;
switch (_active) {
case Axis.X:
// For X axis, project onto YZ plane
var prev = Vector2(prevVector.y, -prevVector.x);
var curr = Vector2(currentVector.y, -currentVector.x);
rotationAngle = _getAngleBetweenVectors(prev, curr);
break;
case Axis.Y:
// For Y axis, project onto XZ plane
var prev = Vector2(prevVector.x, -prevVector.y);
var curr = Vector2(currentVector.x, -currentVector.y);
rotationAngle = _getAngleBetweenVectors(prev, curr);
break;
case Axis.Z:
// For Z axis, use screen plane directly
rotationAngle = -1 * _getAngleBetweenVectors(prevVector, currentVector);
break;
default:
return;
}
// Create rotation matrix based on the active axis
var rotationMatrix = Matrix4.identity();
switch (_active) {
case Axis.X:
rotationMatrix.setRotationX(rotationAngle);
break;
case Axis.Y:
rotationMatrix.setRotationY(rotationAngle);
break;
case Axis.Z:
rotationMatrix.setRotationZ(rotationAngle);
break;
default:
return;
}
// Apply rotation to the current transform
gizmoTransform = rotationMatrix * gizmoTransform!;
await viewer.setTransform(_attachedTo!, gizmoTransform!);
} }
} }
class GizmoInputHandler extends InputHandler { class GizmoInputHandler extends InputHandler {
final InputHandler wrapped; final InputHandler wrapped;
final ThermionViewer viewer; final ThermionViewer viewer;
@@ -151,9 +239,9 @@ class GizmoInputHandler extends InputHandler {
final view = await viewer.getViewAt(0); final view = await viewer.getViewAt(0);
var tg = await viewer.createGizmo(view, GizmoType.translation); var tg = await viewer.createGizmo(view, GizmoType.translation);
this.translationGizmo = _Gizmo(tg, viewer); this.translationGizmo = _Gizmo(tg, viewer, GizmoType.translation);
var rg = await viewer.createGizmo(view, GizmoType.rotation); var rg = await viewer.createGizmo(view, GizmoType.rotation);
this.rotationGizmo = _Gizmo(rg, viewer); this.rotationGizmo = _Gizmo(rg, viewer, GizmoType.rotation);
_initialized.complete(true); _initialized.complete(true);
} }

View File

@@ -8,5 +8,5 @@ ROTATION_GIZMO_GLB_PACKAGE:
ROTATION_GIZMO_GLB_ROTATION_GIZMO_OFFSET: ROTATION_GIZMO_GLB_ROTATION_GIZMO_OFFSET:
.int 0 .int 0
ROTATION_GIZMO_GLB_ROTATION_GIZMO_SIZE: ROTATION_GIZMO_GLB_ROTATION_GIZMO_SIZE:
.int 78876 .int 163152

View File

@@ -8,5 +8,5 @@ _ROTATION_GIZMO_GLB_PACKAGE:
_ROTATION_GIZMO_GLB_ROTATION_GIZMO_OFFSET: _ROTATION_GIZMO_GLB_ROTATION_GIZMO_OFFSET:
.int 0 .int 0
_ROTATION_GIZMO_GLB_ROTATION_GIZMO_SIZE: _ROTATION_GIZMO_GLB_ROTATION_GIZMO_SIZE:
.int 78876 .int 163152

File diff suppressed because it is too large Load Diff

View File

@@ -155,7 +155,7 @@ namespace thermion
View *_view; View *_view;
Material *_material; Material *_material;
float _scale = 8.0f; float _scale = 10.0f;
utils::Entity _parent; utils::Entity _parent;
std::vector<SceneAsset *> _axes; std::vector<SceneAsset *> _axes;