You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
644 lines
18 KiB
C++
644 lines
18 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
// Description : CSelectionGroup implementation.
|
|
|
|
|
|
#include "EditorDefs.h"
|
|
|
|
#include "SelectionGroup.h"
|
|
|
|
// Editor
|
|
#include "ViewManager.h"
|
|
#include "Include/IObjectManager.h"
|
|
|
|
#include <IStatObj.h>
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CSelectionGroup::CSelectionGroup()
|
|
: m_ref(1)
|
|
, m_bVertexSnapped(false)
|
|
{
|
|
m_LastestMoveSelectionFlag = eMS_None;
|
|
m_LastestMovedObjectRot.SetIdentity();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::AddObject(CBaseObject* obj)
|
|
{
|
|
if (!IsContainObject(obj))
|
|
{
|
|
m_objects.push_back(obj);
|
|
m_objectsSet.insert(obj);
|
|
m_filtered.clear();
|
|
|
|
if (obj->GetType() != OBJTYPE_AZENTITY)
|
|
{
|
|
m_legacyObjectsSet.insert(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::RemoveObject(CBaseObject* obj)
|
|
{
|
|
for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it)
|
|
{
|
|
if (*it == obj)
|
|
{
|
|
m_objects.erase(it);
|
|
m_objectsSet.erase(obj);
|
|
m_legacyObjectsSet.erase(obj);
|
|
m_filtered.clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::RemoveAll()
|
|
{
|
|
m_objects.clear();
|
|
m_objectsSet.clear();
|
|
m_filtered.clear();
|
|
m_legacyObjectsSet.clear();
|
|
}
|
|
|
|
void CSelectionGroup::RemoveAllExceptLegacySet()
|
|
{
|
|
m_objects.clear();
|
|
m_objectsSet.clear();
|
|
m_filtered.clear();
|
|
}
|
|
|
|
bool CSelectionGroup::IsContainObject(CBaseObject* obj)
|
|
{
|
|
return (m_objectsSet.find(obj) != m_objectsSet.end());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CSelectionGroup::IsEmpty() const
|
|
{
|
|
return m_objects.empty();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CSelectionGroup::SameObjectType()
|
|
{
|
|
if (IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
CBaseObjectPtr pFirst = (*(m_objects.begin()));
|
|
for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it)
|
|
{
|
|
if ((*it)->metaObject() != pFirst->metaObject())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CSelectionGroup::GetCount() const
|
|
{
|
|
return m_objects.size();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject* CSelectionGroup::GetObject(int index) const
|
|
{
|
|
assert(index >= 0 && index < m_objects.size());
|
|
return m_objects[index];
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject* CSelectionGroup::GetObjectByGuid(REFGUID guid) const
|
|
{
|
|
for (size_t i = 0, count(m_objects.size()); i < count; ++i)
|
|
{
|
|
if (m_objects[i]->GetId() == guid)
|
|
{
|
|
return m_objects[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
std::set<CBaseObjectPtr>& CSelectionGroup::GetLegacyObjects()
|
|
{
|
|
return m_legacyObjectsSet;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Copy(const CSelectionGroup& from)
|
|
{
|
|
m_name = from.m_name;
|
|
m_objects = from.m_objects;
|
|
m_objectsSet = from.m_objectsSet;
|
|
m_filtered = from.m_filtered;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Vec3 CSelectionGroup::GetCenter() const
|
|
{
|
|
Vec3 c(0, 0, 0);
|
|
for (int i = 0; i < GetCount(); i++)
|
|
{
|
|
c += GetObject(i)->GetWorldPos();
|
|
}
|
|
if (GetCount() > 0)
|
|
{
|
|
c /= GetCount();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
AABB CSelectionGroup::GetBounds() const
|
|
{
|
|
AABB b;
|
|
AABB box;
|
|
box.Reset();
|
|
for (int i = 0; i < GetCount(); i++)
|
|
{
|
|
GetObject(i)->GetBoundBox(b);
|
|
box.Add(b.min);
|
|
box.Add(b.max);
|
|
}
|
|
return box;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::FilterParents()
|
|
{
|
|
if (!m_filtered.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_filtered.reserve(m_objects.size());
|
|
for (int i = 0; i < m_objects.size(); i++)
|
|
{
|
|
CBaseObject* obj = m_objects[i];
|
|
CBaseObject* parent = obj->GetParent();
|
|
bool bParentInSet = false;
|
|
while (parent)
|
|
{
|
|
if (m_objectsSet.find(parent) != m_objectsSet.end())
|
|
{
|
|
bParentInSet = true;
|
|
break;
|
|
}
|
|
parent = parent->GetParent();
|
|
}
|
|
if (!bParentInSet)
|
|
{
|
|
m_filtered.push_back(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Move(const Vec3& offset, EMoveSelectionFlag moveFlag, [[maybe_unused]] int referenceCoordSys, const QPoint& point)
|
|
{
|
|
// [MichaelS - 17/3/2005] Removed this code from the three edit functions (move,
|
|
// rotate and scale). This was causing a bug where the render node of objects
|
|
// was not being updated when objects were dragged away from their position
|
|
// and then back again, since movement is re-calculated from the initial position
|
|
// each mouse message (ie first the previous movement is undone and then the
|
|
// movement is applied). This meant that when moving back to the start position
|
|
// it appeared like no movement was applied, although it was still necessary to
|
|
// update the graphics resources. The object transform is explicitly reset
|
|
// below.
|
|
|
|
//if (offset.x == 0 && offset.y == 0 && offset.z == 0)
|
|
// return;
|
|
|
|
m_bVertexSnapped = false;
|
|
FilterParents();
|
|
Vec3 newPos;
|
|
|
|
bool bValidFollowGeometryMode(true);
|
|
if (point.x() == -1 || point.y() == -1)
|
|
{
|
|
bValidFollowGeometryMode = false;
|
|
}
|
|
|
|
SRayHitInfo pickedInfo;
|
|
|
|
if (moveFlag == eMS_FollowGeometryPosNorm)
|
|
{
|
|
if (m_LastestMoveSelectionFlag != eMS_FollowGeometryPosNorm)
|
|
{
|
|
if (GetFilteredCount() > 0)
|
|
{
|
|
CBaseObject* pObj = GetFilteredObject(0);
|
|
m_LastestMovedObjectRot = pObj->GetRotation();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_LastestMoveSelectionFlag = moveFlag;
|
|
|
|
for (int i = 0; i < GetFilteredCount(); i++)
|
|
{
|
|
CBaseObject* obj = GetFilteredObject(i);
|
|
|
|
if(obj->IsFrozen())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (i == 0 && moveFlag == eMS_FollowGeometryPosNorm && bValidFollowGeometryMode)
|
|
{
|
|
Vec3 zaxis = m_LastestMovedObjectRot * Vec3(0, 0, 1);
|
|
zaxis.Normalize();
|
|
Quat nq;
|
|
nq.SetRotationV0V1(zaxis, pickedInfo.vHitNormal);
|
|
obj->SetPos(pickedInfo.vHitPos);
|
|
obj->SetRotation(nq * m_LastestMovedObjectRot);
|
|
continue;
|
|
}
|
|
|
|
Matrix34 wtm = obj->GetWorldTM();
|
|
Vec3 wp = wtm.GetTranslation();
|
|
|
|
newPos = wp + offset;
|
|
if (moveFlag == eMS_FollowTerrain)
|
|
{
|
|
// Make sure object keeps it height.
|
|
float height = wp.z - GetIEditor()->GetTerrainElevation(wp.x, wp.y);
|
|
newPos.z = GetIEditor()->GetTerrainElevation(newPos.x, newPos.y) + height;
|
|
}
|
|
|
|
obj->SetWorldPos(newPos, eObjectUpdateFlags_UserInput | eObjectUpdateFlags_PositionChanged | eObjectUpdateFlags_MoveTool);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::MoveTo(const Vec3& pos, EMoveSelectionFlag moveFlag, int referenceCoordSys, const QPoint& point)
|
|
{
|
|
FilterParents();
|
|
if (GetFilteredCount() < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CBaseObject* refObj = GetFilteredObject(0);
|
|
CSelectionGroup::Move(pos - refObj->GetWorldTM().GetTranslation(), moveFlag, referenceCoordSys, point);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Rotate(const Quat& qRot, int referenceCoordSys)
|
|
{
|
|
Matrix34 rotateTM;
|
|
rotateTM.SetIdentity();
|
|
rotateTM = Matrix33(qRot) * rotateTM;
|
|
|
|
Rotate(rotateTM, referenceCoordSys);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Rotate(const Ang3& angles, int referenceCoordSys)
|
|
{
|
|
//if (angles.x == 0 && angles.y == 0 && angles.z == 0)
|
|
// return;
|
|
|
|
// Rotate selection about selection center.
|
|
Vec3 center = GetCenter();
|
|
|
|
Matrix34 rotateTM = Matrix34::CreateRotationXYZ(DEG2RAD(angles));
|
|
Rotate(rotateTM, referenceCoordSys);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Rotate(const Matrix34& rotateTM, int referenceCoordSys)
|
|
{
|
|
// Rotate selection about selection center.
|
|
Vec3 center = GetCenter();
|
|
|
|
Matrix34 ToOrigin = Matrix34::CreateIdentity();
|
|
Matrix34 FromOrigin = Matrix34::CreateIdentity();
|
|
|
|
if (referenceCoordSys != COORDS_LOCAL)
|
|
{
|
|
ToOrigin.SetTranslation(-center);
|
|
FromOrigin.SetTranslation(center);
|
|
|
|
if (referenceCoordSys == COORDS_USERDEFINED)
|
|
{
|
|
Matrix34 userTM;
|
|
userTM.SetIdentity();
|
|
Matrix34 invUserTM = userTM.GetInvertedFast();
|
|
|
|
ToOrigin = invUserTM * ToOrigin;
|
|
FromOrigin = FromOrigin * userTM;
|
|
}
|
|
}
|
|
|
|
FilterParents();
|
|
|
|
for (int i = 0; i < GetFilteredCount(); i++)
|
|
{
|
|
CBaseObject* obj = GetFilteredObject(i);
|
|
|
|
Matrix34 objectTransform = obj->GetWorldTM();
|
|
if (referenceCoordSys != COORDS_LOCAL)
|
|
{
|
|
if (referenceCoordSys == COORDS_PARENT && obj->GetParent())
|
|
{
|
|
Matrix34 parentTM = obj->GetParent()->GetWorldTM();
|
|
parentTM.OrthonormalizeFast();
|
|
parentTM.SetTranslation(Vec3(0, 0, 0));
|
|
Matrix34 invParentTM = parentTM.GetInvertedFast();
|
|
|
|
objectTransform = FromOrigin * parentTM * rotateTM * invParentTM * ToOrigin * objectTransform;
|
|
}
|
|
else
|
|
{
|
|
objectTransform = FromOrigin * rotateTM * ToOrigin * objectTransform;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Decompose the matrix and reconstruct it to ensure no scaling artifacts are introduced
|
|
AffineParts affineParts;
|
|
affineParts.SpectralDecompose(objectTransform);
|
|
|
|
Matrix33 rotationMatrix(affineParts.rot);
|
|
Matrix34 translationMatrix = Matrix34::CreateTranslationMat(affineParts.pos);
|
|
Matrix33 scaleMatrix = Matrix33::CreateScale(affineParts.scale);
|
|
|
|
objectTransform = translationMatrix * rotationMatrix * rotateTM * scaleMatrix;
|
|
}
|
|
|
|
obj->SetWorldTM(objectTransform, eObjectUpdateFlags_UserInput);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Scale(const Vec3& scale, int referenceCoordSys)
|
|
{
|
|
//if (scale.x == 1 && scale.y == 1 && scale.z == 1)
|
|
// return;
|
|
|
|
Vec3 scl = scale;
|
|
if (scl.x == 0)
|
|
{
|
|
scl.x = 0.01f;
|
|
}
|
|
if (scl.y == 0)
|
|
{
|
|
scl.y = 0.01f;
|
|
}
|
|
if (scl.z == 0)
|
|
{
|
|
scl.z = 0.01f;
|
|
}
|
|
|
|
// Scale selection relative to selection center.
|
|
Vec3 center = GetCenter();
|
|
|
|
Matrix34 scaleTM;
|
|
scaleTM.SetIdentity();
|
|
scaleTM = Matrix33::CreateScale(Vec3(scl.x, scl.y, scl.z)) * scaleTM;
|
|
|
|
Matrix34 ToOrigin;
|
|
Matrix34 FromOrigin;
|
|
|
|
ToOrigin.SetIdentity();
|
|
FromOrigin.SetIdentity();
|
|
|
|
if (referenceCoordSys != COORDS_LOCAL)
|
|
{
|
|
ToOrigin.SetTranslation(-center);
|
|
FromOrigin.SetTranslation(center);
|
|
}
|
|
|
|
FilterParents();
|
|
|
|
for (int i = 0; i < GetFilteredCount(); i++)
|
|
{
|
|
CBaseObject* obj = GetFilteredObject(i);
|
|
Matrix34 m = obj->GetWorldTM();
|
|
|
|
if (referenceCoordSys != COORDS_LOCAL)
|
|
{
|
|
// Apply new scale
|
|
m = FromOrigin * scaleTM * ToOrigin * m;
|
|
}
|
|
else
|
|
{
|
|
// Apply new scale
|
|
m = m * scaleTM;
|
|
}
|
|
|
|
obj->SetWorldTM(m, eObjectUpdateFlags_UserInput | eObjectUpdateFlags_ScaleTool);
|
|
obj->InvalidateTM(eObjectUpdateFlags_UserInput | eObjectUpdateFlags_ScaleTool);
|
|
}
|
|
}
|
|
|
|
|
|
void CSelectionGroup::SetScale(const Vec3& scale, int referenceCoordSys)
|
|
{
|
|
Vec3 relScale = scale;
|
|
|
|
if (GetCount() > 0 && GetObject(0))
|
|
{
|
|
Vec3 objScale = GetObject(0)->GetScale();
|
|
if (relScale == objScale && (objScale.x == 0.0f || objScale.y == 0.0f || objScale.z == 0.0f))
|
|
{
|
|
return;
|
|
}
|
|
relScale = relScale / objScale;
|
|
}
|
|
|
|
Scale(relScale, referenceCoordSys);
|
|
}
|
|
|
|
|
|
void CSelectionGroup::StartScaling()
|
|
{
|
|
for (int i = 0; i < GetFilteredCount(); i++)
|
|
{
|
|
CBaseObject* obj = GetFilteredObject(i);
|
|
obj->StartScaling();
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Align()
|
|
{
|
|
for (int i = 0; i < GetFilteredCount(); ++i)
|
|
{
|
|
bool terrain = false;
|
|
CBaseObject* obj = GetFilteredObject(i);
|
|
Vec3 pos = obj->GetPos();
|
|
Quat rot = obj->GetRotation();
|
|
QPoint point = GetIEditor()->GetActiveView()->WorldToView(pos);
|
|
Vec3 normal = GetIEditor()->GetActiveView()->ViewToWorldNormal(point, false, true);
|
|
pos = GetIEditor()->GetActiveView()->ViewToWorld(point, &terrain, false, false, true);
|
|
Vec3 zaxis = rot * Vec3(0, 0, 1);
|
|
normal.Normalize();
|
|
zaxis.Normalize();
|
|
Quat nq;
|
|
nq.SetRotationV0V1(zaxis, normal);
|
|
obj->SetRotation(nq * rot);
|
|
obj->SetPos(pos);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Transform(const Vec3& offset, EMoveSelectionFlag moveFlag, const Ang3& angles, const Vec3& scale, int referenceCoordSys)
|
|
{
|
|
if (offset != Vec3(0))
|
|
{
|
|
Move(offset, moveFlag, referenceCoordSys);
|
|
}
|
|
|
|
if (!(angles == Ang3(ZERO)))
|
|
{
|
|
Rotate(angles, referenceCoordSys);
|
|
}
|
|
|
|
if (scale != Vec3(0))
|
|
{
|
|
Scale(scale, referenceCoordSys);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::ResetTransformation()
|
|
{
|
|
FilterParents();
|
|
Quat qIdentity;
|
|
qIdentity.SetIdentity();
|
|
Vec3 vScale(1.0f, 1.0f, 1.0f);
|
|
|
|
for (int i = 0, n = GetFilteredCount(); i < n; ++i)
|
|
{
|
|
CBaseObject* pObj = GetFilteredObject(i);
|
|
pObj->SetRotation(qIdentity);
|
|
pObj->SetScale(vScale);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::Clone(CSelectionGroup& newGroup)
|
|
{
|
|
IObjectManager* pObjMan = GetIEditor()->GetObjectManager();
|
|
assert(pObjMan);
|
|
|
|
int i;
|
|
CObjectCloneContext cloneContext;
|
|
|
|
FilterParents();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Clone every object.
|
|
for (i = 0; i < GetFilteredCount(); i++)
|
|
{
|
|
CBaseObject* pFromObject = GetFilteredObject(i);
|
|
CBaseObject* newObj = pObjMan->CloneObject(pFromObject);
|
|
if (!newObj) // can be null, e.g. sequence can't be cloned
|
|
{
|
|
continue;
|
|
}
|
|
|
|
cloneContext.AddClone(pFromObject, newObj);
|
|
newGroup.AddObject(newObj);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Only after everything was cloned, call PostClone on all cloned objects.
|
|
for (i = 0; i < newGroup.GetCount(); ++i)
|
|
{
|
|
CBaseObject* pFromObject = GetFilteredObject(i);
|
|
CBaseObject* pClonedObject = newGroup.GetObject(i);
|
|
if (pClonedObject)
|
|
{
|
|
pClonedObject->PostClone(pFromObject, cloneContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::SendEvent(ObjectEvent event)
|
|
{
|
|
for (int i = 0; i < m_objects.size(); i++)
|
|
{
|
|
CBaseObject* obj = m_objects[i];
|
|
obj->OnEvent(event);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ULONG STDMETHODCALLTYPE CSelectionGroup::AddRef()
|
|
{
|
|
return ++m_ref;
|
|
};
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ULONG STDMETHODCALLTYPE CSelectionGroup::Release()
|
|
{
|
|
if ((--m_ref) == 0)
|
|
{
|
|
delete this;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return m_ref;
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::IndicateSnappingVertex(DisplayContext& dc) const
|
|
{
|
|
if (m_bVertexSnapped == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dc.DepthTestOff();
|
|
|
|
ColorB green(0, 255, 0, 255);
|
|
|
|
dc.SetColor(green);
|
|
float fScale = dc.view->GetScreenScaleFactor(m_snapVertex) * 0.005f;
|
|
Vec3 sz(fScale, fScale, fScale);
|
|
dc.DrawWireBox(m_snapVertex - sz, m_snapVertex + sz);
|
|
|
|
dc.DepthTestOn();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CSelectionGroup::FinishChanges()
|
|
{
|
|
Objects selectedObjects(m_objects);
|
|
int iObjectSize(selectedObjects.size());
|
|
for (int i = 0; i < iObjectSize; ++i)
|
|
{
|
|
CBaseObject* pObject = selectedObjects[i];
|
|
if (pObject == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|