Skip to content

Commit

Permalink
Merge pull request #248 from CesiumGS/tile-excluder
Browse files Browse the repository at this point in the history
Add ability to exclude tiles by implementing a C# class.
  • Loading branch information
j9liu authored Mar 31, 2023
2 parents b8faae3 + c7f314e commit d079b03
Show file tree
Hide file tree
Showing 17 changed files with 522 additions and 59 deletions.
5 changes: 3 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
{
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
Original file line number Diff line number Diff line change
@@ -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
{
/// <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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit d079b03

Please sign in to comment.