/* Copyright (c) 2008 Robert Adams * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of the copyright holder may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Portions of this code are: * Copyright (c) Contributors, http://idealistviewer.org * The basic logic of the extrusion code is based on the Idealist viewer code. * The Idealist viewer is licensed under the three clause BSD license. */ /* * MeshmerizerR class implments OpenMetaverse.Rendering.IRendering interface * using PrimMesher (http://forge.opensimulator.org/projects/primmesher). */ using System; using System.Collections.Generic; using System.Drawing; using System.IO; using OpenMetaverse.StructuredData; using LibreMetaverse.PrimMesher; using OMV = OpenMetaverse; using OMVR = OpenMetaverse.Rendering; namespace OpenMetaverse.Rendering { /// /// Meshing code based on the Idealist Viewer (20081213). /// [RendererName("MeshmerizerR")] public class MeshmerizerR : OMVR.IRendering { /// /// Generates a basic mesh structure from a primitive /// A 'SimpleMesh' is just the prim's overall shape with no material information. /// /// Primitive to generate the mesh from /// Level of detail to generate the mesh at /// The generated mesh or null on failure public OMVR.SimpleMesh GenerateSimpleMesh(OMV.Primitive prim, OMVR.DetailLevel lod) { LibreMetaverse.PrimMesher.PrimMesh newPrim = GeneratePrimMesh(prim, lod, false); if (newPrim == null) return null; SimpleMesh mesh = new SimpleMesh { Path = new Path(), Prim = prim, Profile = new Profile(), Vertices = new List(newPrim.coords.Count) }; foreach (Coord c in newPrim.coords) { mesh.Vertices.Add(new Vertex { Position = new Vector3(c.X, c.Y, c.Z) }); } mesh.Indices = new List(newPrim.faces.Count * 3); foreach (LibreMetaverse.PrimMesher.Face face in newPrim.faces) { mesh.Indices.Add((ushort)face.v1); mesh.Indices.Add((ushort)face.v2); mesh.Indices.Add((ushort)face.v3); } return mesh; } /// /// Generates a basic mesh structure from a sculpted primitive. /// 'SimpleMesh's have a single mesh and no faces or material information. /// /// Sculpted primitive to generate the mesh from /// Sculpt texture /// Level of detail to generate the mesh at /// The generated mesh or null on failure public SimpleMesh GenerateSimpleSculptMesh(Primitive prim, Bitmap sculptTexture, DetailLevel lod) { var faceted = GenerateFacetedSculptMesh(prim, sculptTexture, lod); if (faceted != null && faceted.Faces.Count == 1) { Face face = faceted.Faces[0]; SimpleMesh mesh = new SimpleMesh { Indices = face.Indices, Vertices = face.Vertices, Path = faceted.Path, Prim = prim, Profile = faceted.Profile }; mesh.Vertices = face.Vertices; return mesh; } return null; } /// /// Create a faceted mesh from prim shape parameters. /// Generates a a series of faces, each face containing a mesh and /// material metadata. /// A prim will turn into multiple faces with each being independent /// meshes and each having different material information. /// /// Primitive to generate the mesh from /// Level of detail to generate the mesh at /// The generated mesh public FacetedMesh GenerateFacetedMesh(Primitive prim, DetailLevel lod) { bool isSphere = ((OMV.ProfileCurve)(prim.PrimData.profileCurve & 0x07) == OMV.ProfileCurve.HalfCircle); LibreMetaverse.PrimMesher.PrimMesh newPrim = GeneratePrimMesh(prim, lod, true); if (newPrim == null) return null; // copy the vertex information into OMVR.IRendering structures var omvrmesh = new OMVR.FacetedMesh { Faces = new List(), Prim = prim, Profile = new OMVR.Profile { Faces = new List(), Positions = new List() }, Path = new OMVR.Path {Points = new List()} }; var indexer = newPrim.GetVertexIndexer(); for (int i = 0; i < indexer.numPrimFaces; i++) { OMVR.Face oface = new OMVR.Face { Vertices = new List(), Indices = new List(), TextureFace = prim.Textures.GetFace((uint) i) }; for (int j = 0; j < indexer.viewerVertices[i].Count; j++) { var vert = new OMVR.Vertex(); var m = indexer.viewerVertices[i][j]; vert.Position = new Vector3(m.v.X, m.v.Y, m.v.Z); vert.Normal = new Vector3(m.n.X, m.n.Y, m.n.Z); vert.TexCoord = new OMV.Vector2(m.uv.U, 1.0f - m.uv.V); oface.Vertices.Add(vert); } for (int j = 0; j < indexer.viewerPolygons[i].Count; j++) { var p = indexer.viewerPolygons[i][j]; // Skip "degenerate faces" where the same vertex appears twice in the same tri if (p.v1 == p.v2 || p.v1 == p.v2 || p.v2 == p.v3) continue; oface.Indices.Add((ushort)p.v1); oface.Indices.Add((ushort)p.v2); oface.Indices.Add((ushort)p.v3); } omvrmesh.Faces.Add(oface); } return omvrmesh; } /// /// Create a sculpty faceted mesh. The actual scuplt texture is fetched and passed to this /// routine since all the context for finding teh texture is elsewhere. /// /// The faceted mesh or null if can't do it public OMVR.FacetedMesh GenerateFacetedSculptMesh(Primitive prim, Bitmap scupltTexture, DetailLevel lod) { LibreMetaverse.PrimMesher.SculptMesh.SculptType smSculptType; switch (prim.Sculpt.Type) { case SculptType.Cylinder: smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.cylinder; break; case SculptType.Plane: smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.plane; break; case SculptType.Sphere: smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.sphere; break; case SculptType.Torus: smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.torus; break; default: smSculptType = LibreMetaverse.PrimMesher.SculptMesh.SculptType.plane; break; } // The lod for sculpties is the resolution of the texture passed. // The first guess is 1:1 then lower resolutions after that // int mesherLod = (int)Math.Sqrt(scupltTexture.Width * scupltTexture.Height); int mesherLod = 32; // number used in Idealist viewer switch (lod) { case OMVR.DetailLevel.Highest: break; case OMVR.DetailLevel.High: break; case OMVR.DetailLevel.Medium: mesherLod /= 2; break; case OMVR.DetailLevel.Low: mesherLod /= 4; break; } LibreMetaverse.PrimMesher.SculptMesh newMesh = new LibreMetaverse.PrimMesher.SculptMesh(scupltTexture, smSculptType, mesherLod, true, prim.Sculpt.Mirror, prim.Sculpt.Invert); int numPrimFaces = 1; // a scuplty has only one face // copy the vertex information into OMVR.IRendering structures FacetedMesh omvrmesh = new OMVR.FacetedMesh { Faces = new List(), Prim = prim, Profile = new OMVR.Profile { Faces = new List(), Positions = new List() }, Path = new OMVR.Path {Points = new List()} }; for (int ii = 0; ii < numPrimFaces; ii++) { Face oface = new OMVR.Face { Vertices = new List(), Indices = new List(), TextureFace = prim.Textures.GetFace((uint) ii) }; int faceVertices = newMesh.coords.Count; for (int j = 0; j < faceVertices; j++) { var vert = new OMVR.Vertex { Position = new Vector3(newMesh.coords[j].X, newMesh.coords[j].Y, newMesh.coords[j].Z), Normal = new Vector3(newMesh.normals[j].X, newMesh.normals[j].Y, newMesh.normals[j].Z), TexCoord = new Vector2(newMesh.uvs[j].U, newMesh.uvs[j].V) }; oface.Vertices.Add(vert); } for (int j = 0; j < newMesh.faces.Count; j++) { oface.Indices.Add((ushort)newMesh.faces[j].v1); oface.Indices.Add((ushort)newMesh.faces[j].v2); oface.Indices.Add((ushort)newMesh.faces[j].v3); } if (faceVertices > 0) { oface.TextureFace = prim.Textures.FaceTextures[ii] ?? prim.Textures.DefaultTexture; oface.ID = ii; omvrmesh.Faces.Add(oface); } } return omvrmesh; } /// /// Apply texture coordinate modifications from a /// to a list of vertices /// /// Vertex list to modify texture coordinates for /// Center-point of the face /// Face texture parameters public void TransformTexCoords(List vertices, OMV.Vector3 center, OMV.Primitive.TextureEntryFace teFace, Vector3 primScale) { // compute trig stuff up front float cosineAngle = (float)Math.Cos(teFace.Rotation); float sinAngle = (float)Math.Sin(teFace.Rotation); for (int ii = 0; ii < vertices.Count; ii++) { // tex coord comes to us as a number between zero and one // transform about the center of the texture Vertex vert = vertices[ii]; // aply planar tranforms to the UV first if applicable if (teFace.TexMapType == MappingType.Planar) { Vector3 binormal; float d = Vector3.Dot(vert.Normal, Vector3.UnitX); if (d >= 0.5f || d <= -0.5f) { binormal = Vector3.UnitY; if (vert.Normal.X < 0f) binormal *= -1; } else { binormal = Vector3.UnitX; if (vert.Normal.Y > 0f) binormal *= -1; } Vector3 tangent = binormal % vert.Normal; Vector3 scaledPos = vert.Position * primScale; vert.TexCoord.X = 1f + (Vector3.Dot(binormal, scaledPos) * 2f - 0.5f); vert.TexCoord.Y = -(Vector3.Dot(tangent, scaledPos) * 2f - 0.5f); } float repeatU = teFace.RepeatU; float repeatV = teFace.RepeatV; float tX = vert.TexCoord.X - 0.5f; float tY = vert.TexCoord.Y - 0.5f; vert.TexCoord.X = (tX * cosineAngle + tY * sinAngle) * repeatU + teFace.OffsetU + 0.5f; vert.TexCoord.Y = (-tX * sinAngle + tY * cosineAngle) * repeatV + teFace.OffsetV + 0.5f; vertices[ii] = vert; } } // The mesh reader code is organized so it can be used in several different ways: // // 1. Fetch the highest detail displayable mesh as a FacetedMesh: // var facetedMesh = GenerateFacetedMeshMesh(prim, meshData); // 2. Get the header, examine the submeshes available, and extract the part // desired (good if getting a different LOD of mesh): // OSDMap meshParts = UnpackMesh(meshData); // if (meshParts.ContainsKey("medium_lod")) // var facetedMesh = MeshSubMeshAsFacetedMesh(prim, meshParts["medium_lod"]): // 3. Get a simple mesh from one of the submeshes (good if just getting a physics version): // OSDMap meshParts = UnpackMesh(meshData); // OMV.Mesh flatMesh = MeshSubMeshAsSimpleMesh(prim, meshParts["physics_mesh"]); // // "physics_convex" is specially formatted so there is another routine to unpack // that section: // OSDMap meshParts = UnpackMesh(meshData); // if (meshParts.ContainsKey("physics_convex")) // OSMap hullPieces = MeshSubMeshAsConvexHulls(prim, meshParts["physics_convex"]): // // LL mesh format detailed at http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format /// /// Create a mesh faceted mesh from the compressed mesh data. /// This returns the highest LOD renderable version of the mesh. /// /// The actual mesh data is fetched and passed to this /// routine since all the context for finding the data is elsewhere. /// /// The faceted mesh or null if can't do it public OMVR.FacetedMesh GenerateFacetedMeshMesh(OMV.Primitive prim, byte[] meshData) { OMVR.FacetedMesh ret = null; OSDMap meshParts = UnpackMesh(meshData); if (meshParts != null) { byte[] meshBytes = null; string[] decreasingLOD = { "high_lod", "medium_lod", "low_lod", "lowest_lod" }; foreach (string partName in decreasingLOD) { if (meshParts.ContainsKey(partName)) { meshBytes = meshParts[partName]; break; } } if (meshBytes != null) { ret = MeshSubMeshAsFacetedMesh(prim, meshBytes); } } return ret; } // A version of GenerateFacetedMeshMesh that takes LOD spec so it's similar in calling convention of // the other Generate* methods. public OMVR.FacetedMesh GenerateFacetedMeshMesh(OMV.Primitive prim, byte[] meshData, OMVR.DetailLevel lod) { OMVR.FacetedMesh ret = null; string partName = null; switch (lod) { case OMVR.DetailLevel.Highest: partName = "high_lod"; break; case OMVR.DetailLevel.High: partName = "medium_lod"; break; case OMVR.DetailLevel.Medium: partName = "low_lod"; break; case OMVR.DetailLevel.Low: partName = "lowest_lod"; break; } if (partName != null) { OSDMap meshParts = UnpackMesh(meshData); if (meshParts != null) { if (meshParts.ContainsKey(partName)) { byte[] meshBytes = meshParts[partName]; if (meshBytes != null) { ret = MeshSubMeshAsFacetedMesh(prim, meshBytes); } } } } return ret; } // Convert a compressed submesh buffer into a FacetedMesh. public FacetedMesh MeshSubMeshAsFacetedMesh(OMV.Primitive prim, byte[] compressedMeshData) { FacetedMesh ret = null; OSD meshOSD = Helpers.DecompressOSD(compressedMeshData); OSDArray meshFaces = meshOSD as OSDArray; if (meshFaces != null) { ret = new FacetedMesh {Faces = new List()}; for (int faceIndex = 0; faceIndex < meshFaces.Count; faceIndex++) { AddSubMesh(prim, faceIndex, meshFaces[faceIndex], ref ret); } } return ret; } // Convert a compressed submesh buffer into a SimpleMesh. public OMVR.SimpleMesh MeshSubMeshAsSimpleMesh(OMV.Primitive prim, byte[] compressedMeshData) { OMVR.SimpleMesh ret = null; OSD meshOSD = Helpers.DecompressOSD(compressedMeshData); OSDArray meshFaces = meshOSD as OSDArray; if (meshOSD != null) { ret = new SimpleMesh(); foreach (OSD subMesh in meshFaces) { AddSubMesh(subMesh, ref ret); } } return ret; } public List> MeshSubMeshAsConvexHulls(OMV.Primitive prim, byte[] compressedMeshData) { List> hulls = new List>(); try { OSD convexBlockOsd = Helpers.DecompressOSD(compressedMeshData); if (convexBlockOsd is OSDMap) { OSDMap convexBlock = convexBlockOsd as OSDMap; Vector3 min = new Vector3(-0.5f, -0.5f, -0.5f); if (convexBlock.ContainsKey("Min")) min = convexBlock["Min"].AsVector3(); Vector3 max = new Vector3(0.5f, 0.5f, 0.5f); if (convexBlock.ContainsKey("Max")) max = convexBlock["Max"].AsVector3(); if (convexBlock.ContainsKey("BoundingVerts")) { byte[] boundingVertsBytes = convexBlock["BoundingVerts"].AsBinary(); var boundingHull = new List(); for (int i = 0; i < boundingVertsBytes.Length;) { ushort uX = Utils.BytesToUInt16(boundingVertsBytes, i); i += 2; ushort uY = Utils.BytesToUInt16(boundingVertsBytes, i); i += 2; ushort uZ = Utils.BytesToUInt16(boundingVertsBytes, i); i += 2; Vector3 pos = new Vector3( Utils.UInt16ToFloat(uX, min.X, max.X), Utils.UInt16ToFloat(uY, min.Y, max.Y), Utils.UInt16ToFloat(uZ, min.Z, max.Z) ); boundingHull.Add(pos); } List mBoundingHull = boundingHull; } if (convexBlock.ContainsKey("HullList")) { byte[] hullList = convexBlock["HullList"].AsBinary(); byte[] posBytes = convexBlock["Positions"].AsBinary(); int posNdx = 0; foreach (byte cnt in hullList) { int count = cnt == 0 ? 256 : cnt; List hull = new List(); for (int i = 0; i < count; i++) { ushort uX = Utils.BytesToUInt16(posBytes, posNdx); posNdx += 2; ushort uY = Utils.BytesToUInt16(posBytes, posNdx); posNdx += 2; ushort uZ = Utils.BytesToUInt16(posBytes, posNdx); posNdx += 2; Vector3 pos = new Vector3( Utils.UInt16ToFloat(uX, min.X, max.X), Utils.UInt16ToFloat(uY, min.Y, max.Y), Utils.UInt16ToFloat(uZ, min.Z, max.Z) ); hull.Add(pos); } hulls.Add(hull); } } } } catch (Exception) { // Logger.Log.WarnFormat("{0} exception decoding convex block: {1}", LogHeader, e); } return hulls; } // Add the submesh to the passed SimpleMesh private void AddSubMesh(OSD subMeshOsd, ref OMVR.SimpleMesh holdingMesh) { if (subMeshOsd is OSDMap) { OSDMap subMeshMap = (OSDMap) subMeshOsd; // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level // of Detail Blocks (maps) contain just a NoGeometry key to signal there is no // geometry for this submesh. if (subMeshMap.ContainsKey("NoGeometry") && ((OSDBoolean)subMeshMap["NoGeometry"])) return; holdingMesh.Vertices.AddRange(CollectVertices(subMeshMap)); holdingMesh.Indices.AddRange(CollectIndices(subMeshMap)); } } // Add the submesh to the passed FacetedMesh as a new face. private void AddSubMesh(OMV.Primitive prim, int faceIndex, OSD subMeshOsd, ref OMVR.FacetedMesh holdingMesh) { if (subMeshOsd is OSDMap) { OSDMap subMesh = (OSDMap) subMeshOsd; // As per http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format, some Mesh Level // of Detail Blocks (maps) contain just a NoGeometry key to signal there is no // geometry for this submesh. if (subMesh.ContainsKey("NoGeometry") && ((OSDBoolean)subMesh["NoGeometry"])) return; Face oface = new Face { ID = faceIndex, Vertices = new List(), Indices = new List(), TextureFace = prim.Textures.GetFace((uint) faceIndex) }; OSDMap subMeshMap = (OSDMap)subMeshOsd; oface.Vertices = CollectVertices(subMeshMap); oface.Indices = CollectIndices(subMeshMap); holdingMesh.Faces.Add(oface); } } private List CollectVertices(OSDMap subMeshMap) { List vertices = new List(); Vector3 posMax; Vector3 posMin; // If PositionDomain is not specified, the default is from -0.5 to 0.5 if (subMeshMap.ContainsKey("PositionDomain")) { posMax = ((OSDMap)subMeshMap["PositionDomain"])["Max"]; posMin = ((OSDMap)subMeshMap["PositionDomain"])["Min"]; } else { posMax = new Vector3(0.5f, 0.5f, 0.5f); posMin = new Vector3(-0.5f, -0.5f, -0.5f); } // Vertex positions byte[] posBytes = subMeshMap["Position"]; // Normals byte[] norBytes = null; if (subMeshMap.ContainsKey("Normal")) { norBytes = subMeshMap["Normal"]; } // UV texture map Vector2 texPosMax = Vector2.Zero; Vector2 texPosMin = Vector2.Zero; byte[] texBytes = null; if (subMeshMap.ContainsKey("TexCoord0")) { texBytes = subMeshMap["TexCoord0"]; texPosMax = ((OSDMap)subMeshMap["TexCoord0Domain"])["Max"]; texPosMin = ((OSDMap)subMeshMap["TexCoord0Domain"])["Min"]; } // Extract the vertex position data // If present normals and texture coordinates too for (int i = 0; i < posBytes.Length; i += 6) { ushort uX = Utils.BytesToUInt16(posBytes, i); ushort uY = Utils.BytesToUInt16(posBytes, i + 2); ushort uZ = Utils.BytesToUInt16(posBytes, i + 4); Vertex vx = new Vertex { Position = new Vector3( Utils.UInt16ToFloat(uX, posMin.X, posMax.X), Utils.UInt16ToFloat(uY, posMin.Y, posMax.Y), Utils.UInt16ToFloat(uZ, posMin.Z, posMax.Z)) }; if (norBytes != null && norBytes.Length >= i + 4) { ushort nX = Utils.BytesToUInt16(norBytes, i); ushort nY = Utils.BytesToUInt16(norBytes, i + 2); ushort nZ = Utils.BytesToUInt16(norBytes, i + 4); vx.Normal = new Vector3( Utils.UInt16ToFloat(nX, posMin.X, posMax.X), Utils.UInt16ToFloat(nY, posMin.Y, posMax.Y), Utils.UInt16ToFloat(nZ, posMin.Z, posMax.Z)); } var vertexIndexOffset = vertices.Count * 4; if (texBytes != null && texBytes.Length >= vertexIndexOffset + 4) { ushort tX = Utils.BytesToUInt16(texBytes, vertexIndexOffset); ushort tY = Utils.BytesToUInt16(texBytes, vertexIndexOffset + 2); vx.TexCoord = new Vector2( Utils.UInt16ToFloat(tX, texPosMin.X, texPosMax.X), Utils.UInt16ToFloat(tY, texPosMin.Y, texPosMax.Y)); } vertices. Add(vx); } return vertices; } private List CollectIndices(OSDMap subMeshMap) { List indices = new List(); byte[] triangleBytes = subMeshMap["TriangleList"]; for (int i = 0; i < triangleBytes.Length; i += 6) { ushort v1 = (ushort)(Utils.BytesToUInt16(triangleBytes, i)); indices.Add(v1); ushort v2 = (ushort)(Utils.BytesToUInt16(triangleBytes, i + 2)); indices.Add(v2); ushort v3 = (ushort)(Utils.BytesToUInt16(triangleBytes, i + 4)); indices.Add(v3); } return indices; } /// /// Decodes mesh asset. /// OSDMap of all of the submeshes in the mesh. The value of the submesh name /// is the uncompressed data for that mesh. /// The OSDMap is made up of the asset_header section (which includes a lot of stuff) /// plus each of the submeshes unpacked into compressed byte arrays. /// public OSDMap UnpackMesh(byte[] assetData) { OSDMap meshData = new OSDMap(); try { using (MemoryStream data = new MemoryStream(assetData)) { OSDMap header = (OSDMap)OSDParser.DeserializeLLSDBinary(data); meshData["asset_header"] = header; long start = data.Position; foreach(string partName in header.Keys) { if (header[partName].Type != OSDType.Map) { meshData[partName] = header[partName]; continue; } OSDMap partInfo = (OSDMap)header[partName]; if (partInfo["offset"] < 0 || partInfo["size"] == 0) { meshData[partName] = partInfo; continue; } byte[] part = new byte[partInfo["size"]]; Buffer.BlockCopy(assetData, partInfo["offset"] + (int)start, part, 0, part.Length); meshData[partName] = part; // meshData[partName] = Helpers.ZDecompressOSD(part); // Do decompression at unpack time } } } catch (Exception ex) { Logger.Log("Failed to decode mesh asset", Helpers.LogLevel.Error, ex); meshData = null; } return meshData; } // Local routine to create a mesh from prim parameters. // Collects parameters and calls PrimMesher to create all the faces of the prim. private LibreMetaverse.PrimMesher.PrimMesh GeneratePrimMesh(Primitive prim, DetailLevel lod, bool viewerMode) { Primitive.ConstructionData primData = prim.PrimData; int sides = 4; int hollowsides = 4; float profileBegin = primData.ProfileBegin; float profileEnd = primData.ProfileEnd; bool isSphere = false; if ((ProfileCurve)(primData.profileCurve & 0x07) == OMV.ProfileCurve.Circle) { switch (lod) { case OMVR.DetailLevel.Low: sides = 6; break; case OMVR.DetailLevel.Medium: sides = 12; break; default: sides = 24; break; } } else if ((ProfileCurve)(primData.profileCurve & 0x07) == OMV.ProfileCurve.EqualTriangle) sides = 3; else if ((ProfileCurve)(primData.profileCurve & 0x07) == OMV.ProfileCurve.HalfCircle) { // half circle, prim is a sphere isSphere = true; switch (lod) { case OMVR.DetailLevel.Low: sides = 6; break; case OMVR.DetailLevel.Medium: sides = 12; break; default: sides = 24; break; } profileBegin = 0.5f * profileBegin + 0.5f; profileEnd = 0.5f * profileEnd + 0.5f; } if (primData.ProfileHole == HoleType.Same) hollowsides = sides; else if (primData.ProfileHole == HoleType.Circle) { switch (lod) { case DetailLevel.Low: hollowsides = 6; break; case DetailLevel.Medium: hollowsides = 12; break; default: hollowsides = 24; break; } } else if (primData.ProfileHole == HoleType.Triangle) hollowsides = 3; PrimMesh newPrim = new PrimMesh(sides, profileBegin, profileEnd, primData.ProfileHollow, hollowsides) { viewerMode = viewerMode, sphereMode = isSphere, holeSizeX = primData.PathScaleX, holeSizeY = primData.PathScaleY, pathCutBegin = primData.PathBegin, pathCutEnd = primData.PathEnd, topShearX = primData.PathShearX, topShearY = primData.PathShearY, radius = primData.PathRadiusOffset, revolutions = primData.PathRevolutions, skew = primData.PathSkew }; switch (lod) { case DetailLevel.Low: newPrim.stepsPerRevolution = 6; break; case DetailLevel.Medium: newPrim.stepsPerRevolution = 12; break; default: newPrim.stepsPerRevolution = 24; break; } if (primData.PathCurve == PathCurve.Line || primData.PathCurve == PathCurve.Flexible) { newPrim.taperX = 1.0f - primData.PathScaleX; newPrim.taperY = 1.0f - primData.PathScaleY; newPrim.twistBegin = (int)(180 * primData.PathTwistBegin); newPrim.twistEnd = (int)(180 * primData.PathTwist); newPrim.ExtrudeLinear(); } else { newPrim.taperX = primData.PathTaperX; newPrim.taperY = primData.PathTaperY; newPrim.twistBegin = (int)(360 * primData.PathTwistBegin); newPrim.twistEnd = (int)(360 * primData.PathTwist); newPrim.ExtrudeCircular(); } return newPrim; } /// /// Method for generating mesh Face from a heightmap /// /// Two dimension array of floats containing height information /// Starting value for X /// Max value for X /// Starting value for Y /// Max value of Y /// public Face TerrainMesh(float[,] zMap, float xBegin, float xEnd, float yBegin, float yEnd) { SculptMesh newMesh = new SculptMesh(zMap, xBegin, xEnd, yBegin, yEnd, true); Face terrain = new Face(); int faceVertices = newMesh.coords.Count; terrain.Vertices = new List(faceVertices); terrain.Indices = new List(newMesh.faces.Count * 3); for (int j = 0; j < faceVertices; j++) { var vert = new OMVR.Vertex { Position = new Vector3(newMesh.coords[j].X, newMesh.coords[j].Y, newMesh.coords[j].Z), Normal = new Vector3(newMesh.normals[j].X, newMesh.normals[j].Y, newMesh.normals[j].Z), TexCoord = new Vector2(newMesh.uvs[j].U, newMesh.uvs[j].V) }; terrain.Vertices.Add(vert); } for (int j = 0; j < newMesh.faces.Count; j++) { terrain.Indices.Add((ushort)newMesh.faces[j].v1); terrain.Indices.Add((ushort)newMesh.faces[j].v2); terrain.Indices.Add((ushort)newMesh.faces[j].v3); } return terrain; } } }