reworked mesh decimation yes

This commit is contained in:
2026-01-19 09:50:54 +09:00
parent b57d54d69c
commit 54d6a0a1a4
74 changed files with 15788 additions and 8308 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
using System;
namespace Nanomesh
{
public partial class DecimateModifier
{
public class EdgeCollapse : IComparable<EdgeCollapse>, IEquatable<EdgeCollapse>
{
public int posA;
public int posB;
public Vector3 result;
public double error;
private double _weight = -1;
public ref double Weight => ref _weight;
public void SetWeight(double weight)
{
_weight = weight;
}
public EdgeCollapse(int posA, int posB)
{
this.posA = posA;
this.posB = posB;
}
public override int GetHashCode()
{
unchecked
{
return posA + posB;
}
}
public override bool Equals(object obj)
{
return Equals((EdgeCollapse)obj);
}
public bool Equals(EdgeCollapse pc)
{
if (ReferenceEquals(pc, null))
return false;
if (ReferenceEquals(this, pc))
{
return true;
}
else
{
return (posA == pc.posA && posB == pc.posB) || (posA == pc.posB && posB == pc.posA);
}
}
public int CompareTo(EdgeCollapse other)
{
return error > other.error ? 1 : error < other.error ? -1 : 0;
}
public static bool operator >(EdgeCollapse x, EdgeCollapse y)
{
return x.error > y.error;
}
public static bool operator >=(EdgeCollapse x, EdgeCollapse y)
{
return x.error >= y.error;
}
public static bool operator <(EdgeCollapse x, EdgeCollapse y)
{
return x.error < y.error;
}
public static bool operator <=(EdgeCollapse x, EdgeCollapse y)
{
return x.error <= y.error;
}
public override string ToString()
{
return $"<A:{posA} B:{posB} error:{error} topology:{_weight}>";
}
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace Nanomesh
{
public partial class DecimateModifier
{
private class EdgeComparer : IComparer<EdgeCollapse>
{
public int Compare(EdgeCollapse x, EdgeCollapse y)
{
return x.CompareTo(y);
}
}
}
}

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Linq;
namespace Nanomesh
{
public class SceneDecimator
{
private class ModifierAndOccurrences
{
public int occurrences = 1;
public DecimateModifier modifier = new DecimateModifier();
}
private Dictionary<ConnectedMesh, ModifierAndOccurrences> _modifiers;
public void Initialize(IEnumerable<ConnectedMesh> meshes)
{
_modifiers = new Dictionary<ConnectedMesh, ModifierAndOccurrences>();
foreach (ConnectedMesh mesh in meshes)
{
ModifierAndOccurrences modifier;
if (_modifiers.ContainsKey(mesh))
{
modifier = _modifiers[mesh];
modifier.occurrences++;
}
else
{
_modifiers.Add(mesh, modifier = new ModifierAndOccurrences());
//System.Console.WriteLine($"Faces:{mesh.FaceCount}");
modifier.modifier.Initialize(mesh);
}
_faceCount += mesh.FaceCount;
}
_initalFaceCount = _faceCount;
}
private int _faceCount;
private int _initalFaceCount;
public void DecimateToRatio(float targetTriangleRatio)
{
targetTriangleRatio = MathF.Clamp(targetTriangleRatio, 0f, 1f);
DecimateToPolycount((int)MathF.Round(targetTriangleRatio * _initalFaceCount));
}
public void DecimatePolycount(int polycount)
{
DecimateToPolycount((int)MathF.Round(_initalFaceCount - polycount));
}
public void DecimateToPolycount(int targetTriangleCount)
{
//System.Console.WriteLine($"Faces:{_faceCount} Target:{targetTriangleCount}");
while (_faceCount > targetTriangleCount)
{
KeyValuePair<ConnectedMesh, ModifierAndOccurrences> pair = _modifiers.OrderBy(x => x.Value.modifier.GetMinimumError()).First();
int facesBefore = pair.Key.FaceCount;
pair.Value.modifier.Iterate();
if (facesBefore == pair.Key.FaceCount)
break; // Exit !
_faceCount -= (facesBefore - pair.Key.FaceCount) * pair.Value.occurrences;
}
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Nanomesh
{
public class NormalsModifier
{
public struct PosAndAttribute : IEquatable<PosAndAttribute>
{
public int position;
public Attribute attribute;
public override int GetHashCode()
{
return position.GetHashCode() ^ (attribute.GetHashCode() << 2);
}
public bool Equals(PosAndAttribute other)
{
return position == other.position && attribute.Equals(other.attribute);
}
}
public void Run(ConnectedMesh mesh, float smoothingAngle)
{
float cosineThreshold = MathF.Cos(smoothingAngle * MathF.PI / 180f);
int[] positionToNode = mesh.GetPositionToNode();
Dictionary<PosAndAttribute, int> attributeToIndex = new Dictionary<PosAndAttribute, int>();
for (int p = 0; p < positionToNode.Length; p++)
{
int nodeIndex = positionToNode[p];
if (nodeIndex < 0)
{
continue;
}
Debug.Assert(!mesh.nodes[nodeIndex].IsRemoved);
int sibling1 = nodeIndex;
do
{
Vector3F sum = Vector3F.Zero;
Vector3F normal1 = mesh.GetFaceNormal(sibling1);
int sibling2 = nodeIndex;
do
{
Vector3F normal2 = mesh.GetFaceNormal(sibling2);
float dot = Vector3F.Dot(normal1, normal2);
if (dot >= cosineThreshold)
{
// Area and angle weighting (it gives better results)
sum += mesh.GetFaceArea(sibling2) * mesh.GetAngleRadians(sibling2) * normal2;
}
} while ((sibling2 = mesh.nodes[sibling2].sibling) != nodeIndex);
sum = sum.Normalized;
} while ((sibling1 = mesh.nodes[sibling1].sibling) != nodeIndex);
}
// Assign new attributes
// TODO : Fix
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Nanomesh
{
public class NormalsFixer
{
public void Start(ConnectedMesh mesh)
{
/*
for (int i = 0; i < mesh.attributes.Length; i++)
{
Attribute attribute = mesh.attributes[i];
attribute.normal = attribute.normal.Normalized;
mesh.attributes[i] = attribute;
}
*/
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
namespace Nanomesh
{
public class TriangulateModifier
{
public void Run(ConnectedMesh mesh)
{
for (int i = 0; i < mesh.nodes.Length; i++)
{
int edgeCount = 0;
int relative = i;
while ((relative = mesh.nodes[relative].relative) != i) // Circulate around face
{
edgeCount++;
}
if (edgeCount > 2)
{
throw new Exception("Mesh has polygons of dimension 4 or greater");
}
}
// Todo : Implement
}
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Nanomesh
{
public readonly struct BoneWeight : IEquatable<BoneWeight>, IInterpolable<BoneWeight>
{
public readonly int index0;
public readonly int index1;
public readonly int index2;
public readonly int index3;
public readonly float weight0;
public readonly float weight1;
public readonly float weight2;
public readonly float weight3;
public int GetIndex(int i)
{
switch (i)
{
case 0: return index0;
case 1: return index1;
case 2: return index2;
case 3: return index3;
default: return -1;
}
}
public float GetWeight(int i)
{
switch (i)
{
case 0: return weight0;
case 1: return weight1;
case 2: return weight2;
case 3: return weight3;
default: return -1;
}
}
public BoneWeight(int index0, int index1, int index2, int index3, float weight0, float weight1, float weight2, float weight3)
{
this.index0 = index0;
this.index1 = index1;
this.index2 = index2;
this.index3 = index3;
this.weight0 = weight0;
this.weight1 = weight1;
this.weight2 = weight2;
this.weight3 = weight3;
}
public bool Equals(BoneWeight other)
{
return index0 == other.index0
&& index1 == other.index1
&& index2 == other.index2
&& index3 == other.index3
&& weight0 == other.weight0
&& weight1 == other.weight1
&& weight2 == other.weight2
&& weight3 == other.weight3;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + index0;
hash = hash * 31 + index1;
hash = hash * 31 + index2;
hash = hash * 31 + index3;
hash = hash * 31 + weight0.GetHashCode();
hash = hash * 31 + weight1.GetHashCode();
hash = hash * 31 + weight2.GetHashCode();
hash = hash * 31 + weight3.GetHashCode();
return hash;
}
}
public unsafe BoneWeight Interpolate(BoneWeight other, double ratio)
{
BoneWeight boneWeightA = this;
BoneWeight boneWeightB = other;
Dictionary<int, float> newBoneWeight = new Dictionary<int, float>();
// Map weights and indices
for (int i = 0; i < 4; i++)
{
newBoneWeight.TryAdd(boneWeightA.GetIndex(i), 0);
newBoneWeight.TryAdd(boneWeightB.GetIndex(i), 0);
newBoneWeight[boneWeightA.GetIndex(i)] += (float)((1 - ratio) * boneWeightA.GetWeight(i));
newBoneWeight[boneWeightB.GetIndex(i)] += (float)(ratio * boneWeightB.GetWeight(i));
}
int* newIndices = stackalloc int[4];
float* newWeights = stackalloc float[4];
// Order from biggest to smallest weight, and drop bones above 4th
float totalWeight = 0;
int k = 0;
foreach (KeyValuePair<int, float> boneWeightN in newBoneWeight.OrderByDescending(x => x.Value))
{
newIndices[k] = boneWeightN.Key;
newWeights[k] = boneWeightN.Value;
totalWeight += boneWeightN.Value;
if (k == 3)
break;
k++;
}
var sumA = boneWeightA.weight0 + boneWeightA.weight1 + boneWeightA.weight2 + boneWeightA.weight3;
var sumB = boneWeightB.weight0 + boneWeightB.weight1 + boneWeightB.weight2 + boneWeightB.weight3;
var targetSum = (float)((1d - ratio) * sumA + ratio * sumB);
// Normalize and re-scale to preserve original weight sum.
if (totalWeight > 0f)
{
var scale = targetSum / totalWeight;
for (int j = 0; j < 4; j++)
{
newWeights[j] *= scale;
}
}
return new BoneWeight(
newIndices[0], newIndices[1], newIndices[2], newIndices[3],
newWeights[0], newWeights[1], newWeights[2], newWeights[3]);
//return new BoneWeight(
// ratio < 0.5f ? index0 : other.index0,
// ratio < 0.5f ? index1 : other.index1,
// ratio < 0.5f ? index2 : other.index2,
// ratio < 0.5f ? index3 : other.index3,
// (float)(ratio * weight0 + (1 - ratio) * other.weight0),
// (float)(ratio * weight1 + (1 - ratio) * other.weight1),
// (float)(ratio * weight2 + (1 - ratio) * other.weight2),
// (float)(ratio * weight3 + (1 - ratio) * other.weight3));
}
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Runtime.InteropServices;
namespace Nanomesh
{
[StructLayout(LayoutKind.Explicit)]
public readonly struct Color32 : IEquatable<Color32>, IInterpolable<Color32>
{
[FieldOffset(0)]
internal readonly int rgba;
[FieldOffset(0)]
public readonly byte r;
[FieldOffset(1)]
public readonly byte g;
[FieldOffset(2)]
public readonly byte b;
[FieldOffset(3)]
public readonly byte a;
public Color32(byte r, byte g, byte b, byte a)
{
rgba = 0;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Color32(float r, float g, float b, float a)
{
rgba = 0;
this.r = (byte)MathF.Round(r);
this.g = (byte)MathF.Round(g);
this.b = (byte)MathF.Round(b);
this.a = (byte)MathF.Round(a);
}
public Color32(double r, double g, double b, double a)
{
rgba = 0;
this.r = (byte)Math.Round(r);
this.g = (byte)Math.Round(g);
this.b = (byte)Math.Round(b);
this.a = (byte)Math.Round(a);
}
public bool Equals(Color32 other)
{
return other.rgba == rgba;
}
public Color32 Interpolate(Color32 other, double ratio)
{
return ratio * this + (1 - ratio) * other;
}
/// <summary>
/// Adds two colors.
/// </summary>
/// <returns></returns>
public static Color32 operator +(Color32 a, Color32 b) { return new Color32(a.r + b.r, a.g + b.g, a.b + b.b, a.a + b.a); }
/// <summary>
/// Subtracts one color from another.
/// </summary>
/// <returns></returns>
public static Color32 operator -(Color32 a, Color32 b) { return new Color32(1f * a.r - b.r, a.g - b.g, a.b - b.b, a.a - b.a); }
/// <summary>
/// Multiplies one color by another.
/// </summary>
/// <returns></returns>
public static Color32 operator *(Color32 a, Color32 b) { return new Color32(1f * a.r * b.r, 1f * a.g * b.g, 1f * a.b * b.b, 1f * a.a * b.a); }
/// <summary>
/// Divides one color over another.
/// </summary>
/// <returns></returns>
public static Color32 operator /(Color32 a, Color32 b) { return new Color32(1f * a.r / b.r, 1f * a.g / b.g, 1f * a.b / b.b, 1f * a.a / b.a); }
/// <summary>
/// Multiplies a color by a number.
/// </summary>
/// <param name="a"></param>
/// <param name="d"></param>
/// <returns></returns>
public static Color32 operator *(Color32 a, float d) { return new Color32(d * a.r, d * a.g, d * a.b, d * a.a); }
public static Color32 operator *(Color32 a, double d) { return new Color32(d * a.r, d * a.g, d * a.b, d * a.a); }
/// <summary>
/// Multiplies a color by a number.
/// </summary>
/// <returns></returns>
public static Color32 operator *(float d, Color32 a) { return new Color32(d * a.r, d * a.g, d * a.b, d * a.a); }
public static Color32 operator *(double d, Color32 a) { return new Color32(d * a.r, d * a.g, d * a.b, d * a.a); }
/// <summary>
/// Divides a color by a number.
/// </summary>
/// <returns></returns>
public static Color32 operator /(Color32 a, float d) { return new Color32(1f * a.r / d, 1f * a.g / d, 1f * a.b / d, 1f * a.a / d); }
}
}

View File

@@ -0,0 +1,347 @@
using System;
using System.Runtime.InteropServices;
namespace Nanomesh
{
[Flags]
public enum FfxivAttributeFlags : uint
{
None = 0,
Normal = 1u << 0,
Tangent1 = 1u << 1,
Tangent2 = 1u << 2,
Color = 1u << 3,
BoneWeights = 1u << 4,
PositionW = 1u << 5,
NormalW = 1u << 6,
Uv0 = 1u << 7,
Uv1 = 1u << 8,
Uv2 = 1u << 9,
Uv3 = 1u << 10,
}
[StructLayout(LayoutKind.Sequential)]
public readonly struct FfxivVertexAttribute : IEquatable<FfxivVertexAttribute>, IInterpolable<FfxivVertexAttribute>
{
public readonly Vector3F normal;
public readonly Vector4F tangent1;
public readonly Vector4F tangent2;
public readonly Vector2F uv0;
public readonly Vector2F uv1;
public readonly Vector2F uv2;
public readonly Vector2F uv3;
public readonly Vector4F color;
public readonly BoneWeight boneWeight;
public readonly float positionW;
public readonly float normalW;
public readonly FfxivAttributeFlags flags;
public FfxivVertexAttribute(
FfxivAttributeFlags flags,
Vector3F normal,
Vector4F tangent1,
Vector4F tangent2,
Vector2F uv0,
Vector2F uv1,
Vector2F uv2,
Vector2F uv3,
Vector4F color,
BoneWeight boneWeight,
float positionW,
float normalW)
{
this.flags = flags;
this.normal = normal;
this.tangent1 = tangent1;
this.tangent2 = tangent2;
this.uv0 = uv0;
this.uv1 = uv1;
this.uv2 = uv2;
this.uv3 = uv3;
this.color = color;
this.boneWeight = boneWeight;
this.positionW = positionW;
this.normalW = normalW;
}
public FfxivVertexAttribute Interpolate(FfxivVertexAttribute other, double ratio)
{
var t = (float)ratio;
var inv = 1f - t;
var combinedFlags = flags | other.flags;
var normal = (combinedFlags & FfxivAttributeFlags.Normal) != 0
? NormalizeVector3(new Vector3F(
(this.normal.x * inv) + (other.normal.x * t),
(this.normal.y * inv) + (other.normal.y * t),
(this.normal.z * inv) + (other.normal.z * t)))
: default;
var tangent1 = (combinedFlags & FfxivAttributeFlags.Tangent1) != 0
? BlendTangent(this.tangent1, other.tangent1, t)
: default;
var tangent2 = (combinedFlags & FfxivAttributeFlags.Tangent2) != 0
? BlendTangent(this.tangent2, other.tangent2, t)
: default;
var uv0 = (combinedFlags & FfxivAttributeFlags.Uv0) != 0
? Vector2F.LerpUnclamped(this.uv0, other.uv0, t)
: default;
var uv1 = (combinedFlags & FfxivAttributeFlags.Uv1) != 0
? Vector2F.LerpUnclamped(this.uv1, other.uv1, t)
: default;
var uv2 = (combinedFlags & FfxivAttributeFlags.Uv2) != 0
? Vector2F.LerpUnclamped(this.uv2, other.uv2, t)
: default;
var uv3 = (combinedFlags & FfxivAttributeFlags.Uv3) != 0
? Vector2F.LerpUnclamped(this.uv3, other.uv3, t)
: default;
var color = (combinedFlags & FfxivAttributeFlags.Color) != 0
? new Vector4F(
(this.color.x * inv) + (other.color.x * t),
(this.color.y * inv) + (other.color.y * t),
(this.color.z * inv) + (other.color.z * t),
(this.color.w * inv) + (other.color.w * t))
: default;
var boneWeight = (combinedFlags & FfxivAttributeFlags.BoneWeights) != 0
? BlendBoneWeights(this.boneWeight, other.boneWeight, t)
: default;
var positionW = (combinedFlags & FfxivAttributeFlags.PositionW) != 0
? (this.positionW * inv) + (other.positionW * t)
: 0f;
var normalW = (combinedFlags & FfxivAttributeFlags.NormalW) != 0
? (this.normalW * inv) + (other.normalW * t)
: 0f;
return new FfxivVertexAttribute(
combinedFlags,
normal,
tangent1,
tangent2,
uv0,
uv1,
uv2,
uv3,
color,
boneWeight,
positionW,
normalW);
}
public bool Equals(FfxivVertexAttribute other)
{
if (flags != other.flags)
{
return false;
}
if ((flags & FfxivAttributeFlags.Normal) != 0 && !normal.Equals(other.normal))
{
return false;
}
if ((flags & FfxivAttributeFlags.Tangent1) != 0 && !tangent1.Equals(other.tangent1))
{
return false;
}
if ((flags & FfxivAttributeFlags.Tangent2) != 0 && !tangent2.Equals(other.tangent2))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv0) != 0 && !uv0.Equals(other.uv0))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv1) != 0 && !uv1.Equals(other.uv1))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv2) != 0 && !uv2.Equals(other.uv2))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv3) != 0 && !uv3.Equals(other.uv3))
{
return false;
}
if ((flags & FfxivAttributeFlags.Color) != 0 && !color.Equals(other.color))
{
return false;
}
if ((flags & FfxivAttributeFlags.BoneWeights) != 0 && !boneWeight.Equals(other.boneWeight))
{
return false;
}
if ((flags & FfxivAttributeFlags.PositionW) != 0 && positionW != other.positionW)
{
return false;
}
if ((flags & FfxivAttributeFlags.NormalW) != 0 && normalW != other.normalW)
{
return false;
}
return true;
}
public override bool Equals(object? obj)
=> obj is FfxivVertexAttribute other && Equals(other);
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(normal);
hash.Add(tangent1);
hash.Add(tangent2);
hash.Add(uv0);
hash.Add(uv1);
hash.Add(uv2);
hash.Add(uv3);
hash.Add(color);
hash.Add(boneWeight);
hash.Add(positionW);
hash.Add(normalW);
hash.Add(flags);
return hash.ToHashCode();
}
private static Vector3F NormalizeVector3(in Vector3F value)
{
var length = Vector3F.Magnitude(value);
return length > 0f ? value / length : value;
}
private static Vector4F BlendTangent(in Vector4F a, in Vector4F b, float t)
{
var inv = 1f - t;
var blended = new Vector3F(
(a.x * inv) + (b.x * t),
(a.y * inv) + (b.y * t),
(a.z * inv) + (b.z * t));
blended = NormalizeVector3(blended);
var w = t >= 0.5f ? b.w : a.w;
if (w != 0f)
{
w = w >= 0f ? 1f : -1f;
}
return new Vector4F(blended.x, blended.y, blended.z, w);
}
private static BoneWeight BlendBoneWeights(in BoneWeight a, in BoneWeight b, float ratio)
{
Span<int> indices = stackalloc int[8];
Span<float> weights = stackalloc float[8];
var count = 0;
static void AddWeight(Span<int> indices, Span<float> weights, ref int count, int index, float weight)
{
if (weight <= 0f)
{
return;
}
for (var i = 0; i < count; i++)
{
if (indices[i] == index)
{
weights[i] += weight;
return;
}
}
if (count < indices.Length)
{
indices[count] = index;
weights[count] = weight;
count++;
}
}
var inv = 1f - ratio;
var sumA = a.weight0 + a.weight1 + a.weight2 + a.weight3;
var sumB = b.weight0 + b.weight1 + b.weight2 + b.weight3;
var targetSum = (sumA * inv) + (sumB * ratio);
AddWeight(indices, weights, ref count, a.index0, a.weight0 * inv);
AddWeight(indices, weights, ref count, a.index1, a.weight1 * inv);
AddWeight(indices, weights, ref count, a.index2, a.weight2 * inv);
AddWeight(indices, weights, ref count, a.index3, a.weight3 * inv);
AddWeight(indices, weights, ref count, b.index0, b.weight0 * ratio);
AddWeight(indices, weights, ref count, b.index1, b.weight1 * ratio);
AddWeight(indices, weights, ref count, b.index2, b.weight2 * ratio);
AddWeight(indices, weights, ref count, b.index3, b.weight3 * ratio);
if (count == 0)
{
return a;
}
Span<int> topIndices = stackalloc int[4];
Span<float> topWeights = stackalloc float[4];
for (var i = 0; i < 4; i++)
{
topIndices[i] = -1;
topWeights[i] = 0f;
}
for (var i = 0; i < count; i++)
{
var weight = weights[i];
var index = indices[i];
for (var slot = 0; slot < 4; slot++)
{
if (weight > topWeights[slot])
{
for (var shift = 3; shift > slot; shift--)
{
topWeights[shift] = topWeights[shift - 1];
topIndices[shift] = topIndices[shift - 1];
}
topWeights[slot] = weight;
topIndices[slot] = index;
break;
}
}
}
var sum = topWeights[0] + topWeights[1] + topWeights[2] + topWeights[3];
if (sum > 0f)
{
var scale = targetSum > 0f ? targetSum / sum : 0f;
for (var i = 0; i < 4; i++)
{
topWeights[i] *= scale;
}
}
return new BoneWeight(
topIndices[0] < 0 ? 0 : topIndices[0],
topIndices[1] < 0 ? 0 : topIndices[1],
topIndices[2] < 0 ? 0 : topIndices[2],
topIndices[3] < 0 ? 0 : topIndices[3],
topWeights[0],
topWeights[1],
topWeights[2],
topWeights[3]);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Nanomesh
{
public interface IInterpolable<T>
{
T Interpolate(T other, double ratio);
}
}

View File

@@ -0,0 +1,356 @@
using System;
namespace Nanomesh
{
public static partial class MathF
{
// Returns the sine of angle /f/ in radians.
public static float Sin(float f) { return (float)Math.Sin(f); }
// Returns the cosine of angle /f/ in radians.
public static float Cos(float f) { return (float)Math.Cos(f); }
// Returns the tangent of angle /f/ in radians.
public static float Tan(float f) { return (float)Math.Tan(f); }
// Returns the arc-sine of /f/ - the angle in radians whose sine is /f/.
public static float Asin(float f) { return (float)Math.Asin(f); }
// Returns the arc-cosine of /f/ - the angle in radians whose cosine is /f/.
public static float Acos(float f) { return (float)Math.Acos(f); }
// Returns the arc-tangent of /f/ - the angle in radians whose tangent is /f/.
public static float Atan(float f) { return (float)Math.Atan(f); }
// Returns the angle in radians whose ::ref::Tan is @@y/x@@.
public static float Atan2(float y, float x) { return (float)Math.Atan2(y, x); }
// Returns square root of /f/.
public static float Sqrt(float f) { return (float)Math.Sqrt(f); }
// Returns the absolute value of /f/.
public static float Abs(float f) { return (float)Math.Abs(f); }
// Returns the absolute value of /value/.
public static int Abs(int value) { return Math.Abs(value); }
/// *listonly*
public static float Min(float a, float b) { return a < b ? a : b; }
// Returns the smallest of two or more values.
public static float Min(params float[] values)
{
int len = values.Length;
if (len == 0)
{
return 0;
}
float m = values[0];
for (int i = 1; i < len; i++)
{
if (values[i] < m)
{
m = values[i];
}
}
return m;
}
/// *listonly*
public static int Min(int a, int b) { return a < b ? a : b; }
// Returns the smallest of two or more values.
public static int Min(params int[] values)
{
int len = values.Length;
if (len == 0)
{
return 0;
}
int m = values[0];
for (int i = 1; i < len; i++)
{
if (values[i] < m)
{
m = values[i];
}
}
return m;
}
/// *listonly*
public static float Max(float a, float b) { return a > b ? a : b; }
// Returns largest of two or more values.
public static float Max(params float[] values)
{
int len = values.Length;
if (len == 0)
{
return 0;
}
float m = values[0];
for (int i = 1; i < len; i++)
{
if (values[i] > m)
{
m = values[i];
}
}
return m;
}
/// *listonly*
public static int Max(int a, int b) { return a > b ? a : b; }
// Returns the largest of two or more values.
public static int Max(params int[] values)
{
int len = values.Length;
if (len == 0)
{
return 0;
}
int m = values[0];
for (int i = 1; i < len; i++)
{
if (values[i] > m)
{
m = values[i];
}
}
return m;
}
// Returns /f/ raised to power /p/.
public static float Pow(float f, float p) { return (float)Math.Pow(f, p); }
// Returns e raised to the specified power.
public static float Exp(float power) { return (float)Math.Exp(power); }
// Returns the logarithm of a specified number in a specified base.
public static float Log(float f, float p) { return (float)Math.Log(f, p); }
// Returns the natural (base e) logarithm of a specified number.
public static float Log(float f) { return (float)Math.Log(f); }
// Returns the base 10 logarithm of a specified number.
public static float Log10(float f) { return (float)Math.Log10(f); }
// Returns the smallest integer greater to or equal to /f/.
public static float Ceil(float f) { return (float)Math.Ceiling(f); }
// Returns the largest integer smaller to or equal to /f/.
public static float Floor(float f) { return (float)Math.Floor(f); }
// Returns /f/ rounded to the nearest integer.
public static float Round(float f) { return (float)Math.Round(f); }
// Returns the smallest integer greater to or equal to /f/.
public static int CeilToInt(float f) { return (int)Math.Ceiling(f); }
// Returns the largest integer smaller to or equal to /f/.
public static int FloorToInt(float f) { return (int)Math.Floor(f); }
// Returns /f/ rounded to the nearest integer.
public static int RoundToInt(float f) { return (int)Math.Round(f); }
// Returns the sign of /f/.
public static float Sign(float f) { return f >= 0F ? 1F : -1F; }
// The infamous ''3.14159265358979...'' value (RO).
public const float PI = (float)Math.PI;
// A representation of positive infinity (RO).
public const float Infinity = float.PositiveInfinity;
// A representation of negative infinity (RO).
public const float NegativeInfinity = float.NegativeInfinity;
// Degrees-to-radians conversion constant (RO).
public const float Deg2Rad = PI * 2F / 360F;
// Radians-to-degrees conversion constant (RO).
public const float Rad2Deg = 1F / Deg2Rad;
// Clamps a value between a minimum float and maximum float value.
public static double Clamp(double value, double min, double max)
{
if (value < min)
{
value = min;
}
else if (value > max)
{
value = max;
}
return value;
}
// Clamps a value between a minimum float and maximum float value.
public static float Clamp(float value, float min, float max)
{
if (value < min)
{
value = min;
}
else if (value > max)
{
value = max;
}
return value;
}
// Clamps value between min and max and returns value.
// Set the position of the transform to be that of the time
// but never less than 1 or more than 3
//
public static int Clamp(int value, int min, int max)
{
if (value < min)
{
value = min;
}
else if (value > max)
{
value = max;
}
return value;
}
// Clamps value between 0 and 1 and returns value
public static float Clamp01(float value)
{
if (value < 0F)
{
return 0F;
}
else if (value > 1F)
{
return 1F;
}
else
{
return value;
}
}
// Interpolates between /a/ and /b/ by /t/. /t/ is clamped between 0 and 1.
public static float Lerp(float a, float b, float t)
{
return a + (b - a) * Clamp01(t);
}
// Interpolates between /a/ and /b/ by /t/ without clamping the interpolant.
public static float LerpUnclamped(float a, float b, float t)
{
return a + (b - a) * t;
}
// Same as ::ref::Lerp but makes sure the values interpolate correctly when they wrap around 360 degrees.
public static float LerpAngle(float a, float b, float t)
{
float delta = Repeat((b - a), 360);
if (delta > 180)
{
delta -= 360;
}
return a + delta * Clamp01(t);
}
// Moves a value /current/ towards /target/.
public static float MoveTowards(float current, float target, float maxDelta)
{
if (MathF.Abs(target - current) <= maxDelta)
{
return target;
}
return current + MathF.Sign(target - current) * maxDelta;
}
// Same as ::ref::MoveTowards but makes sure the values interpolate correctly when they wrap around 360 degrees.
public static float MoveTowardsAngle(float current, float target, float maxDelta)
{
float deltaAngle = DeltaAngle(current, target);
if (-maxDelta < deltaAngle && deltaAngle < maxDelta)
{
return target;
}
target = current + deltaAngle;
return MoveTowards(current, target, maxDelta);
}
// Interpolates between /min/ and /max/ with smoothing at the limits.
public static float SmoothStep(float from, float to, float t)
{
t = MathF.Clamp01(t);
t = -2.0F * t * t * t + 3.0F * t * t;
return to * t + from * (1F - t);
}
//*undocumented
public static float Gamma(float value, float absmax, float gamma)
{
bool negative = value < 0F;
float absval = Abs(value);
if (absval > absmax)
{
return negative ? -absval : absval;
}
float result = Pow(absval / absmax, gamma) * absmax;
return negative ? -result : result;
}
// Loops the value t, so that it is never larger than length and never smaller than 0.
public static float Repeat(float t, float length)
{
return Clamp(t - MathF.Floor(t / length) * length, 0.0f, length);
}
// PingPongs the value t, so that it is never larger than length and never smaller than 0.
public static float PingPong(float t, float length)
{
t = Repeat(t, length * 2F);
return length - MathF.Abs(t - length);
}
// Calculates the ::ref::Lerp parameter between of two values.
public static float InverseLerp(float a, float b, float value)
{
if (a != b)
{
return Clamp01((value - a) / (b - a));
}
else
{
return 0.0f;
}
}
// Calculates the shortest difference between two given angles.
public static float DeltaAngle(float current, float target)
{
float delta = MathF.Repeat((target - current), 360.0F);
if (delta > 180.0F)
{
delta -= 360.0F;
}
return delta;
}
internal static long RandomToLong(System.Random r)
{
byte[] buffer = new byte[8];
r.NextBytes(buffer);
return (long)(System.BitConverter.ToUInt64(buffer, 0) & long.MaxValue);
}
}
}

View File

@@ -0,0 +1,114 @@
using System.Runtime.CompilerServices;
namespace Nanomesh
{
public static class MathUtils
{
public const float EpsilonFloat = 1e-15f;
public const double EpsilonDouble = 1e-40f;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DivideSafe(float numerator, float denominator)
{
return (denominator > -EpsilonFloat && denominator < EpsilonFloat) ? 0f : numerator / denominator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double DivideSafe(double numerator, double denominator)
{
return (denominator > -EpsilonDouble && denominator < EpsilonDouble) ? 0d : numerator / denominator;
}
public static void SelectMin<T>(double e1, double e2, double e3, in T v1, in T v2, in T v3, out double e, out T v)
{
if (e1 < e2)
{
if (e1 < e3)
{
e = e1;
v = v1;
}
else
{
e = e3;
v = v3;
}
}
else
{
if (e2 < e3)
{
e = e2;
v = v2;
}
else
{
e = e3;
v = v3;
}
}
}
public static void SelectMin<T>(double e1, double e2, double e3, double e4, in T v1, in T v2, in T v3, in T v4, out double e, out T v)
{
if (e1 < e2)
{
if (e1 < e3)
{
if (e1 < e4)
{
e = e1;
v = v1;
}
else
{
e = e4;
v = v4;
}
}
else
{
if (e3 < e4)
{
e = e3;
v = v3;
}
else
{
e = e4;
v = v4;
}
}
}
else
{
if (e2 < e3)
{
if (e2 < e4)
{
e = e2;
v = v2;
}
else
{
e = e4;
v = v4;
}
}
else
{
if (e3 < e4)
{
e = e3;
v = v3;
}
else
{
e = e4;
v = v4;
}
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Nanomesh
{
public static class Profiling
{
private static readonly Dictionary<string, Stopwatch> stopwatches = new Dictionary<string, Stopwatch>();
public static void Start(string key)
{
if (!stopwatches.ContainsKey(key))
{
stopwatches.Add(key, Stopwatch.StartNew());
}
else
{
stopwatches[key] = Stopwatch.StartNew();
}
}
public static string End(string key)
{
TimeSpan time = EndTimer(key);
return $"{key} done in {time.ToString("mm':'ss':'fff")}";
}
private static TimeSpan EndTimer(string key)
{
if (!stopwatches.ContainsKey(key))
{
return TimeSpan.MinValue;
}
Stopwatch sw = stopwatches[key];
sw.Stop();
stopwatches.Remove(key);
return sw.Elapsed;
}
public static TimeSpan Time(Action toTime)
{
Stopwatch timer = Stopwatch.StartNew();
toTime();
timer.Stop();
return timer.Elapsed;
}
}
}

View File

@@ -0,0 +1,632 @@
using System;
using System.Runtime.InteropServices;
namespace Nanomesh
{
[StructLayout(LayoutKind.Sequential)]
public partial struct Quaternion : IEquatable<Quaternion>
{
private const double radToDeg = 180.0 / Math.PI;
private const double degToRad = Math.PI / 180.0;
public const double kEpsilon = 1E-20; // should probably be used in the 0 tests in LookRotation or Slerp
public Vector3 xyz
{
set
{
x = value.x;
y = value.y;
z = value.z;
}
get => new Vector3(x, y, z);
}
public double x;
public double y;
public double z;
public double w;
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 Quaternion index: " + index + ", can use only 0,1,2,3");
}
}
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 Quaternion index: " + index + ", can use only 0,1,2,3");
}
}
}
/// <summary>
/// <para>The identity rotation (RO).</para>
/// </summary>
public static Quaternion identity => new Quaternion(0, 0, 0, 1);
/// <summary>
/// Gets the length (magnitude) of the quaternion.
/// </summary>
/// <seealso cref="LengthSquared"/>
public double Length => (double)System.Math.Sqrt(x * x + y * y + z * z + w * w);
/// <summary>
/// Gets the square of the quaternion length (magnitude).
/// </summary>
public double LengthSquared => x * x + y * y + z * z + w * w;
/// <summary>
/// <para>Constructs new Quaternion with given x,y,z,w components.</para>
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <param name="w"></param>
public Quaternion(double x, double y, double z, double w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
/// <summary>
/// Construct a new Quaternion from vector and w components
/// </summary>
/// <param name="v">The vector part</param>
/// <param name="w">The w part</param>
public Quaternion(Vector3 v, double w)
{
x = v.x;
y = v.y;
z = v.z;
this.w = w;
}
/// <summary>
/// <para>Set x, y, z and w components of an existing Quaternion.</para>
/// </summary>
/// <param name="new_x"></param>
/// <param name="new_y"></param>
/// <param name="new_z"></param>
/// <param name="new_w"></param>
public void Set(double new_x, double new_y, double new_z, double new_w)
{
x = new_x;
y = new_y;
z = new_z;
w = new_w;
}
/// <summary>
/// Scales the Quaternion to unit length.
/// </summary>
public static Quaternion Normalize(Quaternion q)
{
double mag = Math.Sqrt(Dot(q, q));
if (mag < kEpsilon)
{
return Quaternion.identity;
}
return new Quaternion(q.x / mag, q.y / mag, q.z / mag, q.w / mag);
}
/// <summary>
/// Scale the given quaternion to unit length
/// </summary>
/// <param name="q">The quaternion to normalize</param>
/// <param name="result">The normalized quaternion</param>
public void Normalize()
{
this = Normalize(this);
}
/// <summary>
/// <para>The dot product between two rotations.</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public static double Dot(Quaternion a, Quaternion b)
{
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
/// <summary>
/// <para>Creates a rotation which rotates /angle/ degrees around /axis/.</para>
/// </summary>
/// <param name="angle"></param>
/// <param name="axis"></param>
public static Quaternion AngleAxis(double angle, Vector3 axis)
{
return Quaternion.AngleAxis(angle, ref axis);
}
private static Quaternion AngleAxis(double degress, ref Vector3 axis)
{
if (axis.LengthSquared == 0.0)
{
return identity;
}
Quaternion result = identity;
double radians = degress * degToRad;
radians *= 0.5;
axis = axis.Normalized;
axis = axis * Math.Sin(radians);
result.x = axis.x;
result.y = axis.y;
result.z = axis.z;
result.w = Math.Cos(radians);
return Normalize(result);
}
public void ToAngleAxis(out double angle, out Vector3 axis)
{
Quaternion.ToAxisAngleRad(this, out axis, out angle);
angle *= radToDeg;
}
/// <summary>
/// <para>Creates a rotation which rotates from /fromDirection/ to /toDirection/.</para>
/// </summary>
/// <param name="fromDirection"></param>
/// <param name="toDirection"></param>
public static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection)
{
return RotateTowards(LookRotation(fromDirection), LookRotation(toDirection), double.MaxValue);
}
/// <summary>
/// <para>Creates a rotation which rotates from /fromDirection/ to /toDirection/.</para>
/// </summary>
/// <param name="fromDirection"></param>
/// <param name="toDirection"></param>
public void SetFromToRotation(Vector3 fromDirection, Vector3 toDirection)
{
this = Quaternion.FromToRotation(fromDirection, toDirection);
}
/// <summary>
/// <para>Creates a rotation with the specified /forward/ and /upwards/ directions.</para>
/// </summary>
/// <param name="forward">The direction to look in.</param>
/// <param name="upwards">The vector that defines in which direction up is.</param>
public static Quaternion LookRotation(Vector3 forward, Vector3 upwards)
{
return Quaternion.LookRotation(ref forward, ref upwards);
}
public static Quaternion LookRotation(Vector3 forward)
{
Vector3 up = new Vector3(1, 0, 0);
return Quaternion.LookRotation(ref forward, ref up);
}
private static Quaternion LookRotation(ref Vector3 forward, ref Vector3 up)
{
forward = Vector3.Normalize(forward);
Vector3 right = Vector3.Normalize(Vector3.Cross(up, forward));
up = Vector3.Cross(forward, right);
double m00 = right.x;
double m01 = right.y;
double m02 = right.z;
double m10 = up.x;
double m11 = up.y;
double m12 = up.z;
double m20 = forward.x;
double m21 = forward.y;
double m22 = forward.z;
double num8 = (m00 + m11) + m22;
Quaternion quaternion = new Quaternion();
if (num8 > 0)
{
double num = Math.Sqrt(num8 + 1);
quaternion.w = num * 0.5;
num = 0.5 / num;
quaternion.x = (m12 - m21) * num;
quaternion.y = (m20 - m02) * num;
quaternion.z = (m01 - m10) * num;
return quaternion;
}
if ((m00 >= m11) && (m00 >= m22))
{
double num7 = Math.Sqrt(((1 + m00) - m11) - m22);
double num4 = 0.5 / num7;
quaternion.x = 0.5 * num7;
quaternion.y = (m01 + m10) * num4;
quaternion.z = (m02 + m20) * num4;
quaternion.w = (m12 - m21) * num4;
return quaternion;
}
if (m11 > m22)
{
double num6 = Math.Sqrt(((1 + m11) - m00) - m22);
double num3 = 0.5 / num6;
quaternion.x = (m10 + m01) * num3;
quaternion.y = 0.5 * num6;
quaternion.z = (m21 + m12) * num3;
quaternion.w = (m20 - m02) * num3;
return quaternion;
}
double num5 = Math.Sqrt(((1 + m22) - m00) - m11);
double num2 = 0.5 / num5;
quaternion.x = (m20 + m02) * num2;
quaternion.y = (m21 + m12) * num2;
quaternion.z = 0.5 * num5;
quaternion.w = (m01 - m10) * num2;
return quaternion;
}
public void SetLookRotation(Vector3 view)
{
Vector3 up = new Vector3(1, 0, 0);
SetLookRotation(view, up);
}
/// <summary>
/// <para>Creates a rotation with the specified /forward/ and /upwards/ directions.</para>
/// </summary>
/// <param name="view">The direction to look in.</param>
/// <param name="up">The vector that defines in which direction up is.</param>
public void SetLookRotation(Vector3 view, Vector3 up)
{
this = Quaternion.LookRotation(view, up);
}
/// <summary>
/// <para>Spherically interpolates between /a/ and /b/ by t. The parameter /t/ is clamped to the range [0, 1].</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="t"></param>
public static Quaternion Slerp(Quaternion a, Quaternion b, double t)
{
return Quaternion.Slerp(ref a, ref b, t);
}
private static Quaternion Slerp(ref Quaternion a, ref Quaternion b, double t)
{
if (t > 1)
{
t = 1;
}
if (t < 0)
{
t = 0;
}
return SlerpUnclamped(ref a, ref b, t);
}
/// <summary>
/// <para>Spherically interpolates between /a/ and /b/ by t. The parameter /t/ is not clamped.</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="t"></param>
public static Quaternion SlerpUnclamped(Quaternion a, Quaternion b, double t)
{
return Quaternion.SlerpUnclamped(ref a, ref b, t);
}
private static Quaternion SlerpUnclamped(ref Quaternion a, ref Quaternion b, double t)
{
// if either input is zero, return the other.
if (a.LengthSquared == 0.0)
{
if (b.LengthSquared == 0.0)
{
return identity;
}
return b;
}
else if (b.LengthSquared == 0.0)
{
return a;
}
double cosHalfAngle = a.w * b.w + Vector3.Dot(a.xyz, b.xyz);
if (cosHalfAngle >= 1.0 || cosHalfAngle <= -1.0)
{
// angle = 0.0f, so just return one input.
return a;
}
else if (cosHalfAngle < 0.0)
{
b.xyz = -b.xyz;
b.w = -b.w;
cosHalfAngle = -cosHalfAngle;
}
double blendA;
double blendB;
if (cosHalfAngle < 0.99)
{
// do proper slerp for big angles
double halfAngle = Math.Acos(cosHalfAngle);
double sinHalfAngle = Math.Sin(halfAngle);
double oneOverSinHalfAngle = 1.0 / sinHalfAngle;
blendA = Math.Sin(halfAngle * (1.0 - t)) * oneOverSinHalfAngle;
blendB = Math.Sin(halfAngle * t) * oneOverSinHalfAngle;
}
else
{
// do lerp if angle is really small.
blendA = 1.0f - t;
blendB = t;
}
Quaternion result = new Quaternion(blendA * a.xyz + blendB * b.xyz, blendA * a.w + blendB * b.w);
if (result.LengthSquared > 0.0)
{
return Normalize(result);
}
else
{
return identity;
}
}
/// <summary>
/// <para>Interpolates between /a/ and /b/ by /t/ and normalizes the result afterwards. The parameter /t/ is clamped to the range [0, 1].</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="t"></param>
public static Quaternion Lerp(Quaternion a, Quaternion b, double t)
{
if (t > 1)
{
t = 1;
}
if (t < 0)
{
t = 0;
}
return Slerp(ref a, ref b, t); // TODO: use lerp not slerp, "Because quaternion works in 4D. Rotation in 4D are linear" ???
}
/// <summary>
/// <para>Interpolates between /a/ and /b/ by /t/ and normalizes the result afterwards. The parameter /t/ is not clamped.</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="t"></param>
public static Quaternion LerpUnclamped(Quaternion a, Quaternion b, double t)
{
return Slerp(ref a, ref b, t);
}
/// <summary>
/// <para>Rotates a rotation /from/ towards /to/.</para>
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <param name="maxDegreesDelta"></param>
public static Quaternion RotateTowards(Quaternion from, Quaternion to, double maxDegreesDelta)
{
double num = Quaternion.Angle(from, to);
if (num == 0)
{
return to;
}
double t = Math.Min(1, maxDegreesDelta / num);
return Quaternion.SlerpUnclamped(from, to, t);
}
/// <summary>
/// <para>Returns the Inverse of /rotation/.</para>
/// </summary>
/// <param name="rotation"></param>
public static Quaternion Inverse(Quaternion rotation)
{
double lengthSq = rotation.LengthSquared;
if (lengthSq != 0.0)
{
double i = 1.0 / lengthSq;
return new Quaternion(rotation.xyz * -i, rotation.w * i);
}
return rotation;
}
/// <summary>
/// <para>Returns a nicely formatted string of the Quaternion.</para>
/// </summary>
/// <param name="format"></param>
public override string ToString()
{
return $"{x}, {y}, {z}, {w}";
}
/// <summary>
/// <para>Returns a nicely formatted string of the Quaternion.</para>
/// </summary>
/// <param name="format"></param>
public string ToString(string format)
{
return string.Format("({0}, {1}, {2}, {3})", x.ToString(format), y.ToString(format), z.ToString(format), w.ToString(format));
}
/// <summary>
/// <para>Returns the angle in degrees between two rotations /a/ and /b/.</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
public static double Angle(Quaternion a, Quaternion b)
{
double f = Quaternion.Dot(a, b);
return Math.Acos(Math.Min(Math.Abs(f), 1)) * 2 * radToDeg;
}
/// <summary>
/// <para>Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).</para>
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
public static Quaternion Euler(double x, double y, double z)
{
return Quaternion.FromEulerRad(new Vector3((double)x, (double)y, (double)z) * degToRad);
}
/// <summary>
/// <para>Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).</para>
/// </summary>
/// <param name="euler"></param>
public static Quaternion Euler(Vector3 euler)
{
return Quaternion.FromEulerRad(euler * degToRad);
}
private static double NormalizeAngle(double angle)
{
while (angle > 360)
{
angle -= 360;
}
while (angle < 0)
{
angle += 360;
}
return angle;
}
private static Quaternion FromEulerRad(Vector3 euler)
{
double yaw = euler.x;
double pitch = euler.y;
double roll = euler.z;
double rollOver2 = roll * 0.5;
double sinRollOver2 = (double)System.Math.Sin((double)rollOver2);
double cosRollOver2 = (double)System.Math.Cos((double)rollOver2);
double pitchOver2 = pitch * 0.5;
double sinPitchOver2 = (double)System.Math.Sin((double)pitchOver2);
double cosPitchOver2 = (double)System.Math.Cos((double)pitchOver2);
double yawOver2 = yaw * 0.5;
double sinYawOver2 = (double)System.Math.Sin((double)yawOver2);
double cosYawOver2 = (double)System.Math.Cos((double)yawOver2);
Quaternion result;
result.x = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2;
result.y = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2;
result.z = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2;
result.w = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2;
return result;
}
private static void ToAxisAngleRad(Quaternion q, out Vector3 axis, out double angle)
{
if (System.Math.Abs(q.w) > 1.0)
{
q.Normalize();
}
angle = 2.0f * (double)System.Math.Acos(q.w); // angle
double den = (double)System.Math.Sqrt(1.0 - q.w * q.w);
if (den > 0.0001)
{
axis = q.xyz / den;
}
else
{
// This occurs when the angle is zero.
// Not a problem: just set an arbitrary normalized axis.
axis = new Vector3(1, 0, 0);
}
}
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2 ^ w.GetHashCode() >> 1;
}
public override bool Equals(object other)
{
if (!(other is Quaternion))
{
return false;
}
Quaternion quaternion = (Quaternion)other;
return x.Equals(quaternion.x) && y.Equals(quaternion.y) && z.Equals(quaternion.z) && w.Equals(quaternion.w);
}
public bool Equals(Quaternion other)
{
return x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z) && w.Equals(other.w);
}
public static Quaternion operator *(Quaternion lhs, Quaternion rhs)
{
return new Quaternion(lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y, lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z, lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x, lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z);
}
public static Vector3 operator *(Quaternion rotation, Vector3 point)
{
double num = rotation.x * 2;
double num2 = rotation.y * 2;
double num3 = rotation.z * 2;
double num4 = rotation.x * num;
double num5 = rotation.y * num2;
double num6 = rotation.z * num3;
double num7 = rotation.x * num2;
double num8 = rotation.x * num3;
double num9 = rotation.y * num3;
double num10 = rotation.w * num;
double num11 = rotation.w * num2;
double num12 = rotation.w * num3;
return new Vector3(
(1 - (num5 + num6)) * point.x + (num7 - num12) * point.y + (num8 + num11) * point.z,
(num7 + num12) * point.x + (1 - (num4 + num6)) * point.y + (num9 - num10) * point.z,
(num8 - num11) * point.x + (num9 + num10) * point.y + (1 - (num4 + num5)) * point.z);
}
public static bool operator ==(Quaternion lhs, Quaternion rhs)
{
return Quaternion.Dot(lhs, rhs) > 0.999999999;
}
public static bool operator !=(Quaternion lhs, Quaternion rhs)
{
return Quaternion.Dot(lhs, rhs) <= 0.999999999;
}
}
}

View File

@@ -0,0 +1,97 @@
namespace Nanomesh
{
public readonly struct SymmetricMatrix
{
public readonly double m0, m1, m2, m3, m4, m5, m6, m7, m8, m9;
public SymmetricMatrix(in double m0, in double m1, in double m2, in double m3, in double m4, in double m5, in double m6, in double m7, in double m8, in 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;
}
public SymmetricMatrix(in double a, in double b, in double c, in double d)
{
m0 = a * a;
m1 = a * b;
m2 = a * c;
m3 = a * d;
m4 = b * b;
m5 = b * c;
m6 = b * d;
m7 = c * c;
m8 = c * d;
m9 = d * d;
}
public static SymmetricMatrix operator +(in SymmetricMatrix a, in 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
);
}
public double DeterminantXYZ()
{
return
m0 * m4 * m7 +
m2 * m1 * m5 +
m1 * m5 * m2 -
m2 * m4 * m2 -
m0 * m5 * m5 -
m1 * m1 * m7;
}
public double DeterminantX()
{
return
m1 * m5 * m8 +
m3 * m4 * m7 +
m2 * m6 * m5 -
m3 * m5 * m5 -
m1 * m6 * m7 -
m2 * m4 * m8;
}
public double DeterminantY()
{
return
m0 * m5 * m8 +
m3 * m1 * m7 +
m2 * m6 * m2 -
m3 * m5 * m2 -
m0 * m6 * m7 -
m2 * m1 * m8;
}
public double DeterminantZ()
{
return
m0 * m4 * m8 +
m3 * m1 * m5 +
m1 * m6 * m2 -
m3 * m4 * m2 -
m0 * m6 * m5 -
m1 * m1 * m8;
}
public override string ToString()
{
return $"{m0} {m1} {m2} {m3}| {m4} {m5} {m6} | {m7} {m8} | {m9}";
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Nanomesh
{
public static class TextUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double ToDouble(this string text)
{
return double.Parse(text, CultureInfo.InvariantCulture);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ToFloat(this string text)
{
return float.Parse(text, CultureInfo.InvariantCulture);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToInt(this string text)
{
return int.Parse(text, CultureInfo.InvariantCulture);
}
}
}

View File

@@ -0,0 +1,377 @@
using System;
namespace Nanomesh
{
public readonly struct Vector2 : IEquatable<Vector2>, IInterpolable<Vector2>
{
public readonly double x;
public readonly double y;
// Access the /x/ or /y/ component using [0] or [1] respectively.
public double this[int index]
{
get
{
switch (index)
{
case 0: return x;
case 1: return y;
default:
throw new IndexOutOfRangeException("Invalid Vector2 index!");
}
}
}
// Constructs a new vector with given x, y components.
public Vector2(double x, double y) { this.x = x; this.y = y; }
// Linearly interpolates between two vectors.
public static Vector2 Lerp(Vector2 a, Vector2 b, double t)
{
t = MathF.Clamp(t, 0, 1);
return new Vector2(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
// Linearly interpolates between two vectors without clamping the interpolant
public static Vector2 LerpUnclamped(Vector2 a, Vector2 b, double t)
{
return new Vector2(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
// Moves a point /current/ towards /target/.
public static Vector2 MoveTowards(Vector2 current, Vector2 target, double maxDistanceDelta)
{
// avoid vector ops because current scripting backends are terrible at inlining
double toVector_x = target.x - current.x;
double toVector_y = target.y - current.y;
double sqDist = toVector_x * toVector_x + toVector_y * toVector_y;
if (sqDist == 0 || (maxDistanceDelta >= 0 && sqDist <= maxDistanceDelta * maxDistanceDelta))
{
return target;
}
double dist = Math.Sqrt(sqDist);
return new Vector2(current.x + toVector_x / dist * maxDistanceDelta,
current.y + toVector_y / dist * maxDistanceDelta);
}
// Multiplies two vectors component-wise.
public static Vector2 Scale(Vector2 a, Vector2 b) => new Vector2(a.x * b.x, a.y * b.y);
public static Vector2 Normalize(in Vector2 value)
{
double mag = Magnitude(in value);
if (mag > K_EPSILON)
{
return value / mag;
}
else
{
return Zero;
}
}
public Vector2 Normalize() => Normalize(in this);
public static double SqrMagnitude(in Vector2 a) => a.x * a.x + a.y * a.y;
/// <summary>
/// Returns the squared length of this vector (RO).
/// </summary>
public double SqrMagnitude() => SqrMagnitude(in this);
public static double Magnitude(in Vector2 vector) => Math.Sqrt(SqrMagnitude(in vector));
public double Magnitude() => Magnitude(this);
// used to allow Vector2s to be used as keys in hash tables
public override int GetHashCode()
{
return x.GetHashCode() ^ (y.GetHashCode() << 2);
}
// also required for being able to use Vector2s as keys in hash tables
public override bool Equals(object other)
{
if (!(other is Vector2))
{
return false;
}
return Equals((Vector2)other);
}
public bool Equals(Vector2 other)
{
return x == other.x && y == other.y;
}
public static Vector2 Reflect(Vector2 inDirection, Vector2 inNormal)
{
double factor = -2F * Dot(inNormal, inDirection);
return new Vector2(factor * inNormal.x + inDirection.x, factor * inNormal.y + inDirection.y);
}
public static Vector2 Perpendicular(Vector2 inDirection)
{
return new Vector2(-inDirection.y, inDirection.x);
}
/// <summary>
/// Returns the dot Product of two vectors.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static double Dot(Vector2 lhs, Vector2 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; }
/// <summary>
/// Returns the angle in radians between /from/ and /to/.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static double AngleRadians(Vector2 from, Vector2 to)
{
// sqrt(a) * sqrt(b) = sqrt(a * b) -- valid for real numbers
double denominator = Math.Sqrt(from.SqrMagnitude() * to.SqrMagnitude());
if (denominator < K_EPSILON_NORMAL_SQRT)
{
return 0F;
}
double dot = MathF.Clamp(Dot(from, to) / denominator, -1F, 1F);
return Math.Acos(dot);
}
public static double AngleDegrees(Vector2 from, Vector2 to)
{
return AngleRadians(from, to) / MathF.PI * 180f;
}
/// <summary>
/// Returns the signed angle in degrees between /from/ and /to/. Always returns the smallest possible angle
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static double SignedAngle(Vector2 from, Vector2 to)
{
double unsigned_angle = AngleDegrees(from, to);
double sign = Math.Sign(from.x * to.y - from.y * to.x);
return unsigned_angle * sign;
}
/// <summary>
/// Returns the distance between /a/ and /b/.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static double Distance(Vector2 a, Vector2 b)
{
double diff_x = a.x - b.x;
double diff_y = a.y - b.y;
return Math.Sqrt(diff_x * diff_x + diff_y * diff_y);
}
/// <summary>
/// Returns a copy of /vector/ with its magnitude clamped to /maxLength/.
/// </summary>
/// <param name="vector"></param>
/// <param name="maxLength"></param>
/// <returns></returns>
public static Vector2 ClampMagnitude(Vector2 vector, double maxLength)
{
double sqrMagnitude = vector.SqrMagnitude();
if (sqrMagnitude > maxLength * maxLength)
{
double mag = Math.Sqrt(sqrMagnitude);
//these intermediate variables force the intermediate result to be
//of double precision. without this, the intermediate result can be of higher
//precision, which changes behavior.
double normalized_x = vector.x / mag;
double normalized_y = vector.y / mag;
return new Vector2(normalized_x * maxLength,
normalized_y * maxLength);
}
return vector;
}
/// <summary>
/// Returns a vector that is made from the smallest components of two vectors.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static Vector2 Min(Vector2 lhs, Vector2 rhs) { return new Vector2(Math.Min(lhs.x, rhs.x), Math.Min(lhs.y, rhs.y)); }
/// <summary>
/// Returns a vector that is made from the largest components of two vectors.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static Vector2 Max(Vector2 lhs, Vector2 rhs) { return new Vector2(Math.Max(lhs.x, rhs.x), Math.Max(lhs.y, rhs.y)); }
public Vector2 Interpolate(Vector2 other, double ratio) => this * ratio + other * (1 - ratio);
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2 operator +(Vector2 a, Vector2 b) { return new Vector2(a.x + b.x, a.y + b.y); }
/// <summary>
/// Subtracts one vector from another.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2 operator -(Vector2 a, Vector2 b) { return new Vector2(a.x - b.x, a.y - b.y); }
/// <summary>
/// Multiplies one vector by another.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2 operator *(Vector2 a, Vector2 b) { return new Vector2(a.x * b.x, a.y * b.y); }
/// <summary>
/// Divides one vector over another.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2 operator /(Vector2 a, Vector2 b) { return new Vector2(a.x / b.x, a.y / b.y); }
/// <summary>
/// Negates a vector.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
public static Vector2 operator -(Vector2 a) { return new Vector2(-a.x, -a.y); }
/// <summary>
/// Multiplies a vector by a number.
/// </summary>
/// <param name="a"></param>
/// <param name="d"></param>
/// <returns></returns>
public static Vector2 operator *(Vector2 a, double d) { return new Vector2(a.x * d, a.y * d); }
/// <summary>
/// Multiplies a vector by a number.
/// </summary>
/// <param name="d"></param>
/// <param name="a"></param>
/// <returns></returns>
public static Vector2 operator *(double d, Vector2 a) { return new Vector2(a.x * d, a.y * d); }
/// <summary>
/// Divides a vector by a number.
/// </summary>
/// <param name="a"></param>
/// <param name="d"></param>
/// <returns></returns>
public static Vector2 operator /(Vector2 a, double d) { return new Vector2(a.x / d, a.y / d); }
/// <summary>
/// Returns true if the vectors are equal.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static bool operator ==(Vector2 lhs, Vector2 rhs)
{
// Returns false in the presence of NaN values.
double diff_x = lhs.x - rhs.x;
double diff_y = lhs.y - rhs.y;
return (diff_x * diff_x + diff_y * diff_y) < K_EPSILON * K_EPSILON;
}
/// <summary>
/// Returns true if vectors are different.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static bool operator !=(Vector2 lhs, Vector2 rhs)
{
// Returns true in the presence of NaN values.
return !(lhs == rhs);
}
/// <summary>
/// Converts a [[Vector3]] to a Vector2.
/// </summary>
/// <param name="v"></param>
public static implicit operator Vector2(Vector3F v)
{
return new Vector2(v.x, v.y);
}
/// <summary>
/// Converts a Vector2 to a [[Vector3]].
/// </summary>
/// <param name="v"></param>
public static implicit operator Vector3(Vector2 v)
{
return new Vector3(v.x, v.y, 0);
}
public static implicit operator Vector2F(Vector2 vec)
{
return new Vector2F((float)vec.x, (float)vec.y);
}
public static explicit operator Vector2(Vector2F vec)
{
return new Vector2(vec.x, vec.y);
}
public static readonly Vector2 zeroVector = new Vector2(0F, 0F);
public static readonly Vector2 oneVector = new Vector2(1F, 1F);
public static readonly Vector2 upVector = new Vector2(0F, 1F);
public static readonly Vector2 downVector = new Vector2(0F, -1F);
public static readonly Vector2 leftVector = new Vector2(-1F, 0F);
public static readonly Vector2 rightVector = new Vector2(1F, 0F);
public static readonly Vector2 positiveInfinityVector = new Vector2(double.PositiveInfinity, double.PositiveInfinity);
public static readonly Vector2 negativeInfinityVector = new Vector2(double.NegativeInfinity, double.NegativeInfinity);
public static Vector2 Zero => zeroVector;
public static Vector2 One => oneVector;
public static Vector2 Up => upVector;
public static Vector2 Down => downVector;
public static Vector2 Left => leftVector;
public static Vector2 Right => rightVector;
public static Vector2 PositiveInfinity => positiveInfinityVector;
public static Vector2 NegativeInfinity => negativeInfinityVector;
public const double K_EPSILON = 0.00001F;
public const double K_EPSILON_NORMAL_SQRT = 1e-15f;
}
}

View File

@@ -0,0 +1,371 @@
using System;
namespace Nanomesh
{
public readonly struct Vector2F : IEquatable<Vector2F>, IInterpolable<Vector2F>
{
public readonly float x;
public readonly float y;
// Access the /x/ or /y/ component using [0] or [1] respectively.
public float this[int index]
{
get
{
switch (index)
{
case 0: return x;
case 1: return y;
default:
throw new IndexOutOfRangeException("Invalid Vector2 index!");
}
}
}
// Constructs a new vector with given x, y components.
public Vector2F(float x, float y) { this.x = x; this.y = y; }
// Linearly interpolates between two vectors.
public static Vector2F Lerp(Vector2F a, Vector2F b, float t)
{
t = MathF.Clamp(t, 0, 1);
return new Vector2F(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
// Linearly interpolates between two vectors without clamping the interpolant
public static Vector2F LerpUnclamped(Vector2F a, Vector2F b, float t)
{
return new Vector2F(
a.x + (b.x - a.x) * t,
a.y + (b.y - a.y) * t
);
}
// Moves a point /current/ towards /target/.
public static Vector2F MoveTowards(Vector2F current, Vector2F target, float maxDistanceDelta)
{
// avoid vector ops because current scripting backends are terrible at inlining
float toVector_x = target.x - current.x;
float toVector_y = target.y - current.y;
float sqDist = toVector_x * toVector_x + toVector_y * toVector_y;
if (sqDist == 0 || (maxDistanceDelta >= 0 && sqDist <= maxDistanceDelta * maxDistanceDelta))
{
return target;
}
float dist = MathF.Sqrt(sqDist);
return new Vector2F(current.x + toVector_x / dist * maxDistanceDelta,
current.y + toVector_y / dist * maxDistanceDelta);
}
// Multiplies two vectors component-wise.
public static Vector2F Scale(Vector2F a, Vector2F b) { return new Vector2F(a.x * b.x, a.y * b.y); }
public static Vector2F Normalize(in Vector2F value)
{
float mag = Magnitude(in value);
if (mag > K_EPSILON)
{
return value / mag;
}
else
{
return Zero;
}
}
public Vector2F Normalize() => Normalize(in this);
public static float SqrMagnitude(in Vector2F a) => a.x * a.x + a.y * a.y;
/// <summary>
/// Returns the squared length of this vector (RO).
/// </summary>
public float SqrMagnitude() => SqrMagnitude(in this);
public static float Magnitude(in Vector2F vector) => (float)Math.Sqrt(SqrMagnitude(in vector));
public float Magnitude() => Magnitude(this);
// used to allow Vector2s to be used as keys in hash tables
public override int GetHashCode()
{
return x.GetHashCode() ^ (y.GetHashCode() << 2);
}
// also required for being able to use Vector2s as keys in hash tables
public override bool Equals(object other)
{
if (!(other is Vector2F))
{
return false;
}
return Equals((Vector2F)other);
}
public bool Equals(Vector2F other)
{
return Vector2FComparer.Default.Equals(this, other);
//return x == other.x && y == other.y;
}
public static Vector2F Reflect(Vector2F inDirection, Vector2F inNormal)
{
float factor = -2F * Dot(inNormal, inDirection);
return new Vector2F(factor * inNormal.x + inDirection.x, factor * inNormal.y + inDirection.y);
}
public static Vector2F Perpendicular(Vector2F inDirection)
{
return new Vector2F(-inDirection.y, inDirection.x);
}
/// <summary>
/// Returns the dot Product of two vectors.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static float Dot(Vector2F lhs, Vector2F rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; }
/// <summary>
/// Returns the angle in radians between /from/ and /to/.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static float AngleRadians(Vector2F from, Vector2F to)
{
// sqrt(a) * sqrt(b) = sqrt(a * b) -- valid for real numbers
float denominator = MathF.Sqrt(from.SqrMagnitude() * to.SqrMagnitude());
if (denominator < K_EPSILON_NORMAL_SQRT)
{
return 0F;
}
float dot = MathF.Clamp(Dot(from, to) / denominator, -1F, 1F);
return MathF.Acos(dot);
}
public static float AngleDegrees(Vector2F from, Vector2F to)
{
return AngleRadians(from, to) / MathF.PI * 180f;
}
/// <summary>
/// Returns the signed angle in degrees between /from/ and /to/. Always returns the smallest possible angle
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static float SignedAngle(Vector2F from, Vector2F to)
{
float unsigned_angle = AngleDegrees(from, to);
float sign = MathF.Sign(from.x * to.y - from.y * to.x);
return unsigned_angle * sign;
}
/// <summary>
/// Returns the distance between /a/ and /b/.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static float Distance(Vector2F a, Vector2F b)
{
float diff_x = a.x - b.x;
float diff_y = a.y - b.y;
return MathF.Sqrt(diff_x * diff_x + diff_y * diff_y);
}
/// <summary>
/// Returns a copy of /vector/ with its magnitude clamped to /maxLength/.
/// </summary>
/// <param name="vector"></param>
/// <param name="maxLength"></param>
/// <returns></returns>
public static Vector2F ClampMagnitude(Vector2F vector, float maxLength)
{
float sqrMagnitude = vector.SqrMagnitude();
if (sqrMagnitude > maxLength * maxLength)
{
float mag = MathF.Sqrt(sqrMagnitude);
//these intermediate variables force the intermediate result to be
//of float precision. without this, the intermediate result can be of higher
//precision, which changes behavior.
float normalized_x = vector.x / mag;
float normalized_y = vector.y / mag;
return new Vector2F(normalized_x * maxLength,
normalized_y * maxLength);
}
return vector;
}
/// <summary>
/// Returns a vector that is made from the smallest components of two vectors.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static Vector2F Min(Vector2F lhs, Vector2F rhs) { return new Vector2F(MathF.Min(lhs.x, rhs.x), MathF.Min(lhs.y, rhs.y)); }
/// <summary>
/// Returns a vector that is made from the largest components of two vectors.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static Vector2F Max(Vector2F lhs, Vector2F rhs) { return new Vector2F(MathF.Max(lhs.x, rhs.x), MathF.Max(lhs.y, rhs.y)); }
public Vector2F Interpolate(Vector2F other, double ratio) => this * ratio + other * (1 - ratio);
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2F operator +(Vector2F a, Vector2F b) { return new Vector2F(a.x + b.x, a.y + b.y); }
/// <summary>
/// Subtracts one vector from another.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2F operator -(Vector2F a, Vector2F b) { return new Vector2F(a.x - b.x, a.y - b.y); }
/// <summary>
/// Multiplies one vector by another.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2F operator *(Vector2F a, Vector2F b) { return new Vector2F(a.x * b.x, a.y * b.y); }
/// <summary>
/// Divides one vector over another.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Vector2F operator /(Vector2F a, Vector2F b) { return new Vector2F(a.x / b.x, a.y / b.y); }
/// <summary>
/// Negates a vector.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
public static Vector2F operator -(Vector2F a) { return new Vector2F(-a.x, -a.y); }
/// <summary>
/// Multiplies a vector by a number.
/// </summary>
/// <param name="a"></param>
/// <param name="d"></param>
/// <returns></returns>
public static Vector2F operator *(Vector2F a, float d) { return new Vector2F(a.x * d, a.y * d); }
public static Vector2 operator *(Vector2F a, double d) { return new Vector2(a.x * d, a.y * d); }
/// <summary>
/// Multiplies a vector by a number.
/// </summary>
/// <param name="d"></param>
/// <param name="a"></param>
/// <returns></returns>
public static Vector2F operator *(float d, Vector2F a) { return new Vector2F(a.x * d, a.y * d); }
public static Vector2 operator *(double d, Vector2F a) { return new Vector2(a.x * d, a.y * d); }
/// <summary>
/// Divides a vector by a number.
/// </summary>
/// <param name="a"></param>
/// <param name="d"></param>
/// <returns></returns>
public static Vector2F operator /(Vector2F a, float d) { return new Vector2F(a.x / d, a.y / d); }
/// <summary>
/// Returns true if the vectors are equal.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static bool operator ==(Vector2F lhs, Vector2F rhs)
{
// Returns false in the presence of NaN values.
float diff_x = lhs.x - rhs.x;
float diff_y = lhs.y - rhs.y;
return (diff_x * diff_x + diff_y * diff_y) < K_EPSILON * K_EPSILON;
}
/// <summary>
/// Returns true if vectors are different.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
public static bool operator !=(Vector2F lhs, Vector2F rhs)
{
// Returns true in the presence of NaN values.
return !(lhs == rhs);
}
/// <summary>
/// Converts a [[Vector3]] to a Vector2.
/// </summary>
/// <param name="v"></param>
public static implicit operator Vector2F(Vector3F v)
{
return new Vector2F(v.x, v.y);
}
/// <summary>
/// Converts a Vector2 to a [[Vector3]].
/// </summary>
/// <param name="v"></param>
public static implicit operator Vector3(Vector2F v)
{
return new Vector3(v.x, v.y, 0);
}
public static readonly Vector2F zeroVector = new Vector2F(0F, 0F);
public static readonly Vector2F oneVector = new Vector2F(1F, 1F);
public static readonly Vector2F upVector = new Vector2F(0F, 1F);
public static readonly Vector2F downVector = new Vector2F(0F, -1F);
public static readonly Vector2F leftVector = new Vector2F(-1F, 0F);
public static readonly Vector2F rightVector = new Vector2F(1F, 0F);
public static readonly Vector2F positiveInfinityVector = new Vector2F(float.PositiveInfinity, float.PositiveInfinity);
public static readonly Vector2F negativeInfinityVector = new Vector2F(float.NegativeInfinity, float.NegativeInfinity);
public static Vector2F Zero => zeroVector;
public static Vector2F One => oneVector;
public static Vector2F Up => upVector;
public static Vector2F Down => downVector;
public static Vector2F Left => leftVector;
public static Vector2F Right => rightVector;
public static Vector2F PositiveInfinity => positiveInfinityVector;
public static Vector2F NegativeInfinity => negativeInfinityVector;
public const float K_EPSILON = 0.00001F;
public const float K_EPSILON_NORMAL_SQRT = 1e-15f;
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace Nanomesh
{
public class Vector2FComparer : IEqualityComparer<Vector2F>
{
private static Vector2FComparer _instance;
public static Vector2FComparer Default => _instance ?? (_instance = new Vector2FComparer(0.0001f));
private readonly float _tolerance;
public Vector2FComparer(float tolerance)
{
_tolerance = tolerance;
}
public bool Equals(Vector2F x, Vector2F y)
{
return (int)(x.x / _tolerance) == (int)(y.x / _tolerance)
&& (int)(x.y / _tolerance) == (int)(y.y / _tolerance);
}
public int GetHashCode(Vector2F obj)
{
return (int)(obj.x / _tolerance) ^ ((int)(obj.y / _tolerance) << 2);
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
namespace Nanomesh
{
public readonly struct Vector3 : IEquatable<Vector3>, IInterpolable<Vector3>
{
public readonly double x;
public readonly double y;
public readonly double z;
public Vector3(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
public Vector3(double x, double y)
{
this.x = x;
this.y = y;
z = 0.0;
}
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 Vector3 index!");
}
}
}
public override int GetHashCode()
{
return x.GetHashCode() ^ (y.GetHashCode() << 2) ^ (z.GetHashCode() >> 2);
}
public override bool Equals(object other)
{
if (!(other is Vector3))
{
return false;
}
return Equals((Vector3)other);
}
public bool Equals(Vector3 other)
{
return x == other.x && y == other.y && z == other.z;
}
public static Vector3 operator +(in Vector3 a, in Vector3 b) { return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z); }
public static Vector3 operator -(in Vector3 a, in Vector3 b) { return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z); }
public static Vector3 operator -(in Vector3 a) { return new Vector3(-a.x, -a.y, -a.z); }
public static Vector3 operator *(in Vector3 a, double d) { return new Vector3(a.x * d, a.y * d, a.z * d); }
public static Vector3 operator *(double d, in Vector3 a) { return new Vector3(a.x * d, a.y * d, a.z * d); }
public static Vector3 operator /(in Vector3 a, double d) { return new Vector3(MathUtils.DivideSafe(a.x, d), MathUtils.DivideSafe(a.y, d), MathUtils.DivideSafe(a.z, d)); }
public static bool operator ==(in Vector3 lhs, in Vector3 rhs)
{
double diff_x = lhs.x - rhs.x;
double diff_y = lhs.y - rhs.y;
double diff_z = lhs.z - rhs.z;
double sqrmag = diff_x * diff_x + diff_y * diff_y + diff_z * diff_z;
return sqrmag < MathUtils.EpsilonDouble;
}
public static bool operator !=(in Vector3 lhs, in Vector3 rhs)
{
return !(lhs == rhs);
}
public static Vector3 Cross(in Vector3 lhs, in Vector3 rhs)
{
return 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);
}
public static implicit operator Vector3F(Vector3 vec)
{
return new Vector3F((float)vec.x, (float)vec.y, (float)vec.z);
}
public static explicit operator Vector3(Vector3F vec)
{
return new Vector3(vec.x, vec.y, vec.z);
}
public static double Dot(in Vector3 lhs, in Vector3 rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
}
public static Vector3 Normalize(in Vector3 value)
{
double mag = Magnitude(value);
return value / mag;
}
public Vector3 Normalized => Vector3.Normalize(this);
public static double Distance(in Vector3 a, in Vector3 b)
{
double diff_x = a.x - b.x;
double diff_y = a.y - b.y;
double diff_z = a.z - b.z;
return Math.Sqrt(diff_x * diff_x + diff_y * diff_y + diff_z * diff_z);
}
public static double Magnitude(in Vector3 vector)
{
return Math.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
}
public static Vector3 ProjectPointOnLine(in Vector3 linePoint, in Vector3 lineVec, in Vector3 point)
{
Vector3 linePointToPoint = point - linePoint;
return linePoint + lineVec * Dot(linePointToPoint, lineVec);
}
public static double DistancePointLine(in Vector3 point, in Vector3 lineStart, in Vector3 lineEnd)
{
return Magnitude(ProjectPointOnLine(lineStart, (lineEnd - lineStart).Normalized, point) - point);
}
public double LengthSquared => x * x + y * y + z * z;
public double Length => Math.Sqrt(x * x + y * y + z * z);
public static Vector3 Min(in Vector3 lhs, in Vector3 rhs)
{
return new Vector3(Math.Min(lhs.x, rhs.x), Math.Min(lhs.y, rhs.y), Math.Min(lhs.z, rhs.z));
}
public static Vector3 Max(in Vector3 lhs, in Vector3 rhs)
{
return new Vector3(Math.Max(lhs.x, rhs.x), Math.Max(lhs.y, rhs.y), Math.Max(lhs.z, rhs.z));
}
public static readonly Vector3 zeroVector = new Vector3(0f, 0f, 0f);
public static readonly Vector3 oneVector = new Vector3(1f, 1f, 1f);
public static readonly Vector3 positiveInfinityVector = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
public static readonly Vector3 negativeInfinityVector = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
public static Vector3 Zero => zeroVector;
public static Vector3 One => oneVector;
public static Vector3 PositiveInfinity => positiveInfinityVector;
public static Vector3 NegativeInfinity => negativeInfinityVector;
public static double AngleRadians(in Vector3 from, in Vector3 to)
{
double denominator = Math.Sqrt(from.LengthSquared * to.LengthSquared);
if (denominator < 1e-15F)
{
return 0F;
}
double dot = MathF.Clamp(Dot(from, to) / denominator, -1.0, 1.0);
return Math.Acos(dot);
}
public static double AngleDegrees(in Vector3 from, in Vector3 to)
{
return AngleRadians(from, to) / Math.PI * 180d;
}
public override string ToString()
{
return $"{x}, {y}, {z}";
}
public Vector3 Interpolate(Vector3 other, double ratio) => this * ratio + other * (1 - ratio);
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Nanomesh
{
public class Vector3Comparer : IEqualityComparer<Vector3>
{
private readonly double _tolerance;
public Vector3Comparer(double tolerance)
{
_tolerance = tolerance;
}
public bool Equals(Vector3 x, Vector3 y)
{
return (int)(x.x / _tolerance) == (int)(y.x / _tolerance)
&& (int)(x.y / _tolerance) == (int)(y.y / _tolerance)
&& (int)(x.z / _tolerance) == (int)(y.z / _tolerance);
}
public int GetHashCode(Vector3 obj)
{
return (int)(obj.x / _tolerance) ^ ((int)(obj.y / _tolerance) << 2) ^ ((int)(obj.z / _tolerance) >> 2);
}
}
}

View File

@@ -0,0 +1,172 @@
using System;
namespace Nanomesh
{
public readonly struct Vector3F : IEquatable<Vector3F>, IInterpolable<Vector3F>
{
public readonly float x;
public readonly float y;
public readonly float z;
public Vector3F(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
public Vector3F(float x, float y)
{
this.x = x;
this.y = y;
z = 0F;
}
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 Vector3F index!");
}
}
}
public override int GetHashCode()
{
return Vector3FComparer.Default.GetHashCode(this);
//return x.GetHashCode() ^ (y.GetHashCode() << 2) ^ (z.GetHashCode() >> 2);
}
public override bool Equals(object other)
{
if (!(other is Vector3F))
{
return false;
}
return Equals((Vector3F)other);
}
public bool Equals(Vector3F other)
{
return Vector3FComparer.Default.Equals(this, other);
//return x == other.x && y == other.y && z == other.z;
}
public static Vector3F operator +(in Vector3F a, in Vector3F b) { return new Vector3F(a.x + b.x, a.y + b.y, a.z + b.z); }
public static Vector3F operator -(in Vector3F a, in Vector3F b) { return new Vector3F(a.x - b.x, a.y - b.y, a.z - b.z); }
public static Vector3F operator -(in Vector3F a) { return new Vector3F(-a.x, -a.y, -a.z); }
public static Vector3F operator *(in Vector3F a, float d) { return new Vector3F(a.x * d, a.y * d, a.z * d); }
public static Vector3F operator *(float d, in Vector3F a) { return new Vector3F(a.x * d, a.y * d, a.z * d); }
public static Vector3 operator *(double d, in Vector3F a) { return new Vector3(a.x * d, a.y * d, a.z * d); }
public static Vector3F operator /(in Vector3F a, float d) { return new Vector3F(MathUtils.DivideSafe(a.x, d), MathUtils.DivideSafe(a.y, d), MathUtils.DivideSafe(a.z, d)); }
public static bool operator ==(in Vector3F lhs, in Vector3F rhs)
{
float diff_x = lhs.x - rhs.x;
float diff_y = lhs.y - rhs.y;
float diff_z = lhs.z - rhs.z;
float sqrmag = diff_x * diff_x + diff_y * diff_y + diff_z * diff_z;
return sqrmag < MathUtils.EpsilonFloat;
}
public static bool operator !=(in Vector3F lhs, in Vector3F rhs)
{
return !(lhs == rhs);
}
public static Vector3F Cross(in Vector3F lhs, in Vector3F rhs)
{
return new Vector3F(
lhs.y * rhs.z - lhs.z * rhs.y,
lhs.z * rhs.x - lhs.x * rhs.z,
lhs.x * rhs.y - lhs.y * rhs.x);
}
public static float Dot(in Vector3F lhs, in Vector3F rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
}
public static Vector3F Normalize(in Vector3F value)
{
float mag = Magnitude(value);
return value / mag;
}
public Vector3F Normalized => Vector3F.Normalize(this);
public static float Distance(in Vector3F a, in Vector3F b)
{
float diff_x = a.x - b.x;
float diff_y = a.y - b.y;
float diff_z = a.z - b.z;
return MathF.Sqrt(diff_x * diff_x + diff_y * diff_y + diff_z * diff_z);
}
public static float Magnitude(in Vector3F vector)
{
return MathF.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
}
public float SqrMagnitude => x * x + y * y + z * z;
public static Vector3F Min(in Vector3F lhs, in Vector3F rhs)
{
return new Vector3F(MathF.Min(lhs.x, rhs.x), MathF.Min(lhs.y, rhs.y), MathF.Min(lhs.z, rhs.z));
}
public static Vector3F Max(in Vector3F lhs, in Vector3F rhs)
{
return new Vector3F(MathF.Max(lhs.x, rhs.x), MathF.Max(lhs.y, rhs.y), MathF.Max(lhs.z, rhs.z));
}
public static readonly Vector3F zeroVector = new Vector3F(0f, 0f, 0f);
public static readonly Vector3F oneVector = new Vector3F(1f, 1f, 1f);
public static readonly Vector3F positiveInfinityVector = new Vector3F(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
public static readonly Vector3F negativeInfinityVector = new Vector3F(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
public static Vector3F Zero => zeroVector;
public static Vector3F One => oneVector;
public static Vector3F PositiveInfinity => positiveInfinityVector;
public static Vector3F NegativeInfinity => negativeInfinityVector;
public static float AngleRadians(in Vector3F from, in Vector3F to)
{
float denominator = MathF.Sqrt(from.SqrMagnitude * to.SqrMagnitude);
if (denominator < 1e-15F)
{
return 0F;
}
float dot = MathF.Clamp(Dot(from, to) / denominator, -1F, 1F);
return MathF.Acos(dot);
}
public static float AngleDegrees(in Vector3F from, in Vector3F to)
{
return AngleRadians(from, to) / MathF.PI * 180f;
}
public override string ToString()
{
return $"{x}, {y}, {z}";
}
public Vector3F Interpolate(Vector3F other, double ratio) => (ratio * this + (1 - ratio) * other).Normalized;
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace Nanomesh
{
public class Vector3FComparer : IEqualityComparer<Vector3F>
{
private static Vector3FComparer _instance;
public static Vector3FComparer Default => _instance ?? (_instance = new Vector3FComparer(0.001f));
private readonly float _tolerance;
public Vector3FComparer(float tolerance)
{
_tolerance = tolerance;
}
public bool Equals(Vector3F x, Vector3F y)
{
return (int)(x.x / _tolerance) == (int)(y.x / _tolerance)
&& (int)(x.y / _tolerance) == (int)(y.y / _tolerance)
&& (int)(x.z / _tolerance) == (int)(y.z / _tolerance);
}
public int GetHashCode(Vector3F obj)
{
return (int)(obj.x / _tolerance) ^ ((int)(obj.y / _tolerance) << 2) ^ ((int)(obj.z / _tolerance) >> 2);
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
namespace Nanomesh
{
public readonly struct Vector4F : IEquatable<Vector4F>, IInterpolable<Vector4F>
{
public readonly float x;
public readonly float y;
public readonly float z;
public readonly float w;
public Vector4F(float x, float y, float z, float w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
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 Vector4F index!");
}
}
}
public override int GetHashCode()
{
return Vector4FComparer.Default.GetHashCode(this);
}
public override bool Equals(object other)
{
if (!(other is Vector4F))
{
return false;
}
return Equals((Vector4F)other);
}
public bool Equals(Vector4F other)
{
return Vector4FComparer.Default.Equals(this, other);
}
public static Vector4F operator +(in Vector4F a, in Vector4F b)
=> new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
public static Vector4F operator -(in Vector4F a, in Vector4F b)
=> new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
public static Vector4F operator *(in Vector4F a, float d)
=> new(a.x * d, a.y * d, a.z * d, a.w * d);
public static Vector4F operator *(float d, in Vector4F a)
=> new(a.x * d, a.y * d, a.z * d, a.w * d);
public static Vector4F operator /(in Vector4F a, float d)
=> new(MathUtils.DivideSafe(a.x, d), MathUtils.DivideSafe(a.y, d), MathUtils.DivideSafe(a.z, d), MathUtils.DivideSafe(a.w, d));
public static bool operator ==(in Vector4F lhs, in Vector4F rhs)
=> Vector4FComparer.Default.Equals(lhs, rhs);
public static bool operator !=(in Vector4F lhs, in Vector4F rhs)
=> !Vector4FComparer.Default.Equals(lhs, rhs);
public static float Dot(in Vector4F lhs, in Vector4F rhs)
=> (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z) + (lhs.w * rhs.w);
public Vector4F Interpolate(Vector4F other, double ratio)
{
var t = (float)ratio;
var inv = 1f - t;
return new Vector4F(
(x * inv) + (other.x * t),
(y * inv) + (other.y * t),
(z * inv) + (other.z * t),
(w * inv) + (other.w * t));
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
namespace Nanomesh
{
public class Vector4FComparer : IEqualityComparer<Vector4F>
{
private static Vector4FComparer? _instance;
public static Vector4FComparer Default => _instance ??= new Vector4FComparer(0.0001f);
private readonly float _tolerance;
public Vector4FComparer(float tolerance)
{
_tolerance = tolerance;
}
public bool Equals(Vector4F x, Vector4F y)
{
return (int)(x.x / _tolerance) == (int)(y.x / _tolerance)
&& (int)(x.y / _tolerance) == (int)(y.y / _tolerance)
&& (int)(x.z / _tolerance) == (int)(y.z / _tolerance)
&& (int)(x.w / _tolerance) == (int)(y.w / _tolerance);
}
public int GetHashCode(Vector4F obj)
{
return (int)(obj.x / _tolerance)
^ ((int)(obj.y / _tolerance) << 2)
^ ((int)(obj.z / _tolerance) >> 2)
^ ((int)(obj.w / _tolerance) << 1);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
namespace Nanomesh
{
public struct VertexData : IEquatable<VertexData>
{
public int position;
public List<object> attributes; // TODO : This is not optimal regarding memory
public VertexData(int pos)
{
position = pos;
attributes = new List<object>();
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + position;
foreach (object attr in attributes)
{
hash = hash * 31 + attr.GetHashCode();
}
return hash;
}
}
public bool Equals(VertexData other)
{
if (!position.Equals(other.position))
return false;
if (attributes.Count != other.attributes.Count)
return false;
for (int i = 0; i < attributes.Count; i++)
{
if (!attributes[i].Equals(other.attributes[i]))
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
namespace Nanomesh
{
public static class CollectionUtils
{
public static T[] ToArray<T>(this HashSet<T> items, ref T[] array)
{
int i = 0;
foreach (T item in items)
{
array[i++] = item;
}
return array;
}
public static bool TryAdd<K, V>(this Dictionary<K, V> dictionary, K key, V value)
{
if (dictionary.ContainsKey(key))
{
return false;
}
dictionary.Add(key, value);
return true;
}
public static bool TryAdd<K, V>(this Dictionary<K, V> dictionary, K key, Func<K, V> valueFactory)
{
if (dictionary.ContainsKey(key))
{
return false;
}
dictionary.Add(key, valueFactory(key));
return true;
}
public static V GetOrAdd<K, V>(this Dictionary<K, V> dictionary, K key, V value)
{
if (dictionary.TryGetValue(key, out V existingValue))
{
return existingValue;
}
dictionary.Add(key, value);
return value;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,565 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Nanomesh
{
public class LinkedHashSet<T> : IReadOnlyCollection<T> where T : IComparable<T>
{
private readonly Dictionary<T, LinkedHashNode<T>> elements;
private LinkedHashNode<T> first, last;
/// <summary>
/// Initializes a new instance of the <see cref="LinkedHashSet{T}"/> class.
/// </summary>
public LinkedHashSet()
{
elements = new Dictionary<T, LinkedHashNode<T>>();
}
/// <summary>
/// Initializes a new instance of the <see cref="LinkedHashSet{T}"/> class.
/// </summary>
/// <param name="initialValues"></param>
public LinkedHashSet(IEnumerable<T> initialValues) : this()
{
UnionWith(initialValues);
}
public LinkedHashNode<T> First => first;
public LinkedHashNode<T> Last => last;
#region Implementation of IEnumerable
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>1</filterpriority>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection<T>
/// <summary>
/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
public int Count => elements.Count;
/// <summary>
/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
public void Clear()
{
elements.Clear();
first = null;
last = null;
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
/// </returns>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
public bool Contains(T item)
{
return elements.ContainsKey(item);
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <typeparamref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
public void CopyTo(T[] array, int arrayIndex)
{
int index = arrayIndex;
foreach (T item in this)
{
array[index++] = item;
}
}
/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public bool Remove(T item)
{
if (elements.TryGetValue(item, out LinkedHashNode<T> node))
{
elements.Remove(item);
Unlink(node);
return true;
}
return false;
}
#endregion
#region Implementation of ISet<T>
/// <summary>
/// Modifies the current set so that it contains all elements that are present in either the current set or the specified collection.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void UnionWith(IEnumerable<T> other)
{
foreach (T item in other)
{
Add(item);
}
}
/// <summary>
/// Modifies the current set so that it contains only elements that are also in a specified collection.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void IntersectWith(IEnumerable<T> other)
{
ISet<T> otherSet = AsSet(other);
LinkedHashNode<T> current = first;
while (current != null)
{
if (!otherSet.Contains(current.Value))
{
elements.Remove(current.Value);
Unlink(current);
}
current = current.Next;
}
}
/// <summary>
/// Removes all elements in the specified collection from the current set.
/// </summary>
/// <param name="other">The collection of items to remove from the set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void ExceptWith(IEnumerable<T> other)
{
foreach (T item in other)
{
Remove(item);
}
}
/// <summary>
/// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void SymmetricExceptWith(IEnumerable<T> other)
{
foreach (T item in other)
{
if (elements.TryGetValue(item, out LinkedHashNode<T> node))
{
elements.Remove(item);
Unlink(node);
}
else
{
Add(item);
}
}
}
/// <summary>
/// Determines whether the current set is a superset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a superset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsSupersetOf(IEnumerable<T> other)
{
int numberOfOthers = CountOthers(other, out int numberOfOthersPresent);
// All others must be present.
return numberOfOthersPresent == numberOfOthers;
}
/// <summary>
/// Determines whether the current set is a correct superset of a specified collection.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ISet`1"/> object is a correct superset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set. </param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsProperSupersetOf(IEnumerable<T> other)
{
int numberOfOthers = CountOthers(other, out int numberOfOthersPresent);
// All others must be present, plus we need to have at least one additional item.
return numberOfOthersPresent == numberOfOthers && numberOfOthers < Count;
}
/// <summary>
/// Determines whether the current set and the specified collection contain the same elements.
/// </summary>
/// <returns>
/// true if the current set is equal to <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool SetEquals(IEnumerable<T> other)
{
int numberOfOthers = CountOthers(other, out int numberOfOthersPresent);
return numberOfOthers == Count && numberOfOthersPresent == Count;
}
/// <summary>
/// Adds an element to the current set and returns a value to indicate if the element was successfully added.
/// </summary>
/// <returns>
/// true if the element is added to the set; false if the element is already in the set.
/// </returns>
/// <param name="item">The element to add to the set.</param>
public bool Add(T item)
{
if (elements.ContainsKey(item))
{
return false;
}
LinkedHashNode<T> node = new LinkedHashNode<T>(item) { Previous = last };
if (first == null)
{
first = node;
}
if (last != null)
{
last.Next = node;
}
last = node;
elements.Add(item, node);
return true;
}
public bool AddAfter(T item, LinkedHashNode<T> itemInPlace)
{
if (elements.ContainsKey(item))
{
return false;
}
LinkedHashNode<T> node = new LinkedHashNode<T>(item) { Previous = itemInPlace };
if (itemInPlace.Next != null)
{
node.Next = itemInPlace.Next;
itemInPlace.Next.Previous = node;
}
else
{
last = node;
}
itemInPlace.Next = node;
elements.Add(item, node);
return true;
}
public bool PushAfter(T item, LinkedHashNode<T> itemInPlace)
{
if (elements.ContainsKey(item))
{
return false;
}
LinkedHashNode<T> node = Last;
Unlink(node);
elements.Remove(node.Value);
node.Value = item;
node.Next = null;
node.Previous = itemInPlace;
if (itemInPlace.Next != null)
{
node.Next = itemInPlace.Next;
itemInPlace.Next.Previous = node;
}
else
{
last = node;
}
itemInPlace.Next = node;
elements.Add(item, node);
return true;
}
public bool AddBefore(T item, LinkedHashNode<T> itemInPlace)
{
if (elements.ContainsKey(item))
{
return false;
}
LinkedHashNode<T> node = new LinkedHashNode<T>(item) { Next = itemInPlace };
if (itemInPlace.Previous != null)
{
node.Previous = itemInPlace.Previous;
itemInPlace.Previous.Next = node;
}
else
{
first = node;
}
itemInPlace.Previous = node;
elements.Add(item, node);
return true;
}
public bool PushBefore(T item, LinkedHashNode<T> itemInPlace)
{
if (elements.ContainsKey(item))
{
return false;
}
LinkedHashNode<T> node = Last;
Unlink(node);
elements.Remove(node.Value);
node.Value = item;
node.Previous = null;
node.Next = itemInPlace;
if (itemInPlace.Previous != null)
{
node.Previous = itemInPlace.Previous;
itemInPlace.Previous.Next = node;
}
else
{
first = node;
}
itemInPlace.Previous = node;
elements.Add(item, node);
return true;
}
#endregion
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="Enumerator"/> struct that can be used to iterate through the collection.
/// </returns>
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
/// <summary>
/// Count the elements in the given collection and determine both the total
/// count and how many of the elements that are present in the current set.
/// </summary>
private int CountOthers(IEnumerable<T> items, out int numberOfOthersPresent)
{
numberOfOthersPresent = 0;
int numberOfOthers = 0;
foreach (T item in items)
{
numberOfOthers++;
if (Contains(item))
{
numberOfOthersPresent++;
}
}
return numberOfOthers;
}
/// <summary>
/// Cast the given collection to an ISet&lt;T&gt; if possible. If not,
/// return a new set containing the items.
/// </summary>
private static ISet<T> AsSet(IEnumerable<T> items)
{
return items as ISet<T> ?? new HashSet<T>(items);
}
/// <summary>
/// Unlink a node from the linked list by updating the node pointers in
/// its preceeding and subsequent node. Also update the _first and _last
/// pointers if necessary.
/// </summary>
private void Unlink(LinkedHashNode<T> node)
{
if (node.Previous != null)
{
node.Previous.Next = node.Next;
}
if (node.Next != null)
{
node.Next.Previous = node.Previous;
}
if (ReferenceEquals(node, first))
{
first = node.Next;
}
if (ReferenceEquals(node, last))
{
last = node.Previous;
}
}
public class LinkedHashNode<TElement>
{
public TElement Value;
public LinkedHashNode<TElement> Next;
public LinkedHashNode<TElement> Previous;
public LinkedHashNode(TElement value)
{
Value = value;
}
public override string ToString()
{
return Value.ToString();
}
}
public struct Enumerator : IEnumerator<T>
{
private LinkedHashNode<T> _node;
private T _current;
internal Enumerator(LinkedHashSet<T> set)
{
_current = default(T);
_node = set.first;
}
/// <inheritdoc />
public bool MoveNext()
{
if (_node == null)
{
return false;
}
_current = _node.Value;
_node = _node.Next;
return true;
}
/// <inheritdoc />
public T Current => _current;
/// <inheritdoc />
object IEnumerator.Current => Current;
/// <inheritdoc />
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
/// <inheritdoc />
public void Dispose()
{
}
}
public void AddMin(T item)
{
LinkedHashNode<T> current = Last;
while (current != null && item.CompareTo(current.Value) < 0)
{
current = current.Previous;
}
if (current == Last)
{
return;
}
if (current == null)
{
AddBefore(item, First);
}
else
{
AddAfter(item, current);
}
}
public void PushMin(T item)
{
LinkedHashNode<T> current = Last;
while (current != null && item.CompareTo(current.Value) < 0)
{
current = current.Previous;
}
if (current == Last)
{
return;
}
if (current == null)
{
PushBefore(item, First);
}
else
{
PushAfter(item, current);
}
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
namespace Nanomesh
{
public static class MaxHeap
{
public static T FindKthLargest<T>(T[] nums, int k) where T : IComparable<T>
{
Heap<T> heap = new Heap<T>();
heap.Heapify(nums, nums.Length);
T data = default(T);
for (int i = 0; i < k; i++)
{
data = heap.RemoveMax();
}
return data;
}
}
public class Heap<T> where T : IComparable<T>
{
private T[] arr;
private int count;
private int size;
public int GetLeftChild(int pos)
{
int l = 2 * pos + 1;
return l >= count ? -1 : l;
}
public int GetRightChild(int pos)
{
int r = 2 * pos + 2;
return r >= count ? -1 : r;
}
public void Heapify(T[] num, int n)
{
arr = new T[n];
size = n;
for (int i = 0; i < n; i++)
{
arr[i] = num[i];
}
count = n;
for (int i = (count - 1) / 2; i >= 0; i--)
{
PercolateDown(i);
}
}
public void PercolateDown(int pos)
{
int l = GetLeftChild(pos);
int r = GetRightChild(pos);
int max = pos;
if (l != -1 && arr[max].CompareTo(arr[l]) < 0)
{
max = l;
}
if (r != -1 && arr[max].CompareTo(arr[r]) < 0)
{
max = r;
}
if (max != pos)
{
T temp = arr[pos];
arr[pos] = arr[max];
arr[max] = temp;
PercolateDown(max);
}
}
public T RemoveMax()
{
T data = arr[0];
arr[0] = arr[count - 1];
count--;
PercolateDown(0);
return data;
}
}
}

View File

@@ -0,0 +1,145 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Nanomesh.Collections
{
public class MinHeap<T> : IEnumerable<T>
{
private readonly List<T> values;
private readonly IComparer<T> comparer;
public MinHeap(IEnumerable<T> items, IComparer<T> comparer)
{
values = new List<T>();
this.comparer = comparer;
values.Add(default(T));
values.AddRange(items);
for (int i = values.Count / 2; i >= 1; i--)
{
BubbleDown(i);
}
}
public MinHeap(IEnumerable<T> items) : this(items, Comparer<T>.Default) { }
public MinHeap(IComparer<T> comparer) : this(new T[0], comparer) { }
public MinHeap() : this(Comparer<T>.Default) { }
public int Count => values.Count - 1;
public T Min => values[1];
/// <summary>
/// Extract the smallest element.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public T ExtractMin()
{
int count = Count;
if (count == 0)
{
throw new InvalidOperationException("Heap is empty.");
}
T min = Min;
values[1] = values[count];
values.RemoveAt(count);
if (values.Count > 1)
{
BubbleDown(1);
}
return min;
}
/// <summary>
/// Insert the value.
/// </summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public void Add(T item)
{
values.Add(item);
BubbleUp(Count);
}
private void BubbleUp(int index)
{
int parent = index / 2;
while (index > 1 && CompareResult(parent, index) > 0)
{
Exchange(index, parent);
index = parent;
parent /= 2;
}
}
private void BubbleDown(int index)
{
int min;
while (true)
{
int left = index * 2;
int right = index * 2 + 1;
if (left < values.Count &&
CompareResult(left, index) < 0)
{
min = left;
}
else
{
min = index;
}
if (right < values.Count &&
CompareResult(right, min) < 0)
{
min = right;
}
if (min != index)
{
Exchange(index, min);
index = min;
}
else
{
return;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CompareResult(int index1, int index2)
{
return comparer.Compare(values[index1], values[index2]);
}
private void Exchange(int index, int max)
{
T tmp = values[index];
values[index] = values[max];
values[max] = tmp;
}
public IEnumerator<T> GetEnumerator()
{
return values.Skip(1).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
namespace Nanomesh
{
public static class OrderStatistics
{
private static T FindMedian<T>(T[] arr, int i, int n)
{
if (i <= n)
{
Array.Sort(arr, i, n); // Sort the array
}
else
{
Array.Sort(arr, n, i);
}
return arr[n / 2]; // Return middle element
}
// Returns k'th smallest element
// in arr[l..r] in worst case
// linear time. ASSUMPTION: ALL
// ELEMENTS IN ARR[] ARE DISTINCT
public static T FindKthSmallest<T>(T[] arr, int l, int r, int k) where T : IComparable<T>
{
// If k is smaller than
// number of elements in array
if (k > 0 && k <= r - l + 1)
{
int n = r - l + 1; // Number of elements in arr[l..r]
// Divide arr[] in groups of size 5,
// calculate median of every group
// and store it in median[] array.
int i;
// There will be floor((n+4)/5) groups;
T[] median = new T[(n + 4) / 5];
for (i = 0; i < n / 5; i++)
{
median[i] = FindMedian(arr, l + i * 5, 5);
}
// For last group with less than 5 elements
if (i * 5 < n)
{
median[i] = FindMedian(arr, l + i * 5, n % 5);
i++;
}
// Find median of all medians using recursive call.
// If median[] has only one element, then no need
// of recursive call
T medOfMed = (i == 1) ? median[i - 1] : FindKthSmallest(median, 0, i - 1, i / 2);
// Partition the array around a random element and
// get position of pivot element in sorted array
int pos = Partition(arr, l, r, medOfMed);
// If position is same as k
if (pos - l == k - 1)
{
return arr[pos];
}
if (pos - l > k - 1) // If position is more, recur for left
{
return FindKthSmallest(arr, l, pos - 1, k);
}
// Else recur for right subarray
return FindKthSmallest(arr, pos + 1, r, k - pos + l - 1);
}
// If k is more than number of elements in array
return default(T);
}
private static void Swap<T>(ref T[] arr, int i, int j)
{
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// It searches for x in arr[l..r], and
// partitions the array around x.
private static int Partition<T>(T[] arr, int l, int r, T x) where T : IComparable<T>
{
// Search for x in arr[l..r] and move it to end
int i;
for (i = l; i < r; i++)
{
if (arr[i].CompareTo(x) == 0)
{
break;
}
}
Swap(ref arr, i, r);
// Standard partition algorithm
i = l;
for (int j = l; j <= r - 1; j++)
{
if (arr[j].CompareTo(x) <= 0)
{
Swap(ref arr, i, j);
i++;
}
}
Swap(ref arr, i, r);
return i;
}
}
}

View File

@@ -0,0 +1,30 @@
namespace Nanomesh
{
public struct AttributeDefinition
{
public double weight;
public AttributeType type;
public int id;
public AttributeDefinition(AttributeType type)
{
this.weight = 1;
this.type = type;
this.id = 0;
}
public AttributeDefinition(AttributeType type, double weight)
{
this.weight = weight;
this.type = type;
this.id = 0;
}
public AttributeDefinition(AttributeType type, double weight, int id)
{
this.weight = weight;
this.type = type;
this.id = id;
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
namespace Nanomesh
{
public enum AttributeType
{
Normals,
UVs,
BoneWeights,
Colors,
}
public static class AttributeUtils
{
public static MetaAttributeList CreateAttributesFromDefinitions(IList<AttributeDefinition> attributeDefinitions)
{
MetaAttributeList attributeList = new EmptyMetaAttributeList(0);
for (int i = 0; i < attributeDefinitions.Count; i++)
{
switch (attributeDefinitions[i].type)
{
case AttributeType.Normals:
attributeList = attributeList.AddAttributeType<Vector3F>();
break;
case AttributeType.UVs:
attributeList = attributeList.AddAttributeType<Vector2F>();
break;
case AttributeType.BoneWeights:
attributeList = attributeList.AddAttributeType<BoneWeight>();
break;
default:
throw new NotImplementedException();
}
}
return attributeList;
}
}
}

View File

@@ -0,0 +1,406 @@
using System;
namespace Nanomesh
{
public unsafe interface IMetaAttribute
{
IMetaAttribute Set<K>(int index, K value) where K : unmanaged;
K Get<K>(int index) where K : unmanaged;
}
public unsafe struct MetaAttribute<T0> : IMetaAttribute
where T0 : unmanaged
{
public T0 attr0;
public MetaAttribute(T0 attr0)
{
this.attr0 = attr0;
}
public unsafe K Get<K>(int index) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
return kk[0];
}
default:
throw new ArgumentOutOfRangeException();
}
}
public IMetaAttribute Set<K>(int index, K value) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
default:
throw new ArgumentOutOfRangeException();
}
}
public override int GetHashCode()
{
return attr0.GetHashCode();
}
public override bool Equals(object obj)
{
return ((MetaAttribute<T0>)obj).attr0.Equals(attr0);
}
}
public unsafe struct MetaAttribute<T0, T1> : IMetaAttribute
where T0 : unmanaged
where T1 : unmanaged
{
public T0 attr0;
public T1 attr1;
public MetaAttribute(T0 attr0, T1 attr1)
{
this.attr0 = attr0;
this.attr1 = attr1;
}
public unsafe K Get<K>(int index) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
return kk[0];
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
return kk[0];
}
default:
throw new ArgumentOutOfRangeException();
}
}
public IMetaAttribute Set<K>(int index, K value) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
public unsafe struct MetaAttribute<T0, T1, T2> : IMetaAttribute
where T0 : unmanaged
where T1 : unmanaged
where T2 : unmanaged
{
public T0 attr0;
public T1 attr1;
public T2 attr2;
public MetaAttribute(T0 attr0, T1 attr1, T2 attr2)
{
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
}
public unsafe K Get<K>(int index) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
return kk[0];
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
return kk[0];
}
case 2:
fixed (T2* k = &attr2)
{
K* kk = (K*)k;
return kk[0];
}
default:
throw new ArgumentOutOfRangeException();
}
}
public IMetaAttribute Set<K>(int index, K value) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 2:
fixed (T2* k = &attr2)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
public unsafe struct MetaAttribute<T0, T1, T2, T3> : IMetaAttribute
where T0 : unmanaged
where T1 : unmanaged
where T2 : unmanaged
where T3 : unmanaged
{
public T0 attr0;
public T1 attr1;
public T2 attr2;
public T3 attr3;
public MetaAttribute(T0 attr0, T1 attr1, T2 attr2, T3 attr3)
{
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
public unsafe K Get<K>(int index) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
return kk[0];
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
return kk[0];
}
case 2:
fixed (T2* k = &attr2)
{
K* kk = (K*)k;
return kk[0];
}
case 3:
fixed (T3* k = &attr3)
{
K* kk = (K*)k;
return kk[0];
}
default:
throw new ArgumentOutOfRangeException();
}
// Shorter idea but only C# 8.0:
//fixed (void* v = &this)
//{
// byte* b = (byte*)v;
// b += Positions[index];
// return ((K*)b)[0];
//};
}
public IMetaAttribute Set<K>(int index, K value) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 2:
fixed (T2* k = &attr2)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 3:
fixed (T3* k = &attr3)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
public unsafe struct MetaAttribute<T0, T1, T2, T3, T4> : IMetaAttribute
where T0 : unmanaged
where T1 : unmanaged
where T2 : unmanaged
where T3 : unmanaged
where T4 : unmanaged
{
public T0 attr0;
public T1 attr1;
public T2 attr2;
public T3 attr3;
public T4 attr4;
public MetaAttribute(T0 attr0, T1 attr1, T2 attr2, T3 attr3, T4 attr4)
{
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
this.attr4 = attr4;
}
public unsafe K Get<K>(int index) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
return kk[0];
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
return kk[0];
}
case 2:
fixed (T2* k = &attr2)
{
K* kk = (K*)k;
return kk[0];
}
case 3:
fixed (T3* k = &attr3)
{
K* kk = (K*)k;
return kk[0];
}
case 4:
fixed (T4* k = &attr4)
{
K* kk = (K*)k;
return kk[0];
}
default:
throw new ArgumentOutOfRangeException();
}
// Shorter idea but only C# 8.0:
//fixed (void* v = &this)
//{
// byte* b = (byte*)v;
// b += Positions[index];
// return ((K*)b)[0];
//};
}
public IMetaAttribute Set<K>(int index, K value) where K : unmanaged
{
switch (index)
{
case 0:
fixed (T0* k = &attr0)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 1:
fixed (T1* k = &attr1)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 2:
fixed (T2* k = &attr2)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 3:
fixed (T3* k = &attr3)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
case 4:
fixed (T4* k = &attr4)
{
K* kk = (K*)k;
kk[0] = value;
return this;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -0,0 +1,448 @@
using System;
namespace Nanomesh
{
public abstract class MetaAttributeList
{
public abstract IMetaAttribute this[int index]
{
get;
set;
}
public abstract int Count { get; }
public abstract int CountPerAttribute { get; }
public abstract MetaAttributeList CreateNew(int length);
public abstract MetaAttributeList AddAttributeType<T>()
where T : unmanaged, IInterpolable<T>;
public abstract bool Equals(int indexA, int indexB, int attribute);
public abstract void Interpolate(int attribute, int indexA, int indexB, double ratio);
}
public class EmptyMetaAttributeList : MetaAttributeList
{
private readonly int _length;
public EmptyMetaAttributeList(int length)
{
_length = length;
}
public override IMetaAttribute this[int index]
{
get => throw new System.Exception();
set => throw new System.Exception();
}
public override MetaAttributeList CreateNew(int length) => new EmptyMetaAttributeList(length);
public override unsafe bool Equals(int indexA, int indexB, int attribute)
{
return false;
}
public override void Interpolate(int attribute, int indexA, int indexB, double ratio)
{
throw new System.Exception();
}
public override MetaAttributeList AddAttributeType<T>()
{
return new MetaAttributeList<T>(_length);
}
public override int Count => 0;
public override int CountPerAttribute => 0;
}
public class MetaAttributeList<T0> : MetaAttributeList
where T0 : unmanaged, IInterpolable<T0>
{
private readonly MetaAttribute<T0>[] _attributes;
public MetaAttributeList(int length)
{
_attributes = new MetaAttribute<T0>[length];
}
public override IMetaAttribute this[int index]
{
get => _attributes[index];
set => _attributes[index] = (MetaAttribute<T0>)value;
}
public void Set(MetaAttribute<T0> value, int index)
{
_attributes[index] = value;
}
private void Get(MetaAttribute<T0> value, int index)
{
_attributes[index] = value;
}
public override MetaAttributeList CreateNew(int length) => new MetaAttributeList<T0>(length);
public override unsafe bool Equals(int indexA, int indexB, int attribute)
{
switch (attribute)
{
case 0:
return _attributes[indexA].Get<T0>(0).Equals(_attributes[indexB].Get<T0>(0));
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Interpolate(int attribute, int indexA, int indexB, double ratio)
{
_attributes[indexA].attr0 = _attributes[indexA].Get<T0>(0).Interpolate(_attributes[indexB].Get<T0>(0), ratio);
_attributes[indexB].attr0 = _attributes[indexA].attr0;
}
public override MetaAttributeList AddAttributeType<T>()
{
MetaAttributeList<T0, T> newAttributes = new MetaAttributeList<T0, T>(_attributes.Length);
for (int i = 0; i < Count; i++)
newAttributes.Set(new MetaAttribute<T0, T>(_attributes[i].attr0, default(T)), i);
return newAttributes;
}
public override int Count => _attributes.Length;
public override int CountPerAttribute => 1;
}
public class MetaAttributeList<T0, T1> : MetaAttributeList
where T0 : unmanaged, IInterpolable<T0>
where T1 : unmanaged, IInterpolable<T1>
{
private readonly MetaAttribute<T0, T1>[] _attributes;
public MetaAttributeList(int length)
{
_attributes = new MetaAttribute<T0, T1>[length];
}
public override IMetaAttribute this[int index]
{
get => _attributes[index];
set => _attributes[index] = (MetaAttribute<T0, T1>)value;
}
public void Set(MetaAttribute<T0, T1> value, int index)
{
_attributes[index] = value;
}
private void Get(MetaAttribute<T0, T1> value, int index)
{
_attributes[index] = value;
}
public override MetaAttributeList CreateNew(int length) => new MetaAttributeList<T0, T1>(length);
public override unsafe bool Equals(int indexA, int indexB, int attribute)
{
switch (attribute)
{
case 0:
return _attributes[indexA].Get<T0>(0).Equals(_attributes[indexB].Get<T0>(0));
case 1:
return _attributes[indexA].Get<T1>(1).Equals(_attributes[indexB].Get<T1>(1));
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Interpolate(int attribute, int indexA, int indexB, double ratio)
{
switch (attribute)
{
case 0:
_attributes[indexA].attr0 = _attributes[indexA].Get<T0>(0).Interpolate(_attributes[indexB].Get<T0>(0), ratio);
_attributes[indexB].attr0 = _attributes[indexA].attr0;
break;
case 1:
_attributes[indexA].attr1 = _attributes[indexA].Get<T1>(1).Interpolate(_attributes[indexB].Get<T1>(1), ratio);
_attributes[indexB].attr1 = _attributes[indexA].attr1;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override MetaAttributeList AddAttributeType<T>()
{
MetaAttributeList<T0, T1, T> newAttributes = new MetaAttributeList<T0, T1, T>(_attributes.Length);
for (int i = 0; i < Count; i++)
newAttributes.Set(new MetaAttribute<T0, T1, T>(_attributes[i].attr0, _attributes[i].attr1, default(T)), i);
return newAttributes;
}
public override int Count => _attributes.Length;
public override int CountPerAttribute => 2;
}
public class MetaAttributeList<T0, T1, T2> : MetaAttributeList
where T0 : unmanaged, IInterpolable<T0>
where T1 : unmanaged, IInterpolable<T1>
where T2 : unmanaged, IInterpolable<T2>
{
private readonly MetaAttribute<T0, T1, T2>[] _attributes;
public MetaAttributeList(int length)
{
_attributes = new MetaAttribute<T0, T1, T2>[length];
}
public override IMetaAttribute this[int index]
{
get => _attributes[index];
set => _attributes[index] = (MetaAttribute<T0, T1, T2>)value;
}
public void Set(MetaAttribute<T0, T1, T2> value, int index)
{
_attributes[index] = value;
}
private void Get(MetaAttribute<T0, T1, T2> value, int index)
{
_attributes[index] = value;
}
public override MetaAttributeList CreateNew(int length) => new MetaAttributeList<T0, T1, T2>(length);
public override unsafe bool Equals(int indexA, int indexB, int attribute)
{
switch (attribute)
{
case 0:
return _attributes[indexA].Get<T0>(0).Equals(_attributes[indexB].Get<T0>(0));
case 1:
return _attributes[indexA].Get<T1>(1).Equals(_attributes[indexB].Get<T1>(1));
case 2:
return _attributes[indexA].Get<T2>(2).Equals(_attributes[indexB].Get<T2>(2));
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Interpolate(int attribute, int indexA, int indexB, double ratio)
{
switch (attribute)
{
case 0:
_attributes[indexA].attr0 = _attributes[indexA].Get<T0>(0).Interpolate(_attributes[indexB].Get<T0>(0), ratio);
_attributes[indexB].attr0 = _attributes[indexA].attr0;
break;
case 1:
_attributes[indexA].attr1 = _attributes[indexA].Get<T1>(1).Interpolate(_attributes[indexB].Get<T1>(1), ratio);
_attributes[indexB].attr1 = _attributes[indexA].attr1;
break;
case 2:
_attributes[indexA].attr2 = _attributes[indexA].Get<T2>(2).Interpolate(_attributes[indexB].Get<T2>(2), ratio);
_attributes[indexB].attr2 = _attributes[indexA].attr2;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override MetaAttributeList AddAttributeType<T>()
{
MetaAttributeList<T0, T1, T2, T> newAttributes = new MetaAttributeList<T0, T1, T2, T>(_attributes.Length);
for (int i = 0; i < Count; i++)
newAttributes.Set(new MetaAttribute<T0, T1, T2, T>(_attributes[i].attr0, _attributes[i].attr1, _attributes[i].attr2, default(T)), i);
return newAttributes;
}
public override int Count => _attributes.Length;
public override int CountPerAttribute => 3;
}
public class MetaAttributeList<T0, T1, T2, T3> : MetaAttributeList
where T0 : unmanaged, IInterpolable<T0>
where T1 : unmanaged, IInterpolable<T1>
where T2 : unmanaged, IInterpolable<T2>
where T3 : unmanaged, IInterpolable<T3>
{
private readonly MetaAttribute<T0, T1, T2, T3>[] _attributes;
public MetaAttributeList(int length)
{
_attributes = new MetaAttribute<T0, T1, T2, T3>[length];
}
public override IMetaAttribute this[int index]
{
get => _attributes[index];
set => _attributes[index] = (MetaAttribute<T0, T1, T2, T3>)value;
}
public void Set(MetaAttribute<T0, T1, T2, T3> value, int index)
{
_attributes[index] = value;
}
private void Get(MetaAttribute<T0, T1, T2, T3> value, int index)
{
_attributes[index] = value;
}
public override MetaAttributeList CreateNew(int length) => new MetaAttributeList<T0, T1, T2, T3>(length);
public override unsafe bool Equals(int indexA, int indexB, int attribute)
{
switch (attribute)
{
case 0:
return _attributes[indexA].Get<T0>(0).Equals(_attributes[indexB].Get<T0>(0));
case 1:
return _attributes[indexA].Get<T1>(1).Equals(_attributes[indexB].Get<T1>(1));
case 2:
return _attributes[indexA].Get<T2>(2).Equals(_attributes[indexB].Get<T2>(2));
case 3:
return _attributes[indexA].Get<T3>(3).Equals(_attributes[indexB].Get<T3>(3));
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Interpolate(int attribute, int indexA, int indexB, double ratio)
{
switch (attribute)
{
case 0:
_attributes[indexA].attr0 = _attributes[indexA].Get<T0>(0).Interpolate(_attributes[indexB].Get<T0>(0), ratio);
_attributes[indexB].attr0 = _attributes[indexA].attr0;
break;
case 1:
_attributes[indexA].attr1 = _attributes[indexA].Get<T1>(1).Interpolate(_attributes[indexB].Get<T1>(1), ratio);
_attributes[indexB].attr1 = _attributes[indexA].attr1;
break;
case 2:
_attributes[indexA].attr2 = _attributes[indexA].Get<T2>(2).Interpolate(_attributes[indexB].Get<T2>(2), ratio);
_attributes[indexB].attr2 = _attributes[indexA].attr2;
break;
case 3:
_attributes[indexA].attr3 = _attributes[indexA].Get<T3>(3).Interpolate(_attributes[indexB].Get<T3>(3), ratio);
_attributes[indexB].attr3 = _attributes[indexA].attr3;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override MetaAttributeList AddAttributeType<T>()
{
MetaAttributeList<T0, T1, T2, T3, T> newAttributes = new MetaAttributeList<T0, T1, T2, T3, T>(_attributes.Length);
for (int i = 0; i < Count; i++)
newAttributes.Set(new MetaAttribute<T0, T1, T2, T3, T>(_attributes[i].attr0, _attributes[i].attr1, _attributes[i].attr2, _attributes[i].attr3, default(T)), i);
return newAttributes;
}
public override int Count => _attributes.Length;
public override int CountPerAttribute => 4;
}
public class MetaAttributeList<T0, T1, T2, T3, T4> : MetaAttributeList
where T0 : unmanaged, IInterpolable<T0>
where T1 : unmanaged, IInterpolable<T1>
where T2 : unmanaged, IInterpolable<T2>
where T3 : unmanaged, IInterpolable<T3>
where T4 : unmanaged, IInterpolable<T4>
{
private readonly MetaAttribute<T0, T1, T2, T3, T4>[] _attributes;
public MetaAttributeList(int length)
{
_attributes = new MetaAttribute<T0, T1, T2, T3, T4>[length];
}
public override IMetaAttribute this[int index]
{
get => _attributes[index];
set => _attributes[index] = (MetaAttribute<T0, T1, T2, T3, T4>)value;
}
public void Set(MetaAttribute<T0, T1, T2, T3, T4> value, int index)
{
_attributes[index] = value;
}
private void Get(MetaAttribute<T0, T1, T2, T3, T4> value, int index)
{
_attributes[index] = value;
}
public override MetaAttributeList CreateNew(int length) => new MetaAttributeList<T0, T1, T2, T3, T4>(length);
public override unsafe bool Equals(int indexA, int indexB, int attribute)
{
switch (attribute)
{
case 0:
return _attributes[indexA].Get<T0>(0).Equals(_attributes[indexB].Get<T0>(0));
case 1:
return _attributes[indexA].Get<T1>(1).Equals(_attributes[indexB].Get<T1>(1));
case 2:
return _attributes[indexA].Get<T2>(2).Equals(_attributes[indexB].Get<T2>(2));
case 3:
return _attributes[indexA].Get<T3>(3).Equals(_attributes[indexB].Get<T3>(3));
case 4:
return _attributes[indexA].Get<T3>(3).Equals(_attributes[indexB].Get<T3>(4));
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Interpolate(int attribute, int indexA, int indexB, double ratio)
{
switch (attribute)
{
case 0:
_attributes[indexA].attr0 = _attributes[indexA].Get<T0>(0).Interpolate(_attributes[indexB].Get<T0>(0), ratio);
_attributes[indexB].attr0 = _attributes[indexA].attr0;
break;
case 1:
_attributes[indexA].attr1 = _attributes[indexA].Get<T1>(1).Interpolate(_attributes[indexB].Get<T1>(1), ratio);
_attributes[indexB].attr1 = _attributes[indexA].attr1;
break;
case 2:
_attributes[indexA].attr2 = _attributes[indexA].Get<T2>(2).Interpolate(_attributes[indexB].Get<T2>(2), ratio);
_attributes[indexB].attr2 = _attributes[indexA].attr2;
break;
case 3:
_attributes[indexA].attr3 = _attributes[indexA].Get<T3>(3).Interpolate(_attributes[indexB].Get<T3>(3), ratio);
_attributes[indexB].attr3 = _attributes[indexA].attr3;
break;
case 4:
_attributes[indexA].attr4 = _attributes[indexA].Get<T4>(4).Interpolate(_attributes[indexB].Get<T4>(4), ratio);
_attributes[indexB].attr4 = _attributes[indexA].attr4;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public override MetaAttributeList AddAttributeType<T>()
{
throw new NotImplementedException();
}
public override int Count => _attributes.Length;
public override int CountPerAttribute => 5;
}
}

View File

@@ -0,0 +1,706 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Nanomesh
{
// Let's say F = 2V
// Halfedge mesh is V * sizeof(vertex) + 3F * sizeof(Halfedge) + F * sizeof(Face) = 16 * 0.5F + 3F * 20 + 4F = 72F
// Connected mesh is V * sizeof(Vector3) + 3F * sizeof(Node) + F * sizeof(Face) = 12 * 0.5F + 3F * 12 + 12F = 54F (without attributes)
// Connected mesh no face is V * sizeof(Vector3) + 3F * sizeof(Node) = 12 * 0.5F + 3F * 12 = 42F (without attributes)
public partial class ConnectedMesh
{
// Todo : make this private (can only be modified from the inside)
public Vector3[] positions;
public MetaAttributeList attributes;
public Node[] nodes;
public Group[] groups;
public AttributeDefinition[] attributeDefinitions;
public int[] PositionToNode => _positionToNode ?? (_positionToNode = GetPositionToNode());
private int[] _positionToNode;
internal int _faceCount;
public int FaceCount => _faceCount;
public bool AreNodesSiblings(int nodeIndexA, int nodeIndexB)
{
return nodes[nodeIndexA].position == nodes[nodeIndexB].position;
}
public int[] GetPositionToNode()
{
int[] positionToNode = new int[positions.Length];
for (int i = 0; i < positions.Length; i++)
{
positionToNode[i] = -1;
}
for (int i = 0; i < nodes.Length; i++)
{
if (!nodes[i].IsRemoved)
{
positionToNode[nodes[i].position] = i;
}
}
return positionToNode;
}
public int GetEdgeCount(int nodeIndex)
{
return GetRelativesCount(nodeIndex) + 1;
}
public int GetRelativesCount(int nodeIndex)
{
int k = 0;
int relative = nodeIndex;
while ((relative = nodes[relative].relative) != nodeIndex)
{
k++;
}
return k;
}
public int GetSiblingsCount(int nodeIndex)
{
int k = 0;
int sibling = nodeIndex;
while ((sibling = nodes[sibling].sibling) != nodeIndex)
{
k++;
}
return k;
}
public int ReconnectSiblings(int nodeIndex)
{
int sibling = nodeIndex;
int lastValid = -1;
int firstValid = -1;
int position = -1;
do
{
if (nodes[sibling].IsRemoved)
{
continue;
}
if (firstValid == -1)
{
firstValid = sibling;
position = nodes[sibling].position;
}
if (lastValid != -1)
{
nodes[lastValid].sibling = sibling;
nodes[lastValid].position = position;
}
lastValid = sibling;
}
while ((sibling = nodes[sibling].sibling) != nodeIndex);
if (lastValid == -1)
{
return -1; // All siblings were removed
}
// Close the loop
nodes[lastValid].sibling = firstValid;
nodes[lastValid].position = position;
return firstValid;
}
public int ReconnectSiblings(int nodeIndexA, int nodeIndexB, int position)
{
int sibling = nodeIndexA;
int lastValid = -1;
int firstValid = -1;
do
{
if (nodes[sibling].IsRemoved)
{
continue;
}
if (firstValid == -1)
{
firstValid = sibling;
//position = nodes[sibling].position;
}
if (lastValid != -1)
{
nodes[lastValid].sibling = sibling;
nodes[lastValid].position = position;
}
lastValid = sibling;
}
while ((sibling = nodes[sibling].sibling) != nodeIndexA);
sibling = nodeIndexB;
do
{
if (nodes[sibling].IsRemoved)
{
continue;
}
if (firstValid == -1)
{
firstValid = sibling;
//position = nodes[sibling].position;
}
if (lastValid != -1)
{
nodes[lastValid].sibling = sibling;
nodes[lastValid].position = position;
}
lastValid = sibling;
}
while ((sibling = nodes[sibling].sibling) != nodeIndexB);
if (lastValid == -1)
{
return -1; // All siblings were removed
}
// Close the loop
nodes[lastValid].sibling = firstValid;
nodes[lastValid].position = position;
return firstValid;
}
public int CollapseEdge(int nodeIndexA, int nodeIndexB)
{
int posA = nodes[nodeIndexA].position;
int posB = nodes[nodeIndexB].position;
Debug.Assert(posA != posB, "A and B must have different positions");
Debug.Assert(!nodes[nodeIndexA].IsRemoved);
Debug.Assert(!nodes[nodeIndexB].IsRemoved);
Debug.Assert(CheckRelatives(nodeIndexA), "A's relatives must be valid");
Debug.Assert(CheckRelatives(nodeIndexB), "B's relatives must be valid");
Debug.Assert(CheckSiblings(nodeIndexA), "A's siblings must be valid");
Debug.Assert(CheckSiblings(nodeIndexB), "B's siblings must be valid");
int siblingOfA = nodeIndexA;
do // Iterates over faces around A
{
bool isFaceTouched = false;
int faceEdgeCount = 0;
int nodeIndexC = -1;
int relativeOfA = siblingOfA;
do // Circulate in face
{
int posC = nodes[relativeOfA].position;
if (posC == posB)
{
isFaceTouched = true;
}
else if (posC != posA)
{
nodeIndexC = relativeOfA;
}
faceEdgeCount++;
} while ((relativeOfA = nodes[relativeOfA].relative) != siblingOfA);
if (faceEdgeCount != 3)
throw new NotImplementedException();
if (isFaceTouched && faceEdgeCount == 3)
{
// Remove face : Mark nodes as removed an reconnect siblings around C
int posC = nodes[nodeIndexC].position;
relativeOfA = siblingOfA;
do
{
nodes[relativeOfA].MarkRemoved();
} while ((relativeOfA = nodes[relativeOfA].relative) != siblingOfA);
int validNodeAtC = ReconnectSiblings(nodeIndexC);
if (_positionToNode != null)
{
_positionToNode[posC] = validNodeAtC;
}
_faceCount--;
}
} while ((siblingOfA = nodes[siblingOfA].sibling) != nodeIndexA);
int validNodeAtA = ReconnectSiblings(nodeIndexA, nodeIndexB, posA);
if (_positionToNode != null)
{
_positionToNode[posA] = validNodeAtA;
_positionToNode[posB] = -1;
}
return validNodeAtA;
}
public double GetEdgeTopo(int nodeIndexA, int nodeIndexB)
{
if ((uint)nodeIndexA >= (uint)nodes.Length || (uint)nodeIndexB >= (uint)nodes.Length)
{
return EdgeBorderPenalty;
}
if (nodes[nodeIndexA].IsRemoved || nodes[nodeIndexB].IsRemoved)
{
return EdgeBorderPenalty;
}
int posB = nodes[nodeIndexB].position;
int facesAttached = 0;
int attrAtA = -1;
int attrAtB = -1;
double edgeWeight = 0;
int siblingOfA = nodeIndexA;
do
{
int relativeOfA = siblingOfA;
while ((relativeOfA = nodes[relativeOfA].relative) != siblingOfA)
{
int posC = nodes[relativeOfA].position;
if (posC == posB)
{
facesAttached++;
if (attributes != null)
{
for (int i = 0; i < attributes.CountPerAttribute; i++)
{
if (attrAtB != -1 && !attributes.Equals(attrAtB, nodes[relativeOfA].attribute, i))
{
edgeWeight += attributeDefinitions[i].weight;
}
if (attrAtA != -1 && !attributes.Equals(attrAtA, nodes[siblingOfA].attribute, i))
{
edgeWeight += attributeDefinitions[i].weight;
}
}
}
attrAtB = nodes[relativeOfA].attribute;
attrAtA = nodes[siblingOfA].attribute;
}
}
} while ((siblingOfA = nodes[siblingOfA].sibling) != nodeIndexA);
if (facesAttached != 2) // Border or non-manifold edge
{
edgeWeight += EdgeBorderPenalty;
}
return edgeWeight;
}
internal static double EdgeBorderPenalty = 1027.007;
// TODO : Make it work with any polygon (other than triangle)
public Vector3 GetFaceNormal(int nodeIndex)
{
int posA = nodes[nodeIndex].position;
int posB = nodes[nodes[nodeIndex].relative].position;
int posC = nodes[nodes[nodes[nodeIndex].relative].relative].position;
Vector3 normal = Vector3.Cross(
positions[posB] - positions[posA],
positions[posC] - positions[posA]);
return normal.Normalized;
}
// TODO : Make it work with any polygon (other than triangle)
public double GetFaceArea(int nodeIndex)
{
int posA = nodes[nodeIndex].position;
int posB = nodes[nodes[nodeIndex].relative].position;
int posC = nodes[nodes[nodes[nodeIndex].relative].relative].position;
Vector3 normal = Vector3.Cross(
positions[posB] - positions[posA],
positions[posC] - positions[posA]);
return 0.5 * normal.Length;
}
// Only works with triangles !
public double GetAngleRadians(int nodeIndex)
{
int posA = nodes[nodeIndex].position;
int posB = nodes[nodes[nodeIndex].relative].position;
int posC = nodes[nodes[nodes[nodeIndex].relative].relative].position;
return Vector3.AngleRadians(
positions[posB] - positions[posA],
positions[posC] - positions[posA]);
}
public void Compact()
{
// Rebuild nodes array with only valid nodes
{
int validNodesCount = 0;
for (int i = 0; i < nodes.Length; i++)
if (!nodes[i].IsRemoved)
validNodesCount++;
Node[] newNodes = new Node[validNodesCount];
int k = 0;
Dictionary<int, int> oldToNewNodeIndex = new Dictionary<int, int>();
for (int i = 0; i < nodes.Length; i++)
{
if (!nodes[i].IsRemoved)
{
newNodes[k] = nodes[i];
oldToNewNodeIndex.Add(i, k);
k++;
}
}
for (int i = 0; i < newNodes.Length; i++)
{
newNodes[i].relative = oldToNewNodeIndex[newNodes[i].relative];
newNodes[i].sibling = oldToNewNodeIndex[newNodes[i].sibling];
}
nodes = newNodes;
}
// Remap positions
{
Dictionary<int, int> oldToNewPosIndex = new Dictionary<int, int>();
for (int i = 0; i < nodes.Length; i++)
{
if (!oldToNewPosIndex.ContainsKey(nodes[i].position))
oldToNewPosIndex.Add(nodes[i].position, oldToNewPosIndex.Count);
nodes[i].position = oldToNewPosIndex[nodes[i].position];
}
Vector3[] newPositions = new Vector3[oldToNewPosIndex.Count];
foreach (KeyValuePair<int, int> oldToNewPos in oldToNewPosIndex)
{
newPositions[oldToNewPos.Value] = positions[oldToNewPos.Key];
}
positions = newPositions;
}
// Remap attributes
if (attributes != null)
{
Dictionary<int, int> oldToNewAttrIndex = new Dictionary<int, int>();
for (int i = 0; i < nodes.Length; i++)
{
if (!oldToNewAttrIndex.ContainsKey(nodes[i].attribute))
oldToNewAttrIndex.Add(nodes[i].attribute, oldToNewAttrIndex.Count);
nodes[i].attribute = oldToNewAttrIndex[nodes[i].attribute];
}
MetaAttributeList newAttributes = attributes.CreateNew(oldToNewAttrIndex.Count);
foreach (KeyValuePair<int, int> oldToNewAttr in oldToNewAttrIndex)
{
newAttributes[oldToNewAttr.Value] = attributes[oldToNewAttr.Key];
}
attributes = newAttributes;
}
_positionToNode = null; // Invalid now
}
public void MergePositions(double tolerance = 0.01)
{
Dictionary<Vector3, int> newPositions = new Dictionary<Vector3, int>(tolerance <= 0 ? null : new Vector3Comparer(tolerance));
for (int i = 0; i < positions.Length; i++)
{
newPositions.TryAdd(positions[i], newPositions.Count);
}
for (int i = 0; i < nodes.Length; i++)
{
nodes[i].position = newPositions[positions[nodes[i].position]];
}
positions = new Vector3[newPositions.Count];
foreach (KeyValuePair<Vector3, int> pair in newPositions)
{
positions[pair.Value] = pair.Key;
}
newPositions = null;
// Remapping siblings
Dictionary<int, int> posToLastSibling = new Dictionary<int, int>();
for (int i = 0; i < nodes.Length; i++)
{
if (posToLastSibling.ContainsKey(nodes[i].position))
{
nodes[i].sibling = posToLastSibling[nodes[i].position];
posToLastSibling[nodes[i].position] = i;
}
else
{
nodes[i].sibling = -1;
posToLastSibling.Add(nodes[i].position, i);
}
}
for (int i = 0; i < nodes.Length; i++)
{
if (nodes[i].sibling < 0)
{
// Assign last sibling to close sibling loop
nodes[i].sibling = posToLastSibling[nodes[i].position];
}
}
_positionToNode = null;
// Dereference faces that no longer exist
for (int i = 0; i < nodes.Length; i++)
{
if (nodes[i].IsRemoved)
{
continue;
}
int lastPos = nodes[i].position;
int relative = i;
while ((relative = nodes[relative].relative) != i) // Circulate around face
{
int currPos = nodes[relative].position;
if (lastPos == currPos)
{
RemoveFace(relative);
break;
}
lastPos = currPos;
}
}
}
public void MergeAttributes()
{
Dictionary<IMetaAttribute, int> _uniqueAttributes = new Dictionary<IMetaAttribute, int>();
for (int i = 0; i < nodes.Length; i++)
{
_uniqueAttributes.TryAdd(attributes[nodes[i].attribute], nodes[i].attribute);
}
for (int i = 0; i < nodes.Length; i++)
{
nodes[i].attribute = _uniqueAttributes[attributes[nodes[i].attribute]];
}
}
public void RemoveFace(int nodeIndex)
{
int relative = nodeIndex;
do
{
nodes[relative].MarkRemoved();
ReconnectSiblings(relative);
} while ((relative = nodes[relative].relative) != nodeIndex);
_faceCount--;
}
public void Scale(double factor)
{
for (int i = 0; i < positions.Length; i++)
{
positions[i] = positions[i] * factor;
}
}
public HashSet<Edge> GetAllEdges()
{
HashSet<Edge> edges = new HashSet<Edge>();
for (int p = 0; p < PositionToNode.Length; p++)
{
int nodeIndex = PositionToNode[p];
if (nodeIndex < 0)
{
continue;
}
int sibling = nodeIndex;
do
{
int firstRelative = nodes[sibling].relative;
int secondRelative = nodes[firstRelative].relative;
Edge pair = new Edge(nodes[firstRelative].position, nodes[secondRelative].position);
edges.Add(pair);
} while ((sibling = nodes[sibling].sibling) != nodeIndex);
}
return edges;
}
public SharedMesh ToSharedMesh()
{
// Compating here is an issue if mesh is being decimated :/
//Compact();
SharedMesh mesh = new SharedMesh();
List<int> triangles = new List<int>();
HashSet<int> browsedNodes = new HashSet<int>();
Group[] newGroups = new Group[groups?.Length ?? 0];
mesh.groups = newGroups;
mesh.attributeDefinitions = attributeDefinitions;
int currentGroup = 0;
int indicesInGroup = 0;
Dictionary<(int, int), int> perVertexMap = new Dictionary<(int, int), int>();
for (int i = 0; i < nodes.Length; i++)
{
if (newGroups.Length > 0 && groups[currentGroup].firstIndex == i)
{
if (currentGroup > 0)
{
newGroups[currentGroup - 1].indexCount = indicesInGroup;
newGroups[currentGroup].firstIndex = indicesInGroup + newGroups[currentGroup - 1].firstIndex;
}
indicesInGroup = 0;
if (currentGroup < groups.Length - 1)
{
currentGroup++;
}
}
if (nodes[i].IsRemoved)
{
continue;
}
indicesInGroup++;
if (browsedNodes.Contains(i))
{
continue;
}
// Only works if all elements are triangles
int relative = i;
do
{
if (browsedNodes.Add(relative) && !nodes[relative].IsRemoved)
{
(int position, int attribute) key = (nodes[relative].position, nodes[relative].attribute);
perVertexMap.TryAdd(key, perVertexMap.Count);
triangles.Add(perVertexMap[key]);
}
} while ((relative = nodes[relative].relative) != i);
}
if (newGroups.Length > 0)
{
newGroups[currentGroup].indexCount = indicesInGroup;
}
// Positions
mesh.positions = new Vector3[perVertexMap.Count];
foreach (KeyValuePair<(int, int), int> mapping in perVertexMap)
{
mesh.positions[mapping.Value] = positions[mapping.Key.Item1];
}
// Attributes
if (attributes != null && attributeDefinitions.Length > 0)
{
mesh.attributes = attributes.CreateNew(perVertexMap.Count);
foreach (KeyValuePair<(int, int), int> mapping in perVertexMap)
{
mesh.attributes[mapping.Value] = attributes[mapping.Key.Item2];
}
}
mesh.triangles = triangles.ToArray();
return mesh;
}
}
public struct Edge : IEquatable<Edge>
{
public int posA;
public int posB;
public Edge(int posA, int posB)
{
this.posA = posA;
this.posB = posB;
}
public override int GetHashCode()
{
unchecked
{
return posA + posB;
}
}
public override bool Equals(object obj)
{
return Equals((Edge)obj);
}
public bool Equals(Edge pc)
{
if (ReferenceEquals(this, pc))
{
return true;
}
else
{
return (posA == pc.posA && posB == pc.posB) || (posA == pc.posB && posB == pc.posA);
}
}
public static bool operator ==(Edge x, Edge y)
{
return x.Equals(y);
}
public static bool operator !=(Edge x, Edge y)
{
return !x.Equals(y);
}
public override string ToString()
{
return $"<A:{posA} B:{posB}>";
}
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Linq;
namespace Nanomesh
{
public partial class ConnectedMesh
{
internal string PrintSiblings(int nodeIndex)
{
int sibling = nodeIndex;
string text = string.Join(" > ", Enumerable.Range(0, 12).Select(x =>
{
string res = sibling.ToString() + (nodes[sibling].IsRemoved ? "(x)" : $"({nodes[sibling].position})");
sibling = nodes[sibling].sibling;
return res;
}));
return text + "...";
}
internal string PrintRelatives(int nodeIndex)
{
int relative = nodeIndex;
string text = string.Join(" > ", Enumerable.Range(0, 12).Select(x =>
{
string res = relative.ToString() + (nodes[relative].IsRemoved ? "(x)" : $"({nodes[relative].position})");
relative = nodes[relative].relative;
return res;
}));
return text + "...";
}
internal bool CheckEdge(int nodeIndexA, int nodeIndexB)
{
if (nodes[nodeIndexA].position == nodes[nodeIndexB].position)
{
throw new Exception("Positions must be different");
}
if (nodes[nodeIndexA].IsRemoved)
{
throw new Exception($"Node A is unreferenced {nodeIndexA}");
}
if (nodes[nodeIndexB].IsRemoved)
{
throw new Exception($"Node B is unreferenced {nodeIndexB}");
}
return true;
}
internal bool CheckRelatives(int nodeIndex)
{
if (nodes[nodeIndex].IsRemoved)
{
throw new Exception($"Node {nodeIndex} is removed");
}
int relative = nodeIndex;
int edgecount = 0;
int prevPos = -2;
do
{
if (nodes[relative].position == prevPos)
{
throw new Exception($"Two relatives or more share the same position : {PrintRelatives(nodeIndex)}");
}
if (edgecount > 50)
{
throw new Exception($"Circularity relative violation : {PrintRelatives(nodeIndex)}");
}
if (nodes[relative].IsRemoved)
{
throw new Exception($"Node {nodeIndex} is connected to the deleted relative {relative}");
}
prevPos = nodes[relative].position;
edgecount++;
} while ((relative = nodes[relative].relative) != nodeIndex);
return true;
}
internal bool CheckSiblings(int nodeIndex)
{
if (nodes[nodeIndex].IsRemoved)
{
throw new Exception($"Node {nodeIndex} is removed");
}
int sibling = nodeIndex;
int cardinality = 0;
do
{
if (cardinality > 1000)
{
//throw new Exception($"Node {i}'s cardinality is superior to 50. It is likely to be that face siblings are not circularily linked");
throw new Exception($"Circularity sibling violation : {PrintSiblings(nodeIndex)}");
}
if (nodes[sibling].IsRemoved)
{
throw new Exception($"Node {nodeIndex} has a deleted sibling {sibling}");
}
cardinality++;
} while ((sibling = nodes[sibling].sibling) != nodeIndex);
return true;
}
internal bool Check()
{
for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
{
if (nodes[nodeIndex].IsRemoved)
{
continue;
}
CheckRelatives(nodeIndex);
CheckSiblings(nodeIndex);
if (GetEdgeCount(nodeIndex) == 2)
{
throw new Exception($"Node {nodeIndex} is part of a polygon of degree 2");
}
}
return true;
}
}
}

View File

@@ -0,0 +1,25 @@
namespace Nanomesh
{
public partial class ConnectedMesh
{
public struct Node
{
public int position;
public int sibling;
public int relative;
public int attribute;
public void MarkRemoved()
{
position = -10;
}
public bool IsRemoved => position == -10;
public override string ToString()
{
return $"sibl:{sibling} rela:{relative} posi:{position}";
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Nanomesh
{
public struct Group
{
public int firstIndex;
public int indexCount;
}
}

View File

@@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace Nanomesh
{
/// <summary>
/// A shared mesh is a flattened approach of the triangle mesh.
/// Is does not has connectivity information, but it is simple to create
/// and is a rather lightweight mesh data structure.
/// </summary>
public class SharedMesh
{
public Vector3[] positions;
public int[] triangles;
public Group[] groups;
public MetaAttributeList attributes;
public AttributeDefinition[] attributeDefinitions;
[Conditional("DEBUG")]
public void CheckLengths()
{
//if (attributes != null)
//{
// foreach (var pair in attributes)
// {
// Debug.Assert(pair.Value.Length == vertices.Length, $"Attribute '{pair.Value}' must have as many elements as vertices");
// }
//}
}
public ConnectedMesh ToConnectedMesh()
{
CheckLengths();
ConnectedMesh connectedMesh = new ConnectedMesh
{
groups = groups
};
connectedMesh.positions = positions;
connectedMesh.attributes = attributes;
connectedMesh.attributeDefinitions = attributeDefinitions;
// Building relatives
ConnectedMesh.Node[] nodes = new ConnectedMesh.Node[triangles.Length];
Dictionary<int, List<int>> vertexToNodes = new Dictionary<int, List<int>>();
for (int i = 0; i < triangles.Length; i += 3)
{
ConnectedMesh.Node A = new ConnectedMesh.Node();
ConnectedMesh.Node B = new ConnectedMesh.Node();
ConnectedMesh.Node C = new ConnectedMesh.Node();
A.position = triangles[i];
B.position = triangles[i + 1];
C.position = triangles[i + 2];
A.attribute = triangles[i];
B.attribute = triangles[i + 1];
C.attribute = triangles[i + 2];
A.relative = i + 1; // B
B.relative = i + 2; // C
C.relative = i; // A
if (!vertexToNodes.ContainsKey(A.position))
{
vertexToNodes.Add(A.position, new List<int>());
}
if (!vertexToNodes.ContainsKey(B.position))
{
vertexToNodes.Add(B.position, new List<int>());
}
if (!vertexToNodes.ContainsKey(C.position))
{
vertexToNodes.Add(C.position, new List<int>());
}
vertexToNodes[A.position].Add(i);
vertexToNodes[B.position].Add(i + 1);
vertexToNodes[C.position].Add(i + 2);
nodes[i] = A;
nodes[i + 1] = B;
nodes[i + 2] = C;
connectedMesh._faceCount++;
}
// Building siblings
foreach (KeyValuePair<int, List<int>> pair in vertexToNodes)
{
int previousSibling = -1;
int firstSibling = -1;
foreach (int node in pair.Value)
{
if (firstSibling != -1)
{
nodes[node].sibling = previousSibling;
}
else
{
firstSibling = node;
}
previousSibling = node;
}
nodes[firstSibling].sibling = previousSibling;
}
connectedMesh.nodes = nodes;
Debug.Assert(connectedMesh.Check());
return connectedMesh;
}
}
}

View File

@@ -0,0 +1,22 @@
# Todo List NOT LIGHTLESS RELATED XD
- [x] Bench iterating methods
- [x] Add a bunch of primitives
- [x] Add ConnectedMesh data structure
- [x] Add SharedMesh data structure
- [ ] Add vertex attributes
- [x] Add SharedMesh -> ConnectedMesh
- [ ] Add support for hardedges
- [ ] Add conversion of attributes
- [x] Add ConnectedMesh -> SharedMesh
- [ ] Add support for hardedges
- [x] Add export to obj
- [ ] Add support for normals
- [x] Add import from obj
- [ ] Add support for normals
- [x] Add decimate
- [x] Optimize until it is satisfying
- [ ] Take into account vertex normals
- [ ] Take into account borders
- [ ] Add an error target control
- [ ] Add create normals function