static void TestSlices() { Console.WriteLine("Hello World!"); VoxelSet <Vec4b> colors = new VoxelSet <Vec4b>(32); colors.Set(new Vec4b(0)); colors.Slice(new Vec3i(1), new Vec3i(30)).Set(new Vec4b(255)); colors.Slice(new Vec3i(3), new Vec3i(28)).Set(new Vec4b(0)); colors.Slice(new Vec3i(5), new Vec3i(26)).Set(new Vec4b(255)); for (int i = 0; i < 32; ++i) { if (colors[16, i, 16].x == 0) { Console.Write("."); } else { Console.Write("#"); } } Console.Write("\nPress any key to continue ... "); Console.ReadKey(); }
/// Loads a MagicaVoxel model into the current model. public void LoadMagicaModel(string magicaVoxelFile, bool retainVoxels, VoxelFactory.ColliderType colliderType, MagicaFlags flags) { Clear(); this.colliderType = colliderType; PointQuadList opaqueFaces = new PointQuadList(); PointQuadList transparentFaces = new PointQuadList(); IntPtr model = OpenBoxNative.MagicaLoadModel(magicaVoxelFile, flags); OpenBoxNative.MagicaExtractFaces(model, ref opaqueFaces, ref transparentFaces); if (retainVoxels || colliderType != VoxelFactory.ColliderType.None) { // Copy voxels over to the C# side voxels = new VoxelSet <Color32>(OpenBoxNative.MagicaModelSize(model)); OpenBoxNative.MagicaCopyVoxels(voxels.Pin(), model); voxels.Unpin(); // TODO: Calculate colliders natively UpdateColliders(); } OpenBoxNative.MagicaFreeModel(model); MakeMeshFromQuadLists(opaqueFaces, transparentFaces); OpenBoxNative.FreeFacesHandle(opaqueFaces.handle); OpenBoxNative.FreeFacesHandle(transparentFaces.handle); }
public void OnAfterDeserialize() { if (serializeVoxelColors == null) { return; } voxels = new VoxelSet <Color32>( serializeVoxelDimension.x, serializeVoxelDimension.y, serializeVoxelDimension.z ); // TODO: MemCopy would be better for (int z = 0; z < serializeVoxelDimension.z; ++z) { for (int y = 0; y < serializeVoxelDimension.y; ++y) { int offset = y * serializeVoxelDimension.x + z * serializeVoxelDimension.y * serializeVoxelDimension.x; for (int x = 0; x < serializeVoxelDimension.x; ++x) { voxels[x, y, z] = serializeVoxelColors[offset + x]; } } } serializeVoxelColors = null; }
static void SmearDirection(VoxelSet <float> voxels, float transference, Vec3i dir) { Vec3i dx = new Vec3i(Math.Abs(dir.y), Math.Abs(dir.z), Math.Abs(dir.x)); Vec3i dy = new Vec3i(Math.Abs(dir.z), Math.Abs(dir.x), Math.Abs(dir.y)); Vec3i start = new Vec3i(0); if (Vec3i.Dot(dir, new Vec3i(1)) < 0) { start = voxels.Size.Dot(dir) * dir + dir; } int y = 0; while (voxels.IsValid(start + y * dy)) { int x = 0; while (voxels.IsValid(start + y * dy + x * dx)) { SmearLine(voxels, transference, dir, start + y * dy + x * dx); x++; } y++; } }
public static List <Box> MakeBoxes(VoxelSet <bool> shape) { List <Box> boxes = new List <Box>(); // Directions to explore Vec3i[] dirs = new[] { new Vec3i(1, 0, 0), new Vec3i(0, 1, 0), new Vec3i(0, 0, 1) }; // TODO: Iteration order may need to be reversed for (int z = 0; z < shape.Size.z; z++) { for (int y = 0; y < shape.Size.y; y++) { for (int x = 0; x < shape.Size.x; x++) { if (!shape[x, y, z]) { continue; } Vec3i idx = new Vec3i(x, y, z); Vec3i endIdx = new Vec3i(x, y, z); // Expand box in each cardinal direction as much as possible foreach (var dir in dirs) { Vec3i checkFromIdx = idx; // Expand as much as possible in the current direction while (shape.IsValid(endIdx + dir)) { checkFromIdx += dir; // Create a slice for the next layer in the current dir and make sure it's solid var slice = shape.Slice(checkFromIdx, endIdx + dir); if (!slice.IsAllSolid(b => b)) { break; } slice.Set(false); endIdx += dir; } } Box box = new Box(); box.origin = new Vec3i(x, y, z); box.extents = new Vec3i(endIdx.x + 1 - idx.x, endIdx.y + 1 - idx.y, endIdx.z + 1 - idx.z); boxes.Add(box); } } } return(boxes); }
static void SmearLine(VoxelSet <float> voxels, float transference, Vec3i dir, Vec3i start) { Vec3i idx = start + dir; while (voxels.IsValid(idx)) { voxels[idx] += voxels[idx - dir] * transference; idx += dir; } }
public static MeshSimplifier VoxelsToMesh(VoxelSet <Vec4b> voxels) { MeshSimplifier ms = VoxelsToMeshFull(voxels); Console.WriteLine("Triangles before reduction: " + (ms.Edges.Length / 3)); ms.Simplify(); ms.Compact(); Console.WriteLine("Triangles after reduction: " + (ms.Edges.Length / 3)); return(ms); }
private void CreateRootDictionaries() { for (int i = 0; i < Models.Length; i++) { Models[i] = new ModelSet(); } for (int i = 0; i < Voxels.Length; i++) { Voxels[i] = new VoxelSet(); } }
public static MeshSimplifier VoxelsToMeshFull(VoxelSet <Vec4b> voxels) { var quads = VoxelsToQuads(voxels); Vec3f[] positions = new Vec3f[quads.Count * 4]; Vec3f[] normals = new Vec3f[quads.Count * 4]; int[] tris = new int[quads.Count * 6]; Dictionary <Vec3b, int> posRemap = new Dictionary <Vec3b, int>(); int idx = 0; foreach (var q in quads) { for (int i = 0; i < 4; ++i) { normals[idx * 4 + i] = q.normal; } positions[idx * 4 + 0] = q.offset; positions[idx * 4 + 1] = q.offset + q.dx; positions[idx * 4 + 2] = q.offset + q.dx + q.dy; positions[idx * 4 + 3] = q.offset + q.dy; int[] posIndices = new int[4]; for (int i = 0; i < 4; ++i) { Vec3b posKey = new Vec3b(positions[idx * 4 + i]); if (!posRemap.ContainsKey(posKey)) { posRemap[posKey] = idx * 4 + i; } posIndices[i] = posRemap[posKey]; } tris[idx * 6 + 0] = posIndices[0]; tris[idx * 6 + 1] = posIndices[1]; tris[idx * 6 + 2] = posIndices[2]; tris[idx * 6 + 3] = posIndices[0]; tris[idx * 6 + 4] = posIndices[2]; tris[idx * 6 + 5] = posIndices[3]; idx++; } MeshSimplifier ms = new MeshSimplifier(positions, tris); return(ms); }
public void Clear() { voxels = null; #if !UNITY_EDITOR MeshFilter mf = GetComponent <MeshFilter>(); if (mf.sharedMesh != null) { Destroy(mf.sharedMesh); mf.sharedMesh = null; } #endif }
// Makes a set of covers to completely cover the given shape. static List <BoxMaker.Box> MakeBoxes(VoxelSet <bool> shape, int scale) { List <BoxMaker.Box> boxes = BoxMaker.MakeBoxes(shape); if (scale != 1) { foreach (var box in boxes) { box.extents *= scale; box.origin *= scale; } } return(boxes); }
// Reduces the size of the given shape by the given factor. static VoxelSet <bool> ReduceShape(VoxelSet <bool> shape, int factor) { Vec3i size = Vec3i.Max(shape.Size / factor, new Vec3i(1)); Debug.Log("Old size: " + shape.Size + " New size: " + size); VoxelSet <bool> reducedShape = new VoxelSet <bool>(size); shape.Apply((v, idx) => { Vec3i targetIdx = Vec3i.Min(size - 1, idx / factor); reducedShape[targetIdx] = reducedShape[targetIdx] || v; }); return(reducedShape); }
// Makes a mesh from the given voxel set. public static void MakeMeshNative(VoxelSet <Vec4b> voxels, out Mesh mesh, out Material[] materials) { PointQuadList opaqueFaces = new PointQuadList(); PointQuadList transparentFaces = new PointQuadList(); IntPtr voxelsPtr = voxels.Pin(); obx_ExtractFaces(voxelsPtr, voxels.Size, ref opaqueFaces, ref transparentFaces); voxels.Unpin(); MakeMeshFromQuadLists(opaqueFaces, transparentFaces, out mesh, out materials); obx_FreeFacesHandle(opaqueFaces.handle); obx_FreeFacesHandle(transparentFaces.handle); }
public static VoxelSet <bool> LowPassFilter(VoxelSet <Vec4b> voxels, float transference, float threshold) { VoxelSet <float> fVoxels = voxels.Project(ToFloat); Vec3i[] dirs = { new Vec3i(1, 0, 0), new Vec3i(-1, 0, 0), new Vec3i(0, 1, 0), new Vec3i(0, -1, 0), new Vec3i(0, 0, 1), new Vec3i(0, 0, -1), }; foreach (var dir in dirs) { SmearDirection(fVoxels, transference, dir); } return(fVoxels.Project(v => v >= threshold)); }
// Loads the given model into a voxel set and returns it. VoxelSet <Vec4b> LoadModelChunk(Chunk sizeChunk, Chunk idxChunk) { if (sizeChunk.chunkId != kChunkSize) { throw new Exception("Bad size chunk"); } if (idxChunk.chunkId != kChunkXyzi) { throw new Exception("Bad index chunk"); } BinaryReader sizeReader = sizeChunk.OpenReader(); BinaryReader idxReader = idxChunk.OpenReader(); // TODO: This seems to load models rotated 180 degrees Vec3i size = new Vec3i(); size.x = sizeReader.ReadInt32(); size.z = sizeReader.ReadInt32(); size.y = sizeReader.ReadInt32(); VoxelSet <Vec4b> model = new VoxelSet <Vec4b>(size); // Read individual voxels int numVoxels = idxReader.ReadInt32(); for (int j = 0; j < numVoxels; ++j) { //Vec3i idx = new Vec3i(br.ReadByte(), br.ReadByte(), br.ReadByte()); Vec3i idx = new Vec3i(0); idx.x = idxReader.ReadByte(); idx.z = idxReader.ReadByte(); idx.y = idxReader.ReadByte(); // For now just store the index into the palette model[idx] = new Vec4b(idxReader.ReadByte()); } return(model); }
// Adds colliders to the given game object. public static void AddColliders(GameObject obj, VoxelSet <Color32> voxels, ColliderType colliderType) { if (colliderType != ColliderType.None) { VoxelSet <bool> shape = voxels.Project(v => v.a > 0); int scale = 1; if (colliderType == ColliderType.HalfScale) { //shape = ReduceShape(shape); shape = ReduceShape(shape, 2); scale = 2; } if (colliderType == ColliderType.ThirdScale) { shape = ReduceShape(shape, 3); scale = 3; } if (colliderType == ColliderType.QuarterScale) { //shape = ReduceShape(ReduceShape(shape)); shape = ReduceShape(shape, 4); scale = 4; } List <BoxMaker.Box> boxes = MakeBoxes(shape, scale); // Add box colliders foreach (var boxDesc in boxes) { BoxCollider box = obj.AddComponent <BoxCollider>(); box.size = new Vector3(boxDesc.extents.x, boxDesc.extents.y, boxDesc.extents.z); box.center = new Vector3(boxDesc.origin.x, boxDesc.origin.y, boxDesc.origin.z) + box.size / 2.0f; } } }
public IPDMeshCreator(VoxelSet voxelSet) { VoxelSet = voxelSet; }
public IPDMeshCreator(VoxelSet voxelSet, bool smartUpdate, bool influenceRegion2) : this(voxelSet, smartUpdate) { this.influenceRegion2 = influenceRegion2; }
public void GetMesh(VoxelSet <Vec4b> voxels, out int[] indices, out Vec3f[] points, out Vec3f[] normals, out Vec2f[] uvs, out VoxelSet <Vec4b> textureAtlas) { List <int> indicesList = new List <int>(); List <Vec3f> pointsList = new List <Vec3f>(); List <Vec3f> normalsList = new List <Vec3f>(); List <Vec2f> uvsList = new List <Vec2f>(); Dictionary <int, int> vertIdxRemap = new Dictionary <int, int>(); List <Bin> bins = new List <Bin>(); foreach (var poly in AllPolygons()) { // For each connected polygon vertIdxRemap.Clear(); Vec3f normal = Normal(poly.First()); Vec3f maxP = new Vec3f(-1); Vec3f minP = new Vec3f(1024 * 1024); AtlasEntry atlasEntry = new AtlasEntry(); atlasEntry.startVertIdx = pointsList.Count; atlasEntry.normal = new Vec3i( (int)Math.Round(normal.x), (int)Math.Round(normal.y), (int)Math.Round(normal.z) ); foreach (var baseEdge in poly) { // Each triangle that makes up the polygon foreach (var e in new int[] { baseEdge, Next(baseEdge), Prev(baseEdge) }) { // Each vertex of each triangle int vertIdx = Edges[e].vertexIdx; if (!vertIdxRemap.ContainsKey(vertIdx)) { vertIdxRemap.Add(vertIdx, pointsList.Count); pointsList.Add(Points[vertIdx]); normalsList.Add(normal); maxP = Max3f(maxP, Points[vertIdx]); minP = Min3f(minP, Points[vertIdx]); // TODO: Add UV here } indicesList.Add(vertIdxRemap[vertIdx]); } } atlasEntry.vertCount = pointsList.Count - atlasEntry.startVertIdx; // TODO: Add to texture atlas here Vec3f deltaP = maxP - minP; // Determine which dimension is flat int flatIdx = -1; for (int i = 0; i < 3; ++i) { if (deltaP[i] == 0) { if (flatIdx >= 0) { throw new Exception("Two or more flat dimensions found"); } flatIdx = i; } } Vec2i size; if (flatIdx == 0) { size = new Vec2i((int)Math.Round(deltaP.y), (int)Math.Round(deltaP.z)); } else if (flatIdx == 1) { size = new Vec2i((int)Math.Round(deltaP.x), (int)Math.Round(deltaP.z)); } else { size = new Vec2i((int)Math.Round(deltaP.x), (int)Math.Round(deltaP.y)); } atlasEntry.flatDimension = flatIdx; atlasEntry.max = new Vec3i(maxP + new Vec3f(0.5f)); atlasEntry.min = new Vec3i(minP + new Vec3f(0.5f)); Bin b = new Bin(); b.Size = size; b.UserData = atlasEntry; bins.Add(b); } indices = indicesList.ToArray(); points = pointsList.ToArray(); normals = normalsList.ToArray(); //BinPacker bp = new BinPacker(512); BinPacker bp = new BinPacker(512); bp.BinPadding = new Vec2i(2); bp.Pack(bins); bp.MakePow2(); textureAtlas = new VoxelSet <Vec4b>(bp.Width, bp.Height, 1); for (int y = 0; y < textureAtlas.Size.y; ++y) { for (int x = 0; x < textureAtlas.Size.x; ++x) { textureAtlas[x, y, 0] = new Vec4b(0, 255, 0, 255); } } uvs = new Vec2f[points.Length]; foreach (var bin in bp.Bins) { var atlasEntry = (AtlasEntry)bin.UserData; // Copy to atlas var toSlice = textureAtlas.Slice(new Vec3i(bin.Position, 0), new Vec3i(bin.Position + bin.Size - 1, 0)); Vec3i voxelOffset = new Vec3i(0); if (atlasEntry.normal.Dot(new Vec3i(1)) > 0) { voxelOffset = atlasEntry.normal * -1; } else { //voxelOffset = atlasEntry.normal * -1; voxelOffset = new Vec3i(0); } var fromSlice = voxels.Slice( atlasEntry.min + voxelOffset, atlasEntry.max - atlasEntry.min + atlasEntry.Basis() * new Vec3i(0, 0, 1), atlasEntry.Basis() ); if (toSlice.Size.x != fromSlice.Size.x || toSlice.Size.y != fromSlice.Size.y || toSlice.Size.z != fromSlice.Size.z) { throw new Exception(); } /*for (int y = 0; y < toSlice.Size.y; ++y) { * for (int x = 0; x < toSlice.Size.x; ++x) { * toSlice[x, y, 0] = fromSlice[x, y, 0]; * } * }*/ for (int y = 0; y < toSlice.Size.y; ++y) { for (int x = 0; x < toSlice.Size.x; ++x) { if (Vec4b.Dot(toSlice[x, y, 0], new Vec4b(1)) != 0) { ////throw new Exception(); } toSlice[x, y, 0] = voxels[atlasEntry.To3d(new Vec2i(x, y)) + voxelOffset]; //toSlice[x, y, 0] = c; } } // Compute UVs for (int i = atlasEntry.startVertIdx; i < atlasEntry.startVertIdx + atlasEntry.vertCount; ++i) { Vec2f uv = new Vec2f(atlasEntry.To2d(new Vec3i(points[i]))); //uv *= new Vec2f(bin.Size - 1) / new Vec2f(bin.Size); //uv += 0.5f; uv *= (new Vec2f(bin.Size) - 0.01f) / new Vec2f(bin.Size); uv += 0.005f; ///////// //uv *= 0.998f; //uv += 0.01f; uv += new Vec2f(bin.Position); /////// //uv += 0.25f; uv.x /= bp.Width; uv.y /= bp.Height; uvs[i] = uv; } } Console.WriteLine("Total size: {0} x {1}", bp.Width, bp.Height); }
public IPDMeshCreator(VoxelSet voxelSet, bool smartUpdate, bool influenceRegion2, bool energyFunction2, float regionAngle) : this(voxelSet, smartUpdate, influenceRegion2) { this.energyFunction2 = energyFunction2; this.regionAngle = regionAngle; }
//////////////////////////////////////////////////////////////////////////// // Adds all faces for the given index of the given voxels to the list of quads. static void AddFaces(VoxelSet <Vec4b> voxels, List <Quad> quads, Vec3i idx) { Vec3i[] normals = { new Vec3i(1, 0, 0), new Vec3i(-1, 0, 0), new Vec3i(0, 1, 0), new Vec3i(0, -1, 0), new Vec3i(0, 0, 1), new Vec3i(0, 0, -1) }; bool transparent = IsTransparent(voxels, idx); for (int i = 0; i < normals.Length; ++i) { Vec3i normal = normals[i]; Vec3i neighbor = idx + normal; if (voxels.IsValid(neighbor) && (voxels[neighbor].w > 0)) { if (transparent && IsTransparent(voxels, neighbor)) { // Two transparent voxels - face is hidden. continue; } if (transparent && voxels[neighbor].w == 255) { // Transparent self and opaque neighbor - hidden to avoid z-fighting. continue; } if (!transparent && voxels[neighbor].w == 255) { // Two opaque voxels - face is hidden. continue; } } var c = voxels[idx]; Quad q = new Quad(); q.color = new Color32(c.x, c.y, c.z, c.w); Vec3i pos = idx; if (Vec3i.Dot(normal, new Vec3i(1)) > 0) { pos += normal; } q.position = new Vector3(pos.x, pos.y, pos.z); q.uv = new Vector2(i, 0); quads.Add(q); if (transparent) { // Add back facing as well for transparent quads //q.uv = new Vector2((i - (i % 2)) + ((i + 1) % 2), 0); q.uv = new Vector2(i ^ 1, 0); //q.position += new Vector3(normal.x, normal.y, normal.z); quads.Add(q); } } }
public static List <Quad> VoxelsToQuads(VoxelSet <Vec4b> voxels) { List <Quad> quads = new List <Quad>(); Vec3f[] normals = { new Vec3f(1, 0, 0), new Vec3f(-1, 0, 0), new Vec3f(0, 1, 0), new Vec3f(0, -1, 0), new Vec3f(0, 0, 1), new Vec3f(0, 0, -1) }; Vec3f[] dxs = { new Vec3f(0, 1, 0), new Vec3f(0, 0, 1), new Vec3f(0, 0, 1), new Vec3f(1, 0, 0), new Vec3f(1, 0, 0), new Vec3f(0, 1, 0) }; Vec3f[] dys = { new Vec3f(0, 0, 1), new Vec3f(0, 1, 0), new Vec3f(1, 0, 0), new Vec3f(0, 0, 1), new Vec3f(0, 1, 0), new Vec3f(1, 0, 0) }; voxels.Apply((Vec4b c, Vec3i idx) => { if (c.w == 0) { // Voxel is empty //return; } // Check each face for (int i = 0; i < 6; ++i) { Quad quad = new Quad(); quad.normal = normals[i]; quad.dx = dxs[i]; quad.dy = dys[i]; quad.offset = new Vec3f(idx.x, idx.y, idx.z); quad.color = c; Vec3i neighbor = idx + new Vec3i(new Vec3f(quad.normal.x, quad.normal.y, quad.normal.z)); if (c.w == 0 || voxels.IsValid(neighbor) && voxels[neighbor].w > 0) { // Voxel face is covered continue; } if (quad.normal.x > 0 || quad.normal.y > 0 || quad.normal.z > 0) { quad.offset += quad.normal; } quads.Add(quad); } }); return(quads); }
// Makes a mesh from the given voxel set. public static void MakeMesh(VoxelSet <Vec4b> voxels, out Mesh mesh, out Material[] materials) { List <Quad> quads = new List <Quad>(); List <Quad> transparentQuads = new List <Quad>(); // Find all visible faces for (int z = 0; z < voxels.Size.z; ++z) { for (int y = 0; y < voxels.Size.y; ++y) { for (int x = 0; x < voxels.Size.x; ++x) { if (voxels[x, y, z].w <= 0) { // Empty; skip continue; } if (voxels[x, y, z].w < 255) { AddFaces(voxels, transparentQuads, new Vec3i(x, y, z)); } else { AddFaces(voxels, quads, new Vec3i(x, y, z)); } } } } mesh = new Mesh(); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; int subMeshCount = AddMeshGeometry(new List <Quad>[] { quads, transparentQuads }, mesh); mesh.subMeshCount = subMeshCount; materials = new Material[subMeshCount]; int submeshIdx = 0; int nextPointIdx = 0; // Opaque quads if (quads.Count > 0) { AddMeshIndices(quads.Count, mesh, nextPointIdx, submeshIdx); materials[submeshIdx] = new Material(Shader.Find("Voxel/PointQuads")); nextPointIdx += quads.Count; submeshIdx++; } // Transparent quads if (transparentQuads.Count > 0) { AddMeshIndices(transparentQuads.Count, mesh, nextPointIdx, submeshIdx); materials[submeshIdx] = new Material(Shader.Find("Voxel/PointQuadsTransparent")); nextPointIdx += transparentQuads.Count; submeshIdx++; } }
public static List <Box> MakeBoxes(VoxelSet <Vec4b> voxels) { return(MakeBoxes(voxels.Project(ToSolid))); }
public IPDMeshCreator(VoxelSet voxelSet, bool smartUpdate) : this(voxelSet) { this.smartUpdate = smartUpdate; }
static bool IsTransparent(VoxelSet <Vec4b> voxels, Vec3i idx) { return(voxels[idx].w > 0 && voxels[idx].w < 255); }