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.
o3de/Code/Sandbox/Editor/Geometry/EdMesh.cpp

1549 lines
45 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : Implementation of CEdMesh class.
#include "EditorDefs.h"
// CryCommon
#include <CryCommon/CGFContent.h>
// Editor
#include "EdMesh.h"
#include "Viewport.h"
#include "ViewManager.h"
#include "Util/PakFile.h"
#include "Include/HitContext.h"
#include "Include/ITransformManipulator.h"
#include "Objects/ObjectLoader.h"
#include "Undo/IUndoObject.h"
#include "Util/fastlib.h"
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//! Undo object for Editable Mesh.
class CUndoEdMesh
: public IUndoObject
{
public:
CUndoEdMesh(CEdMesh* pEdMesh, int nCopyFlags, const char* undoDescription)
{
// Stores the current state of this object.
assert(pEdMesh != 0);
m_nCopyFlags = nCopyFlags;
m_undoDescription = undoDescription;
m_pEdMesh = pEdMesh;
pEdMesh->CopyToMesh(undoMesh, nCopyFlags);
}
protected:
virtual int GetSize()
{
// sizeof(undoMesh) + sizeof(redoMesh);
return sizeof(*this);
}
virtual QString GetDescription() { return m_undoDescription; };
virtual void Undo(bool bUndo)
{
if (bUndo)
{
m_pEdMesh->CopyToMesh(redoMesh, m_nCopyFlags);
}
// Undo object state.
m_pEdMesh->CopyFromMesh(undoMesh, m_nCopyFlags, bUndo);
}
virtual void Redo()
{
m_pEdMesh->CopyFromMesh(redoMesh, m_nCopyFlags, true);
}
private:
QString m_undoDescription;
int m_nCopyFlags;
_smart_ptr<CEdMesh> m_pEdMesh;
CTriMesh undoMesh;
CTriMesh redoMesh;
};
//////////////////////////////////////////////////////////////////////////
// Static member of CEdMesh.
//////////////////////////////////////////////////////////////////////////
CEdMesh::MeshMap CEdMesh::m_meshMap;
//////////////////////////////////////////////////////////////////////////
CEdMesh::CEdMesh()
{
m_pStatObj = 0;
m_pSubObjCache = 0;
m_nUserCount = 0;
m_bModified = false;
}
//////////////////////////////////////////////////////////////////////////
CEdMesh::CEdMesh(IStatObj* pGeom)
{
assert(pGeom);
if (pGeom)
{
m_pStatObj = pGeom;
m_pStatObj->AddRef();
}
m_pSubObjCache = 0;
m_nUserCount = 0;
m_bModified = false;
}
//////////////////////////////////////////////////////////////////////////
CEdMesh::~CEdMesh()
{
for (auto ppIndexedMeshes = m_tempIndexedMeshes.begin(); ppIndexedMeshes != m_tempIndexedMeshes.end(); ++ppIndexedMeshes)
{
SAFE_RELEASE(*ppIndexedMeshes);
}
SAFE_RELEASE(m_pStatObj);
// Remove this object from map.
m_meshMap.erase(m_filename);
if (m_pSubObjCache)
{
if (m_pSubObjCache->pTriMesh)
{
delete m_pSubObjCache->pTriMesh;
}
delete m_pSubObjCache;
}
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::Serialize(CObjectArchive& ar)
{
if (ar.bUndo)
{
return;
}
if (ar.bLoading)
{
}
else
{
if (m_bModified)
{
CBaseObject* pObj = ar.GetCurrentObject();
if (pObj)
{
QString levelPath = Path::AddPathSlash(GetIEditor()->GetLevelFolder());
CPakFile* pPakFile = ar.GetGeometryPak((levelPath + "\\Geometry.pak").toUtf8().data());
if (pPakFile)
{
SaveToCGF(m_filename.toUtf8().data(), pPakFile);
}
}
SetModified(false);
}
}
}
//////////////////////////////////////////////////////////////////////////
// CEdMesh implementation.
//////////////////////////////////////////////////////////////////////////
CEdMesh* CEdMesh::LoadMesh(const char* filename)
{
if (strlen(filename) == 0)
{
return 0;
}
// If object created see if its not yet registered.
CEdMesh* pMesh = stl::find_in_map(m_meshMap, filename, (CEdMesh*)0);
if (pMesh)
{
// Found, return it.
return pMesh;
}
// Make new.
IStatObj* pGeom = GetIEditor()->Get3DEngine()->LoadStatObjUnsafeManualRef(filename);
if (!pGeom)
{
return 0;
}
// Not found, Make new.
pMesh = new CEdMesh(pGeom);
pMesh->m_filename = filename;
m_meshMap[filename] = pMesh;
return pMesh;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::AddUser()
{
m_nUserCount++;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::RemoveUser()
{
m_nUserCount--;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::ReloadAllGeometries()
{
for (MeshMap::iterator it = m_meshMap.begin(); it != m_meshMap.end(); ++it)
{
CEdMesh* pMesh = it->second;
if (pMesh)
{
pMesh->ReloadGeometry();
}
}
}
void CEdMesh::ReleaseAll()
{
m_meshMap.clear();
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::ReloadGeometry()
{
// Reload mesh.
if (m_pStatObj)
{
m_pStatObj->Refresh(FRO_GEOMETRY);
}
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::IsSameObject(const char* filename)
{
return QString::compare(m_filename, filename, Qt::CaseInsensitive) == 0;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::GetBounds(AABB& box)
{
assert(m_pStatObj);
if (m_pStatObj)
{
box.min = m_pStatObj->GetBoxMin();
box.max = m_pStatObj->GetBoxMax();
}
}
//////////////////////////////////////////////////////////////////////////
CEdGeometry* CEdMesh::Clone()
{
if (m_pStatObj)
{
// Clone StatObj.
IStatObj* pStatObj = m_pStatObj->Clone(true, true, false);
pStatObj->AddRef();
CEdMesh* pNewMesh = new CEdMesh(pStatObj);
return pNewMesh;
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::SetFilename(const QString& filename)
{
if (!m_filename.isEmpty())
{
m_meshMap.erase(m_filename);
}
m_filename = Path::MakeGamePath(filename);
m_meshMap[m_filename] = this;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::Render(SRendParams& rp, const SRenderingPassInfo& passInfo)
{
if (m_pStatObj)
{
m_pStatObj->Render(rp, passInfo);
}
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::IsDefaultObject()
{
if (m_pStatObj)
{
return m_pStatObj->IsDefaultObject();
}
return false;
}
//////////////////////////////////////////////////////////////////////////
IIndexedMesh* CEdMesh::GetIndexedMesh(size_t idx)
{
if (m_tempIndexedMeshes.size() == 0 && m_pStatObj)
{
if (m_pStatObj->GetIndexedMesh())
{
if (idx == 0)
{
return m_pStatObj->GetIndexedMesh();
}
return nullptr;
}
else
{
// Load from CGF.
QString sFilename = m_pStatObj->GetFilePath();
CContentCGF cgf(sFilename.toUtf8().data());
if (gEnv->p3DEngine->LoadChunkFileContent(&cgf, sFilename.toUtf8().data()))
{
for (int i = 0; i < cgf.GetNodeCount(); ++i)
{
CNodeCGF* pNode = cgf.GetNode(i);
if (pNode->type == CNodeCGF::NODE_MESH)
{
CMesh* pMesh = pNode->pMesh;
if (pMesh)
{
IIndexedMesh* pTempIndexedMesh = GetIEditor()->Get3DEngine()->CreateIndexedMesh();
pTempIndexedMesh->SetMesh(*pMesh);
m_tempIndexedMeshes.push_back(pTempIndexedMesh);
Matrix34 tm = pNode->localTM;
CNodeCGF* pParent = pNode->pParent;
while (pParent)
{
tm = pParent->localTM * tm;
pParent = pParent->pParent;
}
m_tempMatrices.push_back(tm);
}
}
}
}
}
}
if (idx < m_tempIndexedMeshes.size())
{
return m_tempIndexedMeshes[idx];
}
return nullptr;
}
void CEdMesh::GetTM(Matrix34* pTM, size_t idx)
{
if (idx < m_tempMatrices.size())
{
*pTM = m_tempMatrices[idx];
}
else
{
pTM->SetIdentity();
}
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::AcceptModifySelection()
{
// Implement
UpdateIndexedMeshFromCache(true);
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::UpdateIndexedMeshFromCache(bool bFast)
{
// Implement
if (m_pSubObjCache)
{
if (bFast)
{
if (g_SubObjSelOptions.displayType == SO_DISPLAY_GEOMETRY)
{
m_pSubObjCache->pTriMesh->UpdateIndexedMesh(GetIndexedMesh());
if (m_pStatObj)
{
m_pStatObj->Invalidate();
}
}
}
else
{
m_pSubObjCache->pTriMesh->UpdateIndexedMesh(GetIndexedMesh());
if (m_pStatObj)
{
m_pStatObj->Invalidate();
}
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::StartSubObjSelection(const Matrix34& nodeWorldTM, int elemType, [[maybe_unused]] int nFlags)
{
IIndexedMesh* pIndexedMesh = GetIndexedMesh();
if (!pIndexedMesh)
{
return false;
}
CMesh& mesh = *pIndexedMesh->GetMesh();
if (!m_pSubObjCache)
{
m_pSubObjCache = new SubObjCache;
}
m_pSubObjCache->worldTM = nodeWorldTM;
m_pSubObjCache->invWorldTM = nodeWorldTM.GetInverted();
if (!m_pSubObjCache->pTriMesh)
{
m_pSubObjCache->pTriMesh = new CTriMesh;
m_pSubObjCache->pTriMesh->SetFromMesh(mesh);
}
UpdateSubObjCache();
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
triMesh.selectionType = elemType;
m_pSubObjCache->bNoDisplay = false;
return true;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::SetModified(bool bModified)
{
if (m_pSubObjCache && bModified)
{
// Update xformed vertices.
UpdateSubObjCache();
}
m_bModified = bModified;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::UpdateSubObjCache()
{
Matrix34& wtm = m_pSubObjCache->worldTM;
SetWorldTM(wtm);
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::EndSubObjSelection()
{
if (!m_pSubObjCache)
{
return;
}
UpdateIndexedMeshFromCache(false);
if (m_pSubObjCache->pTriMesh)
{
delete m_pSubObjCache->pTriMesh;
}
delete m_pSubObjCache;
m_pSubObjCache = 0;
if (m_pStatObj)
{
if (m_bModified)
{
m_pStatObj->Invalidate(true);
}
// Clear hidden flag from geometry.
m_pStatObj->SetFlags(m_pStatObj->GetFlags() & (~STATIC_OBJECT_HIDDEN));
}
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::Display(DisplayContext& dc)
{
if (!m_pSubObjCache || m_pSubObjCache->bNoDisplay)
{
return;
}
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
if (!triMesh.pWSVertices)
{
return;
}
if (m_pStatObj)
{
int nStatObjFlags = m_pStatObj->GetFlags();
if (g_SubObjSelOptions.displayType == SO_DISPLAY_GEOMETRY)
{
nStatObjFlags &= ~STATIC_OBJECT_HIDDEN;
}
else
{
nStatObjFlags |= STATIC_OBJECT_HIDDEN;
}
m_pStatObj->SetFlags(nStatObjFlags);
}
const Matrix34& worldTM = m_pSubObjCache->worldTM;
Vec3 vWSCameraVector = m_pSubObjCache->worldTM.GetTranslation() - dc.view->GetViewTM().GetTranslation();
Vec3 vOSCameraVector = m_pSubObjCache->invWorldTM.TransformVector(vWSCameraVector).GetNormalized(); // Object space camera vector.
// Render geometry vertices.
uint32 nPrevState = dc.GetState();
//////////////////////////////////////////////////////////////////////////
// Calculate front facing vertices.
//////////////////////////////////////////////////////////////////////////
triMesh.frontFacingVerts.resize(triMesh.GetVertexCount());
triMesh.frontFacingVerts.clear();
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
if (vOSCameraVector.Dot(face.normal) < 0)
{
triMesh.frontFacingVerts[face.v[0]] = true;
triMesh.frontFacingVerts[face.v[1]] = true;
triMesh.frontFacingVerts[face.v[2]] = true;
}
}
//////////////////////////////////////////////////////////////////////////
// Display flat shaded object.
//////////////////////////////////////////////////////////////////////////
if (g_SubObjSelOptions.displayType == SO_DISPLAY_FLAT)
{
ColorB faceColor(0, 250, 250, 255);
ColorB col = faceColor;
dc.SetDrawInFrontMode(false);
dc.SetFillMode(e_FillModeSolid);
dc.CullOn();
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
if (triMesh.selectionType != SO_ELEM_FACE || !triMesh.faceSel[i])
{
ColorB col2 = faceColor;
float dt = -face.normal.Dot(vOSCameraVector);
dt = max(0.4f, dt);
dt = min(1.0f, dt);
col2.r = ftoi(faceColor.r * dt);
col2.g = ftoi(faceColor.g * dt);
col2.b = ftoi(faceColor.b * dt);
col2.a = faceColor.a;
dc.pRenderAuxGeom->DrawTriangle(
triMesh.pWSVertices[face.v[0]], col2,
triMesh.pWSVertices[face.v[1]], col2,
triMesh.pWSVertices[face.v[2]], col2
);
}
}
}
// Draw selected triangles.
ColorB edgeColor(255, 255, 255, 155);
if (triMesh.StreamHaveSelection(CTriMesh::FACES))
{
if (g_SubObjSelOptions.bDisplayBackfacing)
{
dc.CullOff();
}
else
{
dc.CullOn();
}
dc.SetDrawInFrontMode(true);
dc.SetFillMode(e_FillModeWireframe);
// Draw triangles.
//dc.pRenderAuxGeom->DrawTriangles( triMesh.pVertices,triMesh.GetVertexCount(), mesh.m_pIndices,mesh.GetIndexCount(),edgeColor );
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
if (!triMesh.faceSel[i])
{
dc.pRenderAuxGeom->DrawTriangle(
triMesh.pWSVertices[face.v[0]], edgeColor,
triMesh.pWSVertices[face.v[1]], edgeColor,
triMesh.pWSVertices[face.v[2]], edgeColor
);
}
}
}
if (g_SubObjSelOptions.bDisplayNormals)
{
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
Vec3 p1 = triMesh.pWSVertices[face.v[0]];
Vec3 p2 = triMesh.pWSVertices[face.v[1]];
Vec3 p3 = triMesh.pWSVertices[face.v[2]];
Vec3 midp = (p1 + p2 + p3) * (1.0f / 3.0f);
dc.pRenderAuxGeom->DrawLine(midp, edgeColor, midp + worldTM.TransformVector(face.normal) * g_SubObjSelOptions.fNormalsLength, edgeColor);
}
}
if (triMesh.selectionType == SO_ELEM_VERTEX || triMesh.StreamHaveSelection(CTriMesh::VERTICES))
{
ColorB pointColor(0, 255, 255, 255);
float fClrAdd = (g_SubObjSelOptions.bSoftSelection) ? 0 : 1;
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
bool bSelected = triMesh.vertSel[i] || triMesh.pWeights[i] != 0;
if (bSelected)
{
int clr = (triMesh.pWeights[i] + fClrAdd) * 255;
dc.pRenderAuxGeom->DrawPoint(triMesh.pWSVertices[i], ColorB(clr, 255 - clr, 255 - clr, 255), 8);
}
else if (!g_SubObjSelOptions.bDisplayBackfacing || triMesh.frontFacingVerts[i])
{
dc.pRenderAuxGeom->DrawPoint(triMesh.pWSVertices[i], pointColor, 5);
}
}
}
// Draw edges.
if (triMesh.selectionType == SO_ELEM_EDGE || triMesh.StreamHaveSelection(CTriMesh::EDGES))
{
ColorB edgeColor2(200, 255, 200, 255);
ColorB selEdgeColor(255, 0, 0, 255);
// Draw selected edges.
for (int i = 0; i < triMesh.GetEdgeCount(); i++)
{
CTriEdge& edge = triMesh.pEdges[i];
if (triMesh.edgeSel[i])
{
const Vec3& p1 = triMesh.pWSVertices[edge.v[0]];
const Vec3& p2 = triMesh.pWSVertices[edge.v[1]];
dc.pRenderAuxGeom->DrawLine(p1, selEdgeColor, p2, selEdgeColor, 6);
}
else if (!g_SubObjSelOptions.bDisplayBackfacing || (
triMesh.frontFacingVerts[edge.v[0]] && triMesh.frontFacingVerts[edge.v[1]]))
{
const Vec3& p1 = triMesh.pWSVertices[edge.v[0]];
const Vec3& p2 = triMesh.pWSVertices[edge.v[1]];
dc.pRenderAuxGeom->DrawLine(p1, edgeColor2, p2, edgeColor2);
}
}
}
if (triMesh.selectionType == SO_ELEM_FACE)
{
ColorB pointColor(0, 255, 255, 255);
ColorB selFaceColor(255, 0, 0, 180);
// Draw selected faces and face points.
dc.CullOff();
dc.SetFillMode(e_FillModeSolid);
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
const Vec3& p1 = triMesh.pWSVertices[face.v[0]];
const Vec3& p2 = triMesh.pWSVertices[face.v[1]];
const Vec3& p3 = triMesh.pWSVertices[face.v[2]];
if (triMesh.faceSel[i])
{
dc.pRenderAuxGeom->DrawTriangle(p1, selFaceColor, p2, selFaceColor, p3, selFaceColor);
}
if (!g_SubObjSelOptions.bDisplayBackfacing && vOSCameraVector.Dot(face.normal) > 0)
{
continue; // Backfacing.
}
Vec3 midp = (p1 + p2 + p3) * (1.0f / 3.0f);
dc.pRenderAuxGeom->DrawPoint(midp, pointColor, 4);
}
}
else if (triMesh.StreamHaveSelection(CTriMesh::FACES))
{
ColorB pointColor(0, 255, 255, 255);
ColorB selFaceColor(255, 0, 0, 180);
// Draw selected faces and face points.
dc.CullOff();
dc.SetFillMode(e_FillModeSolid);
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
const Vec3& p1 = triMesh.pWSVertices[face.v[0]];
const Vec3& p2 = triMesh.pWSVertices[face.v[1]];
const Vec3& p3 = triMesh.pWSVertices[face.v[2]];
if (triMesh.faceSel[i])
{
dc.pRenderAuxGeom->DrawTriangle(p1, selFaceColor, p2, selFaceColor, p3, selFaceColor);
}
}
}
dc.SetState(nPrevState); // Restore render state.
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::HitTestVertex(HitContext& hit, SSubObjHitTestEnvironment& env, SSubObjHitTestResult& result)
{
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
// This make sure that bit array size matches num vertices, front facing should be calculated in Display method.
triMesh.frontFacingVerts.resize(triMesh.GetVertexCount());
float minDist = FLT_MAX;
int closestElem = -1;
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
if (env.bIgnoreBackfacing && !triMesh.frontFacingVerts[i])
{
continue;
}
QPoint p = hit.view->WorldToView(triMesh.pWSVertices[i]);
if (p.x() >= hit.rect.left() && p.x() <= hit.rect.right() &&
p.y() >= hit.rect.top() && p.y() <= hit.rect.bottom())
{
if (env.bHitTestNearest)
{
float dist = env.vWSCameraPos.GetDistance(triMesh.pWSVertices[i]);
if (dist < minDist)
{
closestElem = i;
minDist = dist;
}
}
else
{
result.elems.push_back(i);
}
}
}
//////////////////////////////////////////////////////////////////////////
if (closestElem >= 0)
{
result.minDistance = minDist;
result.elems.push_back(closestElem);
}
return !result.elems.empty();
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::HitTestEdge(HitContext& hit, SSubObjHitTestEnvironment& env, SSubObjHitTestResult& result)
{
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
// This make sure that bit array size matches num vertices, front facing should be calculated in Display method.
triMesh.frontFacingVerts.resize(triMesh.GetVertexCount());
float minDist = FLT_MAX;
int closestElem = -1;
for (int i = 0; i < triMesh.GetEdgeCount(); i++)
{
CTriEdge& edge = triMesh.pEdges[i];
if (!env.bIgnoreBackfacing ||
(triMesh.frontFacingVerts[edge.v[0]] && triMesh.frontFacingVerts[edge.v[1]]))
{
if (hit.view->HitTestLine(triMesh.pWSVertices[edge.v[0]], triMesh.pWSVertices[edge.v[1]], hit.point2d, 5))
{
if (env.bHitTestNearest)
{
float dist = env.vWSCameraPos.GetDistance(triMesh.pWSVertices[edge.v[0]]);
if (dist < minDist)
{
closestElem = i;
minDist = dist;
}
}
else
{
result.elems.push_back(i);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
if (closestElem >= 0)
{
result.minDistance = minDist;
result.elems.push_back(closestElem);
}
return !result.elems.empty();
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::HitTestFace(HitContext& hit, SSubObjHitTestEnvironment& env, SSubObjHitTestResult& result)
{
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
float minDist = FLT_MAX;
int closestElem = -1;
Vec3 vOut(0, 0, 0);
Ray hitRay(hit.raySrc, hit.rayDir);
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
CTriFace& face = triMesh.pFaces[i];
if (env.bIgnoreBackfacing && env.vOSCameraVector.Dot(face.normal) > 0)
{
continue; // Back facing.
}
Vec3 p1 = triMesh.pWSVertices[face.v[0]];
Vec3 p2 = triMesh.pWSVertices[face.v[1]];
Vec3 p3 = triMesh.pWSVertices[face.v[2]];
if (!env.bHitTestNearest)
{
// Hit test face middle point in rectangle.
Vec3 midp = (p1 + p2 + p3) * (1.0f / 3.0f);
QPoint p = hit.view->WorldToView(midp);
if (p.x() >= hit.rect.left() && p.x() <= hit.rect.right() &&
p.y() >= hit.rect.top() && p.y() <= hit.rect.bottom())
{
result.elems.push_back(i);
}
}
else
{
// Hit test ray/triangle.
if (Intersect::Ray_Triangle(hitRay, p1, p3, p2, vOut))
{
float dist = hitRay.origin.GetSquaredDistance(vOut);
if (dist < minDist)
{
closestElem = i;
minDist = dist;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
if (closestElem >= 0)
{
result.minDistance = (float)sqrt(minDist);
result.elems.push_back(closestElem);
}
return !result.elems.empty();
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::SelectSubObjElements(SSubObjHitTestEnvironment& env, SSubObjHitTestResult& result)
{
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
bool bSelChanged = false;
if (env.bSelectOnHit && !result.elems.empty())
{
CBitArray* streamSel = triMesh.GetStreamSelection(result.stream);
if (streamSel)
{
// Select on hit.
for (int i = 0, num = result.elems.size(); i < num; i++)
{
int elem = result.elems[i];
if ((*streamSel)[elem] != env.bSelectValue)
{
bSelChanged = true;
(*streamSel)[elem] = env.bSelectValue;
}
}
if (bSelChanged)
{
if (env.bSelectValue)
{
triMesh.streamSelMask |= (1 << result.stream);
}
else if (!env.bSelectValue && streamSel->is_zero())
{
triMesh.streamSelMask &= ~(1 << result.stream);
}
}
}
}
return bSelChanged;
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::IsHitTestResultSelected(SSubObjHitTestResult& result)
{
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
if (!result.elems.empty())
{
CBitArray* streamSel = triMesh.GetStreamSelection(result.stream);
if (streamSel)
{
// check if first result element is selected.
if ((*streamSel)[ result.elems[0] ])
{
return true;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::HitTest(HitContext& hit)
{
if (hit.nSubObjFlags & SO_HIT_NO_EDIT)
// This is for a 'move-by-face-normal'. Prepare the mesh and set the 'bNoDisplay'to true
// so that the normal rendering happens instead of the edit-mode rendering.
{
StartSubObjSelection(hit.object->GetWorldTM(), SO_ELEM_FACE, 0);
m_pSubObjCache->bNoDisplay = true;
}
if (!m_pSubObjCache)
{
return false;
}
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
SSubObjHitTestEnvironment env;
env.vWSCameraPos = hit.view->GetViewTM().GetTranslation();
env.vWSCameraVector = m_pSubObjCache->worldTM.GetTranslation() - hit.view->GetViewTM().GetTranslation();
env.vOSCameraVector = m_pSubObjCache->invWorldTM.TransformVector(env.vWSCameraVector).GetNormalized(); // Object space camera vector.
env.bHitTestNearest = hit.nSubObjFlags & SO_HIT_POINT;
env.bHitTestSelected = hit.nSubObjFlags & SO_HIT_TEST_SELECTED;
env.bSelectOnHit = hit.nSubObjFlags & SO_HIT_SELECT;
env.bAdd = hit.nSubObjFlags & SO_HIT_SELECT_ADD;
env.bRemove = hit.nSubObjFlags & SO_HIT_SELECT_REMOVE;
env.bSelectValue = !env.bRemove;
env.bHighlightOnly = hit.nSubObjFlags & SO_HIT_HIGHLIGHT_ONLY;
env.bIgnoreBackfacing = g_SubObjSelOptions.bIgnoreBackfacing && !env.bHitTestNearest;
int nHitTestWhat = (hit.nSubObjFlags & SO_HIT_ELEM_ALL);
if (nHitTestWhat == 0)
{
if (g_SubObjSelOptions.bSelectByVertex)
{
nHitTestWhat |= SO_HIT_ELEM_VERTEX;
}
switch (triMesh.selectionType)
{
case SO_ELEM_VERTEX:
nHitTestWhat |= SO_HIT_ELEM_VERTEX;
break;
case SO_ELEM_EDGE:
nHitTestWhat |= SO_HIT_ELEM_EDGE;
break;
case SO_ELEM_FACE:
nHitTestWhat |= SO_HIT_ELEM_FACE;
break;
case SO_ELEM_POLYGON:
nHitTestWhat |= SO_HIT_ELEM_POLYGON;
break;
}
}
IUndoObject* pUndoObj = NULL;
if (env.bSelectOnHit)
{
if (CUndo::IsRecording() && !(hit.nSubObjFlags & SO_HIT_NO_EDIT))
{
switch (triMesh.selectionType)
{
case SO_ELEM_VERTEX:
pUndoObj = new CUndoEdMesh(this, CTriMesh::COPY_VERT_SEL | CTriMesh::COPY_WEIGHTS, "Select Vertex(s)");
break;
case SO_ELEM_EDGE:
pUndoObj = new CUndoEdMesh(this, CTriMesh::COPY_EDGE_SEL | CTriMesh::COPY_WEIGHTS, "Select Edge(s)");
break;
case SO_ELEM_FACE:
pUndoObj = new CUndoEdMesh(this, CTriMesh::COPY_FACE_SEL | CTriMesh::COPY_WEIGHTS, "Select Face(s)");
break;
}
}
}
bool bSelChanged = false;
bool bAnyHit = false;
//////////////////////////////////////////////////////////////////////////
if (env.bSelectOnHit && !env.bAdd && !env.bRemove)
{
bSelChanged = triMesh.ClearSelection();
}
//////////////////////////////////////////////////////////////////////////
SSubObjHitTestResult result[4];
result[0].stream = CTriMesh::VERTICES;
result[1].stream = CTriMesh::EDGES;
result[2].stream = CTriMesh::FACES;
if (nHitTestWhat & SO_HIT_ELEM_VERTEX)
{
if (HitTestVertex(hit, env, result[0]))
{
bAnyHit = true;
}
}
if (nHitTestWhat & SO_HIT_ELEM_EDGE)
{
if (HitTestEdge(hit, env, result[1]))
{
bAnyHit = true;
}
}
if (nHitTestWhat & SO_HIT_ELEM_FACE)
{
if (HitTestFace(hit, env, result[2]))
{
bAnyHit = true;
}
}
if (bAnyHit && !env.bSelectOnHit && !env.bHitTestSelected)
{
// Return distance to the first hit element.
hit.dist = min(min(result[0].minDistance, result[1].minDistance), result[2].minDistance);
return true;
}
if (bAnyHit && !env.bSelectOnHit && env.bHitTestSelected)
{
// check if we hit selected item.
if (IsHitTestResultSelected(result[0]) ||
IsHitTestResultSelected(result[1]) ||
IsHitTestResultSelected(result[2]))
{
hit.dist = min(min(result[0].minDistance, result[1].minDistance), result[2].minDistance);
return true;
}
// If not hit selected.
return false;
}
if (bAnyHit)
{
// Find closest hit.
int n = 0;
if (!result[0].elems.empty())
{
n = 0;
}
else if (!result[1].elems.empty())
{
n = 1;
}
else if (!result[2].elems.empty())
{
n = 2;
}
hit.dist = result[n].minDistance;
if (env.bSelectOnHit &&
g_SubObjSelOptions.bSelectByVertex &&
!result[0].elems.empty() &&
!env.bHighlightOnly &&
triMesh.selectionType != SO_HIT_ELEM_VERTEX)
{
// When selecting elements by vertex.
switch (triMesh.selectionType)
{
case SO_ELEM_EDGE:
n = 1;
triMesh.GetEdgesByVertex(result[0].elems, result[1].elems);
break;
case SO_ELEM_FACE:
n = 2;
triMesh.GetFacesByVertex(result[0].elems, result[2].elems);
break;
case SO_ELEM_POLYGON:
n = 2;
triMesh.GetFacesByVertex(result[0].elems, result[2].elems);
break;
}
}
if (env.bSelectOnHit && SelectSubObjElements(env, result[n]))
{
bSelChanged = true;
}
}
if (bSelChanged)
{
hit.nSubObjFlags |= SO_HIT_SELECTION_CHANGED;
}
else
{
hit.nSubObjFlags &= ~SO_HIT_SELECTION_CHANGED;
}
bool bSelectionNotEmpty = false;
if (env.bSelectOnHit && bSelChanged && !env.bHighlightOnly)
{
if (CUndo::IsRecording() && pUndoObj)
{
CUndo::Record(pUndoObj);
}
bSelectionNotEmpty = triMesh.UpdateSelection();
if (g_SubObjSelOptions.bSoftSelection)
{
triMesh.SoftSelection(g_SubObjSelOptions);
}
OnSelectionChange();
}
else
{
if (pUndoObj)
{
pUndoObj->Release();
}
}
return bSelectionNotEmpty;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::OnSelectionChange()
{
Matrix34 localRefFrame;
if (!GetSelectionReferenceFrame(localRefFrame))
{
GetIEditor()->ShowTransformManipulator(false);
}
else
{
ITransformManipulator* pManipulator = GetIEditor()->ShowTransformManipulator(true);
// In local space orient axis gizmo by first object.
localRefFrame = m_pSubObjCache->worldTM * localRefFrame;
Matrix34 parentTM = m_pSubObjCache->worldTM;
Matrix34 userTM = GetIEditor()->GetViewManager()->GetGrid()->GetMatrix();
parentTM.SetTranslation(localRefFrame.GetTranslation());
userTM.SetTranslation(localRefFrame.GetTranslation());
//tm.SetTranslation( m_selectionCenter );
pManipulator->SetTransformation(COORDS_LOCAL, localRefFrame);
pManipulator->SetTransformation(COORDS_PARENT, parentTM);
pManipulator->SetTransformation(COORDS_USERDEFINED, userTM);
}
}
//////////////////////////////////////////////////////////////////////////
bool CEdMesh::GetSelectionReferenceFrame(Matrix34& refFrame)
{
if (!m_pSubObjCache)
{
return false;
}
bool bAnySelected = false;
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
Vec3 normal(0, 0, 0);
refFrame.SetIdentity();
if (triMesh.selectionType == SO_ELEM_VERTEX)
{
// Average all selected vertex normals.
int numNormals = 0;
int nFaces = triMesh.GetFacesCount();
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
if (triMesh.vertSel[i])
{
bAnySelected = true;
int nVertexIndex = i;
for (int j = 0; j < nFaces; j++)
{
CTriFace& face = triMesh.pFaces[j];
for (int k = 0; k < 3; k++)
{
if (face.v[k] == nVertexIndex)
{
normal += face.n[k];
numNormals++;
}
}
}
}
}
if (numNormals > 0)
{
normal = normal / numNormals;
if (!normal.IsZero())
{
normal.Normalize();
}
}
}
else if (triMesh.selectionType == SO_ELEM_EDGE)
{
int nNormals = 0;
// Average face normals of a the selected edges.
for (int i = 0; i < triMesh.GetEdgeCount(); i++)
{
if (triMesh.edgeSel[i])
{
bAnySelected = true;
CTriEdge& edge = triMesh.pEdges[i];
for (int j = 0; j < 2; j++)
{
if (edge.face[j] >= 0)
{
normal = normal + triMesh.pFaces[edge.face[j]].normal;
nNormals++;
}
}
}
}
if (nNormals > 0)
{
normal = normal / nNormals;
if (!normal.IsZero())
{
normal.Normalize();
}
}
}
else if (triMesh.selectionType == SO_ELEM_FACE)
{
// Average all face normals.
int nNormals = 0;
for (int i = 0; i < triMesh.GetFacesCount(); i++)
{
if (triMesh.faceSel[i])
{
bAnySelected = true;
CTriFace& face = triMesh.pFaces[i];
normal = normal + face.normal;
nNormals++;
}
}
if (nNormals > 0)
{
normal = normal / nNormals;
if (!normal.IsZero())
{
normal.Normalize();
}
}
}
if (bAnySelected)
{
Vec3 pos(0, 0, 0);
int numSel = 0;
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
if (triMesh.pWeights[i] == 1.0f)
{
pos = pos + triMesh.pVertices[i].pos;
numSel++;
}
}
if (numSel > 0)
{
pos = pos / numSel; // Average position.
}
refFrame.SetTranslation(pos);
if (!normal.IsZero())
{
Vec3 xAxis(1, 0, 0), yAxis(0, 1, 0), zAxis(0, 0, 1);
if (normal.IsEquivalent(zAxis) || normal.IsEquivalent(-zAxis))
{
zAxis = xAxis;
}
xAxis = normal.Cross(zAxis).GetNormalized();
yAxis = xAxis.Cross(normal).GetNormalized();
refFrame.SetFromVectors(xAxis, yAxis, normal, pos);
}
}
return bAnySelected;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::ModifySelection(SSubObjSelectionModifyContext& modCtx, [[maybe_unused]] bool isUndo)
{
if (!m_pSubObjCache)
{
return;
}
IIndexedMesh* pIndexedMesh = GetIndexedMesh();
if (!pIndexedMesh)
{
return;
}
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
if (modCtx.type == SO_MODIFY_UNSELECT)
{
IUndoObject* pUndoObj = NULL;
if (CUndo::IsRecording())
{
pUndoObj = new CUndoEdMesh(this, CTriMesh::COPY_VERT_SEL | CTriMesh::COPY_WEIGHTS, "Move Vertices");
}
bool bChanged = triMesh.ClearSelection();
if (bChanged)
{
OnSelectionChange();
}
if (CUndo::IsRecording() && bChanged)
{
CUndo::Record(pUndoObj);
}
else if (pUndoObj)
{
pUndoObj->Release();
}
return;
}
Matrix34 worldTM = m_pSubObjCache->worldTM;
Matrix34 invTM = worldTM.GetInverted();
// Change modify reference frame to object space.
Matrix34 modRefFrame = invTM * modCtx.worldRefFrame;
Matrix34 modRefFrameInverse = modCtx.worldRefFrame.GetInverted() * worldTM;
if (modCtx.type == SO_MODIFY_MOVE)
{
if (CUndo::IsRecording())
{
CUndo::Record(new CUndoEdMesh(this, CTriMesh::COPY_VERTICES, "Move Vertices"));
}
Vec3 vOffset = modCtx.vValue;
vOffset = modCtx.worldRefFrame.GetInverted().TransformVector(vOffset); // Offset in local space.
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
CTriVertex& vtx = triMesh.pVertices[i];
if (triMesh.pWeights[i] != 0)
{
Matrix34 tm = modRefFrame * Matrix34::CreateTranslationMat(vOffset * triMesh.pWeights[i]) * modRefFrameInverse;
vtx.pos = tm.TransformPoint(vtx.pos);
}
}
OnSelectionChange();
}
else if (modCtx.type == SO_MODIFY_ROTATE)
{
if (CUndo::IsRecording())
{
CUndo::Record(new CUndoEdMesh(this, CTriMesh::COPY_VERTICES, "Rotate Vertices"));
}
Ang3 angles = Ang3(modCtx.vValue);
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
CTriVertex& vtx = triMesh.pVertices[i];
if (triMesh.pWeights[i] != 0)
{
Matrix34 tm = modRefFrame * Matrix33::CreateRotationXYZ(angles * triMesh.pWeights[i]) * modRefFrameInverse;
vtx.pos = tm.TransformPoint(vtx.pos);
}
}
}
else if (modCtx.type == SO_MODIFY_SCALE)
{
if (CUndo::IsRecording())
{
CUndo::Record(new CUndoEdMesh(this, CTriMesh::COPY_VERTICES, "Scale Vertices"));
}
Vec3 vScale = modCtx.vValue;
for (int i = 0; i < triMesh.GetVertexCount(); i++)
{
CTriVertex& vtx = triMesh.pVertices[i];
if (triMesh.pWeights[i] != 0)
{
Vec3 scl = Vec3(1.0f, 1.0f, 1.0f) * (1.0f - triMesh.pWeights[i]) + vScale * triMesh.pWeights[i];
Matrix34 tm = modRefFrame * Matrix33::CreateScale(scl) * modRefFrameInverse;
vtx.pos = tm.TransformPoint(vtx.pos);
}
}
}
SetModified();
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::CopyToMesh(CTriMesh& toMesh, int nCopyFlags)
{
if (!m_pSubObjCache)
{
return;
}
toMesh.Copy(*m_pSubObjCache->pTriMesh, nCopyFlags);
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::CopyFromMesh(CTriMesh& fromMesh, int nCopyFlags, bool bUndo)
{
if (m_pSubObjCache)
{
m_pSubObjCache->pTriMesh->Copy(fromMesh, nCopyFlags);
}
else
{
/*
CMesh mesh;
CTriMesh triMesh;
triMesh.Copy( fromMesh );
trimesh.void CopyToMeshFast( CMesh &mesh );
*/
}
if (bUndo)
{
UpdateIndexedMeshFromCache(true);
OnSelectionChange();
}
if (m_pSubObjCache)
{
UpdateSubObjCache();
}
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::SaveToCGF(const char* sFilename, CPakFile* pPakFile, _smart_ptr<IMaterial> pMaterial)
{
if (m_pStatObj)
{
// Save this EdMesh to CGF file.
m_filename = Path::MakeGamePath(sFilename);
_smart_ptr<IMaterial> pOriginalMaterial = m_pStatObj->GetMaterial();
if (pMaterial)
{
m_pStatObj->SetMaterial(pMaterial);
}
if (!pPakFile)
{
m_pStatObj->SaveToCGF(sFilename);
}
else
{
IChunkFile* pChunkFile = NULL;
if (m_pStatObj->SaveToCGF(sFilename, &pChunkFile))
{
void* pMemFile = NULL;
int nFileSize = 0;
pChunkFile->WriteToMemoryBuffer(&pMemFile, &nFileSize);
pPakFile->UpdateFile(sFilename, pMemFile, nFileSize, true, AZ::IO::INestedArchive::LEVEL_FASTER);
pChunkFile->Release();
}
}
// Restore original material.
if (pMaterial)
{
m_pStatObj->SetMaterial(pOriginalMaterial);
}
}
}
//////////////////////////////////////////////////////////////////////////
CTriMesh* CEdMesh::GetMesh()
{
if (m_pSubObjCache)
{
return m_pSubObjCache->pTriMesh;
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
CEdMesh* CEdMesh::CreateMesh(const char* name)
{
IStatObj* pStatObj = gEnv->p3DEngine->CreateStatObj();
if (pStatObj)
{
CEdMesh* pEdMesh = new CEdMesh;
pEdMesh->m_pStatObj = pStatObj;
pEdMesh->m_pStatObj->AddRef();
// Force create of indexed mesh.
pEdMesh->m_pStatObj->GetIndexedMesh(true);
// Not found, Make new.
pEdMesh->m_filename = name;
if (!pEdMesh->m_pSubObjCache)
{
pEdMesh->m_pSubObjCache = new SubObjCache;
pEdMesh->m_pSubObjCache->pTriMesh = new CTriMesh;
pEdMesh->m_pSubObjCache->worldTM.SetIdentity();
pEdMesh->m_pSubObjCache->invWorldTM.SetIdentity();
}
return pEdMesh;
}
return NULL;
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::InvalidateMesh()
{
if (m_pSubObjCache)
{
UpdateIndexedMeshFromCache(false);
}
if (m_pStatObj)
{
m_pStatObj->Invalidate(true);
}
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::SetWorldTM(const Matrix34& worldTM)
{
if (!m_pSubObjCache)
{
return;
}
m_pSubObjCache->worldTM = worldTM;
m_pSubObjCache->invWorldTM = worldTM.GetInverted();
//////////////////////////////////////////////////////////////////////////
// Transform vertices and normals to world space and store in cached mesh.
//////////////////////////////////////////////////////////////////////////
CTriMesh& triMesh = *m_pSubObjCache->pTriMesh;
int nVerts = triMesh.GetVertexCount();
triMesh.ReallocStream(CTriMesh::WS_POSITIONS, nVerts);
for (int i = 0; i < nVerts; i++)
{
triMesh.pWSVertices[i] = worldTM.TransformPoint(triMesh.pVertices[i].pos);
}
}
//////////////////////////////////////////////////////////////////////////
void CEdMesh::DebugDraw(const SGeometryDebugDrawInfo& info, float fExtrdueScale)
{
if (m_pStatObj)
{
m_pStatObj->DebugDraw(info, fExtrdueScale);
}
}