/* * @Author: namidame * @Description: A Heatmap Generation plugin based on the old HeatMapActor, Supports Heightmap, Texture Coordinate Points And Geographic Location Data. * @Date: 2024/07/27 */ #include "DynamicHeatmap.h" #include #include #include #include "TextureResource.h" #include "GeoReferencingSystem.h" #include "Engine/DataTable.h" #include "Kismet/GameplayStatics.h" #include "Kismet/KismetMathLibrary.h" #include "Dom/JsonValue.h" #include "Dom/JsonObject.h" #include "Misc/FileHelper.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonReader.h" #include "Misc/Paths.h" #define EARTH_RADIUS 6400 * 1000 * 100 // Sets default values ADynamicHeatmap::ADynamicHeatmap() : bRefreshOnConstruction(true) , TextureSize(FVector2D(2048, 2048)) , DefaultTextureSizeBase(2048) , MeshSize(FVector2D(100000, 100000)) , MeshSegment(FVector2D(50, 50)) , MeshMargin(FMargin(0.05)) , HeatmapAltitude(0) , LerpScale(2.5) , HeightScale(2500.0) , Opacity(0.7) , ZeroValueOpacity(0) , InfluenceSize(50) , EmissiveStrength(1) { RootComponent = CreateDefaultSubobject(TEXT("RootComponent")); ProceduralMapMesh = CreateDefaultSubobject(TEXT("MapMesh")); ProceduralMapMesh->SetupAttachment(RootComponent); ProceduralMapMesh->SetCastShadow(false); ProceduralMapMesh->bUseComplexAsSimpleCollision = true; } // Called when the game starts or when spawned void ADynamicHeatmap::BeginPlay() { Super::BeginPlay(); } void ADynamicHeatmap::OnConstruction(const FTransform& Transform) { Super::OnConstruction(Transform); if(bRefreshOnConstruction) { this->UpdateHeatmap(true, true); } } // Called every frame void ADynamicHeatmap::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void ADynamicHeatmap::CreateWithGrayScaleTexture(UTexture2D* texture, FVector2D meshSize, bool bUpdateMesh, bool bRecreateMesh) { if(texture == nullptr) { UE_LOG(LogTemp, Log, TEXT("%s:Assign a valid texture!"), *FString(__FUNCTION__)); } if(texture != nullptr) { GrayScaleTexture = texture; FVector2D textureSize(texture->GetSizeX(), texture->GetSizeY()); TextureSize = textureSize; } HeatmapType = EDynamicHeatmapType::GrayScaleTexture; MeshSize = meshSize; if(bUpdateMesh) { this->UpdateGeneratedMesh(meshSize, bRecreateMesh); } // create material with texture if(!HeatmapMaterial) return; UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr); mat->SetTextureParameterValue(TEXT("Texture"), texture); Material = mat; this->UpdateMaterialParameters(); // set material into map mesh ProceduralMapMesh->SetMaterial(0, mat); } void ADynamicHeatmap::CreateWithTextureCoordPointMap(const TMap& map, FVector2D textureSize, FVector2D meshSize, bool bUpdateMesh, bool bRecreateMesh) { TextureCoordPointMap = map; TArray colorArr = this->GetColorDataFromTextureCoordPointMap(map, textureSize, InfluenceSize); TextureSize = textureSize; // create map mesh if(bUpdateMesh) { this->UpdateGeneratedMesh(meshSize, bRecreateMesh); } // create material with texture if(!HeatmapMaterial) return; UTexture2D* texture = CreateTexture(TextureSize, colorArr); UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr); mat->SetTextureParameterValue(TEXT("Texture"), texture); Material = mat; this->UpdateMaterialParameters(); // set material into map mesh ProceduralMapMesh->SetMaterial(0, mat); } void ADynamicHeatmap::CreateWithTextureCoordPointMapDataTable(UDataTable* DataTable, FVector2D texture_size, FVector2D mesh_size, bool bUpdateMesh, bool bRecreateMesh) { if(DataTable == nullptr) { UE_LOG(LogTemp, Error, TEXT("Input a valid datatable!")); return; } auto map = LoadDataTableToTextureCoordPointMap(DataTable); if(map.Num() == 0) { UE_LOG(LogTemp, Error, TEXT("DataTable empty!")); return; } TextureCoordPointDataTable = DataTable; this->CreateWithTextureCoordPointMap(map, texture_size, mesh_size, bUpdateMesh, bRecreateMesh); } void ADynamicHeatmap::CreateWithGeographicData(const TMap& map, bool bUpdateMesh, bool bRecreateMesh) { if(map.Num() == 0) { UE_LOG(LogTemp, Error, TEXT("Input a valid map!")); return; } HeatmapType = EDynamicHeatmapType::GeographicData; GeographicData = map; TMap rlt = this->ConvertGeoValueMapToUnrealValueMap(map); TArray colorArr = this->GetColorDataFromTextureCoordPointMap(rlt, TextureSize, InfluenceSize); if(colorArr.IsEmpty()) { UE_LOG(LogTemp, Error, TEXT("No valid data!")); return; } // create map mesh if(bUpdateMesh) { this->UpdateGeneratedMesh(GeographicSize, bRecreateMesh); } // create material with texture if(!HeatmapMaterial) return; UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr); UTexture2D* texture = CreateTexture(TextureSize, colorArr); mat->SetTextureParameterValue(TEXT("Texture"), texture); Material = mat; this->UpdateMaterialParameters(); // set material into map mesh ProceduralMapMesh->SetMaterial(0, mat); } void ADynamicHeatmap::CreateWithGeographicDataTable(UDataTable* DataTable, bool bUpdateMesh, bool bRecreateMesh) { if(DataTable == nullptr) { UE_LOG(LogTemp, Error, TEXT("Input a valid datatable!")); return; } auto map = LoadDataTableToGeographicData(DataTable); if(map.Num() == 0) { UE_LOG(LogTemp, Error, TEXT("DataTable empty!")); return; } GeographicDataTable = DataTable; this->CreateWithGeographicData(map, bUpdateMesh, bRecreateMesh); } 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) { FString Fullpath = FilePath; if(bRelativePath) { Fullpath = GetFullpath(FilePath); } CheckFileExists(__FUNCTION__, Fullpath); bProjectRelativePath = bRelativePath; JsonFilePath = FilePath; JsonConfig = FJsonConfig(LngName, LatName, ValueName, bStringCoord, bStringValue); TMap OutMap; if(!LoadJsonFileToGeographicData(Fullpath, OutMap, LngName, LatName, ValueName, bStringCoord, bStringValue)) { return; } HeatmapType = EDynamicHeatmapType::JsonFileData; if(OutMap.Num() == 0) return; TMap rlt = this->ConvertGeoValueMapToUnrealValueMap(OutMap); TArray colorArr = this->GetColorDataFromTextureCoordPointMap(rlt, TextureSize, InfluenceSize); if(colorArr.IsEmpty()) { UE_LOG(LogTemp, Error, TEXT("No valid data!")); return; } // create map mesh if(bUpdateMesh) { this->UpdateGeneratedMesh(GeographicSize, bRecreateMesh); } // create material with texture if(!HeatmapMaterial) return; UTexture2D* texture = CreateTexture(TextureSize, colorArr); UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr); mat->SetTextureParameterValue(TEXT("Texture"), texture); Material = mat; this->UpdateMaterialParameters(); // set material into map mesh ProceduralMapMesh->SetMaterial(0, mat); } void ADynamicHeatmap::CreateWithJsonFile_Config(const FString& FilePath, bool bRelativePath, bool bUpdateMesh, bool bRecreateMesh, FJsonConfig Config) { this->CreateWithJsonFile(FilePath, bRelativePath, bUpdateMesh, bRecreateMesh, Config.LngName, Config.LatName, Config.ValueName, Config.bStringCoord, Config.bStringValue); } void ADynamicHeatmap::CreateWithHgtFile(const FString& FilePath, bool bRelativePath, FVector2D meshSize, bool bUpdateMesh, bool bRecreateMesh) { FString Fullpath = FilePath; if(bRelativePath) { Fullpath = GetFullpath(FilePath); } CheckFileExists(__FUNCTION__, Fullpath); auto colorArr = LoadHgtFile(Fullpath); bProjectRelativePath = bRelativePath; HgtFilePath = FilePath; HeatmapType = EDynamicHeatmapType::HgtFileData; TextureSize = FVector2D(1201, 1201); MeshSize = meshSize; // create map mesh if(bUpdateMesh) { this->UpdateGeneratedMesh(meshSize, bRecreateMesh); } // create material with texture if(!HeatmapMaterial) return; UMaterialInstanceDynamic* mat = UMaterialInstanceDynamic::Create(HeatmapMaterial, nullptr); UTexture2D* texture = CreateTexture(TextureSize, colorArr); mat->SetTextureParameterValue(TEXT("Texture"), texture); Material = mat; this->UpdateMaterialParameters(); // set material into map mesh ProceduralMapMesh->SetMaterial(0, mat); } void ADynamicHeatmap::UpdateMapSegment(FVector2D segment) { bool bUpdateMesh = MeshSegment == segment; bool bRecreateMesh = bUpdateMesh; MeshSegment = FVector2D(round(segment.X), round(segment.Y)); this->UpdateHeatmap(bUpdateMesh, bRecreateMesh); } void ADynamicHeatmap::UpdateMeshSize(FVector2D size) { bool bUpdateMesh = MeshSize == size; MeshSize = size; this->UpdateHeatmap(bUpdateMesh, false); } void ADynamicHeatmap::AddTextureCoordValue(const FVector2D& pos, float value, bool bUpdate) { if(HeatmapType != EDynamicHeatmapType::TextureCoordPoint) return; if(TextureCoordPointMap.Contains(pos)) { TextureCoordPointMap[pos] = value; } else { TextureCoordPointMap.Add(pos, value); } if(bUpdate) { this->CreateWithTextureCoordPointMap(TextureCoordPointMap, TextureSize, MeshSize, false, false); } } void ADynamicHeatmap::DeleteTextureCoordValue(const FVector2D& pos, bool bUpdate) { if(HeatmapType != EDynamicHeatmapType::TextureCoordPoint) return; if(TextureCoordPointMap.Contains(pos)) { TextureCoordPointMap.Remove(pos); } if(bUpdate) { this->CreateWithTextureCoordPointMap(TextureCoordPointMap, TextureSize, MeshSize, false, false); } } void ADynamicHeatmap::UpdatePointInfluenceSize(int32 size) { InfluenceSize = size; this->UpdateHeatmap(false, false); } void ADynamicHeatmap::UpdateHeatmap(bool bUpdateMesh, bool bRecreateMesh) { if(HeatmapType == EDynamicHeatmapType::GrayScaleTexture) { this->CreateWithGrayScaleTexture(GrayScaleTexture, MeshSize, bUpdateMesh, bRecreateMesh); } else if(HeatmapType == EDynamicHeatmapType::TextureCoordPoint) { if(TextureCoordPointDataTable) { this->CreateWithTextureCoordPointMapDataTable(TextureCoordPointDataTable, TextureSize, MeshSize, bUpdateMesh, bRecreateMesh); } else { this->CreateWithTextureCoordPointMap(TextureCoordPointMap, TextureSize, MeshSize, bUpdateMesh, bRecreateMesh); } } else if(HeatmapType == EDynamicHeatmapType::GeographicData) { if(GeographicDataTable) { this->CreateWithGeographicDataTable(GeographicDataTable, bUpdateMesh, bRecreateMesh); } else { this->CreateWithGeographicData(GeographicData, bUpdateMesh, bRecreateMesh); } } else if(HeatmapType == EDynamicHeatmapType::JsonFileData) { this->CreateWithJsonFile_Config(JsonFilePath, bProjectRelativePath, bUpdateMesh, bRecreateMesh, JsonConfig); } else if(HeatmapType == EDynamicHeatmapType::HgtFileData) { this->CreateWithHgtFile(HgtFilePath, bProjectRelativePath, MeshSize, bUpdateMesh, bRecreateMesh); } } FVector ADynamicHeatmap::ConvertGeoGraphicToEngine(FVector location) { FGeographicCoordinates geo(location.X, location.Y, location.Z); FVector newPos; if(GetGeoReferencing()) GetGeoReferencing()->GeographicToEngine(geo, newPos); return newPos; } void ADynamicHeatmap::ComputeLeftUpCornerESUTransform(float MinLng, float MaxLat) { FVector east, north, up; if(GetGeoReferencing()) { FGeographicCoordinates LeftUpCornerGeo = FGeographicCoordinates(MinLng, MaxLat, HeatmapAltitude); GetGeoReferencing()->GetENUVectorsAtGeographicLocation(LeftUpCornerGeo, east, north, up); FVector LeftUpCornerUe; GetGeoReferencing()->GeographicToEngine(LeftUpCornerGeo, LeftUpCornerUe); FRotator Rotation = UKismetMathLibrary::MakeRotationFromAxes(east, -north, up); LeftUpCornerESUTransform = UKismetMathLibrary::MakeTransform(LeftUpCornerUe, Rotation); } } TMap ADynamicHeatmap::ConvertGeoValueMapToUnrealValueMap(const TMap& map) { TMap rlt1; float minLng = 180, maxLng = -180, minLat = 90, maxLat = -90; for(auto it : map) { FVector loc = it.Key; loc.Z = HeatmapAltitude; FVector newPos = this->ConvertGeoGraphicToEngine(loc); rlt1.Add(newPos, it.Value); if(loc.X < minLng) minLng = loc.X; if(loc.X > maxLng) maxLng = loc.X; if(loc.Y < minLat) minLat = loc.Y; if(loc.Y > maxLat) maxLat = loc.Y; } // fix min and max location with margin // Data across 180 longitude bool bAcross180 = maxLng > 90 && maxLng < 180 && minLng < -90 && minLng > -180; if(bAcross180) { minLng += MeshMargin.Right; maxLng -= MeshMargin.Left; } else { minLng -= MeshMargin.Left; maxLng += MeshMargin.Right; if(minLng < -180) minLng += 360; if(maxLng > 180) maxLng -= 360; } minLat -= MeshMargin.Bottom; maxLat += MeshMargin.Top; minLat = FMath::Max(-90, minLat); maxLat = FMath::Min(90, maxLat); ComputeLeftUpCornerESUTransform(minLng, maxLat); float xDeg = FMath::Abs(maxLng - minLng); if(bAcross180) { xDeg = 360 - xDeg; } float yDeg = FMath::Abs(maxLat - minLat); FVector leftTop = GetActorTransform().InverseTransformPosition(ConvertGeoGraphicToEngine(FVector(minLng, maxLat, HeatmapAltitude))); FVector rightTop = ComputeGeoShift(leftTop, xDeg, 0); FVector leftBottom = ComputeGeoShift(leftTop, 0, -yDeg); FVector rightBottom = ComputeGeoShift(leftTop, xDeg, -yDeg); double xLen = FVector::Dist(leftTop, rightTop); double yLen = FVector::Dist(leftTop, leftBottom); TMap rlt2; // 计算textureSize if(xLen > yLen) { TextureSize = FVector2D((int32)(DefaultTextureSizeBase * xLen / yLen), DefaultTextureSizeBase); } else { TextureSize = FVector2D(DefaultTextureSizeBase, (int32)(DefaultTextureSizeBase * yLen / xLen)); } // Map geo points to texture coordinates float xPercent = 0; for(auto it : map) { FVector& loc = it.Key; // When across 180 longitude, maxLng is on the west side. We should compute the distance to maxLng. if(bAcross180) { // Current pos and maxLng on the west of 180 if(loc.X * maxLng > 0) { xPercent = FMath::Abs((loc.X - maxLng) / xDeg); } // Current pos on the east of 180 else { xPercent = (360 - FMath::Abs(loc.X - maxLng)) / xDeg; } } else { xPercent = FMath::Abs((loc.X - minLng) / xDeg); } // xPercent = FMath::Min(xPercent, 1); // xPercent = FMath::Max(xPercent, 0); float x = xPercent * TextureSize.X; float yPercent = FMath::Abs((loc.Y - maxLat) / yDeg); // yPercent = FMath::Max(yPercent, 1); // yPercent = FMath::Max(yPercent, 0); float y = yPercent * TextureSize.Y; rlt2.Add(FVector2D(x, y), it.Value); } GeographicSize = FVector2D(xDeg, yDeg); return rlt2; } bool ADynamicHeatmap::LoadJsonFileToGeographicData(const FString& Fullpath, TMap& OutMap, const FString& LngName, const FString& LatName, const FString& ValueName, bool bStringCoord, bool bStringValue) { FString JsonString; if(!FFileHelper::LoadFileToString(JsonString, *Fullpath)) { UE_LOG(LogTemp, Error, TEXT("Load file failed!")); return false; } OutMap.Empty(); TArray> OutArray; if(FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(JsonString), OutArray)) { for(auto& it : OutArray) { auto& obj = it->AsObject(); double lng = 1000, lat = 1000, heatValue = -1; if(bStringCoord) { FString strLng, strLat; obj->TryGetStringField(LngName, strLng); obj->TryGetStringField(LatName, strLat); lng = FCString::Atod(*strLng); lat = FCString::Atod(*strLat); } else { obj->TryGetNumberField(LngName, lng); obj->TryGetNumberField(LatName, lat); } if(bStringValue) { FString strHeatValue; obj->TryGetStringField(ValueName, strHeatValue); heatValue = FCString::Atod(*strHeatValue); } else { obj->TryGetNumberField(ValueName, heatValue); } if(lng <= 180 && lng >= -180 && lat <= 90 && lat >= -90 && heatValue <= 1 && heatValue >= 0) { OutMap.Add(FVector(lng, lat, HeatmapAltitude), heatValue); } } return true; } return false; } TMap ADynamicHeatmap::LoadDataTableToGeographicData(UDataTable* DataTable) { TMap OutMap; if(DataTable) { for(auto& it : DataTable->GetRowNames()) { auto row = DataTable->FindRow(it, ""); if(row) { OutMap.Add(FVector(row->Longitude, row->Latitude, HeatmapAltitude), row->HeatValue); } } } return OutMap; } TMap ADynamicHeatmap::LoadDataTableToTextureCoordPointMap(UDataTable* DataTable) { TMap OutMap; if(DataTable) { for(auto& it : DataTable->GetRowNames()) { auto row = DataTable->FindRow(it, ""); if(row) { OutMap.Add(FVector2D(row->TextureCoord.X, row->TextureCoord.Y), row->HeatValue); } } } return OutMap; } bool ADynamicHeatmap::CheckFileExists(const FString& FunctionName, const FString& Fullpath) { bool flag = FPaths::FileExists(Fullpath); if(!flag) { UE_LOG(LogTemp, Error, TEXT("%s: File not exist: %s"), *FunctionName, *Fullpath); } return flag; } FString ADynamicHeatmap::GetFullpath(const FString& RelativePath) { FString path = RelativePath; if(!path.StartsWith("/")) { path.InsertAt(0, "/"); } return FPaths::ProjectDir() + path; } void ADynamicHeatmap::UpdateLerpScale(float scale) { LerpScale = scale; if(Material) { Material->SetScalarParameterValue(TEXT("LerpScale"), scale); } } void ADynamicHeatmap::UpdateHeightScale(float scale) { HeightScale = scale; if(Material) { Material->SetScalarParameterValue(TEXT("HeightScale"), scale); } } void ADynamicHeatmap::UpdateOpacity(float opacity) { Opacity = opacity; if(Material) { Material->SetScalarParameterValue(TEXT("Opacity"), opacity); } } void ADynamicHeatmap::UpdateZeroValueOpacity(float value) { ZeroValueOpacity = value; if(Material) { Material->SetScalarParameterValue(TEXT("ZeroValueOpacity"), value); } } void ADynamicHeatmap::UpdateEmissive(float value) { EmissiveStrength = value; if(Material) { Material->SetScalarParameterValue(TEXT("Emissive"), value); } } void ADynamicHeatmap::UpdateMaterialParameters() { this->UpdateLerpScale(LerpScale); this->UpdateHeightScale(HeightScale); this->UpdateOpacity(Opacity); this->UpdateZeroValueOpacity(ZeroValueOpacity); this->UpdateEmissive(EmissiveStrength); } void ADynamicHeatmap::RefreshHeatmap() { this->UpdateHeatmap(true, true); } void ADynamicHeatmap::RefreshMaterial() { this->UpdateMaterialParameters(); } void ConvertGeoVerticesToActorSpace(const FTransform& ActorTrans, TArray& Vertices) { for(int32 i = 0; i < Vertices.Num(); i++) { Vertices[i] = ActorTrans.InverseTransformPosition(Vertices[i]); } } void ADynamicHeatmap::UpdateGeneratedMesh(FVector2D meshSize, bool bRecreate) { if(ProceduralMapMesh->GetNumSections() == 0) bRecreate = true; float segmentX = MeshSegment.X; float segmentY = MeshSegment.Y; TArray Vertices; TArray Triangles; TArray Normals; TArray UV0; TArray VertexColors; TArray Tangents; int32 curX = 0; int32 curY = 0; FVector curLeftTop = FVector::ZeroVector; bool bGeoData = HeatmapType == EDynamicHeatmapType::GeographicData || HeatmapType == EDynamicHeatmapType::JsonFileData; if(bGeoData) { curLeftTop = GetActorTransform().InverseTransformPosition(LeftUpCornerESUTransform.GetLocation()); } FVector startLeftTop = curLeftTop; double xStep = meshSize.X / segmentX; double yStep = meshSize.Y / segmentY; double uStep = 1 / segmentX; double vStep = 1 / segmentY; int32 times = 0; FVector xForward = GetActorForwardVector(); FVector yForward = GetActorRightVector(); while(curY < segmentY) { while(curX < segmentX) { FVector leftTop, rightTop, leftBottom, rightBottom; leftTop = curLeftTop; if(!bGeoData) { rightTop = leftTop + xForward * xStep; leftBottom = curLeftTop + yForward * yStep; rightBottom = leftBottom + xForward * xStep;; } else { rightTop = ComputeGeoShift(leftTop, xStep, 0); leftBottom = ComputeGeoShift(leftTop, 0, -yStep); rightBottom = ComputeGeoShift(leftTop, xStep, -yStep); } int32 startIdx = Vertices.Num(); TArrayt{leftTop, rightTop, leftBottom, rightBottom}; Vertices += t; Triangles += {startIdx+2, startIdx+1, startIdx, startIdx+3, startIdx+1, startIdx+2}; TArray t2{FVector::ZAxisVector, FVector::ZAxisVector, FVector::ZAxisVector, FVector::ZAxisVector}; if(bGeoData) { auto up = GetActorTransform().InverseTransformVector(LeftUpCornerESUTransform.GetUnitAxis(EAxis::Z)); t2 = {up,up,up,up}; } Normals += t2; UV0 += { FVector2D(curX*uStep, curY*vStep), FVector2D((curX+1)*uStep, curY*vStep), FVector2D(curX*uStep, (curY+1)*vStep), FVector2D((curX+1)*uStep, (curY+1)*vStep) }; curLeftTop = rightTop; ++curX; ++times; } curX = 0; ++curY; if(bGeoData) { curLeftTop = ComputeGeoShift(startLeftTop, 0, -curY * yStep); } else { curLeftTop = startLeftTop + yForward * curY * yStep; } } if(bRecreate) { ProceduralMapMesh->ClearAllMeshSections(); ProceduralMapMesh->CreateMeshSection(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false); } else { ProceduralMapMesh->UpdateMeshSection(0, Vertices, Normals, UV0, VertexColors, Tangents); } } FVector ADynamicHeatmap::ComputeGeoShift(const FVector& LocalOriginPos, double LngShift, double LatShift) { auto& trans = GetActorTransform(); FVector newPos; if(GetGeoReferencing()) { FGeographicCoordinates OriginLoc; GetGeoReferencing()->EngineToGeographic(trans.TransformPosition(LocalOriginPos), OriginLoc); FGeographicCoordinates NewLoc = FGeographicCoordinates(OriginLoc.Longitude + LngShift, OriginLoc.Latitude + LatShift, OriginLoc.Altitude); GetGeoReferencing()->GeographicToEngine(NewLoc, newPos); } return trans.InverseTransformPosition(newPos); } TArray ADynamicHeatmap::LoadHgtFile(const FString & Fullpath) { TArray ColorArr; const int SIZE = 1201; std::vector pixelVec(SIZE * SIZE, 0); TArray64 Result; bool flag = FFileHelper::LoadFileToArray(Result, *Fullpath); if(!flag) return ColorArr; FString rlt; for (int i = 0; i < SIZE; ++i) { for (int j = 0; j < SIZE; ++j) { signed short int pixel = (Result[i * SIZE * 2 + j * 2] << 8) | Result[i * SIZE * 2 + j * 2 + 1]; rlt.Append(FString::FromInt(pixel)); pixelVec[i * SIZE + j] = pixel; } } signed short int min = pixelVec[0]; signed short int max = pixelVec[0]; for(auto& it : pixelVec) { if(min > it && it != -32768) { min = it; } else if(max < it) max = it; } float gap = FMath::Abs(max - min); for(auto& it : pixelVec) { float heatValue = it * 1.0f / gap; heatValue = FMath::Max(0, heatValue); int32 bValue = FMath::CeilToInt(heatValue * 255.0f); bValue = FMath::Min(bValue, 255); FColor color(0, 0, bValue); ColorArr.Add(color); } return ColorArr; } TArray ADynamicHeatmap::GetColorDataFromTextureCoordPointMap(const TMap& map, FVector2D textureSize, int32 influenceSize) { int32 sizeX = textureSize.X; int32 sizeY = textureSize.Y; std::vector heightVec(sizeX * sizeY, 0); for(auto it : map) { int32 px = it.Key.X; int32 py = it.Key.Y; if(px < 0 || px >= sizeX || py < 0 || py >= sizeY) continue; float height = it.Value; int32 startX = FMath::Max(px - influenceSize, 0); int32 endX = FMath::Min(px + influenceSize, sizeX - 1); int32 startY = FMath::Max(py - influenceSize, 0); int32 endY = FMath::Min(py + influenceSize, sizeY - 1); for(int32 j = startY; j <= endY; ++j) { for(int32 i = startX; i <= endX; ++i) { int32 distance = (i - px)*(i - px) + (j - py)*(j - py); if(distance <= influenceSize * influenceSize) { float percent = 1.0f * distance / (influenceSize * influenceSize); float influence = 1 - percent; if(InfluenceCurve) { influence = InfluenceCurve->GetFloatValue(percent); } float influenceFactor = 1; if(InfluenceFactorCurve) { influenceFactor = InfluenceFactorCurve->GetFloatValue(height); } float newHeight = influence * influenceFactor * height; heightVec[j * sizeX + i] = FMath::Max(heightVec[j * sizeX + i], newHeight); } } } } TArray rlt; for (int32 i = 0; i < heightVec.size(); ++i) { float height = heightVec[i]; int32 bValue = FMath::CeilToInt(height * 255.0f); bValue = FMath::Min(bValue, 255); FColor color(0, 0, bValue); rlt.Add(color); } return rlt; } UTexture2D* ADynamicHeatmap::CreateTexture(const FVector2D& textureSize, const TArray& colorArr) { UTexture2D* texture = UTexture2D::CreateTransient(textureSize.X, textureSize.Y); FTexture2DMipMap* MipMap = &texture->GetPlatformData()->Mips[0]; FByteBulkData* ImageData = &MipMap->BulkData; uint8* RawImageData = (uint8*)ImageData->Lock(LOCK_READ_WRITE); FMemory::Memcpy(RawImageData, colorArr.GetData(), colorArr.Num()*4); ImageData->Unlock(); texture->UpdateResource(); return texture; } AGeoReferencingSystem* ADynamicHeatmap::GetGeoReferencing() { if(GeoReferencing == nullptr) GeoReferencing = AGeoReferencingSystem::GetGeoReferencingSystem(this); return GeoReferencing; }