// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // particle_dlg.cpp : implementation file // #include "std_afx.h" #include "object_viewer.h" #include "particle_dlg.h" #include "editable_range.h" #include "located_properties.h" #include "particle_system_edit.h" #include "skippable_message_box.h" #include "main_frame.h" // // TODO : remove these include when the test system will be removed #include "nel/3d/particle_system.h" #include "nel/3d/ps_force.h" #include "nel/3d/ps_emitter.h" #include "nel/3d/ps_particle.h" #include "nel/3d/ps_util.h" #include "nel/3d/ps_zone.h" #include "nel/3d/ps_color.h" #include "nel/3d/ps_float.h" #include "nel/3d/ps_int.h" #include "nel/3d/ps_plane_basis_maker.h" #include "nel/3d/particle_system_model.h" #include "nel/3d/particle_system_shape.h" #include "nel/3d/texture_file.h" #include "nel/3d/texture_grouped.h" #include "nel/3d/nelu.h" #include "nel/3d/font_manager.h" // #include "nel/misc/file.h" #include "start_stop_particle_system.h" // #include "save_options_dlg.h" #include "create_file_dlg.h" using namespace NL3D; //************************************************************************************************************************** CParticleDlg::CParticleDlg(class CObjectViewer* main, CWnd *pParent, CMainFrame* mainFrame, CAnimationDlg *animDLG) : CDialog(CParticleDlg::IDD, pParent), MainFrame(mainFrame), CurrentRightPane(NULL), _ActiveNode(NULL), _ObjView(main), _EmptyBBox(true), _AutoUpdateBBox(false), _PW(NULL) { //{{AFX_DATA_INIT(CParticleDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT nlverify (FontManager = main->getFontManager()); nlverify (FontGenerator = main->getFontGenerator()); NL3D::CParticleSystem::setSerializeIdentifierFlag(true); // serialize identifiers for edition ParticleTreeCtrl = new CParticleTreeCtrl(this); StartStopDlg = new CStartStopParticleSystem(this, animDLG); /** register us, so that our 'go' method will be called * this gives us a chance to display a bbox when needed */ _ObjView->registerMainLoopCallBack(this); } //************************************************************************************************************************** BOOL CParticleDlg::Create( UINT nIDTemplate, CWnd* pParentWnd /*= NULL*/ ) { if (!CDialog::Create(nIDTemplate, pParentWnd)) return FALSE; return TRUE; } //************************************************************************************************************************** void CParticleDlg::moveElement(const NLMISC::CMatrix &mat) { ParticleTreeCtrl->moveElement(mat); } //************************************************************************************************************************** NLMISC::CMatrix CParticleDlg::getElementMatrix(void) const { return ParticleTreeCtrl->getElementMatrix(); } //************************************************************************************************************************** CParticleDlg::~CParticleDlg() { _ObjView->removeMainLoopCallBack(this); delete ParticleTreeCtrl; delete CurrentRightPane; delete StartStopDlg; if (_PW) _PW->setModificationCallback(NULL); delete _PW; } //************************************************************************************************************************** void CParticleDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CParticleDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CParticleDlg, CDialog) //{{AFX_MSG_MAP(CParticleDlg) ON_WM_DESTROY() ON_WM_SIZE() ON_WM_SHOWWINDOW() ON_WM_CHAR() ON_COMMAND(IDM_CREATE_NEW_PS_WORKSPACE, OnCreateNewPsWorkspace) ON_COMMAND(IDM_LOAD_PS_WORKSPACE, OnLoadPSWorkspace) ON_COMMAND(IDM_SAVE_ALL_PS_WORKSPACE, OnSaveAllPsWorkspace) ON_COMMAND(IDM_SAVE_PS_WORKSPACE, OnSavePsWorkspace) ON_COMMAND(IDM_VIEW_PS_FILENAME, OnViewPsFilename) //}}AFX_MSG_MAP END_MESSAGE_MAP() //************************************************************************************************************************** void CParticleDlg::OnDestroy() { checkModifiedWorkSpace(); if (CurrentRightPane) { CurrentRightPane->DestroyWindow(); delete CurrentRightPane; CurrentRightPane = NULL; } setRegisterWindowState (this, REGKEY_OBJ_PARTICLE_DLG); CDialog::OnDestroy(); } //************************************************************************************************************************** BOOL CParticleDlg::OnInitDialog() { CDialog::OnInitDialog(); CRect r; GetWindowRect(&r); ParticleTreeCtrl->Create(WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_BORDER | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES | TVS_SHOWSELALWAYS | TVS_EDITLABELS | TVS_DISABLEDRAGDROP , r, this, 0x1005); ParticleTreeCtrl->init(); ParticleTreeCtrl->ShowWindow(SW_SHOW); StartStopDlg->Create(IDD_PARTICLE_SYSTEM_START_STOP, this); // create menu bar that allow to create / load a particle workspace CMenu menu; menu.LoadMenu(MAKEINTRESOURCE(IDR_PARTICLE_DLG_MENU)); this->SetMenu(&menu); menu.Detach(); updateMenu(); // _StatusBar.Create(this); UINT indicators = ID_PS_EDITOR_STATUS; _StatusBar.SetIndicators(&indicators, 1); _StatusBar.SetPaneInfo(0, ID_PS_EDITOR_STATUS, SBPS_NORMAL, computeStatusBarWidth()); RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, ID_PS_EDITOR_STATUS); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } //************************************************************************************************************************** void CParticleDlg::setStatusBarText(CString &str) { _StatusBar.SetPaneText(0, str, TRUE); } //************************************************************************************************************************** void CParticleDlg::OnSize(UINT nType, int cx, int cy) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); bool blocked = false; if (ParticleTreeCtrl->m_hWnd && this->m_hWnd) { CRect r = getTreeRect(cx, cy); ParticleTreeCtrl->MoveWindow(&r); if (CurrentRightPane) { CurrentRightPane->MoveWindow(r.right + 10, r.top, r.right + CurrRightPaneWidth + 10, r.top + CurrRightPaneHeight); } CDialog::OnSize(nType, cx, cy); if (IsWindow(_StatusBar)) { _StatusBar.SetPaneInfo(0, ID_PS_EDITOR_STATUS, SBPS_NORMAL, computeStatusBarWidth()); RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, ID_PS_EDITOR_STATUS); } } } //************************************************************************************************************************** CRect CParticleDlg::getTreeRect(int cx, int cy) const { const uint ox = 0, oy = 10; if (CurrentRightPane) { CRect res(ox, oy, cx - CurrRightPaneWidth - 10, cy - 20); return res; } else { CRect res(ox, oy, cx - 10, cy - 20); return res; } } //************************************************************************************************************************** void CParticleDlg::setRightPane(CWnd *pane) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (CurrentRightPane) { CurrentRightPane->DestroyWindow(); } delete CurrentRightPane; CurrentRightPane = pane; RECT r; if (pane) { pane->ShowWindow(SW_SHOW); CurrentRightPane->GetClientRect(&r); CurrRightPaneWidth = r.right; CurrRightPaneHeight = r.bottom; } GetClientRect(&r); this->SendMessage(WM_SIZE, SIZE_RESTORED, r.right + (r.bottom << 16)); GetWindowRect(&r); this->MoveWindow(&r); if (CurrentRightPane) { CurrentRightPane->Invalidate(); } this->Invalidate(); ParticleTreeCtrl->Invalidate(); } //************************************************************************************************************************** LRESULT CParticleDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_GETMINMAXINFO) { sint cx = 150, cy = 150; if (CurrentRightPane) { RECT r; CurrentRightPane->GetClientRect(&r); cx += CurrRightPaneWidth; if (cy < (CurrRightPaneHeight + 20) ) cy = CurrRightPaneHeight + 20; } MINMAXINFO *inf = (MINMAXINFO *) lParam; inf->ptMinTrackSize.x = cx; inf->ptMinTrackSize.y = cy; } return CDialog::WindowProc(message, wParam, lParam); } //************************************************************************************************************************** void CParticleDlg::OnShowWindow(BOOL bShow, UINT nStatus) { CDialog::OnShowWindow(bShow, nStatus); StartStopDlg->ShowWindow(bShow); } //************************************************************************************************************************** void CParticleDlg::goPostRender() { NL3D::CParticleSystem *currPS = _ActiveNode ? _ActiveNode->getPSPointer() : NULL; if (!currPS) return; if (StartStopDlg->isBBoxDisplayEnabled() && currPS) { NL3D::CNELU::Driver->setupModelMatrix(currPS->getSysMat()); if (_AutoUpdateBBox) { NLMISC::CAABBox currBBox; currPS->forceComputeBBox(currBBox); if (_EmptyBBox) { _EmptyBBox = false; _CurrBBox = currBBox; } else { NL3D::CPSUtil::displayBBox(NL3D::CNELU::Driver, _CurrBBox, CRGBA::Blue); _CurrBBox = NLMISC::CAABBox::computeAABBoxUnion(currBBox, _CurrBBox); } currPS->setPrecomputedBBox(_CurrBBox); } else { currPS->getLastComputedBBox(_CurrBBox); } NL3D::CPSUtil::displayBBox(NL3D::CNELU::Driver, _CurrBBox, currPS->getAutoComputeBBox() ? CRGBA::White : CRGBA::Red); } // copy user matrix into current fx nlassert(_ObjView); _ActiveNode->getPSModel()->setUserMatrix(_ObjView->getFXUserMatrix()); } //************************************************************************************************************************** void CParticleDlg::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (nChar == (UINT) 'p' || nChar == (UINT) 'P' || nChar == (UINT) ' ') { // simulate a start / stop on the system StartStopDlg->toggle(); } CDialog::OnChar(nChar, nRepCnt, nFlags); } //************************************************************************************************************************** const NLMISC::CMatrix &CParticleDlg::getPSMatrix() const { if (!getActivePSM()) return NLMISC::CMatrix::Identity; return getActivePSM()->getMatrix(); } //************************************************************************************************************************** const NLMISC::CMatrix &CParticleDlg::getPSWorldMatrix() const { if (!getActivePSM()) return NLMISC::CMatrix::Identity; return getActivePSM()->getWorldMatrix(); } //************************************************************************************************************************** void CParticleDlg::setPSMatrix(const NLMISC::CMatrix &mat) { if (!getActivePSM()) return; getActivePSM()->setMatrix(mat); } //************************************************************************************************************************** void CParticleDlg::setPSWorldMatrix(const NLMISC::CMatrix &mat) { if (!getActivePSM()) return; CMatrix invParentMat = getActivePSM()->getMatrix() * getActivePSM()->getWorldMatrix().inverted(); invParentMat.normalize(CMatrix::XYZ); CMatrix newMat = invParentMat * mat; newMat.normalize(CMatrix::XYZ); getActivePSM()->setMatrix(newMat); } //************************************************************************************************************************** void CParticleDlg::stickPSToSkeleton(CParticleWorkspace::CNode *node, NL3D::CSkeletonModel *skel, uint bone, const std::string &parentSkelName, const std::string &parentBoneName) { if (!node) return; node->stickPSToSkeleton(skel, bone, parentSkelName, parentBoneName); if (skel) { if (_ObjView->getMainFrame()->MouseMoveType == CMainFrame::MoveFX) { _ObjView->getMainFrame()->OnEditMovecamera(); } _ObjView->getMainFrame()->ToolBar.Invalidate(); } } //************************************************************************************************************************** void CParticleDlg::unstickPSFromSkeleton(CParticleWorkspace::CNode *node) { if (!node) return; node->unstickPSFromSkeleton(); _ObjView->getMainFrame()->ToolBar.Invalidate(); } //************************************************************************************************************************** bool CParticleDlg::savePS(HWND parent, CParticleWorkspace::CNode &psNode, bool askToContinueWhenError) { return savePSAs(parent, psNode, psNode.getFullPath(), askToContinueWhenError); } //************************************************************************************************************************** bool CParticleDlg::savePSAs(HWND parent, CParticleWorkspace::CNode &psNode ,const std::string &fullPath, bool askToContinueWhenError) { nlassert(psNode.getPSPointer()); if (psNode.getResetAutoCountFlag() && psNode.getPSPointer()->getAutoCountFlag()) { MessageBox(psNode.getFilename().c_str() + getStrRsc(IDS_AUTO_COUNT_ERROR), getStrRsc(IDS_WARNING), MB_ICONEXCLAMATION); return false; } StartStopDlg->stop(); try { psNode.savePSAs(fullPath); psNode.setModified(false); setStatusBarText(CString(fullPath.c_str()) + " " + getStrRsc(IDS_SAVED)); } catch (const NLMISC::Exception &e) { if (askToContinueWhenError) { int result = ::MessageBox(parent, CString(e.what()) + getStrRsc(IDS_CONTINUE_SAVING) , getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION); return result == MB_OK; } else { ::MessageBox(parent, utf8ToTStr(e.what()), getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION); return false; } } return true; } //************************************************************************************************************************** bool CParticleDlg::loadPS(HWND parent, CParticleWorkspace::CNode &psNode, TLoadPSBehav behav) { bool loadingOK = true; try { if (!psNode.loadPS()) { loadingOK = false; switch(behav) { case Silent: return false; // no op case ReportError: ::MessageBox(parent, (LPCTSTR) (CString(psNode.getFilename().c_str()) + " : " + getStrRsc(IDS_COULDNT_INSTANCIATE_PS)), getStrRsc(IDS_ERROR), MB_OK|MB_ICONEXCLAMATION); return true; break; case ReportErrorSkippable: { CSkippableMessageBox mb(getStrRsc(IDS_ERROR), CString(psNode.getFilename().c_str()) + " : " + getStrRsc(IDS_COULDNT_INSTANCIATE_PS), CWnd::FromHandle(parent)); mb.DoModal(); return !mb.getBypassFlag(); } break; default: nlassert(0); break; } } else { setStatusBarText(CString(psNode.getFullPath().c_str()) + " " + getStrRsc(IDS_LOADED)); } } catch (const NLMISC::Exception &e) { switch(behav) { case Silent: return false; // no op case ReportError: ::MessageBox(parent, utf8ToTStr(e.what()), getStrRsc(IDS_ERROR), MB_OK|MB_ICONEXCLAMATION); return true; break; case ReportErrorSkippable: { CSkippableMessageBox mb(getStrRsc(IDS_ERROR), CString(e.what()), CWnd::FromHandle(parent)); mb.DoModal(); return !mb.getBypassFlag(); } break; default: nlassert(0); break; } } if (psNode.getPSPointer()->hasLoop()) { localizedMessageBox(parent, IDS_FX_HAS_LOOP, IDS_WARNING, MB_OK|MB_ICONEXCLAMATION); } return behav != Silent; } //************************************************************************************************************************** void CParticleDlg::checkModifiedWorkSpace() { if (_PW) { // see if current tree has been changed if (_PW->isModified()) { int result = localizedMessageBox(*this, IDS_PS_WORKSPACE_MODIFIED, IDS_PARTICLE_EDITOR, MB_YESNO|MB_ICONQUESTION); if (result == IDYES) { saveWorkspaceStructure(); } } if (_PW->isContentModified()) { saveWorkspaceContent(true); } } } //************************************************************************************************************************** void CParticleDlg::closeWorkspace() { setActiveNode(NULL); ParticleTreeCtrl->setActiveNode(NULL); ParticleTreeCtrl->reset(); delete _PW; _PW = NULL; } //************************************************************************************************************************** void CParticleDlg::OnCreateNewPsWorkspace() { checkModifiedWorkSpace(); // ask name of the new workspace to create CCreateFileDlg cf(getStrRsc(IDS_CHOOSE_WORKSPACE_NAME), "", "pws"); INT_PTR result = cf.DoModal(); if (result = IDOK) { if (cf.touchFile()) { CParticleWorkspace *newPW = new CParticleWorkspace; newPW->setModificationCallback(ParticleTreeCtrl); newPW->init(_ObjView, cf.getFullPath(), _ObjView->getFontManager(), _ObjView->getFontGenerator()); // save empty workspace try { newPW->save(); } catch(const NLMISC::EStream &e) { MessageBox(utf8ToTStr(e.what()), getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION); } closeWorkspace(); _PW = newPW; ParticleTreeCtrl->buildTreeFromWorkSpace(*_PW); } } } //************************************************************************************************************************** void CParticleDlg::OnLoadPSWorkspace() { checkModifiedWorkSpace(); static const TCHAR BASED_CODE szFilter[] = _T("particle workspaces(*.pws)|*.pws||"); CFileDialog fd( TRUE, _T(".pws"), _T("*.pws"), 0, szFilter); INT_PTR result = fd.DoModal(); if (result != IDOK) return; loadWorkspace(tStrToUtf8(fd.GetPathName())); } //************************************************************************************************************************** void CParticleDlg::loadWorkspace(const std::string &fullPath) { // Add to the path std::unique_ptr newPW(new CParticleWorkspace); newPW->init(_ObjView, fullPath, _ObjView->getFontManager(), _ObjView->getFontGenerator()); newPW->setModificationCallback(ParticleTreeCtrl); // save empty workspace try { newPW->load(); setStatusBarText(CString(newPW->getFilename().c_str()) + " " + getStrRsc(IDS_LOADED)); } catch(const NLMISC::EStream &e) { MessageBox(utf8ToTStr(e.what()), getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION); setStatusBarText(CString(e.what())); return; } // try to load each ps CParticleWorkspace::CNode *firstLoadedNode = NULL; bool displayErrorMsg = true; for(uint k = 0; k < newPW->getNumNode(); ++k) { displayErrorMsg = loadPS(*this, *newPW->getNode(k), displayErrorMsg ? ReportErrorSkippable : Silent); if (newPW->getNode(k)->isLoaded() && !firstLoadedNode) { firstLoadedNode = newPW->getNode(k); } } closeWorkspace(); _PW = newPW.release(); ParticleTreeCtrl->buildTreeFromWorkSpace(*_PW); setActiveNode(firstLoadedNode); ParticleTreeCtrl->setActiveNode(firstLoadedNode); ParticleTreeCtrl->expandRoot(); setStatusBarText(getStrRsc(IDS_READY)); } //************************************************************************************************************************** void CParticleDlg::OnSaveAllPsWorkspace() { saveWorkspaceStructure(); saveWorkspaceContent(false); } //************************************************************************************************************************** void CParticleDlg::OnSavePsWorkspace() { saveWorkspaceStructure(); saveWorkspaceContent(true); } //************************************************************************************************************************** void CParticleDlg::saveWorkspaceStructure() { nlassert(_PW); try { _PW->save(); setStatusBarText(CString(_PW->getFilename().c_str()) + " " + getStrRsc(IDS_SAVED)); } catch(const NLMISC::EStream &e) { localizedMessageBox(*this, utf8ToTStr(e.what()), IDS_ERROR, MB_ICONEXCLAMATION); setStatusBarText(CString(e.what())); } } //************************************************************************************************************************** void CParticleDlg::saveWorkspaceContent(bool askToSaveModifiedPS) { StartStopDlg->stop(); bool saveAll = !askToSaveModifiedPS; // check each component of the tree for(uint k = 0; k < _PW->getNumNode(); ++k) { if (_PW->getNode(k)->isModified()) { if (saveAll) { bool keepSaving = savePS(*this, *_PW->getNode(k), k != _PW->getNumNode()); if (!keepSaving) break; } else { // ask if the user wants to save the ps, or save all ps CString mess; mess = CString(_PW->getNode(k)->getFilename().c_str()) + getStrRsc(IDS_SAVE_MODIFIED_PS); CSaveOptionsDlg sop(getStrRsc(IDS_SAVE_FILE), mess, this); sop.DoModal(); bool saveThisFile = false; bool stop = false; switch(sop.getChoice()) { case CSaveOptionsDlg::Yes: saveThisFile = true; break; case CSaveOptionsDlg::No: saveThisFile = false; break; case CSaveOptionsDlg::SaveAll: saveAll = true; break; case CSaveOptionsDlg::Stop: stop = true; break; default: nlassert(0); } if (stop) break; if (saveAll || saveThisFile) { bool keepSaving = savePS(*this, *_PW->getNode(k), k != _PW->getNumNode()); if (!keepSaving) break; } } } } setStatusBarText(getStrRsc(IDS_READY)); } //************************************************************************************************************************** void CParticleDlg::setActiveNode(CParticleWorkspace::CNode *node) { if (node == _ActiveNode) return; _ActiveNode = node; StartStopDlg->setActiveNode(node); if(MainFrame->isMoveFX()) { if (node) { _ObjView->getMouseListener().setModelMatrix(node->getPSModel()->getMatrix()); } else { _ObjView->getMouseListener().setModelMatrix(NLMISC::CMatrix::Identity); } } else if(MainFrame->isMoveFXUserMatrix()) { if (node) { _ObjView->getMouseListener().setModelMatrix(node->getPSModel()->getUserMatrix()); } else { _ObjView->getMouseListener().setModelMatrix(NLMISC::CMatrix::Identity); } } } //************************************************************************************************************************** NL3D::CParticleSystemModel *CParticleDlg::getModelFromPS(NL3D::CParticleSystem *ps) const { if (!ps) return NULL; if (!_PW) return NULL; CParticleWorkspace::CNode *node = _PW->getNodeFromPS(ps); if (!node) return NULL; return node->getPSModel(); } //************************************************************************************************************************** uint CParticleDlg::computeStatusBarWidth() const { nlassert(ParticleTreeCtrl); CRect tcRect; ParticleTreeCtrl->GetClientRect(&tcRect); if (!CurrentRightPane) return (uint) std::max((sint) tcRect.Width() - 16, (sint) 0); return (uint) std::max((sint) tcRect.Width() - 4, (sint) 0); } //************************************************************************************************************************** void CParticleDlg::OnViewPsFilename() { ParticleTreeCtrl->setViewFilenameFlag(!ParticleTreeCtrl->getViewFilenameFlag()); updateMenu(); } //************************************************************************************************************************** void CParticleDlg::updateMenu() { if (!ParticleTreeCtrl) return; CMenu *menu = GetMenu(); if (!menu) return; // update the view menu menu->CheckMenuItem(IDM_VIEW_PS_FILENAME, MF_BYCOMMAND|(ParticleTreeCtrl->getViewFilenameFlag() ? MF_CHECKED : 0)); }