Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static bool TryProject(
IDefaultMaterialResolver materialResolver,
Action<string>? progressReporter,
CancellationToken cancellationToken,
out ImportedCityObject? heightMapCityObject)
out TerrainGridProjectedCityObject? heightMapCityObject)
{
cancellationToken.ThrowIfCancellationRequested();
heightMapCityObject = null;
Expand Down Expand Up @@ -151,7 +151,17 @@ internal static bool TryProject(
Z = slotPosition.Z + centerZ,
};

heightMapCityObject = new ImportedCityObject(
TerrainGridGeometry geometry = new(
Width: width,
Height: height,
Size: new Float2(extentX, extentZ),
MinHeight: minHeight,
MaxHeight: maxHeight,
HeightSamples: localHeights,
SampleCoverage: sampleCoverage,
UvScale: heightMapUvScale,
UvOffset: heightMapUvOffset);
ImportedCityObject projectedCityObject = new(
ObjectKey: cityObject.SlotKey,
DisplayName: cityObject.DisplayName,
PackageName: cityObject.PackageName,
Expand All @@ -160,18 +170,10 @@ internal static bool TryProject(
Transform: new Transform3D(
ToContractFloat3(adjustedSlotPosition),
ToContractQuaternion(GridMeshTerrainRotation)),
Geometry: new TerrainGridGeometry(
Width: width,
Height: height,
Size: new Float2(extentX, extentZ),
MinHeight: minHeight,
MaxHeight: maxHeight,
HeightSamples: localHeights,
SampleCoverage: sampleCoverage,
UvScale: heightMapUvScale,
UvOffset: heightMapUvOffset),
Geometry: geometry,
Materials: materials,
SourceFileRelativePath: cityObject.SourceFileRelativePath);
heightMapCityObject = new TerrainGridProjectedCityObject(projectedCityObject, geometry);
return true;
}

Expand Down Expand Up @@ -375,3 +377,7 @@ private sealed record DemTerrainGridBounds(
double MinZ,
double MaxZ);
}

internal sealed record TerrainGridProjectedCityObject(
ImportedCityObject CityObject,
TerrainGridGeometry Geometry);
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ internal static ImportedCityObject ProjectTerrainMeshModeCityObject(
materialResolver,
progressReporter,
cancellationToken,
out ImportedCityObject? heightMapCityObject);
out TerrainGridProjectedCityObject? heightMapCityObject);
if (!hasGrid)
{
return request.TerrainMeshMode == TerrainMeshMode.Dynamic
Expand All @@ -261,24 +261,23 @@ internal static ImportedCityObject ProjectTerrainMeshModeCityObject(

if (request.TerrainMeshMode == TerrainMeshMode.Grid)
{
return heightMapCityObject!;
return heightMapCityObject!.CityObject;
}

ImportedCityObject staticCityObject = CityGmlTriangleMeshCityObjectProjection.Project(
TriangleMeshProjectedCityObject staticCityObject = CityGmlTriangleMeshCityObjectProjection.ProjectTriangleMesh(
cityObject,
globalOriginPoint,
globalCartesian,
demTerrainTextureOverlay,
materialResolver);
TriangleMeshGeometry staticMesh = AssertTriangleMeshGeometry(staticCityObject);
TriangleMeshGeometry rebasedStaticMesh = TriangleMeshTransformRebaser.Rebase(
staticMesh,
staticCityObject.Transform,
heightMapCityObject!.Transform);
return heightMapCityObject with
staticCityObject.Geometry,
staticCityObject.CityObject.Transform,
heightMapCityObject!.CityObject.Transform);
return heightMapCityObject.CityObject with
{
Geometry = new DynamicTerrainGeometry(rebasedStaticMesh, AssertTerrainGridGeometry(heightMapCityObject)),
Materials = staticCityObject.Materials,
Geometry = new DynamicTerrainGeometry(rebasedStaticMesh, heightMapCityObject.Geometry),
Materials = staticCityObject.CityObject.Materials,
};
}

Expand Down Expand Up @@ -452,18 +451,6 @@ private static Float3 CreateScenePosition(
cartesian);
}

private static TriangleMeshGeometry AssertTriangleMeshGeometry(ImportedCityObject cityObject)
{
return cityObject.Geometry as TriangleMeshGeometry
?? throw new InvalidOperationException("Dynamic terrain static variant must be projected as a triangle mesh.");
}

private static TerrainGridGeometry AssertTerrainGridGeometry(ImportedCityObject cityObject)
{
return cityObject.Geometry as TerrainGridGeometry
?? throw new InvalidOperationException("Dynamic terrain grid variant must be projected as a terrain grid.");
}

private static bool HasRenderableGeometry(ImportedCityObject cityObject)
{
return cityObject.Geometry switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,24 @@ internal static HashSet<string> GetCulledSurfaceIdsBeforeProjection(
return [];
}

SurfaceProjectionInfo[] candidates = surfaces
.Select(surface => CreateSurfaceProjectionInfo(surface, cityObjectOrigin, cityObjectCartesian))
.Where(static info => info.MinimumY.HasValue && info.MaximumY.HasValue)
.ToArray();
SurfaceProjectionInfo[] candidates = CreateSurfaceProjectionInfos(
surfaces,
cityObjectOrigin,
cityObjectCartesian);

if (candidates.Length == 0)
{
return [];
}

double objectMinimumY = candidates.Min(static info => info.MinimumY!.Value);
double objectMaximumY = candidates.Max(static info => info.MaximumY!.Value);
double objectMinimumY = candidates.Min(static info => info.MinimumY);
double objectMaximumY = candidates.Max(static info => info.MaximumY);

return candidates
.Where(static info => !info.IsGeneratedLod1RoofSurface)
.Where(static info => info.IsNearHorizontal)
.Where(info => info.MaximumY!.Value <= objectMinimumY + BuildingBottomCullBandMeters)
.Where(info => objectMaximumY > info.MaximumY!.Value + BuildingBottomCullBandMeters)
.Where(info => info.MaximumY <= objectMinimumY + BuildingBottomCullBandMeters)
.Where(info => objectMaximumY > info.MaximumY + BuildingBottomCullBandMeters)
.Select(static info => info.PolygonId)
.ToHashSet(StringComparer.Ordinal);
}
Expand All @@ -74,10 +74,10 @@ internal static HashSet<string> GetCulledSurfaceIdsBeforeProjection(
return null;
}

SurfaceProjectionInfo[] surfaceInfos = surfaces
.Select(surface => CreateSurfaceProjectionInfo(surface, cityObjectOrigin, cityObjectCartesian))
.Where(static info => info.MinimumY.HasValue && info.MaximumY.HasValue)
.ToArray();
SurfaceProjectionInfo[] surfaceInfos = CreateSurfaceProjectionInfos(
surfaces,
cityObjectOrigin,
cityObjectCartesian);
if (surfaceInfos.Length == 0)
{
return null;
Expand Down Expand Up @@ -118,43 +118,63 @@ internal static HashSet<string> GetCulledSurfaceIdsBeforeProjection(
return ComputePolygonNormal(positions);
}

private static SurfaceProjectionInfo CreateSurfaceProjectionInfo(
ParsedSurface surface,
private static SurfaceProjectionInfo[] CreateSurfaceProjectionInfos(
IEnumerable<ParsedSurface> surfaces,
GeodeticPoint cityObjectOrigin,
LocalCartesian? cityObjectCartesian)
{
List<SurfaceProjectionInfo> infos = [];
foreach (ParsedSurface surface in surfaces)
{
if (TryCreateSurfaceProjectionInfo(surface, cityObjectOrigin, cityObjectCartesian, out SurfaceProjectionInfo info))
{
infos.Add(info);
}
}

return [.. infos];
}

private static bool TryCreateSurfaceProjectionInfo(
ParsedSurface surface,
GeodeticPoint cityObjectOrigin,
LocalCartesian? cityObjectCartesian,
out SurfaceProjectionInfo info)
{
info = default;
Float3[] positions = surface.Vertices
.Select(point => CreateScenePosition(point, cityObjectOrigin, cityObjectCartesian))
.ToArray();
if (positions.Length == 0)
{
return new SurfaceProjectionInfo(surface.PolygonId, null, null, false, IsGeneratedLod1RoofSurface(surface.PolygonId));
return false;
}

Float3? normal = ComputePolygonNormal(positions);
bool isNearHorizontal = normal is not null && Math.Abs(normal.Y) >= 0.98;

return new SurfaceProjectionInfo(
info = new SurfaceProjectionInfo(
surface.PolygonId,
positions.Min(static position => position.Y),
positions.Max(static position => position.Y),
isNearHorizontal,
IsGeneratedLod1RoofSurface(surface.PolygonId));
return true;
}

private static (double MinimumY, double MaximumY) ResolveFacadeUvVerticalRange(
IReadOnlyList<SurfaceProjectionInfo> contextSurfaceInfos,
IReadOnlyList<SurfaceProjectionInfo> allSurfaceInfos)
{
double minimumY = contextSurfaceInfos.Min(static info => info.MinimumY!.Value);
double maximumY = contextSurfaceInfos.Max(static info => info.MaximumY!.Value);
double minimumY = contextSurfaceInfos.Min(static info => info.MinimumY);
double maximumY = contextSurfaceInfos.Max(static info => info.MaximumY);
if (maximumY - minimumY > 1e-6 || contextSurfaceInfos.Count == allSurfaceInfos.Count)
{
return (minimumY, maximumY);
}

double fallbackMinimumY = allSurfaceInfos.Min(static info => info.MinimumY!.Value);
double fallbackMaximumY = allSurfaceInfos.Max(static info => info.MaximumY!.Value);
double fallbackMinimumY = allSurfaceInfos.Min(static info => info.MinimumY);
double fallbackMaximumY = allSurfaceInfos.Max(static info => info.MaximumY);
return fallbackMaximumY - fallbackMinimumY > maximumY - minimumY
? (fallbackMinimumY, fallbackMaximumY)
: (minimumY, maximumY);
Expand Down Expand Up @@ -215,8 +235,8 @@ private static Float3 CreateScenePosition(

private readonly record struct SurfaceProjectionInfo(
string PolygonId,
double? MinimumY,
double? MaximumY,
double MinimumY,
double MaximumY,
bool IsNearHorizontal,
bool IsGeneratedLod1RoofSurface);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ internal static ImportedCityObject Project(
LocalCartesian? globalCartesian,
TerrainTextureOverlay? demTerrainTextureOverlay,
IDefaultMaterialResolver materialResolver)
{
return ProjectTriangleMesh(
cityObject,
globalOriginPoint,
globalCartesian,
demTerrainTextureOverlay,
materialResolver).CityObject;
}

internal static TriangleMeshProjectedCityObject ProjectTriangleMesh(
ParsedCityObject cityObject,
GeodeticPoint globalOriginPoint,
LocalCartesian? globalCartesian,
TerrainTextureOverlay? demTerrainTextureOverlay,
IDefaultMaterialResolver materialResolver)
{
double? geometryHeightMeters = cityObject.GeometryHeightMeters
?? ResolveGeometryHeightMeters(cityObject.Surfaces);
Expand Down Expand Up @@ -104,16 +119,18 @@ .. CityGmlSurfaceMaterialResolver.ResolveSurfaces(
materials.Add(CityGmlSurfaceMaterialResolver.CreateMaterialBinding(cityObject.ActualMeshCode, representativeSurface, materialIndex));
}

return new ImportedCityObject(
TriangleMeshGeometry geometry = new(new ImportedMesh(vertices.ToArray(), submeshes.ToArray()));
ImportedCityObject projectedCityObject = new(
ObjectKey: cityObject.SlotKey,
DisplayName: cityObject.DisplayName,
PackageName: cityObject.PackageName,
ActualMeshCode: cityObject.ActualMeshCode,
LodLevel: cityObject.LodLevel,
Transform: new Transform3D(ToContractFloat3(slotPosition)),
Mesh: new ImportedMesh(vertices.ToArray(), submeshes.ToArray()),
Geometry: geometry,
Materials: materials,
SourceFileRelativePath: cityObject.SourceFileRelativePath);
return new TriangleMeshProjectedCityObject(projectedCityObject, geometry);
}

private static GeodeticPoint ResolveCityObjectOrigin(ParsedCityObject cityObject)
Expand Down Expand Up @@ -177,3 +194,7 @@ private static Float3 CreateScenePosition(

private static Float3 ToContractFloat3(Float3 value) => new(value.X, value.Y, value.Z);
}

internal sealed record TriangleMeshProjectedCityObject(
ImportedCityObject CityObject,
TriangleMeshGeometry Geometry);
Loading
Loading