I'm trying to create a tabletop map for a Quest Pro app in Unity 2021.3.24f. Inserting a tabletop map prefab into the scene seems to only render the center tiles. Doesn't seem to be specific to whether the map extent shape is circular, square, or rectangular. Could someone help me troubleshoot what might be happening? I'm able to get a circular map on a desktop version of the app.
Hi Geoffrey. Unfortunately, I don't have an absolute way to check. The shader issue I saw in the past was with a plugin for different hardware, so it wouldn't apply to your project. One more thing to try could be reimporting the ArcGIS Maps SDK package from the project explorer. This is what I do when I have shader cache issues. The reimport brings in the materials and shader subgraphs again.
In order to troubleshoot any further, I would need access to a project with the issue present.
Zack
On the topic of square and rectangle extents-
We are adding this functionality to the plugin in a future release.
In the meantime, you can use this modified tabletop controller to use squares and rectangles now.
// COPYRIGHT 1995-2023 ESRI
// TRADE SECRETS: ESRI PROPRIETARY AND CONFIDENTIAL
// Unpublished material - all rights reserved under the
// Copyright Laws of the United States and applicable international
// laws, treaties, and conventions.
//
// For additional information, contact:
// Attn: Contracts and Legal Department
// Environmental Systems Research Institute, Inc.
// 380 New York Street
// Redlands, California 92373
// USA
//
// email: legal@esri.com
using Esri.ArcGISMapsSDK.Components;
using Esri.ArcGISMapsSDK.Utils;
using Esri.GameEngine.Geometry;
using Unity.Mathematics;
using UnityEngine;
namespace Esri.ArcGISMapsSDK.Samples.Components
{
[DisallowMultipleComponent]
[ExecuteAlways]
[AddComponentMenu("ArcGIS Maps SDK/Samples/ArcGIS Tabletop Controller")]
public class ArcGISTabletopControllerComponent : MonoBehaviour
{
public Transform TransformWrapper;
public ArcGISMapComponent MapComponent;
public ArcGISLocationComponent CameraComponent;
private ArcGISPoint lastCenter;
private double? lastElevationOffset;
private double? lastRadius;
[SerializeField]
[OnChangedCall("OnCenterChanged")]
private ArcGISPoint center = new ArcGISPoint(0, 0, 0, ArcGISSpatialReference.WGS84());
public ArcGISPoint Center
{
get => center;
set
{
if (center != value)
{
center = value;
OnCenterChanged();
}
}
}
[SerializeField]
[OnChangedCall("OnElevationOffsetChanged")]
private double elevationOffset;
public double ElevationOffset
{
get => elevationOffset;
set
{
if (elevationOffset != value)
{
elevationOffset = value;
OnElevationOffsetChanged();
}
}
}
[SerializeField]
[OnChangedCall("OnRadiusChanged")]
private double radius;
public double Radius
{
get => radius;
set
{
if (radius != value)
{
radius = value;
OnRadiusChanged();
}
}
}
internal void OnCenterChanged()
{
PreUpdateTabletop();
}
private void OnDisable()
{
MapComponent.ExtentUpdated -= new ArcGISExtentUpdatedEventHandler(PostUpdateTabletop);
}
internal void OnElevationOffsetChanged()
{
PreUpdateTabletop();
}
private void OnEnable()
{
MapComponent.ExtentUpdated += new ArcGISExtentUpdatedEventHandler(PostUpdateTabletop);
lastCenter = null;
lastElevationOffset = null;
lastRadius = null;
PreUpdateTabletop();
}
internal void OnRadiusChanged()
{
PreUpdateTabletop();
}
private void PostUpdateTabletop(ArcGISExtentUpdatedEventArgs e)
{
if (!e.Type.HasValue)
{
return;
}
var areaMin = e.AreaMin.Value;
var areaMax = e.AreaMax.Value;
// Adjust center and scale only after all tiles were updated
var width = areaMax.x - areaMin.x;
var length = areaMax.y - areaMin.y;
var height = areaMax.z - areaMin.z;
var centerPosition = new double3(areaMin.x + width / 2.0, areaMin.y + length, areaMin.z + height / 2.0);
MapComponent.OriginPosition = MapComponent.View.WorldToGeographic(centerPosition);
var scale = 1.0 / math.max(width, length);
TransformWrapper.localScale = new Vector3((float)scale, (float)scale, (float)scale);
UpdateOffset();
// We need to force an HP Root update to account for scale changes
MapComponent.UpdateHPRoot();
}
private void PreUpdateTabletop()
{
bool needsOffsetUpdate = lastElevationOffset != ElevationOffset;
bool needsExtentUpdate = lastCenter != Center || lastRadius != Radius;
if (!needsExtentUpdate && !needsOffsetUpdate)
{
return;
}
if (needsExtentUpdate)
{
double2 shapeDimensions = new double2(Radius, Radius);
if (MapComponent.Extent.ExtentShape == MapExtentShapes.Rectangle)
{
var x = MapComponent.Extent.ShapeDimensions.x;
var y = MapComponent.Extent.ShapeDimensions.y;
// Maintain Radius as the larger value
if (x > y)
{
shapeDimensions = new double2(Radius, Radius * y / x);
}
else
{
shapeDimensions = new double2(Radius * x / y, Radius);
}
}
var newExtent = new ArcGISExtentInstanceData()
{
GeographicCenter = (ArcGISPoint)Center.Clone(),
ExtentShape = MapComponent.Extent.ExtentShape,
ShapeDimensions = shapeDimensions,
};
// Camera height should be radius to the furthest point. For square or rectangle, we use sqrt of 2 for diagonal distance to corner.
var radiusDistance = MapComponent.Extent.ExtentShape == MapExtentShapes.Circle ? Radius : (Radius / 2) * math.sqrt(2);
if (MapComponent.Extent == newExtent)
{
MapComponent.OriginPosition = newExtent.GeographicCenter;
var scale = 1.0 / (2 * radiusDistance);
TransformWrapper.localScale = new Vector3((float)scale, (float)scale, (float)scale);
}
else
{
MapComponent.Extent = new ArcGISExtentInstanceData()
{
GeographicCenter = (ArcGISPoint)Center.Clone(),
ExtentShape = MapComponent.Extent.ExtentShape,
ShapeDimensions = shapeDimensions,
};
}
CameraComponent.Position = new ArcGISPoint(Center.X, Center.Y, radiusDistance, Center.SpatialReference);
lastCenter = (ArcGISPoint)Center.Clone();
lastRadius = Radius;
}
if (needsOffsetUpdate)
{
UpdateOffset();
lastElevationOffset = ElevationOffset;
}
}
/// <summary>
/// Casts the ray against an horizontal plane plane centered at us and returns whether they intersected it.
/// Returns in currentPoint the point relative to us in Universe space that intersected the plane centered at us
/// </summary>
/// <param name="ray">The ray to test.</param>
/// <param name="currentPoint">The point relative to us in Universe space that intersected the plane centered at us.</param>
/// <returns>True if the ray intersects the plane, false otherwise.</returns>
/// <since>1.3.0</since>
private bool RaycastRelativeToCenter(Ray ray, out Vector3 currentPoint)
{
var planeCenter = MapComponent.transform.localToWorldMatrix.MultiplyPoint(Vector3.zero);
var dragStartPlane = new Plane(Vector3.up, planeCenter);
float dragStartEntry;
if (dragStartPlane.Raycast(ray, out dragStartEntry))
{
currentPoint = MapComponent.transform.worldToLocalMatrix.MultiplyPoint(ray.GetPoint(dragStartEntry));
return true;
}
else
{
currentPoint = Vector3.positiveInfinity;
}
return false;
}
/// <summary>
/// Casts the ray against an horizontal plane plane centered at us and returns whether they intersected it inside the extent.
/// Returns in currentPoint the point relative to us in Universe space that intersected the plane centered at us
/// </summary>
/// <param name="ray">The ray to test.</param>
/// <param name="currentPoint">The point relative to us in Universe space that intersected the plane centered at us.</param>
/// <returns>True if the ray intersects the plane, false otherwise.</returns>
/// <since>1.3.0</since>
public bool Raycast(Ray ray, out Vector3 currentPoint)
{
RaycastRelativeToCenter(ray, out currentPoint);
bool insideRadius = false;
if (MapComponent.Extent.ExtentShape == MapExtentShapes.Circle)
{
insideRadius = Vector3.Distance(currentPoint, Vector3.zero) <= Radius;
}
else if (MapComponent.Extent.ExtentShape == MapExtentShapes.Square)
{
insideRadius = math.abs(currentPoint.x) < MapComponent.Extent.ShapeDimensions.x / 2 && math.abs(currentPoint.z) < MapComponent.Extent.ShapeDimensions.x / 2;
}
else
{
insideRadius = math.abs(currentPoint.x) < MapComponent.Extent.ShapeDimensions.x / 2 && math.abs(currentPoint.z) < MapComponent.Extent.ShapeDimensions.y / 2;
}
currentPoint = MapComponent.transform.localToWorldMatrix.MultiplyPoint(currentPoint);
currentPoint = transform.worldToLocalMatrix.MultiplyPoint(currentPoint);
return insideRadius;
}
private void Update()
{
if (!Application.isPlaying)
{
return;
}
PreUpdateTabletop();
}
private void UpdateOffset()
{
var newPosition = TransformWrapper.localPosition;
newPosition.y = (float)(ElevationOffset * TransformWrapper.localScale.x);
TransformWrapper.localPosition = newPosition;
}
}
}
Zack
I was trying to share a link to our Unity project without propriety code but we've run into another issue. There's a huge memory leak with the Tabletop prefab in the Unity Editor. It happens when build target is set to Android and a tabletop prefab is in the scene. And it only seems to happen when I switch from Unity to some other application (e.g. Chrome, or even Task Manager). When I switch back to Unity, the memory leak stops or slows down.
Switching to another application cause the Unity memory usage to increase fast but seemingly linearly. For example, from around 1GB to maybe 14 GB in a few seconds. But switching back to Unity keeps it stable again at 14GB. Switching away from unity again increases memory usage till around 99% total memory and then Unity can crash.
I'll DM a link to a Unity project that exhibits this issue. But it should also be visible in a blank Unity 2022.3.9f1 project and Tabletop Map scene with the latest InputSystem, Oculus, and ArcGIS packages imported .
Hello!
I'm having a similar problem with HoloLens 2. In Unity Editor it renders quite well but when I compile the map (and a test cube that I create with the same material than the map) it doesn't render in the HoloLens device. I am using URP too. I've already updated the render pipeline and the shaders but with no positive result.
The object is present because I see the colliders but it doesn't render.
Update: We've had to move our project to Unity 2022 from Unity 2021 to overcome this partial rendering issue. There might've been a package that was interfering with the ArcGIS renderer. But after testing by removing each of packages or adding them to a blank project we couldn't determine which package it could be. Even now, the Unity 2022 Editor is showing a partially rendered map, but the map is rendered correctly when actually built to the Quest device.