1#include "SeaCurrentsExtension.h"
4#include "SeaCurrentsSystem.h"
5#include "Utilities/VectorPermutation.h"
6#include "ResourceManifest.h"
9#include "EntityStore.h"
10#include "ExtensionRegistry.h"
11#include "Components/Core/TransformComponent.h"
12#include "Resources/ResourceStore.h"
13#include "Serialization/EntityReader.h"
14#include "Utilities/Parsing.h"
16#include "Foundation/HashFunctions.h"
17#include "Foundation/Geometry/GeodeticUtils.h"
18#include "Foundation/Platform/IO.h"
19#include "Foundation/Logging/Logger.h"
23#include "H5LTpublic.h"
32 struct RawCurrentData {
37 struct RawPositionData {
42 constexpr const char* cSeaCurrentsEntityDefinition = R
"(
43 { "name": "SeaCurrents",
45 "SeaCurrentsComponent",
55 void ComputePriority(SeaCurrentsComponent& seaCurrentsComp,
const std::pair<glm::vec2, glm::vec2>& bounds,
float threshold,
int lowestPrio);
59 static constexpr const char* cExtensionKey =
"SeaCurrents";
62 reinterpret_cast<LoadFromMemoryFn
>(&SeaCurrentsExtension::loadFromMemory),
63 reinterpret_cast<LoadFromFileFn
>(&SeaCurrentsExtension::loadFromFile),
71 SeaCurrentsComponent::registerType();
80 const std::string resourceArchive =
"Cogs.Core.Extensions.SeaCurrents.zip";
85 bool resourceArchiveAdded =
false;
86 auto manifest = getResourceManifest(context);
87 for (
auto& item : manifest) {
88 if (item.find(resourceArchive) != std::string::npos) {
90 resourceArchiveAdded =
true;
94 if (!resourceArchiveAdded) {
100 context->
resourceStore->addSearchPath(
"../Extensions/SeaCurrents/Data/");
105 readEntityDefinition(cSeaCurrentsEntityDefinition, context->
store);
111 static bool loadFromMemory(
void* ctx, EntityId entityId,
const char* extension,
void* data,
size_t dataSize, uint64_t timestamp,
int utmZone = -1) {
121 hid_t fileId = H5LTopen_file_image(data, dataSize, H5LT_FILE_IMAGE_DONT_COPY | H5LT_FILE_IMAGE_DONT_RELEASE);
123 uint32_t numInstances;
124 uint32_t dataCodingFormat;
126 groupId = H5Gopen(fileId,
"/SurfaceCurrent", H5P_DEFAULT);
127 dataCodingFormat = readAttribute<uint32_t>(groupId,
"dataCodingFormat");
128 numInstances = readAttribute<uint32_t>(groupId,
"numInstances");
132 if (numInstances == 1) {
133 groupId = H5Gopen(fileId,
"/SurfaceCurrent/SurfaceCurrent.01", H5P_DEFAULT);
135 uint64_t firstTime =
parseISO8601(readAttribute<std::string>(groupId,
"dateTimeOfFirstRecord"));
136 uint64_t lastTime =
parseISO8601(readAttribute<std::string>(groupId,
"dateTimeOfLastRecord"));
137 uint32_t noOfRecords = readAttribute<uint32_t>(groupId,
"numberOfTimes");
138 uint32_t closestRecord;
139 float eastBound = readAttribute<float>(groupId,
"eastBoundLongitude");
140 float westBound = readAttribute<float>(groupId,
"westBoundLongitude");
141 float southBound = readAttribute<float>(groupId,
"southBoundLatitude");
142 float northBound = readAttribute<float>(groupId,
"northBoundLatitude");
143 hid_t rawCurrentDataMemType = H5Tcreate(H5T_COMPOUND,
sizeof(RawCurrentData));
145 double lowerNorthing;
147 double upperNorthing;
149 LLtoUTM(lowerEasting, lowerNorthing, southBound, westBound, utmZone,
true);
150 LLtoUTM(upperEasting, upperNorthing, northBound, eastBound, utmZone,
true);
152 glm::dvec2 centre((lowerEasting + upperEasting) * 0.5f, (lowerNorthing + upperNorthing) * 0.5f);
154 H5Tinsert(rawCurrentDataMemType,
"surfaceCurrentSpeed", HOFFSET(RawCurrentData, mSpeed), H5T_NATIVE_FLOAT);
155 H5Tinsert(rawCurrentDataMemType,
"surfaceCurrentDirection", HOFFSET(RawCurrentData, mDirection), H5T_NATIVE_FLOAT);
157 if (timestamp <= firstTime) {
160 else if (timestamp >= lastTime) {
161 closestRecord = noOfRecords;
164 closestRecord =
static_cast<uint32_t
>((timestamp - firstTime) * noOfRecords / (lastTime - firstTime));
170 sprintf(filename,
"/SurfaceCurrent/SurfaceCurrent.01/Group_%03d/values", std::min(std::max(1U, closestRecord), noOfRecords));
171 datasetId = H5Dopen(fileId, filename, H5P_DEFAULT);
173 switch (dataCodingFormat) {
179 uint64_t width = readAttribute<uint32_t>(groupId,
"numPointsLongitudinal");
180 uint64_t height = readAttribute<uint32_t>(groupId,
"numPointsLatitudinal");
181 std::vector<RawCurrentData> buffer(width * height);
183 if (H5Dread(datasetId, rawCurrentDataMemType, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer.data()) >= 0) {
184 float y = southBound;
185 float dx = readAttribute<float>(groupId,
"gridSpacingLongitudinal");
186 float dy = readAttribute<float>(groupId,
"gridSpacingLatitudinal");
187 auto i = buffer.begin();
192 component->
speeds.clear();
193 component->
angles.clear();
197 for (uint64_t cy = height; cy--; y += dy) {
200 for (uint64_t cx = width; cx--; x += dx, ++i) {
201 if (i->mSpeed >= 0.0f) {
202 LLtoUTM(easting, northing, y, x, utmZone,
true);
205 northing -= centre.y;
207 component->
positions.push_back(glm::vec2(
static_cast<float>(easting),
static_cast<float>(northing)));
208 component->
speeds.push_back(i->mSpeed);
209 component->
angles.push_back(i->mDirection);
214 entity->getComponent<TransformComponent>()->coordinates = glm::vec3(centre, 0.0f);
215 entity->getComponent<TransformComponent>()->
setChanged();
221 uint32_t noOfNodes = readAttribute<uint32_t>(groupId,
"numberOfNodes");
222 hid_t rawPositionDataMemType = H5Tcreate(H5T_COMPOUND,
sizeof(RawPositionData));
223 std::vector<RawPositionData> positions(noOfNodes);
224 hid_t positionsId = H5Dopen(fileId,
"/SurfaceCurrent/SurfaceCurrent.01/Positioning/geometryValues", H5P_DEFAULT);
226 H5Tinsert(rawPositionDataMemType,
"latitude", HOFFSET(RawPositionData, mLatitude), H5T_NATIVE_FLOAT);
227 H5Tinsert(rawPositionDataMemType,
"longitude", HOFFSET(RawPositionData, mLongitude), H5T_NATIVE_FLOAT);
229 if (H5Dread(positionsId, rawPositionDataMemType, H5S_ALL, H5S_ALL, H5P_DEFAULT, positions.data()) >= 0) {
230 std::vector<RawCurrentData> currents(noOfNodes);
232 if (H5Dread(datasetId, rawCurrentDataMemType, H5S_ALL, H5S_ALL, H5P_DEFAULT, currents.data()) >= 0) {
235 auto pi = positions.begin();
236 auto ci = currents.begin();
239 component->
speeds.clear();
240 component->
angles.clear();
244 for (; noOfNodes--; ++ci, ++pi) {
245 LLtoUTM(easting, northing, pi->mLatitude, pi->mLongitude, utmZone,
true);
248 northing -= centre.y;
250 component->
positions.push_back(glm::vec2(
static_cast<float>(easting),
static_cast<float>(northing)));
251 component->
speeds.push_back(ci->mSpeed);
252 component->
angles.push_back(ci->mDirection);
254 entity->getComponent<TransformComponent>()->coordinates = glm::vec3(centre, 0.0f);
255 entity->getComponent<TransformComponent>()->
setChanged();
259 H5Dclose(positionsId);
260 H5Tclose(rawPositionDataMemType);
281 glm::vec2 minPos(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
282 glm::vec2 maxPos(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
285 std::vector<glm::vec2>::iterator minId, maxId;
286 minId = std::min_element(component->
positions.begin(), component->
positions.end(), [](
const glm::vec2& a,
const glm::vec2& b) {
287 return (a == glm::min(a, b)) ? true : false;
289 maxId = std::max_element(component->
positions.begin(), component->
positions.end(), [](
const glm::vec2& a,
const glm::vec2& b) {
290 return (b == glm::max(a, b)) ? true : false;
293 if (minId != component->
positions.end()) minPos = *minId;
294 if (maxId != component->
positions.end()) maxPos = *maxId;
297 const std::pair<glm::vec2, glm::vec2> bounds = { minPos, maxPos };
300 constexpr float deltaBounds = 0.0001f;
301 if (deltaBounds <= glm::distance(bounds.first, bounds.second)) {
306 SeaCurrentQuadtree tree(minPos, maxPos);
307 for (
const auto& pos : component->
positions)
309 tree.insert(tree.getRoot(), 0, pos);
312 for (
size_t i = 0; i < component->
positions.size(); i++)
314 int priority = tree.search(tree.getRoot(), component->
positions[i], 0);
315 component->
priorities[i] =
static_cast<int>(std::round(priority));
319 auto p = Permutation::get_permutation(component->
priorities,
320 [](
const int& a,
const int& b) { return a < b; });
323 Permutation::apply_permutation(component->
priorities, p);
324 Permutation::apply_permutation(component->
positions, p);
325 Permutation::apply_permutation(component->
speeds, p);
326 Permutation::apply_permutation(component->
angles, p);
333 H5Tclose(rawCurrentDataMemType);
344 LOG_ERROR(gLogger,
"%s: Unsupported extension .h5 in EMSCRIPTEN build", IO::extension(extension).c_str());
350 static bool loadFromFile(
void* context, EntityId entityId,
const char* filename, uint64_t timestamp,
int utmZone = -1) {
351 ResourceBuffer buffer =
reinterpret_cast<Context*
>(context)->resourceStore->getResourceContents(filename);
353 if (!buffer.empty()) {
354 return loadFromMemory(context, entityId, filename, buffer.data(), buffer.size(), timestamp, utmZone);
359 template<
typename T>
static T readAttribute(hid_t ownerId,
const StringView& name) {
360 hid_t attributeId = H5Aopen(ownerId, name.data(), H5P_DEFAULT);
363 if (attributeId != H5I_INVALID_HID) {
364 hid_t typeId = getTypeId<T>();
366 assert(typeId != H5I_INVALID_HID);
368 H5Aread(attributeId, typeId, &value);
369 H5Aclose(attributeId);
374 template<> std::string readAttribute<std::string>(hid_t ownerId,
const StringView& name) {
375 hid_t attributeId = H5Aopen(ownerId, name.data(), H5P_DEFAULT);
378 if (attributeId != H5I_INVALID_HID) {
382 H5Aget_info(attributeId, &info);
383 if (H5Aread(attributeId, H5Aget_type(attributeId), &ptr) >= 0) {
384 value = std::string(ptr, info.data_size);
387 H5Aclose(attributeId);
392 template<
typename T>
static hid_t getTypeId() {
return H5I_INVALID_HID; }
393 template<> hid_t getTypeId<int32_t>() {
return H5T_NATIVE_INT32; }
394 template<> hid_t getTypeId<uint32_t>() {
return H5T_NATIVE_UINT32; }
395 template<> hid_t getTypeId<float>() {
return H5T_NATIVE_FLOAT; }
396 template<> hid_t getTypeId<double>() {
return H5T_NATIVE_DOUBLE; }
399 seaCurrentsExtensionInstance;
402 void ComputePriority(SeaCurrentsComponent& seaCurrentsComp,
const std::pair<glm::vec2, glm::vec2>& bounds,
float threshold,
int lowestPrio) {
405 if (std::find(seaCurrentsComp.priorities.begin(), seaCurrentsComp.priorities.end(), 0) == seaCurrentsComp.priorities.end()) {
409 std::map<int, std::vector<int>> neighbours;
410 int priorityModifier = 0;
413 for (
int idx = 0, pointCount = (
int)seaCurrentsComp.positions.size(); idx < pointCount; ++idx) {
415 if (seaCurrentsComp.priorities[idx] != 0)
continue;
417 for (
int idy = 0; idy < pointCount; ++idy) {
420 if (idx == idy)
continue;
422 if (glm::distance(seaCurrentsComp.positions[idx], seaCurrentsComp.positions[idy]) < threshold) {
423 neighbours[idx].push_back(idy);
424 if (seaCurrentsComp.priorities[idy] == 0) neighbours[idy].push_back(idx);
430 if (!neighbours.empty())
432 std::vector<std::pair<int, int>> sizeKeyTable;
433 std::set<int> toIgnore;
436 sizeKeyTable.reserve(neighbours.size());
437 for (
auto& k : neighbours) {
438 if (seaCurrentsComp.priorities[k.first] == 0) {
439 sizeKeyTable.emplace_back(
static_cast<int>(k.second.size()), k.first);
443 std::stable_sort(sizeKeyTable.begin(), sizeKeyTable.end(), [](
const std::pair<int, int>& a,
const std::pair<int, int>& b) { return a.first > b.first; });
446 for (
const auto& point : sizeKeyTable) {
449 if (std::find(toIgnore.begin(), toIgnore.end(), point.second) != toIgnore.end())
452 int32_t val = int32_t(lowestPrio - (sizeKeyTable.begin()->first - (point.first - 1)));
453 seaCurrentsComp.priorities[point.second] = val;
455 for (
auto& k : neighbours[point.second]) {
460 priorityModifier = sizeKeyTable.begin()->first;
462 ComputePriority(seaCurrentsComp, bounds, threshold, lowestPrio - priorityModifier);
465 ComputePriority(seaCurrentsComp, bounds, threshold * 2.0f, lowestPrio - priorityModifier);
472#define SEA_CURRENTS_EXT_API __declspec(dllexport)
474#define SEA_CURRENTS_EXT_API
478 return &Cogs::Core::seaCurrentsExtensionInstance;
482 return &Cogs::Core::seaCurrentsExtensionInstance.api;
void setChanged()
Sets the component to the ComponentFlags::Changed state with carry.
ComponentType * getComponent() const
A Context instance contains all the services, systems and runtime components needed to use Cogs.
class EntityStore * store
Entity store.
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
EntityPtr getEntity(const StringView &name, bool logIfNotFound=true) const
Retrieve a reference to the shared entity pointer to the Entity with the given name.
static void add(Extension *extension, StringView version)
Adds the given extension to the registry, ensuring the initialization methods are called at appropria...
Log implementation class.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
std::shared_ptr< ComponentModel::Entity > EntityPtr
Smart pointer for Entity access.
uint64_t COGSCORE_DLL_API parseISO8601(const StringView &iso8601)
Parse ISO 8601 timestamps.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
void setChanged(Cogs::Core::Context *context, Cogs::ComponentModel::Component *component, Reflection::FieldId fieldId)
Must be Called after changing a Component field. Mark field changed. Request engine update.
Defines an extension to Cogs.Core and provides methods to override in order to initialize extension c...
Component for displaying surface currents based on the IHO S111 standard.
std::vector< float > speeds
Speed in knots of the current at each position.
std::vector< int > priorities
Priority of each arrow.
std::vector< float > angles
Compass direction of the current at each position.
int priorityLevel
Hide arrows with priority greater than this.
std::vector< glm::vec2 > positions
Position of each arrow in this component in world coordinates.
virtual bool initialize(Context *context) override
Initialize extension for the given context.
virtual bool initializeStatic() override
Initialize extension statically.
virtual const char * getExtensionKey() const override
Get the extensions unique key, used to check for extension presence and retrieve extension specific d...
virtual const void * getPublicAPI() const override
Retrieve a pointer to a struct containing all publicly available function pointers.
@ Geometry
Run at the time geometry data is updated.