/// <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); }
/// <summary> /// Create and return a set of meshes/materials that make the passed SOP. /// This just deals the making a mesh from the SOP and getting the material/texture of the meshes /// into the caches. /// </summary> // Returns 'null' if the SOG/SOP could not be converted into a displayable public IPromise <Displayable> CreateMeshResource(SceneObjectGroup sog, SceneObjectPart sop, OMV.Primitive prim, IAssetFetcher assetFetcher, OMVR.DetailLevel lod) { var prom = new Promise <Displayable>(); try { if (prim.Sculpt != null) { if (prim.Sculpt.Type == OMV.SculptType.Mesh) { BConverterOS.LogBProgress("{0}: CreateMeshResource: creating mesh", _logHeader); ConvOAR.Globals.stats.numMeshAssets++; MeshFromPrimMeshData(sog, sop, prim, assetFetcher, lod) .Then(dispable => { prom.Resolve(new Displayable(dispable, sop)); }, e => { // prom.Reject(e); prom.Resolve(null); }); } else { BConverterOS.LogBProgress("{0}: CreateMeshResource: creating sculpty", _logHeader); ConvOAR.Globals.stats.numSculpties++; MeshFromPrimSculptData(sog, sop, prim, assetFetcher, lod) .Then(dispable => { prom.Resolve(new Displayable(dispable, sop)); }, e => { // prom.Reject(e); prom.Resolve(null); }); } } else { BConverterOS.LogBProgress("{0}: CreateMeshResource: creating primshape", _logHeader); ConvOAR.Globals.stats.numSimplePrims++; MeshFromPrimShapeData(sog, sop, prim, assetFetcher, lod) .Then(dispable => { BConverterOS.LogBProgress("{0} CreateMeshResource: prim created", _logHeader); prom.Resolve(new Displayable(dispable, sop)); }, e => { // prom.Reject(e); prom.Resolve(null); }); } } catch (Exception e) { prom.Reject(e); } return(prom); }
// 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); }
public bool LoadOAR(IAssetService assetService, IAssetFetcher assetFetcher, BSceneLoadedCallback loadedCallback) { bool ret = false; BConverterOS converter = new BConverterOS(); converter.ConvertOarToScene(assetService, assetFetcher) .Catch(e => { ConvOAR.Globals.log.ErrorFormat("{0} LoadOAR exception: {1}", _logHeader, e); throw (e); }) .Then(bScene => { loadedCallback(bScene); }); return(ret); }
// 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); }
public Promise <BScene> ConvertOarToScene(IAssetService assetService, IAssetFetcher assetFetcher) { Promise <BScene> prom = new Promise <BScene>(); // Assemble all the parameters that loadoar takes and uses Dictionary <string, object> options = new Dictionary <string, object>(); // options.Add("merge", false); options.Add("displacement", ConvOAR.Globals.parms.P <OMV.Vector3>("Displacement")); string optRotation = ConvOAR.Globals.parms.P <string>("Rotation"); if (optRotation != null) { options.Add("rotation", float.Parse(optRotation, System.Threading.Thread.CurrentThread.CurrentCulture)); } // options.Add("default-user", OMV.UUID.Random()); // if (optSkipAssets != null) options.Add('skipAssets', true); // if (optForceTerrain != null) options.Add("force-terrain", true); // if (optNoObjects != null) options.Add("no-objects", true); string optSubRegion = ConvOAR.Globals.parms.P <string>("SubRegion"); if (optSubRegion != null) { List <float> bounds = optSubRegion.Split(',').Select <string, float>(x => { return(float.Parse(x)); }).ToList(); options.Add("bounding-origin", new OMV.Vector3(bounds[0], bounds[1], bounds[2])); options.Add("bounding-size", new OMV.Vector3(bounds[3] - bounds[0], bounds[4] - bounds[1], bounds[5] - bounds[2])); } // Create an OpenSimulator region and scene to load the OAR into string regionName = "convoar"; if (String.IsNullOrEmpty(ConvOAR.Globals.parms.P <String>("RegionName"))) { // Try to build the region name from the OAR filesname regionName = Path.GetFileNameWithoutExtension(ConvOAR.Globals.parms.P <string>("InputOAR")); } else { regionName = ConvOAR.Globals.parms.P <string>("RegionName"); } Scene scene = CreateScene(assetService, regionName); // Load the archive into our scene ArchiveReadRequest archive = new ArchiveReadRequest(scene, ConvOAR.Globals.parms.P <string>("InputOAR"), Guid.Empty, options); archive.DearchiveRegion(false); // Convert SOGs from OAR into EntityGroups // ConvOAR.Globals.log.Log("Num assets = {0}", assetService.NumAssets); LogBProgress("Num SOGs = {0}", scene.GetSceneObjectGroups().Count); PrimToMesh mesher = new PrimToMesh(); // Convert SOGs => BInstances Promise <BInstance> .All( scene.GetSceneObjectGroups().Select(sog => { return(ConvertSogToInstance(sog, assetFetcher, mesher)); }) ) .Done(instances => { ConvOAR.Globals.log.DebugFormat("{0} Num instances = {1}", _logHeader, instances.ToList().Count); List <BInstance> instanceList = new List <BInstance>(); instanceList.AddRange(instances); // Add the terrain mesh to the scene BInstance terrainInstance = null; if (ConvOAR.Globals.parms.P <bool>("AddTerrainMesh")) { ConvOAR.Globals.log.DebugFormat("{0} Creating terrain for scene", _logHeader); // instanceList.Add(ConvoarTerrain.CreateTerrainMesh(scene, mesher, assetFetcher)); terrainInstance = ConvoarTerrain.CreateTerrainMesh(scene, mesher, assetFetcher); CoordAxis.FixCoordinates(terrainInstance, new CoordAxis(CoordAxis.RightHand_Yup | CoordAxis.UVOriginLowerLeft)); } // Twist the OpenSimulator Z-up coordinate system to the OpenGL Y-up foreach (var inst in instanceList) { CoordAxis.FixCoordinates(inst, new CoordAxis(CoordAxis.RightHand_Yup | CoordAxis.UVOriginLowerLeft)); } // package instances into a BScene BScene bScene = new BScene(); bScene.instances = instanceList; RegionInfo ri = scene.RegionInfo; bScene.name = ri.RegionName; bScene.terrainInstance = terrainInstance; bScene.attributes.Add("RegionName", ri.RegionName); bScene.attributes.Add("RegionSizeX", ri.RegionSizeX); bScene.attributes.Add("RegionSizeY", ri.RegionSizeY); bScene.attributes.Add("RegionSizeZ", ri.RegionSizeZ); bScene.attributes.Add("RegionLocX", ri.RegionLocX); bScene.attributes.Add("RegionLocY", ri.RegionLocY); bScene.attributes.Add("WorldLocX", ri.WorldLocX); bScene.attributes.Add("WorldLocY", ri.WorldLocY); bScene.attributes.Add("WaterHeight", ri.RegionSettings.WaterHeight); bScene.attributes.Add("DefaultLandingPorint", ri.DefaultLandingPoint); prom.Resolve(bScene); }, e => { ConvOAR.Globals.log.ErrorFormat("{0} failed SOG conversion: {1}", _logHeader, e); // prom.Reject(new Exception(String.Format("Failed conversion: {0}", e))); }); return(prom); }
// Convert a SceneObjectGroup into an instance with displayables public IPromise <BInstance> ConvertSogToInstance(SceneObjectGroup sog, IAssetFetcher assetFetcher, PrimToMesh mesher) { var prom = new Promise <BInstance>(); LogBProgress("{0} ConvertSogToInstance: name={1}, id={2}, SOPs={3}", _logHeader, sog.Name, sog.UUID, sog.Parts.Length); // Create meshes for all the parts of the SOG Promise <Displayable> .All( sog.Parts.Select(sop => { LogBProgress("{0} ConvertSOGToInstance: Calling CreateMeshResource for sog={1}, sop={2}", _logHeader, sog.UUID, sop.UUID); OMV.Primitive aPrim = sop.Shape.ToOmvPrimitive(); return(mesher.CreateMeshResource(sog, sop, aPrim, assetFetcher, OMVR.DetailLevel.Highest)); }) ) .Then(renderables => { // Remove any failed SOG/SOP conversions. List <Displayable> filteredRenderables = renderables.Where(rend => rend != null).ToList(); // 'filteredRenderables' are the DisplayRenderables for all the SOPs in the SOG // Get the root prim of the SOG List <Displayable> rootDisplayableList = filteredRenderables.Where(disp => { return(disp.baseSOP.IsRoot); }).ToList(); if (rootDisplayableList.Count != 1) { // There should be only one root prim ConvOAR.Globals.log.ErrorFormat("{0} ConvertSOGToInstance: Found not one root prim in SOG. ID={1}, numRoots={2}", _logHeader, sog.UUID, rootDisplayableList.Count); prom.Reject(new Exception(String.Format("Found more than one root prim in SOG. ID={0}", sog.UUID))); return(null); } // The root of the SOG Displayable rootDisplayable = rootDisplayableList.First(); // Collect all the children prims and add them to the root Displayable rootDisplayable.children = filteredRenderables.Where(disp => { return(!disp.baseSOP.IsRoot); }).Select(disp => { return(disp); }).ToList(); return(rootDisplayable); }) .Done(rootDisplayable => { // Add the Displayable into the collection of known Displayables for instancing assetFetcher.AddUniqueDisplayable(rootDisplayable); // Package the Displayable into an instance that is position in the world BInstance inst = new BInstance(); inst.Position = sog.AbsolutePosition; inst.Rotation = sog.GroupRotation; inst.Representation = rootDisplayable; if (ConvOAR.Globals.parms.P <bool>("LogBuilding")) { DumpInstance(inst); } prom.Resolve(inst); }, e => { ConvOAR.Globals.log.ErrorFormat("{0} Failed meshing of SOG. ID={1}: {2}", _logHeader, sog.UUID, e); prom.Reject(new Exception(String.Format("failed meshing of SOG. ID={0}: {1}", sog.UUID, e))); }); return(prom); }
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); }
private Promise <DisplayableRenderable> MeshFromPrimMeshData(SceneObjectGroup sog, SceneObjectPart sop, OMV.Primitive prim, IAssetFetcher assetFetcher, OMVR.DetailLevel lod) { EntityHandleUUID meshHandle = new EntityHandleUUID(prim.Sculpt.SculptTexture); return(new Promise <DisplayableRenderable>((resolve, reject) => { assetFetcher.FetchRawAsset(meshHandle) .Then(meshBytes => { // OMVA.AssetMesh meshAsset = new OMVA.AssetMesh(prim.ID, meshBytes); // if (OMVR.FacetedMesh.TryDecodeFromAsset(prim, meshAsset, lod, out fMesh)) { OMVR.FacetedMesh fMesh = null; try { fMesh = _mesher.GenerateFacetedMeshMesh(prim, meshBytes); } catch (Exception e) { ConvOAR.Globals.log.ErrorFormat("{0} Exception in GenerateFacetedMeshMesh: {1}", _logHeader, e); } if (fMesh != null) { DisplayableRenderable dr = ConvertFacetedMeshToDisplayable(assetFetcher, fMesh, prim.Textures.DefaultTexture, prim.Scale); // Don't know the hash of the DisplayableRenderable until after it has been created. // Now use the hash to see if this has already been done. // If this DisplayableRenderable has already been built, use the other one and throw this away. BHash drHash = dr.GetBHash(); DisplayableRenderable realDR = assetFetcher.GetRenderable(drHash, () => { return dr; }); resolve(realDR); } else { reject(new Exception("MeshFromPrimMeshData: could not decode mesh information from asset. ID=" + prim.ID.ToString())); } }, e => { ConvOAR.Globals.log.ErrorFormat("{0} MeshFromPrimMeshData: exception: {1}", _logHeader, e); reject(e); }); })); }
private Promise <DisplayableRenderable> MeshFromPrimSculptData(SceneObjectGroup sog, SceneObjectPart sop, OMV.Primitive prim, IAssetFetcher assetFetcher, OMVR.DetailLevel lod) { return(new Promise <DisplayableRenderable>((resolve, reject) => { // Get the asset that the sculpty is built on EntityHandleUUID texHandle = new EntityHandleUUID(prim.Sculpt.SculptTexture); assetFetcher.FetchTexture(texHandle) .Then((bm) => { OMVR.FacetedMesh fMesh = _mesher.GenerateFacetedSculptMesh(prim, bm.Image.ExportBitmap(), lod); DisplayableRenderable dr = ConvertFacetedMeshToDisplayable(assetFetcher, fMesh, prim.Textures.DefaultTexture, prim.Scale); BHash drHash = dr.GetBHash(); DisplayableRenderable realDR = assetFetcher.GetRenderable(drHash, () => { return dr; }); BConverterOS.LogBProgress("{0} MeshFromPrimSculptData. numFaces={1}, numGenedMeshed={2}", _logHeader, fMesh.Faces.Count, ((RenderableMeshGroup)realDR).meshes.Count); resolve(realDR); }, (e) => { ConvOAR.Globals.log.ErrorFormat("{0} MeshFromPrimSculptData: Rejected FetchTexture: {1}: {2}", _logHeader, texHandle, e); reject(null); }); })); }
private Promise <DisplayableRenderable> MeshFromPrimShapeData(SceneObjectGroup sog, SceneObjectPart sop, OMV.Primitive prim, IAssetFetcher assetFetcher, OMVR.DetailLevel lod) { return(new Promise <DisplayableRenderable>((resolve, reject) => { OMVR.FacetedMesh mesh = _mesher.GenerateFacetedMesh(prim, lod); DisplayableRenderable dr = ConvertFacetedMeshToDisplayable(assetFetcher, mesh, prim.Textures.DefaultTexture, prim.Scale); BHash drHash = dr.GetBHash(); DisplayableRenderable realDR = assetFetcher.GetRenderable(drHash, () => { return dr; }); BConverterOS.LogBProgress("{0} MeshFromPrimShapeData. numGenedMeshed={1}", _logHeader, ((RenderableMeshGroup)realDR).meshes.Count); resolve(realDR); })); }