/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MessageHandler.h" #include "../CommandProc.h" #include "../GameLoop.h" #include "../View.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "ps/Game.h" #include "ps/CStr.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector2D.h" #include "maths/Vector3D.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpCinemaManager.h" namespace AtlasMessage { const float MINIMAL_SCREEN_DISTANCE = 5.f; sCinemaPath ConstructCinemaPath(const CCinemaPath* source) { sCinemaPath path; const CCinemaData* data = source->GetData(); //path.mode = data->m_Mode; //path.style = data->m_Style; path.growth = data->m_Growth; path.timescale = data->m_Timescale.ToFloat(); path.change = data->m_Switch; return path; } CCinemaData ConstructCinemaData(const sCinemaPath& path) { CCinemaData data; data.m_Growth = data.m_GrowthCount = path.growth; data.m_Switch = path.change; //data.m_Mode = path.mode; //data.m_Style = path.style; return data; } sCinemaSplineNode ConstructCinemaNode(const SplineData& data) { sCinemaSplineNode node; node.px = data.Position.X.ToFloat(); node.py = data.Position.Y.ToFloat(); node.pz = data.Position.Z.ToFloat(); node.rx = data.Rotation.X.ToFloat(); node.ry = data.Rotation.Y.ToFloat(); node.rz = data.Rotation.Z.ToFloat(); node.t = data.Distance.ToFloat(); return node; } std::vector GetCurrentPaths() { std::vector atlasPaths; CmpPtr cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpCinemaManager) return atlasPaths; const std::map& paths = cmpCinemaManager->GetPaths(); for ( std::map::const_iterator it=paths.begin(); it!=paths.end(); ++it ) { sCinemaPath path = ConstructCinemaPath(&it->second); path.name = it->first; const std::vector& nodes = it->second.GetAllNodes(); std::vector atlasNodes; for ( size_t i=0; i 2 ) { for ( size_t i=atlasNodes.size()-2; i>0; --i ) atlasNodes[i].t = atlasNodes[i-1].t; } atlasNodes.back().t = atlasNodes.front().t; atlasNodes.front().t = back; } path.nodes = atlasNodes; atlasPaths.push_back(path); } return atlasPaths; } void SetCurrentPaths(const std::vector& atlasPaths) { std::map paths; for ( std::vector::const_iterator it=atlasPaths.begin(); it!=atlasPaths.end(); ++it ) { CStrW pathName(*it->name); paths[pathName] = CCinemaPath(); paths[pathName].SetTimescale(fixed::FromFloat(it->timescale)); const sCinemaPath& atlasPath = *it; const std::vector nodes = *atlasPath.nodes; TNSpline spline; CCinemaData data = ConstructCinemaData(atlasPath); for ( size_t j=0; j cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpCinemaManager) cmpCinemaManager->SetPaths(paths); } QUERYHANDLER(GetCameraInfo) { sCameraInfo info; const CMatrix3D& cameraOrientation = g_Game->GetView()->GetCamera()->GetOrientation(); CQuaternion quatRot = cameraOrientation.GetRotation(); quatRot.Normalize(); CVector3D rotation = quatRot.ToEulerAngles(); rotation.X = RADTODEG(rotation.X); rotation.Y = RADTODEG(rotation.Y); rotation.Z = RADTODEG(rotation.Z); CVector3D translation = cameraOrientation.GetTranslation(); info.pX = translation.X; info.pY = translation.Y; info.pZ = translation.Z; info.rX = rotation.X; info.rY = rotation.Y; info.rZ = rotation.Z; msg->info = info; } MESSAGEHANDLER(CinemaEvent) { CmpPtr cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpCinemaManager) return; if (msg->mode == eCinemaEventMode::SMOOTH) { cmpCinemaManager->AddCinemaPathToQueue(*msg->path); } else if ( msg->mode == eCinemaEventMode::RESET ) { // g_Game->GetView()->ResetCamera(); } else ENSURE(false); } BEGIN_COMMAND(AddCinemaPath) { void Do() { CmpPtr cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpCinemaManager) return; CCinemaData pathData; pathData.m_Name = *msg->pathName; pathData.m_Timescale = fixed::FromInt(1); pathData.m_Orientation = L"target"; pathData.m_Mode = L"ease_inout"; pathData.m_Style = L"default"; CVector3D focus = g_Game->GetView()->GetCamera()->GetFocus(); CFixedVector3D target( fixed::FromFloat(focus.X), fixed::FromFloat(focus.Y), fixed::FromFloat(focus.Z) ); CVector3D camera = g_Game->GetView()->GetCamera()->GetOrientation().GetTranslation(); CFixedVector3D position( fixed::FromFloat(camera.X), fixed::FromFloat(camera.Y), fixed::FromFloat(camera.Z) ); TNSpline positionSpline; positionSpline.AddNode(position, CFixedVector3D(), fixed::FromInt(0)); TNSpline targetSpline; targetSpline.AddNode(target, CFixedVector3D(), fixed::FromInt(0)); cmpCinemaManager->AddPath(CCinemaPath(pathData, positionSpline, targetSpline)); } void Redo() { } void Undo() { } }; END_COMMAND(AddCinemaPath) BEGIN_COMMAND(DeleteCinemaPath) { void Do() { CmpPtr cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpCinemaManager) return; cmpCinemaManager->DeletePath(*msg->pathName); } void Redo() { } void Undo() { } }; END_COMMAND(DeleteCinemaPath) BEGIN_COMMAND(SetCinemaPaths) { std::vector m_oldPaths, m_newPaths; void Do() { m_oldPaths = GetCurrentPaths(); m_newPaths = *msg->paths; Redo(); } void Redo() { SetCurrentPaths(m_newPaths); } void Undo() { SetCurrentPaths(m_oldPaths); } }; END_COMMAND(SetCinemaPaths) BEGIN_COMMAND(SetCinemaPathsDrawing) { void Do() { if (g_Game && g_Game->GetView() && g_Game->GetView()->GetCinema()) g_Game->GetView()->GetCinema()->SetPathsDrawing(msg->drawPaths); } void Redo() { } void Undo() { } }; END_COMMAND(SetCinemaPathsDrawing) static CVector3D GetNearestPointToScreenCoords(const CVector3D& base, const CVector3D& dir, const CVector2D& screen, float lower = -1e5, float upper = 1e5) { // It uses a ternary search, because an intersection of cylinders is the complex task for (int i = 0; i < 64; ++i) { float delta = (upper - lower) / 3.0; float middle1 = lower + delta, middle2 = lower + 2.0f * delta; CVector3D p1 = base + dir * middle1, p2 = base + dir * middle2; CVector2D s1, s2; g_Game->GetView()->GetCamera()->GetScreenCoordinates(p1, s1.X, s1.Y); g_Game->GetView()->GetCamera()->GetScreenCoordinates(p2, s2.X, s2.Y); if ((s1 - screen).Length() < (s2 - screen).Length()) upper = middle2; else lower = middle1; } return base + dir * upper; } #define GET_PATH_NODE_WITH_VALIDATION() \ int index = msg->node->index; \ if (index < 0) \ return; \ CStrW name = *msg->node->name; \ CmpPtr cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); \ if (!cmpCinemaManager || !cmpCinemaManager->HasPath(name)) \ return; \ const CCinemaPath& path = cmpCinemaManager->GetPaths().find(name)->second; \ if (!msg->node->targetNode) \ { \ if (index >= (int)path.GetAllNodes().size()) \ return; \ } \ else \ { \ if (index >= (int)path.GetTargetSpline().GetAllNodes().size()) \ return; \ } BEGIN_COMMAND(AddPathNode) { void Do() { GET_PATH_NODE_WITH_VALIDATION(); CCinemaData data = *path.GetData(); TNSpline positionSpline = path; TNSpline targetSpline = path.GetTargetSpline(); TNSpline& spline = msg->node->targetNode ? targetSpline : positionSpline; CVector3D focus = g_Game->GetView()->GetCamera()->GetFocus(); CFixedVector3D target( fixed::FromFloat(focus.X), fixed::FromFloat(focus.Y), fixed::FromFloat(focus.Z) ); spline.InsertNode(index + 1, target, CFixedVector3D(), fixed::FromInt(1)); spline.BuildSpline(); cmpCinemaManager->DeletePath(name); cmpCinemaManager->AddPath(CCinemaPath(data, positionSpline, targetSpline)); } void Redo() { } void Undo() { } }; END_COMMAND(AddPathNode) BEGIN_COMMAND(DeletePathNode) { void Do() { GET_PATH_NODE_WITH_VALIDATION(); CCinemaData data = *path.GetData(); TNSpline positionSpline = path; TNSpline targetSpline = path.GetTargetSpline(); TNSpline& spline = msg->node->targetNode ? targetSpline : positionSpline; if (spline.GetAllNodes().size() <= 1) return; spline.RemoveNode(index); spline.BuildSpline(); cmpCinemaManager->DeletePath(name); cmpCinemaManager->AddPath(CCinemaPath(data, positionSpline, targetSpline)); g_AtlasGameLoop->view->SetParam(L"movetool", false); } void Redo() { } void Undo() { } }; END_COMMAND(DeletePathNode) BEGIN_COMMAND(MovePathNode) { void Do() { int axis = msg->axis; if (axis == AXIS_INVALID) return; GET_PATH_NODE_WITH_VALIDATION(); CCinemaData data = *path.GetData(); TNSpline positionSpline = path; TNSpline targetSpline = path.GetTargetSpline(); TNSpline& spline = msg->node->targetNode ? targetSpline : positionSpline; // Get shift of the tool by the cursor movement CFixedVector3D pos = spline.GetAllNodes()[index].Position; CVector3D position( pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat() ); CVector3D axisDirection(axis & AXIS_X, axis & AXIS_Y, axis & AXIS_Z); CVector2D from, to; msg->from->GetScreenSpace(from.X, from.Y); msg->to->GetScreenSpace(to.X, to.Y); CVector3D shift( GetNearestPointToScreenCoords(position, axisDirection, to) - GetNearestPointToScreenCoords(position, axisDirection, from) ); // Change, rebuild and update the path position += shift; pos += CFixedVector3D( fixed::FromFloat(shift.X), fixed::FromFloat(shift.Y), fixed::FromFloat(shift.Z) ); spline.UpdateNodePos(index, pos); spline.BuildSpline(); cmpCinemaManager->DeletePath(name); cmpCinemaManager->AddPath(CCinemaPath(data, positionSpline, targetSpline)); // Update visual tool coordinates g_AtlasGameLoop->view->SetParam(L"movetool_x", position.X); g_AtlasGameLoop->view->SetParam(L"movetool_y", position.Y); g_AtlasGameLoop->view->SetParam(L"movetool_z", position.Z); } void Redo() { } void Undo() { } }; END_COMMAND(MovePathNode) QUERYHANDLER(GetCinemaPaths) { msg->paths = GetCurrentPaths(); } static bool isPathNodePicked(const TNSpline& spline, const CVector2D& cursor, AtlasMessage::sCinemaPathNode& node, bool targetNode) { for (size_t i = 0; i < spline.GetAllNodes().size(); ++i) { const SplineData& data = spline.GetAllNodes()[i]; CVector3D position( data.Position.X.ToFloat(), data.Position.Y.ToFloat(), data.Position.Z.ToFloat() ); CVector2D screen_pos; g_Game->GetView()->GetCamera()->GetScreenCoordinates(position, screen_pos.X, screen_pos.Y); if ((screen_pos - cursor).Length() < MINIMAL_SCREEN_DISTANCE) { node.index = i; node.targetNode = targetNode; g_AtlasGameLoop->view->SetParam(L"movetool", true); g_AtlasGameLoop->view->SetParam(L"movetool_x", position.X); g_AtlasGameLoop->view->SetParam(L"movetool_y", position.Y); g_AtlasGameLoop->view->SetParam(L"movetool_z", position.Z); return true; } } return false; } QUERYHANDLER(PickPathNode) { AtlasMessage::sCinemaPathNode node; CmpPtr cmpCinemaManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (!cmpCinemaManager) { msg->node = node; return; } CVector2D cursor; msg->pos->GetScreenSpace(cursor.X, cursor.Y); for (const std::pair& p : cmpCinemaManager->GetPaths()) { const CCinemaPath& path = p.second; if (isPathNodePicked(path, cursor, node, false) || isPathNodePicked(path.GetTargetSpline(), cursor, node, true)) { node.name = path.GetName(); msg->node = node; return; } } msg->node = node; g_AtlasGameLoop->view->SetParam(L"movetool", false); } static bool isAxisPicked(const CVector3D& base, const CVector3D& direction, float length, const CVector2D& cursor) { CVector3D position = GetNearestPointToScreenCoords(base, direction, cursor, 0, length); CVector2D screen_position; g_Game->GetView()->GetCamera()->GetScreenCoordinates(position, screen_position.X, screen_position.Y); return (cursor - screen_position).Length() < MINIMAL_SCREEN_DISTANCE; } QUERYHANDLER(PickAxis) { msg->axis = AXIS_INVALID; GET_PATH_NODE_WITH_VALIDATION(); const TNSpline& spline = msg->node->targetNode ? path.GetTargetSpline() : path; CFixedVector3D pos = spline.GetAllNodes()[index].Position; CVector3D position(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()); CVector3D camera = g_Game->GetView()->GetCamera()->GetOrientation().GetTranslation(); float scale = (position - camera).Length() / 10.0; CVector2D cursor; msg->pos->GetScreenSpace(cursor.X, cursor.Y); if (isAxisPicked(position, CVector3D(1, 0, 0), scale, cursor)) msg->axis = AXIS_X; else if (isAxisPicked(position, CVector3D(0, 1, 0), scale, cursor)) msg->axis = AXIS_Y; else if (isAxisPicked(position, CVector3D(0, 0, 1), scale, cursor)) msg->axis = AXIS_Z; } MESSAGEHANDLER(ClearPathNodePreview) { UNUSED2(msg); g_AtlasGameLoop->view->SetParam(L"movetool", false); } } // namespace AtlasMessage