/* * 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 "StdAfx.h" #include "EdMesh.h" #include "Objects/DisplayContext.h" #include "Objects/ObjectLoader.h" #include "Include/ITransformManipulator.h" #include "Viewport.h" #include "ViewManager.h" #include "Util/PakFile.h" #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //! 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 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, 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 col = faceColor; float dt = -face.normal.Dot(vOSCameraVector); dt = max(0.4f, dt); dt = min(1.0f, dt); col.r = ftoi(faceColor.r * dt); col.g = ftoi(faceColor.g * dt); col.b = ftoi(faceColor.b * dt); col.a = faceColor.a; dc.pRenderAuxGeom->DrawTriangle( triMesh.pWSVertices[face.v[0]], col, triMesh.pWSVertices[face.v[1]], col, triMesh.pWSVertices[face.v[2]], col ); } } } // 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 edgeColor(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, edgeColor, p2, edgeColor); } } } 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, 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 pMaterial) { if (m_pStatObj) { // Save this EdMesh to CGF file. m_filename = Path::MakeGamePath(sFilename); _smart_ptr 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, ICryArchive::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); } }