Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ jobs:
name: Windows Package
path: d:\cesium\CesiumForUnityBuildProject\*.tgz
- name: Run Tests
env:
CESIUM_ION_TOKEN_FOR_TESTS: ${{ secrets.CESIUM_ION_TOKEN_FOR_TESTS }}
run: |
start -FilePath "C:\Program Files\Unity\Hub\Editor\2022.3.41f1\Editor\Unity.exe" -ArgumentList "-runTests -batchmode -projectPath d:\cesium\CesiumForUnityBuildProject -testResults d:\cesium\temp\TestResults.xml -testPlatform PlayMode -logFile d:\cesium\temp\test-log.txt" -Wait
cat d:\cesium\temp\test-log.txt
Expand Down Expand Up @@ -271,6 +273,8 @@ jobs:
name: macOS Package
path: ~/cesium/CesiumForUnityBuildProject/*.tgz
- name: Run Tests
env:
CESIUM_ION_TOKEN_FOR_TESTS: ${{ secrets.CESIUM_ION_TOKEN_FOR_TESTS }}
run: |
/Applications/Unity/Hub/Editor/2022.3.41f1/Unity.app/Contents/MacOS/Unity -runTests -batchmode -projectPath ~/cesium/CesiumForUnityBuildProject -testResults ~/cesium/CesiumForUnityBuildProject/TestResults.xml -testPlatform PlayMode -logFile ~/cesium/CesiumForUnityBuildProject/test-log.txt
cat ~/cesium/CesiumForUnityBuildProject/test-log.txt
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
##### Additions :tada:

- Added a new `CesiumCameraManager` component. It allows configuration of the cameras to use for Cesium3DTileset culling and level-of-detail.
- Added `SampleHeightMostDetailed` method to `Cesium3DTileset`. It asynchronously queries the height of a tileset at a list of positions.

##### Fixes :wrench:

Expand Down
14 changes: 13 additions & 1 deletion Reinterop~/CSharpObjectHandleUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ public static void FreeHandle(IntPtr handle)
if (handle == IntPtr.Zero)
return;

GCHandle.FromIntPtr(handle).Free();
try
{
GCHandle.FromIntPtr(handle).Free();
}
catch (ArgumentException e)
{
// The "GCHandle value belongs to a different domain" exception tends
// to happen on AppDomain reload, which is common in Unity.
// Catch the exception to prevent it propagating through our native
// code and blowing things up.
// See: https://github.com/CesiumGS/cesium-unity/issues/18
System.Console.WriteLine(e.ToString());
}
}

public static object GetObjectFromHandle(IntPtr handle)
Expand Down
10 changes: 5 additions & 5 deletions Reinterop~/MethodsImplementedInCpp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ private static void GenerateMethod(CppGenerationContext context, TypeToGenerate

CSharpType csWrapperType = CSharpType.FromSymbol(context, item.Type);
CSharpType csReturnType = CSharpType.FromSymbol(context, method.ReturnType);
var csParameters = method.Parameters.Select(parameter => (Name: parameter.Name, CallName: parameter.Name, Type: CSharpType.FromSymbol(context, parameter.Type)));
var csParameters = method.Parameters.Select(parameter => (Name: parameter.Name, CallName: parameter.Name, Type: CSharpType.FromSymbol(context, parameter.Type), IsParams: parameter.IsParams));
var csParametersInterop = csParameters;
var implementationPointer = new CSharpType(context, InteropTypeKind.Primitive, csWrapperType.Namespaces, csWrapperType.Name + ".ImplementationHandle", csWrapperType.SpecialType, null);

Expand All @@ -354,13 +354,13 @@ private static void GenerateMethod(CppGenerationContext context, TypeToGenerate
{
csParametersInterop = new[]
{
(Name: "implementation", CallName: "_implementation", Type: implementationPointer)
(Name: "implementation", CallName: "_implementation", Type: implementationPointer, IsParams: false)
}.Concat(csParametersInterop);
}

csParametersInterop = new[]
{
(Name: "thiz", CallName: "this", Type: csWrapperType),
(Name: "thiz", CallName: "this", Type: csWrapperType, IsParams: false),
}.Concat(csParametersInterop);
}

Expand All @@ -370,7 +370,7 @@ private static void GenerateMethod(CppGenerationContext context, TypeToGenerate
{
csParametersInterop = csParametersInterop.Concat(new[]
{
(Name: "pReturnValue", CallName: "&returnValue", Type: csInteropReturnType.AsPointer())
(Name : "pReturnValue", CallName : "&returnValue", Type : csInteropReturnType.AsPointer(), IsParams : false)
});
csInteropReturnType = CSharpType.FromSymbol(context, returnType.Kind == InteropTypeKind.Nullable ? context.Compilation.GetSpecialType(SpecialType.System_Byte) : context.Compilation.GetSpecialType(SpecialType.System_Void));
}
Expand Down Expand Up @@ -429,7 +429,7 @@ private static void GenerateMethod(CppGenerationContext context, TypeToGenerate
result.CSharpPartialMethodDefinitions.Methods.Add(new(
methodDefinition:
$$"""
{{modifiers}} partial {{csReturnType.GetFullyQualifiedName()}} {{method.Name}}({{string.Join(", ", csParameters.Select(parameter => $"{parameter.Type.GetFullyQualifiedName()} {parameter.Name}"))}})
{{modifiers}} partial {{csReturnType.GetFullyQualifiedName()}} {{method.Name}}({{string.Join(", ", csParameters.Select(parameter => $"{(parameter.IsParams ? "params " : "")}{parameter.Type.GetFullyQualifiedName()} {parameter.Name}"))}})
{
unsafe
{
Expand Down
31 changes: 31 additions & 0 deletions Runtime/Cesium3DTileset.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using Reinterop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;

namespace CesiumForUnity
Expand Down Expand Up @@ -714,6 +718,30 @@ public bool createPhysicsMeshes
/// </summary>
public partial void FocusTileset();

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// Use <see cref="WaitForTask"/> inside a coroutine to wait for the asynchronous height
/// query to complete.
/// </para>
/// </remarks>
/// <param name="longitudeLatitudeHeightPositions">
/// 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).
/// </param>
/// <returns>An asynchronous task that will provide the requested heights when complete.</returns>
public partial Task<CesiumSampleHeightResult> SampleHeightMostDetailed(params double3[] longitudeLatitudeHeightPositions);

#endregion

#region Private Methods
Expand All @@ -731,6 +759,8 @@ public bool createPhysicsMeshes

#endregion

#region Backward Compatibility

void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
Expand All @@ -748,5 +778,6 @@ void ISerializationCallbackReceiver.OnAfterDeserialize()
#if UNITY_EDITOR
private bool _useDefaultServer = false;
#endif
#endregion
}
}
47 changes: 47 additions & 0 deletions Runtime/CesiumSampleHeightResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Unity.Mathematics;

namespace CesiumForUnity
{
/// <summary>
/// The asynchronous result of a call to <see cref="Cesium3DTileset.SampleHeightMostDetailed"/>.
/// </summary>
public class CesiumSampleHeightResult
{
/// <summary>
/// The positions and their sampled heights. The X component is Longitude (degrees),
/// the Y component is Latitude (degrees), and the Z component is Height (meters)
/// above the ellipsoid (usually WGS84).
/// </summary>
/// <remarks>
/// <para>
/// For each resulting position, its longitude and latitude values will match
/// values from its input. Its height will either be the height sampled from
/// the tileset at that position, or the original input height if the sample
/// was unsuccessful. To determine which, look at the value of
/// <see cref="CesiumSampleHeightResult.sampleSuccess"/> at the same index.
/// </para>
/// <para>
/// The returned height is measured from the ellipsoid (usually WGS84) and
/// should not be confused with a height above Mean Sea Level.
/// </para>
/// </remarks>
public double3[] longitudeLatitudeHeightPositions { get; set; }

/// <summary>
/// Indicates whether the height for the position at the corresponding index was sampled
/// successfully.
/// </summary>
/// <remarks>
/// If true, then the corresponding position in
/// <see cref="CesiumSampleHeightResult.longitudeLatitudeHeightPositions"/> uses
/// the height sampled from the tileset. If false, the height could not be sampled for
/// the position, so its height is the same as the original input height.
/// </remarks>
public bool[] sampleSuccess { get; set; }

/// <summary>
/// Any warnings that occurred while sampling heights.
/// </summary>
public string[] warnings { get; set; }
}
}
11 changes: 11 additions & 0 deletions Runtime/CesiumSampleHeightResult.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions Runtime/ConfigureReinterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,21 @@ Cesium3DTilesetLoadFailureDetails tilesetDetails
camera = manager.additionalCameras[i];
}

TaskCompletionSource<CesiumSampleHeightResult> promise = new TaskCompletionSource<CesiumSampleHeightResult>();
promise.SetException(new Exception("message"));
CesiumSampleHeightResult result = new CesiumSampleHeightResult();
result.longitudeLatitudeHeightPositions = null;
result.sampleSuccess = null;
result.warnings = null;
promise.SetResult(result);
Task<CesiumSampleHeightResult> task = promise.Task;

double3[] positions = null;
for (int i = 0; i < positions.Length; ++i)
{
positions[i] = positions[i];
}

#if UNITY_EDITOR
SceneView sv = SceneView.lastActiveSceneView;
sv.pivot = sv.pivot;
Expand Down
25 changes: 25 additions & 0 deletions Runtime/WaitForTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using UnityEngine;

namespace CesiumForUnity
{
/// <summary>
/// A YieldInstruction that can be yielded from a coroutine in order to wait
/// until a given task completes.
/// </summary>
public class WaitForTask : CustomYieldInstruction
{
private IAsyncResult _task;

/// <summary>
/// Initializes a new instance.
/// </summary>
/// <param name="task">The task to wait for.</param>
public WaitForTask(IAsyncResult task)
{
this._task = task;
}

public override bool keepWaiting => !this._task.IsCompleted;
}
}
11 changes: 11 additions & 0 deletions Runtime/WaitForTask.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 129 additions & 0 deletions Tests/TestCesium3DTileset.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using CesiumForUnity;
using NUnit.Framework;
using System;
using System.Collections;
using System.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.TestTools;

public class TestCesium3DTileset
{
[UnityTest]
public IEnumerator SampleHeightMostDetailedWorksWithAnEmptyArrayOfPositions()
{
GameObject go = new GameObject();
go.name = "Cesium World Terrain";
Cesium3DTileset tileset = go.AddComponent<Cesium3DTileset>();
tileset.ionAccessToken = Environment.GetEnvironmentVariable("CESIUM_ION_TOKEN_FOR_TESTS") ?? "";
tileset.ionAssetID = 1;

Task<CesiumSampleHeightResult> task = tileset.SampleHeightMostDetailed();

yield return new WaitForTask(task);

CesiumSampleHeightResult result = task.Result;
Assert.IsNotNull(result);
Assert.IsNotNull(result.longitudeLatitudeHeightPositions);
Assert.IsNotNull(result.sampleSuccess);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 0);
Assert.AreEqual(result.sampleSuccess.Length, 0);
Assert.AreEqual(result.warnings.Length, 0);
}

[UnityTest]
public IEnumerator SampleHeightMostDetailedWorksWithASinglePosition()
{
GameObject go = new GameObject();
go.name = "Cesium World Terrain";
Cesium3DTileset tileset = go.AddComponent<Cesium3DTileset>();
tileset.ionAccessToken = Environment.GetEnvironmentVariable("CESIUM_ION_TOKEN_FOR_TESTS") ?? "";
tileset.ionAssetID = 1;

Task<CesiumSampleHeightResult> task = tileset.SampleHeightMostDetailed(new double3(-105.1, 40.1, 1.0));

yield return new WaitForTask(task);

CesiumSampleHeightResult result = task.Result;
Assert.IsNotNull(result);
Assert.IsNotNull(result.longitudeLatitudeHeightPositions);
Assert.IsNotNull(result.sampleSuccess);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 1);
Assert.AreEqual(result.sampleSuccess.Length, 1);
Assert.AreEqual(result.warnings.Length, 0);

Assert.AreEqual(result.sampleSuccess[0], true);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].x, -105.1, 1e-12);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].y, 40.1, 1e-12);
// Returned height should be different from the original height (1.0) by at least one meter.
Assert.IsTrue(math.abs(result.longitudeLatitudeHeightPositions[0].z - 1.0) > 1.0);
}

[UnityTest]
public IEnumerator SampleHeightMostDetailedWorksWithMultiplePositions()
{
GameObject go = new GameObject();
go.name = "Cesium World Terrain";
Cesium3DTileset tileset = go.AddComponent<Cesium3DTileset>();
tileset.ionAccessToken = Environment.GetEnvironmentVariable("CESIUM_ION_TOKEN_FOR_TESTS") ?? "";
tileset.ionAssetID = 1;

Task<CesiumSampleHeightResult> task = tileset.SampleHeightMostDetailed(
new double3(-105.1, 40.1, 1.0),
new double3(105.1, -40.1, 1.0));

yield return new WaitForTask(task);

CesiumSampleHeightResult result = task.Result;
Assert.IsNotNull(result);
Assert.IsNotNull(result.longitudeLatitudeHeightPositions);
Assert.IsNotNull(result.sampleSuccess);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 2);
Assert.AreEqual(result.sampleSuccess.Length, 2);
Assert.AreEqual(result.warnings.Length, 0);

Assert.AreEqual(result.sampleSuccess[0], true);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].x, -105.1, 1e-12);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].y, 40.1, 1e-12);
// Returned height should be different from the original height (1.0) by at least one meter.
Assert.IsTrue(math.abs(result.longitudeLatitudeHeightPositions[0].z - 1.0) > 1.0);

Assert.AreEqual(result.sampleSuccess[1], true);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[1].x, 105.1, 1e-12);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[1].y, -40.1, 1e-12);
// Returned height should be different from the original height (1.0) by at least one meter.
Assert.IsTrue(math.abs(result.longitudeLatitudeHeightPositions[1].z - 1.0) > 1.0);
}

[UnityTest]
public IEnumerator SampleHeightMostDetailedIndicatesNotSampledForPositionOutsideTileset()
{
GameObject go = new GameObject();
go.name = "Melbourne Photogrammetry";
Cesium3DTileset tileset = go.AddComponent<Cesium3DTileset>();
tileset.ionAccessToken = Environment.GetEnvironmentVariable("CESIUM_ION_TOKEN_FOR_TESTS") ?? "";
tileset.ionAssetID = 69380;

// Somewhere in Sydney, not Melbourne
Task<CesiumSampleHeightResult> task = tileset.SampleHeightMostDetailed(new double3(151.20972, -33.87100, 1.0));

yield return new WaitForTask(task);

CesiumSampleHeightResult result = task.Result;
Assert.IsNotNull(result);
Assert.IsNotNull(result.longitudeLatitudeHeightPositions);
Assert.IsNotNull(result.sampleSuccess);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 1);
Assert.AreEqual(result.sampleSuccess.Length, 1);
Assert.AreEqual(result.warnings.Length, 0);

Assert.AreEqual(result.sampleSuccess[0], false);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].x, 151.20972, 1e-12);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].y, -33.87100, 1e-12);
Assert.AreEqual(result.longitudeLatitudeHeightPositions[0].z, 1.0, 1e-12);
}
}
Loading