Files
2025-09-18 17:55:52 -04:00

677 lines
20 KiB
C++

/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#include "stdafx.h"
#include "Tool/AdvMoveTool.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/PartInstance.h"
#include "Tool/Dragger.h"
#include "V8World/World.h"
#include "V8World/ContactManager.h"
#include "Util/Math.h"
#include "Util/HitTest.h"
#include "Util/Sound.h"
#include "AppDraw/DrawAdorn.h"
#include "v8datamodel/changehistory.h"
// TODO - move rotation specific stuff to AdvRotateTool
// TODO - move generic stuff down to the Arrow layer to reduce some redundancy with
// non-adv draggers
DYNAMIC_FASTFLAG(RestoreTransparencyOnToolChange)
FASTFLAG(StudioUseDraggerGrid)
DYNAMIC_FASTFLAGVARIABLE(DraggerUsesNewPartOnDuplicate, false)
namespace RBX {
const char* const sAdvMoveTool = "AdvMove";
static const float MinimumExtentsSize = 0.4f;
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
AdvMoveToolBase::AdvMoveToolBase(Workspace* workspace)
: AdvArrowToolBase(workspace)
, dragging(false)
, cursor("advCursor-default")
, overHandleNormalId(NORM_UNDEFINED)
, mExtents(Vector3::zero(),Vector3::zero())
, mMultiRotation(Matrix3::identity())
, mInitializedExtents(false)
{
}
void AdvMoveToolBase::onMouseHover(const shared_ptr<InputObject>& inputObject)
{
Super::onMouseHover(inputObject);
onMouseIdle(inputObject);
}
void AdvMoveToolBase::onMouseIdle(const shared_ptr<InputObject>& inputObject)
{
Super::onMouseIdle(inputObject);
if (dragging) {
;
}
else if (captured()) {
cursor = "advClosed-hand";
}
else if (getOverHandle(inputObject)) {
cursor = "advCursor-openedHand";
}
else {
cursor = Super::getCursorName();
}
}
shared_ptr<MouseCommand> AdvMoveToolBase::onMouseDown(const shared_ptr<InputObject>& inputObject)
{
NormalId normalId;
Vector3 tempPt;
Extents extents;
overHandleNormalId = NORM_UNDEFINED;
CoordinateFrame location;
bool isLocal = false;
getExtentsAndLocation(extents, location, isLocal);
if (getOverHandle(inputObject, tempPt, normalId))
{
if (DFFlag::DraggerUsesNewPartOnDuplicate)
{
RBX::Selection* selection = ServiceProvider::find<Selection>(workspace);
if (selection)
selectionChangedConnection = selection->selectionChanged.connect(boost::bind(&RBX::AdvMoveToolBase::setToSelection, this));
}
downPoint2d = inputObject->get2DPosition();
dragAxis = (int)(normalId) % 3;
dragAxisDirection = (normalId > 2) ? -1 : 1;
dragNormalId = normalId;
lastPoint3d = tempPt;
dragRay = Ray::fromOriginAndDirection(lastPoint3d, location.vectorToWorldSpace(normalIdToVector3(normalId)));
ServiceClient< FilteredSelection<Instance> > instanceSelection(workspace);
PartArray partArray;
DragUtilities::instancesToParts(instanceSelection->items(), partArray);
megaDragger.reset(new MegaDragger(
NULL,
partArray,
workspace) );
megaDragger->startDragging();
//IMPORTANT: Transparency modification should be done only after startDragging as it will update world correctly else issues with ChangeHistory
//if collision checks are turned off then make parts transparent after saving the original values
if (!RBX::AdvArrowTool::advCollisionCheckMode)
saveAndModifyPartsTransparency();
capture();
return shared_from(this);
}
else {
return Super::onMouseDown(inputObject);
}
}
void AdvMoveToolBase::setToSelection()
{
if (DFFlag::RestoreTransparencyOnToolChange)
AdvArrowToolBase::restoreSavedPartsTransparency();
megaDragger->setToSelection(workspace);
if (DFFlag::RestoreTransparencyOnToolChange && !RBX::AdvArrowTool::advCollisionCheckMode)
saveAndModifyPartsTransparency();
}
float AdvMoveToolBase::snapRotationAngle(float Angle) const
{
if ( AdvArrowToolBase::advGridMode == DRAG::ONE_STUD )
{
float increment = Math::pif() / 4;
Angle = floorf(Angle / increment) * increment;
}
else if ( AdvArrowToolBase::advGridMode == DRAG::QUARTER_STUD )
{
float increment = Math::pif() / 12;
Angle = floorf(Angle / increment) * increment;
}
return Angle;
}
void AdvMoveToolBase::onMouseMove(const shared_ptr<InputObject>& inputObject)
{
RBXASSERT(captured());
Super::onMouseMove(inputObject);
if ( !dragging
&& ((inputObject->get2DPosition() - downPoint2d).length() > 4))
{
megaDragger->alignAndCleanParts();
dragging = true;
}
if ( dragging
&& megaDragger->anyDragPartAlive())
{
if ( getDragType() == HANDLE_MOVE )
{
Vector3 closePoint = Math::closestPointOnRay(dragRay, getUnitMouseRay(inputObject));
Vector3 delta = closePoint - lastPoint3d; // snap point if required
bool isLocal = getLocalSpaceMode();
if (isLocal)
{
delta = DragUtilities::toLocalGrid(delta);
}
else
{
delta = DragUtilities::toGrid(delta);
}
if (delta != Vector3::zero())
{
megaDragger->continueDragging(); // do this every time - multiplayer
Vector3 move(delta);
if (!RBX::AdvArrowTool::advCollisionCheckMode)
megaDragger->moveAlongLine(delta);
else
move = megaDragger->safeMoveAlongLine(delta, !isLocal);
if (move != Vector3::zero())
{
lastPoint3d = lastPoint3d + move;
if (!isLocal)
lastPoint3d = DragUtilities::toGrid(lastPoint3d);
}
}
}
else if ( getDragType() == HANDLE_ROTATE )
{
Extents extents;
CoordinateFrame owner_frame;
bool is_local = false;
if ( !getExtentsAndLocation(extents,owner_frame,is_local) )
return;
// owner position and orientation
const Vector3& owner_translation = owner_frame.translation;
const Matrix3& owner_rotation = owner_frame.rotation;
// compute pick ray
const RbxRay mouse_ray = getUnitMouseRay(inputObject);
const Vector3 mouse_screen(inputObject->get2DPosition().x,inputObject->get2DPosition().y,0);
// project owner to screen space
const Camera& camera = *workspace->getConstCamera();
const Vector3 owner_screen = camera.project(owner_translation);
// project adjusted pick ray to screen space
const Vector3 screen_diff = mouse_screen - owner_screen;
const Vector3 mid_screen = owner_screen + screen_diff / 2;
const RbxRay projection_ray = camera.worldRay(mid_screen.x,mid_screen.y);
const Vector3 axis_vector = normalIdToVector3(dragNormalId);
// determine plane normal
Vector3 plane_normal;
if ( is_local )
plane_normal = normalize(owner_rotation * axis_vector);
else
plane_normal = axis_vector;
// compute intersection of pick ray to axis plane
Vector3 plane_hit;
const Plane plane(plane_normal,owner_translation);
Math::intersectRayPlane(projection_ray,plane,plane_hit);
// compute new vector
Vector3 new_vector = plane_hit - owner_translation;
if ( Math::isNanInfVector3(new_vector) )
return;
// compute new rotation matrix from new vector
Vector3 right;
Vector3 up;
Vector3 at;
const NormalId pos_normal = intToNormalId(dragNormalId % 3);
const int pos_neg = dragNormalId == pos_normal ? 1.0f : -1.0f;
switch ( pos_normal )
{
case NORM_X:
right = normalize(owner_rotation.column(0));
at = normalize(pos_neg * new_vector);
up = normalize(at.cross(right));
break;
case NORM_Y:
up = normalize(owner_rotation.column(1));
right = normalize(pos_neg * new_vector);
at = normalize(right.cross(up));
break;
case NORM_Z:
at = normalize(owner_rotation.column(2));
up = normalize(pos_neg * new_vector);
right = normalize(up.cross(at));
break;
default:
RBXASSERT(false);
}
Matrix3 rotation(
right.x,up.x,at.x,
right.y,up.y,at.y,
right.z,up.z,at.z );
// compute delta rotation
const Matrix3 rotate_by = owner_rotation.inverse() * rotation;
// determine angle of rotation so that we can snap
Vector3 rotation_axis;
float rotation_angle;
rotate_by.toAxisAngle(rotation_axis,rotation_angle);
const float snaped_angle = snapRotationAngle(rotation_angle);
// only rotate with a minimum angle
if ( snaped_angle > 0.001f )
{
Matrix3 snapped_rotate = Matrix3::fromAxisAngle(rotation_axis,snaped_angle);
snapped_rotate = megaDragger->rotateDragParts(snapped_rotate, RBX::AdvArrowTool::advCollisionCheckMode);
if ( !is_local )
{
mMultiRotation *= snapped_rotate;
Math::orthonormalizeIfNecessary(mMultiRotation);
}
}
}
}
}
shared_ptr<MouseCommand> AdvMoveToolBase::onMouseUp(const shared_ptr<InputObject>& inputObject)
{
//restore transparency
if (DFFlag::RestoreTransparencyOnToolChange)
AdvArrowToolBase::restoreSavedPartsTransparency();
else if (!RBX::AdvArrowTool::advCollisionCheckMode)
restoreSavedPartsTransparency();
//make sure if megadragger is present, in case of CTRL+Select, it doesn't get created
if (megaDragger.get())
{
megaDragger->finishDragging();
megaDragger.release();
}
if (DFFlag::DraggerUsesNewPartOnDuplicate && selectionChangedConnection.connected())
selectionChangedConnection.disconnect();
dragging = false;
releaseCapture();
//baseclass will take care of setting waypoint for undo/redo
Super::onMouseUp(inputObject);
return shared_ptr<MouseCommand>();
}
shared_ptr<MouseCommand> AdvMoveToolBase::onKeyDown(const shared_ptr<InputObject>& inputObject)
{
Super::onKeyDown(inputObject);
return shared_from(this);
}
void AdvMoveToolBase::render2d(Adorn* adorn)
{
Extents worldExtents;
CoordinateFrame location;
bool isLocal = false;
if (getExtentsAndLocation(worldExtents, location, isLocal))
{
RBX::DrawAdorn::handles2d(
worldExtents.size(),
location,
*(workspace->getConstCamera()),
adorn,
HANDLE_MOVE,
DrawAdorn::resizeColor());
if (isLocal)
{
RBX::DrawAdorn::partInfoText2D(
worldExtents.size(),
location,
*(workspace->getConstCamera()),
adorn,
"L",
DrawAdorn::cornflowerblue);
}
}
}
void AdvMoveToolBase::render3dAdorn(Adorn* adorn)
{
renderHoverOver(adorn);
Extents worldExtents;
CoordinateFrame location;
bool isLocal = false;
if (getExtentsAndLocation(worldExtents, location, isLocal))
{
RBX::DrawAdorn::handles3d(
worldExtents.size(),
location,
adorn,
HANDLE_MOVE,
workspace->getConstCamera()->coordinateFrame().translation,
DrawAdorn::resizeColor(),
true,
NORM_ALL_MASK,
overHandleNormalId,
Color3(0.3f, 0.7f, 1.0f));
}
}
bool AdvMoveToolBase::getExtentsAndLocation(
Extents& extents,
CoordinateFrame& location,
bool& isLocal ) const
{
ServiceClient<Selection> selection(workspace);
std::vector<Primitive*> primitives;
std::for_each(
selection->begin(),
selection->end(),
boost::bind(
&DragUtilities::getPrimitives2,
_1,
boost::ref(primitives)) );
if ( primitives.empty() )
return false;
if ( primitives.size() == 1 && getLocalSpaceMode())
{
isLocal = true;
PartInstance* part = PartInstance::fromPrimitive(primitives[0]);
if ( !part )
return false;
extents = part->getPartPrimitive()->getExtentsLocal();
if ( extents.longestSide() < MinimumExtentsSize )
extents = Extents::fromCenterRadius(extents.center(),MinimumExtentsSize / 2);
location = part->getLocation();
}
else
{
isLocal = false;
if (getDragType() == HANDLE_ROTATE || getDragType() == HANDLE_MOVE)
{
Extents newExtents;
CoordinateFrame primaryPartLocation;
if (getDragType() == HANDLE_ROTATE)
{
PartInstance* part = Dragger::computePrimaryPart(primitives);
primaryPartLocation = part->getCoordinateFrame();
newExtents = Dragger::computeExtentsRelative(primitives, primaryPartLocation);
}
else
{
newExtents = Dragger::computeExtents(primitives);
}
// The minimum distance between two different world space pivots
// to cause a new extents bounds to be calculated.
const float minDistance = 0.01f;
if ((dragging && getDragType() == HANDLE_MOVE) || !mInitializedExtents ||
(newExtents.center() - mExtents.center()).length() > minDistance)
{
mInitializedExtents = true;
mExtents = newExtents;
if ( mExtents.longestSide() < MinimumExtentsSize )
mExtents = Extents::fromCenterRadius(mExtents.center(),MinimumExtentsSize / 2);
}
extents = mExtents;
location.rotation = mMultiRotation;
if (getDragType() == HANDLE_ROTATE)
{
location.translation = primaryPartLocation.pointToWorldSpace(mExtents.center());
}
else
{
location.translation = mExtents.center();
}
}
else
{
location.translation = extents.center();
location.rotation = Matrix3::identity();
}
}
return true;
}
bool AdvMoveToolBase::getExtents(Extents& extents) const
{
ServiceClient<Selection> selection(workspace);
std::vector<Primitive*> primitives;
std::for_each(
selection->begin(),
selection->end(),
boost::bind( &DragUtilities::getPrimitives2,
_1,
boost::ref(primitives))
);
if (primitives.size() > 0)
{
extents = Dragger::computeExtents(primitives);
return true;
}
else {
return false;
}
}
bool AdvMoveToolBase::getOverHandle(const shared_ptr<InputObject>& inputObject) const
{
Vector3 tempV;
NormalId tempN;
return getOverHandle(inputObject, tempV, tempN);
}
bool AdvMoveToolBase::getOverHandle(const shared_ptr<InputObject>& inputObject, Vector3& hitPointWorld, NormalId& normalId) const
{
RBXASSERT(!captured());
Extents extents;
overHandleNormalId = NORM_UNDEFINED;
CoordinateFrame location;
bool isLocal = false;
if (getExtentsAndLocation(extents, location, isLocal))
{
if (!isLocal)
extents = Extents::fromCenterCorner(Vector3::zero(),extents.size() * 0.5f);
if (HandleHitTest::hitTestHandleLocal(extents,
location,
HANDLE_MOVE,
this->getUnitMouseRay(inputObject),
hitPointWorld,
normalId)) {
overHandleNormalId = normalId;
return true;
}
}
return false;
}
bool AdvMoveToolBase::getLocalSpaceMode() const
{
return AdvMoveTool::advLocalTranslationMode;
}
void AdvMoveToolBase::saveAndModifyPartsTransparency()
{
PartArray allSelectedParts;
ServiceClient< FilteredSelection<PVInstance> > selectedPVInstances(workspace);
DragUtilities::pvsToParts(selectedPVInstances->items(), allSelectedParts);
for (size_t ii = 0; ii < allSelectedParts.size(); ++ii)
{
const shared_ptr<PartInstance>& spPart = allSelectedParts[ii].lock();
if (PartInstance::nonNullInWorkspace(spPart))
{
if (DFFlag::RestoreTransparencyOnToolChange)
AdvArrowToolBase::savePartTransparency(spPart);
else
{
origPartsTransparency[spPart] = spPart->getTransparencyUi();
spPart->setTransparency(0.5f);
}
}
}
}
void AdvMoveToolBase::restoreSavedPartsTransparency()
{
for (PartsTransparencyCollection::const_iterator it = origPartsTransparency.begin(); it != origPartsTransparency.end(); ++it)
{
if (!it->first.expired())
it->first.lock()->setTransparency(it->second);
}
origPartsTransparency.clear();
}
shared_ptr<MouseCommand> AdvMoveTool::onMouseDown(const shared_ptr<InputObject>& inputObject)
{
ServiceClient<Selection> selection(workspace);
std::vector<Primitive*> primitives;
std::for_each(selection->begin(), selection->end(), boost::bind(&DragUtilities::getPrimitives2,_1, boost::ref(primitives)));
if( primitives.size() > 0 )
originalLocation = primitives[0]->getCoordinateFrame().translation;
return AdvMoveToolBase::onMouseDown(inputObject);
}
void AdvMoveTool::render2d(Adorn* adorn)
{
AdvMoveToolBase::render2d(adorn);
if (FFlag::StudioUseDraggerGrid && showDraggerGrid && dragging && (AdvArrowToolBase::advGridMode != DRAG::OFF))
{
ServiceClient<Selection> selection(workspace);
std::vector<Primitive*> primitives;
std::for_each(selection->begin(), selection->end(), boost::bind(&DragUtilities::getPrimitives2, _1, boost::ref(primitives)));
if (primitives.size() > 0)
{
G3D::Color3 gridColor = Color3::gray();
int boxesPerStud = 1;
if( AdvArrowToolBase::advGridMode == DRAG::QUARTER_STUD )
boxesPerStud = 5;
Extents extents = Dragger::computeExtents(primitives);
float effectiveRadius = extents.longestSide();
Vector4 bounds(effectiveRadius, effectiveRadius, -effectiveRadius, -effectiveRadius);
//compute the best plane for grid drawing based upon camera direction
PartInstance* part = PartInstance::fromPrimitive(primitives[0]);
G3D::Vector3 gridXDir, gridYDir;
getGridXYUsingCamera(part, gridXDir, gridYDir);
CoordinateFrame cordFrame(originalLocation);
DrawAdorn::surfaceGridAtCoord(adorn, cordFrame, bounds, gridXDir, gridYDir, gridColor, boxesPerStud);
}
}
}
void AdvMoveTool::getGridXYUsingCamera(RBX::PartInstance *part, G3D::Vector3 &gridXDir, G3D::Vector3 &gridYDir)
{
Vector3 cameraDirection = workspace->getConstCamera()->getCameraCoordinateFrame().vectorToWorldSpace(Vector3::unitZ());
Vector3 bodyXWorld = part->getCoordinateFrame().vectorToWorldSpace(Vector3::unitX());
Vector3 bodyYWorld = part->getCoordinateFrame().vectorToWorldSpace(Vector3::unitY());
Vector3 bodyZWorld = part->getCoordinateFrame().vectorToWorldSpace(Vector3::unitZ());
switch(dragAxis)
{
case NORM_X:
case NORM_X_NEG:
if(fabs(bodyZWorld.dot(cameraDirection)) > fabs(bodyYWorld.dot(cameraDirection)))
{
gridXDir = Vector3::unitX();
gridYDir = Vector3::unitY();
}
else
{
gridXDir = Vector3::unitZ();
gridYDir = Vector3::unitX();
}
break;
case NORM_Y:
case NORM_Y_NEG:
if( fabs(bodyZWorld.dot(cameraDirection)) > fabs(bodyXWorld.dot(cameraDirection)) )
{
gridXDir = Vector3::unitX();
gridYDir = Vector3::unitY();
}
else
{
gridXDir = Vector3::unitY();
gridYDir = Vector3::unitZ();
}
break;
case NORM_Z:
case NORM_Z_NEG:
if( fabs(bodyYWorld.dot(cameraDirection)) > fabs(bodyXWorld.dot(cameraDirection)) )
{
gridXDir = Vector3::unitZ();
gridYDir = Vector3::unitX();
}
else
{
gridXDir = Vector3::unitY();
gridYDir = Vector3::unitZ();
}
break;
}
}
} // namespace