public void Apply(ref VoxelModel model, IProgressListener progress) { var m = model; progress?.Report("Analyzing structure"); var ext = new ExteriorAnalyzer() { Model = m }.Analyze(new ProgressMapper(progress, 0.0, 1.0 / 3.0, null)); progress?.Report("Creating shell"); var extexpanded = new DilationFilter() { Distance = Distance, DistanceType = DistanceType }.Apply(ext, new ProgressMapper(progress, 1.0 / 3.0, 1.0 / 3.0, null)); progress?.Report("Removing invisible voxels"); int d1 = m.Width; int d2 = m.Height; int d3 = m.Depth; int d = IsExteriorSolid ? 0 : Distance; for (int x = d; x < d1 - d; ++x) { for (int y = d; y < d2 - d; ++y) { for (int z = d; z < d3 - d; ++z) { if (!extexpanded[x, y, z]) { m[x, y, z] = VoxelModel.EmptyVoxel; } } } progress?.Report((double)(x - d) / (d1 - d * 2) * (1.0 / 3.0) + 2.0 / 3.0); } }
public void GenerateTextureAndUV(VoxelModel model, MeshSlices slices, out Bitmap bitmap) { throw new NotImplementedException(); }
public VoxelModel LoadVoxelModel(byte[] bytes, IProgressListener progress) { int w = Width, h = Height, d = Depth; var model = new VoxelModel(w, h, d); var r = new Random(); int pos = 0; progress?.Report("Reading voxels"); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int z = 0; uint color = 0; // fill with brownish color for (; z < d; ++z) { uint col = 0x284067; col ^= 0x070707 & (uint) r.Next(); model[x, y, z] = col; } z = 0; while (true) { int number_4byte_chunks = bytes[pos]; int top_color_start = bytes[pos + 1]; int top_color_end = bytes[pos + 2]; int bottom_color_start; int bottom_color_end; int len_top; int len_bottom; for (; z < top_color_start; ++z) { model[x, y, z] = VoxelModel.EmptyVoxel; } int colorpos = pos + 4; for (; z <= top_color_end; z++) { color = BitConverter.ToUInt32(bytes, colorpos); colorpos += 4; model[x, y, z] = color; } if (top_color_end == d - 2) { model[x, y, d - 1] = model[x, y, d - 2]; } len_bottom = top_color_end - top_color_start + 1; if (number_4byte_chunks == 0) { pos += 4 * (len_bottom + 1); break; } len_top = (number_4byte_chunks - 1) - len_bottom; pos += (int) bytes[pos] * 4; bottom_color_end = bytes[pos + 3]; bottom_color_start = bottom_color_end - len_top; for (z = bottom_color_start; z < bottom_color_end; z++) { color = BitConverter.ToUInt32(bytes, colorpos); colorpos += 4; model[x, y, z] = color; } if (bottom_color_end == d - 1) { model[x, y, d - 1] = model[x, y, d - 2]; } } // while (true) } // for x progress?.Report((double)(y + 1) / h); } // for y return model; }
public VoxelModel LoadVoxelModel(byte[] bytes, IProgressListener progress) { uint magic = BitConverter.ToUInt32(bytes, 0); // int version = BitConverter.ToInt32(bytes, 4); if (magic != FourCCs.Vox) { throw new System.IO.IOException("Invalid magic number (wrong file format?)."); } // FIXME: should we check the version number? var mainChunk = Chunk.LoadChunk(new ArraySegment<byte>(bytes, 8, bytes.Length - 8)); if (mainChunk.ChunkID != FourCCs.Main) { throw new System.IO.IOException("File is corrupted. Bad root chunk ID (should be MAIN)."); } Chunk sizeChunk = null; Chunk voxelChunk = null; Chunk paletteChunk = null; foreach (var chunk in mainChunk.Children) { if (chunk.ChunkID == FourCCs.Size) { sizeChunk = chunk; } else if (chunk.ChunkID == FourCCs.Xyzi) { voxelChunk = chunk; } else if (chunk.ChunkID == FourCCs.Rgba) { paletteChunk = chunk; } } if (sizeChunk == null) { throw new System.IO.IOException("File is corrupted. SIZE chunk was not found."); } if (voxelChunk == null) { throw new System.IO.IOException("File is corrupted. XYZI chunk was not found."); } if (paletteChunk == null) { throw new System.IO.IOException("VOX file without a palette (RGBA chunk) is currently not supported."); } // Parse size if (sizeChunk.Contents.Count < 12) { throw new System.IO.IOException("File is corrupted. SIZE chunk is too short."); } int dimX = BitConverter.ToInt32(sizeChunk.Contents.Array, sizeChunk.Contents.Offset); int dimY = BitConverter.ToInt32(sizeChunk.Contents.Array, sizeChunk.Contents.Offset + 4); int dimZ = BitConverter.ToInt32(sizeChunk.Contents.Array, sizeChunk.Contents.Offset + 8); if (dimX <= 0 || dimY <= 0 || dimZ <= 0) { throw new System.IO.IOException("File is corrupted. Bad dimensions."); } // Read palette var palette = new uint[256]; { var paldata = paletteChunk.Contents; if (paldata.Count < 256 * 4) { throw new System.IO.IOException("File is corrupted. RGBA chunk is too short."); } for (int i = 0; i < 255; ++i) { uint r = paldata.Array[paldata.Offset + i * 4]; uint g = paldata.Array[paldata.Offset + i * 4 + 1]; uint b = paldata.Array[paldata.Offset + i * 4 + 2]; palette[i + 1] = b | (g << 8) | (r << 16); } } // Read geometry var model = new VoxelModel(dimX, dimY, dimZ); int numVoxels = BitConverter.ToInt32(voxelChunk.Contents.Array, voxelChunk.Contents.Offset); if (voxelChunk.Contents.Count / 4 < numVoxels + 1) { throw new System.IO.IOException("File is corrupted. XYZI chunk is too short."); } progress?.Report("Reading voxels"); { var data = voxelChunk.Contents; int end = data.Offset + 4 + numVoxels * 4; for (int i = data.Offset + 4; i < end; i += 4) { int x = data.Array[i]; int y = data.Array[i + 1]; int z = data.Array[i + 2]; int color = data.Array[i + 3]; if (x < 0 || x >= dimX || y < 0 || y >= dimY || z < 0 || z >= dimZ) { throw new System.IO.IOException("File is corrupted. Voxel location is out of bounds."); } model[x, y, z] = palette[color]; if (((i & 8191) == 0)) { progress?.Report((double)(i - data.Offset) / (double)(numVoxels * 4)); } } } return model; }
public VoxelModel LoadVoxelModel(System.IO.Stream stream, out Vector3 pivot, IProgressListener progress) { var reader = new System.IO.BinaryReader(stream); { var buf = new byte[4]; if (stream.Read(buf, 0, 4) < 4) { throw new System.IO.IOException("Magic not read"); } if (buf[0] != 'K' || buf[1] != 'v' || buf[2] != 'x' || buf[3] != 'l') { throw new System.IO.IOException("Invalid magic"); } } int xsiz = reader.ReadInt32(); int ysiz = reader.ReadInt32(); int zsiz = reader.ReadInt32(); float xpivot = reader.ReadSingle(); float ypivot = reader.ReadSingle(); float zpivot = reader.ReadSingle(); int numblocks = reader.ReadInt32(); var blocks = new Kv6Block[numblocks]; progress?.Report("Reading voxels"); for (int i = 0; i < blocks.Length; ++i) { blocks[i].color = reader.ReadUInt32(); blocks[i].zpos = (int) reader.ReadUInt16(); reader.ReadUInt16(); // skip visFaces & lighting if (((i & 8191) == 0)) { progress?.Report((double)i / blocks.Length * 0.5); } } var xyoffset = new int[xsiz * ysiz]; // skip xoffset for (int i = 0; i < xsiz; ++i) { reader.ReadInt32(); } for (int i = 0; i < xyoffset.Length; ++i) { xyoffset[i] = (int) reader.ReadUInt16(); } progress?.Report("Placing voxels"); int pos = 0; var model = new VoxelModel(xsiz, ysiz, zsiz); for (int x = 0; x < xsiz; ++x) { for (int y = 0; y < ysiz; ++y) { int sb = xyoffset[x * ysiz + y]; for (int i = 0; i < sb; ++i) { var b = blocks[pos]; model[x, y, b.zpos] = b.color; pos += 1; } } progress?.Report((double)pos / blocks.Length * 0.5 + 0.5); } pivot = new Vector3(xpivot, ypivot, zpivot); return model; }
public void GenerateTextureAndUV(VoxelModel model, MeshSlices slices, out Bitmap bitmap, IProgressListener progress) { // Collect colors var colors = new List<int>(); // Marshal.Copy doesn't support uint[] lol int totalSlices = 0; foreach (var slicelist in slices) { totalSlices += slicelist.Length; } int sliceIndex = 0; progress?.Report("Collecting colors"); foreach (var slicelist in slices) { foreach (var slice in slicelist) { ++sliceIndex; progress?.Report((double)sliceIndex / totalSlices); var verts = slice.Positions; Axis3 paxis1, paxis2; GetPerpendicularAxises(slice.Axis, out paxis1, out paxis2); var pt = new IntVector3(); pt[slice.Axis] = slice.Layer; foreach (var face in slice.MeshSliceFaces) { var scoord = FindSliceCoordForFace(slice, face, slice.Axis, paxis1, paxis2); pt[paxis1] = scoord.X; pt[paxis2] = scoord.Y; colors.Add((int) (model[pt] | 0xff000000)); } } } // Determine the dimensions of the texture int texwidth = (int)Math.Ceiling(Math.Sqrt(colors.Count)); int texheight = (colors.Count + texwidth - 1) / texwidth; // And then create the texture bitmap = new Bitmap(texwidth * 2, texheight * 2, System.Drawing.Imaging.PixelFormat.Format32bppRgb); var bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb); System.Runtime.InteropServices.Marshal.Copy(Scale2x(colors.ToArray(), texwidth, texheight), 0, bmpdata.Scan0, colors.Count * 4); bitmap.UnlockBits(bmpdata); // Assign UVs int faceu = 0, facev = 0; sliceIndex = 0; progress?.Report("Assigning UV coordinates"); foreach (var slicelist in slices) { foreach (var slice in slicelist) { ++sliceIndex; progress?.Report((double)sliceIndex / totalSlices); var verts = slice.Positions; var uvs = slice.UVs = new Vector2[slice.Positions.Length]; Axis3 paxis1, paxis2; GetPerpendicularAxises(slice.Axis, out paxis1, out paxis2); foreach (var face in slice.MeshSliceFaces) { var scoord = FindSliceCoordForFace(slice, face, slice.Axis, paxis1, paxis2); var uv = new Vector2((float) (faceu << 1) + 0.5f, (float) (facev << 1) + 0.5f); for (int i = face.StartIndex; i < face.EndIndex; ++i) { var vt = verts[i]; float u = uv.X + (float)(vt[paxis1] - scoord.X); float v = uv.Y + (float)(vt[paxis2] - scoord.Y); uvs[i] = new Vector2(u, v); } faceu += 1; if (faceu == texwidth) { faceu = 0; facev += 1; } } } } }
static void Traverse(int x, int y, int z, VoxelModel m, bool[,,] ext, Queue<IntVector3> queue, uint adjcolor) { if (m.IsVoxelSolid(x, y, z) || ext[x, y, z]) { return; } m[x, y, z] = adjcolor; queue.Enqueue(new IntVector3(x, y, z)); }
public void Apply(ref VoxelModel model, IProgressListener progress) { var m = model; progress?.Report("Analyzing structure"); var ext = new ExteriorAnalyzer() { Model = m }.Analyze(new ProgressMapper(progress, 0, 1.0 / 3.0, null)); // Find the initial points (solid voxels adjacent to interior empty voxels) progress?.Report("Planting seeds"); var queue = new Queue<IntVector3>(); int width = m.Width, height = m.Height, depth = m.Depth; int numVoxelsToProcess = 0; for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { for (int z = 0; z < depth; ++z) { if (!ext[x, y, z] && !model.IsVoxelSolid(x, y, z)) { ++numVoxelsToProcess; } if (!model.IsVoxelSolid(x, y, z)) { continue; } queue.Enqueue(new IntVector3(x, y, z)); } } progress?.Report((double)(x + 1) / width * (1.0 / 3.0) + (1.0 / 3.0)); } numVoxelsToProcess += queue.Count; progress?.Report("Filling inside"); int numProcessed = 0; while (queue.Count > 0) { ++numProcessed; if ((numProcessed & 2047) == 0) { progress?.Report((double)numProcessed / numVoxelsToProcess * (1.0 / 3.0) + (2.0 / 3.0)); } var p = queue.Dequeue(); uint color = m[p]; if (p.X > 0) { Traverse(p.X - 1, p.Y, p.Z, m, ext, queue, color); } if (p.Y > 0) { Traverse(p.X, p.Y - 1, p.Z, m, ext, queue, color); } if (p.Z > 0) { Traverse(p.X, p.Y, p.Z - 1, m, ext, queue, color); } if (p.X < width - 1) { Traverse(p.X + 1, p.Y, p.Z, m, ext, queue, color); } if (p.Y < height - 1) { Traverse(p.X, p.Y + 1, p.Z, m, ext, queue, color); } if (p.Z < depth - 1) { Traverse(p.X, p.Y, p.Z + 1, m, ext, queue, color); } } }
public MeshSlices GenerateSlices(VoxelModel model, IProgressListener progress) { var slices = new MeshSlices(); var dims = model.Dimensions; int totalsteps = 2 * (model.Width + model.Height + model.Depth); int step = 0; progress?.Report("Generating slices"); for (int iaxis = 0; iaxis < 3; ++iaxis) { for (int iface = 0; iface < 2; ++iface) { var axis = (Axis3) iaxis; var paxis1 = (Axis3) ((iaxis + 1) % 3); var paxis2 = (Axis3) ((iaxis + 2) % 3); bool face = iface != 0; int dim0 = dims[axis]; int dim1 = dims[paxis1]; int dim2 = dims[paxis2]; var slicelist = new MeshSlice[dim0]; slices[axis, face] = slicelist; for (int layer = 0; layer < dim0; ++layer) { ++step; progress?.Report((double)step / totalsteps); var faces = new List<int>(); var ret = new List<IntVector3>(); var pt1 = new IntVector3(); pt1[axis] = layer; for (int x = 0; x < dim1; ++x) { pt1[paxis1] = x; for (int y = 0; y < dim2; ++y) { pt1[paxis2] = y; bool solid1 = model.IsVoxelSolid(pt1); var pt2 = pt1; bool solid2 = false; if (face) { pt2[axis] += 1; if (pt2[axis] < dim0) { solid2 = model.IsVoxelSolid(pt2); } } else { pt2[axis] -= 1; if (pt2[axis] >= 0) { solid2 = model.IsVoxelSolid(pt2); } } if (!solid1 || solid2) { continue; } // Create quad var qpt1 = pt1; if (face) { qpt1[axis] += 1; qpt1[paxis2] += 1; } var qpt2 = qpt1; qpt2[paxis1] += 1; var qpt3 = qpt1; var qpt4 = qpt2; if (face) { qpt3[paxis2] -= 1; qpt4[paxis2] -= 1; } else { qpt3[paxis2] += 1; qpt4[paxis2] += 1; } // Emit polygons faces.Add(ret.Count); ret.Add(qpt3); ret.Add(qpt4); ret.Add(qpt2); ret.Add(qpt1); } // for y } // for x slicelist[layer] = new MeshSlice() { Positions = ret.ToArray(), Faces = faces.ToArray(), Face = face, Axis = axis, Layer = layer }; } // for slice } // iface } // iaxis return slices; }