private FaceDescriptor makeFaceDescriptor(VoxelTypeID typeId, Direction originalDirection, LightValue lightValue, VoxelRotation rotation = default) { if (typeId == VoxelTypeID.AIR_ID) { return(nullFace); } var faceDirection = originalDirection; if (!rotation.isBlank) { ///Face direction needs to be the direction index of the face currently pointing in the original ///direction. Therefore it is the direction such that applying the rotation gives the original direction. faceDirection = directionRotator.GetDirectionBeforeRotation(originalDirection, rotation); } FaceDescriptor faceDescriptor = new FaceDescriptor() { typeId = typeId, faceDirection = faceDirection, rotation = rotation, lightValue = lightValue }; return(faceDescriptor); }
public FaceDescriptor GetFace(BoxFace face) { var r = new FaceDescriptor { VertexCount = 4, IndexCount = 6 }; // Todo: Put the underlying vertex data in the right order and this is just some multiplies. switch (face) { case BoxFace.Top: r.IndexOffset = 12; r.VertexOffset = 8; return(r); case BoxFace.Bottom: r.IndexOffset = 18; r.VertexOffset = 12; return(r); case BoxFace.Left: r.IndexOffset = 24; r.VertexOffset = 16; return(r); case BoxFace.Right: r.IndexOffset = 30; r.VertexOffset = 20; return(r); case BoxFace.Front: r.IndexOffset = 0; r.VertexOffset = 0; return(r); case BoxFace.Back: r.IndexOffset = 6; r.VertexOffset = 4; return(r); } r.IndexOffset = 0; r.VertexOffset = 0; return(r); }
private bool IncludeFace(FaceDescriptor thisFace, FaceDescriptor oppositeFace) { if (thisFace.typeId == oppositeFace.typeId) { return(false);//Dont include faces between voxels of the same type } if (oppositeFace.typeId == VoxelTypeID.AIR_ID) { //Include the face if the opposite voxel is air return(true); } var meshID = data.meshDatabase.voxelTypeToMeshTypeMap[oppositeFace.typeId]; var meshRange = data.meshDatabase.meshTypeRanges[meshID]; var faceIsSolid = data.meshDatabase.isFaceSolid[meshRange.start + (int)oppositeFace.faceDirection]; //Exclude this face if opposite face is solid return(!faceIsSolid); }
public DoLater(FaceDescriptor currentMaskValue, int3 workingCoordinates, byte primaryAxis, byte secondaryAxis, byte tertiaryAxis, int width, int height, bool flip, bool flipNormals) { this.currentMaskValue = currentMaskValue; this.workingCoordinates = workingCoordinates; this.primaryAxis = primaryAxis; this.secondaryAxis = secondaryAxis; this.tertiaryAxis = tertiaryAxis; this.width = width; this.height = height; this.flip = flip; this.flipNormals = flipNormals; }
public void Execute() { if (data.dimensions.x != data.dimensions.y || data.dimensions.y != data.dimensions.z) { throw new ArgumentException($"Greedy meshing does not support non-cubic chunk dimensions. " + $"x,y,z of chunk dimensions must be identical to use greedy meshing."); } //initialise locals nullFace = default; dxdy = data.dimensions.x * data.dimensions.y; runTracker = new MaterialRunTracker(); //Initialise rotated voxels map from list for (int i = 0; i < data.rotatedVoxels.Length; i++) { var item = data.rotatedVoxels[i]; rotatedVoxelsMap.Add(item.flatIndex, item.rotation); } int size = data.dimensions.x; //Sweep over 3-axes for (byte axis = 0; axis < 3; axis++) { //Secondary axis is used for width, tertiary axis is used for height byte secondaryAxis; byte tertiaryAxis; switch (axis) { case 0: //x axis primary secondaryAxis = 2; tertiaryAxis = 1; break; case 1: //y axis primary secondaryAxis = 0; tertiaryAxis = 2; break; case 2: //z axis primary secondaryAxis = 0; tertiaryAxis = 1; break; default: throw new Exception("axis not valid"); } int3 workingCoordinates = new int3(); int3 axisVector = new int3(); axisVector[axis] = 1; //Compute the direction we are currently meshing //Direction currentMeshDirection; Direction positiveAxisDirection; Direction negativeAxisDirection; if (axis == 0) { positiveAxisDirection = Direction.east; negativeAxisDirection = Direction.west; } else if (axis == 1) { positiveAxisDirection = Direction.up; negativeAxisDirection = Direction.down; } else { positiveAxisDirection = Direction.north; negativeAxisDirection = Direction.south; } //Sweep through planes in the direction of the current axis for (workingCoordinates[axis] = -1; workingCoordinates[axis] < size;) { int maskIndex = 0; // Compute the mask for this plane ///NOTE assumes and requires that both masks are clear (full of nullface) ///before this point for (workingCoordinates[tertiaryAxis] = 0; workingCoordinates[tertiaryAxis] < size; ++workingCoordinates[tertiaryAxis]) { for (workingCoordinates[secondaryAxis] = 0; workingCoordinates[secondaryAxis] < size; ++workingCoordinates[secondaryAxis], ++maskIndex) { //Face in the positive axis direction bool positiveFaceInThisChunk = (workingCoordinates[axis] >= 0); FaceDescriptor positiveFace = positiveFaceInThisChunk ? maskData(workingCoordinates, positiveAxisDirection, workingCoordinates + axisVector) : GetFaceInNeighbour(workingCoordinates, negativeAxisDirection, positiveAxisDirection, axis, workingCoordinates + axisVector); //Face in the negative axis direction bool negativeFaceInThisChunk = (workingCoordinates[axis] < size - 1); FaceDescriptor negativeFace = negativeFaceInThisChunk ? maskData(workingCoordinates + axisVector, negativeAxisDirection, workingCoordinates) : GetFaceInNeighbour(workingCoordinates + axisVector, positiveAxisDirection, negativeAxisDirection, axis, workingCoordinates); if (positiveFaceInThisChunk && IncludeFace(positiveFace, negativeFace)) { maskPositive[maskIndex] = positiveFace; } if (negativeFaceInThisChunk && IncludeFace(negativeFace, positiveFace)) { maskNegative[maskIndex] = negativeFace; } } } // Step forward one slice along the axis ++workingCoordinates[axis]; //Run meshing twice, once in the positive direction, then again in the negative direction for (int maskSelector = 0; maskSelector <= 1; maskSelector++) { bool isPositive = (maskSelector == 0) ? true : false; var currentMask = (isPositive) ? maskPositive : maskNegative; ///The X and Y axes need to flip quads for negative faces, ///the z axis needs to flip quads for positive faces bool flip = (axis != 2) ? !isPositive : isPositive; //Normals must be flipped for negative faces bool flipNormals = !isPositive; // Generate mesh for current mask using lexicographic ordering maskIndex = 0; for (int j = 0; j < size; ++j) { for (int i = 0; i < size;) { var currentMaskValue = currentMask[maskIndex]; // Compute width as maximal width such that the mask value does not change int width; for (width = 1; currentMaskValue.Equals(currentMask[maskIndex + width]) && i + width < size; ++width) { } // Compute height int height; bool heightDone = false; // Increase height up to no more than the size for (height = 1; j + height < size; ++height) { //Check that the mask value does not change along the row for (int k = 0; k < width; ++k) { if (!currentMaskValue.Equals(currentMask[maskIndex + k + height * size])) { //If the mask value has changed, we can go no further heightDone = true; break; } } if (heightDone) { break; } } if (!currentMaskValue.Equals(nullFace)) { workingCoordinates[secondaryAxis] = i; workingCoordinates[tertiaryAxis] = j; if (data.voxelTypeDatabase.voxelTypeToIsPassableMap[currentMaskValue.typeId]) { nonCollidableQueue.Add(new DoLater(currentMaskValue, workingCoordinates, axis, secondaryAxis, tertiaryAxis, width, height, flip, flipNormals)); } else { ProcessSection(currentMaskValue, workingCoordinates, axis, secondaryAxis, tertiaryAxis, width, height, flip, flipNormals); } } /// Zero-out mask for this section /// This is necessary to prevent the same area being meshed multiple times, /// and also to ensure the mask is clear for the next pass for (int l = 0; l < height; ++l) { for (int k = 0; k < width; ++k) { currentMask[maskIndex + k + l * size] = nullFace; } } // Increment counters and continue i += width; maskIndex += width; } } } } } //Record length of collidable mesh section data.collisionSubmesh.Record(data.vertices.Length, data.allTriangleIndices.Length, data.materialRuns.Length); //End collidable run runTracker.EndRun(data.materialRuns, data.allTriangleIndices); //Process non collidable sections for (int j = 0; j < nonCollidableQueue.Length; j++) { var item = nonCollidableQueue[j]; ProcessSection(item.currentMaskValue, item.workingCoordinates, item.primaryAxis, item.secondaryAxis, item.tertiaryAxis, item.width, item.height, item.flip, item.flipNormals); } //End non collidable run runTracker.EndRun(data.materialRuns, data.allTriangleIndices); }
/// <summary> /// Compute the properties of the quad with given parameters, /// and add it to the mesh. /// </summary> /// <param name="currentMaskValue"></param> /// <param name="workingCoordinates"></param> /// <param name="du"></param> /// <param name="dv"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="flip"></param> private void ProcessSection(FaceDescriptor currentMaskValue, int3 workingCoordinates, byte primaryAxis, byte secondaryAxis, byte tertiaryAxis, int width, int height, bool flip, bool flipNormals) { // Add quad if mask value not null var meshID = data.meshDatabase.voxelTypeToMeshTypeMap[currentMaskValue.typeId]; var meshRange = data.meshDatabase.meshTypeRanges[meshID]; var usedNodesSlice = data.meshDatabase.nodesUsedByFaces[meshRange.start + (int)currentMaskValue.faceDirection]; var uvStart = data.voxelTypeDatabase.voxelTypeToZIndicesRangeMap[currentMaskValue.typeId].start; var uvZ = data.voxelTypeDatabase.zIndicesPerFace[uvStart + (int)currentMaskValue.faceDirection]; var materialId = data.meshDatabase.voxelTypeToMaterialIDMap[currentMaskValue.typeId]; //Update material runs runTracker.Update(materialId, data.materialRuns, data.allTriangleIndices); //int3 du = new int3(); //int3 dv = new int3(); //du[secondaryAxis] = width; //dv[tertiaryAxis] = height; //float3 bl = workingCoordinates; //float3 tr = workingCoordinates + du + dv; //float3 br = workingCoordinates + du; //float3 tl = workingCoordinates + dv; Node nodebl = data.meshDatabase.allMeshNodes[usedNodesSlice.start]; Node nodetr = data.meshDatabase.allMeshNodes[usedNodesSlice.start + 1]; Node nodebr = data.meshDatabase.allMeshNodes[usedNodesSlice.start + 2]; Node nodetl = data.meshDatabase.allMeshNodes[usedNodesSlice.start + 3]; //Apply rotation if (!currentMaskValue.rotation.isBlank) { var rotationQuat = directionRotator.GetRotationQuat(currentMaskValue.rotation); nodebl = adjustForRotation(nodebl, rotationQuat); nodetr = adjustForRotation(nodetr, rotationQuat); nodebr = adjustForRotation(nodebr, rotationQuat); nodetl = adjustForRotation(nodetl, rotationQuat); } int3 scale = new int3(); scale[secondaryAxis] = width; scale[tertiaryAxis] = height; //Apply scaling nodebl.vertex *= scale; nodetr.vertex *= scale; nodebr.vertex *= scale; nodetl.vertex *= scale; //Apply translation nodebl.vertex += workingCoordinates; nodetr.vertex += workingCoordinates; nodebr.vertex += workingCoordinates; nodetl.vertex += workingCoordinates; int3 normal = new int3(); normal[primaryAxis] = 1; ///NOTE for negative faces, the UVs are already flipped in the face definition ///Therefore, flip just the vertices and normals when necessary //if (flip) //{ // Swap(ref nodebl, ref nodebr); // Swap(ref nodetr, ref nodetl); //} if (flipNormals) { normal *= -1; } nodebl.normal = normal; nodetr.normal = normal; nodebr.normal = normal; nodetl.normal = normal; //nodebl.vertex = bl; //nodetr.vertex = tr; //nodebr.vertex = br; //nodetl.vertex = tl; var secondaryDif = math.lengthsq(nodetl.vertex - nodebl.vertex); var tertiaryDif = math.lengthsq(nodebr.vertex - nodebl.vertex); int2 uvScale = new int2(width, height); ///Calculate the correct uv scale based on which axis is longer ///(if they are equal it doesn't matter). ///This is needed because it is difficult to track the correct orientation ///of width and height after rotation if (!currentMaskValue.rotation.isBlank) { if (width > height) { if (secondaryDif > tertiaryDif) { uvScale = new int2(height, width); } } else { if (secondaryDif < tertiaryDif) { uvScale = new int2(height, width); } } } //Scale the UVs //nodebl.uv *= uvScale;//Don't need to scale the bottom left, as its UV should be 0,0 nodetr.uv *= uvScale; nodebr.uv *= uvScale; nodetl.uv *= uvScale; bool makeBackface = data.meshDatabase.meshIdToIncludeBackfacesMap[meshID]; Color lightForFace = new Color(); if (data.includeLighting) { lightForFace = currentMaskValue.lightValue.ToVertexColour(); } AddQuad(nodebl, nodetr, nodebr, nodetl, uvZ, lightForFace, makeBackface); }