Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Height query API for Unity #507

Merged
merged 18 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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 @@ -138,6 +138,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 @@ -273,6 +275,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
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
30 changes: 30 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,29 @@ public bool createPhysicsMeshes
/// </summary>
public partial void FocusTileset();

/// <summary>
/// Initiates an asynchronous query for the height of this tileset at a list of positions,
/// expressed as longitude and latitude. The most detailed available tiles are used to
/// determine each height.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <remarks>
/// <para>
/// The height of the input positions is ignored. On output, the height is
/// expressed in meters above the ellipsoid (usually WGS84), which should not
/// be confused with a height above mean sea level.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
/// </para>
/// <para>
/// Use <see cref="WaitForTask"/> inside a coroutine to wait for the asynchronous height
/// query to complete.
/// </para>
/// </remarks>
/// <param name="longitudeLatitudeHeightPositions">
/// The 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 +758,8 @@ public bool createPhysicsMeshes

#endregion

#region Backward Compatibility

void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
Expand All @@ -748,5 +777,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 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).
j9liu marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <remarks>
/// <para>
/// The longitudes and latitudes will match the values at the same index in the
/// original input positions. Each height will either be the height sampled
/// from the tileset at that position, or the original input height if the
/// height could not be sampled. To determine which, look at the value of
/// <see cref="CesiumSampleHeightResult.heightSampled"/> at the same index.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
/// </para>
/// <para>
/// The returned height is is measured from the ellipsoid, which is usually WGS84.
/// It should not be confused with a height about Mean Sea Level.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
/// </para>
/// </remarks>
public double3[] longitudeLatitudeHeightPositions { get; set; }

/// <summary>
/// Specifies whether the height for the position at this index was sampled successfully.
/// </summary>
/// <remarks>
/// If true, <see cref="CesiumSampleHeightResult.longitudeLatitudeHeightPositions"/> has
/// a valid height sampled from the tileset at this index. If false, the height
/// could not be sampled for the position at this index, and so the height in
/// <see cref="CesiumSampleHeightResult.longitudeLatitudeHeightPositions"/> is unchanged
/// from the original input height.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
/// </remarks>
public bool[] heightSampled { get; set; }
j9liu marked this conversation as resolved.
Show resolved Hide resolved

/// <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.heightSampled = 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.

141 changes: 141 additions & 0 deletions Tests/TestCesium3DTileset.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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;

// TODO: remove this
j9liu marked this conversation as resolved.
Show resolved Hide resolved
yield return null;

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

yield return new WaitForTask(task);

CesiumSampleHeightResult result = task.Result;
Assert.IsNotNull(result);
Assert.IsNotNull(result.longitudeLatitudeHeightPositions);
Assert.IsNotNull(result.heightSampled);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 0);
Assert.AreEqual(result.heightSampled.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;

// TODO: remove this
yield return null;

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.heightSampled);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 1);
Assert.AreEqual(result.heightSampled.Length, 1);
Assert.AreEqual(result.warnings.Length, 0);

Assert.AreEqual(result.heightSampled[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;

// TODO: remove this
yield return null;

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.heightSampled);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 2);
Assert.AreEqual(result.heightSampled.Length, 2);
Assert.AreEqual(result.warnings.Length, 0);

Assert.AreEqual(result.heightSampled[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.heightSampled[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;

// TODO: remove this
yield return null;

// 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.heightSampled);
Assert.IsNotNull(result.warnings);
Assert.AreEqual(result.longitudeLatitudeHeightPositions.Length, 1);
Assert.AreEqual(result.heightSampled.Length, 1);
Assert.AreEqual(result.warnings.Length, 0);

Assert.AreEqual(result.heightSampled[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);
}
}
11 changes: 11 additions & 0 deletions Tests/TestCesium3DTileset.cs.meta

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

Loading