Skip to content

Commit

Permalink
Create MeshBuilder class from Renderer code
Browse files Browse the repository at this point in the history
Used for creating triangles or quads for Skia rendering. Also can be used for Unity mesh creation (in progress).
Move unit tests to NUnit from Microsoft's Test classes to be a bit more open.
Upped installer version to 1.2 (not yet released).
Added Itch.io banner image for Itch release.
  • Loading branch information
Arlorean committed Aug 13, 2017
1 parent 80af488 commit 1f3cf58
Show file tree
Hide file tree
Showing 17 changed files with 388 additions and 155 deletions.
Binary file added Voxels.CommandLine/1x1x1.vox
Binary file not shown.
2 changes: 1 addition & 1 deletion Voxels.CommandLine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ static void Main(string[] args) {
using (var stream = File.OpenRead(filename)) {
var voxelData = MagicaVoxel.Read(stream);

File.WriteAllBytes(Path.ChangeExtension(filename, ".png"), Renderer.RenderPng(512, voxelData));
File.WriteAllBytes(Path.ChangeExtension(filename, ".png"), Renderer.RenderPng(1024, voxelData));
File.WriteAllBytes(Path.ChangeExtension(filename, ".svg"), Renderer.RenderSvg(512, voxelData));
}
}
Expand Down
3 changes: 3 additions & 0 deletions Voxels.CommandLine/Voxels.CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
<Content Include="2x2x2.vox">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="1x1x1.vox">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="App.config" />
<Content Include="wizard.vox">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
2 changes: 1 addition & 1 deletion Voxels.Installer/Product.wxs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="Voxels.Installer" Language="1033" Version="1.1.0.0" Manufacturer="Arlorean" UpgradeCode="70e5c55f-1a55-4856-a0fc-a82ece384a74">
<Product Id="*" Name="Voxels.Installer" Language="1033" Version="1.2.0.0" Manufacturer="Arlorean" UpgradeCode="70e5c55f-1a55-4856-a0fc-a82ece384a74">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
Expand Down
2 changes: 1 addition & 1 deletion Voxels.Setup/Bundle.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

<!-- Source: https://gist.github.com/nathancorvussolis/6852ba282647aeb0c5c00e742e28eb48 -->
<Bundle Name="Voxels Shell Extensions" Version="1.1.0.0" Manufacturer="Arlorean"
<Bundle Name="Voxels Shell Extensions" Version="1.2.0.0" Manufacturer="Arlorean"
Copyright="© 2017 Adam Davidson" AboutUrl="https://github.com/Arlorean/Voxels"
UpgradeCode="{1823F323-C07E-4502-A4BA-CA8EDCA46161}" Condition="VersionNT &gt;= v5.1"
IconSourceFile="Voxels.ico">
Expand Down
156 changes: 49 additions & 107 deletions Voxels.SkiaSharp/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ public class Renderer {
static void RenderIntoBitmap(int size, VoxelData voxelData, SKBitmap bitmap) {
using (var canvas = new SKCanvas(bitmap)) {
bitmap.Erase(SKColors.Transparent);
Render(size, voxelData, canvas, ao: true);
RenderTriangles(voxelData, size, canvas, new MeshSettings {
AmbientOcclusion = true,
FrontFacesOnly = true,
MeshType = MeshType.Triangles,
});
}
}

Expand Down Expand Up @@ -37,137 +41,75 @@ public static byte[] RenderSvg(int size, VoxelData voxelData) {
using (var skStream = new SKManagedWStream(ms)) {
using (var writer = new SKXmlStreamWriter(skStream)) {
using (var canvas = SKSvgCanvas.Create(SKRect.Create(0, 0, size, size), writer)) {
Render(size, voxelData, canvas, ao:false);
RenderQuads(voxelData, size, canvas, new MeshSettings {
AmbientOcclusion = false,
FrontFacesOnly = true,
MeshType = MeshType.Quads,
});
}
}
}
return ms.ToArray();
}

static XYZ[] TopCross = new[] {
XYZ.OneY,
XYZ.OneX,
-XYZ.OneY,
-XYZ.OneX,
};
static XYZ[] LeftCross = new[] {
XYZ.OneY,
XYZ.OneZ,
-XYZ.OneY,
-XYZ.OneZ,
};
static XYZ[] RightCross = new[] {
XYZ.OneZ,
XYZ.OneX,
-XYZ.OneZ,
-XYZ.OneX,
};
static XYZ TopUp = XYZ.OneZ;
static XYZ RightUp = -XYZ.OneY;
static XYZ LeftUp = -XYZ.OneX;

static void Render(int size, VoxelData voxelData, SKCanvas canvas, bool ao) {
static SKMatrix44 GetMatrix(VoxelData voxelData, int size) {
var r = 1.61803398875f;
var d = size / (r*voxelData.size.MaxDimension);
var tran = SKMatrix44.CreateTranslate(size / 2, size/2,0);
var tran = SKMatrix44.CreateTranslate(size*0.5f, size*0.5f, 0);
var rotx = SKMatrix44.CreateRotationDegrees(1, 0, 0, -26f);
var roty = SKMatrix44.CreateRotationDegrees(0, 1, 0, 45);
var matrix = SKMatrix44.CreateIdentity();
matrix.PreConcat(tran);
matrix.PreConcat(rotx);
matrix.PreConcat(roty);
matrix.PreScale(d, -d, d);
matrix.PreTranslate(-voxelData.size.X*0.5f, -voxelData.size.Z*0.5f, voxelData.size.Y * 0.5f);

for (var y = voxelData.size.Y-1; y >=0; --y) {
for (var x = voxelData.size.X-1; x >= 0; --x) {
for (var z = 0; z < voxelData.size.Z; ++z) {
var i = new XYZ(x, y, z);

var color = voxelData.ColorOf(i);
if (color.A > 0) {
RenderQuad(voxelData, i, canvas, matrix, ao, color*0.8f, LeftCross, LeftUp);
RenderQuad(voxelData, i, canvas, matrix, ao, color * 0.9f, RightCross, RightUp);
RenderQuad(voxelData, i, canvas, matrix, ao, color, TopCross, TopUp);
}
}
}
}
matrix.PreTranslate(-voxelData.size.X*0.5f, -voxelData.size.Z*0.5f, voxelData.size.Y*0.5f);
return matrix;
}

static void RenderQuad(VoxelData voxelData, XYZ p, SKCanvas canvas, SKMatrix44 matrix, bool ao, Color color, XYZ[] cross, XYZ up) {
// Only render quad if face it isn't hidden by voxel above it
if (voxelData[p + up].colorIndex == 0) {
// Map voxel coordinates to projected 3D space

var vertices = Enumerable.Range(0, 4)
.Select(i => cross[i]+cross[(i+1)%4]+up)
.Select(c => c + XYZ.One)
.Select(c => matrix.MapScalars(p.X + c.X * .5f, p.Z + c.Z * .5f, -p.Y - c.Y * .5f, 1f))
static void RenderTriangles(VoxelData voxelData, int size, SKCanvas canvas, MeshSettings settings) {
var matrix = GetMatrix(voxelData, size);
settings.MeshType = MeshType.Triangles;
var triangles = new MeshBuilder(voxelData, settings);

using (var fill = new SKPaint()) {
var vertices = triangles.Vertices
.Select(v => matrix.MapScalars(v.X, v.Z, -v.Y, 1f))
.Select(v => new SKPoint(v[0], v[1]))
.ToArray();
var colors = triangles.Colors.Select(ToSKColor).ToArray();
var indices = triangles.Faces;
canvas.DrawVertices(SKVertexMode.Triangles, vertices, null, colors, indices, fill);
}
}

if (ao) {
// Create face quad path
var col = new SKColor(color.R, color.G, color.B, color.A);
var a00 = AmbientOcclusion.CalculateAO(voxelData, p, cross[0], cross[1], up);
var a01 = AmbientOcclusion.CalculateAO(voxelData, p, cross[1], cross[2], up);
var a11 = AmbientOcclusion.CalculateAO(voxelData, p, cross[2], cross[3], up);
var a10 = AmbientOcclusion.CalculateAO(voxelData, p, cross[3], cross[0], up);
var colors = new SKColor[] {
AOToColor(color, a00),
AOToColor(color, a01),
AOToColor(color, a11),
AOToColor(color, a10),
};

ushort[] indices;
if (a00 + a11 > a01 + a10) {
indices = new ushort[] { 0,1,2, 2,3,0, };
}
else {
indices = new ushort[] { 1,2,3, 3,0,1, };
}

// Calculate Ambient Occlusion
using (var fill = new SKPaint {
IsAntialias = false,
Style = SKPaintStyle.Fill,
}) {
canvas.DrawVertices(SKVertexMode.Triangles, vertices, null, colors, indices, fill);
}
}
else {
// Create face quad path
using (var face = new SKPath()) {
face.AddPoly(vertices, close: true);

// Calculate Ambient Occlusion
using (var fill = new SKPaint {
IsAntialias = false,
Style = SKPaintStyle.Fill,
Color = new SKColor(color.R, color.G, color.B, color.A),
}) {
canvas.DrawPath(face, fill);
}
static void RenderQuads(VoxelData voxelData, int size, SKCanvas canvas, MeshSettings settings) {
var matrix = GetMatrix(voxelData, size);
settings.MeshType = MeshType.Quads;
var quads = new MeshBuilder(voxelData, settings);

var vertices = quads.Vertices
.Select(v => matrix.MapScalars(v.X, v.Z, -v.Y, 1f))
.Select(v => new SKPoint(v[0], v[1]))
.ToArray();
var indices = quads.Faces;
for (var i=0; i < indices.Length; i += 4) {
using (var path = new SKPath()) {
var quad = Enumerable.Range(0, 4)
.Select(n => vertices[indices[i + n]])
.ToArray();
path.AddPoly(quad, close: true);

var color = quads.Colors[quads.Faces[i]]; // Take 1st vertex color for face
using (var fill = new SKPaint() { Color = ToSKColor(color) }) {
canvas.DrawPath(path, fill);
}
}
}
}

static SKColor AOToColor(Color color, int ao) {
var c = new SKColor(color.R, color.G, color.B, color.A);
float h, s, v;
c.ToHsv(out h, out s, out v);

float r = 0;
switch (ao) {
case 0: r = 0.5f; break;
case 1: r = 0.75f; break;
case 2: r = 0.8f; break;
case 3: r = 1f; break;
}
return SKColor.FromHsv(h, s, v * r, c.Alpha);
static SKColor ToSKColor(Color c) {
return new SKColor(c.R, c.G, c.B, c.A);
}
}
}
18 changes: 11 additions & 7 deletions Voxels.Test/TestAmbientOcclusion.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using System.IO;

namespace Voxels.Test {
[TestClass]
[TestFixture]
public class TestAmbientOcclusion {
[OneTimeSetUp]
public void SetUp() {
Directory.SetCurrentDirectory(TestContext.CurrentContext.TestDirectory);
}

[TestMethod]
[Test]
public void TestCase0() {
using (var stream = File.OpenRead("3x3x3.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Assert.AreEqual(0, AmbientOcclusion.CalculateAO(voxelData, new XYZ(1, 1, 1), XYZ.OneY, XYZ.OneX, XYZ.OneZ));
}
}

[TestMethod]
[Test]
public void TestCase1() {
using (var stream = File.OpenRead("3x3x3.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Assert.AreEqual(1, AmbientOcclusion.CalculateAO(voxelData, new XYZ(1, 0, 1), XYZ.OneY, XYZ.OneX, XYZ.OneZ));
Assert.AreEqual(1, AmbientOcclusion.CalculateAO(voxelData, new XYZ(0, 1, 1), XYZ.OneY, XYZ.OneX, XYZ.OneZ));
}
}
[TestMethod]
[Test]
public void TestCase2() {
using (var stream = File.OpenRead("3x3x3.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Assert.AreEqual(2, AmbientOcclusion.CalculateAO(voxelData, new XYZ(1, 1, 0), XYZ.OneY, XYZ.OneX, XYZ.OneZ));
}
}
[TestMethod]
[Test]
public void TestCase3() {
using (var stream = File.OpenRead("3x3x3.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Assert.AreEqual(3, AmbientOcclusion.CalculateAO(voxelData, new XYZ(2, 2, 2), XYZ.OneY, XYZ.OneX, XYZ.OneZ));
}
}
[TestMethod]
[Test]
public void TestCaseComplex() {
using (var stream = File.OpenRead("2x2x2.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Expand Down
53 changes: 53 additions & 0 deletions Voxels.Test/TestColor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Voxels.Test {
[TestFixture]

public class TestColor {
[Test]
public void TestHSV() {
float h, s, v;

Color.Black.ToHSV(out h, out s, out v);
Assert.AreEqual(0, h);
Assert.AreEqual(0, s);
Assert.AreEqual(0, v);
Assert.AreEqual(Color.Black, Color.FromHSV(h, s, v));

Color.White.ToHSV(out h, out s, out v);
Assert.AreEqual(0, h);
Assert.AreEqual(0, s);
Assert.AreEqual(1, v);
Assert.AreEqual(Color.White, Color.FromHSV(h, s, v));

Color.Red.ToHSV(out h, out s, out v);
Assert.AreEqual(0, h);
Assert.AreEqual(1, s);
Assert.AreEqual(1, v);
Assert.AreEqual(Color.Red, Color.FromHSV(h, s, v));

Color.Green.ToHSV(out h, out s, out v);
Assert.AreEqual(120, h);
Assert.AreEqual(1, s);
Assert.AreEqual(1, v);
Assert.AreEqual(Color.Green, Color.FromHSV(h, s, v));

Color.Blue.ToHSV(out h, out s, out v);
Assert.AreEqual(240, h);
Assert.AreEqual(1, s);
Assert.AreEqual(1, v);
Assert.AreEqual(Color.Blue, Color.FromHSV(h, s, v));

Color.Tan.ToHSV(out h, out s, out v);
Assert.AreEqual(34, h);
Assert.AreEqual(0.3333333, s, 1e-6);
Assert.AreEqual(0.8235294, v, 1e-6);
Assert.AreEqual(Color.Tan, Color.FromHSV(h, s, v));
}
}
}
15 changes: 10 additions & 5 deletions Voxels.Test/TestMagicaVoxel.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using System.IO;

namespace Voxels.Test {
[TestClass]
[TestFixture]
public class TestMagicaVoxel {
[TestMethod]
[OneTimeSetUp]
public void SetUp() {
Directory.SetCurrentDirectory(TestContext.CurrentContext.TestDirectory);
}

[Test]
public void Test1x2x3() {
using (var stream = File.OpenRead("1x2x3.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Expand All @@ -16,15 +21,15 @@ public void Test1x2x3() {
}
}

[TestMethod]
[Test]
public void Test3x3x3() {
using (var stream = File.OpenRead("3x3x3.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Assert.AreEqual(20, voxelData.Count);
}
}

[TestMethod]
[Test]
public void Test8x8x8() {
using (var stream = File.OpenRead("8x8x8.vox")) {
var voxelData = MagicaVoxel.Read(stream);
Expand Down
Loading

0 comments on commit 1f3cf58

Please sign in to comment.