diff --git a/CHANGELOG.md b/CHANGELOG.md index 514294f..684e6a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --> +## [3.1.0] - 2023-01-03 + +### Added + +- Added `GlobalPosition` property to `Voxel` + ## [3.0.0] - 2023-01-03 ### Added diff --git a/VoxReader.UnitTests/UnitTests.cs b/VoxReader.UnitTests/UnitTests.cs index 527e749..8e00653 100644 --- a/VoxReader.UnitTests/UnitTests.cs +++ b/VoxReader.UnitTests/UnitTests.cs @@ -16,12 +16,30 @@ public class UnitTests private const string TestFile_3x3x3_at_center_with_corner = "data/3x3x3_at_center_with_corner.zip"; private const string TestFile_groups = "data/groups.zip"; + [Fact] + public void VoxReader_Read_GlobalVoxelPositionIsCorrect() + { + string file = Zip.UnzipFilesFromSevenZipArchive(TestFile_groups).First(); + + IVoxFile voxFile = VoxReader.Read(file); + + voxFile.Models.Single(m => m.Name == "obj1").Voxels[0].GlobalPosition.Should().Be(new Vector3(0, 0, 0)); + voxFile.Models.Single(m => m.Name == "obj2").Voxels[0].GlobalPosition.Should().Be(new Vector3(0, 0, 2)); + + voxFile.Models.Single(m => m.Name == "obj3").Voxels.Single(v => v.Color == Color.Blue).GlobalPosition.Should().Be(new Vector3(-3, 0, 3)); + voxFile.Models.Single(m => m.Name == "obj3").Voxels.Single(v => v.Color == Color.Red).GlobalPosition.Should().Be(new Vector3(-1, 2, 5)); + + voxFile.Models.Single(m => m.Name == "obj4").Voxels.Single(v => v.Color == Color.Blue).GlobalPosition.Should().Be(new Vector3(-3, 0, 7)); + voxFile.Models.Single(m => m.Name == "obj4").Voxels.Single(v => v.Color == Color.Red).GlobalPosition.Should().Be(new Vector3(-1, 2, 9)); + } + [Fact] public void VoxReader_Read_ModelNamesAreParsedCorrectly() { string file = Zip.UnzipFilesFromSevenZipArchive(TestFile_groups).First(); IVoxFile voxFile = VoxReader.Read(file); + voxFile.Models.Should().ContainSingle(m => m.Name == "obj1"); voxFile.Models.Should().ContainSingle(m => m.Name == "obj2"); voxFile.Models.Should().ContainSingle(m => m.Name == "obj3"); @@ -35,6 +53,7 @@ public void VoxReader_Read_ModelPositionsAreCorrectInGroups() string file = Zip.UnzipFilesFromSevenZipArchive(TestFile_groups).First(); IVoxFile voxFile = VoxReader.Read(file); + voxFile.Models.Single(m => m.Name == "obj1").Position.Should().Be(new Vector3(0, 0, 0)); voxFile.Models.Single(m => m.Name == "obj2").Position.Should().Be(new Vector3(0, 0, 2)); voxFile.Models.Single(m => m.Name == "obj3").Position.Should().Be(new Vector3(-2, 1, 4)); @@ -48,6 +67,7 @@ public void VoxReader_Read_ModelPositionsAreCorrect() string file = Zip.UnzipFilesFromSevenZipArchive(TestFile_MultipleModels).First(); IVoxFile voxFile = VoxReader.Read(file); + voxFile.Models.Single(m => m.Name == "black").Position.Should().Be(new Vector3(0, 0, 0)); voxFile.Models.Single(m => m.Name == "red").Position.Should().Be(new Vector3(2, 0, 0)); voxFile.Models.Single(m => m.Name == "green").Position.Should().Be(new Vector3(0, 2, 0)); @@ -63,6 +83,7 @@ public void VoxReader_Read_ModelPositionIsCorrectFor3x3x3Model() string file = Zip.UnzipFilesFromSevenZipArchive(TestFile_3x3x3_at_center_with_corner).First(); IVoxFile voxFile = VoxReader.Read(file); + voxFile.Models.Single(m => m.Name == "obj1").Position.Should().Be(new Vector3(1, 1, 1)); } diff --git a/VoxReader.UnitTests/VoxReader.UnitTests.csproj b/VoxReader.UnitTests/VoxReader.UnitTests.csproj index bf4c5d3..d1420c8 100644 --- a/VoxReader.UnitTests/VoxReader.UnitTests.csproj +++ b/VoxReader.UnitTests/VoxReader.UnitTests.csproj @@ -7,10 +7,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,11 +22,11 @@ - + - + diff --git a/VoxReader.UnitTests/data/groups.zip b/VoxReader.UnitTests/data/groups.zip index 5488f97..3ab2b72 100644 --- a/VoxReader.UnitTests/data/groups.zip +++ b/VoxReader.UnitTests/data/groups.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ca59bcad5d9f276a76770b2e0e504067584e2a8ef36243cff5b2d5af6fa823a -size 2009 +oid sha256:4d7b132c3ac61b208995ecaa8f0b95dafb220222e27fb9469e41d9d112b0faaf +size 2024 diff --git a/VoxReader/Color.cs b/VoxReader/Color.cs index d883b15..a291dba 100644 --- a/VoxReader/Color.cs +++ b/VoxReader/Color.cs @@ -71,5 +71,11 @@ public override string ToString() { return $"R: {R} G: {G} B: {B} A: {A}"; } + + internal static readonly Color Red = new(255, 0, 0, 255); + internal static readonly Color Green = new(0, 255, 0, 255); + internal static readonly Color Blue = new(0, 0, 255, 255); + internal static readonly Color White = new(255, 255, 255, 255); + internal static readonly Color Black = new(0, 0, 0, 255); } } \ No newline at end of file diff --git a/VoxReader/Helper.cs b/VoxReader/Helper.cs index 9e5a7cc..7a54832 100644 --- a/VoxReader/Helper.cs +++ b/VoxReader/Helper.cs @@ -64,9 +64,10 @@ public static IEnumerable ExtractModels(IChunk mainChunk, IPalette palet foreach (int id in ids) { string name = transformNodeChunk.Name; - Vector3 position = GetGlobalTranslation(transformNodeChunk); Vector3 size = sizeChunks[id].Size; - var voxels = voxelChunks[id].Voxels.Select(voxel => new Voxel(voxel.Position, palette.Colors[voxel.ColorIndex - 1])).ToArray(); + Vector3 position = GetGlobalTranslation(transformNodeChunk); + + var voxels = voxelChunks[id].Voxels.Select(voxel => new Voxel(voxel.Position, position + voxel.Position - size / 2, palette.Colors[voxel.ColorIndex - 1])).ToArray(); // Create new model var model = new Model(id, name, position, size, voxels, !processedModelIds.Add(id)); @@ -77,11 +78,11 @@ public static IEnumerable ExtractModels(IChunk mainChunk, IPalette palet Vector3 GetGlobalTranslation(ITransformNodeChunk target) { Vector3 position = target.Frames[0].Translation; - + while (TryGetParentTransformNodeChunk(target, out ITransformNodeChunk parent)) { position += parent.Frames[0].Translation; - + target = parent; } diff --git a/VoxReader/Vector3.cs b/VoxReader/Vector3.cs index ecb5925..3a565b6 100644 --- a/VoxReader/Vector3.cs +++ b/VoxReader/Vector3.cs @@ -8,12 +8,12 @@ public struct Vector3 : IEquatable /// The x-component of the vector (right). /// public readonly int X; - + /// /// The y-component of the vector (forward). /// public readonly int Y; - + /// /// The z-component of the vector (up). /// @@ -51,7 +51,9 @@ public override int GetHashCode() return hashCode; } } - + + //TODO: add unit tests + public static bool operator ==(Vector3 a, Vector3 b) { return a.Equals(b); @@ -61,10 +63,25 @@ public override int GetHashCode() { return !(a == b); } - + public static Vector3 operator +(Vector3 a, Vector3 b) { return new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); } + + public static Vector3 operator -(Vector3 a, Vector3 b) + { + return new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); + } + + public static Vector3 operator *(Vector3 a, int i) + { + return new Vector3(a.X * i, a.Y * i, a.Z * i); + } + + public static Vector3 operator /(Vector3 a, int i) + { + return new Vector3(a.X / i, a.Y / i, a.Z / i); + } } } \ No newline at end of file diff --git a/VoxReader/Voxel.cs b/VoxReader/Voxel.cs index 5c5c320..21d56a4 100644 --- a/VoxReader/Voxel.cs +++ b/VoxReader/Voxel.cs @@ -3,18 +3,24 @@ namespace VoxReader public readonly struct Voxel { /// - /// The position of the voxel. + /// The position of the voxel in the model. /// public Vector3 Position { get; } + + /// + /// The global position of the voxel in the scene. + /// + public Vector3 GlobalPosition { get; } /// /// The color of the voxel. /// public Color Color { get; } - internal Voxel(Vector3 position, Color color) + internal Voxel(Vector3 position, Vector3 globalPosition, Color color) { Position = position; + GlobalPosition = globalPosition; Color = color; } diff --git a/VoxReader/package.json b/VoxReader/package.json index 61f159d..37b6e8e 100644 --- a/VoxReader/package.json +++ b/VoxReader/package.json @@ -1,6 +1,6 @@ { "name": "com.sandrofigo.voxreader", - "version": "3.0.0", + "version": "3.1.0", "displayName": "VoxReader", "description": "A C# library to read .vox files created with MagicaVoxel", "documentationUrl": "https://github.com/sandrofigo/VoxReader",