Exemplo n.º 1
0
        /// <summary>
        /// Given a FacetedMesh, create a DisplayableRenderable (a list of RenderableMesh's with materials).
        /// This also creates underlying MesnInfo, MaterialInfo, and ImageInfo in the AssetFetcher.
        /// </summary>
        /// <param name="assetFetcher"></param>
        /// <param name="fmesh">The FacetedMesh to convert into Renderables</param>
        /// <param name="defaultTexture">If a face doesn't have a texture defined, use this one.
        /// This is an OMV.Primitive.TextureEntryFace that includes a lot of OpenSimulator material info.</param>
        /// <param name="primScale">Scaling for the base prim that is used when appliying any texture
        /// to the face (updating UV).</param>
        /// <returns></returns>
        private DisplayableRenderable ConvertFacetedMeshToDisplayable(IAssetFetcher assetFetcher, OMVR.FacetedMesh fmesh,
                                                                      OMV.Primitive.TextureEntryFace defaultTexture, OMV.Vector3 primScale)
        {
            RenderableMeshGroup ret = new RenderableMeshGroup();

            ret.meshes.AddRange(fmesh.Faces.Where(face => face.Indices.Count > 0).Select(face => {
                return(ConvertFaceToRenderableMesh(face, assetFetcher, defaultTexture, primScale));
            }));
            // ConvOAR.Globals.log.DebugFormat("{0} ConvertFacetedMeshToDisplayable: complete. numMeshes={1}", _logHeader, ret.meshes.Count);
            return(ret);
        }
Exemplo n.º 2
0
 public void Init()
 {
     _parms          = new ConvoarParams();
     _log            = new LoggerConsole();
     ConvOAR.Globals = new GlobalContext(_parms, _log);
     _assetService   = new MemAssetService();
     _converter      = new BConverterOS();
     _scene          = _converter.CreateScene(_assetService, "convoar-test");
     _assetFetcher   = new OSAssetFetcher(_assetService);
     OMV.UUID defaultTextureID = new OMV.UUID("179cdabd-398a-9b6b-1391-4dc333ba321f");
     _defaultTexture           = new OMV.Primitive.TextureEntryFace(null);
     _defaultTexture.TextureID = defaultTextureID;
 }
Exemplo n.º 3
0
        // Returns an ExtendedPrimGroup with a mesh for the passed heightmap.
        // Note that the returned EPG does not include any face information -- the caller must add a texture.
        public DisplayableRenderable MeshFromHeightMap(float[,] pHeightMap, int regionSizeX, int regionSizeY,
                                                       IAssetFetcher assetFetcher, OMV.Primitive.TextureEntryFace defaultTexture)
        {
            // OMVR.Face rawMesh = m_mesher.TerrainMesh(pHeightMap, 0, pHeightMap.GetLength(0)-1, 0, pHeightMap.GetLength(1)-1);
            ConvOAR.Globals.log.DebugFormat("{0} MeshFromHeightMap: heightmap=<{1},{2}>, regionSize=<{3},{4}>",
                                            _logHeader, pHeightMap.GetLength(0), pHeightMap.GetLength(1), regionSizeX, regionSizeY);
            OMVR.Face rawMesh = ConvoarTerrain.TerrainMesh(pHeightMap, (float)regionSizeX, (float)regionSizeY);

            RenderableMesh rm = ConvertFaceToRenderableMesh(rawMesh, assetFetcher, defaultTexture, new OMV.Vector3(1, 1, 1));

            RenderableMeshGroup rmg = new RenderableMeshGroup();

            rmg.meshes.Add(rm);

            return(rmg);
        }
Exemplo n.º 4
0
 public void Init()
 {
     _log            = new BLoggerConsole();
     _params         = new ConvoarParams();
     ConvOAR.Globals = new GlobalContext()
     {
         log   = _log,
         parms = _params
     };
     _assetService = new MemAssetService();
     _converter    = new OarConverter(_log, _params);
     _scene        = _converter.CreateScene(_assetService, "convoar-test");
     _assetManager = new AssetManager(_assetService, _log, _params.OutputDir);
     OMV.UUID defaultTextureID = new OMV.UUID("179cdabd-398a-9b6b-1391-4dc333ba321f");
     _defaultTexture = new OMV.Primitive.TextureEntryFace(null)
     {
         TextureID = defaultTextureID
     };
 }
Exemplo n.º 5
0
 public MaterialInfo(OMVR.Face face, OMV.Primitive.TextureEntryFace defaultTexture)
 {
     handle      = new EntityHandleUUID();
     faceTexture = face.TextureFace;
     if (faceTexture == null)
     {
         faceTexture = defaultTexture;
     }
     textureID = faceTexture.TextureID;
     if (faceTexture.RGBA.A != 1f)
     {
         fullAlpha = true;
     }
     RGBA     = faceTexture.RGBA;
     bump     = faceTexture.Bump;
     glow     = faceTexture.Glow;
     shiny    = faceTexture.Shiny;
     twoSided = ConvOAR.Globals.parms.P <bool>("DoubleSided");
 }
Exemplo n.º 6
0
        // Create a mesh for the terrain of the current scene
        public static BInstance CreateTerrainMesh(
            Scene scene,
            PrimToMesh assetMesher, IAssetFetcher assetFetcher)
        {
            ITerrainChannel terrainDef = scene.Heightmap;
            int             XSize      = terrainDef.Width;
            int             YSize      = terrainDef.Height;

            float[,] heightMap = new float[XSize, YSize];
            if (ConvOAR.Globals.parms.P <bool>("HalfRezTerrain"))
            {
                ConvOAR.Globals.log.DebugFormat("{0}: CreateTerrainMesh. creating half sized terrain sized <{1},{2}>", LogHeader, XSize / 2, YSize / 2);
                // Half resolution mesh that approximates the heightmap
                heightMap = new float[XSize / 2, YSize / 2];
                for (int xx = 0; xx < XSize; xx += 2)
                {
                    for (int yy = 0; yy < YSize; yy += 2)
                    {
                        float here = terrainDef.GetHeightAtXYZ(xx + 0, yy + 0, 26);
                        float ln   = terrainDef.GetHeightAtXYZ(xx + 1, yy + 0, 26);
                        float ll   = terrainDef.GetHeightAtXYZ(xx + 0, yy + 1, 26);
                        float lr   = terrainDef.GetHeightAtXYZ(xx + 1, yy + 1, 26);
                        heightMap[xx / 2, yy / 2] = (here + ln + ll + lr) / 4;
                    }
                }
            }
            else
            {
                ConvOAR.Globals.log.DebugFormat("{0}: CreateTerrainMesh. creating terrain sized <{1},{2}>", LogHeader, XSize / 2, YSize / 2);
                for (int xx = 0; xx < XSize; xx++)
                {
                    for (int yy = 0; yy < YSize; yy++)
                    {
                        heightMap[xx, yy] = terrainDef.GetHeightAtXYZ(xx, yy, 26);
                    }
                }
            }

            // Number found in RegionSettings.cs as DEFAULT_TERRAIN_TEXTURE_3
            OMV.UUID convoarID = new OMV.UUID(ConvOAR.Globals.parms.P <string>("ConvoarID"));

            OMV.UUID defaultTextureID = new OMV.UUID("179cdabd-398a-9b6b-1391-4dc333ba321f");
            OMV.Primitive.TextureEntryFace terrainFace = new OMV.Primitive.TextureEntryFace(null);
            terrainFace.TextureID = defaultTextureID;

            EntityHandleUUID terrainTextureHandle = new EntityHandleUUID();
            MaterialInfo     terrainMaterialInfo  = new MaterialInfo(terrainFace);

            if (ConvOAR.Globals.parms.P <bool>("CreateTerrainSplat"))
            {
                // Use the OpenSim maptile generator to create a texture for the terrain
                var terrainRenderer = new TexturedMapTileRenderer();
                Nini.Config.IConfigSource config = new Nini.Config.IniConfigSource();
                terrainRenderer.Initialise(scene, config);

                var mapbmp = new Bitmap(terrainDef.Width, terrainDef.Height,
                                        System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                terrainRenderer.TerrainToBitmap(mapbmp);

                // Place the newly created image into the Displayable caches
                ImageInfo terrainImageInfo = new ImageInfo();
                terrainImageInfo.handle    = terrainTextureHandle;
                terrainImageInfo.image     = mapbmp;
                terrainImageInfo.resizable = false; // terrain image resolution is not reduced
                assetFetcher.Images.Add(new BHashULong(terrainTextureHandle.GetHashCode()), terrainTextureHandle, terrainImageInfo);
                // Store the new image into the asset system so it can be read later.
                assetFetcher.StoreTextureImage(terrainTextureHandle, scene.Name + " Terrain", convoarID, mapbmp);
                // Link this image to the material
                terrainFace.TextureID = terrainTextureHandle.GetUUID();
            }
            else
            {
                // Use the default texture code for terrain
                terrainTextureHandle = new EntityHandleUUID(defaultTextureID);
                BHash terrainHash = new BHashULong(defaultTextureID.GetHashCode());
                assetFetcher.GetImageInfo(terrainHash, () => {
                    ImageInfo terrainImageInfo = new ImageInfo();
                    terrainImageInfo.handle    = terrainTextureHandle;
                    assetFetcher.FetchTextureAsImage(terrainTextureHandle)
                    .Then(img => {
                        terrainImageInfo.image = img;
                    });
                    return(terrainImageInfo);
                });
            }

            // The above has created a MaterialInfo for the terrain texture

            ConvOAR.Globals.log.DebugFormat("{0}: CreateTerrainMesh. calling MeshFromHeightMap", LogHeader);
            DisplayableRenderable terrainDisplayable = assetMesher.MeshFromHeightMap(heightMap,
                                                                                     terrainDef.Width, terrainDef.Height, assetFetcher, terrainFace);

            BInstance   terrainInstance = new BInstance();
            Displayable terrainDisp     = new Displayable(terrainDisplayable);

            terrainDisp.name               = "Terrain";
            terrainDisp.baseUUID           = OMV.UUID.Random();
            terrainInstance.Representation = terrainDisp;

            return(terrainInstance);
        }
Exemplo n.º 7
0
        /// <summary>
        /// Apply texture coordinate modifications from a
        /// <seealso cref="TextureEntryFace"/> to a list of vertices
        /// </summary>
        /// <param name="vertices">Vertex list to modify texture coordinates for</param>
        /// <param name="center">Center-point of the face</param>
        /// <param name="teFace">Face texture parameters</param>
        public void TransformTexCoords(List <OMVR.Vertex> 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;
            }
        }
Exemplo n.º 8
0
 public MaterialInfo(OMV.Primitive.TextureEntryFace defaultTexture)
 {
     faceTexture = new OMV.Primitive.TextureEntryFace(defaultTexture);
 }
        /// <summary>
        /// Apply texture coordinate modifications from a
        /// <seealso cref="TextureEntryFace"/> to a list of vertices
        /// </summary>
        /// <param name="vertices">Vertex list to modify texture coordinates for</param>
        /// <param name="center">Center-point of the face</param>
        /// <param name="teFace">Face texture parameters</param>
        public void TransformTexCoords(List <OMVR.Vertex> vertices, OMV.Vector3 center, OMV.Primitive.TextureEntryFace teFace)
        {
            // compute trig stuff up front
            float cosineAngle = (float)Math.Cos(teFace.Rotation);
            float sinAngle    = (float)Math.Sin(teFace.Rotation);

            // need a check for plainer vs default
            // just do default for now (I don't know what planar is)
            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
                OMVR.Vertex vert = vertices[ii];
                // repeat, offset, rotate
                // float tX = (vert.TexCoord.X - 0.5f) * teFace.RepeatU + teFace.OffsetU;
                // float tY = (vert.TexCoord.Y - 0.5f) * teFace.RepeatV - teFace.OffsetV;
                // vert.TexCoord.X = (tX * cosineAngle - tY * sinAngle) + 0.5f;
                // vert.TexCoord.Y = (tX * sinAngle + tY * cosineAngle) + 0.5f;
                float tX = vert.TexCoord.X - 0.5f;
                float tY = vert.TexCoord.Y - 0.5f;
                // rotate, scale, offset
                vert.TexCoord.X = (tX * cosineAngle - tY * sinAngle) * teFace.RepeatU - teFace.OffsetU + 0.5f;
                vert.TexCoord.Y = (tX * sinAngle + tY * cosineAngle) * teFace.RepeatV - teFace.OffsetV + 0.5f;
                vertices[ii]    = vert;
            }
            return;
        }
Exemplo n.º 10
0
        private void CreateNewPrim(LLEntityBase ent)
        {
            m_log.Log(LogLevel.DRENDERDETAIL, "Create new prim {0}", ent.Name.Name);
            // entity render info is kept per region. Get the region prim structure
            RegionRenderInfo rri = GetRegionRenderInfo(ent.RegionContext);
            IEntityAvatar    av;

            if (ent.TryGet <IEntityAvatar>(out av))
            {
                // if this entity is an avatar, just put it on the display list
                lock (rri.renderAvatarList) {
                    if (!rri.renderAvatarList.ContainsKey(av.LGID))
                    {
                        RenderableAvatar ravv = new RenderableAvatar();
                        ravv.avatar = av;
                        rri.renderAvatarList.Add(av.LGID, ravv);
                    }
                }
                return;
            }
            OMV.Primitive prim = ent.Prim;

            /* don't do foliage yet
             * if (prim.PrimData.PCode == OMV.PCode.Grass
             || prim.PrimData.PCode == OMV.PCode.Tree
             || prim.PrimData.PCode == OMV.PCode.NewTree) {
             || lock (renderFoliageList)
             ||     renderFoliageList[prim.LocalID] = prim;
             || return;
             ||}
             */

            RenderablePrim render = new RenderablePrim();

            render.Prim      = prim;
            render.acontext  = ent.AssetContext;
            render.rcontext  = ent.RegionContext;
            render.Position  = prim.Position;
            render.Rotation  = prim.Rotation;
            render.isVisible = true; // initially assume visible

            if (m_meshMaker == null)
            {
                m_meshMaker = new Renderer.Mesher.MeshmerizerR();
                m_meshMaker.ShouldScaleMesh = false;
            }

            if (prim.Sculpt != null)
            {
                EntityNameLL          textureEnt    = EntityNameLL.ConvertTextureWorldIDToEntityName(ent.AssetContext, prim.Sculpt.SculptTexture);
                System.Drawing.Bitmap textureBitmap = ent.AssetContext.GetTexture(textureEnt);
                if (textureBitmap == null)
                {
                    // the texture is not available. Request it.
                    // Note that we just call this routine again when it is available. Hope it's not recursive
                    ent.AssetContext.DoTextureLoad(textureEnt, AssetContextBase.AssetType.SculptieTexture,
                                                   delegate(string name, bool trans) {
                        CreateNewPrim(ent);
                        return;
                    }
                                                   );
                    return;
                }
                render.Mesh = m_meshMaker.GenerateSculptMesh(textureBitmap, prim, OMVR.DetailLevel.Medium);
                textureBitmap.Dispose();
            }
            else
            {
                render.Mesh = m_meshMaker.GenerateFacetedMesh(prim, OMVR.DetailLevel.High);
            }

            if (render.Mesh == null)
            {
                // mesh generation failed
                m_log.Log(LogLevel.DBADERROR, "FAILED MESH GENERATION: not generating new prim {0}", ent.Name.Name);
                return;
            }

            // Create a FaceData struct for each face that stores the 3D data
            // in an OpenGL friendly format
            for (int j = 0; j < render.Mesh.Faces.Count; j++)
            {
                OMVR.Face face = render.Mesh.Faces[j];
                FaceData  data = new FaceData();

                // Vertices for this face
                data.Vertices = new float[face.Vertices.Count * 3];
                for (int k = 0; k < face.Vertices.Count; k++)
                {
                    data.Vertices[k * 3 + 0] = face.Vertices[k].Position.X;
                    data.Vertices[k * 3 + 1] = face.Vertices[k].Position.Y;
                    data.Vertices[k * 3 + 2] = face.Vertices[k].Position.Z;
                }

                // Indices for this face
                data.Indices = face.Indices.ToArray();

                // Texture transform for this face
                OMV.Primitive.TextureEntryFace teFace = prim.Textures.GetFace((uint)j);
                m_meshMaker.TransformTexCoords(face.Vertices, face.Center, teFace);

                // Texcoords for this face
                data.TexCoords = new float[face.Vertices.Count * 2];
                for (int k = 0; k < face.Vertices.Count; k++)
                {
                    data.TexCoords[k * 2 + 0] = face.Vertices[k].TexCoord.X;
                    data.TexCoords[k * 2 + 1] = face.Vertices[k].TexCoord.Y;
                }

                data.Normals = new float[face.Vertices.Count * 3];
                for (int k = 0; k < face.Vertices.Count; k++)
                {
                    data.Normals[k * 3 + 0] = face.Vertices[k].Normal.X;
                    data.Normals[k * 3 + 1] = face.Vertices[k].Normal.Y;
                    data.Normals[k * 3 + 2] = face.Vertices[k].Normal.Z;
                }


                // m_log.Log(LogLevel.DRENDERDETAIL, "CreateNewPrim: v={0}, i={1}, t={2}",
                //     data.Vertices.GetLength(0), data.Indices.GetLength(0), data.TexCoords.GetLength(0));

                // Texture for this face
                if (teFace.TextureID != OMV.UUID.Zero &&
                    teFace.TextureID != OMV.Primitive.TextureEntry.WHITE_TEXTURE)
                {
                    lock (Textures) {
                        if (!Textures.ContainsKey(teFace.TextureID))
                        {
                            // temporarily add the entry to the table so we don't request it multiple times
                            Textures.Add(teFace.TextureID, new TextureInfo(0, true));
                            // We haven't constructed this image in OpenGL yet, get ahold of it
                            AssetContextBase.RequestTextureLoad(
                                EntityNameLL.ConvertTextureWorldIDToEntityName(ent.AssetContext, teFace.TextureID),
                                AssetContextBase.AssetType.Texture,
                                OnTextureDownloadFinished);
                        }
                    }
                }

                // Set the UserData for this face to our FaceData struct
                face.UserData        = data;
                render.Mesh.Faces[j] = face;
            }

            lock (rri.renderPrimList) {
                rri.renderPrimList[prim.LocalID] = render;
            }
        }
Exemplo n.º 11
0
        private RenderableMesh ConvertFaceToRenderableMesh(OMVR.Face face, IAssetFetcher assetFetcher,
                                                           OMV.Primitive.TextureEntryFace defaultTexture, OMV.Vector3 primScale)
        {
            RenderableMesh rmesh = new RenderableMesh();

            rmesh.num = face.ID;

            // Copy one face's mesh imformation from the FacetedMesh into a MeshInfo
            MeshInfo meshInfo = new MeshInfo {
                vertexs    = new List <OMVR.Vertex>(face.Vertices),
                indices    = face.Indices.ConvertAll(ii => (int)ii),
                faceCenter = face.Center,
                scale      = primScale
            };

            BConverterOS.LogBProgress("{0} ConvertFaceToRenderableMesh: faceId={1}, numVert={2}, numInd={3}",
                                      _logHeader, face.ID, meshInfo.vertexs.Count, meshInfo.indices.Count);

            if (!ConvOAR.Globals.parms.P <bool>("DisplayTimeScaling"))
            {
                if (ScaleMeshes(meshInfo, primScale))
                {
                    BConverterOS.LogBProgress("{0} ConvertFaceToRenderableMesh: scaled mesh to {1}",
                                              _logHeader, primScale);
                }
                meshInfo.scale = OMV.Vector3.One;
            }

            // Find or create the MaterialInfo for this face.
            MaterialInfo matInfo = new MaterialInfo(face, defaultTexture);

            if (matInfo.textureID != null &&
                matInfo.textureID != OMV.UUID.Zero &&
                matInfo.textureID != OMV.Primitive.TextureEntry.WHITE_TEXTURE)
            {
                // Textures/images use the UUID from OpenSim and the hash is just the hash of the UUID
                EntityHandleUUID textureHandle   = new EntityHandleUUID((OMV.UUID)matInfo.textureID);
                BHash            textureHash     = new BHashULong(textureHandle.GetUUID().GetHashCode());
                ImageInfo        lookupImageInfo = assetFetcher.GetImageInfo(textureHash, () => {
                    // The image is not in the cache yet so create an ImageInfo entry for it
                    // Note that image gets the same UUID as the OpenSim texture
                    ImageInfo imageInfo = new ImageInfo(textureHandle);
                    assetFetcher.FetchTextureAsImage(textureHandle)
                    .Then(img => {
                        imageInfo.SetImage(img);
                    })
                    .Catch(e => {
                        // Failure getting the image
                        ConvOAR.Globals.log.ErrorFormat("{0} Failure fetching texture. id={1}. {2}",
                                                        _logHeader, matInfo.textureID, e);
                        // Create a simple, single color image to fill in for the missing image
                        var fillInImage = new Bitmap(32, 32, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                        Color theColor  = Color.FromArgb(128, 202, 213, 170);       // 0x80CAB5AA
                        for (int xx = 0; xx < 32; xx++)
                        {
                            for (int yy = 0; yy < 32; yy++)
                            {
                                fillInImage.SetPixel(xx, yy, theColor);
                            }
                        }
                        imageInfo.SetImage(fillInImage);
                    });
                    imageInfo.imageIdentifier = (OMV.UUID)matInfo.textureID;
                    BConverterOS.LogBProgress("{0} ConvertFaceToRenderableMesh: create ImageInfo. hash={1}, id={2}",
                                              _logHeader, textureHash, imageInfo.handle);
                    return(imageInfo);
                });
                matInfo.image = lookupImageInfo;

                // Update the UV information for the texture mapping
                BConverterOS.LogBProgress("{0} ConvertFaceToRenderableMesh: Converting tex coords using {1} texture",
                                          _logHeader, face.TextureFace == null ? "default" : "face");
                _mesher.TransformTexCoords(meshInfo.vertexs, meshInfo.faceCenter,
                                           face.TextureFace == null ? defaultTexture : face.TextureFace, primScale);
            }

            // See that the material is in the cache
            MaterialInfo lookupMatInfo = assetFetcher.GetMaterialInfo(matInfo.GetBHash(), () => { return(matInfo); });

            rmesh.material = lookupMatInfo;

            // See that the mesh is in the cache
            MeshInfo lookupMeshInfo = assetFetcher.GetMeshInfo(meshInfo.GetBHash(true), () => { return(meshInfo); });

            rmesh.mesh = lookupMeshInfo;
            if (lookupMeshInfo.indices.Count == 0)      // DEBUG DEBUG
            {
                ConvOAR.Globals.log.ErrorFormat("{0} indices count of zero. rmesh={1}", _logHeader, rmesh.ToString());
            }   // DEBUG DEBUG

            BConverterOS.LogBProgress("{0} ConvertFaceToRenderableMesh: rmesh.mesh={1}, rmesh.material={2}",
                                      _logHeader, rmesh.mesh, rmesh.material);

            return(rmesh);
        }