using Reinterop; using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using Unity.Mathematics; using UnityEngine; namespace CesiumForUnity { /// /// Specifies the source of a Cesium dataset. /// public enum CesiumDataSource { /// /// The dataset is from Cesium ion. /// FromCesiumIon, /// /// The dataset is from a regular web URL. /// FromUrl, /// /// The dataset is generated from the georeference ellipsoid. /// FromEllipsoid } /// /// A tileset in the 3D Tiles format. [3D Tiles](https://github.com/CesiumGS/3d-tiles) /// is an open specification for sharing, visualizing, fusing, and interacting with massive /// heterogenous 3D geospatial content across desktop, web, and mobile applications. The tileset is /// streamed incrementally into Unity based on the current camera view(s). /// /// /// /// A GameObject with this component must be nested inside one with a /// component. The georeference controls how this tileset is mapped into the Unity world. /// /// /// In most cases, the Transform of the GameObject that contains this component, and its ancestors, should /// be an identity transform: 0 position, 0 rotation, 1 scale. Otherwise, this tileset will be misaligned /// with other globe tilesets. However, it is sometimes useful to purposely offset a tileset. /// /// [ExecuteInEditMode] [ReinteropNativeImplementation("CesiumForUnityNative::Cesium3DTilesetImpl", "Cesium3DTilesetImpl.h")] [AddComponentMenu("Cesium/Cesium 3D Tileset")] [IconAttribute("Packages/com.cesium.unity/Editor/Resources/Cesium-24x24.png")] public partial class Cesium3DTileset : MonoBehaviour, IDisposable, ISerializationCallbackReceiver { public void Dispose() { this.OnDisable(); this.DisposeImplementation(); } #region Events and Delegates /// /// Encapsulates a method that receives details of a tileset load failure. /// /// The details of the load failure. public delegate void TilesetLoadFailureDelegate( Cesium3DTilesetLoadFailureDetails details); /// /// An event that is raised when the tileset encounters an error that prevents it from loading. /// public static event TilesetLoadFailureDelegate OnCesium3DTilesetLoadFailure; internal static void BroadcastCesium3DTilesetLoadFailure(Cesium3DTilesetLoadFailureDetails details) { if (OnCesium3DTilesetLoadFailure != null) { OnCesium3DTilesetLoadFailure(details); } } /// /// Occurs when a new GameObject is instantiated for a Tile in the tileset. /// /// /// This event can be used to customize the Tile GameObjects as they are loaded, /// such as adding components, changing materials, or applying transformations. /// public event Action OnTileGameObjectCreated; internal void BroadcastNewGameObjectCreated(GameObject go) { if (OnTileGameObjectCreated != null) { OnTileGameObjectCreated(go); } } internal static event Action OnSetShowCreditsOnScreen; #endregion #region Fields [SerializeField] private bool _showCreditsOnScreen = false; /// /// Whether or not to force this tileset's credits to be shown on the main screen. If false, the /// credits are usually only shown on a "Data Attribution" popup. /// public bool showCreditsOnScreen { get => this._showCreditsOnScreen; set { this._showCreditsOnScreen = value; this.SetShowCreditsOnScreen(this._showCreditsOnScreen); if (Cesium3DTileset.OnSetShowCreditsOnScreen != null) { Cesium3DTileset.OnSetShowCreditsOnScreen(); } } } [SerializeField] private CesiumDataSource _tilesetSource = CesiumDataSource.FromCesiumIon; /// /// The source of the data for this tileset: Cesium ion or a regular URL. /// public CesiumDataSource tilesetSource { get => this._tilesetSource; set { this._tilesetSource = value; this.RecreateTileset(); } } [SerializeField] private string _url = ""; /// /// The URL from which to load the tileset. This property is used only if /// is set to "FromUrl". /// public string url { get => this._url; set { this._url = value; this.RecreateTileset(); } } [SerializeField] private long _ionAssetID = 0; /// /// The Cesium ion asset ID from which to load the tileset. This property is used /// only if is set to "FromCesiumIon". /// public long ionAssetID { get => this._ionAssetID; set { this._ionAssetID = value; this.RecreateTileset(); } } [SerializeField] private string _ionAccessToken = ""; /// /// The Cesium ion access token to use when loading the tileset from Cesium ion. This property is used /// only if is set to "FromCesiumIon". /// public string ionAccessToken { get => this._ionAccessToken; set { this._ionAccessToken = value; this.RecreateTileset(); } } [SerializeField] private CesiumIonServer _ionServer = null; /// /// The Cesium ion server from which this tileset is loaded. /// public CesiumIonServer ionServer { get { if (this._ionServer == null) { #if UNITY_EDITOR // See OnAfterDeserialize. if (this._useDefaultServer) this._ionServer = CesiumIonServer.defaultServer; else this._ionServer = CesiumIonServer.serverForNewObjects; this._useDefaultServer = false; #else this._ionServer = CesiumIonServer.serverForNewObjects; #endif } return this._ionServer; } set { if (value == null) value = CesiumIonServer.serverForNewObjects; this._ionServer = value; this.RecreateTileset(); } } [SerializeField] private float _maximumScreenSpaceError = 16.0f; /// /// The maximum number of pixels of error when rendering this tileset. /// /// /// /// This is used to select an appropriate level-of-detail: A low value /// will cause many tiles with a high level of detail to be loaded, causing /// a finer visual representation of the tiles, but with a higher performance /// cost for loading and rendering. A higher value will cause a coarser /// visual representation, with lower performance requirements. /// /// /// When a tileset uses the older layer.json / quantized-mesh format rather /// than 3D Tiles, this value is effectively divided by 8.0. So the default /// value of 16.0 corresponds to the standard value for quantized-mesh /// terrain of 2.0. /// /// public float maximumScreenSpaceError { get => this._maximumScreenSpaceError; set { this._maximumScreenSpaceError = value; this.RecreateTileset(); } } [SerializeField] private bool _preloadAncestors = true; /// /// Whether to preload ancestor tiles. /// /// /// Setting this to true optimizes the zoom-out experience and provides more /// detail in newly-exposed areas when panning. The down side is that it /// requires loading more tiles. /// public bool preloadAncestors { get => this._preloadAncestors; set { this._preloadAncestors = value; this.RecreateTileset(); } } [SerializeField] private bool _preloadSiblings = true; /// /// Whether to preload sibling tiles. /// /// /// Setting this to true causes tiles with the same parent as a rendered /// tile to be loaded, even if they are culled. Setting this to true may /// provide a better panning experience at the cost of loading more tiles. /// public bool preloadSiblings { get => this._preloadSiblings; set { this._preloadSiblings = value; this.RecreateTileset(); } } [SerializeField] private bool _forbidHoles = false; /// /// Whether to prevent refinement of a parent tile when a child isn't done loading. /// /// /// When this is set to true, the tileset will guarantee that the tileset will /// never be rendered with holes in place of tiles that are not yet loaded, /// even though the tile that is rendered instead may have low resolution. /// When false, overall loading will be faster, but newly-visible parts of the /// tileset may initially be blank. /// public bool forbidHoles { get => this._forbidHoles; set { this._forbidHoles = value; this.RecreateTileset(); } } [SerializeField] private uint _maximumSimultaneousTileLoads = 20; /// /// The maximum number of tiles that may be loaded simultaneously. /// /// /// When new parts of the tileset become visible, the tasks to load the /// corresponding tiles are put into a queue. This value determines how /// many of these tasks are processed at the same time. A higher value may /// cause the tiles to be loaded and rendered more quickly, at the cost of /// a higher network and processing load. /// public uint maximumSimultaneousTileLoads { get => this._maximumSimultaneousTileLoads; set { this._maximumSimultaneousTileLoads = value; this.RecreateTileset(); } } [SerializeField] private long _maximumCachedBytes = 512 * 1024 * 1024; /// /// The maximum number of bytes that may be cached for this tileset. /// /// /// Note that this value, even if 0, will never cause tiles that are needed /// for rendering to be unloaded. However, if the total number of loaded /// bytes is greater than this value, tiles will be unloaded until the /// total is under this number or until only required tiles remain, whichever /// comes first. /// public long maximumCachedBytes { get => this._maximumCachedBytes; set { this._maximumCachedBytes = value; this.RecreateTileset(); } } [SerializeField] private uint _loadingDescendantLimit = 20; /// /// The number of loading descendents a tile should allow before deciding to render itself instead of waiting. /// /// /// Setting this to 0 will cause each level of detail to be loaded /// successively. This will increase the overall loading time, but cause /// additional detail to appear more gradually. Setting this to a high value /// like 1000 will decrease the overall time until the desired level of detail /// is achieved, but this high-detail representation will appear at once, as /// soon as it is loaded completely. /// public uint loadingDescendantLimit { get => this._loadingDescendantLimit; set { this._loadingDescendantLimit = value; this.RecreateTileset(); } } [SerializeField] private bool _enableFrustumCulling = true; /// /// Whether to cull tiles that are outside the frustum. /// /// /// /// By default this is true, meaning that tiles that are not visible with /// the current camera configuration will be ignored. It can be set to false, /// so that these tiles are still considered for loading, refinement and rendering. /// /// /// This will cause more tiles to be loaded, but helps to avoid holes and /// provides a more consistent mesh, which may be helpful for physics and shadows. /// /// /// Note that frustum calling will be disabled if is set to true. /// /// public bool enableFrustumCulling { get => this._enableFrustumCulling; set { this._enableFrustumCulling = value; this.RecreateTileset(); } } [SerializeField] private bool _enableFogCulling = true; /// /// Whether to cull tiles that are occluded by fog. /// /// /// /// This does not refer to the atmospheric fog rendered by Unity, but to an /// internal representation of fog: Depending on the height of the camera /// above the ground, tiles that are far away (close to the horizon) will be /// culled when this flag is enabled. /// /// /// Note that this will always be disabled if is set to true. /// /// public bool enableFogCulling { get => this._enableFogCulling; set { this._enableFogCulling = value; this.RecreateTileset(); } } [SerializeField] private bool _enforceCulledScreenSpaceError = true; /// /// Whether a specified screen-space error should be enforced for tiles /// that are outside the frustum or hidden in fog. /// /// /// /// When and are both true, /// tiles outside the view frustum or hidden in fog are effectively ignored, /// and so their level-of-detail doesn't matter. And in this scenario, this /// property is ignored. /// /// /// However, when either of those flags are false, these "would-be-culled" /// tiles continue to be processed, and the question arises of how to handle /// their level-of-detail. When this property is false, refinement terminates /// at these tiles, no matter what their current screen-space error. The tiles /// are available for physics, shadows, etc., but their level-of-detail may be /// very low. /// /// /// When set to true, these tiles are refined until they achieve the specified /// . This allows control over the minimum quality /// of these would-be-culled tiles. /// /// public bool enforceCulledScreenSpaceError { get => this._enforceCulledScreenSpaceError; set { this._enforceCulledScreenSpaceError = value; this.RecreateTileset(); } } [SerializeField] private float _culledScreenSpaceError = 64.0f; /// /// The screen-space error to be enforced for tiles that are outside the frustum or hidden in fog. /// /// /// /// When and are both true, /// tiles outside the view frustum or hidden in fog are effectively ignored, /// and so their level-of-detail doesn't matter. And in this scenario, this /// property is ignored. /// /// /// However, when either of those flags are false, these \"would-be-culled\" /// tiles continue to be processed, and the question arises of how to handle /// their level-of-detail. When this property is false, refinement terminates /// at these tiles, no matter what their current screen-space error. The tiles /// are available for physics, shadows, etc., but their level-of-detail may be /// very low. /// /// /// When set to true, these tiles are refined until they achieve the specified /// "Culled Screen Space Error". This allows control over the minimum quality /// of these would-be-culled tiles. /// /// public float culledScreenSpaceError { get => this._culledScreenSpaceError; set { this._culledScreenSpaceError = value; this.RecreateTileset(); } } [SerializeField] private Material _opaqueMaterial = null; /// /// The Material to use to render opaque parts of tiles. /// public Material opaqueMaterial { get => this._opaqueMaterial; set { this._opaqueMaterial = value; this.RecreateTileset(); } } //[SerializeField] //private bool _useLodTransitions = false; //public bool useLodTransitions //{ // get => this._useLodTransitions; // set // { // this._useLodTransitions = value; // this.RecreateTileset(); // } //} //[SerializeField] //private float _lodTransitionLength = 0.5f; //public float lodTransitionLength //{ // get => this._lodTransitionLength; // set // { // this._lodTransitionLength = value; // this.RecreateTileset(); // } //} [SerializeField] private bool _generateSmoothNormals = false; /// /// Whether to generate smooth normals when normals are missing in the glTF. /// /// /// According to the glTF spec: "When normals are not specified, client /// implementations should calculate flat normals." However, calculating flat /// normals requires duplicating vertices. This option allows the glTFs to be rendered /// with smooth normals instead when the original glTF is missing normals. /// public bool generateSmoothNormals { get => this._generateSmoothNormals; set { this._generateSmoothNormals = value; this.RecreateTileset(); } } [SerializeField] private bool _ignoreKhrMaterialsUnlit = false; /// /// Whether to ignore the KHR_materials_unlit extension on the glTF tiles in /// this tileset, if it exists /// /// /// Whether to ignore the KHR_materials_unlit extension on the glTF tiles in /// this tileset, if it exists, and instead render with standard lighting and /// shadows. This property will have no effect if the tileset does not have any /// tiles that use this extension. /// /// The KHR_materials_unlit extension is often applied to photogrammetry /// tilesets because lighting and shadows are already baked into their /// textures. /// public bool ignoreKhrMaterialsUnlit { get => this._ignoreKhrMaterialsUnlit; set { this._ignoreKhrMaterialsUnlit = value; this.RecreateTileset(); } } [SerializeField] private CesiumPointCloudShading _pointCloudShading = new CesiumPointCloudShading(); /// /// The CesiumPointCloudShading attached to this tileset. If the tileset /// contains points, their appearance can be configured with the point /// cloud shading parameters. /// public CesiumPointCloudShading pointCloudShading { get => this._pointCloudShading; } [SerializeField] private bool _suspendUpdate = false; /// /// Pauses level-of-detail and culling updates of this tileset. /// public bool suspendUpdate { get => this._suspendUpdate; set { this._suspendUpdate = value; } } // Normally tilesets are destroyed when anything in the editor changes. // But if suspendUpdate is the only value that has changed, the tileset // should not be reloaded, and instead continue updating after the setting // has been toggled. This variable saves the last value of suspendUpdate, // so OnValidate() can determine if this property was modified. If so, it // prevents the tileset from being destroyed. private bool _previousSuspendUpdate = false; internal bool previousSuspendUpdate { get => this._previousSuspendUpdate; set { this._previousSuspendUpdate = value; } } [SerializeField] private bool _showTilesInHierarchy = false; /// /// Whether to show tiles as individual game objects in the hierarchy window. /// public bool showTilesInHierarchy { get => this._showTilesInHierarchy; set { this._showTilesInHierarchy = value; this.RecreateTileset(); } } [SerializeField] private bool _updateInEditor = true; /// /// If true, this tileset is ticked/updated in the editor. If false, it is only ticked while playing (including Play-in-Editor). /// public bool updateInEditor { get => this._updateInEditor; set { this._updateInEditor = value; this.RecreateTileset(); } } [SerializeField] private bool _logSelectionStats = false; /// /// Whether to log details about the tile selection process. /// public bool logSelectionStats { get => this._logSelectionStats; set { this._logSelectionStats = value; } } [SerializeField] private bool _createPhysicsMeshes = true; /// /// Whether to generate physics meshes for this tileset. /// /// /// Disabling this option will improve the performance of tile loading, /// but it will no longer be possible to collide with the tileset since /// the physics meshes will not be created. /// public bool createPhysicsMeshes { get => this._createPhysicsMeshes; set { this._createPhysicsMeshes = value; this.RecreateTileset(); } } #endregion #region Public Methods /// /// Estimate the percentage of the tiles for the current view that have been loaded. /// /// /// A float value between 0 and 100 representing the load progress. /// public partial float ComputeLoadProgress(); /// /// Destroy and recreate the tilset. All tiles are unloaded, and then the tileset is reloaded /// based on the current view. /// public partial void RecreateTileset(); /// /// Zoom the Editor camera to this tileset. This method does nothing outside of the Editor. /// public partial void FocusTileset(); /// /// Initiates an asynchronous query for the height of this tileset at a list of /// cartographic positions, where the longitude (X) and latitude (Y) are given in degrees. /// The most detailed available tiles are used to determine each height. /// /// /// /// The height of the input positions is ignored, unless height sampling fails /// at that location. The output height is expressed in meters above the ellipsoid /// (usually WGS84), which should not be confused with a height above mean sea level. /// /// /// Use inside a coroutine to wait for the asynchronous height /// query to complete. /// /// /// /// The cartographic positions for which to sample heights. The X component is the /// Longitude (degrees), the Y component is the Latitude (degrees), and the Z component /// is the Height (meters). /// /// An asynchronous task that will provide the requested heights when complete. public partial Task SampleHeightMostDetailed(params double3[] longitudeLatitudeHeightPositions); #endregion #region Private Methods private partial void SetShowCreditsOnScreen(bool value); private partial void Start(); private partial void Update(); private partial void OnValidate(); private partial void OnEnable(); private partial void OnDisable(); internal partial void UpdateOverlayMaterialKeys(); #endregion #region Backward Compatibility void ISerializationCallbackReceiver.OnBeforeSerialize() { } void ISerializationCallbackReceiver.OnAfterDeserialize() { #if UNITY_EDITOR // For backward compatibility, tilesets loaded without a server should adopt // the default one rather than the current one. if (this._ionServer == null) this._useDefaultServer = true; #endif } #if UNITY_EDITOR private bool _useDefaultServer = false; #endif #endregion } }