/************************************************************************* * Copyright © 2018-2023 Liwen All rights reserved. *------------------------------------------------------------------------ * File : PagedLod.cs * Description : update lod with cameradistance *------------------------------------------------------------------------ * Author : Liwen * Version : 1.0.0 * Date : 1/5/2019 * Description : Initial development version. *************************************************************************/ using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO; using System.Runtime.InteropServices; using System; using System.Text; using System.Text.RegularExpressions; using LitJson; namespace AIPagedLod { [System.Serializable] public class PagedLodInfo { public string mFileName = string.Empty; public PagedLod mPagedLod = null; public bool mIsBasicInfoLoaded = false; public bool mIsDownloaded = false; public PagedLodRangeInfo mRangeValue = new PagedLodRangeInfo(); } public class PagedLodLoaderInfo { public int mIndex = 0; public PagedLodFileLoader mLoader = null; } [System.Serializable] public class B3dmBasicInfo { public Vector3 mCenter; public Vector3 mExtents; public float mGeometricError; public string mContent; public Bounds mBounds; public List mChildrenList = new List(); } public class PagedLod : MonoBehaviour { public List mChildTileList = new List(); //初始化加载时赋值 public int mSelfLevel = 0; public bool mIsRootTile = false; public string mTileDirPath; //osgb文件属性 public float mPixelValue; public float mLoadChildPixelValue; //固有基本属性 public float mOsgbBoundRadius; //缩放变化后需要重新求取,osgb库中求得,暂时不用 public Bounds mOsgbBounds; //根据固有属性计算 public Vector3 mWorldCenter = Vector3.zero; //相对于父节点的局部中心坐标,仅在根节点中使用,位置变化后需要重新求取 public List mBoundsVertexList = new List();//位置及缩放变化后需要重新求取 public List mMeshRenderers = new List(); //根节点拥有,用于计算瓦片是否可见 public GameObject mRenderNode = null; public bool mIsRenderNodeLoaded = false; public bool mIsRenderNodeMeshLoaded = false;//Mesh网格节点是否加载 //每帧计算 public bool mIsVisiable = true; private int mVisiableFrameNumber = 0; private int mInRangeFrameNumber = 0; private LoadPagedLodFromFileManager mManager = null; private Camera mMainCamera = null; //b3dm专有 public PagedLod mRootPagedLod = null; public Dictionary mBasicInfoDict = new Dictionary();//根节点拥有 public List mBasicInfoList = new List();//根节点拥有 public B3dmBasicInfo mBasicInfo = new B3dmBasicInfo(); public float mScreenSpaceError = 0.0f; public static int mMaxLoadLevel = 0; private LoadPagedLodFromFileManager GetLodManager() { if (mManager == null) { mManager = transform.GetComponentInParent(); } return mManager; } string GetDataPath() { return GetLodManager().GetDataPath() + "\\" + mTileDirPath; } public void InitBasicInfoDictFromList() { if (mBasicInfoDict.Count == 0) { for (int i = 0; i < mBasicInfoList.Count; ++i) { mBasicInfoDict.Add(mBasicInfoList[i].mContent, mBasicInfoList[i]); } } } void ResetLod() { mChildTileList.Clear(); mMeshRenderers.Clear(); mVisiableFrameNumber = 0; mInRangeFrameNumber = 0; mSelfLevel = 0; mManager = null; mRootPagedLod = null; mBasicInfoDict.Clear(); mIsRenderNodeLoaded = false; mIsRenderNodeMeshLoaded = false; } #region UpdateTileVisiableStatus----------------------- void UpdateTileVisiableStatus() { mIsVisiable = true; if (mIsRootTile) { GetTileVisiableStatus(); } } void GetTileVisiableStatus() { if (mMeshRenderers.Count == 0) { mIsVisiable = true; return; } if (mMainCamera == null) mMainCamera = Camera.main; Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mMainCamera); for (int i = 0; i < mMeshRenderers.Count; ++i) { if (GeometryUtility.TestPlanesAABB(planes, mMeshRenderers[i].bounds)) { mIsVisiable = true; return; } } mIsVisiable = false; } void UpdateInvisiableTiles() { if (!mRenderNode.activeSelf) mRenderNode.SetActive(true); if (!mIsRenderNodeLoaded) { LoadTileRenderNode(); } if (Time.frameCount - mVisiableFrameNumber > PagedLodConfig.mInstance.mExpiryFrames) { if (mChildTileList.Count != 0 && mChildTileList[0].mPagedLod != null) { CollectChildTiles(); } } else { if (mChildTileList.Count != 0 && mChildTileList[0].mPagedLod != null && mChildTileList[0].mPagedLod.gameObject.activeSelf) { SetChildTilesActive(false); } } } #endregion UpdateTileVisiableStatus #region CollectChildTiles----------------------------- public void CollectChildTiles() { for (int i = 0; i < mChildTileList.Count; ++i) { PagedLod childLod = mChildTileList[i].mPagedLod; if (childLod != null) { PagedLod[] childPagedlods = childLod.transform.GetComponentsInChildren(true); for (int j = 0; j < childPagedlods.Length; ++j) { #if UNITY_WEBGL GameObject.Destroy(childPagedlods[j].gameObject); PagedLodConfig.mInstance.mCollectCount++; #else childPagedlods[j].ResetLod(); MeshRenderer[] renderers = childPagedlods[j].gameObject.GetComponentsInChildren(true); for (int m = 0; m < renderers.Length; ++m) { MeshPool.mInstance.PushMeshObject(renderers[m].gameObject); } TileObjectPool.mInstance.PushTileObject(childPagedlods[j].gameObject); #endif } } mChildTileList[i].mPagedLod = null; mChildTileList[i].mIsBasicInfoLoaded = false; #if UNITY_WEBGL if (PagedLodConfig.mInstance.mCollectCount >= 100) { StartCoroutine(DestoryTiles()); PagedLodConfig.mInstance.mCollectCount = 0; //Debug.Log("-------------------UnloadUnusedAssets"); } #endif } } IEnumerator DestoryTiles() { yield return new WaitForSeconds(0.1f); Resources.UnloadUnusedAssets();//卸载未占用的asset资源 System.GC.Collect();//回收内存 } #endregion public bool UpdatePagedLod() { mMaxLoadLevel = (int)MathF.Max(mMaxLoadLevel,mSelfLevel); //RecaculateBounds(); UpdateTileVisiableStatus(); if (!mIsVisiable) //不可见,则回收瓦片 { UpdateInvisiableTiles(); return false; } else { mVisiableFrameNumber = Time.frameCount; bool isLoadChildTile = false; if (PagedLodConfig.mInstance.mTileDataType == TileDataType.OSGB) { mPixelValue = GetPixelValue();//瓦片所占像素大小 isLoadChildTile = mPixelValue > mLoadChildPixelValue; } else { mScreenSpaceError = GetScreenSpaceError(); isLoadChildTile = mScreenSpaceError > PagedLodConfig.mInstance.mMaxSSE; } if (isLoadChildTile) { #if UNITY_WEBGL || UNITY_ANDROID LoadChildTilePagedLodFromUrl(); #else if (PagedLodConfig.mInstance.mIsLoadFromUrl) LoadChildTilePagedLodFromUrl(); else LoadChildTilePagedLod(); #endif if (mRenderNode != null ) { if (mRenderNode.activeSelf) { if(IsChildTilesLoaded()) { mRenderNode.SetActive(false); } } //else //{ // mRenderNode.SetActive(true); //} } return true; } else //回收子瓦片 { UpdateCollectChildTiles(); return false; } } } #region LoadB3dmFile-------------------------------------- void LoadSelfRenderNodeFromUrl() { if (DownloadFileManager.mInstance.CanDownload()) { mIsRenderNodeLoaded = true; DownloadFileManager.mInstance.StartDownloadTileFile(this,GetLodManager().mConfig.mBaseUrl); } } void LoadChildTilePagedLodFromUrl() { if (!DownloadFileManager.mInstance.CanDownload()) return; for (int i = 0; i < mChildTileList.Count; ++i) { if (mChildTileList[i].mPagedLod != null) { mChildTileList[i].mPagedLod.gameObject.SetActive(true); } else { if (!mChildTileList[i].mIsBasicInfoLoaded) { PagedLodInfo pagedLodInfo = mChildTileList[i]; B3dmFileLoader pagedLodLoader = new B3dmFileLoader(); pagedLodLoader.mTileName = pagedLodInfo.mFileName.Replace(PagedLodConfig.mInstance.GetFileSufix(), ""); pagedLodInfo.mIsBasicInfoLoaded = true; LoadTilePagedLod(pagedLodInfo, pagedLodLoader); break; } } } } #endregion LoadB3dmFile #region LoadSelfRenderNode-------------------------------------- void UpdateCollectChildTiles() { if (!mIsRenderNodeLoaded) { LoadTileRenderNode(); } else { if (mRenderNode != null && !mRenderNode.activeSelf) { mRenderNode.SetActive(true); } } //if (mIsRenderNodeLoaded && mRenderNode != null && mRenderNode.activeSelf) if (mIsRenderNodeMeshLoaded) { if (Time.frameCount - mInRangeFrameNumber > PagedLodConfig.mInstance.mExpiryFrames) { CollectChildTiles(); } else { SetChildTilesActive(false); } } } void LoadTileRenderNode() { #if UNITY_WEBGL || UNITY_ANDROID LoadSelfRenderNodeFromUrl(); #else if (PagedLodConfig.mInstance.mIsLoadFromUrl) LoadSelfRenderNodeFromUrl(); else LoadSelfRenderNode(); #endif } PagedLodFileLoader GetSelfRenderNodeLoader() { PagedLodFileLoader pagedLodLoader = null; string fileFullPath = GetDataPath() + "/" + gameObject.name + PagedLodConfig.mInstance.GetFileSufix(); fileFullPath = System.Text.RegularExpressions.Regex.Replace(fileFullPath, @"\p{C}+", ""); if (File.Exists(fileFullPath)) { pagedLodLoader = PagedLodFileLoader.GetFileLoader(fileFullPath); pagedLodLoader.mIsDaJiangData = GetLodManager().mConfig.mIsDaJiangData; } return pagedLodLoader; } void LoadSelfRenderNode() { if (!PagedLodThreadPool.IsQueueWorkItem()) return; PagedLodFileLoader pagedLodLoader = GetSelfRenderNodeLoader(); if (pagedLodLoader == null) return; mIsRenderNodeLoaded = true; PagedLod targetPagedLod = this; PagedLodThreadPool.RunAsync(() => { pagedLodLoader.LoadObjectMeshInfo(); PagedLodThreadPool.QueueOnMainThread(() => { if (GetLodManager() != null) { if (targetPagedLod != null) { LoadTileAsyncManager.mInstance.StartLoadRenderNode(targetPagedLod, pagedLodLoader); } else { Debug.Log("targetPagedLod is null ************* "); } } else { pagedLodLoader.Dispose(); pagedLodLoader = null; } }); }); } #endregion #region LoadChildTilePagedLod------------------------------------ List GetNeedLoadChildTile() { List loaderList = new List(); for (int i = 0; i < mChildTileList.Count; ++i) { if (mChildTileList[i].mPagedLod != null) { mChildTileList[i].mPagedLod.gameObject.SetActive(true); } else { if (!mChildTileList[i].mIsBasicInfoLoaded) { PagedLodInfo pagedLodInfo = mChildTileList[i]; string fileFullPath = GetDataPath() + "/" + mChildTileList[i].mFileName; fileFullPath = System.Text.RegularExpressions.Regex.Replace(fileFullPath, @"\p{C}+", ""); if (File.Exists(fileFullPath)) { PagedLodLoaderInfo info = new PagedLodLoaderInfo(); PagedLodFileLoader pagedLodLoader = PagedLodFileLoader.GetFileLoader(fileFullPath); pagedLodLoader.mIsDaJiangData = GetLodManager().mConfig.mIsDaJiangData; FileInfo fileInfo = new FileInfo(pagedLodLoader.mFileName); pagedLodLoader.mTileName = fileInfo.Name.Replace(PagedLodConfig.mInstance.GetFileSufix(), ""); pagedLodInfo.mIsBasicInfoLoaded = true; info.mLoader = pagedLodLoader; info.mIndex = i; loaderList.Add(info); } } } } return loaderList; } void LoadChildTilePagedLod() { if (!PagedLodThreadPool.IsQueueWorkItem()) return; List loaderList = GetNeedLoadChildTile(); PagedLod targetPagedLod = this; PagedLodThreadPool.RunAsync(() => { for (int i = 0; i < loaderList.Count; ++i) { if (loaderList[i].mLoader != null) { loaderList[i].mLoader.LoadObjectBasicInfo(); } } PagedLodThreadPool.QueueOnMainThread(() => { if (GetLodManager() != null && targetPagedLod != null) { for (int i = 0; i < loaderList.Count; ++i) { if (loaderList[i].mLoader != null) { LoadTileAsyncManager.mInstance.StartLoadChildTile(targetPagedLod, loaderList[i].mLoader, mChildTileList[loaderList[i].mIndex]); } } } else { for (int i = 0; i < loaderList.Count; ++i) { if (loaderList[i].mLoader != null) { loaderList[i].mLoader.Dispose(); loaderList[i].mLoader = null; } } } }); }); } #endregion //运行时调用 public void LoadTilePagedLod(PagedLodInfo info, PagedLodFileLoader pagedLodLoader) { GameObject tileObj = TileObjectPool.mInstance.GetTileObject(); PagedLod pagedLod = null; if (tileObj == null) { tileObj = new GameObject(); pagedLod = tileObj.AddComponent(); } else { tileObj.SetActive(true); pagedLod = tileObj.GetComponent(); } info.mPagedLod = pagedLod; tileObj.name = pagedLodLoader.mTileName; tileObj.transform.SetParent(transform); tileObj.transform.localPosition = Vector3.zero; tileObj.transform.localRotation = Quaternion.identity; tileObj.transform.localScale = Vector3.one; pagedLod.mSelfLevel = mSelfLevel + 1; pagedLod.mTileDirPath = mTileDirPath; if(mIsRootTile) { pagedLod.mRootPagedLod = this; } else { pagedLod.mRootPagedLod = mRootPagedLod; } if(PagedLodConfig.mInstance.mTileDataType == TileDataType.OSGB) { pagedLod.LoadOsgbBasicInfo(pagedLodLoader); } else { pagedLod.mBasicInfo = pagedLod.mRootPagedLod.GetTileBasicInfo(tileObj.name); pagedLod.LoadB3dmBasicInfo(pagedLodLoader); } } public void LoadOsgbBasicInfo(PagedLodFileLoader pagedLodLoader) { OsgbFileLoader osgbFileLoader = pagedLodLoader as OsgbFileLoader; Vector3 center = GetLodManager().transform.TransformPoint(osgbFileLoader.mBoundsCenter); Vector3 min = GetLodManager().transform.TransformPoint(osgbFileLoader.mBoundsBoxMin); Vector3 max = GetLodManager().transform.TransformPoint(osgbFileLoader.mBoundsBoxMax); mOsgbBoundRadius = osgbFileLoader.mBoundRadius; mOsgbBounds.min = min; mOsgbBounds.max = max; //mWorldCenter = new Vector3(center.x, -center.y, center.z); mWorldCenter = new Vector3(center.x, -center.z, -center.y); InitOsgbChildFiles(this, pagedLodLoader); GetBoundsVertexList(); } public void LoadB3dmBasicInfo(PagedLodFileLoader pagedLodLoader) { mOsgbBounds = mBasicInfo.mBounds; mWorldCenter = GetLodManager().transform.TransformPoint(mOsgbBounds.center); mOsgbBoundRadius = mOsgbBounds.extents.magnitude; InitB3dmChildFiles(this, pagedLodLoader); GetBoundsVertexList(); } public void RecaculateBounds() { Vector3 center = Vector3.zero; if (mMeshRenderers.Count != 0) { Bounds bounds = new Bounds(center, Vector3.zero); foreach (Renderer mr in mMeshRenderers) { if (bounds.size == Vector3.zero) { bounds = mr.bounds; } else { bounds.Encapsulate(mr.bounds); } } mOsgbBounds = bounds; mOsgbBoundRadius = Vector3.Distance(bounds.center, bounds.max); mWorldCenter = bounds.center; GetBoundsVertexList(); } } private void InitOsgbChildFiles(PagedLod pagedLod, PagedLodFileLoader pagedLodLoader) { OsgbFileLoader osgbFileLoader = pagedLodLoader as OsgbFileLoader; pagedLod.mChildTileList.Clear(); float minPixelValue = float.MaxValue; for (int i = 0; i < osgbFileLoader.mChildFiles.Count; ++i) { PagedLodInfo info = new PagedLodInfo(); string fileFullPath = pagedLod.GetDataPath() + "\\" + osgbFileLoader.mChildFiles[i]; fileFullPath = System.Text.RegularExpressions.Regex.Replace(fileFullPath, @"\p{C}+", ""); info.mFileName = osgbFileLoader.mChildFiles[i]; info.mRangeValue = osgbFileLoader.mChildRangeValues[i]; pagedLod.mChildTileList.Add(info); minPixelValue = Mathf.Min(minPixelValue, info.mRangeValue.mChildMinValue); } pagedLod.mLoadChildPixelValue = minPixelValue; } private void InitB3dmChildFiles(PagedLod pagedLod, PagedLodFileLoader pagedLodLoader) { pagedLod.mChildTileList.Clear(); for (int i = 0; i < pagedLod.mBasicInfo.mChildrenList.Count; ++i) { B3dmBasicInfo childBasicInfo = pagedLod.mBasicInfo.mChildrenList[i]; PagedLodInfo info = new PagedLodInfo(); info.mFileName = childBasicInfo.mContent + PagedLodConfig.mInstance.GetFileSufix(); pagedLod.mChildTileList.Add(info); } } public void LoadRenderNode(PagedLodFileLoader pagedLodLoader) { if (mRenderNode == null) { mRenderNode = new GameObject("RenderNode"); mRenderNode.transform.SetParent(transform); mRenderNode.transform.localPosition = Vector3.zero; mRenderNode.transform.localRotation = Quaternion.identity; mRenderNode.transform.localScale = Vector3.one; } else { mRenderNode.SetActive(true); } pagedLodLoader.BuildObjectForThread(mRenderNode); transform.localScale = Vector3.one; mIsRenderNodeMeshLoaded = true; GetMeshRenders(); RecaculateBounds(); } public void GetMeshRenders() { mMeshRenderers = new List(gameObject.GetComponentsInChildren()); } public void GetBoundsVertexList() { Vector3 center = mWorldCenter; var targetBoundsExtents = mOsgbBounds.extents; //得到目标物包围盒范围:extents List vertexList = new List(); float x = targetBoundsExtents.x; float y = targetBoundsExtents.y; float z = targetBoundsExtents.z; vertexList.Add(center); vertexList.Add(center + new Vector3(x, y, z)); vertexList.Add(center + new Vector3(x, -y, z)); vertexList.Add(center + new Vector3(x, y, -z)); vertexList.Add(center + new Vector3(x, -y, -z)); vertexList.Add(center + new Vector3(-x, y, z)); vertexList.Add(center + new Vector3(-x, -y, z)); vertexList.Add(center + new Vector3(-x, y, -z)); vertexList.Add(center + new Vector3(-x, -y, -z)); mBoundsVertexList = vertexList; } public bool IsChildTilesLoaded() { if (mIsVisiable) { for (int i = 0; i < mChildTileList.Count; ++i) { if (mChildTileList[i].mPagedLod != null) { if (!mChildTileList[i].mPagedLod.gameObject.activeSelf) return false; bool flag = mChildTileList[i].mPagedLod.IsRenderNodeLoaded(); if (!flag) { return false; } } else { return false; } } } return true; } public bool IsRenderNodeLoaded() { bool isLoadChildTile = false; if (PagedLodConfig.mInstance.mTileDataType == TileDataType.OSGB) { mPixelValue = GetPixelValue();//瓦片所占像素大小 isLoadChildTile = mPixelValue > mLoadChildPixelValue; } else { mScreenSpaceError = GetScreenSpaceError(); isLoadChildTile = mScreenSpaceError > PagedLodConfig.mInstance.mMaxSSE; } if (isLoadChildTile) { return IsChildTilesLoaded(); } else { return mIsRenderNodeMeshLoaded; //return mIsRenderNodeLoaded && mRenderNode != null && mRenderNode.activeSelf; } } void SetChildTilesActive(bool flag) { for (int i = 0; i < mChildTileList.Count; ++i) { PagedLod childTile = mChildTileList[i].mPagedLod; if (childTile != null && childTile.gameObject.activeSelf != flag) { childTile.gameObject.SetActive(flag); } } } float GetMinCameraDistance() { if (mMainCamera == null) mMainCamera = Camera.main; float distance = float.MaxValue; for (int i = 0; i < mBoundsVertexList.Count; ++i) { float d = Vector3.Distance(mMainCamera.transform.position, mBoundsVertexList[i]); distance = Mathf.Min(distance, d); } return distance; } public float mDistance = 0.0f; float GetPixelValue() { if (mWorldCenter == Vector3.zero) return float.MinValue; Vector3 center = mWorldCenter; if (mMainCamera == null) { mMainCamera = Camera.main; } float distance = GetMinCameraDistance(); mDistance = distance; float slope = Mathf.Tan(mMainCamera.fieldOfView * Mathf.Deg2Rad * 0.5f); int pixelHeight = 1300;/*mMainCamera.pixelHeight*/; float projFactor = (0.5f * pixelHeight) / (slope * distance); float pixelValue = mOsgbBoundRadius * projFactor; return pixelValue; } public bool IsMaxLevelTile()//最高级没有子节点 { if (mRenderNode != null) { return mChildTileList.Count == 0; } return false; } #region B3DM file Load------------------------- float GetScreenSpaceError() { if (mWorldCenter == Vector3.zero) return float.MinValue; Vector3 center = mWorldCenter; if (mMainCamera == null) { mMainCamera = Camera.main; } float distance = Vector3.Distance(mMainCamera.transform.position, mWorldCenter); //GetMinCameraDistance(); mDistance = distance; float slope = Mathf.Tan(mMainCamera.fieldOfView * Mathf.Deg2Rad * 0.5f); int pixelHeight = 1300;/*mMainCamera.pixelHeight*/; float es = (mBasicInfo.mGeometricError * pixelHeight) / (distance * 2.0f * slope); return es; } //根节点调用,加载瓦片结构组织 public void LoadTileSetJsonFile() { if (mBasicInfoDict.Count == 0) { string tileSetJsonPath = GetDataPath() + "/tileset.json"; string jsonText = System.IO.File.ReadAllText(tileSetJsonPath); B3dmBasicInfo rootTileInfo = null; mBasicInfoDict = LoadTileSetJsonText(jsonText, ref rootTileInfo); mBasicInfoList = new List(mBasicInfoDict.Values); //mBasicInfo = rootTileInfo; mBasicInfo = GetTileBasicInfo(gameObject.name); } } public static Dictionary LoadTileSetJsonText(string jsonText,ref B3dmBasicInfo rootTileInfo) { JsonData resultJson = JsonMapper.ToObject(jsonText); JsonData root = resultJson["root"]; Dictionary basicInfoDict = new Dictionary(); TraverseLoadTileBasicInfo(root, ref basicInfoDict,ref rootTileInfo); return basicInfoDict; } static void TraverseLoadTileBasicInfo(JsonData tileJson, ref Dictionary basicInfoDict, ref B3dmBasicInfo rootTileInfo) { B3dmBasicInfo info = LoadTileBasicInfo(tileJson); if(rootTileInfo == null) { rootTileInfo = info; } if (tileJson.Keys.Contains("children")) { JsonData children = tileJson["children"]; for (int i = 0; i < children.Count; ++i) { B3dmBasicInfo childInfo = LoadTileBasicInfo(children[i]); info.mChildrenList.Add(childInfo); TraverseLoadTileBasicInfo(children[i], ref basicInfoDict,ref rootTileInfo); } } basicInfoDict.Add(info.mContent, info); } public static B3dmBasicInfo LoadTileBasicInfo(JsonData tileJson) { JsonData boundingVolume = tileJson["boundingVolume"]; JsonData box = boundingVolume["box"]; float centerX = float.Parse(box[0].ToString()); float centerY = float.Parse(box[1].ToString()); float centerZ = float.Parse(box[2].ToString()); float extentX = float.Parse(box[3].ToString()); float extentY = float.Parse(box[7].ToString()); float extentZ = float.Parse(box[11].ToString()); B3dmBasicInfo info = new B3dmBasicInfo(); info.mCenter = new Vector3(centerX, centerZ, centerY); info.mExtents = new Vector3(extentX, extentZ, extentY); Bounds bounds = new Bounds(); bounds.center = info.mCenter; bounds.extents = info.mExtents; info.mBounds = bounds; if (tileJson.Keys.Contains("content")) { info.mContent = tileJson["content"]["uri"].ToString(); info.mContent = info.mContent.Replace("./", ""); info.mContent = info.mContent.Replace(PagedLodConfig.mInstance.GetFileSufix(), ""); } info.mGeometricError = float.Parse(tileJson["geometricError"].ToString()); return info; } public B3dmBasicInfo GetTileBasicInfo(string tileName) { if (mBasicInfoDict.ContainsKey(tileName)) { return mBasicInfoDict[tileName]; } return null; } #endregion B3DM file Load } }