highly experimental runtime model decimation + file cache adjustment to clean up processed file copies

This commit is contained in:
2026-01-02 09:54:34 +09:00
parent 44bb53023e
commit aeed8503c2
46 changed files with 9990 additions and 84 deletions

View File

@@ -481,53 +481,13 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
}
}
long totalSizeExtras = 0;
FileCacheSize = totalSize;
if (Directory.Exists(_configService.Current.CacheFolder + "/downscaled"))
{
var filesDownscaled = Directory.EnumerateFiles(_configService.Current.CacheFolder + "/downscaled").Select(f => new FileInfo(f)).OrderBy(f => f.LastAccessTime).ToList();
totalSizeExtras += GetFolderSize(Path.Combine(_configService.Current.CacheFolder, "downscaled"), token, isWine);
totalSizeExtras += GetFolderSize(Path.Combine(_configService.Current.CacheFolder, "decimated"), token, isWine);
long totalSizeDownscaled = 0;
foreach (var f in filesDownscaled)
{
token.ThrowIfCancellationRequested();
try
{
long size = 0;
if (!isWine)
{
try
{
size = _fileCompactor.GetFileSizeOnDisk(f);
}
catch (Exception ex)
{
Logger.LogTrace(ex, "GetFileSizeOnDisk failed for {file}, using fallback length", f.FullName);
size = f.Length;
}
}
else
{
size = f.Length;
}
totalSizeDownscaled += size;
}
catch (Exception ex)
{
Logger.LogTrace(ex, "Error getting size for {file}", f.FullName);
}
}
FileCacheSize = (totalSize + totalSizeDownscaled);
}
else
{
FileCacheSize = totalSize;
}
FileCacheSize = totalSize + totalSizeExtras;
var maxCacheInBytes = (long)(_configService.Current.MaxLocalCacheInGiB * 1024d * 1024d * 1024d);
if (FileCacheSize < maxCacheInBytes)
@@ -559,6 +519,54 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
HaltScanLocks.Clear();
}
private long GetFolderSize(string directory, CancellationToken token, bool isWine)
{
if (!Directory.Exists(directory))
{
return 0;
}
var files = Directory.EnumerateFiles(directory)
.Select(f => new FileInfo(f))
.OrderBy(f => f.LastAccessTime)
.ToList();
long totalSize = 0;
foreach (var file in files)
{
token.ThrowIfCancellationRequested();
try
{
long size;
if (!isWine)
{
try
{
size = _fileCompactor.GetFileSizeOnDisk(file);
}
catch (Exception ex)
{
Logger.LogTrace(ex, "GetFileSizeOnDisk failed for {file}, using fallback length", file.FullName);
size = file.Length;
}
}
else
{
size = file.Length;
}
totalSize += size;
}
catch (Exception ex)
{
Logger.LogTrace(ex, "Error getting size for {file}", file.FullName);
}
}
return totalSize;
}
public void ResumeScan(string source)
{
if (!HaltScanLocks.ContainsKey(source)) HaltScanLocks[source] = 0;

View File

@@ -582,9 +582,10 @@ public sealed class FileCacheManager : IHostedService
}
}
public void RemoveHashedFile(string hash, string prefixedFilePath)
public void RemoveHashedFile(string hash, string prefixedFilePath, bool removeDerivedFiles = true)
{
var normalizedPath = NormalizePrefixedPathKey(prefixedFilePath);
var removedHash = false;
if (_fileCaches.TryGetValue(hash, out var caches))
{
@@ -597,11 +598,16 @@ public sealed class FileCacheManager : IHostedService
if (caches.IsEmpty)
{
_fileCaches.TryRemove(hash, out _);
removedHash = _fileCaches.TryRemove(hash, out _);
}
}
_fileCachesByPrefixedPath.TryRemove(normalizedPath, out _);
if (removeDerivedFiles && removedHash)
{
RemoveDerivedCacheFiles(hash);
}
}
public void UpdateHashedFile(FileCacheEntity fileCache, bool computeProperties = true)
@@ -617,7 +623,8 @@ public sealed class FileCacheManager : IHostedService
fileCache.Hash = Crypto.ComputeFileHash(fileCache.ResolvedFilepath, Crypto.HashAlgo.Sha1);
fileCache.LastModifiedDateTicks = fi.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture);
}
RemoveHashedFile(oldHash, prefixedPath);
var removeDerivedFiles = !string.Equals(oldHash, fileCache.Hash, StringComparison.OrdinalIgnoreCase);
RemoveHashedFile(oldHash, prefixedPath, removeDerivedFiles);
AddHashedFile(fileCache);
}
@@ -767,7 +774,7 @@ public sealed class FileCacheManager : IHostedService
{
try
{
RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath);
RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath, removeDerivedFiles: false);
var extensionPath = fileCache.ResolvedFilepath.ToUpper(CultureInfo.InvariantCulture) + "." + ext;
File.Move(fileCache.ResolvedFilepath, extensionPath, overwrite: true);
var newHashedEntity = new FileCacheEntity(fileCache.Hash, fileCache.PrefixedFilePath + "." + ext, DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture));
@@ -784,6 +791,33 @@ public sealed class FileCacheManager : IHostedService
}
}
private void RemoveDerivedCacheFiles(string hash)
{
var cacheFolder = _configService.Current.CacheFolder;
if (string.IsNullOrWhiteSpace(cacheFolder))
{
return;
}
TryDeleteDerivedCacheFile(Path.Combine(cacheFolder, "downscaled", $"{hash}.tex"));
TryDeleteDerivedCacheFile(Path.Combine(cacheFolder, "decimated", $"{hash}.mdl"));
}
private void TryDeleteDerivedCacheFile(string path)
{
try
{
if (File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception ex)
{
_logger.LogTrace(ex, "Failed to delete derived cache file {path}", path);
}
}
private void AddHashedFile(FileCacheEntity fileCache)
{
var normalizedPath = NormalizePrefixedPathKey(fileCache.PrefixedFilePath);

View File

@@ -22,4 +22,12 @@ public class PlayerPerformanceConfig : ILightlessConfiguration
public int TextureDownscaleMaxDimension { get; set; } = 2048;
public bool OnlyDownscaleUncompressedTextures { get; set; } = true;
public bool KeepOriginalTextureFiles { get; set; } = false;
public bool EnableModelDecimation { get; set; } = false;
public int ModelDecimationTriangleThreshold { get; set; } = 50_000;
public double ModelDecimationTargetRatio { get; set; } = 0.8;
public bool ModelDecimationAllowBody { get; set; } = false;
public bool ModelDecimationAllowFaceHead { get; set; } = false;
public bool ModelDecimationAllowTail { get; set; } = false;
public bool ModelDecimationAllowClothing { get; set; } = true;
public bool ModelDecimationAllowAccessories { get; set; } = true;
}

View File

@@ -5,6 +5,7 @@ namespace LightlessSync.LightlessConfiguration.Configurations;
public class XivDataStorageConfig : ILightlessConfiguration
{
public ConcurrentDictionary<string, long> TriangleDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public ConcurrentDictionary<string, long> EffectiveTriangleDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public ConcurrentDictionary<string, Dictionary<string, List<ushort>>> BonesDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public int Version { get; set; } = 0;
}

View File

@@ -1,6 +1,7 @@
using LightlessSync.FileCache;
using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ModelDecimation;
using LightlessSync.Services.TextureCompression;
using LightlessSync.WebAPI.Files;
using Microsoft.Extensions.Logging;
@@ -16,6 +17,7 @@ public class FileDownloadManagerFactory
private readonly FileCompactor _fileCompactor;
private readonly LightlessConfigService _configService;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly ModelDecimationService _modelDecimationService;
private readonly TextureMetadataHelper _textureMetadataHelper;
public FileDownloadManagerFactory(
@@ -26,6 +28,7 @@ public class FileDownloadManagerFactory
FileCompactor fileCompactor,
LightlessConfigService configService,
TextureDownscaleService textureDownscaleService,
ModelDecimationService modelDecimationService,
TextureMetadataHelper textureMetadataHelper)
{
_loggerFactory = loggerFactory;
@@ -35,6 +38,7 @@ public class FileDownloadManagerFactory
_fileCompactor = fileCompactor;
_configService = configService;
_textureDownscaleService = textureDownscaleService;
_modelDecimationService = modelDecimationService;
_textureMetadataHelper = textureMetadataHelper;
}
@@ -48,6 +52,7 @@ public class FileDownloadManagerFactory
_fileCompactor,
_configService,
_textureDownscaleService,
_modelDecimationService,
_textureMetadataHelper);
}
}

View File

@@ -16,4 +16,5 @@ public interface IPairPerformanceSubject
long LastAppliedApproximateVRAMBytes { get; set; }
long LastAppliedApproximateEffectiveVRAMBytes { get; set; }
long LastAppliedDataTris { get; set; }
long LastAppliedApproximateEffectiveTris { get; set; }
}

View File

@@ -69,6 +69,7 @@ public class Pair
public string? PlayerName => TryGetHandler()?.PlayerName ?? UserPair.User.AliasOrUID;
public long LastAppliedDataBytes => TryGetHandler()?.LastAppliedDataBytes ?? -1;
public long LastAppliedDataTris => TryGetHandler()?.LastAppliedDataTris ?? -1;
public long LastAppliedApproximateEffectiveTris => TryGetHandler()?.LastAppliedApproximateEffectiveTris ?? -1;
public long LastAppliedApproximateVRAMBytes => TryGetHandler()?.LastAppliedApproximateVRAMBytes ?? -1;
public long LastAppliedApproximateEffectiveVRAMBytes => TryGetHandler()?.LastAppliedApproximateEffectiveVRAMBytes ?? -1;
public string Ident => TryGetHandler()?.Ident ?? TryGetConnection()?.Ident ?? string.Empty;

View File

@@ -12,6 +12,7 @@ using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Events;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ModelDecimation;
using LightlessSync.Services.PairProcessing;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.Services.TextureCompression;
@@ -45,6 +46,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly ModelDecimationService _modelDecimationService;
private readonly PairStateCache _pairStateCache;
private readonly PairPerformanceMetricsCache _performanceMetricsCache;
private readonly PenumbraTempCollectionJanitor _tempCollectionJanitor;
@@ -162,6 +164,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
public long LastAppliedDataBytes { get; private set; }
public long LastAppliedDataTris { get; set; } = -1;
public long LastAppliedApproximateEffectiveTris { get; set; } = -1;
public long LastAppliedApproximateVRAMBytes { get; set; } = -1;
public long LastAppliedApproximateEffectiveVRAMBytes { get; set; } = -1;
public CharacterData? LastReceivedCharacterData { get; private set; }
@@ -198,6 +201,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
PairProcessingLimiter pairProcessingLimiter,
ServerConfigurationManager serverConfigManager,
TextureDownscaleService textureDownscaleService,
ModelDecimationService modelDecimationService,
PairStateCache pairStateCache,
PairPerformanceMetricsCache performanceMetricsCache,
PenumbraTempCollectionJanitor tempCollectionJanitor) : base(logger, mediator)
@@ -217,6 +221,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_pairProcessingLimiter = pairProcessingLimiter;
_serverConfigManager = serverConfigManager;
_textureDownscaleService = textureDownscaleService;
_modelDecimationService = modelDecimationService;
_pairStateCache = pairStateCache;
_performanceMetricsCache = performanceMetricsCache;
_tempCollectionJanitor = tempCollectionJanitor;
@@ -242,7 +247,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return;
}
if (LastAppliedDataBytes < 0 || LastAppliedDataTris < 0
if (LastAppliedDataBytes < 0 || LastAppliedDataTris < 0 || LastAppliedApproximateEffectiveTris < 0
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
{
_forceApplyMods = true;
@@ -579,6 +584,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_forceApplyMods = true;
LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
LastAppliedApproximateEffectiveTris = -1;
LastAppliedApproximateVRAMBytes = -1;
LastAppliedApproximateEffectiveVRAMBytes = -1;
}
@@ -626,6 +632,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_forceFullReapply = true;
LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
LastAppliedApproximateEffectiveTris = -1;
LastAppliedApproximateVRAMBytes = -1;
LastAppliedApproximateEffectiveVRAMBytes = -1;
}
@@ -725,6 +732,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private void ApplyCachedMetrics(PairPerformanceMetrics metrics)
{
LastAppliedDataTris = metrics.TriangleCount;
LastAppliedApproximateEffectiveTris = metrics.ApproximateEffectiveTris;
LastAppliedApproximateVRAMBytes = metrics.ApproximateVramBytes;
LastAppliedApproximateEffectiveVRAMBytes = metrics.ApproximateEffectiveVramBytes;
}
@@ -732,6 +740,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private void StorePerformanceMetrics(CharacterData charaData)
{
if (LastAppliedDataTris < 0
|| LastAppliedApproximateEffectiveTris < 0
|| LastAppliedApproximateVRAMBytes < 0
|| LastAppliedApproximateEffectiveVRAMBytes < 0)
{
@@ -747,7 +756,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_performanceMetricsCache.StoreMetrics(
Ident,
dataHash,
new PairPerformanceMetrics(LastAppliedDataTris, LastAppliedApproximateVRAMBytes, LastAppliedApproximateEffectiveVRAMBytes));
new PairPerformanceMetrics(LastAppliedDataTris, LastAppliedApproximateVRAMBytes, LastAppliedApproximateEffectiveVRAMBytes, LastAppliedApproximateEffectiveTris));
}
private bool HasMissingCachedFiles(CharacterData characterData)
@@ -1079,7 +1088,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, GetPrimaryAliasOrUidSafe());
var forceFullReapply = _forceFullReapply
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedDataTris < 0;
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedDataTris < 0 || LastAppliedApproximateEffectiveTris < 0;
DownloadAndApplyCharacter(applicationBase, characterData.DeepClone(), charaDataToUpdate, forceFullReapply);
}
@@ -1895,6 +1904,17 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
await _textureDownscaleService.WaitForPendingJobsAsync(downloadedTextureHashes, downloadToken).ConfigureAwait(false);
}
var downloadedModelHashes = toDownloadReplacements
.Where(static replacement => replacement.GamePaths.Any(static path => path.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)))
.Select(static replacement => replacement.Hash)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (downloadedModelHashes.Count > 0)
{
await _modelDecimationService.WaitForPendingJobsAsync(downloadedModelHashes, downloadToken).ConfigureAwait(false);
}
}
toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
@@ -2117,18 +2137,19 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_needsCollectionRebuild = false;
if (LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
{
_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, new List<DownloadFileTransfer>());
}
if (LastAppliedDataTris < 0)
{
await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false);
}
_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, new List<DownloadFileTransfer>());
}
StorePerformanceMetrics(charaData);
_lastSuccessfulDataHash = GetDataHashSafe(charaData);
_lastSuccessfulApplyAt = DateTime.UtcNow;
ClearFailureState();
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
if (LastAppliedDataTris < 0 || LastAppliedApproximateEffectiveTris < 0)
{
await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false);
}
StorePerformanceMetrics(charaData);
_lastSuccessfulDataHash = GetDataHashSafe(charaData);
_lastSuccessfulApplyAt = DateTime.UtcNow;
ClearFailureState();
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
}
catch (OperationCanceledException)
{
@@ -2397,9 +2418,18 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
foreach (var gamePath in item.GamePaths)
{
var preferredPath = skipDownscaleForPair
? fileCache.ResolvedFilepath
: _textureDownscaleService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath);
var preferredPath = fileCache.ResolvedFilepath;
if (!skipDownscaleForPair)
{
if (gamePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase))
{
preferredPath = _textureDownscaleService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath);
}
else if (gamePath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase))
{
preferredPath = _modelDecimationService.GetPreferredPath(item.Hash, fileCache.ResolvedFilepath);
}
}
outputDict[(gamePath, item.Hash)] = preferredPath;
}
}
@@ -2541,6 +2571,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_cachedData = null;
LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
LastAppliedApproximateEffectiveTris = -1;
LastAppliedApproximateVRAMBytes = -1;
LastAppliedApproximateEffectiveVRAMBytes = -1;
}

View File

@@ -4,6 +4,7 @@ using LightlessSync.PlayerData.Factories;
using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ModelDecimation;
using LightlessSync.Services.PairProcessing;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.Services.TextureCompression;
@@ -30,6 +31,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly ServerConfigurationManager _serverConfigManager;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly ModelDecimationService _modelDecimationService;
private readonly PairStateCache _pairStateCache;
private readonly PairPerformanceMetricsCache _pairPerformanceMetricsCache;
private readonly PenumbraTempCollectionJanitor _tempCollectionJanitor;
@@ -51,6 +53,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
PairProcessingLimiter pairProcessingLimiter,
ServerConfigurationManager serverConfigManager,
TextureDownscaleService textureDownscaleService,
ModelDecimationService modelDecimationService,
PairStateCache pairStateCache,
PairPerformanceMetricsCache pairPerformanceMetricsCache,
PenumbraTempCollectionJanitor tempCollectionJanitor)
@@ -70,6 +73,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
_pairProcessingLimiter = pairProcessingLimiter;
_serverConfigManager = serverConfigManager;
_textureDownscaleService = textureDownscaleService;
_modelDecimationService = modelDecimationService;
_pairStateCache = pairStateCache;
_pairPerformanceMetricsCache = pairPerformanceMetricsCache;
_tempCollectionJanitor = tempCollectionJanitor;
@@ -98,6 +102,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
_pairProcessingLimiter,
_serverConfigManager,
_textureDownscaleService,
_modelDecimationService,
_pairStateCache,
_pairPerformanceMetricsCache,
_tempCollectionJanitor);

View File

@@ -89,7 +89,7 @@ public sealed class PairHandlerRegistry : IDisposable
}
if (handler.LastReceivedCharacterData is not null &&
(handler.LastAppliedApproximateVRAMBytes < 0 || handler.LastAppliedDataTris < 0))
(handler.LastAppliedApproximateVRAMBytes < 0 || handler.LastAppliedDataTris < 0 || handler.LastAppliedApproximateEffectiveTris < 0))
{
handler.ApplyLastReceivedData(forced: true);
}

View File

@@ -258,7 +258,8 @@ public sealed class PairLedger : DisposableMediatorSubscriberBase
if (handler.LastAppliedApproximateVRAMBytes >= 0
&& handler.LastAppliedDataTris >= 0
&& handler.LastAppliedApproximateEffectiveVRAMBytes >= 0)
&& handler.LastAppliedApproximateEffectiveVRAMBytes >= 0
&& handler.LastAppliedApproximateEffectiveTris >= 0)
{
continue;
}

View File

@@ -5,7 +5,8 @@ namespace LightlessSync.PlayerData.Pairs;
public readonly record struct PairPerformanceMetrics(
long TriangleCount,
long ApproximateVramBytes,
long ApproximateEffectiveVramBytes);
long ApproximateEffectiveVramBytes,
long ApproximateEffectiveTris);
/// <summary>
/// caches performance metrics keyed by pair ident

View File

@@ -40,6 +40,7 @@ using System.Reflection;
using OtterTex;
using LightlessSync.Services.LightFinder;
using LightlessSync.Services.PairProcessing;
using LightlessSync.Services.ModelDecimation;
using LightlessSync.UI.Models;
namespace LightlessSync;
@@ -126,6 +127,7 @@ public sealed class Plugin : IDalamudPlugin
services.AddSingleton<LightlessProfileManager>();
services.AddSingleton<TextureCompressionService>();
services.AddSingleton<TextureDownscaleService>();
services.AddSingleton<ModelDecimationService>();
services.AddSingleton<GameObjectHandlerFactory>();
services.AddSingleton<FileDownloadManagerFactory>();
services.AddSingleton<PairProcessingLimiter>();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,275 @@
using LightlessSync.LightlessConfiguration;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace LightlessSync.Services.ModelDecimation;
public sealed class ModelDecimationService
{
private const int MaxConcurrentJobs = 1;
private const double MinTargetRatio = 0.01;
private const double MaxTargetRatio = 0.99;
private readonly ILogger<ModelDecimationService> _logger;
private readonly LightlessConfigService _configService;
private readonly PlayerPerformanceConfigService _performanceConfigService;
private readonly XivDataStorageService _xivDataStorageService;
private readonly SemaphoreSlim _decimationSemaphore = new(MaxConcurrentJobs);
private readonly ConcurrentDictionary<string, Task> _activeJobs = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, string> _decimatedPaths = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, byte> _failedHashes = new(StringComparer.OrdinalIgnoreCase);
public ModelDecimationService(
ILogger<ModelDecimationService> logger,
LightlessConfigService configService,
PlayerPerformanceConfigService performanceConfigService,
XivDataStorageService xivDataStorageService)
{
_logger = logger;
_configService = configService;
_performanceConfigService = performanceConfigService;
_xivDataStorageService = xivDataStorageService;
}
public void ScheduleDecimation(string hash, string filePath, string? gamePath = null)
{
if (!ShouldScheduleDecimation(hash, filePath, gamePath))
{
return;
}
if (_decimatedPaths.ContainsKey(hash) || _failedHashes.ContainsKey(hash) || _activeJobs.ContainsKey(hash))
{
return;
}
_logger.LogInformation("Queued model decimation for {Hash}", hash);
_activeJobs[hash] = Task.Run(async () =>
{
await _decimationSemaphore.WaitAsync().ConfigureAwait(false);
try
{
await DecimateInternalAsync(hash, filePath).ConfigureAwait(false);
}
catch (Exception ex)
{
_failedHashes[hash] = 1;
_logger.LogWarning(ex, "Model decimation failed for {Hash}", hash);
}
finally
{
_decimationSemaphore.Release();
_activeJobs.TryRemove(hash, out _);
}
}, CancellationToken.None);
}
public bool ShouldScheduleDecimation(string hash, string filePath, string? gamePath = null)
=> IsDecimationEnabled()
&& filePath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)
&& IsDecimationAllowed(gamePath)
&& !ShouldSkipByTriangleCache(hash);
public string GetPreferredPath(string hash, string originalPath)
{
if (!IsDecimationEnabled())
{
return originalPath;
}
if (_decimatedPaths.TryGetValue(hash, out var existing) && File.Exists(existing))
{
return existing;
}
var resolved = GetExistingDecimatedPath(hash);
if (!string.IsNullOrEmpty(resolved))
{
_decimatedPaths[hash] = resolved;
return resolved;
}
return originalPath;
}
public Task WaitForPendingJobsAsync(IEnumerable<string>? hashes, CancellationToken token)
{
if (hashes is null)
{
return Task.CompletedTask;
}
var pending = new List<Task>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var hash in hashes)
{
if (string.IsNullOrEmpty(hash) || !seen.Add(hash))
{
continue;
}
if (_activeJobs.TryGetValue(hash, out var job))
{
pending.Add(job);
}
}
if (pending.Count == 0)
{
return Task.CompletedTask;
}
return Task.WhenAll(pending).WaitAsync(token);
}
private Task DecimateInternalAsync(string hash, string sourcePath)
{
if (!File.Exists(sourcePath))
{
_failedHashes[hash] = 1;
_logger.LogWarning("Cannot decimate model {Hash}; source path missing: {Path}", hash, sourcePath);
return Task.CompletedTask;
}
if (!TryGetDecimationSettings(out var triangleThreshold, out var targetRatio))
{
_logger.LogInformation("Model decimation disabled or invalid settings for {Hash}", hash);
return Task.CompletedTask;
}
_logger.LogInformation("Starting model decimation for {Hash} (threshold {Threshold}, ratio {Ratio:0.##})", hash, triangleThreshold, targetRatio);
var destination = Path.Combine(GetDecimatedDirectory(), $"{hash}.mdl");
if (File.Exists(destination))
{
_decimatedPaths[hash] = destination;
return Task.CompletedTask;
}
if (!MdlDecimator.TryDecimate(sourcePath, destination, triangleThreshold, targetRatio, _logger))
{
_failedHashes[hash] = 1;
_logger.LogInformation("Model decimation skipped for {Hash}", hash);
return Task.CompletedTask;
}
_decimatedPaths[hash] = destination;
_logger.LogInformation("Decimated model {Hash} -> {Path}", hash, destination);
return Task.CompletedTask;
}
private bool IsDecimationEnabled()
=> _performanceConfigService.Current.EnableModelDecimation;
private bool ShouldSkipByTriangleCache(string hash)
{
if (string.IsNullOrEmpty(hash))
{
return false;
}
if (!_xivDataStorageService.Current.TriangleDictionary.TryGetValue(hash, out var cachedTris) || cachedTris <= 0)
{
return false;
}
var threshold = Math.Max(0, _performanceConfigService.Current.ModelDecimationTriangleThreshold);
return threshold > 0 && cachedTris < threshold;
}
private bool IsDecimationAllowed(string? gamePath)
{
if (string.IsNullOrWhiteSpace(gamePath))
{
return true;
}
var normalized = NormalizeGamePath(gamePath);
if (normalized.Contains("/hair/", StringComparison.Ordinal))
{
return false;
}
if (normalized.Contains("/chara/equipment/", StringComparison.Ordinal))
{
return _performanceConfigService.Current.ModelDecimationAllowClothing;
}
if (normalized.Contains("/chara/accessory/", StringComparison.Ordinal))
{
return _performanceConfigService.Current.ModelDecimationAllowAccessories;
}
if (normalized.Contains("/chara/human/", StringComparison.Ordinal))
{
if (normalized.Contains("/body/", StringComparison.Ordinal))
{
return _performanceConfigService.Current.ModelDecimationAllowBody;
}
if (normalized.Contains("/face/", StringComparison.Ordinal) || normalized.Contains("/head/", StringComparison.Ordinal))
{
return _performanceConfigService.Current.ModelDecimationAllowFaceHead;
}
if (normalized.Contains("/tail/", StringComparison.Ordinal))
{
return _performanceConfigService.Current.ModelDecimationAllowTail;
}
}
return true;
}
private static string NormalizeGamePath(string path)
=> path.Replace('\\', '/').ToLowerInvariant();
private bool TryGetDecimationSettings(out int triangleThreshold, out double targetRatio)
{
triangleThreshold = 15_000;
targetRatio = 0.8;
var config = _performanceConfigService.Current;
if (!config.EnableModelDecimation)
{
return false;
}
triangleThreshold = Math.Max(0, config.ModelDecimationTriangleThreshold);
targetRatio = config.ModelDecimationTargetRatio;
if (double.IsNaN(targetRatio) || double.IsInfinity(targetRatio))
{
return false;
}
targetRatio = Math.Clamp(targetRatio, MinTargetRatio, MaxTargetRatio);
return true;
}
private string? GetExistingDecimatedPath(string hash)
{
var candidate = Path.Combine(GetDecimatedDirectory(), $"{hash}.mdl");
return File.Exists(candidate) ? candidate : null;
}
private string GetDecimatedDirectory()
{
var directory = Path.Combine(_configService.Current.CacheFolder, "decimated");
if (!Directory.Exists(directory))
{
try
{
Directory.CreateDirectory(directory);
}
catch (Exception ex)
{
_logger.LogTrace(ex, "Failed to create decimated directory {Directory}", directory);
}
}
return directory;
}
}

View File

@@ -4,6 +4,7 @@ using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Events;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ModelDecimation;
using LightlessSync.Services.TextureCompression;
using LightlessSync.UI;
using LightlessSync.WebAPI.Files.Models;
@@ -18,12 +19,14 @@ public class PlayerPerformanceService
private readonly ILogger<PlayerPerformanceService> _logger;
private readonly LightlessMediator _mediator;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly ModelDecimationService _modelDecimationService;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly Dictionary<string, bool> _warnedForPlayers = new(StringComparer.Ordinal);
public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, LightlessMediator mediator,
PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager,
XivDataAnalyzer xivDataAnalyzer, TextureDownscaleService textureDownscaleService)
XivDataAnalyzer xivDataAnalyzer, TextureDownscaleService textureDownscaleService,
ModelDecimationService modelDecimationService)
{
_logger = logger;
_mediator = mediator;
@@ -31,6 +34,7 @@ public class PlayerPerformanceService
_fileCacheManager = fileCacheManager;
_xivDataAnalyzer = xivDataAnalyzer;
_textureDownscaleService = textureDownscaleService;
_modelDecimationService = modelDecimationService;
}
public async Task<bool> CheckBothThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData)
@@ -111,10 +115,12 @@ public class PlayerPerformanceService
var config = _playerPerformanceConfigService.Current;
long triUsage = 0;
long effectiveTriUsage = 0;
if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? playerReplacements))
{
pairHandler.LastAppliedDataTris = 0;
pairHandler.LastAppliedApproximateEffectiveTris = 0;
return true;
}
@@ -125,12 +131,31 @@ public class PlayerPerformanceService
foreach (var hash in moddedModelHashes)
{
triUsage += await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false);
var tris = await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false);
triUsage += tris;
long effectiveTris = tris;
var fileEntry = _fileCacheManager.GetFileCacheByHash(hash);
if (fileEntry != null)
{
var preferredPath = _modelDecimationService.GetPreferredPath(hash, fileEntry.ResolvedFilepath);
if (!string.Equals(preferredPath, fileEntry.ResolvedFilepath, StringComparison.OrdinalIgnoreCase))
{
var decimatedTris = await _xivDataAnalyzer.GetEffectiveTrianglesByHash(hash, preferredPath).ConfigureAwait(false);
if (decimatedTris > 0)
{
effectiveTris = decimatedTris;
}
}
}
effectiveTriUsage += effectiveTris;
}
pairHandler.LastAppliedDataTris = triUsage;
pairHandler.LastAppliedApproximateEffectiveTris = effectiveTriUsage;
_logger.LogDebug("Calculated VRAM usage for {p}", pairHandler);
_logger.LogDebug("Calculated triangle usage for {p}", pairHandler);
// no warning of any kind on ignored pairs
if (config.UIDsToIgnore
@@ -274,4 +299,4 @@ public class PlayerPerformanceService
private static bool CheckForThreshold(bool thresholdEnabled, long threshold, long value, bool checkForPrefPerm, bool isPrefPerm) =>
thresholdEnabled && threshold > 0 && threshold < value && ((checkForPrefPerm && isPrefPerm) || !isPrefPerm);
}
}

View File

@@ -678,7 +678,7 @@ public sealed class TextureDownscaleService
if (!string.Equals(cacheEntry.PrefixedFilePath, prefixed, StringComparison.OrdinalIgnoreCase))
{
_fileCacheManager.RemoveHashedFile(cacheEntry.Hash, cacheEntry.PrefixedFilePath);
_fileCacheManager.RemoveHashedFile(cacheEntry.Hash, cacheEntry.PrefixedFilePath, removeDerivedFiles: false);
}
_fileCacheManager.UpdateHashedFile(replacement, computeProperties: false);

View File

@@ -8,6 +8,7 @@ using LightlessSync.Interop.GameModel;
using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Handlers;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
namespace LightlessSync.Services;
@@ -18,6 +19,7 @@ public sealed class XivDataAnalyzer
private readonly FileCacheManager _fileCacheManager;
private readonly XivDataStorageService _configService;
private readonly List<string> _failedCalculatedTris = [];
private readonly List<string> _failedCalculatedEffectiveTris = [];
public XivDataAnalyzer(ILogger<XivDataAnalyzer> logger, FileCacheManager fileCacheManager,
XivDataStorageService configService)
@@ -162,16 +164,41 @@ public sealed class XivDataAnalyzer
if (path == null || !path.ResolvedFilepath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase))
return 0;
var filePath = path.ResolvedFilepath;
return CalculateTrianglesFromPath(hash, path.ResolvedFilepath, _configService.Current.TriangleDictionary, _failedCalculatedTris);
}
public async Task<long> GetEffectiveTrianglesByHash(string hash, string filePath)
{
if (_configService.Current.EffectiveTriangleDictionary.TryGetValue(hash, out var cachedTris) && cachedTris > 0)
return cachedTris;
if (_failedCalculatedEffectiveTris.Contains(hash, StringComparer.Ordinal))
return 0;
if (string.IsNullOrEmpty(filePath)
|| !filePath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)
|| !File.Exists(filePath))
{
return 0;
}
return CalculateTrianglesFromPath(hash, filePath, _configService.Current.EffectiveTriangleDictionary, _failedCalculatedEffectiveTris);
}
private long CalculateTrianglesFromPath(
string hash,
string filePath,
ConcurrentDictionary<string, long> cache,
List<string> failedList)
{
try
{
_logger.LogDebug("Detected Model File {path}, calculating Tris", filePath);
var file = new MdlFile(filePath);
if (file.LodCount <= 0)
{
_failedCalculatedTris.Add(hash);
_configService.Current.TriangleDictionary[hash] = 0;
failedList.Add(hash);
cache[hash] = 0;
_configService.Save();
return 0;
}
@@ -195,7 +222,7 @@ public sealed class XivDataAnalyzer
if (tris > 0)
{
_logger.LogDebug("TriAnalysis: {filePath} => {tris} triangles", filePath, tris);
_configService.Current.TriangleDictionary[hash] = tris;
cache[hash] = tris;
_configService.Save();
break;
}
@@ -205,8 +232,8 @@ public sealed class XivDataAnalyzer
}
catch (Exception e)
{
_failedCalculatedTris.Add(hash);
_configService.Current.TriangleDictionary[hash] = 0;
failedList.Add(hash);
cache[hash] = 0;
_configService.Save();
_logger.LogWarning(e, "Could not parse file {file}", filePath);
return 0;

View File

@@ -0,0 +1,169 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using Microsoft.Extensions.Logging;
namespace MeshDecimator.Algorithms
{
/// <summary>
/// A decimation algorithm.
/// </summary>
public abstract class DecimationAlgorithm
{
#region Delegates
/// <summary>
/// A callback for decimation status reports.
/// </summary>
/// <param name="iteration">The current iteration, starting at zero.</param>
/// <param name="originalTris">The original count of triangles.</param>
/// <param name="currentTris">The current count of triangles.</param>
/// <param name="targetTris">The target count of triangles.</param>
public delegate void StatusReportCallback(int iteration, int originalTris, int currentTris, int targetTris);
#endregion
#region Fields
private bool preserveBorders = false;
private int maxVertexCount = 0;
private bool verbose = false;
private StatusReportCallback statusReportInvoker = null;
#endregion
#region Properties
/// <summary>
/// Gets or sets if borders should be kept.
/// Default value: false
/// </summary>
[Obsolete("Use the 'DecimationAlgorithm.PreserveBorders' property instead.", false)]
public bool KeepBorders
{
get { return preserveBorders; }
set { preserveBorders = value; }
}
/// <summary>
/// Gets or sets if borders should be preserved.
/// Default value: false
/// </summary>
public bool PreserveBorders
{
get { return preserveBorders; }
set { preserveBorders = value; }
}
/// <summary>
/// Gets or sets if linked vertices should be kept.
/// Default value: false
/// </summary>
[Obsolete("This feature has been removed, for more details why please read the readme.", true)]
public bool KeepLinkedVertices
{
get { return false; }
set { }
}
/// <summary>
/// Gets or sets the maximum vertex count. Set to zero for no limitation.
/// Default value: 0 (no limitation)
/// </summary>
public int MaxVertexCount
{
get { return maxVertexCount; }
set { maxVertexCount = Math.MathHelper.Max(value, 0); }
}
/// <summary>
/// Gets or sets if verbose information should be printed in the console.
/// Default value: false
/// </summary>
public bool Verbose
{
get { return verbose; }
set { verbose = value; }
}
/// <summary>
/// Gets or sets the logger used for diagnostics.
/// </summary>
public ILogger? Logger { get; set; }
#endregion
#region Events
/// <summary>
/// An event for status reports for this algorithm.
/// </summary>
public event StatusReportCallback StatusReport
{
add { statusReportInvoker += value; }
remove { statusReportInvoker -= value; }
}
#endregion
#region Protected Methods
/// <summary>
/// Reports the current status of the decimation.
/// </summary>
/// <param name="iteration">The current iteration, starting at zero.</param>
/// <param name="originalTris">The original count of triangles.</param>
/// <param name="currentTris">The current count of triangles.</param>
/// <param name="targetTris">The target count of triangles.</param>
protected void ReportStatus(int iteration, int originalTris, int currentTris, int targetTris)
{
var statusReportInvoker = this.statusReportInvoker;
if (statusReportInvoker != null)
{
statusReportInvoker.Invoke(iteration, originalTris, currentTris, targetTris);
}
}
#endregion
#region Public Methods
/// <summary>
/// Initializes the algorithm with the original mesh.
/// </summary>
/// <param name="mesh">The mesh.</param>
public abstract void Initialize(Mesh mesh);
/// <summary>
/// Decimates the mesh.
/// </summary>
/// <param name="targetTrisCount">The target triangle count.</param>
public abstract void DecimateMesh(int targetTrisCount);
/// <summary>
/// Decimates the mesh without losing any quality.
/// </summary>
public abstract void DecimateMeshLossless();
/// <summary>
/// Returns the resulting mesh.
/// </summary>
/// <returns>The resulting mesh.</returns>
public abstract Mesh ToMesh();
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,249 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using MeshDecimator.Math;
namespace MeshDecimator
{
/// <summary>
/// A bone weight.
/// </summary>
public struct BoneWeight : IEquatable<BoneWeight>
{
#region Fields
/// <summary>
/// The first bone index.
/// </summary>
public int boneIndex0;
/// <summary>
/// The second bone index.
/// </summary>
public int boneIndex1;
/// <summary>
/// The third bone index.
/// </summary>
public int boneIndex2;
/// <summary>
/// The fourth bone index.
/// </summary>
public int boneIndex3;
/// <summary>
/// The first bone weight.
/// </summary>
public float boneWeight0;
/// <summary>
/// The second bone weight.
/// </summary>
public float boneWeight1;
/// <summary>
/// The third bone weight.
/// </summary>
public float boneWeight2;
/// <summary>
/// The fourth bone weight.
/// </summary>
public float boneWeight3;
#endregion
#region Constructor
/// <summary>
/// Creates a new bone weight.
/// </summary>
/// <param name="boneIndex0">The first bone index.</param>
/// <param name="boneIndex1">The second bone index.</param>
/// <param name="boneIndex2">The third bone index.</param>
/// <param name="boneIndex3">The fourth bone index.</param>
/// <param name="boneWeight0">The first bone weight.</param>
/// <param name="boneWeight1">The second bone weight.</param>
/// <param name="boneWeight2">The third bone weight.</param>
/// <param name="boneWeight3">The fourth bone weight.</param>
public BoneWeight(int boneIndex0, int boneIndex1, int boneIndex2, int boneIndex3, float boneWeight0, float boneWeight1, float boneWeight2, float boneWeight3)
{
this.boneIndex0 = boneIndex0;
this.boneIndex1 = boneIndex1;
this.boneIndex2 = boneIndex2;
this.boneIndex3 = boneIndex3;
this.boneWeight0 = boneWeight0;
this.boneWeight1 = boneWeight1;
this.boneWeight2 = boneWeight2;
this.boneWeight3 = boneWeight3;
}
#endregion
#region Operators
/// <summary>
/// Returns if two bone weights equals eachother.
/// </summary>
/// <param name="lhs">The left hand side bone weight.</param>
/// <param name="rhs">The right hand side bone weight.</param>
/// <returns>If equals.</returns>
public static bool operator ==(BoneWeight lhs, BoneWeight rhs)
{
return (lhs.boneIndex0 == rhs.boneIndex0 && lhs.boneIndex1 == rhs.boneIndex1 && lhs.boneIndex2 == rhs.boneIndex2 && lhs.boneIndex3 == rhs.boneIndex3 &&
new Vector4(lhs.boneWeight0, lhs.boneWeight1, lhs.boneWeight2, lhs.boneWeight3) == new Vector4(rhs.boneWeight0, rhs.boneWeight1, rhs.boneWeight2, rhs.boneWeight3));
}
/// <summary>
/// Returns if two bone weights don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side bone weight.</param>
/// <param name="rhs">The right hand side bone weight.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(BoneWeight lhs, BoneWeight rhs)
{
return !(lhs == rhs);
}
#endregion
#region Private Methods
private void MergeBoneWeight(int boneIndex, float weight)
{
if (boneIndex == boneIndex0)
{
boneWeight0 = (boneWeight0 + weight) * 0.5f;
}
else if (boneIndex == boneIndex1)
{
boneWeight1 = (boneWeight1 + weight) * 0.5f;
}
else if (boneIndex == boneIndex2)
{
boneWeight2 = (boneWeight2 + weight) * 0.5f;
}
else if (boneIndex == boneIndex3)
{
boneWeight3 = (boneWeight3 + weight) * 0.5f;
}
else if(boneWeight0 == 0f)
{
boneIndex0 = boneIndex;
boneWeight0 = weight;
}
else if (boneWeight1 == 0f)
{
boneIndex1 = boneIndex;
boneWeight1 = weight;
}
else if (boneWeight2 == 0f)
{
boneIndex2 = boneIndex;
boneWeight2 = weight;
}
else if (boneWeight3 == 0f)
{
boneIndex3 = boneIndex;
boneWeight3 = weight;
}
Normalize();
}
private void Normalize()
{
float mag = (float)System.Math.Sqrt(boneWeight0 * boneWeight0 + boneWeight1 * boneWeight1 + boneWeight2 * boneWeight2 + boneWeight3 * boneWeight3);
if (mag > float.Epsilon)
{
boneWeight0 /= mag;
boneWeight1 /= mag;
boneWeight2 /= mag;
boneWeight3 /= mag;
}
else
{
boneWeight0 = boneWeight1 = boneWeight2 = boneWeight3 = 0f;
}
}
#endregion
#region Public Methods
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return boneIndex0.GetHashCode() ^ boneIndex1.GetHashCode() << 2 ^ boneIndex2.GetHashCode() >> 2 ^ boneIndex3.GetHashCode() >>
1 ^ boneWeight0.GetHashCode() << 5 ^ boneWeight1.GetHashCode() << 4 ^ boneWeight2.GetHashCode() >> 4 ^ boneWeight3.GetHashCode() >> 3;
}
/// <summary>
/// Returns if this bone weight is equal to another object.
/// </summary>
/// <param name="obj">The other object to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object obj)
{
if (!(obj is BoneWeight))
{
return false;
}
BoneWeight other = (BoneWeight)obj;
return (boneIndex0 == other.boneIndex0 && boneIndex1 == other.boneIndex1 && boneIndex2 == other.boneIndex2 && boneIndex3 == other.boneIndex3 &&
boneWeight0 == other.boneWeight0 && boneWeight1 == other.boneWeight1 && boneWeight2 == other.boneWeight2 && boneWeight3 == other.boneWeight3);
}
/// <summary>
/// Returns if this bone weight is equal to another one.
/// </summary>
/// <param name="other">The other bone weight to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(BoneWeight other)
{
return (boneIndex0 == other.boneIndex0 && boneIndex1 == other.boneIndex1 && boneIndex2 == other.boneIndex2 && boneIndex3 == other.boneIndex3 &&
boneWeight0 == other.boneWeight0 && boneWeight1 == other.boneWeight1 && boneWeight2 == other.boneWeight2 && boneWeight3 == other.boneWeight3);
}
/// <summary>
/// Returns a nicely formatted string for this bone weight.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}:{4:F1}, {1}:{5:F1}, {2}:{6:F1}, {3}:{7:F1})",
boneIndex0, boneIndex1, boneIndex2, boneIndex3, boneWeight0, boneWeight1, boneWeight2, boneWeight3);
}
#endregion
#region Static
/// <summary>
/// Merges two bone weights and stores the merged result in the first parameter.
/// </summary>
/// <param name="a">The first bone weight, also stores result.</param>
/// <param name="b">The second bone weight.</param>
public static void Merge(ref BoneWeight a, ref BoneWeight b)
{
if (b.boneWeight0 > 0f) a.MergeBoneWeight(b.boneIndex0, b.boneWeight0);
if (b.boneWeight1 > 0f) a.MergeBoneWeight(b.boneIndex1, b.boneWeight1);
if (b.boneWeight2 > 0f) a.MergeBoneWeight(b.boneIndex2, b.boneWeight2);
if (b.boneWeight3 > 0f) a.MergeBoneWeight(b.boneIndex3, b.boneWeight3);
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,179 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
namespace MeshDecimator.Collections
{
/// <summary>
/// A resizable array.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
internal sealed class ResizableArray<T>
{
#region Fields
private T[] items = null;
private int length = 0;
private static T[] emptyArr = new T[0];
#endregion
#region Properties
/// <summary>
/// Gets the length of this array.
/// </summary>
public int Length
{
get { return length; }
}
/// <summary>
/// Gets the internal data buffer for this array.
/// </summary>
public T[] Data
{
get { return items; }
}
/// <summary>
/// Gets or sets the element value at a specific index.
/// </summary>
/// <param name="index">The element index.</param>
/// <returns>The element value.</returns>
public T this[int index]
{
get { return items[index]; }
set { items[index] = value; }
}
#endregion
#region Constructor
/// <summary>
/// Creates a new resizable array.
/// </summary>
/// <param name="capacity">The initial array capacity.</param>
public ResizableArray(int capacity)
: this(capacity, 0)
{
}
/// <summary>
/// Creates a new resizable array.
/// </summary>
/// <param name="capacity">The initial array capacity.</param>
/// <param name="length">The initial length of the array.</param>
public ResizableArray(int capacity, int length)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException("capacity");
else if (length < 0 || length > capacity)
throw new ArgumentOutOfRangeException("length");
if (capacity > 0)
items = new T[capacity];
else
items = emptyArr;
this.length = length;
}
#endregion
#region Private Methods
private void IncreaseCapacity(int capacity)
{
T[] newItems = new T[capacity];
Array.Copy(items, 0, newItems, 0, System.Math.Min(length, capacity));
items = newItems;
}
#endregion
#region Public Methods
/// <summary>
/// Clears this array.
/// </summary>
public void Clear()
{
Array.Clear(items, 0, length);
length = 0;
}
/// <summary>
/// Resizes this array.
/// </summary>
/// <param name="length">The new length.</param>
/// <param name="trimExess">If exess memory should be trimmed.</param>
public void Resize(int length, bool trimExess = false)
{
if (length < 0)
throw new ArgumentOutOfRangeException("capacity");
if (length > items.Length)
{
IncreaseCapacity(length);
}
else if (length < this.length)
{
//Array.Clear(items, capacity, length - capacity);
}
this.length = length;
if (trimExess)
{
TrimExcess();
}
}
/// <summary>
/// Trims any excess memory for this array.
/// </summary>
public void TrimExcess()
{
if (items.Length == length) // Nothing to do
return;
T[] newItems = new T[length];
Array.Copy(items, 0, newItems, 0, length);
items = newItems;
}
/// <summary>
/// Adds a new item to the end of this array.
/// </summary>
/// <param name="item">The new item.</param>
public void Add(T item)
{
if (length >= items.Length)
{
IncreaseCapacity(items.Length << 1);
}
items[length++] = item;
}
#endregion
}
}

View File

@@ -0,0 +1,79 @@
using System;
namespace MeshDecimator.Collections
{
/// <summary>
/// A collection of UV channels.
/// </summary>
/// <typeparam name="TVec">The UV vector type.</typeparam>
internal sealed class UVChannels<TVec>
{
#region Fields
private ResizableArray<TVec>[] channels = null;
private TVec[][] channelsData = null;
#endregion
#region Properties
/// <summary>
/// Gets the channel collection data.
/// </summary>
public TVec[][] Data
{
get
{
for (int i = 0; i < Mesh.UVChannelCount; i++)
{
if (channels[i] != null)
{
channelsData[i] = channels[i].Data;
}
else
{
channelsData[i] = null;
}
}
return channelsData;
}
}
/// <summary>
/// Gets or sets a specific channel by index.
/// </summary>
/// <param name="index">The channel index.</param>
public ResizableArray<TVec> this[int index]
{
get { return channels[index]; }
set { channels[index] = value; }
}
#endregion
#region Constructor
/// <summary>
/// Creates a new collection of UV channels.
/// </summary>
public UVChannels()
{
channels = new ResizableArray<TVec>[Mesh.UVChannelCount];
channelsData = new TVec[Mesh.UVChannelCount][];
}
#endregion
#region Public Methods
/// <summary>
/// Resizes all channels at once.
/// </summary>
/// <param name="capacity">The new capacity.</param>
/// <param name="trimExess">If exess memory should be trimmed.</param>
public void Resize(int capacity, bool trimExess = false)
{
for (int i = 0; i < Mesh.UVChannelCount; i++)
{
if (channels[i] != null)
{
channels[i].Resize(capacity, trimExess);
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,286 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
namespace MeshDecimator.Math
{
/// <summary>
/// Math helpers.
/// </summary>
public static class MathHelper
{
#region Consts
/// <summary>
/// The Pi constant.
/// </summary>
public const float PI = 3.14159274f;
/// <summary>
/// The Pi constant.
/// </summary>
public const double PId = 3.1415926535897932384626433832795;
/// <summary>
/// Degrees to radian constant.
/// </summary>
public const float Deg2Rad = PI / 180f;
/// <summary>
/// Degrees to radian constant.
/// </summary>
public const double Deg2Radd = PId / 180.0;
/// <summary>
/// Radians to degrees constant.
/// </summary>
public const float Rad2Deg = 180f / PI;
/// <summary>
/// Radians to degrees constant.
/// </summary>
public const double Rad2Degd = 180.0 / PId;
#endregion
#region Min
/// <summary>
/// Returns the minimum of two values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <returns>The minimum value.</returns>
public static int Min(int val1, int val2)
{
return (val1 < val2 ? val1 : val2);
}
/// <summary>
/// Returns the minimum of three values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="val3">The third value.</param>
/// <returns>The minimum value.</returns>
public static int Min(int val1, int val2, int val3)
{
return (val1 < val2 ? (val1 < val3 ? val1 : val3) : (val2 < val3 ? val2 : val3));
}
/// <summary>
/// Returns the minimum of two values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <returns>The minimum value.</returns>
public static float Min(float val1, float val2)
{
return (val1 < val2 ? val1 : val2);
}
/// <summary>
/// Returns the minimum of three values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="val3">The third value.</param>
/// <returns>The minimum value.</returns>
public static float Min(float val1, float val2, float val3)
{
return (val1 < val2 ? (val1 < val3 ? val1 : val3) : (val2 < val3 ? val2 : val3));
}
/// <summary>
/// Returns the minimum of two values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <returns>The minimum value.</returns>
public static double Min(double val1, double val2)
{
return (val1 < val2 ? val1 : val2);
}
/// <summary>
/// Returns the minimum of three values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="val3">The third value.</param>
/// <returns>The minimum value.</returns>
public static double Min(double val1, double val2, double val3)
{
return (val1 < val2 ? (val1 < val3 ? val1 : val3) : (val2 < val3 ? val2 : val3));
}
#endregion
#region Max
/// <summary>
/// Returns the maximum of two values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <returns>The maximum value.</returns>
public static int Max(int val1, int val2)
{
return (val1 > val2 ? val1 : val2);
}
/// <summary>
/// Returns the maximum of three values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="val3">The third value.</param>
/// <returns>The maximum value.</returns>
public static int Max(int val1, int val2, int val3)
{
return (val1 > val2 ? (val1 > val3 ? val1 : val3) : (val2 > val3 ? val2 : val3));
}
/// <summary>
/// Returns the maximum of two values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <returns>The maximum value.</returns>
public static float Max(float val1, float val2)
{
return (val1 > val2 ? val1 : val2);
}
/// <summary>
/// Returns the maximum of three values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="val3">The third value.</param>
/// <returns>The maximum value.</returns>
public static float Max(float val1, float val2, float val3)
{
return (val1 > val2 ? (val1 > val3 ? val1 : val3) : (val2 > val3 ? val2 : val3));
}
/// <summary>
/// Returns the maximum of two values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <returns>The maximum value.</returns>
public static double Max(double val1, double val2)
{
return (val1 > val2 ? val1 : val2);
}
/// <summary>
/// Returns the maximum of three values.
/// </summary>
/// <param name="val1">The first value.</param>
/// <param name="val2">The second value.</param>
/// <param name="val3">The third value.</param>
/// <returns>The maximum value.</returns>
public static double Max(double val1, double val2, double val3)
{
return (val1 > val2 ? (val1 > val3 ? val1 : val3) : (val2 > val3 ? val2 : val3));
}
#endregion
#region Clamping
/// <summary>
/// Clamps a value between a minimum and a maximum value.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static float Clamp(float value, float min, float max)
{
return (value >= min ? (value <= max ? value : max) : min);
}
/// <summary>
/// Clamps a value between a minimum and a maximum value.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static double Clamp(double value, double min, double max)
{
return (value >= min ? (value <= max ? value : max) : min);
}
/// <summary>
/// Clamps the value between 0 and 1.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <returns>The clamped value.</returns>
public static float Clamp01(float value)
{
return (value > 0f ? (value < 1f ? value : 1f) : 0f);
}
/// <summary>
/// Clamps the value between 0 and 1.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <returns>The clamped value.</returns>
public static double Clamp01(double value)
{
return (value > 0.0 ? (value < 1.0 ? value : 1.0) : 0.0);
}
#endregion
#region Triangle Area
/// <summary>
/// Calculates the area of a triangle.
/// </summary>
/// <param name="p0">The first point.</param>
/// <param name="p1">The second point.</param>
/// <param name="p2">The third point.</param>
/// <returns>The triangle area.</returns>
public static float TriangleArea(ref Vector3 p0, ref Vector3 p1, ref Vector3 p2)
{
var dx = p1 - p0;
var dy = p2 - p0;
return dx.Magnitude * ((float)System.Math.Sin(Vector3.Angle(ref dx, ref dy) * Deg2Rad) * dy.Magnitude) * 0.5f;
}
/// <summary>
/// Calculates the area of a triangle.
/// </summary>
/// <param name="p0">The first point.</param>
/// <param name="p1">The second point.</param>
/// <param name="p2">The third point.</param>
/// <returns>The triangle area.</returns>
public static double TriangleArea(ref Vector3d p0, ref Vector3d p1, ref Vector3d p2)
{
var dx = p1 - p0;
var dy = p2 - p0;
return dx.Magnitude * (System.Math.Sin(Vector3d.Angle(ref dx, ref dy) * Deg2Radd) * dy.Magnitude) * 0.5f;
}
#endregion
}
}

View File

@@ -0,0 +1,303 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
namespace MeshDecimator.Math
{
/// <summary>
/// A symmetric matrix.
/// </summary>
public struct SymmetricMatrix
{
#region Fields
/// <summary>
/// The m11 component.
/// </summary>
public double m0;
/// <summary>
/// The m12 component.
/// </summary>
public double m1;
/// <summary>
/// The m13 component.
/// </summary>
public double m2;
/// <summary>
/// The m14 component.
/// </summary>
public double m3;
/// <summary>
/// The m22 component.
/// </summary>
public double m4;
/// <summary>
/// The m23 component.
/// </summary>
public double m5;
/// <summary>
/// The m24 component.
/// </summary>
public double m6;
/// <summary>
/// The m33 component.
/// </summary>
public double m7;
/// <summary>
/// The m34 component.
/// </summary>
public double m8;
/// <summary>
/// The m44 component.
/// </summary>
public double m9;
#endregion
#region Properties
/// <summary>
/// Gets the component value with a specific index.
/// </summary>
/// <param name="index">The component index.</param>
/// <returns>The value.</returns>
public double this[int index]
{
get
{
switch (index)
{
case 0:
return m0;
case 1:
return m1;
case 2:
return m2;
case 3:
return m3;
case 4:
return m4;
case 5:
return m5;
case 6:
return m6;
case 7:
return m7;
case 8:
return m8;
case 9:
return m9;
default:
throw new IndexOutOfRangeException();
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a symmetric matrix with a value in each component.
/// </summary>
/// <param name="c">The component value.</param>
public SymmetricMatrix(double c)
{
this.m0 = c;
this.m1 = c;
this.m2 = c;
this.m3 = c;
this.m4 = c;
this.m5 = c;
this.m6 = c;
this.m7 = c;
this.m8 = c;
this.m9 = c;
}
/// <summary>
/// Creates a symmetric matrix.
/// </summary>
/// <param name="m0">The m11 component.</param>
/// <param name="m1">The m12 component.</param>
/// <param name="m2">The m13 component.</param>
/// <param name="m3">The m14 component.</param>
/// <param name="m4">The m22 component.</param>
/// <param name="m5">The m23 component.</param>
/// <param name="m6">The m24 component.</param>
/// <param name="m7">The m33 component.</param>
/// <param name="m8">The m34 component.</param>
/// <param name="m9">The m44 component.</param>
public SymmetricMatrix(double m0, double m1, double m2, double m3,
double m4, double m5, double m6, double m7, double m8, double m9)
{
this.m0 = m0;
this.m1 = m1;
this.m2 = m2;
this.m3 = m3;
this.m4 = m4;
this.m5 = m5;
this.m6 = m6;
this.m7 = m7;
this.m8 = m8;
this.m9 = m9;
}
/// <summary>
/// Creates a symmetric matrix from a plane.
/// </summary>
/// <param name="a">The plane x-component.</param>
/// <param name="b">The plane y-component</param>
/// <param name="c">The plane z-component</param>
/// <param name="d">The plane w-component</param>
public SymmetricMatrix(double a, double b, double c, double d)
{
this.m0 = a * a;
this.m1 = a * b;
this.m2 = a * c;
this.m3 = a * d;
this.m4 = b * b;
this.m5 = b * c;
this.m6 = b * d;
this.m7 = c * c;
this.m8 = c * d;
this.m9 = d * d;
}
#endregion
#region Operators
/// <summary>
/// Adds two matrixes together.
/// </summary>
/// <param name="a">The left hand side.</param>
/// <param name="b">The right hand side.</param>
/// <returns>The resulting matrix.</returns>
public static SymmetricMatrix operator +(SymmetricMatrix a, SymmetricMatrix b)
{
return new SymmetricMatrix(
a.m0 + b.m0, a.m1 + b.m1, a.m2 + b.m2, a.m3 + b.m3,
a.m4 + b.m4, a.m5 + b.m5, a.m6 + b.m6,
a.m7 + b.m7, a.m8 + b.m8,
a.m9 + b.m9
);
}
#endregion
#region Internal Methods
/// <summary>
/// Determinant(0, 1, 2, 1, 4, 5, 2, 5, 7)
/// </summary>
/// <returns></returns>
internal double Determinant1()
{
double det =
m0 * m4 * m7 +
m2 * m1 * m5 +
m1 * m5 * m2 -
m2 * m4 * m2 -
m0 * m5 * m5 -
m1 * m1 * m7;
return det;
}
/// <summary>
/// Determinant(1, 2, 3, 4, 5, 6, 5, 7, 8)
/// </summary>
/// <returns></returns>
internal double Determinant2()
{
double det =
m1 * m5 * m8 +
m3 * m4 * m7 +
m2 * m6 * m5 -
m3 * m5 * m5 -
m1 * m6 * m7 -
m2 * m4 * m8;
return det;
}
/// <summary>
/// Determinant(0, 2, 3, 1, 5, 6, 2, 7, 8)
/// </summary>
/// <returns></returns>
internal double Determinant3()
{
double det =
m0 * m5 * m8 +
m3 * m1 * m7 +
m2 * m6 * m2 -
m3 * m5 * m2 -
m0 * m6 * m7 -
m2 * m1 * m8;
return det;
}
/// <summary>
/// Determinant(0, 1, 3, 1, 4, 6, 2, 5, 8)
/// </summary>
/// <returns></returns>
internal double Determinant4()
{
double det =
m0 * m4 * m8 +
m3 * m1 * m5 +
m1 * m6 * m2 -
m3 * m4 * m2 -
m0 * m6 * m5 -
m1 * m1 * m8;
return det;
}
#endregion
#region Public Methods
/// <summary>
/// Computes the determinant of this matrix.
/// </summary>
/// <param name="a11">The a11 index.</param>
/// <param name="a12">The a12 index.</param>
/// <param name="a13">The a13 index.</param>
/// <param name="a21">The a21 index.</param>
/// <param name="a22">The a22 index.</param>
/// <param name="a23">The a23 index.</param>
/// <param name="a31">The a31 index.</param>
/// <param name="a32">The a32 index.</param>
/// <param name="a33">The a33 index.</param>
/// <returns>The determinant value.</returns>
public double Determinant(int a11, int a12, int a13,
int a21, int a22, int a23,
int a31, int a32, int a33)
{
double det =
this[a11] * this[a22] * this[a33] +
this[a13] * this[a21] * this[a32] +
this[a12] * this[a23] * this[a31] -
this[a13] * this[a22] * this[a31] -
this[a11] * this[a23] * this[a32] -
this[a12] * this[a21] * this[a33];
return det;
}
#endregion
}
}

View File

@@ -0,0 +1,425 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A single precision 2D vector.
/// </summary>
public struct Vector2 : IEquatable<Vector2>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector2 zero = new Vector2(0, 0);
#endregion
#region Consts
/// <summary>
/// The vector epsilon.
/// </summary>
public const float Epsilon = 9.99999944E-11f;
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public float x;
/// <summary>
/// The y component.
/// </summary>
public float y;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public float Magnitude
{
get { return (float)System.Math.Sqrt(x * x + y * y); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public float MagnitudeSqr
{
get { return (x * x + y * y); }
}
/// <summary>
/// Gets a normalized vector from this vector.
/// </summary>
public Vector2 Normalized
{
get
{
Vector2 result;
Normalize(ref this, out result);
return result;
}
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public float this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
default:
throw new IndexOutOfRangeException("Invalid Vector2 index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector2 index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector2(float value)
{
this.x = value;
this.y = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
public Vector2(float x, float y)
{
this.x = x;
this.y = y;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2 operator +(Vector2 a, Vector2 b)
{
return new Vector2(a.x + b.x, a.y + b.y);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2 operator -(Vector2 a, Vector2 b)
{
return new Vector2(a.x - b.x, a.y - b.y);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector2 operator *(Vector2 a, float d)
{
return new Vector2(a.x * d, a.y * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2 operator *(float d, Vector2 a)
{
return new Vector2(a.x * d, a.y * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector2 operator /(Vector2 a, float d)
{
return new Vector2(a.x / d, a.y / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2 operator -(Vector2 a)
{
return new Vector2(-a.x, -a.y);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector2 lhs, Vector2 rhs)
{
return (lhs - rhs).MagnitudeSqr < Epsilon;
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector2 lhs, Vector2 rhs)
{
return (lhs - rhs).MagnitudeSqr >= Epsilon;
}
/// <summary>
/// Explicitly converts from a double-precision vector into a single-precision vector.
/// </summary>
/// <param name="v">The double-precision vector.</param>
public static explicit operator Vector2(Vector2d v)
{
return new Vector2((float)v.x, (float)v.y);
}
/// <summary>
/// Implicitly converts from an integer vector into a single-precision vector.
/// </summary>
/// <param name="v">The integer vector.</param>
public static implicit operator Vector2(Vector2i v)
{
return new Vector2(v.x, v.y);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x and y components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
public void Set(float x, float y)
{
this.x = x;
this.y = y;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector2 scale)
{
x *= scale.x;
y *= scale.y;
}
/// <summary>
/// Normalizes this vector.
/// </summary>
public void Normalize()
{
float mag = this.Magnitude;
if (mag > Epsilon)
{
x /= mag;
y /= mag;
}
else
{
x = y = 0;
}
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(float min, float max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector2))
{
return false;
}
Vector2 vector = (Vector2)other;
return (x == vector.x && y == vector.y);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector2 other)
{
return (x == other.x && y == other.y);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1})",
x.ToString("F1", CultureInfo.InvariantCulture),
y.ToString("F1", CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The float format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Dot Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
public static float Dot(ref Vector2 lhs, ref Vector2 rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y;
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="a">The vector to interpolate from.</param>
/// <param name="b">The vector to interpolate to.</param>
/// <param name="t">The time fraction.</param>
/// <param name="result">The resulting vector.</param>
public static void Lerp(ref Vector2 a, ref Vector2 b, float t, out Vector2 result)
{
result = new Vector2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
}
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector2 a, ref Vector2 b, out Vector2 result)
{
result = new Vector2(a.x * b.x, a.y * b.y);
}
/// <summary>
/// Normalizes a vector.
/// </summary>
/// <param name="value">The vector to normalize.</param>
/// <param name="result">The resulting normalized vector.</param>
public static void Normalize(ref Vector2 value, out Vector2 result)
{
float mag = value.Magnitude;
if (mag > Epsilon)
{
result = new Vector2(value.x / mag, value.y / mag);
}
else
{
result = Vector2.zero;
}
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,425 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A double precision 2D vector.
/// </summary>
public struct Vector2d : IEquatable<Vector2d>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector2d zero = new Vector2d(0, 0);
#endregion
#region Consts
/// <summary>
/// The vector epsilon.
/// </summary>
public const double Epsilon = double.Epsilon;
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public double x;
/// <summary>
/// The y component.
/// </summary>
public double y;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public double Magnitude
{
get { return System.Math.Sqrt(x * x + y * y); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public double MagnitudeSqr
{
get { return (x * x + y * y); }
}
/// <summary>
/// Gets a normalized vector from this vector.
/// </summary>
public Vector2d Normalized
{
get
{
Vector2d result;
Normalize(ref this, out result);
return result;
}
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public double this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
default:
throw new IndexOutOfRangeException("Invalid Vector2d index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector2d index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector2d(double value)
{
this.x = value;
this.y = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
public Vector2d(double x, double y)
{
this.x = x;
this.y = y;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2d operator +(Vector2d a, Vector2d b)
{
return new Vector2d(a.x + b.x, a.y + b.y);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2d operator -(Vector2d a, Vector2d b)
{
return new Vector2d(a.x - b.x, a.y - b.y);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector2d operator *(Vector2d a, double d)
{
return new Vector2d(a.x * d, a.y * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2d operator *(double d, Vector2d a)
{
return new Vector2d(a.x * d, a.y * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector2d operator /(Vector2d a, double d)
{
return new Vector2d(a.x / d, a.y / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2d operator -(Vector2d a)
{
return new Vector2d(-a.x, -a.y);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector2d lhs, Vector2d rhs)
{
return (lhs - rhs).MagnitudeSqr < Epsilon;
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector2d lhs, Vector2d rhs)
{
return (lhs - rhs).MagnitudeSqr >= Epsilon;
}
/// <summary>
/// Implicitly converts from a single-precision vector into a double-precision vector.
/// </summary>
/// <param name="v">The single-precision vector.</param>
public static implicit operator Vector2d(Vector2 v)
{
return new Vector2d(v.x, v.y);
}
/// <summary>
/// Implicitly converts from an integer vector into a double-precision vector.
/// </summary>
/// <param name="v">The integer vector.</param>
public static implicit operator Vector2d(Vector2i v)
{
return new Vector2d(v.x, v.y);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x and y components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
public void Set(double x, double y)
{
this.x = x;
this.y = y;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector2d scale)
{
x *= scale.x;
y *= scale.y;
}
/// <summary>
/// Normalizes this vector.
/// </summary>
public void Normalize()
{
double mag = this.Magnitude;
if (mag > Epsilon)
{
x /= mag;
y /= mag;
}
else
{
x = y = 0;
}
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(double min, double max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector2d))
{
return false;
}
Vector2d vector = (Vector2d)other;
return (x == vector.x && y == vector.y);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector2d other)
{
return (x == other.x && y == other.y);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1})",
x.ToString("F1", CultureInfo.InvariantCulture),
y.ToString("F1", CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The float format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Dot Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
public static double Dot(ref Vector2d lhs, ref Vector2d rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y;
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="a">The vector to interpolate from.</param>
/// <param name="b">The vector to interpolate to.</param>
/// <param name="t">The time fraction.</param>
/// <param name="result">The resulting vector.</param>
public static void Lerp(ref Vector2d a, ref Vector2d b, double t, out Vector2d result)
{
result = new Vector2d(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
}
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector2d a, ref Vector2d b, out Vector2d result)
{
result = new Vector2d(a.x * b.x, a.y * b.y);
}
/// <summary>
/// Normalizes a vector.
/// </summary>
/// <param name="value">The vector to normalize.</param>
/// <param name="result">The resulting normalized vector.</param>
public static void Normalize(ref Vector2d value, out Vector2d result)
{
double mag = value.Magnitude;
if (mag > Epsilon)
{
result = new Vector2d(value.x / mag, value.y / mag);
}
else
{
result = Vector2d.zero;
}
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,348 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A 2D integer vector.
/// </summary>
public struct Vector2i : IEquatable<Vector2i>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector2i zero = new Vector2i(0, 0);
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public int x;
/// <summary>
/// The y component.
/// </summary>
public int y;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public int Magnitude
{
get { return (int)System.Math.Sqrt(x * x + y * y); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public int MagnitudeSqr
{
get { return (x * x + y * y); }
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public int this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
default:
throw new IndexOutOfRangeException("Invalid Vector2i index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector2i index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector2i(int value)
{
this.x = value;
this.y = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
public Vector2i(int x, int y)
{
this.x = x;
this.y = y;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2i operator +(Vector2i a, Vector2i b)
{
return new Vector2i(a.x + b.x, a.y + b.y);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2i operator -(Vector2i a, Vector2i b)
{
return new Vector2i(a.x - b.x, a.y - b.y);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector2i operator *(Vector2i a, int d)
{
return new Vector2i(a.x * d, a.y * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2i operator *(int d, Vector2i a)
{
return new Vector2i(a.x * d, a.y * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector2i operator /(Vector2i a, int d)
{
return new Vector2i(a.x / d, a.y / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector2i operator -(Vector2i a)
{
return new Vector2i(-a.x, -a.y);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector2i lhs, Vector2i rhs)
{
return (lhs.x == rhs.x && lhs.y == rhs.y);
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector2i lhs, Vector2i rhs)
{
return (lhs.x != rhs.x || lhs.y != rhs.y);
}
/// <summary>
/// Explicitly converts from a single-precision vector into an integer vector.
/// </summary>
/// <param name="v">The single-precision vector.</param>
public static explicit operator Vector2i(Vector2 v)
{
return new Vector2i((int)v.x, (int)v.y);
}
/// <summary>
/// Explicitly converts from a double-precision vector into an integer vector.
/// </summary>
/// <param name="v">The double-precision vector.</param>
public static explicit operator Vector2i(Vector2d v)
{
return new Vector2i((int)v.x, (int)v.y);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x and y components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
public void Set(int x, int y)
{
this.x = x;
this.y = y;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector2i scale)
{
x *= scale.x;
y *= scale.y;
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(int min, int max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector2i))
{
return false;
}
Vector2i vector = (Vector2i)other;
return (x == vector.x && y == vector.y);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector2i other)
{
return (x == other.x && y == other.y);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1})",
x.ToString(CultureInfo.InvariantCulture),
y.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The integer format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector2i a, ref Vector2i b, out Vector2i result)
{
result = new Vector2i(a.x * b.x, a.y * b.y);
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,494 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A single precision 3D vector.
/// </summary>
public struct Vector3 : IEquatable<Vector3>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector3 zero = new Vector3(0, 0, 0);
#endregion
#region Consts
/// <summary>
/// The vector epsilon.
/// </summary>
public const float Epsilon = 9.99999944E-11f;
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public float x;
/// <summary>
/// The y component.
/// </summary>
public float y;
/// <summary>
/// The z component.
/// </summary>
public float z;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public float Magnitude
{
get { return (float)System.Math.Sqrt(x * x + y * y + z * z); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public float MagnitudeSqr
{
get { return (x * x + y * y + z * z); }
}
/// <summary>
/// Gets a normalized vector from this vector.
/// </summary>
public Vector3 Normalized
{
get
{
Vector3 result;
Normalize(ref this, out result);
return result;
}
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public float this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector3(float value)
{
this.x = value;
this.y = value;
this.z = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
public Vector3(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
/// <summary>
/// Creates a new vector from a double precision vector.
/// </summary>
/// <param name="vector">The double precision vector.</param>
public Vector3(Vector3d vector)
{
this.x = (float)vector.x;
this.y = (float)vector.y;
this.z = (float)vector.z;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3 operator +(Vector3 a, Vector3 b)
{
return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3 operator -(Vector3 a, Vector3 b)
{
return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector3 operator *(Vector3 a, float d)
{
return new Vector3(a.x * d, a.y * d, a.z * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3 operator *(float d, Vector3 a)
{
return new Vector3(a.x * d, a.y * d, a.z * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector3 operator /(Vector3 a, float d)
{
return new Vector3(a.x / d, a.y / d, a.z / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3 operator -(Vector3 a)
{
return new Vector3(-a.x, -a.y, -a.z);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector3 lhs, Vector3 rhs)
{
return (lhs - rhs).MagnitudeSqr < Epsilon;
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector3 lhs, Vector3 rhs)
{
return (lhs - rhs).MagnitudeSqr >= Epsilon;
}
/// <summary>
/// Explicitly converts from a double-precision vector into a single-precision vector.
/// </summary>
/// <param name="v">The double-precision vector.</param>
public static explicit operator Vector3(Vector3d v)
{
return new Vector3((float)v.x, (float)v.y, (float)v.z);
}
/// <summary>
/// Implicitly converts from an integer vector into a single-precision vector.
/// </summary>
/// <param name="v">The integer vector.</param>
public static implicit operator Vector3(Vector3i v)
{
return new Vector3(v.x, v.y, v.z);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x, y and z components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
public void Set(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector3 scale)
{
x *= scale.x;
y *= scale.y;
z *= scale.z;
}
/// <summary>
/// Normalizes this vector.
/// </summary>
public void Normalize()
{
float mag = this.Magnitude;
if (mag > Epsilon)
{
x /= mag;
y /= mag;
z /= mag;
}
else
{
x = y = z = 0;
}
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(float min, float max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
if (z < min) z = min;
else if (z > max) z = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector3))
{
return false;
}
Vector3 vector = (Vector3)other;
return (x == vector.x && y == vector.y && z == vector.z);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector3 other)
{
return (x == other.x && y == other.y && z == other.z);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1}, {2})",
x.ToString("F1", CultureInfo.InvariantCulture),
y.ToString("F1", CultureInfo.InvariantCulture),
z.ToString("F1", CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The float format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture),
z.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Dot Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
public static float Dot(ref Vector3 lhs, ref Vector3 rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
}
/// <summary>
/// Cross Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Cross(ref Vector3 lhs, ref Vector3 rhs, out Vector3 result)
{
result = new Vector3(lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x);
}
/// <summary>
/// Calculates the angle between two vectors.
/// </summary>
/// <param name="from">The from vector.</param>
/// <param name="to">The to vector.</param>
/// <returns>The angle.</returns>
public static float Angle(ref Vector3 from, ref Vector3 to)
{
Vector3 fromNormalized = from.Normalized;
Vector3 toNormalized = to.Normalized;
return (float)System.Math.Acos(MathHelper.Clamp(Vector3.Dot(ref fromNormalized, ref toNormalized), -1f, 1f)) * MathHelper.Rad2Deg;
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="a">The vector to interpolate from.</param>
/// <param name="b">The vector to interpolate to.</param>
/// <param name="t">The time fraction.</param>
/// <param name="result">The resulting vector.</param>
public static void Lerp(ref Vector3 a, ref Vector3 b, float t, out Vector3 result)
{
result = new Vector3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);
}
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector3 a, ref Vector3 b, out Vector3 result)
{
result = new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
}
/// <summary>
/// Normalizes a vector.
/// </summary>
/// <param name="value">The vector to normalize.</param>
/// <param name="result">The resulting normalized vector.</param>
public static void Normalize(ref Vector3 value, out Vector3 result)
{
float mag = value.Magnitude;
if (mag > Epsilon)
{
result = new Vector3(value.x / mag, value.y / mag, value.z / mag);
}
else
{
result = Vector3.zero;
}
}
/// <summary>
/// Normalizes both vectors and makes them orthogonal to each other.
/// </summary>
/// <param name="normal">The normal vector.</param>
/// <param name="tangent">The tangent.</param>
public static void OrthoNormalize(ref Vector3 normal, ref Vector3 tangent)
{
normal.Normalize();
Vector3 proj = normal * Vector3.Dot(ref tangent, ref normal);
tangent -= proj;
tangent.Normalize();
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,481 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A double precision 3D vector.
/// </summary>
public struct Vector3d : IEquatable<Vector3d>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector3d zero = new Vector3d(0, 0, 0);
#endregion
#region Consts
/// <summary>
/// The vector epsilon.
/// </summary>
public const double Epsilon = double.Epsilon;
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public double x;
/// <summary>
/// The y component.
/// </summary>
public double y;
/// <summary>
/// The z component.
/// </summary>
public double z;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public double Magnitude
{
get { return System.Math.Sqrt(x * x + y * y + z * z); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public double MagnitudeSqr
{
get { return (x * x + y * y + z * z); }
}
/// <summary>
/// Gets a normalized vector from this vector.
/// </summary>
public Vector3d Normalized
{
get
{
Vector3d result;
Normalize(ref this, out result);
return result;
}
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public double this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
throw new IndexOutOfRangeException("Invalid Vector3d index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3d index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector3d(double value)
{
this.x = value;
this.y = value;
this.z = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
public Vector3d(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
/// <summary>
/// Creates a new vector from a single precision vector.
/// </summary>
/// <param name="vector">The single precision vector.</param>
public Vector3d(Vector3 vector)
{
this.x = vector.x;
this.y = vector.y;
this.z = vector.z;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3d operator +(Vector3d a, Vector3d b)
{
return new Vector3d(a.x + b.x, a.y + b.y, a.z + b.z);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3d operator -(Vector3d a, Vector3d b)
{
return new Vector3d(a.x - b.x, a.y - b.y, a.z - b.z);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector3d operator *(Vector3d a, double d)
{
return new Vector3d(a.x * d, a.y * d, a.z * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3d operator *(double d, Vector3d a)
{
return new Vector3d(a.x * d, a.y * d, a.z * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector3d operator /(Vector3d a, double d)
{
return new Vector3d(a.x / d, a.y / d, a.z / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3d operator -(Vector3d a)
{
return new Vector3d(-a.x, -a.y, -a.z);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector3d lhs, Vector3d rhs)
{
return (lhs - rhs).MagnitudeSqr < Epsilon;
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector3d lhs, Vector3d rhs)
{
return (lhs - rhs).MagnitudeSqr >= Epsilon;
}
/// <summary>
/// Implicitly converts from a single-precision vector into a double-precision vector.
/// </summary>
/// <param name="v">The single-precision vector.</param>
public static implicit operator Vector3d(Vector3 v)
{
return new Vector3d(v.x, v.y, v.z);
}
/// <summary>
/// Implicitly converts from an integer vector into a double-precision vector.
/// </summary>
/// <param name="v">The integer vector.</param>
public static implicit operator Vector3d(Vector3i v)
{
return new Vector3d(v.x, v.y, v.z);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x, y and z components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
public void Set(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector3d scale)
{
x *= scale.x;
y *= scale.y;
z *= scale.z;
}
/// <summary>
/// Normalizes this vector.
/// </summary>
public void Normalize()
{
double mag = this.Magnitude;
if (mag > Epsilon)
{
x /= mag;
y /= mag;
z /= mag;
}
else
{
x = y = z = 0;
}
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(double min, double max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
if (z < min) z = min;
else if (z > max) z = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector3d))
{
return false;
}
Vector3d vector = (Vector3d)other;
return (x == vector.x && y == vector.y && z == vector.z);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector3d other)
{
return (x == other.x && y == other.y && z == other.z);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1}, {2})",
x.ToString("F1", CultureInfo.InvariantCulture),
y.ToString("F1", CultureInfo.InvariantCulture),
z.ToString("F1", CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The float format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture),
z.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Dot Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
public static double Dot(ref Vector3d lhs, ref Vector3d rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
}
/// <summary>
/// Cross Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Cross(ref Vector3d lhs, ref Vector3d rhs, out Vector3d result)
{
result = new Vector3d(lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x);
}
/// <summary>
/// Calculates the angle between two vectors.
/// </summary>
/// <param name="from">The from vector.</param>
/// <param name="to">The to vector.</param>
/// <returns>The angle.</returns>
public static double Angle(ref Vector3d from, ref Vector3d to)
{
Vector3d fromNormalized = from.Normalized;
Vector3d toNormalized = to.Normalized;
return System.Math.Acos(MathHelper.Clamp(Vector3d.Dot(ref fromNormalized, ref toNormalized), -1.0, 1.0)) * MathHelper.Rad2Degd;
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="a">The vector to interpolate from.</param>
/// <param name="b">The vector to interpolate to.</param>
/// <param name="t">The time fraction.</param>
/// <param name="result">The resulting vector.</param>
public static void Lerp(ref Vector3d a, ref Vector3d b, double t, out Vector3d result)
{
result = new Vector3d(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);
}
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector3d a, ref Vector3d b, out Vector3d result)
{
result = new Vector3d(a.x * b.x, a.y * b.y, a.z * b.z);
}
/// <summary>
/// Normalizes a vector.
/// </summary>
/// <param name="value">The vector to normalize.</param>
/// <param name="result">The resulting normalized vector.</param>
public static void Normalize(ref Vector3d value, out Vector3d result)
{
double mag = value.Magnitude;
if (mag > Epsilon)
{
result = new Vector3d(value.x / mag, value.y / mag, value.z / mag);
}
else
{
result = Vector3d.zero;
}
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,368 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A 3D integer vector.
/// </summary>
public struct Vector3i : IEquatable<Vector3i>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector3i zero = new Vector3i(0, 0, 0);
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public int x;
/// <summary>
/// The y component.
/// </summary>
public int y;
/// <summary>
/// The z component.
/// </summary>
public int z;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public int Magnitude
{
get { return (int)System.Math.Sqrt(x * x + y * y + z * z); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public int MagnitudeSqr
{
get { return (x * x + y * y + z * z); }
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public int this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
throw new IndexOutOfRangeException("Invalid Vector3i index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3i index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector3i(int value)
{
this.x = value;
this.y = value;
this.z = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
public Vector3i(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3i operator +(Vector3i a, Vector3i b)
{
return new Vector3i(a.x + b.x, a.y + b.y, a.z + b.z);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3i operator -(Vector3i a, Vector3i b)
{
return new Vector3i(a.x - b.x, a.y - b.y, a.z - b.z);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector3i operator *(Vector3i a, int d)
{
return new Vector3i(a.x * d, a.y * d, a.z * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3i operator *(int d, Vector3i a)
{
return new Vector3i(a.x * d, a.y * d, a.z * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector3i operator /(Vector3i a, int d)
{
return new Vector3i(a.x / d, a.y / d, a.z / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector3i operator -(Vector3i a)
{
return new Vector3i(-a.x, -a.y, -a.z);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector3i lhs, Vector3i rhs)
{
return (lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z);
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector3i lhs, Vector3i rhs)
{
return (lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z);
}
/// <summary>
/// Explicitly converts from a single-precision vector into an integer vector.
/// </summary>
/// <param name="v">The single-precision vector.</param>
public static implicit operator Vector3i(Vector3 v)
{
return new Vector3i((int)v.x, (int)v.y, (int)v.z);
}
/// <summary>
/// Explicitly converts from a double-precision vector into an integer vector.
/// </summary>
/// <param name="v">The double-precision vector.</param>
public static explicit operator Vector3i(Vector3d v)
{
return new Vector3i((int)v.x, (int)v.y, (int)v.z);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x, y and z components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
public void Set(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector3i scale)
{
x *= scale.x;
y *= scale.y;
z *= scale.z;
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(int min, int max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
if (z < min) z = min;
else if (z > max) z = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector3i))
{
return false;
}
Vector3i vector = (Vector3i)other;
return (x == vector.x && y == vector.y && z == vector.z);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector3i other)
{
return (x == other.x && y == other.y && z == other.z);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1}, {2})",
x.ToString(CultureInfo.InvariantCulture),
y.ToString(CultureInfo.InvariantCulture),
z.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The integer format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture),
z.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector3i a, ref Vector3i b, out Vector3i result)
{
result = new Vector3i(a.x * b.x, a.y * b.y, a.z * b.z);
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,467 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A single precision 4D vector.
/// </summary>
public struct Vector4 : IEquatable<Vector4>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector4 zero = new Vector4(0, 0, 0, 0);
#endregion
#region Consts
/// <summary>
/// The vector epsilon.
/// </summary>
public const float Epsilon = 9.99999944E-11f;
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public float x;
/// <summary>
/// The y component.
/// </summary>
public float y;
/// <summary>
/// The z component.
/// </summary>
public float z;
/// <summary>
/// The w component.
/// </summary>
public float w;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public float Magnitude
{
get { return (float)System.Math.Sqrt(x * x + y * y + z * z + w * w); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public float MagnitudeSqr
{
get { return (x * x + y * y + z * z + w * w); }
}
/// <summary>
/// Gets a normalized vector from this vector.
/// </summary>
public Vector4 Normalized
{
get
{
Vector4 result;
Normalize(ref this, out result);
return result;
}
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public float this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
default:
throw new IndexOutOfRangeException("Invalid Vector4 index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
case 3:
w = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector4 index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector4(float value)
{
this.x = value;
this.y = value;
this.z = value;
this.w = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
/// <param name="w">The w value.</param>
public Vector4(float x, float y, float z, float w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4 operator +(Vector4 a, Vector4 b)
{
return new Vector4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4 operator -(Vector4 a, Vector4 b)
{
return new Vector4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector4 operator *(Vector4 a, float d)
{
return new Vector4(a.x * d, a.y * d, a.z * d, a.w * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4 operator *(float d, Vector4 a)
{
return new Vector4(a.x * d, a.y * d, a.z * d, a.w * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector4 operator /(Vector4 a, float d)
{
return new Vector4(a.x / d, a.y / d, a.z / d, a.w / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4 operator -(Vector4 a)
{
return new Vector4(-a.x, -a.y, -a.z, -a.w);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector4 lhs, Vector4 rhs)
{
return (lhs - rhs).MagnitudeSqr < Epsilon;
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector4 lhs, Vector4 rhs)
{
return (lhs - rhs).MagnitudeSqr >= Epsilon;
}
/// <summary>
/// Explicitly converts from a double-precision vector into a single-precision vector.
/// </summary>
/// <param name="v">The double-precision vector.</param>
public static explicit operator Vector4(Vector4d v)
{
return new Vector4((float)v.x, (float)v.y, (float)v.z, (float)v.w);
}
/// <summary>
/// Implicitly converts from an integer vector into a single-precision vector.
/// </summary>
/// <param name="v">The integer vector.</param>
public static implicit operator Vector4(Vector4i v)
{
return new Vector4(v.x, v.y, v.z, v.w);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x, y and z components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
/// <param name="w">The w value.</param>
public void Set(float x, float y, float z, float w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector4 scale)
{
x *= scale.x;
y *= scale.y;
z *= scale.z;
w *= scale.w;
}
/// <summary>
/// Normalizes this vector.
/// </summary>
public void Normalize()
{
float mag = this.Magnitude;
if (mag > Epsilon)
{
x /= mag;
y /= mag;
z /= mag;
w /= mag;
}
else
{
x = y = z = w = 0;
}
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(float min, float max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
if (z < min) z = min;
else if (z > max) z = max;
if (w < min) w = min;
else if (w > max) w = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2 ^ w.GetHashCode() >> 1;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector4))
{
return false;
}
Vector4 vector = (Vector4)other;
return (x == vector.x && y == vector.y && z == vector.z && w == vector.w);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector4 other)
{
return (x == other.x && y == other.y && z == other.z && w == other.w);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1}, {2}, {3})",
x.ToString("F1", CultureInfo.InvariantCulture),
y.ToString("F1", CultureInfo.InvariantCulture),
z.ToString("F1", CultureInfo.InvariantCulture),
w.ToString("F1", CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The float format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2}, {3})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture),
z.ToString(format, CultureInfo.InvariantCulture),
w.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Dot Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
public static float Dot(ref Vector4 lhs, ref Vector4 rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w;
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="a">The vector to interpolate from.</param>
/// <param name="b">The vector to interpolate to.</param>
/// <param name="t">The time fraction.</param>
/// <param name="result">The resulting vector.</param>
public static void Lerp(ref Vector4 a, ref Vector4 b, float t, out Vector4 result)
{
result = new Vector4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t);
}
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector4 a, ref Vector4 b, out Vector4 result)
{
result = new Vector4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
}
/// <summary>
/// Normalizes a vector.
/// </summary>
/// <param name="value">The vector to normalize.</param>
/// <param name="result">The resulting normalized vector.</param>
public static void Normalize(ref Vector4 value, out Vector4 result)
{
float mag = value.Magnitude;
if (mag > Epsilon)
{
result = new Vector4(value.x / mag, value.y / mag, value.z / mag, value.w / mag);
}
else
{
result = Vector4.zero;
}
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,467 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A double precision 4D vector.
/// </summary>
public struct Vector4d : IEquatable<Vector4d>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector4d zero = new Vector4d(0, 0, 0, 0);
#endregion
#region Consts
/// <summary>
/// The vector epsilon.
/// </summary>
public const double Epsilon = double.Epsilon;
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public double x;
/// <summary>
/// The y component.
/// </summary>
public double y;
/// <summary>
/// The z component.
/// </summary>
public double z;
/// <summary>
/// The w component.
/// </summary>
public double w;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public double Magnitude
{
get { return System.Math.Sqrt(x * x + y * y + z * z + w * w); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public double MagnitudeSqr
{
get { return (x * x + y * y + z * z + w * w); }
}
/// <summary>
/// Gets a normalized vector from this vector.
/// </summary>
public Vector4d Normalized
{
get
{
Vector4d result;
Normalize(ref this, out result);
return result;
}
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public double this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
default:
throw new IndexOutOfRangeException("Invalid Vector4d index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
case 3:
w = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector4d index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector4d(double value)
{
this.x = value;
this.y = value;
this.z = value;
this.w = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
/// <param name="w">The w value.</param>
public Vector4d(double x, double y, double z, double w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4d operator +(Vector4d a, Vector4d b)
{
return new Vector4d(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4d operator -(Vector4d a, Vector4d b)
{
return new Vector4d(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector4d operator *(Vector4d a, double d)
{
return new Vector4d(a.x * d, a.y * d, a.z * d, a.w * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4d operator *(double d, Vector4d a)
{
return new Vector4d(a.x * d, a.y * d, a.z * d, a.w * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector4d operator /(Vector4d a, double d)
{
return new Vector4d(a.x / d, a.y / d, a.z / d, a.w / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4d operator -(Vector4d a)
{
return new Vector4d(-a.x, -a.y, -a.z, -a.w);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector4d lhs, Vector4d rhs)
{
return (lhs - rhs).MagnitudeSqr < Epsilon;
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector4d lhs, Vector4d rhs)
{
return (lhs - rhs).MagnitudeSqr >= Epsilon;
}
/// <summary>
/// Implicitly converts from a single-precision vector into a double-precision vector.
/// </summary>
/// <param name="v">The single-precision vector.</param>
public static implicit operator Vector4d(Vector4 v)
{
return new Vector4d(v.x, v.y, v.z, v.w);
}
/// <summary>
/// Implicitly converts from an integer vector into a double-precision vector.
/// </summary>
/// <param name="v">The integer vector.</param>
public static implicit operator Vector4d(Vector4i v)
{
return new Vector4d(v.x, v.y, v.z, v.w);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x, y and z components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
/// <param name="w">The w value.</param>
public void Set(double x, double y, double z, double w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector4d scale)
{
x *= scale.x;
y *= scale.y;
z *= scale.z;
w *= scale.w;
}
/// <summary>
/// Normalizes this vector.
/// </summary>
public void Normalize()
{
double mag = this.Magnitude;
if (mag > Epsilon)
{
x /= mag;
y /= mag;
z /= mag;
w /= mag;
}
else
{
x = y = z = w = 0;
}
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(double min, double max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
if (z < min) z = min;
else if (z > max) z = max;
if (w < min) w = min;
else if (w > max) w = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2 ^ w.GetHashCode() >> 1;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector4d))
{
return false;
}
Vector4d vector = (Vector4d)other;
return (x == vector.x && y == vector.y && z == vector.z && w == vector.w);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector4d other)
{
return (x == other.x && y == other.y && z == other.z && w == other.w);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1}, {2}, {3})",
x.ToString("F1", CultureInfo.InvariantCulture),
y.ToString("F1", CultureInfo.InvariantCulture),
z.ToString("F1", CultureInfo.InvariantCulture),
w.ToString("F1", CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The float format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2}, {3})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture),
z.ToString(format, CultureInfo.InvariantCulture),
w.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Dot Product of two vectors.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
public static double Dot(ref Vector4d lhs, ref Vector4d rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w;
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="a">The vector to interpolate from.</param>
/// <param name="b">The vector to interpolate to.</param>
/// <param name="t">The time fraction.</param>
/// <param name="result">The resulting vector.</param>
public static void Lerp(ref Vector4d a, ref Vector4d b, double t, out Vector4d result)
{
result = new Vector4d(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t);
}
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector4d a, ref Vector4d b, out Vector4d result)
{
result = new Vector4d(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
}
/// <summary>
/// Normalizes a vector.
/// </summary>
/// <param name="value">The vector to normalize.</param>
/// <param name="result">The resulting normalized vector.</param>
public static void Normalize(ref Vector4d value, out Vector4d result)
{
double mag = value.Magnitude;
if (mag > Epsilon)
{
result = new Vector4d(value.x / mag, value.y / mag, value.z / mag, value.w / mag);
}
else
{
result = Vector4d.zero;
}
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,388 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Globalization;
namespace MeshDecimator.Math
{
/// <summary>
/// A 4D integer vector.
/// </summary>
public struct Vector4i : IEquatable<Vector4i>
{
#region Static Read-Only
/// <summary>
/// The zero vector.
/// </summary>
public static readonly Vector4i zero = new Vector4i(0, 0, 0, 0);
#endregion
#region Fields
/// <summary>
/// The x component.
/// </summary>
public int x;
/// <summary>
/// The y component.
/// </summary>
public int y;
/// <summary>
/// The z component.
/// </summary>
public int z;
/// <summary>
/// The w component.
/// </summary>
public int w;
#endregion
#region Properties
/// <summary>
/// Gets the magnitude of this vector.
/// </summary>
public int Magnitude
{
get { return (int)System.Math.Sqrt(x * x + y * y + z * z + w * w); }
}
/// <summary>
/// Gets the squared magnitude of this vector.
/// </summary>
public int MagnitudeSqr
{
get { return (x * x + y * y + z * z + w * w); }
}
/// <summary>
/// Gets or sets a specific component by index in this vector.
/// </summary>
/// <param name="index">The component index.</param>
public int this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
default:
throw new IndexOutOfRangeException("Invalid Vector4i index!");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
case 3:
w = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector4i index!");
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new vector with one value for all components.
/// </summary>
/// <param name="value">The value.</param>
public Vector4i(int value)
{
this.x = value;
this.y = value;
this.z = value;
this.w = value;
}
/// <summary>
/// Creates a new vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
/// <param name="w">The w value.</param>
public Vector4i(int x, int y, int z, int w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
#endregion
#region Operators
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4i operator +(Vector4i a, Vector4i b)
{
return new Vector4i(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
}
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4i operator -(Vector4i a, Vector4i b)
{
return new Vector4i(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The scaling value.</param>
/// <returns>The resulting vector.</returns>
public static Vector4i operator *(Vector4i a, int d)
{
return new Vector4i(a.x * d, a.y * d, a.z * d, a.w * d);
}
/// <summary>
/// Scales the vector uniformly.
/// </summary>
/// <param name="d">The scaling value.</param>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4i operator *(int d, Vector4i a)
{
return new Vector4i(a.x * d, a.y * d, a.z * d, a.w * d);
}
/// <summary>
/// Divides the vector with a float.
/// </summary>
/// <param name="a">The vector.</param>
/// <param name="d">The dividing float value.</param>
/// <returns>The resulting vector.</returns>
public static Vector4i operator /(Vector4i a, int d)
{
return new Vector4i(a.x / d, a.y / d, a.z / d, a.w / d);
}
/// <summary>
/// Subtracts the vector from a zero vector.
/// </summary>
/// <param name="a">The vector.</param>
/// <returns>The resulting vector.</returns>
public static Vector4i operator -(Vector4i a)
{
return new Vector4i(-a.x, -a.y, -a.z, -a.w);
}
/// <summary>
/// Returns if two vectors equals eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If equals.</returns>
public static bool operator ==(Vector4i lhs, Vector4i rhs)
{
return (lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w);
}
/// <summary>
/// Returns if two vectors don't equal eachother.
/// </summary>
/// <param name="lhs">The left hand side vector.</param>
/// <param name="rhs">The right hand side vector.</param>
/// <returns>If not equals.</returns>
public static bool operator !=(Vector4i lhs, Vector4i rhs)
{
return (lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w);
}
/// <summary>
/// Explicitly converts from a single-precision vector into an integer vector.
/// </summary>
/// <param name="v">The single-precision vector.</param>
public static explicit operator Vector4i(Vector4 v)
{
return new Vector4i((int)v.x, (int)v.y, (int)v.z, (int)v.w);
}
/// <summary>
/// Explicitly converts from a double-precision vector into an integer vector.
/// </summary>
/// <param name="v">The double-precision vector.</param>
public static explicit operator Vector4i(Vector4d v)
{
return new Vector4i((int)v.x, (int)v.y, (int)v.z, (int)v.w);
}
#endregion
#region Public Methods
#region Instance
/// <summary>
/// Set x, y and z components of an existing vector.
/// </summary>
/// <param name="x">The x value.</param>
/// <param name="y">The y value.</param>
/// <param name="z">The z value.</param>
/// <param name="w">The w value.</param>
public void Set(int x, int y, int z, int w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/// <summary>
/// Multiplies with another vector component-wise.
/// </summary>
/// <param name="scale">The vector to multiply with.</param>
public void Scale(ref Vector4i scale)
{
x *= scale.x;
y *= scale.y;
z *= scale.z;
w *= scale.w;
}
/// <summary>
/// Clamps this vector between a specific range.
/// </summary>
/// <param name="min">The minimum component value.</param>
/// <param name="max">The maximum component value.</param>
public void Clamp(int min, int max)
{
if (x < min) x = min;
else if (x > max) x = max;
if (y < min) y = min;
else if (y > max) y = max;
if (z < min) z = min;
else if (z > max) z = max;
if (w < min) w = min;
else if (w > max) w = max;
}
#endregion
#region Object
/// <summary>
/// Returns a hash code for this vector.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2 ^ w.GetHashCode() >> 1;
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public override bool Equals(object other)
{
if (!(other is Vector4i))
{
return false;
}
Vector4i vector = (Vector4i)other;
return (x == vector.x && y == vector.y && z == vector.z && w == vector.w);
}
/// <summary>
/// Returns if this vector is equal to another one.
/// </summary>
/// <param name="other">The other vector to compare to.</param>
/// <returns>If equals.</returns>
public bool Equals(Vector4i other)
{
return (x == other.x && y == other.y && z == other.z && w == other.w);
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
return string.Format("({0}, {1}, {2}, {3})",
x.ToString(CultureInfo.InvariantCulture),
y.ToString(CultureInfo.InvariantCulture),
z.ToString(CultureInfo.InvariantCulture),
w.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns a nicely formatted string for this vector.
/// </summary>
/// <param name="format">The integer format.</param>
/// <returns>The string.</returns>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2}, {3})",
x.ToString(format, CultureInfo.InvariantCulture),
y.ToString(format, CultureInfo.InvariantCulture),
z.ToString(format, CultureInfo.InvariantCulture),
w.ToString(format, CultureInfo.InvariantCulture));
}
#endregion
#region Static
/// <summary>
/// Multiplies two vectors component-wise.
/// </summary>
/// <param name="a">The first vector.</param>
/// <param name="b">The second vector.</param>
/// <param name="result">The resulting vector.</param>
public static void Scale(ref Vector4i a, ref Vector4i b, out Vector4i result)
{
result = new Vector4i(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,955 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using System.Collections.Generic;
using MeshDecimator.Math;
namespace MeshDecimator
{
/// <summary>
/// A mesh.
/// </summary>
public sealed class Mesh
{
#region Consts
/// <summary>
/// The count of supported UV channels.
/// </summary>
public const int UVChannelCount = 4;
#endregion
#region Fields
private Vector3d[] vertices = null;
private int[][] indices = null;
private Vector3[] normals = null;
private Vector4[] tangents = null;
private Vector2[][] uvs2D = null;
private Vector3[][] uvs3D = null;
private Vector4[][] uvs4D = null;
private Vector4[] colors = null;
private BoneWeight[] boneWeights = null;
private static readonly int[] emptyIndices = new int[0];
#endregion
#region Properties
/// <summary>
/// Gets the count of vertices of this mesh.
/// </summary>
public int VertexCount
{
get { return vertices.Length; }
}
/// <summary>
/// Gets or sets the count of submeshes in this mesh.
/// </summary>
public int SubMeshCount
{
get { return indices.Length; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("value");
int[][] newIndices = new int[value][];
Array.Copy(indices, 0, newIndices, 0, MathHelper.Min(indices.Length, newIndices.Length));
indices = newIndices;
}
}
/// <summary>
/// Gets the total count of triangles in this mesh.
/// </summary>
public int TriangleCount
{
get
{
int triangleCount = 0;
for (int i = 0; i < indices.Length; i++)
{
if (indices[i] != null)
{
triangleCount += indices[i].Length / 3;
}
}
return triangleCount;
}
}
/// <summary>
/// Gets or sets the vertices for this mesh. Note that this resets all other vertex attributes.
/// </summary>
public Vector3d[] Vertices
{
get { return vertices; }
set
{
if (value == null)
throw new ArgumentNullException("value");
vertices = value;
ClearVertexAttributes();
}
}
/// <summary>
/// Gets or sets the combined indices for this mesh. Once set, the sub-mesh count gets set to 1.
/// </summary>
public int[] Indices
{
get
{
if (indices.Length == 1)
{
return indices[0] ?? emptyIndices;
}
else
{
List<int> indexList = new List<int>(TriangleCount * 3);
for (int i = 0; i < indices.Length; i++)
{
if (indices[i] != null)
{
indexList.AddRange(indices[i]);
}
}
return indexList.ToArray();
}
}
set
{
if (value == null)
throw new ArgumentNullException("value");
else if ((value.Length % 3) != 0)
throw new ArgumentException("The index count must be multiple by 3.", "value");
SubMeshCount = 1;
SetIndices(0, value);
}
}
/// <summary>
/// Gets or sets the normals for this mesh.
/// </summary>
public Vector3[] Normals
{
get { return normals; }
set
{
if (value != null && value.Length != vertices.Length)
throw new ArgumentException(string.Format("The vertex normals must be as many as the vertices. Assigned: {0} Require: {1}", value.Length, vertices.Length));
normals = value;
}
}
/// <summary>
/// Gets or sets the tangents for this mesh.
/// </summary>
public Vector4[] Tangents
{
get { return tangents; }
set
{
if (value != null && value.Length != vertices.Length)
throw new ArgumentException(string.Format("The vertex tangents must be as many as the vertices. Assigned: {0} Require: {1}", value.Length, vertices.Length));
tangents = value;
}
}
/// <summary>
/// Gets or sets the first UV set for this mesh.
/// </summary>
public Vector2[] UV1
{
get { return GetUVs2D(0); }
set { SetUVs(0, value); }
}
/// <summary>
/// Gets or sets the second UV set for this mesh.
/// </summary>
public Vector2[] UV2
{
get { return GetUVs2D(1); }
set { SetUVs(1, value); }
}
/// <summary>
/// Gets or sets the third UV set for this mesh.
/// </summary>
public Vector2[] UV3
{
get { return GetUVs2D(2); }
set { SetUVs(2, value); }
}
/// <summary>
/// Gets or sets the fourth UV set for this mesh.
/// </summary>
public Vector2[] UV4
{
get { return GetUVs2D(3); }
set { SetUVs(3, value); }
}
/// <summary>
/// Gets or sets the vertex colors for this mesh.
/// </summary>
public Vector4[] Colors
{
get { return colors; }
set
{
if (value != null && value.Length != vertices.Length)
throw new ArgumentException(string.Format("The vertex colors must be as many as the vertices. Assigned: {0} Require: {1}", value.Length, vertices.Length));
colors = value;
}
}
/// <summary>
/// Gets or sets the vertex bone weights for this mesh.
/// </summary>
public BoneWeight[] BoneWeights
{
get { return boneWeights; }
set
{
if (value != null && value.Length != vertices.Length)
throw new ArgumentException(string.Format("The vertex bone weights must be as many as the vertices. Assigned: {0} Require: {1}", value.Length, vertices.Length));
boneWeights = value;
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new mesh.
/// </summary>
/// <param name="vertices">The mesh vertices.</param>
/// <param name="indices">The mesh indices.</param>
public Mesh(Vector3d[] vertices, int[] indices)
{
if (vertices == null)
throw new ArgumentNullException("vertices");
else if (indices == null)
throw new ArgumentNullException("indices");
else if ((indices.Length % 3) != 0)
throw new ArgumentException("The index count must be multiple by 3.", "indices");
this.vertices = vertices;
this.indices = new int[1][];
this.indices[0] = indices;
}
/// <summary>
/// Creates a new mesh.
/// </summary>
/// <param name="vertices">The mesh vertices.</param>
/// <param name="indices">The mesh indices.</param>
public Mesh(Vector3d[] vertices, int[][] indices)
{
if (vertices == null)
throw new ArgumentNullException("vertices");
else if (indices == null)
throw new ArgumentNullException("indices");
for (int i = 0; i < indices.Length; i++)
{
if (indices[i] != null && (indices[i].Length % 3) != 0)
throw new ArgumentException(string.Format("The index count must be multiple by 3 at sub-mesh index {0}.", i), "indices");
}
this.vertices = vertices;
this.indices = indices;
}
#endregion
#region Private Methods
private void ClearVertexAttributes()
{
normals = null;
tangents = null;
uvs2D = null;
uvs3D = null;
uvs4D = null;
colors = null;
boneWeights = null;
}
#endregion
#region Public Methods
#region Recalculate Normals
/// <summary>
/// Recalculates the normals for this mesh smoothly.
/// </summary>
public void RecalculateNormals()
{
int vertexCount = vertices.Length;
Vector3[] normals = new Vector3[vertexCount];
int subMeshCount = this.indices.Length;
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
{
int[] indices = this.indices[subMeshIndex];
if (indices == null)
continue;
int indexCount = indices.Length;
for (int i = 0; i < indexCount; i += 3)
{
int i0 = indices[i];
int i1 = indices[i + 1];
int i2 = indices[i + 2];
var v0 = (Vector3)vertices[i0];
var v1 = (Vector3)vertices[i1];
var v2 = (Vector3)vertices[i2];
var nx = v1 - v0;
var ny = v2 - v0;
Vector3 normal;
Vector3.Cross(ref nx, ref ny, out normal);
normal.Normalize();
normals[i0] += normal;
normals[i1] += normal;
normals[i2] += normal;
}
}
for (int i = 0; i < vertexCount; i++)
{
normals[i].Normalize();
}
this.normals = normals;
}
#endregion
#region Recalculate Tangents
/// <summary>
/// Recalculates the tangents for this mesh.
/// </summary>
public void RecalculateTangents()
{
// Make sure we have the normals first
if (normals == null)
return;
// Also make sure that we have the first UV set
bool uvIs2D = (uvs2D != null && uvs2D[0] != null);
bool uvIs3D = (uvs3D != null && uvs3D[0] != null);
bool uvIs4D = (uvs4D != null && uvs4D[0] != null);
if (!uvIs2D && !uvIs3D && !uvIs4D)
return;
int vertexCount = vertices.Length;
var tangents = new Vector4[vertexCount];
var tan1 = new Vector3[vertexCount];
var tan2 = new Vector3[vertexCount];
Vector2[] uv2D = (uvIs2D ? uvs2D[0] : null);
Vector3[] uv3D = (uvIs3D ? uvs3D[0] : null);
Vector4[] uv4D = (uvIs4D ? uvs4D[0] : null);
int subMeshCount = this.indices.Length;
for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++)
{
int[] indices = this.indices[subMeshIndex];
if (indices == null)
continue;
int indexCount = indices.Length;
for (int i = 0; i < indexCount; i += 3)
{
int i0 = indices[i];
int i1 = indices[i + 1];
int i2 = indices[i + 2];
var v0 = vertices[i0];
var v1 = vertices[i1];
var v2 = vertices[i2];
float s1, s2, t1, t2;
if (uvIs2D)
{
var w0 = uv2D[i0];
var w1 = uv2D[i1];
var w2 = uv2D[i2];
s1 = w1.x - w0.x;
s2 = w2.x - w0.x;
t1 = w1.y - w0.y;
t2 = w2.y - w0.y;
}
else if (uvIs3D)
{
var w0 = uv3D[i0];
var w1 = uv3D[i1];
var w2 = uv3D[i2];
s1 = w1.x - w0.x;
s2 = w2.x - w0.x;
t1 = w1.y - w0.y;
t2 = w2.y - w0.y;
}
else
{
var w0 = uv4D[i0];
var w1 = uv4D[i1];
var w2 = uv4D[i2];
s1 = w1.x - w0.x;
s2 = w2.x - w0.x;
t1 = w1.y - w0.y;
t2 = w2.y - w0.y;
}
float x1 = (float)(v1.x - v0.x);
float x2 = (float)(v2.x - v0.x);
float y1 = (float)(v1.y - v0.y);
float y2 = (float)(v2.y - v0.y);
float z1 = (float)(v1.z - v0.z);
float z2 = (float)(v2.z - v0.z);
float r = 1f / (s1 * t2 - s2 * t1);
var sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
var tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i0] += sdir;
tan1[i1] += sdir;
tan1[i2] += sdir;
tan2[i0] += tdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
}
}
for (int i = 0; i < vertexCount; i++)
{
var n = normals[i];
var t = tan1[i];
var tmp = (t - n * Vector3.Dot(ref n, ref t));
tmp.Normalize();
Vector3 c;
Vector3.Cross(ref n, ref t, out c);
float dot = Vector3.Dot(ref c, ref tan2[i]);
float w = (dot < 0f ? -1f : 1f);
tangents[i] = new Vector4(tmp.x, tmp.y, tmp.z, w);
}
this.tangents = tangents;
}
#endregion
#region Triangles
/// <summary>
/// Returns the count of triangles for a specific sub-mesh in this mesh.
/// </summary>
/// <param name="subMeshIndex">The sub-mesh index.</param>
/// <returns>The triangle count.</returns>
public int GetTriangleCount(int subMeshIndex)
{
if (subMeshIndex < 0 || subMeshIndex >= indices.Length)
throw new IndexOutOfRangeException();
return indices[subMeshIndex].Length / 3;
}
/// <summary>
/// Returns the triangle indices of a specific sub-mesh in this mesh.
/// </summary>
/// <param name="subMeshIndex">The sub-mesh index.</param>
/// <returns>The triangle indices.</returns>
public int[] GetIndices(int subMeshIndex)
{
if (subMeshIndex < 0 || subMeshIndex >= indices.Length)
throw new IndexOutOfRangeException();
return indices[subMeshIndex] ?? emptyIndices;
}
/// <summary>
/// Returns the triangle indices for all sub-meshes in this mesh.
/// </summary>
/// <returns>The sub-mesh triangle indices.</returns>
public int[][] GetSubMeshIndices()
{
var subMeshIndices = new int[indices.Length][];
for (int subMeshIndex = 0; subMeshIndex < indices.Length; subMeshIndex++)
{
subMeshIndices[subMeshIndex] = indices[subMeshIndex] ?? emptyIndices;
}
return subMeshIndices;
}
/// <summary>
/// Sets the triangle indices of a specific sub-mesh in this mesh.
/// </summary>
/// <param name="subMeshIndex">The sub-mesh index.</param>
/// <param name="indices">The triangle indices.</param>
public void SetIndices(int subMeshIndex, int[] indices)
{
if (subMeshIndex < 0 || subMeshIndex >= this.indices.Length)
throw new IndexOutOfRangeException();
else if (indices == null)
throw new ArgumentNullException("indices");
else if ((indices.Length % 3) != 0)
throw new ArgumentException("The index count must be multiple by 3.", "indices");
this.indices[subMeshIndex] = indices;
}
#endregion
#region UV Sets
#region Getting
/// <summary>
/// Returns the UV dimension for a specific channel.
/// </summary>
/// <param name="channel"></param>
/// <returns>The UV dimension count.</returns>
public int GetUVDimension(int channel)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs2D != null && uvs2D[channel] != null)
{
return 2;
}
else if (uvs3D != null && uvs3D[channel] != null)
{
return 3;
}
else if (uvs4D != null && uvs4D[channel] != null)
{
return 4;
}
else
{
return 0;
}
}
/// <summary>
/// Returns the UVs (2D) from a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <returns>The UVs.</returns>
public Vector2[] GetUVs2D(int channel)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs2D != null && uvs2D[channel] != null)
{
return uvs2D[channel];
}
else
{
return null;
}
}
/// <summary>
/// Returns the UVs (3D) from a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <returns>The UVs.</returns>
public Vector3[] GetUVs3D(int channel)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs3D != null && uvs3D[channel] != null)
{
return uvs3D[channel];
}
else
{
return null;
}
}
/// <summary>
/// Returns the UVs (4D) from a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <returns>The UVs.</returns>
public Vector4[] GetUVs4D(int channel)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs4D != null && uvs4D[channel] != null)
{
return uvs4D[channel];
}
else
{
return null;
}
}
/// <summary>
/// Returns the UVs (2D) from a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void GetUVs(int channel, List<Vector2> uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
else if (uvs == null)
throw new ArgumentNullException("uvs");
uvs.Clear();
if (uvs2D != null && uvs2D[channel] != null)
{
var uvData = uvs2D[channel];
if (uvData != null)
{
uvs.AddRange(uvData);
}
}
}
/// <summary>
/// Returns the UVs (3D) from a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void GetUVs(int channel, List<Vector3> uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
else if (uvs == null)
throw new ArgumentNullException("uvs");
uvs.Clear();
if (uvs3D != null && uvs3D[channel] != null)
{
var uvData = uvs3D[channel];
if (uvData != null)
{
uvs.AddRange(uvData);
}
}
}
/// <summary>
/// Returns the UVs (4D) from a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void GetUVs(int channel, List<Vector4> uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
else if (uvs == null)
throw new ArgumentNullException("uvs");
uvs.Clear();
if (uvs4D != null && uvs4D[channel] != null)
{
var uvData = uvs4D[channel];
if (uvData != null)
{
uvs.AddRange(uvData);
}
}
}
#endregion
#region Setting
/// <summary>
/// Sets the UVs (2D) for a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void SetUVs(int channel, Vector2[] uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs != null && uvs.Length > 0)
{
if (uvs.Length != vertices.Length)
throw new ArgumentException(string.Format("The vertex UVs must be as many as the vertices. Assigned: {0} Require: {1}", uvs.Length, vertices.Length));
if (uvs2D == null)
uvs2D = new Vector2[UVChannelCount][];
int uvCount = uvs.Length;
var uvSet = new Vector2[uvCount];
uvs2D[channel] = uvSet;
uvs.CopyTo(uvSet, 0);
}
else
{
if (uvs2D != null)
{
uvs2D[channel] = null;
}
}
if (uvs3D != null)
{
uvs3D[channel] = null;
}
if (uvs4D != null)
{
uvs4D[channel] = null;
}
}
/// <summary>
/// Sets the UVs (3D) for a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void SetUVs(int channel, Vector3[] uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs != null && uvs.Length > 0)
{
int uvCount = uvs.Length;
if (uvCount != vertices.Length)
throw new ArgumentException(string.Format("The vertex UVs must be as many as the vertices. Assigned: {0} Require: {1}", uvCount, vertices.Length), "uvs");
if (uvs3D == null)
uvs3D = new Vector3[UVChannelCount][];
var uvSet = new Vector3[uvCount];
uvs3D[channel] = uvSet;
uvs.CopyTo(uvSet, 0);
}
else
{
if (uvs3D != null)
{
uvs3D[channel] = null;
}
}
if (uvs2D != null)
{
uvs2D[channel] = null;
}
if (uvs4D != null)
{
uvs4D[channel] = null;
}
}
/// <summary>
/// Sets the UVs (4D) for a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void SetUVs(int channel, Vector4[] uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs != null && uvs.Length > 0)
{
int uvCount = uvs.Length;
if (uvCount != vertices.Length)
throw new ArgumentException(string.Format("The vertex UVs must be as many as the vertices. Assigned: {0} Require: {1}", uvCount, vertices.Length), "uvs");
if (uvs4D == null)
uvs4D = new Vector4[UVChannelCount][];
var uvSet = new Vector4[uvCount];
uvs4D[channel] = uvSet;
uvs.CopyTo(uvSet, 0);
}
else
{
if (uvs4D != null)
{
uvs4D[channel] = null;
}
}
if (uvs2D != null)
{
uvs2D[channel] = null;
}
if (uvs3D != null)
{
uvs3D[channel] = null;
}
}
/// <summary>
/// Sets the UVs (2D) for a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void SetUVs(int channel, List<Vector2> uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs != null && uvs.Count > 0)
{
int uvCount = uvs.Count;
if (uvCount != vertices.Length)
throw new ArgumentException(string.Format("The vertex UVs must be as many as the vertices. Assigned: {0} Require: {1}", uvCount, vertices.Length), "uvs");
if (uvs2D == null)
uvs2D = new Vector2[UVChannelCount][];
var uvSet = new Vector2[uvCount];
uvs2D[channel] = uvSet;
uvs.CopyTo(uvSet, 0);
}
else
{
if (uvs2D != null)
{
uvs2D[channel] = null;
}
}
if (uvs3D != null)
{
uvs3D[channel] = null;
}
if (uvs4D != null)
{
uvs4D[channel] = null;
}
}
/// <summary>
/// Sets the UVs (3D) for a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void SetUVs(int channel, List<Vector3> uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs != null && uvs.Count > 0)
{
int uvCount = uvs.Count;
if (uvCount != vertices.Length)
throw new ArgumentException(string.Format("The vertex UVs must be as many as the vertices. Assigned: {0} Require: {1}", uvCount, vertices.Length), "uvs");
if (uvs3D == null)
uvs3D = new Vector3[UVChannelCount][];
var uvSet = new Vector3[uvCount];
uvs3D[channel] = uvSet;
uvs.CopyTo(uvSet, 0);
}
else
{
if (uvs3D != null)
{
uvs3D[channel] = null;
}
}
if (uvs2D != null)
{
uvs2D[channel] = null;
}
if (uvs4D != null)
{
uvs4D[channel] = null;
}
}
/// <summary>
/// Sets the UVs (4D) for a specific channel.
/// </summary>
/// <param name="channel">The channel index.</param>
/// <param name="uvs">The UVs.</param>
public void SetUVs(int channel, List<Vector4> uvs)
{
if (channel < 0 || channel >= UVChannelCount)
throw new ArgumentOutOfRangeException("channel");
if (uvs != null && uvs.Count > 0)
{
int uvCount = uvs.Count;
if (uvCount != vertices.Length)
throw new ArgumentException(string.Format("The vertex UVs must be as many as the vertices. Assigned: {0} Require: {1}", uvCount, vertices.Length), "uvs");
if (uvs4D == null)
uvs4D = new Vector4[UVChannelCount][];
var uvSet = new Vector4[uvCount];
uvs4D[channel] = uvSet;
uvs.CopyTo(uvSet, 0);
}
else
{
if (uvs4D != null)
{
uvs4D[channel] = null;
}
}
if (uvs2D != null)
{
uvs2D[channel] = null;
}
if (uvs3D != null)
{
uvs3D[channel] = null;
}
}
#endregion
#endregion
#region To String
/// <summary>
/// Returns the text-representation of this mesh.
/// </summary>
/// <returns>The text-representation.</returns>
public override string ToString()
{
return string.Format("Vertices: {0}", vertices.Length);
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,180 @@
#region License
/*
MIT License
Copyright(c) 2017-2018 Mattias Edlund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
using System;
using MeshDecimator.Algorithms;
namespace MeshDecimator
{
#region Algorithm
/// <summary>
/// The decimation algorithms.
/// </summary>
public enum Algorithm
{
/// <summary>
/// The default algorithm.
/// </summary>
Default,
/// <summary>
/// The fast quadric mesh simplification algorithm.
/// </summary>
FastQuadricMesh
}
#endregion
/// <summary>
/// The mesh decimation API.
/// </summary>
public static class MeshDecimation
{
#region Public Methods
#region Create Algorithm
/// <summary>
/// Creates a specific decimation algorithm.
/// </summary>
/// <param name="algorithm">The desired algorithm.</param>
/// <returns>The decimation algorithm.</returns>
public static DecimationAlgorithm CreateAlgorithm(Algorithm algorithm)
{
DecimationAlgorithm alg = null;
switch (algorithm)
{
case Algorithm.Default:
case Algorithm.FastQuadricMesh:
alg = new FastQuadricMeshSimplification();
break;
default:
throw new ArgumentException("The specified algorithm is not supported.", "algorithm");
}
return alg;
}
#endregion
#region Decimate Mesh
/// <summary>
/// Decimates a mesh.
/// </summary>
/// <param name="mesh">The mesh to decimate.</param>
/// <param name="targetTriangleCount">The target triangle count.</param>
/// <returns>The decimated mesh.</returns>
public static Mesh DecimateMesh(Mesh mesh, int targetTriangleCount)
{
return DecimateMesh(Algorithm.Default, mesh, targetTriangleCount);
}
/// <summary>
/// Decimates a mesh.
/// </summary>
/// <param name="algorithm">The desired algorithm.</param>
/// <param name="mesh">The mesh to decimate.</param>
/// <param name="targetTriangleCount">The target triangle count.</param>
/// <returns>The decimated mesh.</returns>
public static Mesh DecimateMesh(Algorithm algorithm, Mesh mesh, int targetTriangleCount)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
var decimationAlgorithm = CreateAlgorithm(algorithm);
return DecimateMesh(decimationAlgorithm, mesh, targetTriangleCount);
}
/// <summary>
/// Decimates a mesh.
/// </summary>
/// <param name="algorithm">The decimation algorithm.</param>
/// <param name="mesh">The mesh to decimate.</param>
/// <param name="targetTriangleCount">The target triangle count.</param>
/// <returns>The decimated mesh.</returns>
public static Mesh DecimateMesh(DecimationAlgorithm algorithm, Mesh mesh, int targetTriangleCount)
{
if (algorithm == null)
throw new ArgumentNullException("algorithm");
else if (mesh == null)
throw new ArgumentNullException("mesh");
int currentTriangleCount = mesh.TriangleCount;
if (targetTriangleCount > currentTriangleCount)
targetTriangleCount = currentTriangleCount;
else if (targetTriangleCount < 0)
targetTriangleCount = 0;
algorithm.Initialize(mesh);
algorithm.DecimateMesh(targetTriangleCount);
return algorithm.ToMesh();
}
#endregion
#region Decimate Mesh Lossless
/// <summary>
/// Decimates a mesh without losing any quality.
/// </summary>
/// <param name="mesh">The mesh to decimate.</param>
/// <returns>The decimated mesh.</returns>
public static Mesh DecimateMeshLossless(Mesh mesh)
{
return DecimateMeshLossless(Algorithm.Default, mesh);
}
/// <summary>
/// Decimates a mesh without losing any quality.
/// </summary>
/// <param name="algorithm">The desired algorithm.</param>
/// <param name="mesh">The mesh to decimate.</param>
/// <returns>The decimated mesh.</returns>
public static Mesh DecimateMeshLossless(Algorithm algorithm, Mesh mesh)
{
if (mesh == null)
throw new ArgumentNullException("mesh");
var decimationAlgorithm = CreateAlgorithm(algorithm);
return DecimateMeshLossless(decimationAlgorithm, mesh);
}
/// <summary>
/// Decimates a mesh without losing any quality.
/// </summary>
/// <param name="algorithm">The decimation algorithm.</param>
/// <param name="mesh">The mesh to decimate.</param>
/// <returns>The decimated mesh.</returns>
public static Mesh DecimateMeshLossless(DecimationAlgorithm algorithm, Mesh mesh)
{
if (algorithm == null)
throw new ArgumentNullException("algorithm");
else if (mesh == null)
throw new ArgumentNullException("mesh");
int currentTriangleCount = mesh.TriangleCount;
algorithm.Initialize(mesh);
algorithm.DecimateMeshLossless();
return algorithm.ToMesh();
}
#endregion
#endregion
}
}

View File

@@ -944,6 +944,7 @@ public class CompactUi : WindowMediatorSubscriberBase
VisiblePairSortMode.VramUsage => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateVramBytes),
VisiblePairSortMode.EffectiveVramUsage => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateEffectiveVramBytes),
VisiblePairSortMode.TriangleCount => SortVisibleByMetric(entryList, e => e.LastAppliedDataTris),
VisiblePairSortMode.EffectiveTriangleCount => SortVisibleByMetric(entryList, e => e.LastAppliedApproximateEffectiveTris),
VisiblePairSortMode.Alphabetical => [.. entryList.OrderBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)],
VisiblePairSortMode.PreferredDirectPairs => SortVisibleByPreferred(entryList),
_ => SortEntries(entryList),

View File

@@ -326,6 +326,7 @@ public class DrawFolderTag : DrawFolderBase
VisiblePairSortMode.VramUsage => "VRAM usage (descending)",
VisiblePairSortMode.EffectiveVramUsage => "Effective VRAM usage (descending)",
VisiblePairSortMode.TriangleCount => "Triangle count (descending)",
VisiblePairSortMode.EffectiveTriangleCount => "Effective triangle count (descending)",
VisiblePairSortMode.PreferredDirectPairs => "Preferred permissions & Direct pairs",
_ => "Default",
};

View File

@@ -429,6 +429,7 @@ public class DrawUserPair
_pair.LastAppliedApproximateVRAMBytes,
_pair.LastAppliedApproximateEffectiveVRAMBytes,
_pair.LastAppliedDataTris,
_pair.LastAppliedApproximateEffectiveTris,
_pair.IsPaired,
groupDisplays is null ? ImmutableArray<string>.Empty : ImmutableArray.CreateRange(groupDisplays));
@@ -444,6 +445,8 @@ public class DrawUserPair
private static string BuildTooltip(in TooltipSnapshot snapshot)
{
var builder = new StringBuilder(256);
static string FormatTriangles(long count) =>
count > 1000 ? (count / 1000d).ToString("0.0'k'") : count.ToString();
if (snapshot.IsPaused)
{
@@ -510,9 +513,13 @@ public class DrawUserPair
{
builder.Append(Environment.NewLine);
builder.Append("Approx. Triangle Count (excl. Vanilla): ");
builder.Append(snapshot.LastAppliedDataTris > 1000
? (snapshot.LastAppliedDataTris / 1000d).ToString("0.0'k'")
: snapshot.LastAppliedDataTris);
builder.Append(FormatTriangles(snapshot.LastAppliedDataTris));
if (snapshot.LastAppliedApproximateEffectiveTris >= 0)
{
builder.Append(" (Effective: ");
builder.Append(FormatTriangles(snapshot.LastAppliedApproximateEffectiveTris));
builder.Append(')');
}
}
}
@@ -544,11 +551,12 @@ public class DrawUserPair
long LastAppliedApproximateVRAMBytes,
long LastAppliedApproximateEffectiveVRAMBytes,
long LastAppliedDataTris,
long LastAppliedApproximateEffectiveTris,
bool IsPaired,
ImmutableArray<string> GroupDisplays)
{
public static TooltipSnapshot Empty { get; } =
new(false, false, false, IndividualPairStatus.None, string.Empty, string.Empty, -1, -1, -1, -1, false, ImmutableArray<string>.Empty);
new(false, false, false, IndividualPairStatus.None, string.Empty, string.Empty, -1, -1, -1, -1, -1, false, ImmutableArray<string>.Empty);
}
private void DrawPairedClientMenu()

View File

@@ -217,6 +217,7 @@ public class DrawEntityFactory
entry.PairStatus,
handler?.LastAppliedDataBytes ?? -1,
handler?.LastAppliedDataTris ?? -1,
handler?.LastAppliedApproximateEffectiveTris ?? -1,
handler?.LastAppliedApproximateVRAMBytes ?? -1,
handler?.LastAppliedApproximateEffectiveVRAMBytes ?? -1,
handler);

View File

@@ -415,7 +415,9 @@ public class IdDisplayHandler
var vramBytes = pair.LastAppliedApproximateEffectiveVRAMBytes >= 0
? pair.LastAppliedApproximateEffectiveVRAMBytes
: pair.LastAppliedApproximateVRAMBytes;
var triangleCount = pair.LastAppliedDataTris;
var triangleCount = pair.LastAppliedApproximateEffectiveTris >= 0
? pair.LastAppliedApproximateEffectiveTris
: pair.LastAppliedDataTris;
if (vramBytes < 0 && triangleCount < 0)
{
return null;

View File

@@ -21,6 +21,7 @@ public sealed record PairUiEntry(
IndividualPairStatus? PairStatus,
long LastAppliedDataBytes,
long LastAppliedDataTris,
long LastAppliedApproximateEffectiveTris,
long LastAppliedApproximateVramBytes,
long LastAppliedApproximateEffectiveVramBytes,
IPairHandlerAdapter? Handler)

View File

@@ -7,4 +7,5 @@ public enum VisiblePairSortMode
EffectiveVramUsage = 2,
TriangleCount = 3,
PreferredDirectPairs = 4,
EffectiveTriangleCount = 5,
}

View File

@@ -574,6 +574,94 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
}
private void DrawTriangleDecimationCounters()
{
HashSet<Pair> trackedPairs = new();
var snapshot = _pairUiService.GetSnapshot();
foreach (var pair in snapshot.DirectPairs)
{
trackedPairs.Add(pair);
}
foreach (var group in snapshot.GroupPairs.Values)
{
foreach (var pair in group)
{
trackedPairs.Add(pair);
}
}
long totalOriginalTris = 0;
long totalEffectiveTris = 0;
var hasData = false;
foreach (var pair in trackedPairs)
{
if (!pair.IsVisible)
continue;
var original = pair.LastAppliedDataTris;
var effective = pair.LastAppliedApproximateEffectiveTris;
if (original >= 0)
{
hasData = true;
totalOriginalTris += original;
}
if (effective >= 0)
{
hasData = true;
totalEffectiveTris += effective;
}
}
if (!hasData)
{
ImGui.TextDisabled("Triangle usage has not been calculated yet.");
return;
}
var savedTris = Math.Max(0L, totalOriginalTris - totalEffectiveTris);
var originalText = FormatTriangleCount(totalOriginalTris);
var effectiveText = FormatTriangleCount(totalEffectiveTris);
var savedText = FormatTriangleCount(savedTris);
ImGui.TextUnformatted($"Total triangle usage (original): {originalText}");
ImGui.TextUnformatted($"Total triangle usage (effective): {effectiveText}");
if (savedTris > 0)
{
UiSharedService.ColorText($"Triangles saved by decimation: {savedText}", UIColors.Get("LightlessGreen"));
}
else
{
ImGui.TextUnformatted($"Triangles saved by decimation: {savedText}");
}
static string FormatTriangleCount(long triangleCount)
{
if (triangleCount < 0)
{
return "n/a";
}
if (triangleCount >= 1_000_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000_000d:0.#}m tris");
}
if (triangleCount >= 1_000)
{
return FormattableString.Invariant($"{triangleCount / 1_000d:0.#}k tris");
}
return $"{triangleCount} tris";
}
}
private void DrawThemeVectorRow(MainStyle.StyleVector2Option option)
{
ImGui.TableNextRow();
@@ -1495,6 +1583,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
DrawPairPropertyRow("Approx. VRAM", FormatBytes(pair.LastAppliedApproximateVRAMBytes));
DrawPairPropertyRow("Effective VRAM", FormatBytes(pair.LastAppliedApproximateEffectiveVRAMBytes));
DrawPairPropertyRow("Last Triangles", pair.LastAppliedDataTris < 0 ? "n/a" : pair.LastAppliedDataTris.ToString(CultureInfo.InvariantCulture));
DrawPairPropertyRow("Effective Triangles", pair.LastAppliedApproximateEffectiveTris < 0 ? "n/a" : pair.LastAppliedApproximateEffectiveTris.ToString(CultureInfo.InvariantCulture));
ImGui.EndTable();
}
@@ -3528,6 +3617,102 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TreePop();
}
ImGui.Separator();
if (_uiShared.MediumTreeNode("Model Optimization", UIColors.Get("DimRed")))
{
_uiShared.MediumText("Warning", UIColors.Get("DimRed"));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Model decimation is a "),
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("this shit is placeholder still owo"));
var performanceConfig = _playerPerformanceConfigService.Current;
var enableDecimation = performanceConfig.EnableModelDecimation;
if (ImGui.Checkbox("Enable model decimation", ref enableDecimation))
{
performanceConfig.EnableModelDecimation = enableDecimation;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When enabled, Lightless generates a decimated copy of given model after download.");
var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold;
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
if (ImGui.SliderInt("Decimate models above", ref triangleThreshold, 10_000, 100_000))
{
performanceConfig.ModelDecimationTriangleThreshold = Math.Clamp(triangleThreshold, 10_000, 100_000);
_playerPerformanceConfigService.Save();
}
ImGui.SameLine();
ImGui.Text("triangles");
_uiShared.DrawHelpText($"Models below this triangle count are left untouched.{UiSharedService.TooltipSeparator}Default: 50,000");
var targetPercent = (float)(performanceConfig.ModelDecimationTargetRatio * 100.0);
var clampedPercent = Math.Clamp(targetPercent, 70f, 99f);
if (Math.Abs(clampedPercent - targetPercent) > float.Epsilon)
{
performanceConfig.ModelDecimationTargetRatio = clampedPercent / 100.0;
_playerPerformanceConfigService.Save();
targetPercent = clampedPercent;
}
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
if (ImGui.SliderFloat("Target triangle ratio", ref targetPercent, 70f, 99f, "%.0f%%"))
{
performanceConfig.ModelDecimationTargetRatio = Math.Clamp(targetPercent / 100f, 0.7f, 0.99f);
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText($"Target ratio relative to original triangle count (70% keeps 70% of triangles).{UiSharedService.TooltipSeparator}Default: 70%");
ImGui.Dummy(new Vector2(5));
ImGui.TextUnformatted("Decimation targets");
_uiShared.DrawHelpText("Hair mods are always excluded from decimation.");
var allowBody = performanceConfig.ModelDecimationAllowBody;
if (ImGui.Checkbox("Body", ref allowBody))
{
performanceConfig.ModelDecimationAllowBody = allowBody;
_playerPerformanceConfigService.Save();
}
var allowFaceHead = performanceConfig.ModelDecimationAllowFaceHead;
if (ImGui.Checkbox("Face/head", ref allowFaceHead))
{
performanceConfig.ModelDecimationAllowFaceHead = allowFaceHead;
_playerPerformanceConfigService.Save();
}
var allowTail = performanceConfig.ModelDecimationAllowTail;
if (ImGui.Checkbox("Tails/Ears", ref allowTail))
{
performanceConfig.ModelDecimationAllowTail = allowTail;
_playerPerformanceConfigService.Save();
}
var allowClothing = performanceConfig.ModelDecimationAllowClothing;
if (ImGui.Checkbox("Clothing (body/legs/shoes/gloves/hats)", ref allowClothing))
{
performanceConfig.ModelDecimationAllowClothing = allowClothing;
_playerPerformanceConfigService.Save();
}
var allowAccessories = performanceConfig.ModelDecimationAllowAccessories;
if (ImGui.Checkbox("Accessories (earring/rings/bracelet/necklace)", ref allowAccessories))
{
performanceConfig.ModelDecimationAllowAccessories = allowAccessories;
_playerPerformanceConfigService.Save();
}
ImGui.Dummy(new Vector2(5));
DrawTriangleDecimationCounters();
ImGui.Dummy(new Vector2(5));
UiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.5f);
ImGui.TreePop();
}
ImGui.Separator();
ImGui.Dummy(new Vector2(10));

View File

@@ -6,6 +6,7 @@ using LightlessSync.FileCache;
using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Handlers;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ModelDecimation;
using LightlessSync.Services.TextureCompression;
using LightlessSync.WebAPI.Files.Models;
using Microsoft.Extensions.Logging;
@@ -25,6 +26,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private readonly FileTransferOrchestrator _orchestrator;
private readonly LightlessConfigService _configService;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly ModelDecimationService _modelDecimationService;
private readonly TextureMetadataHelper _textureMetadataHelper;
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
@@ -41,6 +43,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
FileCompactor fileCompactor,
LightlessConfigService configService,
TextureDownscaleService textureDownscaleService,
ModelDecimationService modelDecimationService,
TextureMetadataHelper textureMetadataHelper) : base(logger, mediator)
{
_downloadStatus = new Dictionary<string, FileDownloadStatus>(StringComparer.Ordinal);
@@ -49,6 +52,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
_fileCompactor = fileCompactor;
_configService = configService;
_textureDownscaleService = textureDownscaleService;
_modelDecimationService = modelDecimationService;
_textureMetadataHelper = textureMetadataHelper;
_activeDownloadStreams = new();
_lastConfigDirectDownloadsState = _configService.Current.EnableDirectDownloads;
@@ -1026,6 +1030,11 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
() => _textureMetadataHelper.DetermineMapKind(gamePath, filePath));
}
if (!skipDownscale && _modelDecimationService.ShouldScheduleDecimation(fileHash, filePath, gamePath))
{
_modelDecimationService.ScheduleDecimation(fileHash, filePath, gamePath);
}
if (entry != null && !string.Equals(entry.Hash, fileHash, StringComparison.OrdinalIgnoreCase))
{
Logger.LogError("Hash mismatch after extracting, got {hash}, expected {expectedHash}, deleting file",