/* Copyright (C) 2010 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 "simulation2/system/Component.h" #include "ICmpVisual.h" #include "ICmpPosition.h" #include "simulation2/MessageTypes.h" #include "graphics/Frustum.h" #include "graphics/Model.h" #include "graphics/ObjectBase.h" #include "graphics/ObjectEntry.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "lib/utf8.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "renderer/Scene.h" class CCmpVisualActor : public ICmpVisual { public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeToMessageType(MT_Interpolate); componentManager.SubscribeToMessageType(MT_RenderSubmit); componentManager.SubscribeToMessageType(MT_OwnershipChanged); } DEFAULT_COMPONENT_ALLOCATOR(VisualActor) CUnit* m_Unit; // Current animation state std::string m_AnimName; bool m_AnimOnce; float m_AnimSpeed; CCmpVisualActor() : m_Unit(NULL) { } virtual void Init(const CSimContext& context, const CParamNode& paramNode) { if (!context.HasUnitManager()) return; // do nothing if graphics are disabled // TODO: we should do some fancy animation of under-construction buildings rising from the ground, // but for now we'll just use the foundation actor and ignore the normal one std::string name; if (paramNode.GetChild("Foundation").IsOk()) name = utf8_from_wstring(paramNode.GetChild("FoundationActor").ToString()); else name = utf8_from_wstring(paramNode.GetChild("Actor").ToString()); std::set selections; m_Unit = context.GetUnitManager().CreateUnit(name, NULL, selections); if (!m_Unit) { // The error will have already been logged return; } SelectAnimation("idle", false, 0.f); m_Unit->SetID(GetEntityId()); // TODO: is it safe to be using entity IDs for unit IDs? } virtual void Deinit(const CSimContext& context) { if (m_Unit) { context.GetUnitManager().DeleteUnit(m_Unit); m_Unit = NULL; } } virtual void Serialize(ISerializer& serialize) { // TODO: store the actor name, if !debug and it differs from the template if (serialize.IsDebug()) { if (m_Unit == NULL) serialize.StringASCII("actor", "[none]", 0, 256); else serialize.String("actor", m_Unit->GetObject()->m_Base->m_Name, 0, 256); } // TODO: store random variation. This ought to be synchronised across saved games // and networks, so everyone sees the same thing. Saving the list of selection strings // would be awfully inefficient, so actors should be changed to (by default) represent // variations with a 16-bit RNG seed (selected randomly when creating new units, or // when someone hits the "randomise" button in the map editor), only overridden with // a list of strings if it really needs to be a specific variation. // TODO: store animation state } virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) { Init(context, paramNode); } virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_Interpolate: { const CMessageInterpolate& msgData = static_cast (msg); Interpolate(context, msgData.frameTime, msgData.offset); break; } case MT_RenderSubmit: { const CMessageRenderSubmit& msgData = static_cast (msg); RenderSubmit(context, msgData.collector, msgData.frustum, msgData.culling); break; } case MT_OwnershipChanged: { const CMessageOwnershipChanged& msgData = static_cast (msg); if (m_Unit) m_Unit->SetPlayerID(msgData.to); break; } } } virtual CBound GetBounds() { if (!m_Unit) return CBound(); return m_Unit->GetModel()->GetBounds(); } virtual CVector3D GetPosition() { if (!m_Unit) return CVector3D(0, 0, 0); return m_Unit->GetModel()->GetTransform().GetTranslation(); } virtual void SelectAnimation(std::string name, bool once, float speed) { if (!m_Unit) return; if (!isfinite(speed) || speed < 0) // JS 'undefined' converts to NaN, which causes Bad Things speed = 0; m_AnimName = name; m_AnimOnce = once; m_AnimSpeed = speed; m_Unit->SetAnimationState(name, once, speed); } private: void Interpolate(const CSimContext& context, float frameTime, float frameOffset); void RenderSubmit(const CSimContext& context, SceneCollector& collector, const CFrustum& frustum, bool culling); }; REGISTER_COMPONENT_TYPE(VisualActor) void CCmpVisualActor::Interpolate(const CSimContext& context, float frameTime, float frameOffset) { if (m_Unit == NULL) return; CmpPtr cmpPosition(context, GetEntityId()); if (cmpPosition.null()) return; if (!cmpPosition->IsInWorld()) { // TODO: need to hide the unit from rendering return; } CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset)); m_Unit->GetModel()->SetTransform(transform); m_Unit->UpdateModel(frameTime); } void CCmpVisualActor::RenderSubmit(const CSimContext& UNUSED(context), SceneCollector& collector, const CFrustum& frustum, bool culling) { if (m_Unit == NULL) return; // TODO: need to think about things like LOS here CModel* model = m_Unit->GetModel(); model->ValidatePosition(); if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetBounds())) return; // TODO: model->SetShadingColor(CColor(1.0f, 1.0f, 1.0f, 1.0f) if visible, CColor(0.7f, 0.7f, 0.7f, 1.0f) if hidden in FOW) collector.SubmitRecursive(model); }