711 lines
34 KiB
C++
711 lines
34 KiB
C++
|
|
// Lot STM1 (STM1-SPEC-09, B5 ; STM1-SPEC-17 : 1.4.0). Voir LPBuildCityStaticCommandlet.h.
|
||
|
|
|
||
|
|
#include "LPBuildCityStaticCommandlet.h"
|
||
|
|
|
||
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||
|
|
#include "Dom/JsonObject.h"
|
||
|
|
#include "Engine/StaticMesh.h"
|
||
|
|
#include "HAL/FileManager.h"
|
||
|
|
#include "HAL/PlatformTime.h"
|
||
|
|
#include "LPCityLandmarks.h"
|
||
|
|
#include "LPCityOsm.h"
|
||
|
|
#include "LPCityWaterMask.h"
|
||
|
|
#include "Materials/MaterialInterface.h"
|
||
|
|
#include "MeshDescription.h"
|
||
|
|
#include "Misc/FileHelper.h"
|
||
|
|
#include "Misc/PackageName.h"
|
||
|
|
#include "RawMesh.h"
|
||
|
|
#include "Serialization/JsonReader.h"
|
||
|
|
#include "Serialization/JsonSerializer.h"
|
||
|
|
#include "StaticMeshAttributes.h"
|
||
|
|
#include "StreetMap.h"
|
||
|
|
#include "StreetMapComponent.h"
|
||
|
|
#include "UObject/Package.h"
|
||
|
|
#include "UObject/SavePackage.h"
|
||
|
|
|
||
|
|
DEFINE_LOG_CATEGORY_STATIC(LogLPBuildCityStatic, Log, All);
|
||
|
|
|
||
|
|
namespace LPBuildCityStatic
|
||
|
|
{
|
||
|
|
/** Paquets autorises : pack de ville lpm_city_* ou paquet d'essai _Pilot. */
|
||
|
|
bool IsAllowedPackage(const FString& PackageName)
|
||
|
|
{
|
||
|
|
if (PackageName.StartsWith(TEXT("/Game/Heavy/StreetMap/_Pilot/")))
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
TArray<FString> Parts;
|
||
|
|
PackageName.ParseIntoArray(Parts, TEXT("/"));
|
||
|
|
return Parts.Num() >= 4 && Parts[0].StartsWith(TEXT("lpm_city_")) && Parts[1] == TEXT("StreetMap") && (Parts[2] == TEXT("Static") || Parts[2] == TEXT("Classic"));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Reglages du composant : bWant3DBuildings et bWantLitBuildings (champs proteges, ecrits par reflexion) ; 1.3.2 :
|
||
|
|
* couleur de bordure des batiments (le remplissage des murs et des toits en vaut 0,33). */
|
||
|
|
struct FBuildOptions
|
||
|
|
{
|
||
|
|
bool bWant3DBuildings = true;
|
||
|
|
bool bWantLitBuildings = true;
|
||
|
|
bool bSetBuildingColor = false;
|
||
|
|
FLinearColor BuildingBorderColor = FLinearColor(0.85f, 0.85f, 0.85f);
|
||
|
|
// 1.4.1 (STM1-DIAG-03 f) : hauteur des routes (RoadOffesetZ du plugin) ; les parcs restent a 0 (ParkFillZ code en dur).
|
||
|
|
float RoadZCm = 0.f;
|
||
|
|
};
|
||
|
|
|
||
|
|
bool SetBool(UStruct* Struct, void* Container, const TCHAR* Name, bool bValue)
|
||
|
|
{
|
||
|
|
FBoolProperty* Prop = CastField<FBoolProperty>(Struct->FindPropertyByName(Name));
|
||
|
|
if (!Prop) return false;
|
||
|
|
Prop->SetPropertyValue_InContainer(Container, bValue);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Maillage brut genere par le composant du plugin. */
|
||
|
|
UStreetMapComponent* BuildComponent(UStreetMap* Map, const FBuildOptions& Options)
|
||
|
|
{
|
||
|
|
UStreetMapComponent* Component = NewObject<UStreetMapComponent>(GetTransientPackage());
|
||
|
|
FStructProperty* SettingsProp = CastField<FStructProperty>(UStreetMapComponent::StaticClass()->FindPropertyByName(TEXT("MeshBuildSettings")));
|
||
|
|
void* Settings = SettingsProp ? SettingsProp->ContainerPtrToValuePtr<void>(Component) : nullptr;
|
||
|
|
const bool bSet = Settings
|
||
|
|
&& SetBool(SettingsProp->Struct, Settings, TEXT("bWant3DBuildings"), Options.bWant3DBuildings)
|
||
|
|
&& SetBool(SettingsProp->Struct, Settings, TEXT("bWantLitBuildings"), Options.bWantLitBuildings);
|
||
|
|
check(bSet);
|
||
|
|
if (Options.RoadZCm != 0.f)
|
||
|
|
{
|
||
|
|
FFloatProperty* RoadZProp = CastField<FFloatProperty>(SettingsProp->Struct->FindPropertyByName(TEXT("RoadOffesetZ")));
|
||
|
|
check(RoadZProp);
|
||
|
|
RoadZProp->SetPropertyValue_InContainer(Settings, Options.RoadZCm);
|
||
|
|
}
|
||
|
|
if (Options.bSetBuildingColor)
|
||
|
|
{
|
||
|
|
FStructProperty* ColorProp = CastField<FStructProperty>(SettingsProp->Struct->FindPropertyByName(TEXT("BuildingBorderLinearColor")));
|
||
|
|
check(ColorProp);
|
||
|
|
*ColorProp->ContainerPtrToValuePtr<FLinearColor>(Settings) = Options.BuildingBorderColor;
|
||
|
|
}
|
||
|
|
Component->SetStreetMap(Map, true, true);
|
||
|
|
return Component;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 1.4.0 (STM1-SPEC-17) : surfaces OSM des routes pavees (le materiau du pack choisit les paves par U > 1,5). */
|
||
|
|
bool IsPavedSurface(const FString& Surface)
|
||
|
|
{
|
||
|
|
return Surface == TEXT("sett") || Surface == TEXT("cobblestone") || Surface == TEXT("unhewn_cobblestone");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ULPBuildCityStaticCommandlet::ULPBuildCityStaticCommandlet()
|
||
|
|
{
|
||
|
|
IsClient = false;
|
||
|
|
IsServer = false;
|
||
|
|
IsEditor = true;
|
||
|
|
LogToConsole = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
int32 ULPBuildCityStaticCommandlet::Main(const FString& Params)
|
||
|
|
{
|
||
|
|
using namespace LPBuildCityStatic;
|
||
|
|
|
||
|
|
TArray<FString> Tokens, Switches;
|
||
|
|
TMap<FString, FString> ParamsMap;
|
||
|
|
ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
|
||
|
|
const FString MapPath = ParamsMap.FindRef(TEXT("StreetMap"));
|
||
|
|
const FString PackageName = ParamsMap.FindRef(TEXT("Package"));
|
||
|
|
const FString ReferencePath = ParamsMap.Contains(TEXT("Reference")) ? ParamsMap[TEXT("Reference")] : FString(TEXT("/Game/Heavy/StreetMap/Static/CentralLondon.CentralLondon"));
|
||
|
|
const FString ComparePath = ParamsMap.FindRef(TEXT("CompareStreetMap"));
|
||
|
|
const FString ReportPath = ParamsMap.FindRef(TEXT("Report"));
|
||
|
|
const bool bDryRun = Switches.Contains(TEXT("DryRun"));
|
||
|
|
// Ecart admis (en %) entre les triangles de la carte de comparaison reconstruite et ceux du maillage de reference : la
|
||
|
|
// description source du moteur ecarte les triangles degeneres du FRawMesh (bordures de longueur nulle).
|
||
|
|
const double TolerancePercent = ParamsMap.Contains(TEXT("TolerancePercent")) ? FCString::Atod(*ParamsMap[TEXT("TolerancePercent")]) : 0.0;
|
||
|
|
FBuildOptions Options;
|
||
|
|
Options.bWant3DBuildings = !Switches.Contains(TEXT("Flat"));
|
||
|
|
Options.bWantLitBuildings = !Switches.Contains(TEXT("Unlit"));
|
||
|
|
// 1.4.1 (STM1-DIAG-03 f) : -RoadZCm=<cm> eleve les routes au-dessus des parcs (meme hauteur 0 par defaut : scintillement).
|
||
|
|
Options.RoadZCm = ParamsMap.Contains(TEXT("RoadZCm")) ? FCString::Atof(*ParamsMap[TEXT("RoadZCm")]) : 0.f;
|
||
|
|
// 1.3.0 : carte classique en 3D. -Osm -Origin : etages OSM reels (sinon height / 3 m, sinon -DefaultLevels ; petits types et
|
||
|
|
// emprises sous 30 m2 sans etages : 1), batiments filtres comme la table (souterrains, layer < 0, tunnels, toits, emprises
|
||
|
|
// sous 20 m2) retires ; -BuildingsOnly : routes, parcs et eaux retires (maillage de batiments seuls, pose a part en jeu).
|
||
|
|
// Ces changements portent sur une copie transitoire de l'UStreetMap, jamais sur l'asset.
|
||
|
|
const FString OsmPath = ParamsMap.FindRef(TEXT("Osm"));
|
||
|
|
const FString OriginText = ParamsMap.FindRef(TEXT("Origin"));
|
||
|
|
const bool bBuildingsOnly = Switches.Contains(TEXT("BuildingsOnly"));
|
||
|
|
// -ExcludeHidden : retire aussi les batiments que le jeu masque (centres des monuments et du masque d'eau de la ville).
|
||
|
|
const bool bExcludeHidden = Switches.Contains(TEXT("ExcludeHidden"));
|
||
|
|
const FString MaterialPath = ParamsMap.FindRef(TEXT("Material"));
|
||
|
|
// 1.3.2 : -BuildingColor=R,G,B (octets / 255, ecrits tels quels par le plugin et relus comme du sRGB) : couleur des murs et des toits (le plugin les remplit avec
|
||
|
|
// 0,33 x BuildingBorderLinearColor) ; -SampleColors : couleurs moyennes des toits et des murs du maillage de reference.
|
||
|
|
const FString BuildingColorText = ParamsMap.FindRef(TEXT("BuildingColor"));
|
||
|
|
const int32 DefaultLevels = ParamsMap.Contains(TEXT("DefaultLevels")) ? FCString::Atoi(*ParamsMap[TEXT("DefaultLevels")]) : 6;
|
||
|
|
// 1.4.0 (STM1-SPEC-17) : -PavedOsm=<f.osm.xml> (avec -Origin) : U + 2 sur les quadrilateres des routes pavees. Couleurs de
|
||
|
|
// sommet et geometrie inchangees : la carte classique (StreetMapClassic, couleur de sommet seule) ne change pas.
|
||
|
|
const FString PavedOsmPath = ParamsMap.FindRef(TEXT("PavedOsm"));
|
||
|
|
// 1.5.1 (STM1-DIAG-06) : -PavedZCm=<cm> (avec -PavedOsm) : hauteur des routes pavees, sous les autres (-RoadZCm). Aux carrefours et
|
||
|
|
// sous les ponts, l'asphalte et les paves ne sont plus coplanaires (z-fighting de deux textures).
|
||
|
|
const bool bPavedZ = ParamsMap.Contains(TEXT("PavedZCm"));
|
||
|
|
const float PavedZCm = bPavedZ ? FCString::Atof(*ParamsMap[TEXT("PavedZCm")]) : 0.f;
|
||
|
|
|
||
|
|
TArray<FString> Report;
|
||
|
|
auto Line = [&Report](const FString& Text)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLPBuildCityStatic, Display, TEXT("%s"), *Text);
|
||
|
|
Report.Add(Text);
|
||
|
|
};
|
||
|
|
auto Finish = [&](int32 Code)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("CODE %d"), Code));
|
||
|
|
if (!ReportPath.IsEmpty())
|
||
|
|
{
|
||
|
|
FFileHelper::SaveStringArrayToFile(Report, *ReportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
|
||
|
|
}
|
||
|
|
return Code;
|
||
|
|
};
|
||
|
|
|
||
|
|
if (MapPath.IsEmpty() || PackageName.IsEmpty())
|
||
|
|
{
|
||
|
|
Line(TEXT("usage : -StreetMap=<objet> -Package=</lpm_city_<v>/StreetMap/(Static|Classic)/<Ville>> [-Reference=<objet>] [-CompareStreetMap=<objet>] [-Flat] [-Unlit] [-Osm=<f.osm.xml> -Origin=<lat>,<lon> [-DefaultLevels=6]] [-BuildingsOnly] [-ExcludeHidden] [-Material=<objet>] [-BuildingColor=R,G,B] [-SampleColors] [-PavedOsm=<f.osm.xml> -Origin=<lat>,<lon> [-PavedZCm=<cm>]] [-RoadZCm=0] [-CutBridgeRoads=<liste.json> -Origin=<lat>,<lon>] [-DryRun] [-Report=<f.md>]"));
|
||
|
|
return Finish(2);
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("# LPBuildCityStatic %s <- %s (%s) ; bWant3DBuildings=%d bWantLitBuildings=%d ; routes a %.1f cm"), *PackageName, *MapPath,
|
||
|
|
bDryRun ? TEXT("DryRun") : TEXT("ecriture"), Options.bWant3DBuildings ? 1 : 0, Options.bWantLitBuildings ? 1 : 0, Options.RoadZCm));
|
||
|
|
if (!IsAllowedPackage(PackageName) || !FPackageName::IsValidLongPackageName(PackageName))
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : paquet hors de /lpm_city_*/StreetMap/(Static|Classic)/ et de /Game/Heavy/StreetMap/_Pilot/ ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
if (FPackageName::DoesPackageExist(PackageName))
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : %s existe deja ; aucun asset ecrit"), *PackageName));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
UStreetMap* Map = LoadObject<UStreetMap>(nullptr, *MapPath);
|
||
|
|
const UStaticMesh* Reference = LoadObject<UStaticMesh>(nullptr, *ReferencePath);
|
||
|
|
if (!Map || !Reference)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : carte (%d) ou maillage de reference (%d) introuvable"), Map ? 1 : 0, Reference ? 1 : 0));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
if (!OsmPath.IsEmpty() || bBuildingsOnly || bExcludeHidden)
|
||
|
|
{
|
||
|
|
Map = DuplicateObject<UStreetMap>(Map, GetTransientPackage());
|
||
|
|
// 1.3.2 : parcs de la carte (avant leur retrait), pour masquer les musees et batiments historiques qui s'y trouvent.
|
||
|
|
TArray<TArray<FVector2D>> ParkPolygons;
|
||
|
|
TArray<FBox2D> ParkBoxes;
|
||
|
|
for (const FStreetMapPark& Park : Map->GetParks())
|
||
|
|
{
|
||
|
|
if (Park.ParkPoints.Num() < 3) continue;
|
||
|
|
ParkPolygons.Add(Park.ParkPoints);
|
||
|
|
ParkBoxes.Add(FBox2D(Park.ParkPoints));
|
||
|
|
}
|
||
|
|
if (bBuildingsOnly)
|
||
|
|
{
|
||
|
|
Map->GetRoads().Empty();
|
||
|
|
Map->GetNodes().Empty();
|
||
|
|
Map->GetWaters().Empty();
|
||
|
|
Map->GetParks().Empty();
|
||
|
|
}
|
||
|
|
if (!OsmPath.IsEmpty())
|
||
|
|
{
|
||
|
|
TArray<FString> OriginParts;
|
||
|
|
OriginText.ParseIntoArray(OriginParts, TEXT(","));
|
||
|
|
LPCityOsm::FReader Osm;
|
||
|
|
FString Error;
|
||
|
|
if (OriginParts.Num() != 2)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : -Osm demande -Origin=<lat>,<lon> ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
Osm.OriginLat = FCString::Atod(*OriginParts[0]);
|
||
|
|
Osm.OriginLon = FCString::Atod(*OriginParts[1]);
|
||
|
|
if (!LPCityOsm::Read(Osm, OsmPath, Error))
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : %s ; aucun asset ecrit"), *Error));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
Osm.Nodes.Empty();
|
||
|
|
LPCityOsm::FWayIndex Index;
|
||
|
|
Index.Build(Osm.WayCenters);
|
||
|
|
static const TSet<FString> SmallTypes = { TEXT("kiosk"), TEXT("shed"), TEXT("toilets"), TEXT("hut"), TEXT("cabin"), TEXT("garage"), TEXT("garages"), TEXT("carport") };
|
||
|
|
int32 Matched = 0, Removed = 0, Real = 0, Small = 0, Default = 0, ParkMonuments = 0;
|
||
|
|
TArray<FStreetMapBuilding>& Buildings = Map->GetBuildings();
|
||
|
|
for (int32 I = Buildings.Num() - 1; I >= 0; --I)
|
||
|
|
{
|
||
|
|
FStreetMapBuilding& Building = Buildings[I];
|
||
|
|
if (Building.BuildingPoints.Num() < 3) { Buildings.RemoveAt(I); ++Removed; continue; }
|
||
|
|
const int32 Way = Index.Find(LPCityOsm::BoxCenter(Building.BuildingPoints));
|
||
|
|
const TMap<FString, FString>* Tags = Way != INDEX_NONE ? &Osm.WayTags[Way] : nullptr;
|
||
|
|
Matched += Tags ? 1 : 0;
|
||
|
|
double Area = 0.0;
|
||
|
|
for (int32 P = 0; P < Building.BuildingPoints.Num(); ++P)
|
||
|
|
{
|
||
|
|
const FVector2D& A = Building.BuildingPoints[P];
|
||
|
|
const FVector2D& B = Building.BuildingPoints[(P + 1) % Building.BuildingPoints.Num()];
|
||
|
|
Area += A.X * B.Y - B.X * A.Y;
|
||
|
|
}
|
||
|
|
Area = FMath::Abs(Area) * 0.5;
|
||
|
|
if (LPCityOsm::IsExcludedBuilding(Tags) || Area < 20.0 * 1e4) { Buildings.RemoveAt(I); ++Removed; continue; }
|
||
|
|
if (LPCityOsm::IsMuseumOrHistoric(Tags) && LPCityOsm::InAny(ParkPolygons, ParkBoxes, LPCityOsm::BoxCenter(Building.BuildingPoints)))
|
||
|
|
{
|
||
|
|
Buildings.RemoveAt(I); ++ParkMonuments; continue;
|
||
|
|
}
|
||
|
|
int32 Levels = LPCityOsm::KnownLevels(Tags, 300.0);
|
||
|
|
if (Levels > 0) ++Real;
|
||
|
|
else if ((Tags && SmallTypes.Contains(Tags->FindRef(TEXT("building")))) || Area < 30.0 * 1e4) { Levels = 1; ++Small; }
|
||
|
|
else { Levels = DefaultLevels; ++Default; }
|
||
|
|
Building.BuildingLevels = Levels;
|
||
|
|
Building.Height = 0.0f;
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("OSM %s : batiments associes %d ; retires %d (filtres et emprises sous 20 m2) ; musees et batiments historiques de parc retires %d ; etages reels %d, petits a 1 etage %d, defaut (%d) %d ; restent %d"),
|
||
|
|
*OsmPath, Matched, Removed, ParkMonuments, Real, Small, DefaultLevels, Default, Buildings.Num()));
|
||
|
|
}
|
||
|
|
if (bExcludeHidden)
|
||
|
|
{
|
||
|
|
// Racine et ville d'apres l'objet de la carte : </racine>/<Ville>.<Ville>.
|
||
|
|
const FString MapPackage = FPackageName::ObjectPathToPackageName(MapPath);
|
||
|
|
const FString Root = FPackageName::GetLongPackagePath(MapPackage);
|
||
|
|
const FString CityName = FPackageName::GetLongPackageAssetName(MapPackage);
|
||
|
|
TArray<FVector2D> Hidden;
|
||
|
|
if (const ULPCityLandmarks* Landmarks = LoadObject<ULPCityLandmarks>(nullptr, *FString::Printf(TEXT("%s/Landmarks/%s.%s"), *Root, *CityName, *CityName)))
|
||
|
|
{
|
||
|
|
for (const FLPCityLandmark& L : Landmarks->Landmarks) Hidden.Append(L.HideCenters);
|
||
|
|
}
|
||
|
|
if (const ULPCityWaterMask* Mask = LoadObject<ULPCityWaterMask>(nullptr, *FString::Printf(TEXT("%s/Water/%s_Mask.%s_Mask"), *Root, *CityName, *CityName)))
|
||
|
|
{
|
||
|
|
Hidden.Append(Mask->Centers);
|
||
|
|
}
|
||
|
|
TArray<FStreetMapBuilding>& Buildings = Map->GetBuildings();
|
||
|
|
const int32 Before = Buildings.Num();
|
||
|
|
Buildings.RemoveAll([&Hidden](const FStreetMapBuilding& Building)
|
||
|
|
{
|
||
|
|
if (Building.BuildingPoints.Num() < 3) return false;
|
||
|
|
const FVector2D C = LPCityOsm::BoxCenter(Building.BuildingPoints);
|
||
|
|
return Hidden.ContainsByPredicate([&C](const FVector2D& H) { return FVector2D::Distance(H, C) <= 50.0; });
|
||
|
|
});
|
||
|
|
Line(FString::Printf(TEXT("batiments masques par le jeu (monuments et eau, %d centres) : %d retires"), Hidden.Num(), Before - Buildings.Num()));
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("copie transitoire de la carte : %d batiments, %d routes, %d parcs, %d eaux%s"), Map->GetBuildings().Num(), Map->GetRoads().Num(),
|
||
|
|
Map->GetParks().Num(), Map->GetWaters().Num(), bBuildingsOnly ? TEXT(" (batiments seuls)") : TEXT("")));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!BuildingColorText.IsEmpty())
|
||
|
|
{
|
||
|
|
TArray<FString> Rgb;
|
||
|
|
BuildingColorText.ParseIntoArray(Rgb, TEXT(","));
|
||
|
|
if (Rgb.Num() != 3)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : -BuildingColor=R,G,B attendu ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
const FLinearColor Fill(FCString::Atof(*Rgb[0]), FCString::Atof(*Rgb[1]), FCString::Atof(*Rgb[2]), 1.f);
|
||
|
|
Options.bSetBuildingColor = true;
|
||
|
|
Options.BuildingBorderColor = FLinearColor(Fill.R / 0.33f, Fill.G / 0.33f, Fill.B / 0.33f, 1.f);
|
||
|
|
Line(FString::Printf(TEXT("couleur des batiments (murs et toits) : %.3f, %.3f, %.3f (bordure %.3f, %.3f, %.3f)"), Fill.R, Fill.G, Fill.B,
|
||
|
|
Options.BuildingBorderColor.R, Options.BuildingBorderColor.G, Options.BuildingBorderColor.B));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reference : materiaux et taille de la description source.
|
||
|
|
const FMeshDescription* RefDescription = Reference->GetMeshDescription(0);
|
||
|
|
Line(FString::Printf(TEXT("reference %s : %d materiau(x), description source %d sommets / %d triangles"), *ReferencePath,
|
||
|
|
Reference->GetStaticMaterials().Num(), RefDescription ? RefDescription->Vertices().Num() : -1, RefDescription ? RefDescription->Triangles().Num() : -1));
|
||
|
|
for (const FStaticMaterial& Material : Reference->GetStaticMaterials())
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT(" materiau de reference : %s (emplacement %s)"), *GetPathNameSafe(Material.MaterialInterface), *Material.MaterialSlotName.ToString()));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 1.3.2 : couleurs de sommet moyennes du maillage de reference, par orientation de la face (toits, murs, sol).
|
||
|
|
// 1.3.3 (COMMUN-28 S6) : -SampleMesh=<objet> echantillonne un autre maillage que la reference ; octets sRGB, UV0 et normales
|
||
|
|
// en plus (le plugin ecrit des octets sans conversion ; la description source les relit en lineaire avec conversion sRGB).
|
||
|
|
const UStaticMesh* SampleMesh = ParamsMap.Contains(TEXT("SampleMesh")) ? LoadObject<UStaticMesh>(nullptr, *ParamsMap[TEXT("SampleMesh")]) : Reference;
|
||
|
|
const FMeshDescription* SampleDescription = SampleMesh ? SampleMesh->GetMeshDescription(0) : nullptr;
|
||
|
|
if (Switches.Contains(TEXT("SampleColors")) && SampleDescription)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("echantillon : %s ; materiau %s"), *SampleMesh->GetPathName(),
|
||
|
|
SampleMesh->GetStaticMaterials().Num() ? *GetPathNameSafe(SampleMesh->GetStaticMaterials()[0].MaterialInterface) : TEXT("-")));
|
||
|
|
FStaticMeshConstAttributes Attributes(*SampleDescription);
|
||
|
|
const TVertexInstanceAttributesConstRef<FVector2f> UVs = Attributes.GetVertexInstanceUVs();
|
||
|
|
FVector2f UvMin[3] = { FVector2f(1e9f), FVector2f(1e9f), FVector2f(1e9f) }, UvMax[3] = { FVector2f(-1e9f), FVector2f(-1e9f), FVector2f(-1e9f) };
|
||
|
|
double NormalZ[3] = { 0.0, 0.0, 0.0 };
|
||
|
|
const TVertexInstanceAttributesConstRef<FVector4f> Colors = Attributes.GetVertexInstanceColors();
|
||
|
|
const TVertexInstanceAttributesConstRef<FVector3f> Normals = Attributes.GetVertexInstanceNormals();
|
||
|
|
const TVertexAttributesConstRef<FVector3f> Positions = Attributes.GetVertexPositions();
|
||
|
|
FVector4f Sum[3] = { FVector4f(0), FVector4f(0), FVector4f(0) };
|
||
|
|
int64 Num[3] = { 0, 0, 0 };
|
||
|
|
TMap<uint32, int64> Histogram;
|
||
|
|
for (const FVertexInstanceID I : SampleDescription->VertexInstances().GetElementIDs())
|
||
|
|
{
|
||
|
|
const FVector3f N = Normals[I];
|
||
|
|
const float Z = Positions[SampleDescription->GetVertexInstanceVertex(I)].Z;
|
||
|
|
// 0 : toits (face vers le haut, au-dessus du sol) ; 1 : murs ; 2 : sol (routes, parcs, emprises).
|
||
|
|
const int32 Kind = FMath::Abs(N.Z) < 0.3f ? 1 : (Z > 50.f ? 0 : 2);
|
||
|
|
Sum[Kind] += Colors[I];
|
||
|
|
++Num[Kind];
|
||
|
|
NormalZ[Kind] += N.Z;
|
||
|
|
if (UVs.GetNumChannels() > 0)
|
||
|
|
{
|
||
|
|
const FVector2f Uv = UVs.Get(I, 0);
|
||
|
|
UvMin[Kind] = FVector2f::Min(UvMin[Kind], Uv);
|
||
|
|
UvMax[Kind] = FVector2f::Max(UvMax[Kind], Uv);
|
||
|
|
}
|
||
|
|
if (Kind != 2)
|
||
|
|
{
|
||
|
|
const FColor C = FLinearColor(Colors[I]).ToFColor(false);
|
||
|
|
++Histogram.FindOrAdd(C.DWColor());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const TCHAR* Names[3] = { TEXT("toits"), TEXT("murs"), TEXT("sol") };
|
||
|
|
for (int32 K = 0; K < 3; ++K)
|
||
|
|
{
|
||
|
|
const FVector4f Mean = Num[K] ? Sum[K] / float(Num[K]) : FVector4f(0);
|
||
|
|
const FColor Srgb = FLinearColor(Mean).ToFColor(true);
|
||
|
|
Line(FString::Printf(TEXT("couleurs de %s : %lld sommets ; moyenne lineaire %.3f, %.3f, %.3f, %.3f (octets sRGB %d, %d, %d) ; normale Z moyenne %.2f ; UV0 de (%.2f, %.2f) a (%.2f, %.2f)"),
|
||
|
|
Names[K], Num[K], Mean.X, Mean.Y, Mean.Z, Mean.W, Srgb.R, Srgb.G, Srgb.B, Num[K] ? NormalZ[K] / double(Num[K]) : 0.0,
|
||
|
|
UvMin[K].X, UvMin[K].Y, UvMax[K].X, UvMax[K].Y));
|
||
|
|
}
|
||
|
|
Histogram.ValueSort([](int64 A, int64 B) { return A > B; });
|
||
|
|
int32 Shown = 0;
|
||
|
|
for (const TPair<uint32, int64>& Pair : Histogram)
|
||
|
|
{
|
||
|
|
if (Shown++ >= 5) break;
|
||
|
|
FColor C;
|
||
|
|
C.DWColor() = Pair.Key;
|
||
|
|
Line(FString::Printf(TEXT(" couleur frequente (toits et murs), lineaire quantifiee : %d, %d, %d, %d ; en octets sRGB du FRawMesh : %d, %d, %d : %lld sommets"), C.R, C.G, C.B, C.A,
|
||
|
|
FLinearColor(C.R / 255.f, C.G / 255.f, C.B / 255.f).ToFColor(true).R, FLinearColor(C.R / 255.f, C.G / 255.f, C.B / 255.f).ToFColor(true).G,
|
||
|
|
FLinearColor(C.R / 255.f, C.G / 255.f, C.B / 255.f).ToFColor(true).B, Pair.Value));
|
||
|
|
}
|
||
|
|
Line(TEXT("SampleColors : aucun asset ecrit"));
|
||
|
|
return Finish(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Preuve des reglages : la carte de reference, reconstruite en memoire, doit redonner les memes triangles.
|
||
|
|
if (!ComparePath.IsEmpty())
|
||
|
|
{
|
||
|
|
UStreetMap* CompareMap = LoadObject<UStreetMap>(nullptr, *ComparePath);
|
||
|
|
if (!CompareMap)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : carte de comparaison %s introuvable"), *ComparePath));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
const UStreetMapComponent* CompareComponent = BuildComponent(CompareMap, Options);
|
||
|
|
const int32 Vertices = CompareComponent->GetRawMeshVertices().Num();
|
||
|
|
const int32 Triangles = CompareComponent->GetRawMeshIndices().Num() / 3;
|
||
|
|
const int32 RefTriangles = RefDescription ? RefDescription->Triangles().Num() : -1;
|
||
|
|
const double GapPercent = RefTriangles > 0 ? 100.0 * FMath::Abs(double(Triangles - RefTriangles)) / RefTriangles : 100.0;
|
||
|
|
const bool bSame = RefTriangles > 0 && GapPercent <= TolerancePercent;
|
||
|
|
Line(FString::Printf(TEXT("comparaison : %s reconstruite en memoire = %d sommets / %d triangles ; reference %d triangles ; ecart %.2f %% (tolerance %.2f %%) -> %s"),
|
||
|
|
*ComparePath, Vertices, Triangles, RefTriangles, GapPercent, TolerancePercent, bSame ? TEXT("CONFORME") : TEXT("DIFFERENT")));
|
||
|
|
if (!bSame)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : les reglages par defaut ne redonnent pas le maillage de reference ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Maillage brut de la carte (meme copie que OnCreateStaticMeshAssetClicked).
|
||
|
|
const double Start = FPlatformTime::Seconds();
|
||
|
|
const UStreetMapComponent* Component = BuildComponent(Map, Options);
|
||
|
|
TArray<FStreetMapVertex> RawVertices = Component->GetRawMeshVertices();
|
||
|
|
TArray<uint32> RawIndices = Component->GetRawMeshIndices();
|
||
|
|
Line(FString::Printf(TEXT("maillage brut : %d sommets, %d triangles (%.1f s)"), RawVertices.Num(), RawIndices.Num() / 3, FPlatformTime::Seconds() - Start));
|
||
|
|
if (RawVertices.Num() <= 3 || RawIndices.Num() <= 3)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : maillage vide ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
// 1.4.0 : routes pavees. Le composant ecrit les routes en premier, dans l'ordre de GetRoads(), 4 sommets par segment
|
||
|
|
// (AddThick2DLine) ; chaque route est controlee (milieu de ses deux premiers sommets = son premier point).
|
||
|
|
if (!PavedOsmPath.IsEmpty())
|
||
|
|
{
|
||
|
|
TArray<FString> OriginParts;
|
||
|
|
OriginText.ParseIntoArray(OriginParts, TEXT(","));
|
||
|
|
if (OriginParts.Num() != 2)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : -PavedOsm demande -Origin=<lat>,<lon> ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
LPCityOsm::FReader Osm;
|
||
|
|
Osm.OriginLat = FCString::Atod(*OriginParts[0]);
|
||
|
|
Osm.OriginLon = FCString::Atod(*OriginParts[1]);
|
||
|
|
Osm.bLines = true;
|
||
|
|
FString Error;
|
||
|
|
if (!LPCityOsm::Read(Osm, PavedOsmPath, Error))
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : %s ; aucun asset ecrit"), *Error));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
Osm.Nodes.Empty();
|
||
|
|
TMultiMap<FIntPoint, int32> ByFirst;
|
||
|
|
int32 PavedWays = 0;
|
||
|
|
for (int32 L = 0; L < Osm.Lines.Num(); ++L)
|
||
|
|
{
|
||
|
|
if (!IsPavedSurface(Osm.LineTags[L].FindRef(TEXT("surface")))) continue;
|
||
|
|
ByFirst.Add(LPCityOsm::Cell(Osm.Lines[L][0]), L);
|
||
|
|
++PavedWays;
|
||
|
|
}
|
||
|
|
const TArray<FStreetMapRoad>& Roads = Map->GetRoads();
|
||
|
|
int32 Base = 0, Paved = 0, BadOrder = 0;
|
||
|
|
double PavedCm = 0.0, TotalCm = 0.0;
|
||
|
|
for (const FStreetMapRoad& Road : Roads)
|
||
|
|
{
|
||
|
|
const int32 Segments = FMath::Max(0, Road.RoadPoints.Num() - 1);
|
||
|
|
if (Base + Segments * 4 > RawVertices.Num()) { ++BadOrder; break; }
|
||
|
|
double LengthCm = 0.0;
|
||
|
|
for (int32 P = 0; P + 1 < Road.RoadPoints.Num(); ++P) LengthCm += FVector2D::Distance(Road.RoadPoints[P], Road.RoadPoints[P + 1]);
|
||
|
|
TotalCm += LengthCm;
|
||
|
|
if (Segments > 0)
|
||
|
|
{
|
||
|
|
const FVector2f Mid = (FVector2f(RawVertices[Base].Position) + FVector2f(RawVertices[Base + 1].Position)) * 0.5f;
|
||
|
|
if (FVector2f::Distance(Mid, FVector2f(Road.RoadPoints[0])) > 1.f) { ++BadOrder; }
|
||
|
|
bool bPaved = false;
|
||
|
|
const FVector2D First = Road.RoadPoints[0], Last = Road.RoadPoints.Last();
|
||
|
|
for (int32 DX = -1; DX <= 1 && !bPaved; ++DX)
|
||
|
|
for (int32 DY = -1; DY <= 1 && !bPaved; ++DY)
|
||
|
|
for (auto It = ByFirst.CreateConstKeyIterator(LPCityOsm::Cell(First) + FIntPoint(DX, DY)); It && !bPaved; ++It)
|
||
|
|
{
|
||
|
|
const TArray<FVector2D>& Pts = Osm.Lines[It.Value()];
|
||
|
|
bPaved = Pts.Num() == Road.RoadPoints.Num() && FVector2D::Distance(Pts[0], First) <= 50.0 && FVector2D::Distance(Pts.Last(), Last) <= 50.0;
|
||
|
|
}
|
||
|
|
if (bPaved)
|
||
|
|
{
|
||
|
|
for (int32 V = Base; V < Base + Segments * 4; ++V)
|
||
|
|
{
|
||
|
|
RawVertices[V].TextureCoordinate.X += 2.f;
|
||
|
|
if (bPavedZ) RawVertices[V].Position.Z = PavedZCm;
|
||
|
|
}
|
||
|
|
++Paved;
|
||
|
|
PavedCm += LengthCm;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Base += Segments * 4;
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("routes pavees (%s) : voies OSM pavees %d ; routes de la carte %d, pavees %d (%.1f km sur %.1f, %.1f %%) ; sommets de routes %d ; ordre non conforme %d ; hauteur des pavees %s"),
|
||
|
|
*PavedOsmPath, PavedWays, Roads.Num(), Paved, PavedCm / 1e5, TotalCm / 1e5, TotalCm > 0.0 ? 100.0 * PavedCm / TotalCm : 0.0, Base, BadOrder,
|
||
|
|
bPavedZ ? *FString::Printf(TEXT("%.1f cm"), PavedZCm) : TEXT("celle des routes")));
|
||
|
|
if (BadOrder > 0)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : les sommets des routes ne suivent pas l'ordre attendu ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 1.7.5 (STM1-DIAG-20, option B) : -CutBridgeRoads=<liste.json> (avec -Origin) retire du dessin les routes portees par un
|
||
|
|
// modele de pont. Chaque entree de la liste qui a "road_cut_m": [longueur, largeur, axe] (m, m, degres depuis l'est vers le
|
||
|
|
// nord, boite centree sur lat / lon) coupe la ligne mediane des segments de route a moins de 30 degres de l'axe, dans la
|
||
|
|
// boite ; les morceaux hors de la boite restent (segment raccourci, ou scinde en deux). Seul le maillage change : les
|
||
|
|
// donnees du UStreetMap (voitures, noms de rues) ne sont pas touchees.
|
||
|
|
const FString CutPath = ParamsMap.FindRef(TEXT("CutBridgeRoads"));
|
||
|
|
if (!CutPath.IsEmpty())
|
||
|
|
{
|
||
|
|
TArray<FString> OriginParts;
|
||
|
|
OriginText.ParseIntoArray(OriginParts, TEXT(","));
|
||
|
|
FString JsonText;
|
||
|
|
TSharedPtr<FJsonObject> Root;
|
||
|
|
const TArray<TSharedPtr<FJsonValue>>* Entries = nullptr;
|
||
|
|
if (OriginParts.Num() != 2 || !FFileHelper::LoadFileToString(JsonText, *CutPath)
|
||
|
|
|| !FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(JsonText), Root) || !Root.IsValid()
|
||
|
|
|| !Root->TryGetArrayField(TEXT("landmarks"), Entries))
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : -CutBridgeRoads=%s illisible (liste landmarks) ou -Origin absent ; aucun asset ecrit"), *CutPath));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
struct FCutBox { FString Name; FVector2D Center, Dir, Perp; double HalfLen, HalfWid; int32 Cut = 0, Shortened = 0, Split = 0; double RemovedCm = 0.0; };
|
||
|
|
TArray<FCutBox> Boxes;
|
||
|
|
for (const TSharedPtr<FJsonValue>& Value : *Entries)
|
||
|
|
{
|
||
|
|
const TSharedPtr<FJsonObject> Obj = Value->AsObject();
|
||
|
|
const TArray<TSharedPtr<FJsonValue>>* Box = nullptr;
|
||
|
|
if (!Obj.IsValid() || !Obj->TryGetArrayField(TEXT("road_cut_m"), Box) || Box->Num() != 3) continue;
|
||
|
|
FCutBox B;
|
||
|
|
B.Name = Obj->GetStringField(TEXT("name"));
|
||
|
|
B.Center = LPCityOsm::Project(Obj->GetNumberField(TEXT("lat")), Obj->GetNumberField(TEXT("lon")), FCString::Atod(*OriginParts[0]), FCString::Atod(*OriginParts[1]));
|
||
|
|
const double AxisRad = FMath::DegreesToRadians((*Box)[2]->AsNumber());
|
||
|
|
B.Dir = FVector2D(FMath::Cos(AxisRad), -FMath::Sin(AxisRad)); // repere de la carte : Y vers le sud
|
||
|
|
B.Perp = FVector2D(FMath::Sin(AxisRad), FMath::Cos(AxisRad));
|
||
|
|
B.HalfLen = (*Box)[0]->AsNumber() * 50.0;
|
||
|
|
B.HalfWid = (*Box)[1]->AsNumber() * 50.0;
|
||
|
|
Boxes.Add(B);
|
||
|
|
}
|
||
|
|
const double MinCos = FMath::Cos(FMath::DegreesToRadians(30.0));
|
||
|
|
TSet<int32> RemovedQuads;
|
||
|
|
int32 Base = 0, BadOrder = 0;
|
||
|
|
for (const FStreetMapRoad& Road : Map->GetRoads())
|
||
|
|
{
|
||
|
|
const int32 Segments = FMath::Max(0, Road.RoadPoints.Num() - 1);
|
||
|
|
if (Base + Segments * 4 > RawVertices.Num()) { ++BadOrder; break; }
|
||
|
|
if (Segments > 0)
|
||
|
|
{
|
||
|
|
const FVector2f Mid = (FVector2f(RawVertices[Base].Position) + FVector2f(RawVertices[Base + 1].Position)) * 0.5f;
|
||
|
|
if (FVector2f::Distance(Mid, FVector2f(Road.RoadPoints[0])) > 1.f) { ++BadOrder; }
|
||
|
|
}
|
||
|
|
for (int32 S = 0; S < Segments; ++S)
|
||
|
|
{
|
||
|
|
const int32 Q = Base + S * 4;
|
||
|
|
const FVector2D A = Road.RoadPoints[S], B2 = Road.RoadPoints[S + 1];
|
||
|
|
const double Len = FVector2D::Distance(A, B2);
|
||
|
|
if (Len < 1.0) continue;
|
||
|
|
const FVector2D U = (B2 - A) / Len;
|
||
|
|
for (FCutBox& Box : Boxes)
|
||
|
|
{
|
||
|
|
if (FMath::Abs(FVector2D::DotProduct(U, Box.Dir)) < MinCos) continue;
|
||
|
|
// Liang-Barsky : intervalle [T0, T1] de la ligne mediane dans la boite.
|
||
|
|
double T0 = 0.0, T1 = 1.0;
|
||
|
|
bool bIn = true;
|
||
|
|
for (int32 Axis = 0; Axis < 2 && bIn; ++Axis)
|
||
|
|
{
|
||
|
|
const FVector2D& N = Axis == 0 ? Box.Dir : Box.Perp;
|
||
|
|
const double Half = Axis == 0 ? Box.HalfLen : Box.HalfWid;
|
||
|
|
const double P0 = FVector2D::DotProduct(A - Box.Center, N), D = FVector2D::DotProduct(B2 - A, N);
|
||
|
|
if (FMath::Abs(D) < 1e-9) { bIn = FMath::Abs(P0) <= Half; continue; }
|
||
|
|
double Ta = (-Half - P0) / D, Tb = (Half - P0) / D;
|
||
|
|
if (Ta > Tb) Swap(Ta, Tb);
|
||
|
|
T0 = FMath::Max(T0, Ta);
|
||
|
|
T1 = FMath::Min(T1, Tb);
|
||
|
|
bIn = T1 - T0 > 1.0 / Len;
|
||
|
|
}
|
||
|
|
if (!bIn) continue;
|
||
|
|
// Sommets du quadrilatere (AddThick2DLine) : Q debut gauche, Q+1 debut droite, Q+2 fin droite, Q+3 fin gauche.
|
||
|
|
auto Lerp = [&RawVertices](int32 From, int32 To, double T)
|
||
|
|
{
|
||
|
|
FStreetMapVertex V = RawVertices[From];
|
||
|
|
V.Position = FMath::Lerp(RawVertices[From].Position, RawVertices[To].Position, float(T));
|
||
|
|
V.TextureCoordinate.Y = float(T);
|
||
|
|
return V;
|
||
|
|
};
|
||
|
|
Box.RemovedCm += (T1 - T0) * Len;
|
||
|
|
if (T0 <= 0.0 && T1 >= 1.0)
|
||
|
|
{
|
||
|
|
RemovedQuads.Add(Q);
|
||
|
|
++Box.Cut;
|
||
|
|
}
|
||
|
|
else if (T0 <= 0.0)
|
||
|
|
{
|
||
|
|
const FStreetMapVertex V0 = Lerp(Q, Q + 3, T1), V1 = Lerp(Q + 1, Q + 2, T1);
|
||
|
|
RawVertices[Q] = V0;
|
||
|
|
RawVertices[Q + 1] = V1;
|
||
|
|
++Box.Shortened;
|
||
|
|
}
|
||
|
|
else if (T1 >= 1.0)
|
||
|
|
{
|
||
|
|
const FStreetMapVertex V3 = Lerp(Q, Q + 3, T0), V2 = Lerp(Q + 1, Q + 2, T0);
|
||
|
|
RawVertices[Q + 3] = V3;
|
||
|
|
RawVertices[Q + 2] = V2;
|
||
|
|
++Box.Shortened;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Morceau [T1, 1] : nouveau quadrilatere en fin de tableau ; l'original garde [0, T0].
|
||
|
|
const uint32 N0 = RawVertices.Num();
|
||
|
|
const FStreetMapVertex S0 = Lerp(Q, Q + 3, T1), S1 = Lerp(Q + 1, Q + 2, T1), S2 = RawVertices[Q + 2], S3 = RawVertices[Q + 3];
|
||
|
|
RawVertices.Append({ S0, S1, S2, S3 });
|
||
|
|
RawIndices.Append({ N0, N0 + 1, N0 + 2, N0, N0 + 2, N0 + 3 });
|
||
|
|
const FStreetMapVertex V3 = Lerp(Q, Q + 3, T0), V2 = Lerp(Q + 1, Q + 2, T0);
|
||
|
|
RawVertices[Q + 3] = V3;
|
||
|
|
RawVertices[Q + 2] = V2;
|
||
|
|
++Box.Split;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Base += Segments * 4;
|
||
|
|
}
|
||
|
|
if (BadOrder > 0)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : -CutBridgeRoads : les sommets des routes ne suivent pas l'ordre attendu ; aucun asset ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
const int32 TrianglesBefore = RawIndices.Num() / 3;
|
||
|
|
TArray<uint32> Kept;
|
||
|
|
Kept.Reserve(RawIndices.Num());
|
||
|
|
for (int32 T = 0; T + 2 < RawIndices.Num(); T += 3)
|
||
|
|
{
|
||
|
|
const uint32 First = RawIndices[T];
|
||
|
|
if (First < uint32(Base) && RemovedQuads.Contains(int32(First - First % 4))) continue;
|
||
|
|
Kept.Append({ RawIndices[T], RawIndices[T + 1], RawIndices[T + 2] });
|
||
|
|
}
|
||
|
|
RawIndices = MoveTemp(Kept);
|
||
|
|
int32 Cut = 0, Shortened = 0, Split = 0, Touched = 0;
|
||
|
|
double RemovedCm = 0.0;
|
||
|
|
for (const FCutBox& Box : Boxes)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT(" pont %s : segments retires %d, raccourcis %d, scindes %d ; %.1f m de route retires"), *Box.Name, Box.Cut, Box.Shortened, Box.Split, Box.RemovedCm / 100.0));
|
||
|
|
Cut += Box.Cut; Shortened += Box.Shortened; Split += Box.Split; RemovedCm += Box.RemovedCm;
|
||
|
|
Touched += (Box.Cut + Box.Shortened + Box.Split) > 0 ? 1 : 0;
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("routes des ponts (%s) : boites %d, ponts touches %d ; segments retires %d, raccourcis %d, scindes %d ; %.1f m retires ; triangles %d -> %d"),
|
||
|
|
*CutPath, Boxes.Num(), Touched, Cut, Shortened, Split, RemovedCm / 100.0, TrianglesBefore, RawIndices.Num() / 3));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bDryRun)
|
||
|
|
{
|
||
|
|
Line(TEXT("DryRun : aucun asset ecrit"));
|
||
|
|
return Finish(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
FRawMesh RawMesh;
|
||
|
|
for (const FStreetMapVertex& Vertex : RawVertices)
|
||
|
|
{
|
||
|
|
RawMesh.VertexPositions.Add(Vertex.Position);
|
||
|
|
}
|
||
|
|
for (const uint32 VertexIndex : RawIndices)
|
||
|
|
{
|
||
|
|
const FStreetMapVertex& Vertex = RawVertices[VertexIndex];
|
||
|
|
RawMesh.WedgeIndices.Add(VertexIndex);
|
||
|
|
RawMesh.WedgeTangentX.Add(Vertex.TangentX);
|
||
|
|
RawMesh.WedgeTangentY.Add((Vertex.TangentX ^ Vertex.TangentZ).GetSafeNormal());
|
||
|
|
RawMesh.WedgeTangentZ.Add(Vertex.TangentZ);
|
||
|
|
RawMesh.WedgeTexCoords[0].Add(Vertex.TextureCoordinate);
|
||
|
|
RawMesh.WedgeColors.Add(Vertex.Color);
|
||
|
|
}
|
||
|
|
for (int32 Tri = 0; Tri < RawIndices.Num() / 3; ++Tri)
|
||
|
|
{
|
||
|
|
RawMesh.FaceMaterialIndices.Add(0);
|
||
|
|
RawMesh.FaceSmoothingMasks.Add(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
UPackage* Package = CreatePackage(*PackageName);
|
||
|
|
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, *FPackageName::GetLongPackageAssetName(PackageName), RF_Public | RF_Standalone);
|
||
|
|
StaticMesh->InitResources();
|
||
|
|
StaticMesh->SetLightingGuid(FGuid::NewGuid());
|
||
|
|
FStaticMeshSourceModel& SourceModel = StaticMesh->AddSourceModel();
|
||
|
|
SourceModel.BuildSettings.bRecomputeNormals = false;
|
||
|
|
SourceModel.BuildSettings.bRecomputeTangents = false;
|
||
|
|
SourceModel.BuildSettings.bRemoveDegenerates = false;
|
||
|
|
SourceModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
|
||
|
|
SourceModel.BuildSettings.bUseFullPrecisionUVs = false;
|
||
|
|
SourceModel.BuildSettings.bGenerateLightmapUVs = true;
|
||
|
|
SourceModel.BuildSettings.SrcLightmapIndex = 0;
|
||
|
|
SourceModel.BuildSettings.DstLightmapIndex = 1;
|
||
|
|
SourceModel.RawMeshBulkData->SaveRawMesh(RawMesh);
|
||
|
|
// 1.3.1 (COMMUN-27 S4) : -Material=<objet> remplace le materiau de reference. StreetMapMasked (Londres) masque les
|
||
|
|
// batiments ; la carte classique en 3D prend StreetMapClassic, celui que le jeu met sur la carte en mode classique.
|
||
|
|
UMaterialInterface* Override = MaterialPath.IsEmpty() ? nullptr : LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
|
||
|
|
if (!MaterialPath.IsEmpty() && !Override)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : materiau %s introuvable ; aucun asset ecrit"), *MaterialPath));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
for (const FStaticMaterial& Material : Reference->GetStaticMaterials())
|
||
|
|
{
|
||
|
|
StaticMesh->GetStaticMaterials().Add(FStaticMaterial(Override ? Override : Material.MaterialInterface.Get(), Material.MaterialSlotName));
|
||
|
|
}
|
||
|
|
if (Override)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("materiau impose : %s"), *Override->GetPathName()));
|
||
|
|
}
|
||
|
|
StaticMesh->SetImportVersion(EImportStaticMeshVersion::LastVersion);
|
||
|
|
const double BuildStart = FPlatformTime::Seconds();
|
||
|
|
StaticMesh->Build(/*bSilent=*/true);
|
||
|
|
StaticMesh->PostEditChange();
|
||
|
|
FAssetRegistryModule::AssetCreated(StaticMesh);
|
||
|
|
Line(FString::Printf(TEXT("Build : %.1f s ; sommets rendus LOD0 %d"), FPlatformTime::Seconds() - BuildStart, StaticMesh->GetNumVertices(0)));
|
||
|
|
|
||
|
|
FSavePackageArgs SaveArgs;
|
||
|
|
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
||
|
|
const FString FileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
|
||
|
|
if (!UPackage::SavePackage(Package, StaticMesh, *FileName, SaveArgs))
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : sauvegarde de %s"), *FileName));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("asset ecrit : %s (%s), %lld octets"), *PackageName, *FileName, IFileManager::Get().FileSize(*FileName)));
|
||
|
|
return Finish(0);
|
||
|
|
}
|