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

Add ability to exclude tiles by implementing a C# class. #248

Merged
merged 19 commits into from Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGES.md
Expand Up @@ -7,8 +7,9 @@
- Added `CesiumPointCloudShading`, which allows point cloud tilesets to be rendered with attenuation based on geometric error. Attenuation is currently only supported in URP.
- Added support for Unity's built-in render pipeline.
- `GameObject` instances created for the tiles in a `Cesium3DTileset` now inherit the `layer` of the parent tileset.
- Added setting in CesiumRuntimeSettings to configure the maximum number of items to keep in the Sqlite cache.
- Added setting in CesiumRuntimeSettings to configure the number of requests to the cache database before pruning.
- Added the `CesiumTileExcluder` abstract class. By creating a class derived from `CesiumTileExcluder`, then adding it to a `Cesium3DTileset`'s game object, you can implement custom rules for excluding tiles in the `Cesium3DTileset` from loading and rendering.
- Added setting in `CesiumRuntimeSettings` to configure the maximum number of items to keep in the Sqlite cache.
- Added setting in `CesiumRuntimeSettings` to configure the number of requests to the cache database before pruning.

##### Fixes :wrench:

Expand Down
1 change: 1 addition & 0 deletions Editor/ConfigureReinterop.cs
Expand Up @@ -165,6 +165,7 @@ public void ExposeToCPP()

CesiumRasterOverlay[] rasterOverlays = tileset.gameObject.GetComponents<CesiumRasterOverlay>();
CesiumRasterOverlay overlay = rasterOverlays[0];
UnityEngine.Object.DestroyImmediate(overlay, true);
UnityEngine.Object.DestroyImmediate(overlay);

CesiumIonRasterOverlay[] ionRasterOverlays =
Expand Down
39 changes: 39 additions & 0 deletions Reinterop~/CSharpTypeUtility.cs
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Text.RegularExpressions;

namespace Reinterop
{
Expand Down Expand Up @@ -63,5 +64,43 @@ public static IEnumerable<ISymbol> FindMembers(ITypeSymbol type, string name)
current = current.BaseType;
}
}

public static IPropertySymbol? GetAutoPropertyForField(IFieldSymbol field)
{
string fieldName = field.Name;

IPropertySymbol? autoProperty = field.AssociatedSymbol as IPropertySymbol;

// Unfortunately, the above will only work if the C# compiler is looking at the source code for the property.
// So for backing fields in referenced assemblies, we need to do this more manually.
if (autoProperty == null && fieldName.EndsWith("__BackingField"))
{
Match match = new Regex("<(.+)>k__BackingField").Match(fieldName);
if (match.Success && match.Groups.Count > 1)
{
autoProperty = CSharpTypeUtility.FindMember(field.ContainingType, match.Groups[1].Value) as IPropertySymbol;
}
}

return autoProperty;
}

public static string GetFieldName(IFieldSymbol field)
{
IPropertySymbol? autoProperty = GetAutoPropertyForField(field);
if (autoProperty != null)
return autoProperty.Name;
else
return field.Name;
}

public static bool GetFieldIsPrivate(IFieldSymbol field)
{
IPropertySymbol? autoProperty = GetAutoPropertyForField(field);
if (autoProperty != null)
return autoProperty.DeclaredAccessibility != Accessibility.Public;
else
return field.DeclaredAccessibility != Accessibility.Public;
}
}
}
99 changes: 64 additions & 35 deletions Reinterop~/Constructors.cs
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Xml.Linq;

namespace Reinterop
Expand All @@ -7,17 +8,6 @@ internal class Constructors
{
public static void Generate(CppGenerationContext context, TypeToGenerate item, GeneratedResult result)
{
// TODO: We're not currently generating constructors for blittable value types. They'll need to be slightly different (no handle).
// We only need handle management for non-static classes.

GeneratedCppDeclaration declaration = result.CppDeclaration;
if (declaration.Type.Kind != InteropTypeKind.ClassWrapper &&
declaration.Type.Kind != InteropTypeKind.NonBlittableStructWrapper &&
declaration.Type.Kind != InteropTypeKind.Delegate)
{
return;
}

if (item.Type.IsStatic)
GenerateStatic(context, item, result);
else
Expand Down Expand Up @@ -50,8 +40,12 @@ private static void GenerateSingleNonStatic(CppGenerationContext context, TypeTo
GeneratedInit init = result.Init;

var parameters = constructor.Parameters.Select(parameter => (Name: parameter.Name, Type: CppType.FromCSharp(context, parameter.Type).AsParameterType()));
var interopReturnType = declaration.Type.AsInteropType();
var returnType = declaration.Type;
var interopReturnType = returnType.AsInteropType();
var interopParameters = parameters.Select(parameter => (ParameterName: parameter.Name, CallSiteName: parameter.Name, Type: parameter.Type, InteropType: parameter.Type.AsInteropType()));

bool hasStructRewrite = Interop.RewriteStructReturn(ref interopParameters, ref returnType, ref interopReturnType);

var interopParameterStrings = interopParameters.Select(parameter => $"{parameter.InteropType.GetFullyQualifiedName()} {parameter.ParameterName}");

string interopFunctionName = $"Construct_{Interop.HashParameters(constructor.Parameters)}";
Expand Down Expand Up @@ -83,30 +77,65 @@ private static void GenerateSingleNonStatic(CppGenerationContext context, TypeTo
CSharpContent: csContent
));

// Constructor declaration
var parameterStrings = parameters.Select(parameter => $"{parameter.Type.GetFullyQualifiedName()} {parameter.Name}");
declaration.Elements.Add(new(
Content: $"{declaration.Type.Name}({string.Join(", ", parameterStrings)});",
TypeDeclarationsReferenced: parameters.Select(parameter => parameter.Type)
));

// Constructor definition
var parameterPassStrings = interopParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName));
definition.Elements.Add(new(
Content:
$$"""
{{definition.Type.Name}}{{templateSpecialization}}::{{definition.Type.Name}}({{string.Join(", ", parameterStrings)}})
: _handle({{interopFunctionName}}({{string.Join(", ", parameterPassStrings)}}))
// For blittable structs, add static "Construct" functions rather than C++ constructors.
// This way we can use default construction and member initialization and avoid a call into C# to
// construct simple blittable types, but can still call explicit C# constructors when necessary.
if (declaration.Type.Kind == InteropTypeKind.BlittableStruct)
{
// Constructor declaration
var parameterStrings = parameters.Select(parameter => $"{parameter.Type.GetFullyQualifiedName()} {parameter.Name}");
declaration.Elements.Add(new(
Content: $"static {declaration.Type.Name} Construct({string.Join(", ", parameterStrings)});",
TypeDeclarationsReferenced: parameters.Select(parameter => parameter.Type)
));

// Constructor definition
var parameterPassStrings = interopParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName));
definition.Elements.Add(new(
Content:
$$"""
{{definition.Type.Name}} {{definition.Type.Name}}{{templateSpecialization}}::Construct({{string.Join(", ", parameterStrings)}})
{
{{definition.Type.Name}} result;
{{interopFunctionName}}({{string.Join(", ", parameterPassStrings)}});
return result;
}
""",
TypeDefinitionsReferenced: new[]
{
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
interopReturnType,
CppObjectHandle.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
definition.Type,
interopReturnType,
CppObjectHandle.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
}
else
{
// Constructor declaration
var parameterStrings = parameters.Select(parameter => $"{parameter.Type.GetFullyQualifiedName()} {parameter.Name}");
declaration.Elements.Add(new(
Content: $"{declaration.Type.Name}({string.Join(", ", parameterStrings)});",
TypeDeclarationsReferenced: parameters.Select(parameter => parameter.Type)
));

// Constructor definition
var parameterPassStrings = interopParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName));
definition.Elements.Add(new(
Content:
$$"""
{{definition.Type.Name}}{{templateSpecialization}}::{{definition.Type.Name}}({{string.Join(", ", parameterStrings)}})
: _handle({{interopFunctionName}}({{string.Join(", ", parameterPassStrings)}}))
{
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
interopReturnType,
CppObjectHandle.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
}
}
}
}
24 changes: 2 additions & 22 deletions Reinterop~/Fields.cs
Expand Up @@ -43,28 +43,8 @@ private static void GenerateField(CppGenerationContext context, TypeToGenerate i
if (field.IsStatic)
return;

string fieldName = field.Name;
bool isPrivate = field.DeclaredAccessibility != Accessibility.Public;

// If this is a backing field for an automatic property, use the property name instead.
IPropertySymbol? autoProperty = field.AssociatedSymbol as IPropertySymbol;

// Unfortunately, the above will only work if the C# compiler is looking at the source code for the property.
// So for backing fields in referenced assemblies, we need to do this more manually.
if (autoProperty == null && fieldName.EndsWith("__BackingField"))
{
Match match = new Regex("<(.+)>k__BackingField").Match(fieldName);
if (match.Success && match.Groups.Count > 1)
{
autoProperty = CSharpTypeUtility.FindMember(field.ContainingType, match.Groups[1].Value) as IPropertySymbol;
}
}

if (autoProperty != null)
{
fieldName = autoProperty.Name;
isPrivate = autoProperty.DeclaredAccessibility != Accessibility.Public;
}
string fieldName = CSharpTypeUtility.GetFieldName(field);
bool isPrivate = CSharpTypeUtility.GetFieldIsPrivate(field);

CppType fieldType = CppType.FromCSharp(context, field.Type);

Expand Down
37 changes: 37 additions & 0 deletions Runtime/Cesium3DTile.cs
@@ -0,0 +1,37 @@

using Reinterop;
using System;
using Unity.Mathematics;
using UnityEngine;

namespace CesiumForUnity
{
/// <summary>
/// Represents a tile in a <see cref="Cesium3DTileset"/> and allows information
/// about the tile to be queried from the underlying C++ tile representation.
/// </summary>
[ReinteropNativeImplementation("CesiumForUnityNative::Cesium3DTileImpl", "Cesium3DTileImpl.h", staticOnly: true)]
public partial class Cesium3DTile
j9liu marked this conversation as resolved.
Show resolved Hide resolved
{
internal double4x4 _transform;
internal IntPtr _pTile;

internal Cesium3DTile()
{
}

/// <summary>
/// Gets the axis-aligned bounding box of this tile. If this tile came from a <see cref="CesiumTileExcluder"/>,
/// the bounding box is expressed in the local coordinates of the excluder's game object.
/// </summary>
public Bounds bounds
{
get
{
return Cesium3DTile.getBounds(this._pTile, this._transform);
}
}

private static partial Bounds getBounds(IntPtr pTile, double4x4 ecefToLocalMatrix);
}
}
11 changes: 11 additions & 0 deletions Runtime/Cesium3DTile.cs.meta

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

47 changes: 47 additions & 0 deletions Runtime/CesiumTileExcluder.cs
@@ -0,0 +1,47 @@
using Reinterop;
using UnityEngine;

namespace CesiumForUnity
{
/// <summary>
/// The abstract base class for <see cref="Cesium3DTileset"/> tile excluders. By creating a class derived
/// from `CesiumTileExcluder`, then adding it to a game object containing a `Cesium3DTileset` (or one of
/// its parents), you can implement custom rules for excluding tiles in the `Cesium3DTileset` from loading
/// and rendering.
/// </summary>
[ExecuteInEditMode]
[ReinteropNativeImplementation("CesiumForUnityNative::CesiumTileExcluderImpl", "CesiumTileExcluderImpl.h", staticOnly: true)]
public abstract partial class CesiumTileExcluder : MonoBehaviour
j9liu marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Determines whether the given tile should be excluded from loading and rendering. If a tile is
/// excluded, all of its children and other descendants in the bounding volume hierarchy will be
/// excluded as well.
/// </summary>
/// <param name="tile">The tile to check. This instance is only valid for the duration of this call. Saving
/// it and using it later will result in undefined behavior, including crashes.</param>
/// <returns>True if the tile should be excluded, false if the tile should be loaded and rendered.</returns>
public abstract bool ShouldExclude(Cesium3DTile tile);

protected virtual void OnEnable()
{
Cesium3DTileset[] tilesets = this.GetComponentsInChildren<Cesium3DTileset>();
foreach (Cesium3DTileset tileset in tilesets)
{
this.AddToTileset(tileset);
}
}

protected virtual void OnDisable()
{
Cesium3DTileset[] tilesets = this.GetComponentsInChildren<Cesium3DTileset>();
foreach (Cesium3DTileset tileset in tilesets)
{
this.RemoveFromTileset(tileset);
}
}

internal partial void AddToTileset(Cesium3DTileset tileset);
internal partial void RemoveFromTileset(Cesium3DTileset tileset);
}
}
11 changes: 11 additions & 0 deletions Runtime/CesiumTileExcluder.cs.meta

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

11 changes: 11 additions & 0 deletions Runtime/ConfigureReinterop.cs
Expand Up @@ -160,6 +160,7 @@ public void ExposeToCPP()
meshRenderer.sharedMaterial = meshRenderer.sharedMaterial;
meshRenderer.material.shader = meshRenderer.material.shader;
UnityEngine.Object.Destroy(meshGameObject);
UnityEngine.Object.DestroyImmediate(meshGameObject, true);
UnityEngine.Object.DestroyImmediate(meshGameObject);

MeshFilter meshFilter = new MeshFilter();
Expand Down Expand Up @@ -341,6 +342,7 @@ public void ExposeToCPP()
georeference.ecefY = georeference.ecefY;
georeference.ecefZ = georeference.ecefZ;
georeference.originAuthority = georeference.originAuthority;
double4x4 ecefToLocal = georeference.ecefToLocalMatrix;

CesiumGeoreference inParent = go.GetComponentInParent<CesiumGeoreference>();
inParent.MoveOrigin();
Expand Down Expand Up @@ -458,6 +460,15 @@ Cesium3DTilesetLoadFailureDetails tilesetDetails
globeAnchor._lastLocalToWorld = new Matrix4x4();
globeAnchor.UpdateGeoreferenceIfNecessary();

CesiumTileExcluder[] excluders = go.GetComponentsInParent<CesiumTileExcluder>();
CesiumTileExcluder excluder = excluders[0];
excluder.AddToTileset(null);
excluder.RemoveFromTileset(null);
excluder.ShouldExclude(new Cesium3DTile());
Cesium3DTile tile = new Cesium3DTile();
tile._transform = new double4x4();
tile._pTile = IntPtr.Zero;

Cesium3DTileInfo info;
info.usesAdditiveRefinement = true;
info.geometricError = 1.0f;
Expand Down