Index: ps/trunk/binaries/data/mods/public/simulation/components/Trigger.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Trigger.js (revision 17593) +++ ps/trunk/binaries/data/mods/public/simulation/components/Trigger.js (revision 17594) @@ -1,274 +1,288 @@ function Trigger() {} Trigger.prototype.Schema = ""; /** * Events we're able to receive and call handlers for */ Trigger.prototype.eventNames = [ "StructureBuilt", "ConstructionStarted", "TrainingFinished", "TrainingQueued", "ResearchFinished", "ResearchQueued", "OwnershipChanged", "PlayerCommand", "Interval", "Range", "TreasureCollected", + "CinemaPathEnded", + "CinemaQueueEnded" ]; Trigger.prototype.Init = function() { this.triggerPoints = {}; // Each event has its own set of actions determined by the map maker. for each (var eventName in this.eventNames) this["On" + eventName + "Actions"] = {}; }; Trigger.prototype.RegisterTriggerPoint = function(ref, ent) { if (!this.triggerPoints[ref]) this.triggerPoints[ref] = []; this.triggerPoints[ref].push(ent); }; Trigger.prototype.RemoveRegisteredTriggerPoint = function(ref, ent) { if (!this.triggerPoints[ref]) { warn("no trigger points found with ref "+ref); return; } var i = this.triggerPoints[ref].indexOf(ent); if (i == -1) { warn("entity " + ent + " wasn't found under the trigger points with ref "+ref); return; } this.triggerPoints[ref].splice(i, 1); }; Trigger.prototype.GetTriggerPoints = function(ref) { return this.triggerPoints[ref] || []; }; /** Binds the "action" function to one of the implemented events. * * @param event Name of the event (see the list in init) * @param action Functionname of a function available under this object * @param Extra Data for the trigger (enabled or not, delay for timers, range for range triggers ...) * * Interval triggers example: * data = {enabled: true, interval: 1000, delay: 500} * * Range trigger: * data.entities = [id1, id2] * Ids of the source * data.players = [1,2,3,...] * list of player ids * data.minRange = 0 * Minimum range for the query * data.maxRange = -1 * Maximum range for the query (-1 = no maximum) * data.requiredComponent = 0 * Required component id the entities will have * data.enabled = false * If the query is enabled by default */ Trigger.prototype.RegisterTrigger = function(event, action, data) { var eventString = event + "Actions"; if (!this[eventString]) { warn("Trigger.js: Invalid trigger event \"" + event + "\"."); return; } if (this[eventString][action]) { warn("Trigger.js: Trigger \"" + action + "\" has been registered before. Aborting..."); return; } // clone the data to be sure it's only modified locally // We could run into triggers overwriting each other's data otherwise. // F.e. getting the wrong timer tag data = clone(data) || {"enabled": false}; this[eventString][action] = data; // setup range query if (event == "OnRange") { if (!data.entities) { warn("Trigger.js: Range triggers should carry extra data"); return; } data.queries = []; for (var ent of data.entities) { var cmpTriggerPoint = Engine.QueryInterface(ent, IID_TriggerPoint); if (!cmpTriggerPoint) { warn("Trigger.js: Range triggers must be defined on trigger points"); continue; } data.queries.push(cmpTriggerPoint.RegisterRangeTrigger(action, data)); } } if (data.enabled) this.EnableTrigger(event, action); }; // Disable trigger Trigger.prototype.DisableTrigger = function(event, action) { var eventString = event + "Actions"; if (!this[eventString][action]) { warn("Trigger.js: Disabling unknown trigger"); return; } var data = this[eventString][action]; // special casing interval and range triggers for performance if (event == "OnInterval") { if (!data.timer) // don't disable it a second time return; var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(data.timer); data.timer = null; } else if (event == "OnRange") { if (!data.queries) { warn("Trigger.js: Range query wasn't set up before trying to disable it."); return; } var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); for (var query of data.queries) cmpRangeManager.DisableActiveQuery(query); } data.enabled = false; }; // Enable trigger Trigger.prototype.EnableTrigger = function(event, action) { var eventString = event + "Actions"; if (!this[eventString][action]) { warn("Trigger.js: Enabling unknown trigger"); return; } var data = this[eventString][action]; // special casing interval and range triggers for performance if (event == "OnInterval") { if (data.timer) // don't enable it a second time return; if (!data.interval) { warn("Trigger.js: An interval trigger should have an intervel in its data"); return; } var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); var timer = cmpTimer.SetInterval(this.entity, IID_Trigger, "DoAction", data.delay || 0, data.interval, {"action" : action}); data.timer = timer; } else if (event == "OnRange") { if (!data.queries) { warn("Trigger.js: Range query wasn't set up before"); return; } var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); for (var query of data.queries) cmpRangeManager.EnableActiveQuery(query); } data.enabled = true; }; /** * This function executes the actions bound to the events * It's either called directlty from other simulation scripts, * or from message listeners in this file * * @param event Name of the event (see the list in init) * @param data Data object that will be passed to the actions */ Trigger.prototype.CallEvent = function(event, data) { var eventString = "On" + event + "Actions"; if (!this[eventString]) { warn("Trigger.js: Unknown trigger event called:\"" + event + "\"."); return; } for (var action in this[eventString]) { if (this[eventString][action].enabled) this.DoAction({"action": action, "data":data}); } }; // Handles "OnStructureBuilt" event. Trigger.prototype.OnGlobalConstructionFinished = function(msg) { this.CallEvent("StructureBuilt", {"building": msg.newentity}); // The data for this one is {"building": constructedBuilding} }; // Handles "OnTrainingFinished" event. Trigger.prototype.OnGlobalTrainingFinished = function(msg) { this.CallEvent("TrainingFinished", msg); // The data for this one is {"entities": createdEnts, // "owner": cmpOwnership.GetOwner(), // "metadata": metadata} // See function "SpawnUnits" in ProductionQueue for more details }; // Handles "OnTrainingFinished" event. Trigger.prototype.OnGlobalResearchFinished = function(msg) { this.CallEvent("ResearchFinished", msg); // The data for this one is {"player": playerID, // "tech": tech} }; +// Handles "OnCinemaPathEnded" event. +Trigger.prototype.OnGlobalCinemaPathEnded = function(msg) +{ + this.CallEvent("CinemaPathEnded", msg); +} + +// Handles "OnCinemaQueueEnded" event. +Trigger.prototype.OnGlobalCinemaQueueEnded = function(msg) +{ + this.CallEvent("CinemaQueueEnded", msg); +} + Trigger.prototype.OnGlobalOwnershipChanged = function(msg) { this.CallEvent("OwnershipChanged", msg); // data is {"entity": ent, "from": playerId, "to": playerId} }; /** * Execute a function after a certain delay * @param time The delay expressed in milleseconds * @param action Name of the action function * @param data Data object that will be passed to the action function */ Trigger.prototype.DoAfterDelay = function(miliseconds, action, data) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); return cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "DoAction", miliseconds, {"action": action, "data": data}); }; /** * Called by the trigger listeners to exucute the actual action. Including sanity checks. */ Trigger.prototype.DoAction = function(msg) { if (this[msg.action]) this[msg.action](msg.data || null); else warn("Trigger.js: called a trigger action '" + msg.action + "' that wasn't found"); }; Engine.RegisterSystemComponentType(IID_Trigger, "Trigger", Trigger); Index: ps/trunk/source/collada/CommonConvert.cpp =================================================================== --- ps/trunk/source/collada/CommonConvert.cpp (revision 17593) +++ ps/trunk/source/collada/CommonConvert.cpp (revision 17594) @@ -1,450 +1,450 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2016 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 "CommonConvert.h" #include "StdSkeletons.h" #include "XMLFix.h" #include "FCollada.h" #include "FCDocument/FCDSceneNode.h" #include "FCDocument/FCDSkinController.h" #include "FUtils/FUDaeSyntax.h" #include "FUtils/FUFileManager.h" #include #include void require_(int line, bool value, const char* type, const char* message) { if (value) return; char linestr[16]; sprintf(linestr, "%d", line); throw ColladaException(std::string(type) + " (line " + linestr + "): " + message); } /** Error handler for libxml2 */ void errorHandler(void* ctx, const char* msg, ...) { char buffer[1024]; va_list ap; va_start(ap, msg); vsnprintf(buffer, sizeof(buffer), msg, ap); buffer[sizeof(buffer)-1] = '\0'; va_end(ap); *((std::string*)ctx) += buffer; } FColladaErrorHandler::FColladaErrorHandler(std::string& xmlErrors_) : xmlErrors(xmlErrors_) { // Grab all the error output from libxml2, for useful error reporting xmlSetGenericErrorFunc(&xmlErrors, &errorHandler); FUError::AddErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError); FUError::AddErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError); FUError::AddErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError); } FColladaErrorHandler::~FColladaErrorHandler() { xmlSetGenericErrorFunc(NULL, NULL); FUError::RemoveErrorCallback(FUError::DEBUG_LEVEL, this, &FColladaErrorHandler::OnError); FUError::RemoveErrorCallback(FUError::WARNING_LEVEL, this, &FColladaErrorHandler::OnError); FUError::RemoveErrorCallback(FUError::ERROR_LEVEL, this, &FColladaErrorHandler::OnError); } void FColladaErrorHandler::OnError(FUError::Level errorLevel, uint32 errorCode, uint32 UNUSED(lineNumber)) { // Ignore warnings about missing materials, since we ignore materials entirely anyway if (errorCode == FUError::WARNING_INVALID_POLYGON_MAT_SYMBOL) return; const char* errorString = FUError::GetErrorString((FUError::Code) errorCode); if (! errorString) errorString = "Unknown error code"; if (errorLevel == FUError::DEBUG_LEVEL) Log(LOG_INFO, "FCollada %d: %s", errorCode, errorString); else if (errorLevel == FUError::WARNING_LEVEL) Log(LOG_WARNING, "FCollada %d: %s", errorCode, errorString); else throw ColladaException(errorString); } ////////////////////////////////////////////////////////////////////////// void FColladaDocument::LoadFromText(const char *text) { document.reset(FCollada::NewTopDocument()); const char* newText = NULL; size_t newTextSize = 0; FixBrokenXML(text, &newText, &newTextSize); // Log(LOG_INFO, "%s", newText); bool status = FCollada::LoadDocumentFromMemory("unknown.dae", document.get(), (void*)newText, newTextSize); if (newText != text) xmlFree((void*)newText); REQUIRE_SUCCESS(status); } void FColladaDocument::ReadExtras(xmlNode* UNUSED(colladaNode)) { // TODO: This was needed to recognise and load XSI models. // XSI support should be reintroduced some time, but this function // may not be necessary since FCollada might now provide access to the // 'extra' data via a proper API. /* if (! IsEquivalent(colladaNode->name, DAE_COLLADA_ELEMENT)) return; extra.reset(new FCDExtra(document.get())); xmlNodeList extraNodes; FUXmlParser::FindChildrenByType(colladaNode, DAE_EXTRA_ELEMENT, extraNodes); for (xmlNodeList::iterator it = extraNodes.begin(); it != extraNodes.end(); ++it) { xmlNode* extraNode = (*it); extra->LoadFromXML(extraNode); } */ } ////////////////////////////////////////////////////////////////////////// CommonConvert::CommonConvert(const char* text, std::string& xmlErrors) : m_Err(xmlErrors) { m_Doc.LoadFromText(text); FCDSceneNode* root = m_Doc.GetDocument()->GetVisualSceneRoot(); REQUIRE(root != NULL, "has root object"); // Find the instance to convert if (! FindSingleInstance(root, m_Instance, m_EntityTransform)) throw ColladaException("Couldn't find object to convert"); assert(m_Instance); Log(LOG_INFO, "Converting '%s'", m_Instance->GetEntity()->GetName().c_str()); m_IsXSI = false; FCDAsset* asset = m_Doc.GetDocument()->GetAsset(); if (asset && asset->GetContributorCount() >= 1) { std::string tool (asset->GetContributor(0)->GetAuthoringTool()); if (tool.find("XSI") != tool.npos) m_IsXSI = true; } FMVector3 upAxis = m_Doc.GetDocument()->GetAsset()->GetUpAxis(); m_YUp = (upAxis.y != 0); // assume either Y_UP or Z_UP (TODO: does anyone ever do X_UP?) } CommonConvert::~CommonConvert() { } ////////////////////////////////////////////////////////////////////////// // HACK: The originals don't get exported properly from FCollada (3.02, DLL), so define // them here instead of fixing it correctly. const FMVector3 FMVector3_XAxis(1.0f, 0.0f, 0.0f); static float identity[] = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; FMMatrix44 FMMatrix44_Identity(identity); struct FoundInstance { FCDEntityInstance* instance; FMMatrix44 transform; }; static bool IsVisible_XSI(FCDSceneNode* node, bool& visible) { // Look for FCDExtra* extra = node->GetExtra(); if (! extra) return false; FCDEType* type = extra->GetDefaultType(); if (! type) return false; FCDETechnique* technique = type->FindTechnique("XSI"); if (! technique) return false; FCDENode* visibility1 = technique->FindChildNode("SI_Visibility"); if (! visibility1) return false; FCDENode* visibility2 = visibility1->FindChildNode("xsi_param"); if (! visibility2) return false; if (IsEquivalent(visibility2->GetContent(), "TRUE")) visible = true; else if (IsEquivalent(visibility2->GetContent(), "FALSE")) visible = false; return true; } static bool IsVisible(FCDSceneNode* node) { bool visible = false; // Try the XSI visibility property if (IsVisible_XSI(node, visible)) return visible; // Else fall back to the FCollada-specific setting visible = (node->GetVisibility() != 0.0); return visible; } /** * Recursively finds all entities under the current node. If onlyMarked is * set, only matches entities where the user-defined property was set to * "export" in the modelling program. * * @param node root of subtree to search * @param instances output - appends matching entities * @param transform transform matrix of current subtree * @param onlyMarked only match entities with "export" property */ static void FindInstances(FCDSceneNode* node, std::vector& instances, const FMMatrix44& transform, bool onlyMarked) { for (size_t i = 0; i < node->GetChildrenCount(); ++i) { FCDSceneNode* child = node->GetChild(i); FindInstances(child, instances, transform * node->ToMatrix(), onlyMarked); } for (size_t i = 0; i < node->GetInstanceCount(); ++i) { if (onlyMarked) { if (node->GetNote() != "export") continue; } // Only accept instances of appropriate types, and not e.g. lights FCDEntity::Type type = node->GetInstance(i)->GetEntityType(); if (! (type == FCDEntity::GEOMETRY || type == FCDEntity::CONTROLLER)) continue; // Ignore invisible objects, because presumably nobody wanted to export them if (! IsVisible(node)) continue; FoundInstance f; f.transform = transform * node->ToMatrix(); f.instance = node->GetInstance(i); instances.push_back(f); Log(LOG_INFO, "Found convertible object '%s'", node->GetName().c_str()); } } bool FindSingleInstance(FCDSceneNode* node, FCDEntityInstance*& instance, FMMatrix44& transform) { std::vector instances; FindInstances(node, instances, FMMatrix44_Identity, true); if (instances.size() > 1) { Log(LOG_ERROR, "Found too many export-marked objects"); return false; } if (instances.empty()) { FindInstances(node, instances, FMMatrix44_Identity, false); if (instances.size() > 1) { Log(LOG_ERROR, "Found too many possible objects to convert - try adding the 'export' property to disambiguate one"); return false; } if (instances.empty()) { Log(LOG_ERROR, "Didn't find any objects in the scene"); return false; } } assert(instances.size() == 1); // if we got this far instance = instances[0].instance; transform = instances[0].transform; return true; } ////////////////////////////////////////////////////////////////////////// static bool ReverseSortWeight(const FCDJointWeightPair& a, const FCDJointWeightPair& b) { return (a.weight > b.weight); } void SkinReduceInfluences(FCDSkinController* skin, size_t maxInfluenceCount, float minimumWeight) { // Approximately equivalent to: // skin->ReduceInfluences(maxInfluenceCount, minimumWeight); // except this version merges multiple weights for the same joint for (size_t i = 0; i < skin->GetInfluenceCount(); ++i) { FCDSkinControllerVertex& influence = *skin->GetVertexInfluence(i); std::vector newWeights; for (size_t i = 0; i < influence.GetPairCount(); ++i) { FCDJointWeightPair* weight = influence.GetPair(i); for (size_t j = 0; j < newWeights.size(); ++j) { FCDJointWeightPair& newWeight = newWeights[j]; if (weight->jointIndex == newWeight.jointIndex) { newWeight.weight += weight->weight; goto MERGED_WEIGHTS; } } newWeights.push_back(*weight); MERGED_WEIGHTS: ; } // Put highest-weighted influences at the front of the list std::sort(newWeights.begin(), newWeights.end(), ReverseSortWeight); // Limit the maximum number of influences if (newWeights.size() > maxInfluenceCount) newWeights.resize(maxInfluenceCount); // Enforce the minimum weight per influence // (This is done here rather than in the earlier loop, because several // small weights for the same bone might add up to a value above the // threshold) while (!newWeights.empty() && newWeights.back().weight < minimumWeight) newWeights.pop_back(); // Renormalise, so sum(weights)=1 float totalWeight = 0; for (std::vector::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) totalWeight += itNW->weight; for (std::vector::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) itNW->weight /= totalWeight; // Copy new weights into the skin influence.SetPairCount(0); for (std::vector::iterator itNW = newWeights.begin(); itNW != newWeights.end(); ++itNW) influence.AddPair(itNW->jointIndex, itNW->weight); } skin->SetDirtyFlag(); } void FixSkeletonRoots(FCDControllerInstance& UNUSED(controllerInstance)) { // TODO: Need to reintroduce XSI support at some point #if 0 // HACK: The XSI exporter doesn't do a and FCollada doesn't // seem to know where else to look, so just guess that it's somewhere // under Scene_Root if (controllerInstance.GetSkeletonRoots().empty()) { // HACK (evil): SetSkeletonRoot is declared but not defined, and there's // no other proper way to modify the skeleton-roots list, so cheat horribly FUUriList& uriList = const_cast(controllerInstance.GetSkeletonRoots()); uriList.push_back(FUUri("Scene_Root")); controllerInstance.LinkImport(); } #endif } const Skeleton& FindSkeleton(const FCDControllerInstance& controllerInstance) { // I can't see any proper way to determine the real root of the skeleton, // so just choose an arbitrary bone and search upwards until we find a // recognised ancestor (or until we fall off the top of the tree) const Skeleton* skeleton = NULL; const FCDSceneNode* joint = controllerInstance.GetJoint(0); while (joint && (skeleton = Skeleton::FindSkeleton(joint->GetName().c_str())) == NULL) { joint = joint->GetParent(); } REQUIRE(skeleton != NULL, "recognised skeleton structure"); return *skeleton; } void TransformBones(std::vector& bones, const FMMatrix44& scaleTransform, bool yUp) { for (size_t i = 0; i < bones.size(); ++i) { // Apply the desired transformation to the bone coordinates FMVector3 trans(bones[i].translation, 0); trans = scaleTransform.TransformCoordinate(trans); bones[i].translation[0] = trans.x; bones[i].translation[1] = trans.y; bones[i].translation[2] = trans.z; // DON'T apply the transformation to orientation, because I can't get // that kind of thing to work in practice (interacting nicely between // the models and animations), so this function assumes the transform // just does scaling, so there's no need to rotate anything. (But I think // this code would work for rotation, though not very efficiently.) /* FMMatrix44 m = FMQuaternion(bones[i].orientation[0], bones[i].orientation[1], bones[i].orientation[2], bones[i].orientation[3]).ToMatrix(); m *= scaleTransform; HMatrix matrix; memcpy(matrix, m.Transposed().m, sizeof(matrix)); AffineParts parts; decomp_affine(matrix, &parts); bones[i].orientation[0] = parts.q.x; bones[i].orientation[1] = parts.q.y; bones[i].orientation[2] = parts.q.z; bones[i].orientation[3] = parts.q.w; */ if (yUp) { // TODO: this is all just guesses which seem to work for data // exported from XSI, rather than having been properly thought // through bones[i].translation[2] = -bones[i].translation[2]; bones[i].orientation[2] = -bones[i].orientation[2]; bones[i].orientation[3] = -bones[i].orientation[3]; } else { // Convert bone translations from xyz into xzy axes: std::swap(bones[i].translation[1], bones[i].translation[2]); // To convert the quaternions: imagine you're using the axis/angle // representation, then swap the y,z basis vectors and change the // direction of rotation by negating the angle ( => negating sin(angle) // => negating x,y,z => changing (x,y,z,w) to (-x,-z,-y,w) // but then (-x,-z,-y,w) == (x,z,y,-w) so do that instead) std::swap(bones[i].orientation[1], bones[i].orientation[2]); bones[i].orientation[3] = -bones[i].orientation[3]; } } } Index: ps/trunk/source/simulation2/components/ICmpTerritoryManager.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpTerritoryManager.h (revision 17593) +++ ps/trunk/source/simulation2/components/ICmpTerritoryManager.h (revision 17594) @@ -1,84 +1,89 @@ /* Copyright (C) 2015 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 . */ #ifndef INCLUDED_ICMPTERRITORYMANAGER #define INCLUDED_ICMPTERRITORYMANAGER #include "simulation2/system/Interface.h" #include "simulation2/helpers/Grid.h" #include "simulation2/helpers/Player.h" #include "simulation2/components/ICmpPosition.h" class ICmpTerritoryManager : public IComponent { public: virtual bool NeedUpdate(size_t* dirtyID) = 0; /** * Number of pathfinder navcells per territory tile. * Passability data is stored per navcell, but we probably don't need that much * resolution, and a lower resolution can make the boundary lines look prettier * and will take less memory, so we downsample the passability data. */ static const int NAVCELLS_PER_TERRITORY_TILE = 8; static const int TERRITORY_PLAYER_MASK = 0x1F; static const int TERRITORY_CONNECTED_MASK = 0x20; static const int TERRITORY_BLINKING_MASK = 0x40; static const int TERRITORY_PROCESSED_MASK = 0x80; //< For internal use; marks a tile as processed. /** * For each tile, the TERRITORY_PLAYER_MASK bits are player ID; * TERRITORY_CONNECTED_MASK is set if the tile is connected to a root object * (civ center etc). */ virtual const Grid& GetTerritoryGrid() = 0; /** * Get owner of territory at given position. * @return player ID of owner; 0 if neutral territory */ virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 0; /** * get the number of neighbour tiles for per player for the selected position * @return A list with the number of neighbour tiles per player */ virtual std::vector GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected) = 0; /** * Get whether territory at given position is connected to a root object * (civ center etc) owned by that territory's player. */ virtual bool IsConnected(entity_pos_t x, entity_pos_t z) = 0; /** * Set a piece of territory to blinking. Must be updated on every territory calculation */ virtual void SetTerritoryBlinking(entity_pos_t x, entity_pos_t z) = 0; /** * Returns the percentage of the world controlled by a given player as defined by * the number of territory cells the given player owns */ virtual u8 GetTerritoryPercentage(player_id_t player) = 0; + /** + * Enables or disables rendering of an territory borders. + */ + virtual void SetVisibility(bool visible) = 0; + DECLARE_INTERFACE_TYPE(TerritoryManager) }; #endif // INCLUDED_ICMPTERRITORYMANAGER Index: ps/trunk/source/simulation2/system/ComponentManager.cpp =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 17593) +++ ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 17594) @@ -1,1233 +1,1234 @@ /* Copyright (C) 2015 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 "ComponentManager.h" #include "DynamicSubscription.h" #include "IComponent.h" #include "ParamNode.h" #include "SimContext.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpTemplateManager.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" /** * Used for script-only message types. */ class CMessageScripted : public CMessage { public: virtual int GetType() const { return mtid; } virtual const char* GetScriptHandlerName() const { return handlerName.c_str(); } virtual const char* GetScriptGlobalHandlerName() const { return globalHandlerName.c_str(); } virtual JS::Value ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return msg.get(); } CMessageScripted(ScriptInterface& scriptInterface, int mtid, const std::string& name, JS::HandleValue msg) : mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(scriptInterface.GetJSRuntime(), msg) { } int mtid; std::string handlerName; std::string globalHandlerName; JS::PersistentRootedValue msg; }; CComponentManager::CComponentManager(CSimContext& context, shared_ptr rt, bool skipScriptFunctions) : m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine", "Simulation", rt), m_SimContext(context), m_CurrentlyHotloading(false) { context.SetComponentManager(this); m_ScriptInterface.SetCallbackData(static_cast (this)); // TODO: ought to seed the RNG (in a network-synchronised way) before we use it m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG); m_ScriptInterface.LoadGlobalScripts(); // For component script tests, the test system sets up its own scripted implementation of // these functions, so we skip registering them here in those cases if (!skipScriptFunctions) { m_ScriptInterface.RegisterFunction ("RegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterSystemComponentType"); m_ScriptInterface.RegisterFunction ("ReRegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterInterface"); m_ScriptInterface.RegisterFunction ("RegisterMessageType"); m_ScriptInterface.RegisterFunction ("RegisterGlobal"); m_ScriptInterface.RegisterFunction ("QueryInterface"); m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface"); m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface"); m_ScriptInterface.RegisterFunction ("PostMessage"); m_ScriptInterface.RegisterFunction ("BroadcastMessage"); m_ScriptInterface.RegisterFunction ("AddEntity"); m_ScriptInterface.RegisterFunction ("AddLocalEntity"); m_ScriptInterface.RegisterFunction ("DestroyEntity"); m_ScriptInterface.RegisterFunction ("FlushDestroyedEntities"); m_ScriptInterface.RegisterFunction ("ReadJSONFile"); m_ScriptInterface.RegisterFunction ("ReadCivJSONFile"); m_ScriptInterface.RegisterFunction, std::wstring, bool, CComponentManager::Script_FindJSONFiles> ("FindJSONFiles"); } // Define MT_*, IID_* as script globals, and store their names #define MESSAGE(name) m_ScriptInterface.SetGlobal("MT_" #name, (int)MT_##name); #define INTERFACE(name) \ m_ScriptInterface.SetGlobal("IID_" #name, (int)IID_##name); \ m_InterfaceIdsByName[#name] = IID_##name; #define COMPONENT(name) #include "simulation2/TypeList.h" #undef MESSAGE #undef INTERFACE #undef COMPONENT m_ScriptInterface.SetGlobal("INVALID_ENTITY", (int)INVALID_ENTITY); m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); m_ComponentsByInterface.resize(IID__LastNative); ResetState(); } CComponentManager::~CComponentManager() { ResetState(); } void CComponentManager::LoadComponentTypes() { #define MESSAGE(name) \ RegisterMessageType(MT_##name, #name); #define INTERFACE(name) \ extern void RegisterComponentInterface_##name(ScriptInterface&); \ RegisterComponentInterface_##name(m_ScriptInterface); #define COMPONENT(name) \ extern void RegisterComponentType_##name(CComponentManager&); \ m_CurrentComponent = CID_##name; \ RegisterComponentType_##name(*this); #include "simulation2/TypeList.h" m_CurrentComponent = CID__Invalid; #undef MESSAGE #undef INTERFACE #undef COMPONENT } bool CComponentManager::LoadScript(const VfsPath& filename, bool hotload) { m_CurrentlyHotloading = hotload; CVFSFile file; PSRETURN loadOk = file.Load(g_VFS, filename); if (loadOk != PSRETURN_OK) // VFS will log the failed file and the reason return false; std::string content = file.DecodeUTF8(); // assume it's UTF-8 bool ok = m_ScriptInterface.LoadScript(filename, content); return ok; } void CComponentManager::Script_RegisterComponentType_Common(ScriptInterface::CxPrivate* pCxPrivate, int iid, std::string cname, JS::HandleValue ctor, bool reRegister, bool systemComponent) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); JSContext* cx = componentManager->m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); // Find the C++ component that wraps the interface int cidWrapper = componentManager->GetScriptWrapper(iid); if (cidWrapper == CID__Invalid) { componentManager->m_ScriptInterface.ReportError("Invalid interface id"); return; } const ComponentType& ctWrapper = componentManager->m_ComponentTypesById[cidWrapper]; bool mustReloadComponents = false; // for hotloading ComponentTypeId cid = componentManager->LookupCID(cname); if (cid == CID__Invalid) { if (reRegister) { std::string msg("ReRegistering component type that was not registered before '"+cname+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } // Allocate a new cid number cid = componentManager->m_NextScriptComponentTypeId++; componentManager->m_ComponentTypeIdsByName[cname] = cid; if (systemComponent) componentManager->MarkScriptedComponentForSystemEntity(cid); } else { // Component type is already loaded, so do hotloading: if (!componentManager->m_CurrentlyHotloading && !reRegister) { std::string msg("Registering component type with already-registered name '"+cname+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } const ComponentType& ctPrevious = componentManager->m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) { std::string msg("Loading script component type with same name '"+cname+"' as native component"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } // We don't support changing the IID of a component type (it would require fiddling // around with m_ComponentsByInterface and being careful to guarantee uniqueness per entity) if (ctPrevious.iid != iid) { // ...though it only matters if any components exist with this type if (!componentManager->m_ComponentsByTypeId[cid].empty()) { componentManager->m_ScriptInterface.ReportError("Hotloading script component type mustn't change interface ID"); return; } } // Remove the old component type's message subscriptions std::map >::iterator it; for (it = componentManager->m_LocalMessageSubscriptions.begin(); it != componentManager->m_LocalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } for (it = componentManager->m_GlobalMessageSubscriptions.begin(); it != componentManager->m_GlobalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } mustReloadComponents = true; } std::string schema = ""; { JS::RootedValue prototype(cx); if (componentManager->m_ScriptInterface.GetProperty(ctor, "prototype", &prototype) && componentManager->m_ScriptInterface.HasProperty(prototype, "Schema")) { componentManager->m_ScriptInterface.GetProperty(prototype, "Schema", schema); } } // Construct a new ComponentType, using the wrapper's alloc functions ComponentType ct( CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, schema, DefPersistentRooted(cx, ctor) ); componentManager->m_ComponentTypesById[cid] = std::move(ct); componentManager->m_CurrentComponent = cid; // needed by Subscribe // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: JS::RootedValue protoVal(cx); if (!componentManager->m_ScriptInterface.GetProperty(ctor, "prototype", &protoVal)) return; // error std::vector methods; JS::RootedObject proto(cx); if (!protoVal.isObjectOrNull()) return; // error proto = protoVal.toObjectOrNull(); if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(protoVal, "On", methods)) return; // error for (std::vector::const_iterator it = methods.begin(); it != methods.end(); ++it) { std::string name = (*it).substr(2); // strip the "On" prefix // Handle "OnGlobalFoo" functions specially bool isGlobal = false; if (name.substr(0, 6) == "Global") { isGlobal = true; name = name.substr(6); } std::map::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name); if (mit == componentManager->m_MessageTypeIdsByName.end()) { std::string msg("Registered component has unrecognised '" + *it + "' message handler method"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } if (isGlobal) componentManager->SubscribeGloballyToMessageType(mit->second); else componentManager->SubscribeToMessageType(mit->second); } componentManager->m_CurrentComponent = CID__Invalid; if (mustReloadComponents) { // For every script component with this cid, we need to switch its // prototype from the old constructor's prototype property to the new one's const std::map& comps = componentManager->m_ComponentsByTypeId[cid]; std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { JS::RootedValue instance(cx, eit->second->GetJSInstance()); if (!instance.isNull()) { componentManager->m_ScriptInterface.SetPrototype(instance, protoVal); } } } } void CComponentManager::Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, std::string cname, JS::HandleValue ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, false); componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); } void CComponentManager::Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, std::string cname, JS::HandleValue ctor) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, false, true); componentManager->m_ScriptInterface.SetGlobal(cname.c_str(), ctor, componentManager->m_CurrentlyHotloading); } void CComponentManager::Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, std::string cname, JS::HandleValue ctor) { Script_RegisterComponentType_Common(pCxPrivate, iid, cname, ctor, true, false); } void CComponentManager::Script_RegisterInterface(ScriptInterface::CxPrivate* pCxPrivate, std::string name) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::map::iterator it = componentManager->m_InterfaceIdsByName.find(name); if (it != componentManager->m_InterfaceIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) { std::string msg("Registering interface with already-registered name '"+name+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); } return; } // IIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_InterfaceIdsByName.size() + 1; componentManager->m_InterfaceIdsByName[name] = (InterfaceId)id; componentManager->m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterMessageType(ScriptInterface::CxPrivate* pCxPrivate, std::string name) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::map::iterator it = componentManager->m_MessageTypeIdsByName.find(name); if (it != componentManager->m_MessageTypeIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) { std::string msg("Registering message type with already-registered name '"+name+"'"); componentManager->m_ScriptInterface.ReportError(msg.c_str()); } return; } // MTIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_MessageTypeIdsByName.size() + 1; componentManager->RegisterMessageType((MessageTypeId)id, name.c_str()); componentManager->m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, std::string name, JS::HandleValue value) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); // Set the value, and accept duplicates only if hotloading (otherwise it's an error, // in order to detect accidental duplicate definitions of globals) componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading); } IComponent* CComponentManager::Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid); return component; } std::vector CComponentManager::Script_GetEntitiesWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::vector ret; const InterfaceListUnordered& ents = componentManager->GetEntitiesWithInterfaceUnordered(iid); for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) if (!ENTITY_IS_LOCAL(it->first)) ret.push_back(it->first); std::sort(ret.begin(), ret.end()); return ret; } std::vector CComponentManager::Script_GetComponentsWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::vector ret; InterfaceList ents = componentManager->GetEntitiesWithInterface(iid); for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) ret.push_back(it->second); // TODO: maybe we should exclude local entities return ret; } CMessage* CComponentManager::ConstructMessage(int mtid, JS::HandleValue data) { if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here) LOGERROR("PostMessage with invalid message type ID '%d'", mtid); if (mtid < MT__LastNative) { return CMessageFromJSVal(mtid, m_ScriptInterface, data); } else { return new CMessageScripted(m_ScriptInterface, mtid, m_MessageTypeNamesById[mtid], data); } } void CComponentManager::Script_PostMessage(ScriptInterface::CxPrivate* pCxPrivate, int ent, int mtid, JS::HandleValue data) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->PostMessage(ent, *msg); delete msg; } void CComponentManager::Script_BroadcastMessage(ScriptInterface::CxPrivate* pCxPrivate, int mtid, JS::HandleValue data) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->BroadcastMessage(*msg); delete msg; } int CComponentManager::Script_AddEntity(ScriptInterface::CxPrivate* pCxPrivate, std::string templateName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewEntity()); return (int)ent; } int CComponentManager::Script_AddLocalEntity(ScriptInterface::CxPrivate* pCxPrivate, std::string templateName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewLocalEntity()); return (int)ent; } void CComponentManager::Script_DestroyEntity(ScriptInterface::CxPrivate* pCxPrivate, int ent) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->DestroyComponentsSoon(ent); } void CComponentManager::Script_FlushDestroyedEntities(ScriptInterface::CxPrivate *pCxPrivate) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); componentManager->FlushDestroyedComponents(); } void CComponentManager::ResetState() { // Delete all dynamic message subscriptions m_DynamicMessageSubscriptionsNonsync.clear(); m_DynamicMessageSubscriptionsNonsyncByComponent.clear(); // Delete all IComponents std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.begin(); for (; eit != iit->second.end(); ++eit) { eit->second->Deinit(); m_ComponentTypesById[iit->first].dealloc(eit->second); } } std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) ifcit->clear(); m_ComponentsByTypeId.clear(); // Delete all SEntityComponentCaches std::map::iterator ccit = m_ComponentCaches.begin(); for (; ccit != m_ComponentCaches.end(); ++ccit) free(ccit->second); m_ComponentCaches.clear(); m_SystemEntity = CEntityHandle(); m_DestructionQueue.clear(); // Reset IDs m_NextEntityId = SYSTEM_ENTITY + 1; m_NextLocalEntityId = FIRST_LOCAL_ENTITY; } void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c(CT_Native, iid, alloc, dealloc, name, schema, std::move(DefPersistentRooted())); m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c(CT_ScriptWrapper, iid, alloc, dealloc, name, schema, std::move(DefPersistentRooted())); m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; // TODO: merge with RegisterComponentType } void CComponentManager::MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid) { m_ScriptedSystemComponents.push_back(cid); } void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name) { m_MessageTypeIdsByName[name] = mtid; m_MessageTypeNamesById[mtid] = name; } void CComponentManager::SubscribeToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_LocalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_GlobalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::FlattenDynamicSubscriptions() { std::map::iterator it; for (it = m_DynamicMessageSubscriptionsNonsync.begin(); it != m_DynamicMessageSubscriptionsNonsync.end(); ++it) { it->second.Flatten(); } } void CComponentManager::DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enable) { if (enable) { bool newlyInserted = m_DynamicMessageSubscriptionsNonsyncByComponent[component].insert(mtid).second; if (newlyInserted) m_DynamicMessageSubscriptionsNonsync[mtid].Add(component); } else { size_t numRemoved = m_DynamicMessageSubscriptionsNonsyncByComponent[component].erase(mtid); if (numRemoved) m_DynamicMessageSubscriptionsNonsync[mtid].Remove(component); } } void CComponentManager::RemoveComponentDynamicSubscriptions(IComponent* component) { std::map >::iterator it = m_DynamicMessageSubscriptionsNonsyncByComponent.find(component); if (it == m_DynamicMessageSubscriptionsNonsyncByComponent.end()) return; std::set::iterator mtit; for (mtit = it->second.begin(); mtit != it->second.end(); ++mtit) { m_DynamicMessageSubscriptionsNonsync[*mtit].Remove(component); // Need to flatten the subscription lists immediately to avoid dangling IComponent* references m_DynamicMessageSubscriptionsNonsync[*mtit].Flatten(); } m_DynamicMessageSubscriptionsNonsyncByComponent.erase(it); } CComponentManager::ComponentTypeId CComponentManager::LookupCID(const std::string& cname) const { std::map::const_iterator it = m_ComponentTypeIdsByName.find(cname); if (it == m_ComponentTypeIdsByName.end()) return CID__Invalid; return it->second; } std::string CComponentManager::LookupComponentTypeName(ComponentTypeId cid) const { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) return ""; return it->second.name; } CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(InterfaceId iid) { if (iid >= IID__LastNative && iid <= (int)m_InterfaceIdsByName.size()) // use <= since IDs start at 1 return CID_UnknownScript; std::map::const_iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.iid == iid && it->second.type == CT_ScriptWrapper) return it->first; std::map::const_iterator iiit = m_InterfaceIdsByName.begin(); for (; iiit != m_InterfaceIdsByName.end(); ++iiit) if (iiit->second == iid) { LOGERROR("No script wrapper found for interface id %d '%s'", iid, iiit->first.c_str()); return CID__Invalid; } LOGERROR("No script wrapper found for interface id %d", iid); return CID__Invalid; } entity_id_t CComponentManager::AllocateNewEntity() { entity_id_t id = m_NextEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewLocalEntity() { entity_id_t id = m_NextLocalEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewEntity(entity_id_t preferredId) { // TODO: ensure this ID hasn't been allocated before // (this might occur with broken map files) // Trying to actually add two entities with the same id will fail in AddEntitiy entity_id_t id = preferredId; // Ensure this ID won't be allocated again if (id >= m_NextEntityId) m_NextEntityId = id+1; // TODO: check for overflow return id; } bool CComponentManager::AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode) { IComponent* component = ConstructComponent(ent, cid); if (!component) return false; component->Init(paramNode); return true; } void CComponentManager::AddSystemComponents(bool skipScriptedComponents, bool skipAI) { CParamNode noParam; AddComponent(m_SystemEntity, CID_TemplateManager, noParam); + AddComponent(m_SystemEntity, CID_CinemaManager, noParam); AddComponent(m_SystemEntity, CID_CommandQueue, noParam); AddComponent(m_SystemEntity, CID_ObstructionManager, noParam); AddComponent(m_SystemEntity, CID_ParticleManager, noParam); AddComponent(m_SystemEntity, CID_Pathfinder, noParam); AddComponent(m_SystemEntity, CID_ProjectileManager, noParam); AddComponent(m_SystemEntity, CID_RangeManager, noParam); AddComponent(m_SystemEntity, CID_SoundManager, noParam); AddComponent(m_SystemEntity, CID_Terrain, noParam); AddComponent(m_SystemEntity, CID_TerritoryManager, noParam); AddComponent(m_SystemEntity, CID_UnitRenderer, noParam); AddComponent(m_SystemEntity, CID_WaterManager, noParam); // Add scripted system components: if (!skipScriptedComponents) { for (uint32_t i = 0; i < m_ScriptedSystemComponents.size(); ++i) AddComponent(m_SystemEntity, m_ScriptedSystemComponents[i], noParam); if (!skipAI) AddComponent(m_SystemEntity, CID_AIManager, noParam); } } IComponent* CComponentManager::ConstructComponent(CEntityHandle ent, ComponentTypeId cid) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) { LOGERROR("Invalid component id %d", cid); return NULL; } const ComponentType& ct = it->second; ENSURE((size_t)ct.iid < m_ComponentsByInterface.size()); boost::unordered_map& emap1 = m_ComponentsByInterface[ct.iid]; if (emap1.find(ent.GetId()) != emap1.end()) { LOGERROR("Multiple components for interface %d", ct.iid); return NULL; } std::map& emap2 = m_ComponentsByTypeId[cid]; // If this is a scripted component, construct the appropriate JS object first JS::RootedValue obj(cx); if (ct.type == CT_Script) { m_ScriptInterface.CallConstructor(ct.ctor.get(), JS::HandleValueArray::empty(), &obj); if (obj.isNull()) { LOGERROR("Script component constructor failed"); return NULL; } } // Construct the new component IComponent* component = ct.alloc(m_ScriptInterface, obj); ENSURE(component); component->SetEntityHandle(ent); component->SetSimContext(m_SimContext); // Store a reference to the new component emap1.insert(std::make_pair(ent.GetId(), component)); emap2.insert(std::make_pair(ent.GetId(), component)); // TODO: We need to more careful about this - if an entity is constructed by a component // while we're iterating over all components, this will invalidate the iterators and everything // will break. // We probably need some kind of delayed addition, so they get pushed onto a queue and then // inserted into the world later on. (Be careful about immediation deletion in that case, too.) SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && ct.iid < (int)cache->numInterfaces && cache->interfaces[ct.iid] == NULL); cache->interfaces[ct.iid] = component; return component; } void CComponentManager::AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component) { // Just add it into the by-interface map, not the by-component-type map, // so it won't be considered for messages or deletion etc boost::unordered_map& emap1 = m_ComponentsByInterface.at(iid); if (emap1.find(ent.GetId()) != emap1.end()) debug_warn(L"Multiple components for interface"); emap1.insert(std::make_pair(ent.GetId(), &component)); SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && iid < (int)cache->numInterfaces && cache->interfaces[iid] == NULL); cache->interfaces[iid] = &component; } CEntityHandle CComponentManager::AllocateEntityHandle(entity_id_t ent) { // Interface IDs start at 1, and SEntityComponentCache is defined with a 1-sized array, // so we need space for an extra m_InterfaceIdsByName.size() items SEntityComponentCache* cache = (SEntityComponentCache*)calloc(1, sizeof(SEntityComponentCache) + sizeof(IComponent*) * m_InterfaceIdsByName.size()); ENSURE(cache != NULL); cache->numInterfaces = m_InterfaceIdsByName.size() + 1; ENSURE(m_ComponentCaches.find(ent) == m_ComponentCaches.end()); m_ComponentCaches[ent] = cache; return CEntityHandle(ent, cache); } CEntityHandle CComponentManager::LookupEntityHandle(entity_id_t ent, bool allowCreate) { std::map::iterator it; it = m_ComponentCaches.find(ent); if (it == m_ComponentCaches.end()) { if (allowCreate) return AllocateEntityHandle(ent); else return CEntityHandle(ent, NULL); } else return CEntityHandle(ent, it->second); } void CComponentManager::InitSystemEntity() { ENSURE(m_SystemEntity.GetId() == INVALID_ENTITY); m_SystemEntity = AllocateEntityHandle(SYSTEM_ENTITY); m_SimContext.SetSystemEntity(m_SystemEntity); } entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entity_id_t ent) { ICmpTemplateManager *cmpTemplateManager = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager)); if (!cmpTemplateManager) { debug_warn(L"No ICmpTemplateManager loaded"); return INVALID_ENTITY; } const CParamNode* tmpl = cmpTemplateManager->LoadTemplate(ent, utf8_from_wstring(templateName), -1); if (!tmpl) return INVALID_ENTITY; // LoadTemplate will have reported the error // This also ensures that ent does not exist CEntityHandle handle = AllocateEntityHandle(ent); // Construct a component for each child of the root element const CParamNode::ChildrenMap& tmplChilds = tmpl->GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = tmplChilds.begin(); it != tmplChilds.end(); ++it) { // Ignore attributes on the root element if (it->first.length() && it->first[0] == '@') continue; CComponentManager::ComponentTypeId cid = LookupCID(it->first); if (cid == CID__Invalid) { LOGERROR("Unrecognised component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } if (!AddComponent(handle, cid, it->second)) { LOGERROR("Failed to construct component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } // TODO: maybe we should delete already-constructed components if one of them fails? } CMessageCreate msg(ent); PostMessage(ent, msg); return ent; } void CComponentManager::DestroyComponentsSoon(entity_id_t ent) { m_DestructionQueue.push_back(ent); } void CComponentManager::FlushDestroyedComponents() { while (!m_DestructionQueue.empty()) { // Make a copy of the destruction queue, so that the iterators won't be invalidated if the // CMessageDestroy handlers try to destroy more entities themselves std::vector queue; queue.swap(m_DestructionQueue); // Flatten all the dynamic subscriptions to ensure there are no dangling // references in the 'removed' lists to components we're going to delete FlattenDynamicSubscriptions(); for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) { entity_id_t ent = *it; CEntityHandle handle = LookupEntityHandle(ent); CMessageDestroy msg(ent); PostMessage(ent, msg); // Destroy the components, and remove from m_ComponentsByTypeId: std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.find(ent); if (eit != iit->second.end()) { eit->second->Deinit(); RemoveComponentDynamicSubscriptions(eit->second); m_ComponentTypesById[iit->first].dealloc(eit->second); iit->second.erase(ent); handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL; } } free(handle.GetComponentCache()); m_ComponentCaches.erase(ent); // Remove from m_ComponentsByInterface std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) { ifcit->erase(ent); } } } } IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return NULL; } boost::unordered_map::const_iterator eit = m_ComponentsByInterface[iid].find(ent); if (eit == m_ComponentsByInterface[iid].end()) { // This entity doesn't implement this interface return NULL; } return eit->second; } CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { std::vector > ret; if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return ret; } ret.reserve(m_ComponentsByInterface[iid].size()); boost::unordered_map::const_iterator it = m_ComponentsByInterface[iid].begin(); for (; it != m_ComponentsByInterface[iid].end(); ++it) ret.push_back(*it); std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID return ret; } static CComponentManager::InterfaceListUnordered g_EmptyEntityMap; const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesWithInterfaceUnordered(InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return g_EmptyEntityMap; } return m_ComponentsByInterface[iid]; } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) { // Send the message to components of ent, that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.find(ent); if (eit != emap->second.end()) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(ent, msg); } void CComponentManager::BroadcastMessage(const CMessage& msg) { // Send the message to components of all entities that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(INVALID_ENTITY, msg); } void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) { // (Common functionality for PostMessage and BroadcastMessage) // Send the message to components of all entities that subscribed globally to this message std::map >::const_iterator it; it = m_GlobalMessageSubscriptions.find(msg.GetType()); if (it != m_GlobalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Special case: Messages for local entities shouldn't be sent to script // components that subscribed globally, so that we don't have to worry about // them accidentally picking up non-network-synchronised data. if (ENTITY_IS_LOCAL(ent)) { std::map::const_iterator it = m_ComponentTypesById.find(*ctit); if (it != m_ComponentTypesById.end() && it->second.type == CT_Script) continue; } // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, true); } } // Send the message to component instances that dynamically subscribed to this message std::map::iterator dit = m_DynamicMessageSubscriptionsNonsync.find(msg.GetType()); if (dit != m_DynamicMessageSubscriptionsNonsync.end()) { dit->second.Flatten(); const std::vector& dynamic = dit->second.GetComponents(); for (size_t i = 0; i < dynamic.size(); i++) dynamic[i]->HandleMessage(msg, false); } } std::string CComponentManager::GenerateSchema() { std::string numericOperation = "" "" "" "add" "mul" "" "" ""; std::string schema = "" "" "" + numericOperation + "" "" "0" + numericOperation + "" "" "0" + numericOperation + "" "" "" "" "" "" "" "" "" "" "" "" ""; std::map > interfaceComponentTypes; std::vector componentTypes; for (std::map::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it) { schema += "" "" "" + it->second.schema + "" "" ""; interfaceComponentTypes[it->second.iid].push_back(it->second.name); componentTypes.push_back(it->second.name); } // Declare the implementation of each interface, for documentation for (std::map::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it) { schema += ""; std::vector& cts = interfaceComponentTypes[it->second]; for (size_t i = 0; i < cts.size(); ++i) schema += ""; schema += ""; } // List all the component types, in alphabetical order (to match the reordering performed by CParamNode). // (We do it this way, rather than ing all the interface definitions (which would additionally perform // a check that we don't use multiple component types of the same interface in one file), because libxml2 gives // useless error messages in the latter case; this way lets it report the real error.) std::sort(componentTypes.begin(), componentTypes.end()); schema += "" "" ""; for (std::vector::const_iterator it = componentTypes.begin(); it != componentTypes.end(); ++it) schema += ""; schema += "" ""; schema += ""; return schema; } JS::Value CComponentManager::Script_ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring fileName) { return ReadJSONFile(pCxPrivate, L"simulation/data", fileName); } JS::Value CComponentManager::Script_ReadCivJSONFile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring fileName) { return ReadJSONFile(pCxPrivate, L"simulation/data/civs", fileName); } JS::Value CComponentManager::ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath, const std::wstring& fileName) { CComponentManager* componentManager = static_cast (pCxPrivate->pCBData); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); VfsPath path = VfsPath(filePath) / fileName; JS::RootedValue out(cx); componentManager->GetScriptInterface().ReadJSONFile(path, &out); return out; } Status CComponentManager::FindJSONFilesCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) { FindJSONFilesCallbackData* data = (FindJSONFilesCallbackData*)cbData; VfsPath pathstem = pathname.ChangeExtension(L""); // Strip the root from the path std::wstring name = pathstem.string().substr(data->path.string().length()); data->templates.push_back(std::string(name.begin(), name.end())); return INFO::OK; } std::vector CComponentManager::Script_FindJSONFiles(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring subPath, bool recursive) { FindJSONFilesCallbackData cbData; cbData.path = VfsPath(L"simulation/data/" + subPath + L"/"); int dir_flags = 0; if (recursive) { dir_flags = vfs::DIR_RECURSIVE; } // Find all simulation/data/{subPath}/*.json recursively Status ret = vfs::ForEachFile(g_VFS, cbData.path, FindJSONFilesCallback, (uintptr_t)&cbData, L"*.json", dir_flags); if (ret != INFO::OK) { // Some error reading directory wchar_t error[200]; LOGERROR("Error reading directory '%s': %s", cbData.path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error)))); } return cbData.templates; } Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp (revision 17593) +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp (revision 17594) @@ -1,218 +1,214 @@ /* Copyright (C) 2014 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 "../MessagePasserImpl.h" #include "../GameLoop.h" #include "../View.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "gui/GUIManager.h" #include "gui/GUI.h" #include "lib/external_libraries/libsdl.h" #include "lib/sysdep/cpu.h" #include "maths/MathUtil.h" #include "ps/Game.h" #include "ps/Util.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/GameSetup.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpSoundManager.h" extern void (*Atlas_GLSwapBuffers)(void* context); namespace AtlasMessage { MESSAGEHANDLER(MessageTrace) { ((MessagePasserImpl*)g_MessagePasser)->SetTrace(msg->enable); } MESSAGEHANDLER(Screenshot) { if (msg->big) WriteBigScreenshot(L".bmp", msg->tiles); else WriteScreenshot(L".png"); } QUERYHANDLER(CinemaRecord) { - CCinemaManager* manager = g_Game->GetView()->GetCinema(); - manager->SetCurrentPath(*msg->path, false, false); - const int w = msg->width, h = msg->height; { g_Renderer.Resize(w, h); SViewPort vp = { 0, 0, w, h }; g_Game->GetView()->GetCamera()->SetViewPort(vp); g_Game->GetView()->SetCameraProjection(); } unsigned char* img = new unsigned char [w*h*3]; unsigned char* temp = new unsigned char[w*3]; int num_frames = msg->framerate * msg->duration; AtlasView::GetView_Game()->SaveState(L"cinema_record"); // Set it to update the simulation at normal speed AtlasView::GetView_Game()->SetSpeedMultiplier(1.f); for (int frame = 0; frame < num_frames; ++frame) { AtlasView::GetView_Game()->Update(1.f / msg->framerate); - manager->MoveToPointAt((float)frame/msg->framerate); Render(); Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas); glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, img); // Swap the rows around, else the image will be upside down //* // TODO: BGR24 output doesn't need flipping, YUV420 and RGBA32 do for (int y = 0; y < h/2; ++y) { memcpy(temp, &img[y*w*3], w*3); memcpy(&img[y*w*3], &img[(h-1-y)*w*3], w*3); memcpy(&img[(h-1-y)*w*3], temp, w*3); } //*/ // Call the user-supplied function with this data, so they can // store it as a video sCinemaRecordCB cbdata = { img }; msg->cb.Call(cbdata); } // Pause the game once we've finished AtlasView::GetView_Game()->SetSpeedMultiplier(0.f); AtlasView::GetView_Game()->RestoreState(L"cinema_record"); // TODO: delete the saved state now that we don't need it any more delete[] img; delete[] temp; // Restore viewport { g_Renderer.Resize(g_xres, g_yres); SViewPort vp = { 0, 0, g_xres, g_yres }; g_Game->GetView()->GetCamera()->SetViewPort(vp); g_Game->GetView()->SetCameraProjection(); } } QUERYHANDLER(Ping) { UNUSED2(msg); } MESSAGEHANDLER(SimStopMusic) { UNUSED2(msg); CmpPtr cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpSoundManager) cmpSoundManager->StopMusic(); } MESSAGEHANDLER(SimStateSave) { AtlasView::GetView_Game()->SaveState(*msg->label); } MESSAGEHANDLER(SimStateRestore) { AtlasView::GetView_Game()->RestoreState(*msg->label); } QUERYHANDLER(SimStateDebugDump) { msg->dump = AtlasView::GetView_Game()->DumpState(msg->binary); } MESSAGEHANDLER(SimPlay) { AtlasView::GetView_Game()->SetSpeedMultiplier(msg->speed); AtlasView::GetView_Game()->SetTesting(msg->simTest); } MESSAGEHANDLER(JavaScript) { g_GUI->GetActiveGUI()->GetScriptInterface()->LoadGlobalScript(L"Atlas", *msg->command); } MESSAGEHANDLER(GuiSwitchPage) { g_GUI->SwitchPage(*msg->page, NULL, JS::UndefinedHandleValue); } MESSAGEHANDLER(GuiMouseButtonEvent) { SDL_Event_ ev = { { 0 } }; ev.ev.type = msg->pressed ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; ev.ev.button.button = msg->button; ev.ev.button.state = msg->pressed ? SDL_PRESSED : SDL_RELEASED; float x, y; msg->pos->GetScreenSpace(x, y); ev.ev.button.x = (u16)clamp((int)x, 0, g_xres); ev.ev.button.y = (u16)clamp((int)y, 0, g_yres); in_dispatch_event(&ev); } MESSAGEHANDLER(GuiMouseMotionEvent) { SDL_Event_ ev = { { 0 } }; ev.ev.type = SDL_MOUSEMOTION; float x, y; msg->pos->GetScreenSpace(x, y); ev.ev.motion.x = (u16)clamp((int)x, 0, g_xres); ev.ev.motion.y = (u16)clamp((int)y, 0, g_yres); in_dispatch_event(&ev); } MESSAGEHANDLER(GuiKeyEvent) { SDL_Event_ ev = { { 0 } }; ev.ev.type = msg->pressed ? SDL_KEYDOWN : SDL_KEYUP; ev.ev.key.keysym.sym = (SDL_Keycode)(int)msg->sdlkey; in_dispatch_event(&ev); } MESSAGEHANDLER(GuiCharEvent) { // wxWidgets has special Char events but SDL doesn't, so convert it to // a keydown+keyup sequence. (We do the conversion here instead of on // the wx side to avoid nondeterministic behaviour caused by async messaging.) SDL_Event_ ev = { { 0 } }; ev.ev.type = SDL_KEYDOWN; ev.ev.key.keysym.sym = (SDL_Keycode)(int)msg->sdlkey; in_dispatch_event(&ev); ev.ev.type = SDL_KEYUP; in_dispatch_event(&ev); } } Index: ps/trunk/source/graphics/CinemaManager.cpp =================================================================== --- ps/trunk/source/graphics/CinemaManager.cpp (revision 17593) +++ ps/trunk/source/graphics/CinemaManager.cpp (revision 17594) @@ -1,115 +1,297 @@ /* Copyright (C) 2015 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 #include +#include -#include "CinemaManager.h" - -#include "CinemaPath.h" -#include "ps/CStr.h" +#include "graphics/Camera.h" +#include "graphics/CinemaManager.h" +#include "graphics/GameView.h" +#include "gui/CGUI.h" +#include "gui/GUIManager.h" +#include "gui/IGUIObject.h" +#include "lib/ogl.h" +#include "maths/MathUtil.h" +#include "maths/Quaternion.h" +#include "maths/Vector3D.h" #include "maths/Vector4D.h" +#include "ps/CLogger.h" +#include "ps/CStr.h" +#include "ps/Game.h" +#include "ps/Hotkey.h" +#include "simulation2/components/ICmpOverlayRenderer.h" +#include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpSelectable.h" +#include "simulation2/components/ICmpTerritoryManager.h" +#include "simulation2/MessageTypes.h" +#include "simulation2/system/ComponentManager.h" +#include "simulation2/Simulation2.h" +#include "renderer/Renderer.h" -CCinemaManager::CCinemaManager() : m_DrawCurrentSpline(false), m_Active(true), m_ValidCurrent(false) + +CCinemaManager::CCinemaManager() + : m_DrawPaths(false) { - m_CurrentPath = m_Paths.end(); } -void CCinemaManager::AddPath(CCinemaPath path, const CStrW& name) +void CCinemaManager::AddPath(const CStrW& name, const CCinemaPath& path) { - ENSURE( m_Paths.find( name ) == m_Paths.end() ); - m_Paths[name] = path; + if (m_CinematicSimulationData.m_Paths.find(name) != m_CinematicSimulationData.m_Paths.end()) + { + LOGWARNING("Path with name '%s' already exists", name.ToUTF8()); + return; + } + m_CinematicSimulationData.m_Paths[name] = path; } -void CCinemaManager::QueuePath(const CStrW& name, bool queue) +void CCinemaManager::AddPathToQueue(const CStrW& name) { - if (!m_PathQueue.empty() && queue == false) + if (!HasPath(name)) { + LOGWARNING("Path with name '%s' doesn't exist", name.ToUTF8()); return; } - else + m_CinematicSimulationData.m_PathQueue.push_back(m_CinematicSimulationData.m_Paths[name]); +} + +void CCinemaManager::ClearQueue() +{ + m_CinematicSimulationData.m_PathQueue.clear(); +} + +void CCinemaManager::SetAllPaths(const std::map& paths) +{ + m_CinematicSimulationData.m_Paths = paths; +} + +bool CCinemaManager::HasPath(const CStrW& name) const +{ + return m_CinematicSimulationData.m_Paths.find(name) != m_CinematicSimulationData.m_Paths.end(); +} + +void CCinemaManager::SetEnabled(bool enabled) +{ + // TODO: maybe assert? + if (m_CinematicSimulationData.m_PathQueue.empty() && enabled) + { + enabled = false; + m_CinematicSimulationData.m_Paused = true; + } + + if (m_CinematicSimulationData.m_Enabled == enabled) + return; + + // TODO: Enabling/Disabling does not work if the session GUI page is not the top page. + // This can happen in various situations, for example when the player wins/looses the game + // while the cinematic is running (a message box is the top page in this case). + // It might be better to disable the whole GUI during the cinematic instead of a specific + // GUI object. + + // sn - session gui object + IGUIObject *sn = g_GUI->FindObjectByName("sn"); + CmpPtr cmpRangeManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); + CmpPtr cmpTerritoryManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); + + // GUI visibility + if (sn) + { + if (enabled) + sn->SetSetting("hidden", L"true"); + else + sn->SetSetting("hidden", L"false"); + } + + // Overlay visibility + g_Renderer.SetOptionBool(CRenderer::Option::OPT_SILHOUETTES, !enabled); + if (cmpRangeManager) { - ENSURE(HasTrack(name)); - m_PathQueue.push_back(m_Paths[name]); + if (enabled) + m_CinematicSimulationData.m_MapRevealed = cmpRangeManager->GetLosRevealAll(-1); + // TODO: improve m_MapRevealed state and without fade in + cmpRangeManager->SetLosRevealAll(-1, enabled); } + if (cmpTerritoryManager) + cmpTerritoryManager->SetVisibility(!enabled); + ICmpSelectable::SetOverrideVisibility(!enabled); + ICmpOverlayRenderer::SetOverrideVisibility(!enabled); + + m_CinematicSimulationData.m_Enabled = enabled; } -void CCinemaManager::OverridePath(const CStrW& name) +void CCinemaManager::Play() { - m_PathQueue.clear(); - ENSURE(HasTrack(name)); - m_PathQueue.push_back( m_Paths[name] ); + m_CinematicSimulationData.m_Paused = false; } -void CCinemaManager::SetAllPaths(const std::map& paths) +void CCinemaManager::Stop() { - CStrW name; - m_Paths = paths; + m_CinematicSimulationData.m_PathQueue.clear(); } -void CCinemaManager::SetCurrentPath(const CStrW& name, bool current, bool drawLines) + +void CCinemaManager::Update(const float deltaRealTime) { - if ( !HasTrack(name) ) - m_ValidCurrent = false; - else - m_ValidCurrent = true; + if (g_Game->m_Paused != m_CinematicSimulationData.m_Paused) + { + m_CinematicSimulationData.m_Paused = g_Game->m_Paused; + + // sn - session gui object + IGUIObject *sn = g_GUI->FindObjectByName("sn"); + + // GUI visibility + if (sn) + { + if (m_CinematicSimulationData.m_Paused) + sn->SetSetting("hidden", L"false"); + else + sn->SetSetting("hidden", L"true"); + } + } - m_CurrentPath = m_Paths.find(name); - m_DrawCurrentSpline = current; - m_DrawLines = drawLines; - DrawSpline(); + if (m_CinematicSimulationData.m_PathQueue.empty() || !m_CinematicSimulationData.m_Enabled || m_CinematicSimulationData.m_Paused) + return; + + if (HotkeyIsPressed("leave")) + { + // TODO: implement skip + } + + m_CinematicSimulationData.m_PathQueue.front().Play(deltaRealTime); } -bool CCinemaManager::HasTrack(const CStrW& name) const -{ - return m_Paths.find(name) != m_Paths.end(); +void CCinemaManager::Render() +{ + if (GetEnabled()) + { + DrawBars(); + return; + } + + if (!m_DrawPaths) + return; + + // draw all paths + for (auto it : m_CinematicSimulationData.m_Paths) + it.second.Draw(); } -void CCinemaManager::DrawSpline() const +void CCinemaManager::DrawBars() const { - if ( !(m_DrawCurrentSpline && m_ValidCurrent) ) + int height = (float)g_xres / 2.39f; + int shift = (g_yres - height) / 2; + if (shift <= 0) return; - static const int smoothness = 200; - m_CurrentPath->second.DrawSpline(CVector4D(0.f, 0.f, 1.f, 1.f), smoothness, m_DrawLines); +#if CONFIG2_GLES + #warning TODO : implement bars for GLES +#else + // Set up transform for GL bars + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + CMatrix3D transform; + transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); + glLoadMatrixf(&transform._11); + + glColor4f(0.0f, 0.0f, 0.0f, 1.0f); + + glEnable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + + glBegin(GL_QUADS); + glVertex2i(0, 0); + glVertex2i(g_xres, 0); + glVertex2i(g_xres, shift); + glVertex2i(0, shift); + glEnd(); + + glBegin(GL_QUADS); + glVertex2i(0, g_yres - shift); + glVertex2i(g_xres, g_yres - shift); + glVertex2i(g_xres, g_yres); + glVertex2i(0, g_yres); + glEnd(); + + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + + // Restore transform + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +#endif } -void CCinemaManager::MoveToPointAt(float time) +InReaction cinema_manager_handler(const SDL_Event_* ev) { - ENSURE(m_CurrentPath != m_Paths.end()); - StopPlaying(); + // put any events that must be processed even if inactive here + if (!g_Game || !g_Game->IsGameStarted()) + return IN_PASS; - m_CurrentPath->second.m_TimeElapsed = time; - if (!m_CurrentPath->second.Validate()) - return; + CCinemaManager* pCinemaManager = g_Game->GetView()->GetCinema(); - m_CurrentPath->second.MoveToPointAt(m_CurrentPath->second.m_TimeElapsed / - m_CurrentPath->second.GetDuration(), m_CurrentPath->second.GetNodeFraction(), - m_CurrentPath->second.m_PreviousRotation); + return pCinemaManager->HandleEvent(ev); } -bool CCinemaManager::Update(const float deltaRealTime) +InReaction CCinemaManager::HandleEvent(const SDL_Event_* ev) { - if (!m_PathQueue.front().Play(deltaRealTime)) + switch (ev->ev.type) { - m_PathQueue.pop_front(); - return false; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (GetEnabled() && !m_CinematicSimulationData.m_Paused) + return IN_HANDLED; + default: + return IN_PASS; } - return true; } + +bool CCinemaManager::GetEnabled() const +{ + return m_CinematicSimulationData.m_Enabled; +} + +bool CCinemaManager::IsPlaying() const +{ + return !m_CinematicSimulationData.m_Paused; +} + +const std::map& CCinemaManager::GetAllPaths() +{ + return m_CinematicSimulationData.m_Paths; +} + +CinematicSimulationData* CCinemaManager::GetCinematicSimulationData() +{ + return &m_CinematicSimulationData; +} + +bool CCinemaManager::GetPathsDrawing() const +{ + return m_DrawPaths; +} + +void CCinemaManager::SetPathsDrawing(const bool drawPath) +{ + m_DrawPaths = drawPath; +} \ No newline at end of file Property changes on: ps/trunk/source/graphics/CinemaManager.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/graphics/CinemaManager.h =================================================================== --- ps/trunk/source/graphics/CinemaManager.h (revision 17593) +++ ps/trunk/source/graphics/CinemaManager.h (revision 17594) @@ -1,69 +1,142 @@ /* Copyright (C) 2015 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 . */ #ifndef INCLUDED_CINEMAMANAGER #define INCLUDED_CINEMAMANAGER #include #include +#include "lib/input.h" // InReaction - can't forward-declare enum + #include "graphics/CinemaPath.h" #include "ps/CStr.h" -//Class for in game playing of cinematics. Should only be instantiated in CGameView. +/* + desc: contains various functions used for cinematic camera paths + See also: CinemaHandler.cpp, CinemaPath.cpp +*/ + +// Cinematic structure for data accessable from the simulation +struct CinematicSimulationData +{ + bool m_Enabled; + bool m_Paused; + std::map m_Paths; + std::list m_PathQueue; + + // States before playing + bool m_MapRevealed; + + fixed m_ElapsedTime, m_TotalTime, m_CurrentPathElapsedTime; + + CinematicSimulationData() + : m_Enabled(false), + m_Paused(true), + m_MapRevealed(false), + m_ElapsedTime(fixed::Zero()), + m_TotalTime(fixed::Zero()), + m_CurrentPathElapsedTime(fixed::Zero()) + {} +}; + +/** + * Class for in game playing of cinematics. Should only be instantiated in CGameView. + */ + class CCinemaManager { public: CCinemaManager(); ~CCinemaManager() {} - void AddPath(CCinemaPath path, const CStrW& name); + /** + * Adds the path to the path list + * @param name path name + * @param CCinemaPath path data + */ + void AddPath(const CStrW& name, const CCinemaPath& path); - //Adds track to list of being played. - void QueuePath(const CStrW& name, bool queue); - void OverridePath(const CStrW& name); //clears track queue and replaces with 'name' + /** + * Adds the path to the playlist + * @param name path name + */ + void AddPathToQueue(const CStrW& name); /** - * @param deltaRealTime Elapsed real time since the last frame. + * Clears the playlist */ - bool Update(const float deltaRealTime); - - //These stop track play, and accept time, not ratio of time - void MoveToPointAt(float time); + void ClearQueue(); - inline void StopPlaying() { m_PathQueue.clear(); } - void DrawSpline() const; + /** + * Checks the path name in the path list + * @param name path name + * @return true if path with that name exists, else false + */ + bool HasPath(const CStrW& name) const; - inline bool IsPlaying() const { return !m_PathQueue.empty(); } - bool HasTrack(const CStrW& name) const; - inline bool IsActive() const { return m_Active; } - inline void SetActive(bool active) { m_Active=active; } - - inline const std::map& GetAllPaths() { return m_Paths; } + const std::map& GetAllPaths(); void SetAllPaths( const std::map& tracks); - void SetCurrentPath(const CStrW& name, bool current, bool lines); -private: - - bool m_Active, m_DrawCurrentSpline, m_DrawLines, m_ValidCurrent; - std::map::iterator m_CurrentPath; - std::map m_Paths; - std::list m_PathQueue; + /** + * Starts play paths + */ + void Play(); + void Stop(); + bool IsPlaying() const; + + /** + * Renders black bars and paths (if enabled) + */ + void Render(); + void DrawBars() const; + + /** + * Get current enable state of the cinema manager + */ + bool GetEnabled() const; + + /** + * Sets enable state of the cinema manager (shows/hide gui, show/hide rings, etc) + * @enable new state + */ + void SetEnabled(bool enabled); + + /** + * Updates CCinemManager and current path + * @param deltaRealTime Elapsed real time since the last frame. + */ + void Update(const float deltaRealTime); + + InReaction HandleEvent(const SDL_Event_* ev); + + CinematicSimulationData* GetCinematicSimulationData(); + + bool GetPathsDrawing() const; + void SetPathsDrawing(const bool drawPath); + +private: + bool m_DrawPaths; + + // Cinematic data is accessed from the simulation + CinematicSimulationData m_CinematicSimulationData; }; +extern InReaction cinema_manager_handler(const SDL_Event_* ev); + #endif Property changes on: ps/trunk/source/graphics/CinemaManager.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/graphics/CinemaPath.cpp =================================================================== --- ps/trunk/source/graphics/CinemaPath.cpp (revision 17593) +++ ps/trunk/source/graphics/CinemaPath.cpp (revision 17594) @@ -1,264 +1,324 @@ /* Copyright (C) 2016 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 "CinemaPath.h" #include #include #include "Camera.h" +#include "CinemaManager.h" #include "GameView.h" +#include "gui/CGUI.h" +#include "gui/GUIManager.h" +#include "gui/IGUIObject.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" +#include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Game.h" +#include "renderer/Renderer.h" -CCinemaPath::CCinemaPath(const CCinemaData& data, const TNSpline& spline) - : CCinemaData(data), TNSpline(spline), m_TimeElapsed(0.f) -{ +CCinemaPath::CCinemaPath(const CCinemaData& data, const TNSpline& spline, const TNSpline& targetSpline) + : CCinemaData(data), TNSpline(spline), m_TargetSpline(targetSpline), m_TimeElapsed(0.f) +{ m_TimeElapsed = 0; BuildSpline(); - // Set distortion mode and style - switch(data.m_Mode) + if (m_Orientation == L"target") { - case CCinemaPath::EM_IN: + m_LookAtTarget = true; + ENSURE(!m_TargetSpline.GetAllNodes().empty()); + } + + // Set distortion mode and style + if (data.m_Mode == L"ease_in") DistModePtr = &CCinemaPath::EaseIn; - break; - case CCinemaPath::EM_OUT: + else if (data.m_Mode == L"ease_out") DistModePtr = &CCinemaPath::EaseOut; - break; - case CCinemaPath::EM_INOUT: + else if (data.m_Mode == L"ease_inout") DistModePtr = &CCinemaPath::EaseInOut; - break; - case CCinemaPath::EM_OUTIN: + else if (data.m_Mode == L"ease_outin") DistModePtr = &CCinemaPath::EaseOutIn; - break; - default: - debug_printf("Cinematic mode not found for %d\n", data.m_Mode); - break; + else + { + LOGWARNING("Cinematic mode not found for '%s'", data.m_Mode.ToUTF8().c_str()); + DistModePtr = &CCinemaPath::EaseInOut; } - switch (data.m_Style) - { - case CCinemaPath::ES_DEFAULT: + if (data.m_Style == L"default") DistStylePtr = &CCinemaPath::EaseDefault; - break; - case CCinemaPath::ES_GROWTH: + else if (data.m_Style == L"growth") DistStylePtr = &CCinemaPath::EaseGrowth; - break; - case CCinemaPath::ES_EXPO: + else if (data.m_Style == L"expo") DistStylePtr = &CCinemaPath::EaseExpo; - break; - case CCinemaPath::ES_CIRCLE: + else if (data.m_Style == L"circle") DistStylePtr = &CCinemaPath::EaseCircle; - break; - case CCinemaPath::ES_SINE: + else if (data.m_Style == L"sine") DistStylePtr = &CCinemaPath::EaseSine; - break; - default: - debug_printf("Cinematic mode not found for %d\n", data.m_Style); - break; + else + { + LOGWARNING("Cinematic style not found for '%s'", data.m_Style.ToUTF8().c_str()); + DistStylePtr = &CCinemaPath::EaseDefault; } } -void CCinemaPath::DrawSpline(const CVector4D& RGBA, int smoothness, bool lines) const +void CCinemaPath::Draw() const +{ + DrawSpline(*this, CVector4D(0.2f, 0.2f, 1.f, 0.5f), 100, true); + DrawSpline(m_TargetSpline, CVector4D(1.0f, 0.2f, 0.2f, 0.5f), 100, true); + DrawNodes(CVector4D(0.5f, 1.0f, 0.f, 0.5f)); +} + +void CCinemaPath::DrawSpline(const RNSpline& spline, const CVector4D& RGBA, int smoothness, bool lines) const { - if (NodeCount < 2 || DistModePtr == NULL) + if (spline.NodeCount < 2 || DistModePtr == NULL) return; - if (NodeCount == 2 && lines) + if (spline.NodeCount == 2 && lines) smoothness = 2; - float start = MaxDistance / smoothness; + float start = spline.MaxDistance.ToFloat() / smoothness; float time = 0; #if CONFIG2_GLES #warning TODO: do something about CCinemaPath on GLES #else glColor4f(RGBA.X, RGBA.Y, RGBA.Z, RGBA.W); if (lines) { glLineWidth(1.8f); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINE_STRIP); for (int i = 0; i <= smoothness; ++i) { // Find distorted time - time = start*i / MaxDistance; - CVector3D tmp = GetPosition(time); + time = start*i / spline.MaxDistance.ToFloat(); + CVector3D tmp = spline.GetPosition(time); glVertex3f(tmp.X, tmp.Y, tmp.Z); } glEnd(); glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); } else { smoothness /= 2; - start = MaxDistance / smoothness; + start = spline.MaxDistance.ToFloat() / smoothness; glEnable(GL_POINT_SMOOTH); glPointSize(3.0f); glBegin(GL_POINTS); for (int i = 0; i <= smoothness; ++i) { // Find distorted time - time = (this->*DistModePtr)(start*i / MaxDistance); - CVector3D tmp = GetPosition(time); + time = (this->*DistModePtr)(start*i / spline.MaxDistance.ToFloat()); + CVector3D tmp = spline.GetPosition(time); glVertex3f(tmp.X, tmp.Y, tmp.Z); } glColor3f(1.0f, 1.0f, 0.0f); // yellow - for (size_t i = 0; i < Node.size(); ++i) - glVertex3f(Node[i].Position.X, Node[i].Position.Y, Node[i].Position.Z); + for (size_t i = 0; i < spline.GetAllNodes().size(); ++i) + glVertex3f( + spline.GetAllNodes()[i].Position.X.ToFloat(), + spline.GetAllNodes()[i].Position.Y.ToFloat(), + spline.GetAllNodes()[i].Position.Z.ToFloat() + ); glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); } #endif } +void CCinemaPath::DrawNodes(const CVector4D& RGBA) const +{ +#if CONFIG2_GLES + #warning TODO : do something about CCinemaPath on GLES +#else + glEnable(GL_POINT_SMOOTH); + glPointSize(5.0f); + + glColor4f(RGBA.X, RGBA.Y, RGBA.Z, RGBA.W); + glBegin(GL_POINTS); + for (size_t i = 0; i < Node.size(); ++i) + glVertex3f(Node[i].Position.X.ToFloat(), Node[i].Position.Y.ToFloat(), Node[i].Position.Z.ToFloat()); + glEnd(); + + if (!m_LookAtTarget) + { + glPointSize(1.0f); + glDisable(GL_POINT_SMOOTH); + return; + } + + // draw target nodes + glColor4f(RGBA.Y, RGBA.X, RGBA.Z, RGBA.W); + glBegin(GL_POINTS); + for (size_t i = 0; i < m_TargetSpline.GetAllNodes().size(); ++i) + glVertex3f( + m_TargetSpline.GetAllNodes()[i].Position.X.ToFloat(), + m_TargetSpline.GetAllNodes()[i].Position.Y.ToFloat(), + m_TargetSpline.GetAllNodes()[i].Position.Z.ToFloat() + ); + glEnd(); + + glPointSize(1.0f); + glDisable(GL_POINT_SMOOTH); +#endif +} + void CCinemaPath::MoveToPointAt(float t, float nodet, const CVector3D& startRotation) { - CCamera *Cam = g_Game->GetView()->GetCamera(); + CCamera *camera = g_Game->GetView()->GetCamera(); t = (this->*DistModePtr)(t); - - CVector3D nodeRotation = Node[m_CurrentNode + 1].Rotation; - CQuaternion start, end; - start.FromEulerAngles(DEGTORAD(startRotation.X), DEGTORAD(startRotation.Y), DEGTORAD(startRotation.Z)); - end.FromEulerAngles(DEGTORAD(nodeRotation.X), DEGTORAD(nodeRotation.Y), DEGTORAD(nodeRotation.Z)); - start.Slerp(start, end, nodet); + CVector3D pos = GetPosition(t); - CQuaternion quat; - - Cam->m_Orientation.SetIdentity(); - Cam->m_Orientation.Rotate(start); - Cam->m_Orientation.Translate(pos); - Cam->UpdateFrustum(); + + if (m_LookAtTarget) + { + if (m_TimeElapsed <= m_TargetSpline.MaxDistance.ToFloat()) + camera->LookAt(pos, m_TargetSpline.GetPosition(m_TimeElapsed / m_TargetSpline.MaxDistance.ToFloat()), CVector3D(0, 1, 0)); + else + camera->LookAt(pos, m_TargetSpline.GetAllNodes().back().Position, CVector3D(0, 1, 0)); + } + else + { + CVector3D nodeRotation = Node[m_CurrentNode + 1].Rotation; + CQuaternion start, end; + start.FromEulerAngles(DEGTORAD(startRotation.X), DEGTORAD(startRotation.Y), DEGTORAD(startRotation.Z)); + end.FromEulerAngles(DEGTORAD(nodeRotation.X), DEGTORAD(nodeRotation.Y), DEGTORAD(nodeRotation.Z)); + start.Slerp(start, end, nodet); + + camera->m_Orientation.SetIdentity(); + camera->m_Orientation.Rotate(start); + camera->m_Orientation.Translate(pos); + } + camera->UpdateFrustum(); } // Distortion mode functions float CCinemaPath::EaseIn(float t) const { return (this->*DistStylePtr)(t); } float CCinemaPath::EaseOut(float t) const { return 1.0f - EaseIn(1.0f-t); } float CCinemaPath::EaseInOut(float t) const { if (t < m_Switch) return EaseIn(1.0f/m_Switch * t) * m_Switch; return EaseOut(1.0f/m_Switch * (t-m_Switch)) * m_Switch + m_Switch; } float CCinemaPath::EaseOutIn(float t) const { if (t < m_Switch) return EaseOut(1.0f/m_Switch * t) * m_Switch; return EaseIn(1.0f/m_Switch * (t-m_Switch)) * m_Switch + m_Switch; } // Distortion style functions float CCinemaPath::EaseDefault(float t) const { return t; } float CCinemaPath::EaseGrowth(float t) const { return pow(t, m_Growth); } float CCinemaPath::EaseExpo(float t) const { if (t == 0) return t; return powf(m_Growth, 10*(t-1.0f)); } float CCinemaPath::EaseCircle(float t) const { t = -(sqrt(1.0f - t*t) - 1.0f); if (m_GrowthCount > 1.0f) { --m_GrowthCount; return (this->*DistStylePtr)(t); } return t; } float CCinemaPath::EaseSine(float t) const { t = 1.0f - cos(t * (float)M_PI/2); if (m_GrowthCount > 1.0f) { --m_GrowthCount; return (this->*DistStylePtr)(t); } return t; } bool CCinemaPath::Validate() { - if (m_TimeElapsed > GetDuration() || m_TimeElapsed < 0.0f) + if (m_TimeElapsed > GetDuration().ToFloat() || m_TimeElapsed < 0.0f) return false; // Find current node and past "node time" float previousTime = 0.0f, cumulation = 0.0f; // Ignore the last node, since it is a blank (node time values are shifted down one from interface) for (size_t i = 0; i < Node.size() - 1; ++i) { - cumulation += Node[i].Distance; + cumulation += Node[i].Distance.ToFloat(); if (m_TimeElapsed <= cumulation) { m_PreviousNodeTime = previousTime; m_PreviousRotation = Node[i].Rotation; m_CurrentNode = i; // We're moving toward this next node, so use its rotation return true; } - previousTime += Node[i].Distance; + previousTime += Node[i].Distance.ToFloat(); } + debug_warn("validation of cinema path is wrong\n"); return false; } bool CCinemaPath::Play(const float deltaRealTime) { - m_TimeElapsed += m_Timescale * deltaRealTime; + m_TimeElapsed += m_Timescale.ToFloat() * deltaRealTime; if (!Validate()) { m_TimeElapsed = 0.0f; return false; } - MoveToPointAt(m_TimeElapsed / GetDuration(), GetNodeFraction(), m_PreviousRotation); + MoveToPointAt(m_TimeElapsed / GetDuration().ToFloat(), GetNodeFraction(), m_PreviousRotation); return true; } \ No newline at end of file Index: ps/trunk/source/graphics/CinemaPath.h =================================================================== --- ps/trunk/source/graphics/CinemaPath.h (revision 17593) +++ ps/trunk/source/graphics/CinemaPath.h (revision 17594) @@ -1,124 +1,136 @@ /* Copyright (C) 2016 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 . */ - #ifndef INCLUDED_CINEMAPATH #define INCLUDED_CINEMAPATH -#include -#include -#include "ps/CStr.h" #include "maths/NUSpline.h" - -/* - desc: contains various functions used for cinematic camera paths - See also: CinemaHandler.cpp, Cinematic.h/.cpp -*/ +#include "ps/CStr.h" class CVector3D; class CVector4D; class CCamera; // For loading data class CCinemaData { public: - CCinemaData() : m_GrowthCount(0), m_Growth(0), m_Switch(0), - m_Mode(0), m_Style(0), m_Timescale(1) {} + CCinemaData() : m_LookAtTarget(false), m_GrowthCount(0), m_Growth(1), m_Switch(1), m_Timescale(fixed::FromInt(1)) {} virtual ~CCinemaData() {} const CCinemaData* GetData() const { return this; } - + + CStrW m_Name; + CStrW m_Orientation; + CStrW m_Mode; + CStrW m_Style; + + bool m_LookAtTarget; + + fixed m_Timescale; // a negative timescale results in backwards play + // Distortion variables mutable float m_GrowthCount; float m_Growth; float m_Switch; - int m_Mode; - int m_Style; - float m_Timescale; // a negative timescale results in backwards play - }; // Once the data is part of the path, it shouldn't be changeable, so use private inheritance. // This class encompasses the spline and the information which determines how the path will operate // and also provides the functionality for doing so class CCinemaPath : private CCinemaData, public TNSpline { public: - CCinemaPath() { m_TimeElapsed = 0.0f; m_PreviousNodeTime = 0.0f; } - CCinemaPath(const CCinemaData& data, const TNSpline& spline); + CCinemaPath() : m_TimeElapsed(0), m_PreviousNodeTime(0) {} + CCinemaPath(const CCinemaData& data, const TNSpline& spline, const TNSpline& targetSpline); ~CCinemaPath() { DistStylePtr = NULL; DistModePtr = NULL; } - - enum { EM_IN, EM_OUT, EM_INOUT, EM_OUTIN }; - enum { ES_DEFAULT, ES_GROWTH, ES_EXPO, ES_CIRCLE, ES_SINE }; - + // Sets camera position to calculated point on spline void MoveToPointAt(float t, float nodet, const CVector3D&); - + // Distortion mode functions-change how ratio is passed to distortion style functions float EaseIn(float t) const; float EaseOut(float t) const; float EaseInOut(float t) const; float EaseOutIn(float t) const; // Distortion style functions float EaseDefault(float t) const; float EaseGrowth(float t) const; float EaseExpo(float t) const; float EaseCircle(float t) const; float EaseSine(float t) const; float (CCinemaPath::*DistStylePtr)(float ratio) const; float (CCinemaPath::*DistModePtr)(float ratio) const; const CCinemaData* GetData() const { return CCinemaData::GetData(); } public: - void DrawSpline(const CVector4D& RGBA, int smoothness, bool lines) const; + void Draw() const; + void DrawSpline(const RNSpline& spline, const CVector4D& RGBA, int smoothness, bool lines) const; + void DrawNodes(const CVector4D& RGBA) const; inline CVector3D GetNodePosition(const int index) const { return Node[index].Position; } - inline float GetNodeDuration(const int index) const { return Node[index].Distance; } - inline float GetDuration() const { return MaxDistance; } + inline fixed GetNodeDuration(const int index) const { return Node[index].Distance; } + inline fixed GetDuration() const { return MaxDistance; } - inline float GetNodeFraction() const { return (m_TimeElapsed - m_PreviousNodeTime) / Node[m_CurrentNode].Distance; } + inline float GetNodeFraction() const { return (m_TimeElapsed - m_PreviousNodeTime) / Node[m_CurrentNode].Distance.ToFloat(); } inline float GetElapsedTime() const { return m_TimeElapsed; } - inline void SetTimescale(float scale) { m_Timescale = scale; } + CStrW GetName() const { return m_Name; } + + inline void SetTimescale(fixed scale) { m_Timescale = scale; } float m_TimeElapsed; float m_PreviousNodeTime; // How much time has passed before the current node size_t m_CurrentNode; CVector3D m_PreviousRotation; public: /** * Returns false if finished. - * * @param deltaRealTime Elapsed real time since the last frame. */ bool Play(const float deltaRealTime); + + /** + * Validate the path + * @return true if the path is valid + */ bool Validate(); - inline float GetTimescale() const { return m_Timescale; } + /** + * Returns true if path doesn't contain nodes + */ + bool Empty() { return Node.empty(); } + + fixed GetTimescale() const { return m_Timescale; } + + TNSpline* getTargetSpline() { return &m_TargetSpline; } + +private: + + TNSpline m_TargetSpline; }; -#endif +#endif // INCLUDED_CINEMAPATH Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 17593) +++ ps/trunk/source/graphics/GameView.cpp (revision 17594) @@ -1,1159 +1,1156 @@ /* Copyright (C) 2015 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 "GameView.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LOSTexture.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/scripting/JSInterface_GameView.h" #include "lib/input.h" #include "lib/timer.h" #include "lobby/IXmppClient.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/TouchInput.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" extern int g_xres, g_yres; // Maximum distance outside the edge of the map that the camera's // focus point can be moved static const float CAMERA_EDGE_MARGIN = 2.0f*TERRAIN_TILE_SIZE; /** * A value with exponential decay towards the target value. */ class CSmoothedValue { public: CSmoothedValue(float value, float smoothness, float minDelta) : m_Target(value), m_Current(value), m_Smoothness(smoothness), m_MinDelta(minDelta) { } float GetSmoothedValue() { return m_Current; } void SetValueSmoothly(float value) { m_Target = value; } void AddSmoothly(float value) { m_Target += value; } void Add(float value) { m_Target += value; m_Current += value; } float GetValue() { return m_Target; } void SetValue(float value) { m_Target = value; m_Current = value; } float Update(float time) { if (fabs(m_Target - m_Current) < m_MinDelta) return 0.0f; double p = pow((double)m_Smoothness, 10.0 * (double)time); // (add the factor of 10 so that smoothnesses don't have to be tiny numbers) double delta = (m_Target - m_Current) * (1.0 - p); m_Current += delta; return (float)delta; } void ClampSmoothly(float min, float max) { m_Target = Clamp(m_Target, (double)min, (double)max); } // Wrap so 'target' is in the range [min, max] void Wrap(float min, float max) { double t = fmod(m_Target - min, (double)(max - min)); if (t < 0) t += max - min; t += min; m_Current += t - m_Target; m_Target = t; } private: double m_Target; // the value which m_Current is tending towards double m_Current; // (We use double because the extra precision is worthwhile here) float m_MinDelta; // cutoff where we stop moving (to avoid ugly shimmering effects) public: float m_Smoothness; }; class CGameViewImpl { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), LOSTexture(*game->GetSimulation2()), TerritoryTexture(*game->GetSimulation2()), ViewCamera(), CullCamera(), LockCullCamera(false), ConstrainCamera(true), Culling(true), FollowEntity(INVALID_ENTITY), FollowFirstPerson(false), // Dummy values (these will be filled in by the config file) ViewScrollSpeed(0), ViewScrollSpeedModifier(1), ViewRotateXSpeed(0), ViewRotateXMin(0), ViewRotateXMax(0), ViewRotateXDefault(0), ViewRotateYSpeed(0), ViewRotateYSpeedWheel(0), ViewRotateYDefault(0), ViewRotateSpeedModifier(1), ViewDragSpeed(0), ViewZoomSpeed(0), ViewZoomSpeedWheel(0), ViewZoomMin(0), ViewZoomMax(0), ViewZoomDefault(0), ViewZoomSpeedModifier(1), ViewFOV(DEGTORAD(45.f)), ViewNear(2.f), ViewFar(4096.f), JoystickPanX(-1), JoystickPanY(-1), JoystickRotateX(-1), JoystickRotateY(-1), JoystickZoomIn(-1), JoystickZoomOut(-1), HeightSmoothness(0.5f), HeightMin(16.f), PosX(0, 0, 0.01f), PosY(0, 0, 0.01f), PosZ(0, 0, 0.01f), Zoom(0, 0, 0.1f), RotateX(0, 0, 0.001f), RotateY(0, 0, 0.001f) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; /** * this camera controls the eye position when rendering */ CCamera ViewCamera; /** * this camera controls the frustum that is used for culling * and shadow calculations * * Note that all code that works with camera movements should only change * m_ViewCamera. The render functions automatically sync the cull camera to * the view camera depending on the value of m_LockCullCamera. */ CCamera CullCamera; /** * When @c true, the cull camera is locked in place. * When @c false, the cull camera follows the view camera. * * Exposed to JS as gameView.lockCullCamera */ bool LockCullCamera; /** * When @c true, culling is enabled so that only models that have a chance of * being visible are sent to the renderer. * Otherwise, the entire world is sent to the renderer. * * Exposed to JS as gameView.culling */ bool Culling; /** * Whether the camera movement should be constrained by min/max limits * and terrain avoidance. */ bool ConstrainCamera; /** * Cache global lighting environment. This is used to check whether the * environment has changed during the last frame, so that vertex data can be updated etc. */ CLightEnv CachedLightEnv; CCinemaManager CinemaManager; /** * Entity for the camera to follow, or INVALID_ENTITY if none. */ entity_id_t FollowEntity; /** * Whether to follow FollowEntity in first-person mode. */ bool FollowFirstPerson; //////////////////////////////////////// // Settings float ViewScrollSpeed; float ViewScrollSpeedModifier; float ViewRotateXSpeed; float ViewRotateXMin; float ViewRotateXMax; float ViewRotateXDefault; float ViewRotateYSpeed; float ViewRotateYSpeedWheel; float ViewRotateYDefault; float ViewRotateSpeedModifier; float ViewDragSpeed; float ViewZoomSpeed; float ViewZoomSpeedWheel; float ViewZoomMin; float ViewZoomMax; float ViewZoomDefault; float ViewZoomSpeedModifier; float ViewFOV; float ViewNear; float ViewFar; int JoystickPanX; int JoystickPanY; int JoystickRotateX; int JoystickRotateY; int JoystickZoomIn; int JoystickZoomOut; float HeightSmoothness; float HeightMin; //////////////////////////////////////// // Camera Controls State CSmoothedValue PosX; CSmoothedValue PosY; CSmoothedValue PosZ; CSmoothedValue Zoom; CSmoothedValue RotateX; // inclination around x axis (relative to camera) CSmoothedValue RotateY; // rotation around y (vertical) axis }; #define IMPLEMENT_BOOLEAN_SETTING(NAME) \ bool CGameView::Get##NAME##Enabled() \ { \ return m->NAME; \ } \ \ void CGameView::Set##NAME##Enabled(bool Enabled) \ { \ m->NAME = Enabled; \ } IMPLEMENT_BOOLEAN_SETTING(Culling); IMPLEMENT_BOOLEAN_SETTING(LockCullCamera); IMPLEMENT_BOOLEAN_SETTING(ConstrainCamera); #undef IMPLEMENT_BOOLEAN_SETTING static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue()); } static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetValue()); orientation->RotateY(m->RotateY.GetValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } CGameView::CGameView(CGame *pGame): m(new CGameViewImpl(pGame)) { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); } CGameView::~CGameView() { UnloadResources(); delete m; } void CGameView::SetViewport(const SViewPort& vp) { m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } CObjectManager& CGameView::GetObjectManager() const { return m->ObjectManager; } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->CinemaManager; }; CLOSTexture& CGameView::GetLOSTexture() { return m->LOSTexture; } CTerritoryTexture& CGameView::GetTerritoryTexture() { return m->TerritoryTexture; } int CGameView::Initialize() { CFG_GET_VAL("view.scroll.speed", m->ViewScrollSpeed); CFG_GET_VAL("view.scroll.speed.modifier", m->ViewScrollSpeedModifier); CFG_GET_VAL("view.rotate.x.speed", m->ViewRotateXSpeed); CFG_GET_VAL("view.rotate.x.min", m->ViewRotateXMin); CFG_GET_VAL("view.rotate.x.max", m->ViewRotateXMax); CFG_GET_VAL("view.rotate.x.default", m->ViewRotateXDefault); CFG_GET_VAL("view.rotate.y.speed", m->ViewRotateYSpeed); CFG_GET_VAL("view.rotate.y.speed.wheel", m->ViewRotateYSpeedWheel); CFG_GET_VAL("view.rotate.y.default", m->ViewRotateYDefault); CFG_GET_VAL("view.rotate.speed.modifier", m->ViewRotateSpeedModifier); CFG_GET_VAL("view.drag.speed", m->ViewDragSpeed); CFG_GET_VAL("view.zoom.speed", m->ViewZoomSpeed); CFG_GET_VAL("view.zoom.speed.wheel", m->ViewZoomSpeedWheel); CFG_GET_VAL("view.zoom.min", m->ViewZoomMin); CFG_GET_VAL("view.zoom.max", m->ViewZoomMax); CFG_GET_VAL("view.zoom.default", m->ViewZoomDefault); CFG_GET_VAL("view.zoom.speed.modifier", m->ViewZoomSpeedModifier); CFG_GET_VAL("joystick.camera.pan.x", m->JoystickPanX); CFG_GET_VAL("joystick.camera.pan.y", m->JoystickPanY); CFG_GET_VAL("joystick.camera.rotate.x", m->JoystickRotateX); CFG_GET_VAL("joystick.camera.rotate.y", m->JoystickRotateY); CFG_GET_VAL("joystick.camera.zoom.in", m->JoystickZoomIn); CFG_GET_VAL("joystick.camera.zoom.out", m->JoystickZoomOut); CFG_GET_VAL("view.height.smoothness", m->HeightSmoothness); CFG_GET_VAL("view.height.min", m->HeightMin); CFG_GET_VAL("view.pos.smoothness", m->PosX.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosY.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosZ.m_Smoothness); CFG_GET_VAL("view.zoom.smoothness", m->Zoom.m_Smoothness); CFG_GET_VAL("view.rotate.x.smoothness", m->RotateX.m_Smoothness); CFG_GET_VAL("view.rotate.y.smoothness", m->RotateY.m_Smoothness); CFG_GET_VAL("view.near", m->ViewNear); CFG_GET_VAL("view.far", m->ViewFar); CFG_GET_VAL("view.fov", m->ViewFOV); // Convert to radians m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->ViewFOV = DEGTORAD(m->ViewFOV); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); } void CGameView::BeginFrame() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); m->Game->CachePlayerColors(); } void CGameView::Render() { g_Renderer.RenderScene(*this); } /////////////////////////////////////////////////////////// // This callback is part of the Scene interface // Submit all objects visible in the given frustum void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { { PROFILE3("submit terrain"); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide(); // find out which patches will be drawn for (ssize_t j=0; jGetPatch(i,j); // can't fail // If the patch is underwater, calculate a bounding box that also contains the water plane CBoundingBoxAligned bounds = patch->GetWorldBounds(); if(bounds[1].Y < waterHeight) bounds[1].Y = waterHeight; if (!m->Culling || frustum.IsBoxVisible(bounds)) c->Submit(patch); } } } m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel()) g_Renderer.MakeShadersDirty(); m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); if (!pTerrain) return; PROFILE("update light env"); pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR); const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits(); for (size_t i = 0; i < units.size(); ++i) units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR); } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } static void FocusHeight(CGameViewImpl* m, bool smooth) { /* The camera pivot height is moved towards ground level. To prevent excessive zoom when looking over a cliff, the target ground level is the maximum of the ground level at the camera's near and pivot points. The ground levels are filtered to achieve smooth camera movement. The filter radius is proportional to the zoom level. The camera height is clamped to prevent map penetration. */ if (!m->ConstrainCamera) return; CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); const CVector3D position = targetCam.m_Orientation.GetTranslation(); const CVector3D forwards = targetCam.m_Orientation.GetIn(); // horizontal view radius const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue(); const float near_radius = radius * m->HeightSmoothness; const float pivot_radius = radius * m->HeightSmoothness; const CVector3D nearPoint = position + forwards * m->ViewNear; const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue(); const float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); // filter ground levels for smooth camera movement const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius); const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius); // filtered maximum visible ground level in view const float filtered_ground = std::max(filtered_near_ground, filtered_pivot_ground); // target camera height above pivot point const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear); // minimum camera height above filtered ground level const float min_height = (m->HeightMin + ground - filtered_ground); const float target_height = std::max(pivot_height, min_height); const float height = (nearPoint.Y - filtered_ground); const float diff = target_height - height; if (fabsf(diff) < 0.0001f) return; if (smooth) { m->PosY.AddSmoothly(diff); } else { m->PosY.Add(diff); } } CVector3D CGameView::GetSmoothPivot(CCamera& camera) const { return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue(); } void CGameView::Update(const float deltaRealTime) { // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) return; if (!g_app_has_focus) return; - if (m->CinemaManager.IsActive() && m->CinemaManager.IsPlaying()) + if (m->CinemaManager.GetEnabled()) { - if (! m->CinemaManager.Update(deltaRealTime)) - { -// ResetCamera(); - } + m->CinemaManager.Update(deltaRealTime); return; } // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; if (HotkeyIsPressed("camera.rotate.cw")) m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.ccw")) m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.up")) m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.down")) m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m->ViewDragSpeed * mouse_dx; moveForward += m->ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m->ViewScrollSpeed * deltaRealTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.up")) moveForward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.down")) moveForward -= m->ViewScrollSpeed * deltaRealTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime; moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime; m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime); m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m->FollowEntity = INVALID_ENTITY; float s = sin(m->RotateY.GetSmoothedValue()); float c = cos(m->RotateY.GetSmoothedValue()); m->PosX.AddSmoothly(c * moveRightward); m->PosZ.AddSmoothly(-s * moveRightward); m->PosX.AddSmoothly(s * moveForward); m->PosZ.AddSmoothly(c * moveForward); } if (m->FollowEntity) { CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity); CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpPosition && cmpPosition->IsInWorld() && cmpRangeManager && cmpRangeManager->GetLosVisibility(m->FollowEntity, m->Game->GetPlayerID()) == ICmpRangeManager::VIS_VISIBLE) { // Get the most recent interpolated position float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset); CVector3D pos = transform.GetTranslation(); if (m->FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m->ViewCamera.m_Orientation.SetIdentity(); m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f); m->ViewCamera.m_Orientation.RotateY(angle); m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); m->ViewCamera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pos - pivot; m->PosX.AddSmoothly(delta.X); m->PosY.AddSmoothly(delta.Y); m->PosZ.AddSmoothly(delta.Z); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m->FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime); if (HotkeyIsPressed("camera.zoom.out")) m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime); if (m->ConstrainCamera) m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax); float zoomDelta = -m->Zoom.Update(deltaRealTime); if (zoomDelta) { CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); m->PosX.AddSmoothly(forwards.X * zoomDelta); m->PosY.AddSmoothly(forwards.Y * zoomDelta); m->PosZ.AddSmoothly(forwards.Z * zoomDelta); } if (m->ConstrainCamera) m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax)); FocusHeight(m, true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m->ConstrainCamera) { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpRangeManager && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; CVector3D center(r, desiredPivot.Y, r); float dist = (desiredPivot - center).Length(); if (dist > r - CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin m->PosX.SetValueSmoothly(desiredPivot.X + delta.X); m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); } m->PosX.Update(deltaRealTime); m->PosY.Update(deltaRealTime); m->PosZ.Update(deltaRealTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateYDelta = m->RotateY.Update(deltaRealTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateXDelta = m->RotateX.Update(deltaRealTime); if (rotateXDelta) { CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f; CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m->ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m->RotateX.GetValue()); target.RotateY(m->RotateY.GetValue()); target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m->PosY.AddSmoothly(limit - nearPoint.Y); } */ m->RotateY.Wrap(-(float)M_PI, (float)M_PI); // Update the camera matrix m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); } float CGameView::GetCameraX() { CCamera targetCam = m->ViewCamera; CVector3D pivot = GetSmoothPivot(targetCam); return pivot.X; } float CGameView::GetCameraZ() { CCamera targetCam = m->ViewCamera; CVector3D pivot = GetSmoothPivot(targetCam); return pivot.Z; } float CGameView::GetCameraPosX() { return m->PosX.GetValue(); } float CGameView::GetCameraPosY() { return m->PosY.GetValue(); } float CGameView::GetCameraPosZ() { return m->PosZ.GetValue(); } float CGameView::GetCameraRotX() { return m->RotateX.GetValue(); } float CGameView::GetCameraRotY() { return m->RotateY.GetValue(); } float CGameView::GetCameraZoom() { return m->Zoom.GetValue(); } void CGameView::SetCamera(CVector3D Pos, float RotX, float RotY, float zoom) { m->PosX.SetValue(Pos.X); m->PosY.SetValue(Pos.Y); m->PosZ.SetValue(Pos.Z); m->RotateX.SetValue(RotX); m->RotateY.SetValue(RotY); m->Zoom.SetValue(zoom); FocusHeight(m, false); SetupCameraMatrixNonSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::MoveCameraTarget(const CVector3D& target) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference between that position and the camera point, and restoring // that difference to our new target) CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = target - pivot; m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue()); m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue()); FocusHeight(m, false); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m->ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m->ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m->ViewZoomDefault; m->PosX.SetValue(target.X - delta.X); m->PosY.SetValue(target.Y - delta.Y); m->PosZ.SetValue(target.Z - delta.Z); m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->Zoom.SetValue(m->ViewZoomDefault); FocusHeight(m, false); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraAngleZoom() { CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); // Compute the zoom adjustment to get us back to the default CVector3D forwards = targetCam.m_Orientation.GetIn(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation(); float dist = delta.Dot(forwards); m->Zoom.AddSmoothly(m->ViewZoomDefault - dist); // Reset orientations to default m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault)); } void CGameView::CameraFollow(entity_id_t entity, bool firstPerson) { m->FollowEntity = entity; m->FollowFirstPerson = firstPerson; } entity_id_t CGameView::GetFollowedEntity() { return m->FollowEntity; } float CGameView::GetNear() const { return m->ViewNear; } float CGameView::GetFar() const { return m->ViewFar; } float CGameView::GetFOV() const { return m->ViewFOV; } void CGameView::SetCameraProjection() { m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if(!g_app_has_focus || !g_Game || !g_Game->IsGameStarted()) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "wireframe") { if (g_XmppClient && g_rankedGame == true) break; else if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); } return IN_HANDLED; } // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). else if (hotkey == "camera.zoom.wheel.in") { m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.zoom.wheel.out") { m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.cw") { m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.ccw") { m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.increase") { m->ViewScrollSpeed *= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.decrease") { m->ViewScrollSpeed /= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.increase") { m->ViewRotateXSpeed *= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed *= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.decrease") { m->ViewRotateXSpeed /= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed /= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.increase") { m->ViewZoomSpeed *= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.decrease") { m->ViewZoomSpeed /= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.reset") { ResetCameraAngleZoom(); return IN_HANDLED; } } return IN_PASS; } Index: ps/trunk/source/graphics/MapReader.cpp =================================================================== --- ps/trunk/source/graphics/MapReader.cpp (revision 17593) +++ ps/trunk/source/graphics/MapReader.cpp (revision 17594) @@ -1,1605 +1,1600 @@ /* Copyright (C) 2015 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 "MapReader.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/Entity.h" #include "graphics/GameView.h" #include "graphics/MapGenerator.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/PostprocManager.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" #include CMapReader::CMapReader() : xml_reader(0), m_PatchesPerSide(0), m_MapGen(0) { cur_terrain_tex = 0; // important - resets generator state // Maps that don't override the default probably want the old lighting model //m_LightEnv.SetLightingModel("old"); //pPostproc->SetPostEffect(L"default"); } // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful void CMapReader::LoadMap(const VfsPath& pathname, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities) { // latch parameters (held until DelayedLoadFinished) pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; pSimulation2 = pSimulation2_; pSimContext = pSimContext_; m_PlayerID = playerID_; m_SkipEntities = skipEntities; m_StartingCameraTarget = INVALID_ENTITY; m_ScriptSettings.set(rt, settings); filename_xml = pathname.ChangeExtension(L".xml"); // In some cases (particularly tests) we don't want to bother storing a large // mostly-empty .pmp file, so we let the XML file specify basic terrain instead. // If there's an .xml file and no .pmp, then we're probably in this XML-only mode only_xml = false; if (!VfsFileExists(pathname) && VfsFileExists(filename_xml)) { only_xml = true; } file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp if (!only_xml) { // [25ms] unpacker.Read(pathname, "PSMP"); file_format_version = unpacker.GetVersion(); } // check oldest supported version if (file_format_version < FILE_READ_VERSION) throw PSERROR_File_InvalidVersion(); // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); // reset post effects if (pPostproc) pPostproc->SetPostEffect(L"default"); // load map or script settings script if (settings.isUndefined()) RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50); else RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // unpack the data if (!only_xml) RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200); // read the corresponding XML file RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // read entities RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful void CMapReader::LoadRandomMap(const CStrW& scriptFile, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, int playerID_) { // latch parameters (held until DelayedLoadFinished) m_ScriptFile = scriptFile; pSimulation2 = pSimulation2_; pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL; m_ScriptSettings.set(rt, settings); pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; m_PlayerID = playerID_; m_SkipEntities = false; m_StartingCameraTarget = INVALID_ENTITY; // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); only_xml = false; // copy random map settings (before entity creation) RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // load map generator with random map script RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 5000); // parse RMS results into terrain structure RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500); // parse RMS results into environment settings RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5); // parse RMS results into camera settings RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // parse RMS results into entities RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // UnpackMap: unpack the given data from the raw data stream into local variables int CMapReader::UnpackMap() { // now unpack everything into local data int ret = UnpackTerrain(); if (ret != 0) // failed or timed out { return ret; } return 0; } // UnpackTerrain: unpack the terrain from the end of the input data stream // - data: map size, heightmap, list of textures used by map, texture tile assignments int CMapReader::UnpackTerrain() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; // first call to generator (this is skipped after first call, // i.e. when the loop below was interrupted) if (cur_terrain_tex == 0) { m_PatchesPerSide = (ssize_t)unpacker.UnpackSize(); // unpack heightmap [600us] size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1; m_Heightmap.resize(SQR(verticesPerSide)); unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16)); // unpack # textures num_terrain_tex = unpacker.UnpackSize(); m_TerrainTextures.reserve(num_terrain_tex); } // unpack texture names; find handle for each texture. // interruptible. while (cur_terrain_tex < num_terrain_tex) { CStr texturename; unpacker.UnpackString(texturename); ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename); m_TerrainTextures.push_back(texentry); cur_terrain_tex++; LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex); } // unpack tile data [3ms] ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE; m_Tiles.resize(size_t(SQR(tilesPerSide))); unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size()); // reset generator state. cur_terrain_tex = 0; return 0; } int CMapReader::ApplyTerrainData() { if (m_PatchesPerSide == 0) { // we'll probably crash when trying to use this map later throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details."); } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); return 0; } // ApplyData: take all the input data, and rebuild the scene from it int CMapReader::ApplyData() { // copy over the lighting parameters if (pLightEnv) *pLightEnv = m_LightEnv; CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY); if (pGameView && cmpPlayerManager) { // Default to global camera (with constraints) pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus()); // TODO: Starting rotation? CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID)); if (cmpPlayer && cmpPlayer->HasStartingCamera()) { // Use player starting camera CFixedVector3D pos = cmpPlayer->GetStartingCameraPos(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } else if (m_StartingCameraTarget != INVALID_ENTITY) { // Point camera at entity CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget); if (cmpPosition) { CFixedVector3D pos = cmpPosition->GetPosition(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname) { VfsPath filename_xml = pathname.ChangeExtension(L".xml"); CXeromyces xmb_file; if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK) return PSRETURN_File_ReadFailed; // Define all the relevant elements used in the XML file #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(scenario); EL(scriptsettings); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(root.GetNodeName() == el_scenario); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_scriptsettings) { m_ScriptSettings = child.GetText(); } } return PSRETURN_OK; } void CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.Eval("({})", ret); if (m_ScriptSettings.empty()) return; JS::RootedValue scriptSettingsVal(cx); scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal); scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Holds various state data while reading maps, so that loading can be // interrupted (e.g. to update the progress display) then later resumed. class CXMLReader { NONCOPYABLE(CXMLReader); public: CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader) : m_MapReader(mapReader), nodes(NULL, 0, NULL) { Init(xml_filename); } CStr ReadScriptSettings(); // read everything except for entities void ReadXML(); // return semantics: see Loader.cpp!LoadFunc. int ProgressiveReadEntities(); private: CXeromyces xmb_file; CMapReader& m_MapReader; int el_entity; int el_tracks; int el_template, el_player; int el_position, el_orientation, el_obstruction; int el_actor; int at_x, at_y, at_z; int at_group, at_group2; int at_angle; int at_uid; int at_seed; XMBElementList nodes; // children of root // loop counters size_t node_idx; size_t entity_idx; // # entities+nonentities processed and total (for progress calc) int completed_jobs, total_jobs; // maximum used entity ID, so we can safely allocate new ones entity_id_t max_uid; void Init(const VfsPath& xml_filename); void ReadTerrain(XMBElement parent); void ReadEnvironment(XMBElement parent); void ReadCamera(XMBElement parent); - void ReadCinema(XMBElement parent); + void ReadPaths(XMBElement parent); void ReadTriggers(XMBElement parent); int ReadEntities(XMBElement parent, double end_time); }; void CXMLReader::Init(const VfsPath& xml_filename) { // must only assign once, so do it here node_idx = entity_idx = 0; if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK) throw PSERROR_File_ReadFailed(); // define the elements and attributes that are frequently used in the XML file, // so we don't need to do lots of string construction and comparison when // reading the data. // (Needs to be synchronised with the list in CXMLReader - ugh) #define EL(x) el_##x = xmb_file.GetElementID(#x) #define AT(x) at_##x = xmb_file.GetAttributeID(#x) EL(entity); EL(tracks); EL(template); EL(player); EL(position); EL(orientation); EL(obstruction); EL(actor); AT(x); AT(y); AT(z); AT(group); AT(group2); AT(angle); AT(uid); AT(seed); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); // find out total number of entities+nonentities // (used when calculating progress) completed_jobs = 0; total_jobs = 0; for (XMBElement node : nodes) total_jobs += node.GetChildNodes().size(); // Find the maximum entity ID, so we can safely allocate new IDs without conflicts max_uid = SYSTEM_ENTITY; XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities")); XERO_ITER_EL(ents, ent) { CStr uid = ent.GetAttributes().GetNamedItem(at_uid); max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt()); } } CStr CXMLReader::ReadScriptSettings() { XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings")); return settings.GetText(); } void CXMLReader::ReadTerrain(XMBElement parent) { #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) AT(patches); AT(texture); AT(priority); AT(height); #undef AT ssize_t patches = 9; CStr texture = "grass1_spring"; int priority = 0; u16 height = 16384; XERO_ITER_ATTR(parent, attr) { if (attr.Name == at_patches) patches = attr.Value.ToInt(); else if (attr.Name == at_texture) texture = attr.Value; else if (attr.Name == at_priority) priority = attr.Value.ToInt(); else if (attr.Name == at_height) height = (u16)attr.Value.ToInt(); } m_MapReader.m_PatchesPerSide = patches; // Load the texture ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture); m_MapReader.pTerrain->Initialize(patches, NULL); // Fill the heightmap u16* heightmap = m_MapReader.pTerrain->GetHeightMap(); ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide(); for (ssize_t i = 0; i < SQR(verticesPerSide); ++i) heightmap[i] = height; // Fill the texture map for (ssize_t pz = 0; pz < patches; ++pz) { for (ssize_t px = 0; px < patches; ++px) { CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail for (ssize_t z = 0; z < PATCH_SIZE; ++z) { for (ssize_t x = 0; x < PATCH_SIZE; ++x) { patch->m_MiniPatches[z][x].Tex = texentry; patch->m_MiniPatches[z][x].Priority = priority; } } } } } void CXMLReader::ReadEnvironment(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(lightingmodel); EL(posteffect); EL(skyset); EL(suncolor); EL(sunelevation); EL(sunrotation); EL(terrainambientcolor); EL(unitsambientcolor); EL(water); EL(waterbody); EL(type); EL(color); EL(tint); EL(height); EL(shininess); // for compatibility EL(waviness); EL(murkiness); EL(windangle); EL(reflectiontint); // for compatibility EL(reflectiontintstrength); // for compatibility EL(fog); EL(fogcolor); EL(fogfactor); EL(fogthickness); EL(postproc); EL(brightness); EL(contrast); EL(saturation); EL(bloom); AT(r); AT(g); AT(b); #undef AT #undef EL XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_lightingmodel) { // NOP - obsolete. } else if (element_name == el_skyset) { if (m_MapReader.pSkyMan) m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8()); } else if (element_name == el_suncolor) { m_MapReader.m_LightEnv.m_SunColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_sunelevation) { m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_sunrotation) { m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_terrainambientcolor) { m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_unitsambientcolor) { m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fog) { XERO_ITER_EL(element, fog) { int element_name = fog.GetNodeName(); if (element_name == el_fogcolor) { XMBAttributeList attrs = fog.GetAttributes(); m_MapReader.m_LightEnv.m_FogColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fogfactor) { m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat(); } else if (element_name == el_fogthickness) { m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat(); } } } else if (element_name == el_postproc) { XERO_ITER_EL(element, postproc) { int element_name = postproc.GetNodeName(); if (element_name == el_brightness) { m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat(); } else if (element_name == el_contrast) { m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat(); } else if (element_name == el_saturation) { m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat(); } else if (element_name == el_bloom) { m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat(); } else if (element_name == el_posteffect) { if (m_MapReader.pPostproc) m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8()); } } } else if (element_name == el_water) { XERO_ITER_EL(element, waterbody) { ENSURE(waterbody.GetNodeName() == el_waterbody); XERO_ITER_EL(waterbody, waterelement) { int element_name = waterelement.GetNodeName(); if (element_name == el_height) { CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY); ENSURE(cmpWaterManager); cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText())); continue; } // The rest are purely graphical effects, and should be ignored if // graphics are disabled if (!m_MapReader.pWaterMan) continue; if (element_name == el_type) { if (waterelement.GetText() == "default") m_MapReader.pWaterMan->m_WaterType = L"ocean"; else m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8(); } else if (element_name == el_shininess || element_name == el_reflectiontint || element_name == el_reflectiontintstrength) { // deprecated. } #define READ_COLOR(el, out) \ else if (element_name == el) \ { \ XMBAttributeList attrs = waterelement.GetAttributes(); \ out = CColor( \ attrs.GetNamedItem(at_r).ToFloat(), \ attrs.GetNamedItem(at_g).ToFloat(), \ attrs.GetNamedItem(at_b).ToFloat(), \ 1.f); \ } #define READ_FLOAT(el, out) \ else if (element_name == el) \ { \ out = waterelement.GetText().ToFloat(); \ } \ READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor) READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint) READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness) READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness) READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle) #undef READ_FLOAT #undef READ_COLOR else debug_warn(L"Invalid map XML data"); } } } else debug_warn(L"Invalid map XML data"); } m_MapReader.m_LightEnv.CalculateSunDirection(); } void CXMLReader::ReadCamera(XMBElement parent) { // defaults if we don't find player starting camera #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(declination); EL(rotation); EL(position); AT(angle); AT(x); AT(y); AT(z); #undef AT #undef EL float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f); CVector3D translation = CVector3D(100, 150, -100); XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_declination) { declination = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_rotation) { rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_position) { translation = CVector3D( attrs.GetNamedItem(at_x).ToFloat(), attrs.GetNamedItem(at_y).ToFloat(), attrs.GetNamedItem(at_z).ToFloat()); } else debug_warn(L"Invalid map XML data"); } if (m_MapReader.pGameView) { m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination); m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation); m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation); m_MapReader.pGameView->GetCamera()->UpdateFrustum(); } } -void CXMLReader::ReadCinema(XMBElement parent) +void CXMLReader::ReadPaths(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(path); EL(rotation); - EL(distortion); EL(node); EL(position); - EL(time); + EL(target); AT(name); AT(timescale); + AT(orientation); AT(mode); AT(style); - AT(growth); - AT(switch); AT(x); AT(y); AT(z); + AT(deltatime); #undef EL #undef AT - - std::map pathList; + XERO_ITER_EL(parent, element) { int elementName = element.GetNodeName(); if (elementName == el_path) { - XMBAttributeList attrs = element.GetAttributes(); - CStrW name(attrs.GetNamedItem(at_name).FromUTF8()); - float timescale = attrs.GetNamedItem(at_timescale).ToFloat(); CCinemaData pathData; - pathData.m_Timescale = timescale; - TNSpline spline, backwardSpline; + XMBAttributeList attrs = element.GetAttributes(); + CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8()); + pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale)); + TNSpline pathSpline, targetSpline; + fixed lastTargetTime = fixed::Zero(); + + pathData.m_Name = pathName; + pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8(); + pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8(); + pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8(); XERO_ITER_EL(element, pathChild) { elementName = pathChild.GetNodeName(); attrs = pathChild.GetAttributes(); - //Load distortion attributes - if (elementName == el_distortion) - { - pathData.m_Mode = attrs.GetNamedItem(at_mode).ToInt(); - pathData.m_Style = attrs.GetNamedItem(at_style).ToInt(); - pathData.m_Growth = attrs.GetNamedItem(at_growth).ToInt(); - pathData.m_Switch = attrs.GetNamedItem(at_switch).ToInt(); - } - - //Load node data used for spline - else if (elementName == el_node) + // Load node data used for spline + if (elementName == el_node) { + bool positionDeclared = false; SplineData data; + data.Distance = fixed::FromString(attrs.GetNamedItem(at_deltatime)); + lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime)); XERO_ITER_EL(pathChild, nodeChild) { elementName = nodeChild.GetNodeName(); attrs = nodeChild.GetAttributes(); - - //Fix?: assumes that time is last element + if (elementName == el_position) { - data.Position.X = attrs.GetNamedItem(at_x).ToFloat(); - data.Position.Y = attrs.GetNamedItem(at_y).ToFloat(); - data.Position.Z = attrs.GetNamedItem(at_z).ToFloat(); - continue; + data.Position.X = fixed::FromString(attrs.GetNamedItem(at_x)); + data.Position.Y = fixed::FromString(attrs.GetNamedItem(at_y)); + data.Position.Z = fixed::FromString(attrs.GetNamedItem(at_z)); + positionDeclared = true; } else if (elementName == el_rotation) { - data.Rotation.X = attrs.GetNamedItem(at_x).ToFloat(); - data.Rotation.Y = attrs.GetNamedItem(at_y).ToFloat(); - data.Rotation.Z = attrs.GetNamedItem(at_z).ToFloat(); - continue; + data.Rotation.X = fixed::FromString(attrs.GetNamedItem(at_x)); + data.Rotation.Y = fixed::FromString(attrs.GetNamedItem(at_y)); + data.Rotation.Z = fixed::FromString(attrs.GetNamedItem(at_z)); + } + else if (elementName == el_target) + { + CFixedVector3D targetPosition; + targetPosition.X = fixed::FromString(attrs.GetNamedItem(at_x)); + targetPosition.Y = fixed::FromString(attrs.GetNamedItem(at_y)); + targetPosition.Z = fixed::FromString(attrs.GetNamedItem(at_z)); + + targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime); + lastTargetTime = fixed::Zero(); } - else if (elementName == el_time) - data.Distance = nodeChild.GetText().ToFloat(); else - debug_warn(L"Invalid cinematic element for node child"); - - backwardSpline.AddNode(data.Position, data.Rotation, data.Distance); + LOGWARNING("Invalid cinematic element for node child"); } + + // Skip the node if no position + if (positionDeclared) + pathSpline.AddNode(data.Position, data.Rotation, data.Distance); } else - debug_warn(L"Invalid cinematic element for path child"); - - + LOGWARNING("Invalid cinematic element for path child"); } - //Construct cinema path with data gathered - CCinemaPath temp(pathData, backwardSpline); - const std::vector& nodes = temp.GetAllNodes(); - if (nodes.empty()) + // Construct cinema path with data gathered + CCinemaPath path(pathData, pathSpline, targetSpline); + if (path.Empty()) { - debug_warn(L"Failure loading cinematics"); + LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8()); return; } - for (std::vector::const_reverse_iterator it = nodes.rbegin(); - it != nodes.rend(); ++it) - { - spline.AddNode(it->Position, it->Rotation, it->Distance); - } - - CCinemaPath path(pathData, spline); - pathList[name] = path; + if (!m_MapReader.pCinema->HasPath(pathName)) + m_MapReader.pCinema->AddPath(pathName, path); + else + LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8()); } else - ENSURE("Invalid cinema child"); + LOGWARNING("Invalid path child with name '%s'", element.GetText()); } - - if (m_MapReader.pCinema) - m_MapReader.pCinema->SetAllPaths(pathList); } void CXMLReader::ReadTriggers(XMBElement UNUSED(parent)) { } int CXMLReader::ReadEntities(XMBElement parent, double end_time) { XMBElementList entities = parent.GetChildNodes(); ENSURE(m_MapReader.pSimulation2); CSimulation2& sim = *m_MapReader.pSimulation2; CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY); while (entity_idx < entities.size()) { // all new state at this scope and below doesn't need to be // wrapped, since we only yield after a complete iteration. XMBElement entity = entities[entity_idx++]; ENSURE(entity.GetNodeName() == el_entity); XMBAttributeList attrs = entity.GetAttributes(); CStr uid = attrs.GetNamedItem(at_uid); ENSURE(!uid.empty()); int EntityUid = uid.ToInt(); CStrW TemplateName; int PlayerID = 0; CFixedVector3D Position; CFixedVector3D Orientation; long Seed = -1; // Obstruction control groups. entity_id_t ControlGroup = INVALID_ENTITY; entity_id_t ControlGroup2 = INVALID_ENTITY; XERO_ITER_EL(entity, setting) { int element_name = setting.GetNodeName(); //