feat: expand editor scripting asset and viewport flow

This commit is contained in:
2026-04-03 13:22:30 +08:00
parent ed8c27fde2
commit a05d0b80a2
124 changed files with 10397 additions and 1737 deletions

View File

@@ -14,7 +14,7 @@ namespace {
constexpr float kMoveGizmoHandleLengthPixels = 100.0f;
constexpr float kMoveGizmoHoverThresholdPixels = 10.0f;
Math::Vector3 GetAxisVector(SceneViewportGizmoAxis axis) {
Math::Vector3 GetBaseAxisVector(SceneViewportGizmoAxis axis) {
switch (axis) {
case SceneViewportGizmoAxis::X:
return Math::Vector3::Right();
@@ -28,6 +28,16 @@ Math::Vector3 GetAxisVector(SceneViewportGizmoAxis axis) {
}
}
Math::Vector3 GetAxisVector(
SceneViewportGizmoAxis axis,
const Math::Quaternion& orientation) {
const Math::Vector3 baseAxis = GetBaseAxisVector(axis);
const Math::Vector3 orientedAxis = orientation * baseAxis;
return orientedAxis.SqrMagnitude() <= Math::EPSILON
? baseAxis
: orientedAxis.Normalized();
}
Math::Color GetAxisBaseColor(SceneViewportGizmoAxis axis) {
switch (axis) {
case SceneViewportGizmoAxis::X:
@@ -61,20 +71,21 @@ SceneViewportGizmoPlane GetPlaneForIndex(size_t index) {
void GetPlaneAxes(
SceneViewportGizmoPlane plane,
const Math::Quaternion& orientation,
Math::Vector3& outAxisA,
Math::Vector3& outAxisB) {
switch (plane) {
case SceneViewportGizmoPlane::XY:
outAxisA = Math::Vector3::Right();
outAxisB = Math::Vector3::Up();
outAxisA = GetAxisVector(SceneViewportGizmoAxis::X, orientation);
outAxisB = GetAxisVector(SceneViewportGizmoAxis::Y, orientation);
return;
case SceneViewportGizmoPlane::XZ:
outAxisA = Math::Vector3::Right();
outAxisB = Math::Vector3::Forward();
outAxisA = GetAxisVector(SceneViewportGizmoAxis::X, orientation);
outAxisB = GetAxisVector(SceneViewportGizmoAxis::Z, orientation);
return;
case SceneViewportGizmoPlane::YZ:
outAxisA = Math::Vector3::Up();
outAxisB = Math::Vector3::Forward();
outAxisA = GetAxisVector(SceneViewportGizmoAxis::Y, orientation);
outAxisB = GetAxisVector(SceneViewportGizmoAxis::Z, orientation);
return;
case SceneViewportGizmoPlane::None:
default:
@@ -84,14 +95,16 @@ void GetPlaneAxes(
}
}
Math::Vector3 GetPlaneNormal(SceneViewportGizmoPlane plane) {
Math::Vector3 GetPlaneNormal(
SceneViewportGizmoPlane plane,
const Math::Quaternion& orientation) {
switch (plane) {
case SceneViewportGizmoPlane::XY:
return Math::Vector3::Forward();
return GetAxisVector(SceneViewportGizmoAxis::Z, orientation);
case SceneViewportGizmoPlane::XZ:
return Math::Vector3::Up();
return GetAxisVector(SceneViewportGizmoAxis::Y, orientation);
case SceneViewportGizmoPlane::YZ:
return Math::Vector3::Right();
return GetAxisVector(SceneViewportGizmoAxis::X, orientation);
case SceneViewportGizmoPlane::None:
default:
return Math::Vector3::Zero();
@@ -185,10 +198,9 @@ float ComputeWorldUnitsPerPixel(
void SceneViewportMoveGizmo::Update(const SceneViewportMoveGizmoContext& context) {
BuildDrawData(context);
if (m_dragMode == DragMode::None && IsMouseInsideViewport(context)) {
m_hoveredAxis = HitTestAxis(context.mousePosition);
m_hoveredPlane = m_hoveredAxis == SceneViewportGizmoAxis::None
? HitTestPlane(context.mousePosition)
: SceneViewportGizmoPlane::None;
const SceneViewportMoveGizmoHitResult hitResult = EvaluateHit(context.mousePosition);
m_hoveredAxis = hitResult.axis;
m_hoveredPlane = hitResult.plane;
} else if (m_dragMode == DragMode::None) {
m_hoveredAxis = SceneViewportGizmoAxis::None;
m_hoveredPlane = SceneViewportGizmoPlane::None;
@@ -221,18 +233,17 @@ bool SceneViewportMoveGizmo::TryBeginDrag(const SceneViewportMoveGizmoContext& c
return false;
}
const Math::Vector3 objectWorldPosition = context.selectedObject->GetTransform()->GetPosition();
const Math::Vector3 pivotWorldPosition = context.selectedObject->GetTransform()->GetPosition();
const Math::Vector3 pivotWorldPosition = context.pivotWorldPosition;
Math::Vector3 dragPlaneNormal = Math::Vector3::Zero();
Math::Vector3 worldAxis = Math::Vector3::Zero();
if (m_hoveredAxis != SceneViewportGizmoAxis::None) {
worldAxis = GetAxisVector(m_hoveredAxis);
worldAxis = GetAxisVector(m_hoveredAxis, context.axisOrientation);
if (!BuildSceneViewportAxisDragPlaneNormal(context.overlay, worldAxis, dragPlaneNormal)) {
return false;
}
} else {
dragPlaneNormal = GetPlaneNormal(m_hoveredPlane);
dragPlaneNormal = GetPlaneNormal(m_hoveredPlane, context.axisOrientation);
if (dragPlaneNormal.SqrMagnitude() <= Math::EPSILON) {
return false;
}
@@ -257,10 +268,21 @@ bool SceneViewportMoveGizmo::TryBeginDrag(const SceneViewportMoveGizmoContext& c
m_activeAxisDirection = worldAxis;
m_activePlaneNormal = dragPlaneNormal;
m_dragPlane = dragPlane;
m_dragStartObjectWorldPosition = objectWorldPosition;
m_dragStartPivotWorldPosition = pivotWorldPosition;
m_dragStartHitWorldPosition = hitPoint;
m_dragStartAxisScalar = Math::Vector3::Dot(hitPoint - pivotWorldPosition, worldAxis);
m_dragObjects = context.selectedObjects;
if (m_dragObjects.empty()) {
m_dragObjects.push_back(context.selectedObject);
}
m_dragStartObjectWorldPositions.clear();
m_dragStartObjectWorldPositions.reserve(m_dragObjects.size());
for (Components::GameObject* gameObject : m_dragObjects) {
m_dragStartObjectWorldPositions.push_back(
gameObject != nullptr && gameObject->GetTransform() != nullptr
? gameObject->GetTransform()->GetPosition()
: Math::Vector3::Zero());
}
RefreshHandleState();
return true;
}
@@ -268,7 +290,9 @@ bool SceneViewportMoveGizmo::TryBeginDrag(const SceneViewportMoveGizmoContext& c
void SceneViewportMoveGizmo::UpdateDrag(const SceneViewportMoveGizmoContext& context) {
if (m_dragMode == DragMode::None ||
context.selectedObject == nullptr ||
context.selectedObject->GetID() != m_activeEntityId) {
context.selectedObject->GetID() != m_activeEntityId ||
m_dragObjects.empty() ||
m_dragObjects.size() != m_dragStartObjectWorldPositions.size()) {
return;
}
@@ -290,17 +314,28 @@ void SceneViewportMoveGizmo::UpdateDrag(const SceneViewportMoveGizmoContext& con
if (m_dragMode == DragMode::Axis) {
const float currentAxisScalar = Math::Vector3::Dot(hitPoint - m_dragStartPivotWorldPosition, m_activeAxisDirection);
const float deltaScalar = currentAxisScalar - m_dragStartAxisScalar;
context.selectedObject->GetTransform()->SetPosition(
m_dragStartObjectWorldPosition + m_activeAxisDirection * deltaScalar);
const Math::Vector3 worldDelta = m_activeAxisDirection * deltaScalar;
for (size_t index = 0; index < m_dragObjects.size(); ++index) {
if (m_dragObjects[index] == nullptr || m_dragObjects[index]->GetTransform() == nullptr) {
continue;
}
m_dragObjects[index]->GetTransform()->SetPosition(
m_dragStartObjectWorldPositions[index] + worldDelta);
}
return;
}
if (m_dragMode == DragMode::Plane) {
const Math::Vector3 planeDelta = Math::Vector3::ProjectOnPlane(
const Math::Vector3 worldDelta = Math::Vector3::ProjectOnPlane(
hitPoint - m_dragStartHitWorldPosition,
m_activePlaneNormal);
context.selectedObject->GetTransform()->SetPosition(
m_dragStartObjectWorldPosition + planeDelta);
for (size_t index = 0; index < m_dragObjects.size(); ++index) {
if (m_dragObjects[index] == nullptr || m_dragObjects[index]->GetTransform() == nullptr) {
continue;
}
m_dragObjects[index]->GetTransform()->SetPosition(
m_dragStartObjectWorldPositions[index] + worldDelta);
}
}
}
@@ -319,10 +354,11 @@ void SceneViewportMoveGizmo::EndDrag(IUndoManager& undoManager) {
m_activeEntityId = 0;
m_activeAxisDirection = Math::Vector3::Zero();
m_activePlaneNormal = Math::Vector3::Zero();
m_dragStartObjectWorldPosition = Math::Vector3::Zero();
m_dragStartPivotWorldPosition = Math::Vector3::Zero();
m_dragStartHitWorldPosition = Math::Vector3::Zero();
m_dragStartAxisScalar = 0.0f;
m_dragObjects.clear();
m_dragStartObjectWorldPositions.clear();
RefreshHandleState();
}
@@ -337,10 +373,11 @@ void SceneViewportMoveGizmo::CancelDrag(IUndoManager* undoManager) {
m_activeEntityId = 0;
m_activeAxisDirection = Math::Vector3::Zero();
m_activePlaneNormal = Math::Vector3::Zero();
m_dragStartObjectWorldPosition = Math::Vector3::Zero();
m_dragStartPivotWorldPosition = Math::Vector3::Zero();
m_dragStartHitWorldPosition = Math::Vector3::Zero();
m_dragStartAxisScalar = 0.0f;
m_dragObjects.clear();
m_dragStartObjectWorldPositions.clear();
m_hoveredAxis = SceneViewportGizmoAxis::None;
m_hoveredPlane = SceneViewportGizmoPlane::None;
RefreshHandleState();
@@ -363,19 +400,74 @@ const SceneViewportMoveGizmoDrawData& SceneViewportMoveGizmo::GetDrawData() cons
return m_drawData;
}
SceneViewportMoveGizmoHitResult SceneViewportMoveGizmo::EvaluateHit(const Math::Vector2& mousePosition) const {
SceneViewportMoveGizmoHitResult result = {};
if (!m_drawData.visible) {
return result;
}
const float hoverThresholdSq = kMoveGizmoHoverThresholdPixels * kMoveGizmoHoverThresholdPixels;
for (const SceneViewportMoveGizmoHandleDrawData& handle : m_drawData.handles) {
if (!handle.visible) {
continue;
}
const float distanceSq = DistanceToSegmentSquared(mousePosition, handle.start, handle.end);
if (distanceSq > result.distanceSq || distanceSq > hoverThresholdSq) {
continue;
}
result.axis = handle.axis;
result.plane = SceneViewportGizmoPlane::None;
result.distanceSq = distanceSq;
}
if (result.axis != SceneViewportGizmoAxis::None) {
return result;
}
for (const SceneViewportMoveGizmoPlaneDrawData& plane : m_drawData.planes) {
if (!plane.visible || !PointInQuad(mousePosition, plane.corners)) {
continue;
}
const float distanceSq = (QuadCenter(plane.corners) - mousePosition).SqrMagnitude();
if (distanceSq >= result.distanceSq) {
continue;
}
result.axis = SceneViewportGizmoAxis::None;
result.plane = plane.plane;
result.distanceSq = distanceSq;
}
return result;
}
void SceneViewportMoveGizmo::SetHoveredHandle(
SceneViewportGizmoAxis axis,
SceneViewportGizmoPlane plane) {
if (m_dragMode != DragMode::None) {
return;
}
m_hoveredAxis = axis;
m_hoveredPlane = axis == SceneViewportGizmoAxis::None ? plane : SceneViewportGizmoPlane::None;
RefreshHandleState();
}
void SceneViewportMoveGizmo::BuildDrawData(const SceneViewportMoveGizmoContext& context) {
m_drawData = {};
m_drawData.pivotRadius = 5.0f;
const Components::GameObject* selectedObject = context.selectedObject;
if (selectedObject == nullptr ||
if ((context.selectedObject == nullptr && context.selectedObjects.empty()) ||
!context.overlay.valid ||
context.viewportSize.x <= 1.0f ||
context.viewportSize.y <= 1.0f) {
return;
}
const Math::Vector3 gizmoWorldOrigin = selectedObject->GetTransform()->GetPosition();
const Math::Vector3 gizmoWorldOrigin = context.pivotWorldPosition;
const SceneViewportProjectedPoint projectedPivot = ProjectSceneViewportWorldPoint(
context.overlay,
context.viewportSize.x,
@@ -411,7 +503,7 @@ void SceneViewportMoveGizmo::BuildDrawData(const SceneViewportMoveGizmoContext&
handle.start = projectedPivot.screenPosition;
const Math::Vector3 axisEndWorld =
gizmoWorldOrigin + GetAxisVector(handle.axis) * axisLengthWorld;
gizmoWorldOrigin + GetAxisVector(handle.axis, context.axisOrientation) * axisLengthWorld;
const SceneViewportProjectedPoint projectedEnd = ProjectSceneViewportWorldPoint(
context.overlay,
context.viewportSize.x,
@@ -436,7 +528,7 @@ void SceneViewportMoveGizmo::BuildDrawData(const SceneViewportMoveGizmoContext&
Math::Vector3 axisA = Math::Vector3::Zero();
Math::Vector3 axisB = Math::Vector3::Zero();
GetPlaneAxes(plane.plane, axisA, axisB);
GetPlaneAxes(plane.plane, context.axisOrientation, axisA, axisB);
if (axisA.SqrMagnitude() <= Math::EPSILON || axisB.SqrMagnitude() <= Math::EPSILON) {
continue;
}
@@ -502,55 +594,5 @@ void SceneViewportMoveGizmo::RefreshHandleState() {
}
}
SceneViewportGizmoAxis SceneViewportMoveGizmo::HitTestAxis(const Math::Vector2& mousePosition) const {
if (!m_drawData.visible) {
return SceneViewportGizmoAxis::None;
}
const float hoverThresholdSq = kMoveGizmoHoverThresholdPixels * kMoveGizmoHoverThresholdPixels;
SceneViewportGizmoAxis bestAxis = SceneViewportGizmoAxis::None;
float bestDistanceSq = hoverThresholdSq;
for (const SceneViewportMoveGizmoHandleDrawData& handle : m_drawData.handles) {
if (!handle.visible) {
continue;
}
const float distanceSq = DistanceToSegmentSquared(mousePosition, handle.start, handle.end);
if (distanceSq > bestDistanceSq) {
continue;
}
bestDistanceSq = distanceSq;
bestAxis = handle.axis;
}
return bestAxis;
}
SceneViewportGizmoPlane SceneViewportMoveGizmo::HitTestPlane(const Math::Vector2& mousePosition) const {
if (!m_drawData.visible) {
return SceneViewportGizmoPlane::None;
}
SceneViewportGizmoPlane bestPlane = SceneViewportGizmoPlane::None;
float bestDistanceSq = Math::FLOAT_MAX;
for (const SceneViewportMoveGizmoPlaneDrawData& plane : m_drawData.planes) {
if (!plane.visible || !PointInQuad(mousePosition, plane.corners)) {
continue;
}
const float distanceSq = (QuadCenter(plane.corners) - mousePosition).SqrMagnitude();
if (distanceSq >= bestDistanceSq) {
continue;
}
bestDistanceSq = distanceSq;
bestPlane = plane.plane;
}
return bestPlane;
}
} // namespace Editor
} // namespace XCEngine