/// <summary> /// Check if the mesh contains the requested arrays. /// </summary> /// <param name="channels">A flag containing the array types that a ProBuilder mesh stores.</param> /// <returns>True if all arrays in the flag are present, false if not.</returns> public bool HasArrays(MeshArrays channels) { bool missing = false; int vc = vertexCount; missing |= (channels & MeshArrays.Position) == MeshArrays.Position && m_Positions == null; missing |= (channels & MeshArrays.Normal) == MeshArrays.Normal && (m_Normals == null || m_Normals.Length != vc); missing |= (channels & MeshArrays.Texture0) == MeshArrays.Texture0 && (m_Textures0 == null || m_Textures0.Length != vc); missing |= (channels & MeshArrays.Texture2) == MeshArrays.Texture2 && (m_Textures2 == null || m_Textures2.Count != vc); missing |= (channels & MeshArrays.Texture3) == MeshArrays.Texture3 && (m_Textures3 == null || m_Textures3.Count != vc); missing |= (channels & MeshArrays.Color) == MeshArrays.Color && (m_Colors == null || m_Colors.Length != vc); missing |= (channels & MeshArrays.Tangent) == MeshArrays.Tangent && (m_Tangents == null || m_Tangents.Length != vc); // UV2 is a special case. It is not stored in ProBuilderMesh, does not necessarily match the vertex count, // at it has a cost to check. if ((channels & MeshArrays.Texture1) == MeshArrays.Texture1 && mesh != null) { #if UNITY_2019_3_OR_NEWER missing |= !mesh.HasVertexAttribute(VertexAttribute.TexCoord1); #else var m_Textures1 = m_Mesh.uv2; missing |= (m_Textures1 == null || m_Textures1.Length < 3); #endif } return(!missing); }
protected override void AddMeshData(Chunk chunk, Pos pos, Block block, ref MeshData meshData) { foreach (var dir in DirectionUtils.Directions) { if (!chunk.GetBlock(pos + dir).GetBlockType(vm).IsSolid(chunk, pos, block, DirectionUtils.Opposites[dir])) { // Side meshData.verts.AddRange(MeshArrays.VertexCubeFaces(pos - chunk.Pos, chunk.BlockSize, dir)); meshData.tris.AddRange(MeshArrays.TriCubeFaces(meshData.verts.Count)); if (dir == Direction.up) { // Foliage top texture meshData.uvs.AddRange(MeshArrays.QuadFaceTexture(foliageTextureSet.GetTexture(chunk, pos, dir))); // Add FoliageGrass meshData.verts.AddRange(MeshArrays.VertexFoliageFaces(pos - chunk.Pos, chunk.BlockSize)); meshData.tris.AddRange(MeshArrays.TriFoliageFaces(meshData.verts.Count)); meshData.uvs.AddRange(MeshArrays.QuadFaceTexture(foliageTextureSet.GetTexture(chunk, pos, Direction.south))); } else { // Normal sides meshData.uvs.AddRange(MeshArrays.QuadFaceTexture(textureSet.GetTexture(chunk, pos, dir))); } } } }
protected override void AddMeshData(Chunk chunk, Pos pos, Block block, ref MeshData meshData) { foreach (var dir in DirectionUtils.Directions) { if (!chunk.GetBlock(pos + dir).GetBlockType(vm).IsSolid(chunk, pos, block, DirectionUtils.Opposite(dir))) { meshData.verts.AddRange(MeshArrays.VertexCubeFaces(pos - chunk.pos, chunk.blockSize, dir)); meshData.tris.AddRange(MeshArrays.TriCubeFaces(meshData.verts.Count)); meshData.uvs.AddRange(MeshArrays.QuadFaceTexture(textureSet.GetTexture(chunk, pos, dir))); } } }
public bool Equals(Vertex other, MeshArrays mask) { if (ReferenceEquals(other, null)) { return(false); } return(((mask & MeshArrays.Position) != MeshArrays.Position || Math.Approx3(m_Position, other.m_Position)) && ((mask & MeshArrays.Color) != MeshArrays.Color || Math.ApproxC(m_Color, other.m_Color)) && ((mask & MeshArrays.Normal) != MeshArrays.Normal || Math.Approx3(m_Normal, other.m_Normal)) && ((mask & MeshArrays.Tangent) != MeshArrays.Tangent || Math.Approx4(m_Tangent, other.m_Tangent)) && ((mask & MeshArrays.Texture0) != MeshArrays.Texture0 || Math.Approx2(m_UV0, other.m_UV0)) && ((mask & MeshArrays.Texture1) != MeshArrays.Texture1 || Math.Approx2(m_UV2, other.m_UV2)) && ((mask & MeshArrays.Texture2) != MeshArrays.Texture2 || Math.Approx4(m_UV3, other.m_UV3)) && ((mask & MeshArrays.Texture3) != MeshArrays.Texture3 || Math.Approx4(m_UV4, other.m_UV4))); }
/// <summary> /// Compare two meshes for value-wise equality. /// </summary> /// <param name="expected"></param> /// <param name="result"></param> /// <returns></returns> public static bool AssertAreEqual(Mesh expected, Mesh result, MeshArrays compare = k_DefaultMeshArraysCompare, string message = null) { int vertexCount = expected.vertexCount; int subMeshCount = expected.subMeshCount; Assert.AreEqual(vertexCount, result.vertexCount, expected.name + " != " + result.name + " (submesh count)"); Assert.AreEqual(subMeshCount, result.subMeshCount, expected.name + " != " + result.name + " (submesh count)"); Vertex[] leftVertices = expected.GetVertices(); Vertex[] rightVertices = result.GetVertices(); for (int i = 0; i < vertexCount; i++) { Assert.True(leftVertices[i].Equals(rightVertices[i], compare), expected.name + " != " + result.name + "\nExpected\n" + leftVertices[i].ToString("F5") + "\n---\nReceived:\n" + rightVertices[i].ToString("F5")); } List <int> leftIndices = new List <int>(); List <int> rightIndices = new List <int>(); for (int i = 0; i < subMeshCount; i++) { uint indexCount = expected.GetIndexCount(i); Assert.AreEqual(expected.GetTopology(i), result.GetTopology(i)); Assert.AreEqual(indexCount, result.GetIndexCount(i)); expected.GetIndices(leftIndices, i); result.GetIndices(rightIndices, i); for (int n = 0; n < indexCount; n++) { Assert.AreEqual(leftIndices[n], rightIndices[n], message); } } return(true); }
/// <summary> /// Allocate and fill the requested attribute arrays. /// </summary> /// <remarks> /// If you are using this function to rebuild a mesh, use SetMesh instead. SetMesh handles setting null arrays where appropriate for you. /// </remarks> /// <seealso cref="SetMesh"/> /// <param name="vertices">The source vertices.</param> /// <param name="position">A new array of the vertex position values if requested by the attributes parameter, or null.</param> /// <param name="color">A new array of the vertex color values if requested by the attributes parameter, or null.</param> /// <param name="uv0">A new array of the vertex uv0 values if requested by the attributes parameter, or null.</param> /// <param name="normal">A new array of the vertex normal values if requested by the attributes parameter, or null.</param> /// <param name="tangent">A new array of the vertex tangent values if requested by the attributes parameter, or null.</param> /// <param name="uv2">A new array of the vertex uv2 values if requested by the attributes parameter, or null.</param> /// <param name="uv3">A new array of the vertex uv3 values if requested by the attributes parameter, or null.</param> /// <param name="uv4">A new array of the vertex uv4 values if requested by the attributes parameter, or null.</param> /// <param name="attributes">A flag with the MeshAttributes requested.</param> /// <seealso cref="HasArrays"/> public static void GetArrays( IList <CSG_Vertex> vertices, out Vector3[] position, out Color[] color, out Vector2[] uv0, out Vector3[] normal, out Vector4[] tangent, out Vector2[] uv2, out List <Vector4> uv3, out List <Vector4> uv4, MeshArrays attributes) { if (vertices == null) { throw new ArgumentNullException("vertices"); } int vc = vertices.Count; var first = vc < 1 ? new CSG_Vertex() : vertices[0]; bool hasPosition = ((attributes & MeshArrays.Position) == MeshArrays.Position) && first.hasPosition; bool hasColor = ((attributes & MeshArrays.Color) == MeshArrays.Color) && first.hasColor; bool hasUv0 = ((attributes & MeshArrays.Texture0) == MeshArrays.Texture0) && first.hasUV0; bool hasNormal = ((attributes & MeshArrays.Normal) == MeshArrays.Normal) && first.hasNormal; bool hasTangent = ((attributes & MeshArrays.Tangent) == MeshArrays.Tangent) && first.hasTangent; bool hasUv2 = ((attributes & MeshArrays.Texture1) == MeshArrays.Texture1) && first.hasUV2; bool hasUv3 = ((attributes & MeshArrays.Texture2) == MeshArrays.Texture2) && first.hasUV3; bool hasUv4 = ((attributes & MeshArrays.Texture3) == MeshArrays.Texture3) && first.hasUV4; position = hasPosition ? new Vector3[vc] : null; color = hasColor ? new Color[vc] : null; uv0 = hasUv0 ? new Vector2[vc] : null; normal = hasNormal ? new Vector3[vc] : null; tangent = hasTangent ? new Vector4[vc] : null; uv2 = hasUv2 ? new Vector2[vc] : null; uv3 = hasUv3 ? new List <Vector4>(vc) : null; uv4 = hasUv4 ? new List <Vector4>(vc) : null; for (int i = 0; i < vc; i++) { if (hasPosition) { position[i] = vertices[i].position; } if (hasColor) { color[i] = vertices[i].color; } if (hasUv0) { uv0[i] = vertices[i].uv0; } if (hasNormal) { normal[i] = vertices[i].normal; } if (hasTangent) { tangent[i] = vertices[i].tangent; } if (hasUv2) { uv2[i] = vertices[i].uv2; } if (hasUv3) { uv3.Add(vertices[i].uv3); } if (hasUv4) { uv4.Add(vertices[i].uv4); } } }
static string WriteObjContents(string name, IEnumerable <Model> models, Dictionary <Material, string> materialMap, ObjOptions options) { // Empty names in OBJ groups can crash some 3d programs (meshlab) if (string.IsNullOrEmpty(name)) { name = "ProBuilderModel"; } StringBuilder sb = new StringBuilder(); SemVer version; if (Version.TryGetPackageVersion(out version)) { sb.AppendLine("# ProBuilder " + version.MajorMinorPatch); } else { sb.AppendLine("# ProBuilder"); } sb.AppendLine("# https://unity3d.com/unity/features/worldbuilding/probuilder"); sb.AppendLine(string.Format("# {0}", System.DateTime.Now)); sb.AppendLine(); sb.AppendLine(string.Format("mtllib ./{0}.mtl", name.Replace(" ", "_"))); sb.AppendLine(string.Format("o {0}", name)); sb.AppendLine(); // obj orders indices 1 indexed int positionOffset = 1; int normalOffset = 1; int textureOffset = 1; bool reverseWinding = options.handedness == ObjOptions.Handedness.Right; float handedness = options.handedness == ObjOptions.Handedness.Left ? 1f : -1f; foreach (Model model in models) { int subMeshCount = model.submeshCount; Matrix4x4 matrix = options.applyTransforms ? model.matrix : Matrix4x4.identity; int vertexCount = model.vertexCount; Vector3[] positions; Color[] colors; Vector2[] textures0; Vector3[] normals; Vector4[] tangent; Vector2[] uv2; List <Vector4> uv3; List <Vector4> uv4; MeshArrays attribs = MeshArrays.Position | MeshArrays.Normal | MeshArrays.Texture0; if (options.vertexColors) { attribs = attribs | MeshArrays.Color; } Vertex.GetArrays(model.vertices, out positions, out colors, out textures0, out normals, out tangent, out uv2, out uv3, out uv4, attribs); // Can skip this entirely if handedness matches Unity & not applying transforms. // matrix is set to identity if not applying transforms. if (options.handedness != ObjOptions.Handedness.Left || options.applyTransforms) { for (int i = 0; i < vertexCount; i++) { if (positions != null) { positions[i] = matrix.MultiplyPoint3x4(positions[i]); positions[i].x *= handedness; } if (normals != null) { normals[i] = matrix.MultiplyVector(normals[i]); normals[i].x *= handedness; } } } sb.AppendLine(string.Format("g {0}", model.name)); Dictionary <int, int> positionIndexMap; var positionCount = AppendPositions(sb, positions, colors, true, options.vertexColors, out positionIndexMap); sb.AppendLine(); Dictionary <int, int> textureIndexMap; var textureCount = AppendArrayVec2(sb, textures0, "vt", true, out textureIndexMap); sb.AppendLine(); Dictionary <int, int> normalIndexMap; var normalCount = AppendArrayVec3(sb, normals, "vn", true, out normalIndexMap); sb.AppendLine(); // Material assignment for (int submeshIndex = 0; submeshIndex < subMeshCount; submeshIndex++) { Submesh submesh = model.submeshes[submeshIndex]; string materialName = ""; if (materialMap.TryGetValue(model.materials[submeshIndex], out materialName)) { sb.AppendLine(string.Format("usemtl {0}", materialName)); } else { sb.AppendLine(string.Format("usemtl {0}", "null")); } int[] indexes = submesh.m_Indexes; int inc = submesh.m_Topology == MeshTopology.Quads ? 4 : 3; int inc1 = inc - 1; int o0 = reverseWinding ? inc1 : 0; int o1 = reverseWinding ? inc1 - 1 : 1; int o2 = reverseWinding ? inc1 - 2 : 2; int o3 = reverseWinding ? inc1 - 3 : 3; for (int ff = 0; ff < indexes.Length; ff += inc) { int p0 = positionIndexMap[indexes[ff + o0]] + positionOffset; int p1 = positionIndexMap[indexes[ff + o1]] + positionOffset; int p2 = positionIndexMap[indexes[ff + o2]] + positionOffset; int t0 = textureIndexMap[indexes[ff + o0]] + textureOffset; int t1 = textureIndexMap[indexes[ff + o1]] + textureOffset; int t2 = textureIndexMap[indexes[ff + o2]] + textureOffset; int n0 = normalIndexMap[indexes[ff + o0]] + normalOffset; int n1 = normalIndexMap[indexes[ff + o1]] + normalOffset; int n2 = normalIndexMap[indexes[ff + o2]] + normalOffset; if (inc == 4) { int p3 = positionIndexMap[indexes[ff + o3]] + positionOffset; int n3 = normalIndexMap[indexes[ff + o3]] + normalOffset; int t3 = textureIndexMap[indexes[ff + o3]] + textureOffset; sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "f {0}/{4}/{8} {1}/{5}/{9} {2}/{6}/{10} {3}/{7}/{11}", p0, p1, p2, p3, t0, t1, t2, t3, n0, n1, n2, n3 )); } else { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "f {0}/{3}/{6} {1}/{4}/{7} {2}/{5}/{8}", p0, p1, p2, t0, t1, t2, n0, n1, n2 )); } } sb.AppendLine(); } positionOffset += positionCount; normalOffset += normalCount; textureOffset += textureCount; } return(sb.ToString()); }
/// <summary> /// Update the MeshFilter data for this chunk. /// </summary> /// <param name="chunksToSync">The chunks that need to have their mesh filter updated in the same frame as /// this. Null if no chunk sync is required.</param> private void UpdateMeshFilterJob(Vector2I[] chunksToSync) { // The chunk GameObject (this) is destroyed after the logical instance, so check that it still exists TerrainChunk chunk; if (chunksToSync == null) { if (TerrainSystem.Instance.Terrain.TryGetChunk(this.Chunk, out chunk)) { // Copy the mesh data into arrays var mesh = new MeshArrays( this.Chunk, chunk.Mesh.Data.Vertices.ToArray(), chunk.Mesh.Data.Normals.ToArray(), chunk.Mesh.Data.Indices.ToArray(), chunk.Mesh.Data.Light.ToArray()); // Update the mesh filter geometry GameScheduler.Instance.Invoke( () => { this.cMeshFilter.mesh.Clear(); this.cMeshFilter.mesh.vertices = mesh.Vertices; this.cMeshFilter.mesh.normals = mesh.Normals; this.cMeshFilter.mesh.triangles = mesh.Triangles; this.cMeshFilter.mesh.colors = mesh.Colors; }); } } else { var meshes = new MeshArrays[chunksToSync.Length]; for (int i = 0; i < meshes.Length; i++) { Vector2I chunkIndex = chunksToSync[i]; if (TerrainSystem.Instance.Terrain.TryGetChunk(chunkIndex, out chunk)) { // Copy the mesh data into arrays meshes[i] = new MeshArrays( chunkIndex, chunk.Mesh.Data.Vertices.ToArray(), chunk.Mesh.Data.Normals.ToArray(), chunk.Mesh.Data.Indices.ToArray(), chunk.Mesh.Data.Light.ToArray()); } } // Update the mesh filter geometry GameScheduler.Instance.Invoke( () => { foreach (MeshArrays mesh in meshes) { if (mesh == null) { continue; } Transform chunkTransform = this.transform.parent.FindChild(TerrainChunkComponent.GetLabel(mesh.Chunk)); if (chunkTransform == null) { continue; } TerrainChunkComponent cChunk = chunkTransform.GetComponent<TerrainChunkComponent>(); cChunk.cMeshFilter.mesh.Clear(); cChunk.cMeshFilter.mesh.vertices = mesh.Vertices; cChunk.cMeshFilter.mesh.normals = mesh.Normals; cChunk.cMeshFilter.mesh.triangles = mesh.Triangles; cChunk.cMeshFilter.mesh.colors = mesh.Colors; } }); } }
/// <summary> /// Find if a vertex attribute has been set. /// </summary> /// <param name="attribute">The attribute or attributes to test for.</param> /// <returns>True if this vertex has the specified attributes set, false if they are default values.</returns> public bool HasArrays(MeshArrays attribute) { return((m_Attributes & attribute) == attribute); }
static string WriteObjContents(string name, IEnumerable <Model> models, Dictionary <Material, string> materialMap, ObjOptions options) { // Empty names in OBJ groups can crash some 3d programs (meshlab) if (string.IsNullOrEmpty(name)) { name = "ProBuilderModel"; } StringBuilder sb = new StringBuilder(); sb.AppendLine("# Exported from ProBuilder"); sb.AppendLine("# http://www.procore3d.com/probuilder"); sb.AppendLine(string.Format("# {0}", System.DateTime.Now)); sb.AppendLine(); sb.AppendLine(string.Format("mtllib ./{0}.mtl", name.Replace(" ", "_"))); sb.AppendLine(string.Format("o {0}", name)); sb.AppendLine(); int triangleOffset = 1; bool reverseWinding = options.handedness == ObjOptions.Handedness.Right; float handedness = options.handedness == ObjOptions.Handedness.Left ? 1f : -1f; foreach (Model model in models) { int subMeshCount = model.submeshCount; Matrix4x4 matrix = options.applyTransforms ? model.matrix : Matrix4x4.identity; int vertexCount = model.vertexCount; Vector3[] positions; Color[] colors; Vector2[] textures0; Vector3[] normals; Vector4[] tangent; Vector2[] uv2; List <Vector4> uv3; List <Vector4> uv4; MeshArrays attribs = MeshArrays.Position | MeshArrays.Normal | MeshArrays.Texture0; if (options.vertexColors) { attribs = attribs | MeshArrays.Color; } Vertex.GetArrays(model.vertices, out positions, out colors, out textures0, out normals, out tangent, out uv2, out uv3, out uv4, attribs); // Can skip this entirely if handedness matches Unity & not applying transforms. // matrix is set to identity if not applying transforms. if (options.handedness != ObjOptions.Handedness.Left || options.applyTransforms) { for (int i = 0; i < vertexCount; i++) { if (positions != null) { positions[i] = matrix.MultiplyPoint3x4(positions[i]); positions[i].x *= handedness; } if (normals != null) { normals[i] = matrix.MultiplyVector(normals[i]); normals[i].x *= handedness; } } } sb.AppendLine(string.Format("g {0}", model.name)); if (options.vertexColors && colors != null && colors.Length == vertexCount) { for (int i = 0; i < vertexCount; i++) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "v {0} {1} {2} {3} {4} {5}", positions[i].x, positions[i].y, positions[i].z, colors[i].r, colors[i].g, colors[i].b)); } } else { for (int i = 0; i < vertexCount; i++) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "v {0} {1} {2}", positions[i].x, positions[i].y, positions[i].z)); } } sb.AppendLine(); for (int i = 0; normals != null && i < vertexCount; i++) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "vn {0} {1} {2}", normals[i].x, normals[i].y, normals[i].z)); } sb.AppendLine(); for (int i = 0; textures0 != null && i < vertexCount; i++) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "vt {0} {1}", textures0[i].x, textures0[i].y)); } sb.AppendLine(); // Material assignment for (int submeshIndex = 0; submeshIndex < subMeshCount; submeshIndex++) { Submesh submesh = model.submeshes[submeshIndex]; if (subMeshCount > 1) { sb.AppendLine(string.Format("g {0}_{1}", model.name, submeshIndex)); } else { sb.AppendLine(string.Format("g {0}", model.name)); } string materialName = ""; if (materialMap.TryGetValue(model.materials[submeshIndex], out materialName)) { sb.AppendLine(string.Format("usemtl {0}", materialName)); } else { sb.AppendLine(string.Format("usemtl {0}", "null")); } int[] indexes = submesh.m_Indexes; int inc = submesh.m_Topology == MeshTopology.Quads ? 4 : 3; for (int ff = 0; ff < indexes.Length; ff += inc) { if (inc == 4) { if (reverseWinding) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2} {3}/{3}/{3}", indexes[ff + 3] + triangleOffset, indexes[ff + 2] + triangleOffset, indexes[ff + 1] + triangleOffset, indexes[ff + 0] + triangleOffset)); } else { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2} {3}/{3}/{3}", indexes[ff + 0] + triangleOffset, indexes[ff + 1] + triangleOffset, indexes[ff + 2] + triangleOffset, indexes[ff + 3] + triangleOffset)); } } else { if (reverseWinding) { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", indexes[ff + 2] + triangleOffset, indexes[ff + 1] + triangleOffset, indexes[ff + 0] + triangleOffset)); } else { sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", indexes[ff + 0] + triangleOffset, indexes[ff + 1] + triangleOffset, indexes[ff + 2] + triangleOffset)); } } } sb.AppendLine(); } triangleOffset += vertexCount; } return(sb.ToString()); }