DynamicHeatmap.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. /*
  2. * @Author: namidame
  3. * @Description: A Heatmap Generation plugin based on the old HeatMapActor, Supports Heightmap, Texture Coordinate Points And Geographic Location Data.
  4. * @Date: 2024/07/27
  5. */
  6. #include "DynamicHeatmap.h"
  7. #include <fstream>
  8. #include <vector>
  9. #include <functional>
  10. #include "TextureResource.h"
  11. #include "GeoReferencingSystem.h"
  12. #include "Engine/DataTable.h"
  13. #include "Kismet/GameplayStatics.h"
  14. #include "Kismet/KismetMathLibrary.h"
  15. #include "Dom/JsonValue.h"
  16. #include "Dom/JsonObject.h"
  17. #include "Misc/FileHelper.h"
  18. #include "Serialization/JsonSerializer.h"
  19. #include "Serialization/JsonReader.h"
  20. #include "Misc/Paths.h"
  21. #define EARTH_RADIUS 6400 * 1000 * 100
  22. // Sets default values
  23. ADynamicHeatmap::ADynamicHeatmap()
  24. : bRefreshOnConstruction(true)
  25. , TextureSize(FVector2D(2048, 2048))
  26. , DefaultTextureSizeBase(2048)
  27. , MeshSize(FVector2D(100000, 100000))
  28. , MeshSegment(FVector2D(50, 50))
  29. , MeshMargin(FMargin(0.05))
  30. , HeatmapAltitude(0)
  31. , LerpScale(2.5)
  32. , HeightScale(2500.0)
  33. , Opacity(0.7)
  34. , ZeroValueOpacity(0)
  35. , InfluenceSize(50)
  36. , EmissiveStrength(1)
  37. {
  38. RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
  39. ProceduralMapMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("MapMesh"));
  40. ProceduralMapMesh->SetupAttachment(RootComponent);
  41. ProceduralMapMesh->SetCastShadow(false);
  42. ProceduralMapMesh->bUseComplexAsSimpleCollision = true;
  43. }
  44. // Called when the game starts or when spawned
  45. void ADynamicHeatmap::BeginPlay()
  46. {
  47. Super::BeginPlay();
  48. }
  49. void ADynamicHeatmap::OnConstruction(const FTransform& Transform)
  50. {
  51. Super::OnConstruction(Transform);
  52. if(bRefreshOnConstruction)
  53. {
  54. this->UpdateHeatmap(true, true);
  55. }
  56. }
  57. // Called every frame
  58. void ADynamicHeatmap::Tick(float DeltaTime)
  59. {
  60. Super::Tick(DeltaTime);
  61. }
  62. void ADynamicHeatmap::CreateWithGrayScaleTexture(UTexture2D* texture, FVector2D meshSize, bool bUpdateMesh, bool bRecreateMesh)
  63. {
  64. if(texture == nullptr)
  65. {
  66. UE_LOG(LogTemp, Log, TEXT("%s:Assign a valid texture!"), *FString(__FUNCTION__));
  67. }
  68. if(texture != nullptr)
  69. {
  70. GrayScaleTexture = texture;
  71. FVector2D textureSize(texture->GetSizeX(), texture->GetSizeY());
  72. TextureSize = textureSize;
  73. }
  74. HeatmapType = EDynamicHeatmapType::GrayScaleTexture;
  75. MeshSize = meshSize;
  76. if(bUpdateMesh)
  77. {
  78. this->UpdateGeneratedMesh(meshSize, bRecreateMesh);
  79. }
  80. // create material with texture
  81. if(!HeatmapMaterial) return;
  82. UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr);
  83. mat->SetTextureParameterValue(TEXT("Texture"), texture);
  84. Material = mat;
  85. this->UpdateMaterialParameters();
  86. // set material into map mesh
  87. ProceduralMapMesh->SetMaterial(0, mat);
  88. }
  89. void ADynamicHeatmap::CreateWithTextureCoordPointMap(const TMap<FVector2D, float>& map, FVector2D textureSize, FVector2D meshSize, bool bUpdateMesh, bool bRecreateMesh)
  90. {
  91. TextureCoordPointMap = map;
  92. TArray<FColor> colorArr = this->GetColorDataFromTextureCoordPointMap(map, textureSize, InfluenceSize);
  93. TextureSize = textureSize;
  94. // create map mesh
  95. if(bUpdateMesh)
  96. {
  97. this->UpdateGeneratedMesh(meshSize, bRecreateMesh);
  98. }
  99. // create material with texture
  100. if(!HeatmapMaterial) return;
  101. UTexture2D* texture = CreateTexture(TextureSize, colorArr);
  102. UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr);
  103. mat->SetTextureParameterValue(TEXT("Texture"), texture);
  104. Material = mat;
  105. this->UpdateMaterialParameters();
  106. // set material into map mesh
  107. ProceduralMapMesh->SetMaterial(0, mat);
  108. }
  109. void ADynamicHeatmap::CreateWithTextureCoordPointMapDataTable(UDataTable* DataTable, FVector2D texture_size,
  110. FVector2D mesh_size, bool bUpdateMesh, bool bRecreateMesh)
  111. {
  112. if(DataTable == nullptr)
  113. {
  114. UE_LOG(LogTemp, Error, TEXT("Input a valid datatable!"));
  115. return;
  116. }
  117. auto map = LoadDataTableToTextureCoordPointMap(DataTable);
  118. if(map.Num() == 0)
  119. {
  120. UE_LOG(LogTemp, Error, TEXT("DataTable empty!"));
  121. return;
  122. }
  123. TextureCoordPointDataTable = DataTable;
  124. this->CreateWithTextureCoordPointMap(map, texture_size, mesh_size, bUpdateMesh, bRecreateMesh);
  125. }
  126. void ADynamicHeatmap::CreateWithGeographicData(const TMap<FVector, float>& map, bool bUpdateMesh, bool bRecreateMesh)
  127. {
  128. if(map.Num() == 0)
  129. {
  130. UE_LOG(LogTemp, Error, TEXT("Input a valid map!"));
  131. return;
  132. }
  133. HeatmapType = EDynamicHeatmapType::GeographicData;
  134. GeographicData = map;
  135. TMap<FVector2D, float> rlt = this->ConvertGeoValueMapToUnrealValueMap(map);
  136. TArray<FColor> colorArr = this->GetColorDataFromTextureCoordPointMap(rlt, TextureSize, InfluenceSize);
  137. if(colorArr.IsEmpty())
  138. {
  139. UE_LOG(LogTemp, Error, TEXT("No valid data!"));
  140. return;
  141. }
  142. // create map mesh
  143. if(bUpdateMesh)
  144. {
  145. this->UpdateGeneratedMesh(GeographicSize, bRecreateMesh);
  146. }
  147. // create material with texture
  148. if(!HeatmapMaterial) return;
  149. UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr);
  150. UTexture2D* texture = CreateTexture(TextureSize, colorArr);
  151. mat->SetTextureParameterValue(TEXT("Texture"), texture);
  152. Material = mat;
  153. this->UpdateMaterialParameters();
  154. // set material into map mesh
  155. ProceduralMapMesh->SetMaterial(0, mat);
  156. }
  157. void ADynamicHeatmap::CreateWithGeographicDataTable(UDataTable* DataTable, bool bUpdateMesh, bool bRecreateMesh)
  158. {
  159. if(DataTable == nullptr)
  160. {
  161. UE_LOG(LogTemp, Error, TEXT("Input a valid datatable!"));
  162. return;
  163. }
  164. auto map = LoadDataTableToGeographicData(DataTable);
  165. if(map.Num() == 0)
  166. {
  167. UE_LOG(LogTemp, Error, TEXT("DataTable empty!"));
  168. return;
  169. }
  170. GeographicDataTable = DataTable;
  171. this->CreateWithGeographicData(map, bUpdateMesh, bRecreateMesh);
  172. }
  173. void ADynamicHeatmap::CreateWithJsonFile(const FString& FilePath, bool bRelativePath, bool bUpdateMesh, bool bRecreateMesh, const FString& LngName, const FString& LatName, const FString& ValueName, bool bStringCoord, bool bStringValue)
  174. {
  175. FString Fullpath = FilePath;
  176. if(bRelativePath)
  177. {
  178. Fullpath = GetFullpath(FilePath);
  179. }
  180. CheckFileExists(__FUNCTION__, Fullpath);
  181. bProjectRelativePath = bRelativePath;
  182. JsonFilePath = FilePath;
  183. JsonConfig = FJsonConfig(LngName, LatName, ValueName, bStringCoord, bStringValue);
  184. TMap<FVector, float> OutMap;
  185. if(!LoadJsonFileToGeographicData(Fullpath, OutMap, LngName, LatName, ValueName, bStringCoord, bStringValue))
  186. {
  187. return;
  188. }
  189. HeatmapType = EDynamicHeatmapType::JsonFileData;
  190. if(OutMap.Num() == 0) return;
  191. TMap<FVector2D, float> rlt = this->ConvertGeoValueMapToUnrealValueMap(OutMap);
  192. TArray<FColor> colorArr = this->GetColorDataFromTextureCoordPointMap(rlt, TextureSize, InfluenceSize);
  193. if(colorArr.IsEmpty())
  194. {
  195. UE_LOG(LogTemp, Error, TEXT("No valid data!"));
  196. return;
  197. }
  198. // create map mesh
  199. if(bUpdateMesh)
  200. {
  201. this->UpdateGeneratedMesh(GeographicSize, bRecreateMesh);
  202. }
  203. // create material with texture
  204. if(!HeatmapMaterial) return;
  205. UTexture2D* texture = CreateTexture(TextureSize, colorArr);
  206. UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr);
  207. mat->SetTextureParameterValue(TEXT("Texture"), texture);
  208. Material = mat;
  209. this->UpdateMaterialParameters();
  210. // set material into map mesh
  211. ProceduralMapMesh->SetMaterial(0, mat);
  212. }
  213. void ADynamicHeatmap::CreateWithJsonFile_Config(const FString& FilePath, bool bRelativePath, bool bUpdateMesh, bool bRecreateMesh, FJsonConfig Config)
  214. {
  215. this->CreateWithJsonFile(FilePath, bRelativePath, bUpdateMesh, bRecreateMesh, Config.LngName, Config.LatName, Config.ValueName, Config.bStringCoord, Config.bStringValue);
  216. }
  217. void ADynamicHeatmap::CreateWithHgtFile(const FString& FilePath, bool bRelativePath, FVector2D meshSize, bool bUpdateMesh, bool bRecreateMesh)
  218. {
  219. FString Fullpath = FilePath;
  220. if(bRelativePath)
  221. {
  222. Fullpath = GetFullpath(FilePath);
  223. }
  224. CheckFileExists(__FUNCTION__, Fullpath);
  225. auto colorArr = LoadHgtFile(Fullpath);
  226. bProjectRelativePath = bRelativePath;
  227. HgtFilePath = FilePath;
  228. HeatmapType = EDynamicHeatmapType::HgtFileData;
  229. TextureSize = FVector2D(1201, 1201);
  230. MeshSize = meshSize;
  231. // create map mesh
  232. if(bUpdateMesh)
  233. {
  234. this->UpdateGeneratedMesh(meshSize, bRecreateMesh);
  235. }
  236. // create material with texture
  237. if(!HeatmapMaterial) return;
  238. UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr);
  239. UTexture2D* texture = CreateTexture(TextureSize, colorArr);
  240. mat->SetTextureParameterValue(TEXT("Texture"), texture);
  241. Material = mat;
  242. this->UpdateMaterialParameters();
  243. // set material into map mesh
  244. ProceduralMapMesh->SetMaterial(0, mat);
  245. }
  246. void ADynamicHeatmap::UpdateMapSegment(FVector2D segment)
  247. {
  248. bool bUpdateMesh = MeshSegment == segment;
  249. bool bRecreateMesh = bUpdateMesh;
  250. MeshSegment = FVector2D(round(segment.X), round(segment.Y));
  251. this->UpdateHeatmap(bUpdateMesh, bRecreateMesh);
  252. }
  253. void ADynamicHeatmap::UpdateMeshSize(FVector2D size)
  254. {
  255. bool bUpdateMesh = MeshSize == size;
  256. MeshSize = size;
  257. this->UpdateHeatmap(bUpdateMesh, false);
  258. }
  259. void ADynamicHeatmap::AddTextureCoordValue(const FVector2D& pos, float value, bool bUpdate)
  260. {
  261. if(HeatmapType != EDynamicHeatmapType::TextureCoordPoint) return;
  262. if(TextureCoordPointMap.Contains(pos))
  263. {
  264. TextureCoordPointMap[pos] = value;
  265. }
  266. else
  267. {
  268. TextureCoordPointMap.Add(pos, value);
  269. }
  270. if(bUpdate)
  271. {
  272. this->CreateWithTextureCoordPointMap(TextureCoordPointMap, TextureSize, MeshSize, false, false);
  273. }
  274. }
  275. void ADynamicHeatmap::DeleteTextureCoordValue(const FVector2D& pos, bool bUpdate)
  276. {
  277. if(HeatmapType != EDynamicHeatmapType::TextureCoordPoint) return;
  278. if(TextureCoordPointMap.Contains(pos))
  279. {
  280. TextureCoordPointMap.Remove(pos);
  281. }
  282. if(bUpdate)
  283. {
  284. this->CreateWithTextureCoordPointMap(TextureCoordPointMap, TextureSize, MeshSize, false, false);
  285. }
  286. }
  287. void ADynamicHeatmap::UpdatePointInfluenceSize(int32 size)
  288. {
  289. InfluenceSize = size;
  290. this->UpdateHeatmap(false, false);
  291. }
  292. void ADynamicHeatmap::UpdateHeatmap(bool bUpdateMesh, bool bRecreateMesh)
  293. {
  294. if(HeatmapType == EDynamicHeatmapType::GrayScaleTexture)
  295. {
  296. this->CreateWithGrayScaleTexture(GrayScaleTexture, MeshSize, bUpdateMesh, bRecreateMesh);
  297. }
  298. else if(HeatmapType == EDynamicHeatmapType::TextureCoordPoint)
  299. {
  300. if(TextureCoordPointDataTable)
  301. {
  302. this->CreateWithTextureCoordPointMapDataTable(TextureCoordPointDataTable, TextureSize, MeshSize, bUpdateMesh, bRecreateMesh);
  303. }
  304. else
  305. {
  306. this->CreateWithTextureCoordPointMap(TextureCoordPointMap, TextureSize, MeshSize, bUpdateMesh, bRecreateMesh);
  307. }
  308. }
  309. else if(HeatmapType == EDynamicHeatmapType::GeographicData)
  310. {
  311. if(GeographicDataTable)
  312. {
  313. this->CreateWithGeographicDataTable(GeographicDataTable, bUpdateMesh, bRecreateMesh);
  314. }
  315. else
  316. {
  317. this->CreateWithGeographicData(GeographicData, bUpdateMesh, bRecreateMesh);
  318. }
  319. }
  320. else if(HeatmapType == EDynamicHeatmapType::JsonFileData)
  321. {
  322. this->CreateWithJsonFile_Config(JsonFilePath, bProjectRelativePath, bUpdateMesh, bRecreateMesh, JsonConfig);
  323. }
  324. else if(HeatmapType == EDynamicHeatmapType::HgtFileData)
  325. {
  326. this->CreateWithHgtFile(HgtFilePath, bProjectRelativePath, MeshSize, bUpdateMesh, bRecreateMesh);
  327. }
  328. }
  329. FVector ADynamicHeatmap::ConvertGeoGraphicToEngine(FVector location)
  330. {
  331. FGeographicCoordinates geo(location.X, location.Y, location.Z);
  332. FVector newPos;
  333. if(GetGeoReferencing()) GetGeoReferencing()->GeographicToEngine(geo, newPos);
  334. return newPos;
  335. }
  336. void ADynamicHeatmap::ComputeLeftUpCornerESUTransform(float MinLng, float MaxLat)
  337. {
  338. FVector east, north, up;
  339. if(GetGeoReferencing())
  340. {
  341. FGeographicCoordinates LeftUpCornerGeo = FGeographicCoordinates(MinLng, MaxLat, HeatmapAltitude);
  342. GetGeoReferencing()->GetENUVectorsAtGeographicLocation(LeftUpCornerGeo, east, north, up);
  343. FVector LeftUpCornerUe;
  344. GetGeoReferencing()->GeographicToEngine(LeftUpCornerGeo, LeftUpCornerUe);
  345. FRotator Rotation = UKismetMathLibrary::MakeRotationFromAxes(east, -north, up);
  346. LeftUpCornerESUTransform = UKismetMathLibrary::MakeTransform(LeftUpCornerUe, Rotation);
  347. }
  348. }
  349. TMap<FVector2D, float> ADynamicHeatmap::ConvertGeoValueMapToUnrealValueMap(const TMap<FVector, float>& map)
  350. {
  351. TMap<FVector, float> rlt1;
  352. float minLng = 180, maxLng = -180, minLat = 90, maxLat = -90;
  353. for(auto it : map)
  354. {
  355. FVector loc = it.Key;
  356. loc.Z = HeatmapAltitude;
  357. FVector newPos = this->ConvertGeoGraphicToEngine(loc);
  358. rlt1.Add(newPos, it.Value);
  359. if(loc.X < minLng) minLng = loc.X;
  360. if(loc.X > maxLng) maxLng = loc.X;
  361. if(loc.Y < minLat) minLat = loc.Y;
  362. if(loc.Y > maxLat) maxLat = loc.Y;
  363. }
  364. // fix min and max location with margin
  365. // Data across 180 longitude
  366. bool bAcross180 = maxLng > 90 && maxLng < 180 && minLng < -90 && minLng > -180;
  367. if(bAcross180)
  368. {
  369. minLng += MeshMargin.Right;
  370. maxLng -= MeshMargin.Left;
  371. }
  372. else
  373. {
  374. minLng -= MeshMargin.Left;
  375. maxLng += MeshMargin.Right;
  376. if(minLng < -180) minLng += 360;
  377. if(maxLng > 180) maxLng -= 360;
  378. }
  379. minLat -= MeshMargin.Bottom;
  380. maxLat += MeshMargin.Top;
  381. minLat = FMath::Max(-90, minLat);
  382. maxLat = FMath::Min(90, maxLat);
  383. ComputeLeftUpCornerESUTransform(minLng, maxLat);
  384. float xDeg = FMath::Abs(maxLng - minLng);
  385. if(bAcross180)
  386. {
  387. xDeg = 360 - xDeg;
  388. }
  389. float yDeg = FMath::Abs(maxLat - minLat);
  390. FVector leftTop = GetActorTransform().InverseTransformPosition(ConvertGeoGraphicToEngine(FVector(minLng, maxLat, HeatmapAltitude)));
  391. FVector rightTop = ComputeGeoShift(leftTop, xDeg, 0);
  392. FVector leftBottom = ComputeGeoShift(leftTop, 0, -yDeg);
  393. FVector rightBottom = ComputeGeoShift(leftTop, xDeg, -yDeg);
  394. double xLen = FVector::Dist(leftTop, rightTop);
  395. double yLen = FVector::Dist(leftTop, leftBottom);
  396. TMap<FVector2D, float> rlt2;
  397. // 计算textureSize
  398. if(xLen > yLen)
  399. {
  400. TextureSize = FVector2D((int32)(DefaultTextureSizeBase * xLen / yLen), DefaultTextureSizeBase);
  401. }
  402. else
  403. {
  404. TextureSize = FVector2D(DefaultTextureSizeBase, (int32)(DefaultTextureSizeBase * yLen / xLen));
  405. }
  406. // Map geo points to texture coordinates
  407. float xPercent = 0;
  408. for(auto it : map)
  409. {
  410. FVector& loc = it.Key;
  411. // When across 180 longitude, maxLng is on the west side. We should compute the distance to maxLng.
  412. if(bAcross180)
  413. {
  414. // Current pos and maxLng on the west of 180
  415. if(loc.X * maxLng > 0)
  416. {
  417. xPercent = FMath::Abs((loc.X - maxLng) / xDeg);
  418. }
  419. // Current pos on the east of 180
  420. else
  421. {
  422. xPercent = (360 - FMath::Abs(loc.X - maxLng)) / xDeg;
  423. }
  424. }
  425. else
  426. {
  427. xPercent = FMath::Abs((loc.X - minLng) / xDeg);
  428. }
  429. // xPercent = FMath::Min(xPercent, 1);
  430. // xPercent = FMath::Max(xPercent, 0);
  431. float x = xPercent * TextureSize.X;
  432. float yPercent = FMath::Abs((loc.Y - maxLat) / yDeg);
  433. // yPercent = FMath::Max(yPercent, 1);
  434. // yPercent = FMath::Max(yPercent, 0);
  435. float y = yPercent * TextureSize.Y;
  436. rlt2.Add(FVector2D(x, y), it.Value);
  437. }
  438. GeographicSize = FVector2D(xDeg, yDeg);
  439. return rlt2;
  440. }
  441. bool ADynamicHeatmap::LoadJsonFileToGeographicData(const FString& Fullpath, TMap<FVector, float>& OutMap, const FString& LngName, const FString& LatName, const FString& ValueName, bool bStringCoord, bool bStringValue)
  442. {
  443. FString JsonString;
  444. if(!FFileHelper::LoadFileToString(JsonString, *Fullpath))
  445. {
  446. UE_LOG(LogTemp, Error, TEXT("Load file failed!"));
  447. return false;
  448. }
  449. OutMap.Empty();
  450. TArray<TSharedPtr<FJsonValue>> OutArray;
  451. if(FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(JsonString), OutArray))
  452. {
  453. for(auto& it : OutArray)
  454. {
  455. auto& obj = it->AsObject();
  456. double lng = 1000, lat = 1000, heatValue = -1;
  457. if(bStringCoord)
  458. {
  459. FString strLng, strLat;
  460. obj->TryGetStringField(LngName, strLng);
  461. obj->TryGetStringField(LatName, strLat);
  462. lng = FCString::Atod(*strLng);
  463. lat = FCString::Atod(*strLat);
  464. }
  465. else
  466. {
  467. obj->TryGetNumberField(LngName, lng);
  468. obj->TryGetNumberField(LatName, lat);
  469. }
  470. if(bStringValue)
  471. {
  472. FString strHeatValue;
  473. obj->TryGetStringField(ValueName, strHeatValue);
  474. heatValue = FCString::Atod(*strHeatValue);
  475. }
  476. else
  477. {
  478. obj->TryGetNumberField(ValueName, heatValue);
  479. }
  480. if(lng <= 180 && lng >= -180 && lat <= 90 && lat >= -90 && heatValue <= 1 && heatValue >= 0)
  481. {
  482. OutMap.Add(FVector(lng, lat, HeatmapAltitude), heatValue);
  483. }
  484. }
  485. return true;
  486. }
  487. return false;
  488. }
  489. TMap<FVector, float> ADynamicHeatmap::LoadDataTableToGeographicData(UDataTable* DataTable)
  490. {
  491. TMap<FVector, float> OutMap;
  492. if(DataTable)
  493. {
  494. for(auto& it : DataTable->GetRowNames())
  495. {
  496. auto row = DataTable->FindRow<FHeatmapData>(it, "");
  497. if(row)
  498. {
  499. OutMap.Add(FVector(row->Longitude, row->Latitude, HeatmapAltitude), row->HeatValue);
  500. }
  501. }
  502. }
  503. return OutMap;
  504. }
  505. TMap<FVector2D, float> ADynamicHeatmap::LoadDataTableToTextureCoordPointMap(UDataTable* DataTable)
  506. {
  507. TMap<FVector2D, float> OutMap;
  508. if(DataTable)
  509. {
  510. for(auto& it : DataTable->GetRowNames())
  511. {
  512. auto row = DataTable->FindRow<FTextureCoordPointData>(it, "");
  513. if(row)
  514. {
  515. OutMap.Add(FVector2D(row->TextureCoord.X, row->TextureCoord.Y), row->HeatValue);
  516. }
  517. }
  518. }
  519. return OutMap;
  520. }
  521. bool ADynamicHeatmap::CheckFileExists(const FString& FunctionName, const FString& Fullpath)
  522. {
  523. bool flag = FPaths::FileExists(Fullpath);
  524. if(!flag)
  525. {
  526. UE_LOG(LogTemp, Error, TEXT("%s: File not exist: %s"), *FunctionName, *Fullpath);
  527. }
  528. return flag;
  529. }
  530. FString ADynamicHeatmap::GetFullpath(const FString& RelativePath)
  531. {
  532. FString path = RelativePath;
  533. if(!path.StartsWith("/"))
  534. {
  535. path.InsertAt(0, "/");
  536. }
  537. return FPaths::ProjectDir() + path;
  538. }
  539. void ADynamicHeatmap::UpdateLerpScale(float scale)
  540. {
  541. LerpScale = scale;
  542. if(Material)
  543. {
  544. Material->SetScalarParameterValue(TEXT("LerpScale"), scale);
  545. }
  546. }
  547. void ADynamicHeatmap::UpdateHeightScale(float scale)
  548. {
  549. HeightScale = scale;
  550. if(Material)
  551. {
  552. Material->SetScalarParameterValue(TEXT("HeightScale"), scale);
  553. }
  554. }
  555. void ADynamicHeatmap::UpdateOpacity(float opacity)
  556. {
  557. Opacity = opacity;
  558. if(Material)
  559. {
  560. Material->SetScalarParameterValue(TEXT("Opacity"), opacity);
  561. }
  562. }
  563. void ADynamicHeatmap::UpdateZeroValueOpacity(float value)
  564. {
  565. ZeroValueOpacity = value;
  566. if(Material)
  567. {
  568. Material->SetScalarParameterValue(TEXT("ZeroValueOpacity"), value);
  569. }
  570. }
  571. void ADynamicHeatmap::UpdateEmissive(float value)
  572. {
  573. EmissiveStrength = value;
  574. if(Material)
  575. {
  576. Material->SetScalarParameterValue(TEXT("Emissive"), value);
  577. }
  578. }
  579. void ADynamicHeatmap::UpdateMaterialParameters()
  580. {
  581. this->UpdateLerpScale(LerpScale);
  582. this->UpdateHeightScale(HeightScale);
  583. this->UpdateOpacity(Opacity);
  584. this->UpdateZeroValueOpacity(ZeroValueOpacity);
  585. this->UpdateEmissive(EmissiveStrength);
  586. }
  587. void ADynamicHeatmap::RefreshHeatmap()
  588. {
  589. this->UpdateHeatmap(true, true);
  590. }
  591. void ADynamicHeatmap::RefreshMaterial()
  592. {
  593. this->UpdateMaterialParameters();
  594. }
  595. void ConvertGeoVerticesToActorSpace(const FTransform& ActorTrans, TArray<FVector>& Vertices)
  596. {
  597. for(int32 i = 0; i < Vertices.Num(); i++)
  598. {
  599. Vertices[i] = ActorTrans.InverseTransformPosition(Vertices[i]);
  600. }
  601. }
  602. void ADynamicHeatmap::UpdateGeneratedMesh(FVector2D meshSize, bool bRecreate)
  603. {
  604. if(ProceduralMapMesh->GetNumSections() == 0) bRecreate = true;
  605. float segmentX = MeshSegment.X;
  606. float segmentY = MeshSegment.Y;
  607. TArray<FVector> Vertices;
  608. TArray<int32> Triangles;
  609. TArray<FVector> Normals;
  610. TArray<FVector2D> UV0;
  611. TArray<FColor> VertexColors;
  612. TArray<FProcMeshTangent> Tangents;
  613. int32 curX = 0;
  614. int32 curY = 0;
  615. FVector curLeftTop = FVector::ZeroVector;
  616. bool bGeoData = HeatmapType == EDynamicHeatmapType::GeographicData || HeatmapType == EDynamicHeatmapType::JsonFileData;
  617. if(bGeoData)
  618. {
  619. curLeftTop = GetActorTransform().InverseTransformPosition(LeftUpCornerESUTransform.GetLocation());
  620. }
  621. FVector startLeftTop = curLeftTop;
  622. double xStep = meshSize.X / segmentX;
  623. double yStep = meshSize.Y / segmentY;
  624. double uStep = 1 / segmentX;
  625. double vStep = 1 / segmentY;
  626. int32 times = 0;
  627. FVector xForward = GetActorForwardVector();
  628. FVector yForward = GetActorRightVector();
  629. while(curY < segmentY)
  630. {
  631. while(curX < segmentX)
  632. {
  633. FVector leftTop, rightTop, leftBottom, rightBottom;
  634. leftTop = curLeftTop;
  635. if(!bGeoData)
  636. {
  637. rightTop = leftTop + xForward * xStep;
  638. leftBottom = curLeftTop + yForward * yStep;
  639. rightBottom = leftBottom + xForward * xStep;;
  640. }
  641. else
  642. {
  643. rightTop = ComputeGeoShift(leftTop, xStep, 0);
  644. leftBottom = ComputeGeoShift(leftTop, 0, -yStep);
  645. rightBottom = ComputeGeoShift(leftTop, xStep, -yStep);
  646. }
  647. int32 startIdx = Vertices.Num();
  648. TArray<FVector>t{leftTop, rightTop, leftBottom, rightBottom};
  649. Vertices += t;
  650. Triangles += {startIdx+2, startIdx+1, startIdx, startIdx+3, startIdx+1, startIdx+2};
  651. TArray<FVector> t2{FVector::ZAxisVector, FVector::ZAxisVector, FVector::ZAxisVector, FVector::ZAxisVector};
  652. if(bGeoData)
  653. {
  654. auto up = GetActorTransform().InverseTransformVector(LeftUpCornerESUTransform.GetUnitAxis(EAxis::Z));
  655. t2 = {up,up,up,up};
  656. }
  657. Normals += t2;
  658. UV0 +=
  659. {
  660. FVector2D(curX*uStep, curY*vStep),
  661. FVector2D((curX+1)*uStep, curY*vStep),
  662. FVector2D(curX*uStep, (curY+1)*vStep),
  663. FVector2D((curX+1)*uStep, (curY+1)*vStep)
  664. };
  665. curLeftTop = rightTop;
  666. ++curX;
  667. ++times;
  668. }
  669. curX = 0;
  670. ++curY;
  671. if(bGeoData)
  672. {
  673. curLeftTop = ComputeGeoShift(startLeftTop, 0, -curY * yStep);
  674. }
  675. else
  676. {
  677. curLeftTop = startLeftTop + yForward * curY * yStep;
  678. }
  679. }
  680. if(bRecreate)
  681. {
  682. ProceduralMapMesh->ClearAllMeshSections();
  683. ProceduralMapMesh->CreateMeshSection(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false);
  684. }
  685. else
  686. {
  687. ProceduralMapMesh->UpdateMeshSection(0, Vertices, Normals, UV0, VertexColors, Tangents);
  688. }
  689. }
  690. FVector ADynamicHeatmap::ComputeGeoShift(const FVector& LocalOriginPos, double LngShift, double LatShift)
  691. {
  692. auto& trans = GetActorTransform();
  693. FVector newPos;
  694. if(GetGeoReferencing())
  695. {
  696. FGeographicCoordinates OriginLoc;
  697. GetGeoReferencing()->EngineToGeographic(trans.TransformPosition(LocalOriginPos), OriginLoc);
  698. FGeographicCoordinates NewLoc = FGeographicCoordinates(OriginLoc.Longitude + LngShift, OriginLoc.Latitude + LatShift, OriginLoc.Altitude);
  699. GetGeoReferencing()->GeographicToEngine(NewLoc, newPos);
  700. }
  701. return trans.InverseTransformPosition(newPos);
  702. }
  703. TArray<FColor> ADynamicHeatmap::LoadHgtFile(const FString & Fullpath)
  704. {
  705. TArray<FColor> ColorArr;
  706. const int SIZE = 1201;
  707. std::vector<signed short int> pixelVec(SIZE * SIZE, 0);
  708. TArray64<uint8> Result;
  709. bool flag = FFileHelper::LoadFileToArray(Result, *Fullpath);
  710. if(!flag) return ColorArr;
  711. FString rlt;
  712. for (int i = 0; i < SIZE; ++i)
  713. {
  714. for (int j = 0; j < SIZE; ++j)
  715. {
  716. signed short int pixel = (Result[i * SIZE * 2 + j * 2] << 8) | Result[i * SIZE * 2 + j * 2 + 1];
  717. rlt.Append(FString::FromInt(pixel));
  718. pixelVec[i * SIZE + j] = pixel;
  719. }
  720. }
  721. signed short int min = pixelVec[0];
  722. signed short int max = pixelVec[0];
  723. for(auto& it : pixelVec)
  724. {
  725. if(min > it && it != -32768)
  726. {
  727. min = it;
  728. }
  729. else if(max < it) max = it;
  730. }
  731. float gap = FMath::Abs(max - min);
  732. for(auto& it : pixelVec)
  733. {
  734. float heatValue = it * 1.0f / gap;
  735. heatValue = FMath::Max(0, heatValue);
  736. int32 bValue = FMath::CeilToInt(heatValue * 255.0f);
  737. bValue = FMath::Min(bValue, 255);
  738. FColor color(0, 0, bValue);
  739. ColorArr.Add(color);
  740. }
  741. return ColorArr;
  742. }
  743. TArray<FColor> ADynamicHeatmap::GetColorDataFromTextureCoordPointMap(const TMap<FVector2D, float>& map, FVector2D textureSize, int32 influenceSize)
  744. {
  745. int32 sizeX = textureSize.X;
  746. int32 sizeY = textureSize.Y;
  747. std::vector<float> heightVec(sizeX * sizeY, 0);
  748. for(auto it : map)
  749. {
  750. int32 px = it.Key.X;
  751. int32 py = it.Key.Y;
  752. if(px < 0 || px >= sizeX || py < 0 || py >= sizeY) continue;
  753. float height = it.Value;
  754. int32 startX = FMath::Max(px - influenceSize, 0);
  755. int32 endX = FMath::Min(px + influenceSize, sizeX - 1);
  756. int32 startY = FMath::Max(py - influenceSize, 0);
  757. int32 endY = FMath::Min(py + influenceSize, sizeY - 1);
  758. for(int32 j = startY; j <= endY; ++j)
  759. {
  760. for(int32 i = startX; i <= endX; ++i)
  761. {
  762. int32 distance = (i - px)*(i - px) + (j - py)*(j - py);
  763. if(distance <= influenceSize * influenceSize)
  764. {
  765. float percent = 1.0f * distance / (influenceSize * influenceSize);
  766. float influence = 1 - percent;
  767. if(InfluenceCurve)
  768. {
  769. influence = InfluenceCurve->GetFloatValue(percent);
  770. }
  771. float influenceFactor = 1;
  772. if(InfluenceFactorCurve)
  773. {
  774. influenceFactor = InfluenceFactorCurve->GetFloatValue(height);
  775. }
  776. float newHeight = influence * influenceFactor * height;
  777. heightVec[j * sizeX + i] = FMath::Max(heightVec[j * sizeX + i], newHeight);
  778. }
  779. }
  780. }
  781. }
  782. TArray<FColor> rlt;
  783. for (int32 i = 0; i < heightVec.size(); ++i)
  784. {
  785. float height = heightVec[i];
  786. int32 bValue = FMath::CeilToInt(height * 255.0f);
  787. bValue = FMath::Min(bValue, 255);
  788. FColor color(0, 0, bValue);
  789. rlt.Add(color);
  790. }
  791. return rlt;
  792. }
  793. UTexture2D* ADynamicHeatmap::CreateTexture(const FVector2D& textureSize, const TArray<FColor>& colorArr)
  794. {
  795. UTexture2D* texture = UTexture2D::CreateTransient(textureSize.X, textureSize.Y);
  796. FTexture2DMipMap* MipMap = &texture->GetPlatformData()->Mips[0];
  797. FByteBulkData* ImageData = &MipMap->BulkData;
  798. uint8* RawImageData = (uint8*)ImageData->Lock(LOCK_READ_WRITE);
  799. FMemory::Memcpy(RawImageData, colorArr.GetData(), colorArr.Num()*4);
  800. ImageData->Unlock();
  801. texture->UpdateResource();
  802. return texture;
  803. }
  804. AGeoReferencingSystem* ADynamicHeatmap::GetGeoReferencing()
  805. {
  806. if(GeoReferencing == nullptr) GeoReferencing = AGeoReferencingSystem::GetGeoReferencingSystem(this);
  807. return GeoReferencing;
  808. }