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
141 changes: 141 additions & 0 deletions src/ExternalTextureHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using SharpGLTF.Schema2;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace i3dm.export;

internal static class ExternalTextureHelper
{
public static WriteSettings ConfigureExternalTextureUris(ModelRoot model, Dictionary<string, string> externalTextures, string outputDirectory, bool suppressSatelliteWrite = false)
{
var relativeUrisUsed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

foreach (var image in model.LogicalImages)
{
var relativeUri = ResolveRelativeUriForImage(image, externalTextures);
if (string.IsNullOrWhiteSpace(relativeUri)) continue;

image.AlternateWriteFileName = relativeUri;
relativeUrisUsed.Add(relativeUri);
}

EnsureOutputDirectories(outputDirectory, relativeUrisUsed);
return CreateSatelliteWriteSettings(suppressSatelliteWrite);
}

public static MemoryStream WriteGlbToStream(ModelRoot model, WriteSettings writeSettings)
{
var stream = new MemoryStream();
model.WriteGLB(stream, writeSettings);
stream.Position = 0;
return stream;
}

public static void CollectExternalTextures(Dictionary<string, string> externalTextures, string modelPath, ModelRoot modelRoot)
{
if (externalTextures == null) return;

foreach (var image in modelRoot.LogicalImages)
{
if (!TryGetExternalTextureReference(image, modelPath, out var absoluteSourcePath, out var relativeTextureUri)) continue;
externalTextures[absoluteSourcePath] = relativeTextureUri;
}
}

public static bool TryGetExternalTextureReference(Image image, string modelPath, out string absoluteSourcePath, out string relativeTextureUri)
{
absoluteSourcePath = null;
relativeTextureUri = null;

if (image.Content.IsEmpty) return false;
var sourcePath = image.Content.SourcePath;
if (string.IsNullOrWhiteSpace(sourcePath)) return false;

var modelDirectory = Path.GetDirectoryName(modelPath) ?? string.Empty;
var modelName = Path.GetFileNameWithoutExtension(modelPath);
absoluteSourcePath = GetAbsoluteTexturePath(sourcePath, modelDirectory);
relativeTextureUri = $"textures/{modelName}/{Path.GetFileName(absoluteSourcePath)}";
return true;
}

public static string ResolveRelativeUriForImage(Image image, Dictionary<string, string> externalTextures)
{
if (image.Content.IsEmpty) return null;
var sourcePath = image.Content.SourcePath;
if (string.IsNullOrWhiteSpace(sourcePath)) return null;

var fileName = Path.GetFileName(sourcePath);
var matches = externalTextures
.Where(kvp => Path.GetFileName(kvp.Key).Equals(fileName, StringComparison.OrdinalIgnoreCase))
.Select(kvp => kvp.Value)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();

return matches.Count == 1 ? matches[0] : $"textures/_shared/{fileName}";
}

public static void EnsureOutputDirectories(string outputDirectory, IEnumerable<string> relativeUris)
{
if (string.IsNullOrWhiteSpace(outputDirectory)) return;

foreach (var rel in relativeUris)
{
var fsRel = rel.Replace('/', Path.DirectorySeparatorChar);
var dir = Path.GetDirectoryName(Path.Combine(outputDirectory, fsRel));
if (!string.IsNullOrWhiteSpace(dir))
{
Directory.CreateDirectory(dir);
}
}
}

public static void CopyTextureIfMissing(string outputDirectory, string absoluteSourcePath, string relativeTextureUri)
{
var destination = Path.Combine(outputDirectory, relativeTextureUri.Replace('/', Path.DirectorySeparatorChar));
var destinationDirectory = Path.GetDirectoryName(destination);
if (!string.IsNullOrWhiteSpace(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}

if (!File.Exists(destination))
{
File.Copy(absoluteSourcePath, destination);
}
}

public static void CopyExternalTextures(string outputDirectory, IReadOnlyDictionary<string, string> externalTextures, ISet<string> copiedDestinations = null)
{
foreach (var texture in externalTextures)
{
var destination = Path.Combine(outputDirectory, texture.Value.Replace('/', Path.DirectorySeparatorChar));
if (copiedDestinations != null && !copiedDestinations.Add(destination)) continue;
CopyTextureIfMissing(outputDirectory, texture.Key, texture.Value);
}
}

private static WriteSettings CreateSatelliteWriteSettings(bool suppressSatelliteWrite)
{
var settings = new WriteSettings
{
ImageWriting = ResourceWriteMode.SatelliteFile
};

if (suppressSatelliteWrite)
{
settings.ImageWriteCallback = (ctx, assetName, image) => assetName;
}

return settings;
}

private static string GetAbsoluteTexturePath(string sourcePath, string modelDirectory)
{
if (string.IsNullOrWhiteSpace(sourcePath)) return sourcePath;
if (Path.IsPathRooted(sourcePath)) return Path.GetFullPath(sourcePath);
if (string.IsNullOrEmpty(modelDirectory)) return Path.GetFullPath(sourcePath);
return Path.GetFullPath(Path.Combine(modelDirectory, sourcePath));
}
}
66 changes: 2 additions & 64 deletions src/GPUTileHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,8 @@ public static void SaveGPUTile(string filePath, List<Instance> instances, bool U
return;
}

var relativeUrisUsed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

foreach (var image in model.LogicalImages)
{
if (image.Content.IsEmpty) continue;

var sourcePath = image.Content.SourcePath;
if (string.IsNullOrWhiteSpace(sourcePath)) continue; // embedded images stay embedded

var fileName = Path.GetFileName(sourcePath);

var matches = externalTextures
.Where(kvp => Path.GetFileName(kvp.Key).Equals(fileName, StringComparison.OrdinalIgnoreCase))
.Select(kvp => kvp.Value)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();

var relativeUri = matches.Count == 1 ? matches[0] : $"textures/_shared/{fileName}";

image.AlternateWriteFileName = relativeUri;
relativeUrisUsed.Add(relativeUri);
}

var outputDirectory = Path.GetDirectoryName(filePath) ?? string.Empty;
foreach (var rel in relativeUrisUsed)
{
var fsRel = rel.Replace('/', Path.DirectorySeparatorChar);
var dir = Path.GetDirectoryName(Path.Combine(outputDirectory, fsRel));
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
}

var writeSettings = new WriteSettings { ImageWriting = ResourceWriteMode.SatelliteFile };
var writeSettings = ExternalTextureHelper.ConfigureExternalTextureUris(model, externalTextures, outputDirectory);
model.SaveGLB(filePath, writeSettings);
}

Expand Down Expand Up @@ -149,7 +119,7 @@ private static SceneBuilder AddModels(IEnumerable<Instance> instances, Point tra
var modelPath = (string)model;
var modelRoot = ModelRoot.Load(modelPath);

CollectExternalTextures(externalTextures, modelPath, modelRoot);
ExternalTextureHelper.CollectExternalTextures(externalTextures, modelPath, modelRoot);

var meshNodeCount = AddModelInstancesToScene(sceneBuilder, instances, UseScaleNonUniform, translation, modelPath, modelRoot);
if (meshNodeCountsByModel != null) meshNodeCountsByModel[modelPath] = meshNodeCount;
Expand Down Expand Up @@ -212,38 +182,6 @@ private static SceneBuilder GetSceneBuilder(IMeshBuilder<MaterialBuilder> meshBu
return sceneBuilder;
}

private static void CollectExternalTextures(Dictionary<string, string> externalTextures, string modelPath, ModelRoot modelRoot)
{
if (externalTextures == null) return;

var modelName = Path.GetFileNameWithoutExtension(modelPath);
var modelDirectory = Path.GetDirectoryName(modelPath) ?? string.Empty;

foreach (var image in modelRoot.LogicalImages)
{
if (image.Content.IsEmpty) continue;
var sourcePath = image.Content.SourcePath;
if (string.IsNullOrWhiteSpace(sourcePath)) continue;

var absoluteSourcePath = GetAbsoluteTexturePath(sourcePath, modelDirectory);
var fileName = Path.GetFileName(absoluteSourcePath);

externalTextures[absoluteSourcePath] = $"textures/{modelName}/{fileName}";
}
}


private static string GetAbsoluteTexturePath(string sourcePath, string modelDirectory)
{
if (string.IsNullOrWhiteSpace(sourcePath)) return sourcePath;

if (Path.IsPathRooted(sourcePath)) return Path.GetFullPath(sourcePath);

if (string.IsNullOrEmpty(modelDirectory)) return Path.GetFullPath(sourcePath);

return Path.GetFullPath(Path.Combine(modelDirectory, sourcePath));
}

private static AffineTransform GetInstanceTransform(Instance instance, bool UseScaleNonUniform, Point translation)
{
var point = (Point)instance.Position;
Expand Down
21 changes: 8 additions & 13 deletions src/ImplicitTiling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public static List<Tile> GenerateTiles(Options o, NpgsqlConnection conn, Boundin
}
else
{
var bytes = CreateTile(o, instances, useGpuInstancing, useI3dm);
SaveTile(contentDirectory, tile, bytes, useGpuInstancing, useI3dm, instances, (bool)o.UseExternalModel);
var bytes = CreateTile(o, instances, useGpuInstancing, useI3dm, contentDirectory);
SaveTile(contentDirectory, tile, bytes, useGpuInstancing, useI3dm);
}
}
else
Expand Down Expand Up @@ -100,8 +100,8 @@ public static List<Tile> GenerateTiles(Options o, NpgsqlConnection conn, Boundin
else
{
var instances = InstancesRepository.GetInstances(conn, o.Table, o.GeometryColumn, bbox, source_epsg, where, (bool)o.UseScaleNonUniform, useGpuInstancing, keepProjection);
var bytes = CreateTile(o, instances, useGpuInstancing, useI3dm);
SaveTile(contentDirectory, tile, bytes, useGpuInstancing, useI3dm, instances, (bool)o.UseExternalModel);
var bytes = CreateTile(o, instances, useGpuInstancing, useI3dm, contentDirectory);
SaveTile(contentDirectory, tile, bytes, useGpuInstancing, useI3dm);
}

var t1 = new Tile(tile.Z, tile.X, tile.Y);
Expand All @@ -119,7 +119,7 @@ private static void SaveGpuTile(string contentDirectory, Tile tile, List<Instanc
GPUTileHandler.SaveGPUTile(file, instances, useScaleNonUniform);
}

private static void SaveTile(string contentDirectory, Tile tile, byte[] bytes, bool useGpuInstancing, bool useI3dm, List<Instance> instances = null, bool useExternalModel = false)
private static void SaveTile(string contentDirectory, Tile tile, byte[] bytes, bool useGpuInstancing, bool useI3dm)
{
var extension = useGpuInstancing ? "glb" : "cmpt";
if (useI3dm)
Expand All @@ -130,14 +130,9 @@ private static void SaveTile(string contentDirectory, Tile tile, byte[] bytes, b
Console.Write($"\rCreating tile: {file} ");

File.WriteAllBytes(file, bytes);

if (!useGpuInstancing && !useExternalModel && instances != null && instances.Count > 0)
{
TileHandler.CopyExternalTexturesForEmbeddedModels(contentDirectory, instances);
}
}

private static byte[] CreateTile(Options o, List<Instance> instances, bool useGpuInstancing, bool useI3dm)
private static byte[] CreateTile(Options o, List<Instance> instances, bool useGpuInstancing, bool useI3dm, string contentDirectory = null)
{
byte[] tile;

Expand All @@ -148,12 +143,12 @@ private static byte[] CreateTile(Options o, List<Instance> instances, bool useGp
else if(!useI3dm)
{
// create cmpt
tile = TileHandler.GetCmptTile(instances, (bool)o.UseExternalModel, (bool)o.UseScaleNonUniform);
tile = TileHandler.GetCmptTile(instances, (bool)o.UseExternalModel, (bool)o.UseScaleNonUniform, contentDirectory);
}
else
{
// take the first model for i3dm
tile = TileHandler.GetI3dmTile(instances, (bool)o.UseExternalModel, (bool)o.UseScaleNonUniform, instances.First().Model);
tile = TileHandler.GetI3dmTile(instances, (bool)o.UseExternalModel, (bool)o.UseScaleNonUniform, instances.First().Model, contentDirectory);
}

return tile;
Expand Down
Loading