Skip to content

Commit

Permalink
Merge pull request #10 from sandrofigo/7-add-support-to-get-model-pos…
Browse files Browse the repository at this point in the history
…itions

Add support for model name and position
  • Loading branch information
sandrofigo authored Jan 3, 2023
2 parents 58a1a54 + 169a2b8 commit ffa2eaa
Show file tree
Hide file tree
Showing 15 changed files with 233 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Parse model position
- Parse model name
- Better validation for Unity meta files in build pipeline

## [2.1.2] - 2023-01-03
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A C# library to read .vox files created with [MagicaVoxel](https://ephtracy.gith
## Currently supported features

At the moment it is possible to read:
- the size of all models
- the name, size and position of all models
- all individual voxels including their color and position
- the palette that was used for the model
- all raw data related to the scene
Expand Down Expand Up @@ -48,6 +48,6 @@ Follow the [manual installation](https://openupm.com/packages/com.sandrofigo.vox

## Extending the library

The file format specification made by [ephtracy](https://github.com/ephtracy) is available at [.vox file format](https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox.txt)
The file format specification made by [ephtracy](https://github.com/ephtracy) is available at [.vox file format](https://github.com/ephtracy/voxel-model)

Support this project with a ⭐️, report an issue or if you feel adventurous and would like to extend the functionality open a pull request.
53 changes: 53 additions & 0 deletions VoxReader.UnitTests/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,59 @@ public class UnitTests
private const string TestFile_3x3_3 = "data/3x3_3.zip";
private const string TestFile_1x1 = "data/1x1.zip";
private const string TestFile_256x256 = "data/256x256.zip";
private const string TestFile_MultipleModels = "data/multiple_models.zip";
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_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");
voxFile.Models.Should().ContainSingle(m => m.Name == "obj4");
voxFile.Models.Should().ContainSingle(m => m.Name == "obj5");
}

[Fact]
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));
voxFile.Models.Single(m => m.Name == "obj4").Position.Should().Be(new Vector3(-2, 1, 8));
voxFile.Models.Single(m => m.Name == "obj5").Position.Should().Be(new Vector3(2, 1, 8));
}

[Fact]
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));
voxFile.Models.Single(m => m.Name == "blue").Position.Should().Be(new Vector3(0, 0, 2));
voxFile.Models.Single(m => m.Name == "yellow").Position.Should().Be(new Vector3(0, 0, -2));
voxFile.Models.Single(m => m.Name == "magenta").Position.Should().Be(new Vector3(0, -2, 0));
voxFile.Models.Single(m => m.Name == "cyan").Position.Should().Be(new Vector3(-2, 0, 0));
}

[Fact]
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));
}

[Theory]
[InlineData(TestFile_3x3, 1)]
Expand Down
33 changes: 12 additions & 21 deletions VoxReader.UnitTests/VoxReader.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="FluentAssertions" Version="6.8.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1"/>
<PackageReference Include="SharpCompress" Version="0.32.2"/>
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand All @@ -22,29 +22,20 @@
</ItemGroup>

<ItemGroup>
<Folder Include="data" />
<Folder Include="data"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\VoxReader\VoxReader.csproj" />
<ProjectReference Include="..\VoxReader\VoxReader.csproj"/>
</ItemGroup>

<ItemGroup>
<None Update="data\1x1.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data\256x256.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data\3x3.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data\3x3_2.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data\3x3_3.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data\*.zip">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data\*.vox">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions VoxReader.UnitTests/data/3x3x3_at_center_with_corner.zip
Git LFS file not shown
3 changes: 3 additions & 0 deletions VoxReader.UnitTests/data/groups.zip
Git LFS file not shown
3 changes: 3 additions & 0 deletions VoxReader.UnitTests/data/multiple_models.zip
Git LFS file not shown
27 changes: 22 additions & 5 deletions VoxReader/Chunks/TransformNodeChunk.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Linq;
using VoxReader.Interfaces;
using VoxReader.Interfaces;

namespace VoxReader.Chunks
{
Expand Down Expand Up @@ -28,10 +27,28 @@ public TransformNodeChunk(byte[] data) : base(data)

int frameCount = FormatParser.ParseInt32();

if (frameCount > 0)
Frames = new Frame[frameCount];

for (int i = 0; i < frameCount; i++)
{
//TODO: implement frame parsing
Frames = Enumerable.Repeat(new Frame(), frameCount).ToArray();
var frameDictionary = FormatParser.ParseDictionary();

byte frameRotation = 0;
if (frameDictionary.TryGetValue("_r", out string r))
frameRotation = byte.Parse(r);

var frameTranslation = new Vector3(0,0,0);
if (frameDictionary.TryGetValue("_t", out string t))
{
string[] values = t.Split(' ');
frameTranslation = new Vector3(int.Parse(values[0]), int.Parse(values[1]), int.Parse(values[2]));
}

Frames[i] = new Frame
{
Rotation = frameRotation,
Translation = frameTranslation
};
}
}
}
Expand Down
35 changes: 34 additions & 1 deletion VoxReader/Color.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,44 @@
using System;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("VoxReader.UnitTests")]

namespace VoxReader
{
public readonly struct Color
public readonly struct Color : IEquatable<Color>
{
public bool Equals(Color other)
{
return R == other.R && G == other.G && B == other.B && A == other.A;
}

public override bool Equals(object obj)
{
return obj is Color other && Equals(other);
}

public override int GetHashCode()
{
unchecked
{
int hashCode = R.GetHashCode();
hashCode = (hashCode * 397) ^ G.GetHashCode();
hashCode = (hashCode * 397) ^ B.GetHashCode();
hashCode = (hashCode * 397) ^ A.GetHashCode();
return hashCode;
}
}

public static bool operator ==(Color left, Color right)
{
return left.Equals(right);
}

public static bool operator !=(Color left, Color right)
{
return !left.Equals(right);
}

/// <summary>
/// The red component of the color.
/// </summary>
Expand Down
93 changes: 75 additions & 18 deletions VoxReader/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,95 @@ public static IEnumerable<IModel> ExtractModels(IChunk mainChunk, IPalette palet
{
var sizeChunks = mainChunk.GetChildren<ISizeChunk>();
var voxelChunks = mainChunk.GetChildren<IVoxelChunk>();
var shapeNodeChunks = mainChunk.GetChildren<IShapeNodeChunk>();

if (sizeChunks.Length != voxelChunks.Length)
throw new InvalidDataException("Can not extract models, because the number of SIZE chunks does not match the number of XYZI chunks!");

var shapeNodeChunksQueue = new Queue<IShapeNodeChunk>(shapeNodeChunks);
var shapeNodeChunks = mainChunk.GetChildren<IShapeNodeChunk>();
var transformNodeChunks = mainChunk.GetChildren<ITransformNodeChunk>();
var groupNodeChunks = mainChunk.GetChildren<IGroupNodeChunk>();

var processedModels = new Dictionary<int, Model>();
var allNodes = new Dictionary<int, INodeChunk>();
foreach (ITransformNodeChunk t in transformNodeChunks)
allNodes.Add(t.NodeId, t);
foreach (IGroupNodeChunk g in groupNodeChunks)
allNodes.Add(g.NodeId, g);
foreach (IShapeNodeChunk s in shapeNodeChunks)
allNodes.Add(s.NodeId, s);

int duplicateModelCount = 0;

for (int i = 0; i < shapeNodeChunks.Length; i++)
var transformNodesThatHaveAShapeNode = new Dictionary<ITransformNodeChunk, IShapeNodeChunk>();
foreach (ITransformNodeChunk transformNodeChunk in transformNodeChunks)
{
Vector3 size = sizeChunks[i - duplicateModelCount].Size;
var voxels = voxelChunks[i - duplicateModelCount].Voxels.Select(voxel => new Voxel(voxel.Position, palette.Colors[voxel.ColorIndex - 1])).ToArray();

int id = shapeNodeChunksQueue.Dequeue().Models[0];

if (processedModels.ContainsKey(id))
foreach (IShapeNodeChunk shapeNodeChunk in shapeNodeChunks)
{
// Create copy of already existing model
duplicateModelCount++;
yield return processedModels[id].GetCopy();
if (transformNodeChunk.ChildNodeId != shapeNodeChunk.NodeId)
continue;

transformNodesThatHaveAShapeNode.Add(transformNodeChunk, shapeNodeChunk);
break;
}
else
}

var processedModelIds = new HashSet<int>();

foreach (var keyValuePair in transformNodesThatHaveAShapeNode)
{
ITransformNodeChunk transformNodeChunk = keyValuePair.Key;
IShapeNodeChunk shapeNodeChunk = keyValuePair.Value;

int[] ids = shapeNodeChunk.Models;

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();

// Create new model
var model = new Model(id, size, voxels, false);
processedModels.Add(id, model);
var model = new Model(id, name, position, size, voxels, !processedModelIds.Add(id));
yield return model;
}
}

Vector3 GetGlobalTranslation(ITransformNodeChunk target)
{
Vector3 position = target.Frames[0].Translation;

while (TryGetParentTransformNodeChunk(target, out ITransformNodeChunk parent))
{
position += parent.Frames[0].Translation;

target = parent;
}

return position;
}

bool TryGetParentTransformNodeChunk(ITransformNodeChunk target, out ITransformNodeChunk parent)
{
//TODO: performance here is questionable; might need an additional scene structure to query the parent efficiently
foreach (IGroupNodeChunk groupNodeChunk in groupNodeChunks)
{
foreach (int parentGroupNodeChunkChildId in groupNodeChunk.ChildrenNodes)
{
if (parentGroupNodeChunkChildId != target.NodeId)
continue;

foreach (ITransformNodeChunk transformNodeChunk in transformNodeChunks)
{
if (transformNodeChunk.ChildNodeId != groupNodeChunk.NodeId)
continue;

parent = transformNodeChunk;
return true;
}
}
}

parent = null;
return false;
}
}
}
}
10 changes: 10 additions & 0 deletions VoxReader/Interfaces/IModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ namespace VoxReader.Interfaces
{
public interface IModel
{
/// <summary>
/// The name of the model.
/// </summary>
string Name { get; }

/// <summary>
/// The position of the model.
/// </summary>
Vector3 Position { get; }

/// <summary>
/// The size of the model.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion VoxReader/Interfaces/IShapeNodeChunk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
internal interface IShapeNodeChunk : INodeChunk
{
/// <summary>
/// The number of models. Must be '1'.
/// The number of models.
/// </summary>
int ModelCount { get; }

Expand Down
Loading

0 comments on commit ffa2eaa

Please sign in to comment.