441 lines
15 KiB
C#
441 lines
15 KiB
C#
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Oculus SDK License Agreement (the "License");
|
|
* you may not use the Oculus SDK except in compliance with the License,
|
|
* which is provided at the time of installation or download, or which
|
|
* otherwise accompanies this software in either electronic or hard copy form.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://developer.oculus.com/licenses/oculussdk/
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Meta.XR.Util;
|
|
using Unity.Collections;
|
|
using Unity.Jobs;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// A <see cref="OVRSceneAnchor"/> that has a 2D bounds associated with it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Examples of scene anchors that would be associated with this component include Wall, Floor, and Ceiling.
|
|
///
|
|
/// <see cref="OVRSceneManager"/> and associated classes are deprecated (v65), please use [MR Utility Kit](https://developer.oculus.com/documentation/unity/unity-mr-utility-kit-overview)" instead.
|
|
/// </remarks>
|
|
[DisallowMultipleComponent]
|
|
[RequireComponent(typeof(OVRSceneAnchor))]
|
|
[HelpURL("https://developer.oculus.com/documentation/unity/unity-scene-use-scene-anchors/#further-scene-model-unity-components")]
|
|
[Obsolete(OVRSceneManager.DeprecationMessage)]
|
|
[Feature(Feature.Scene)]
|
|
public class OVRScenePlane : MonoBehaviour, IOVRSceneComponent
|
|
{
|
|
/// <summary>
|
|
/// The plane's width (in the local X-direction), in meters.
|
|
/// </summary>
|
|
public float Width { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The plane's height (in the local Y-direction), in meters.
|
|
/// </summary>
|
|
public float Height { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The offset of the plane with respect to the anchor's pivot as a Vector2.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The offset is mostly zero, as objects have the anchor's pivot
|
|
/// aligned with centroid of the plane.
|
|
///
|
|
/// The Offset is provided in the local coordinate space of the
|
|
/// children. See <seealso cref="OVRSceneAnchor"/> to see the
|
|
/// transformation of Unity and OpenXR coordinate systems.
|
|
public Vector2 Offset { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The dimensions of the plane as a Vector2.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This property corresponds to a Vector whose components are
|
|
/// (<see cref="Width"/>, <see cref="Height"/>).
|
|
/// </remarks>
|
|
public Vector2 Dimensions => new Vector2(Width, Height);
|
|
|
|
/// <summary>
|
|
/// The vertices of the 2D plane boundary as list of Vector2.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The vertices are provided in clockwise order and in plane-space (relative to the
|
|
/// plane's local space). The X and Y coordinates of the 2D coordinates are the same as the 3D
|
|
/// coordinates. To map the 2D vertices (x, y) to 3D, set the Z coordinate to zero: (x, y, 0).
|
|
/// </remarks>
|
|
public IReadOnlyList<Vector2> Boundary => _boundary;
|
|
|
|
/// <summary>
|
|
/// Whether the child transforms will be scaled according to the dimensions of this plane.
|
|
/// </summary>
|
|
/// <remarks>If set to True, all the child transforms will be scaled to the dimensions of this plane immediately.
|
|
/// And, if it's set to False, dimensions of this plane will no longer affect the child transforms, and child
|
|
/// transforms will retain their current scale. This can be controlled further by using a
|
|
/// <seealso cref="OVRSceneObjectTransformType"/>. Note: if the current game object also contains
|
|
/// a <seealso cref="OVRSceneVolume"/>, then the volume's scale will take precedence.</remarks>
|
|
public bool ScaleChildren
|
|
{
|
|
get => _scaleChildren;
|
|
set
|
|
{
|
|
_scaleChildren = value;
|
|
if (_scaleChildren && _sceneAnchor.Space.Valid)
|
|
{
|
|
SetChildScale();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the child transforms will be offset according to the offset of this plane.
|
|
/// </summary>
|
|
/// <remarks>If set to True, all the child transforms will be offset to the offset of this plane immediately.
|
|
/// And, if it's set to False, offsets of this plane will no longer affect the child transforms, and child
|
|
/// transforms will retain their current offset. This can be controlled further by using a
|
|
/// <seealso cref="OVRSceneObjectTransformType"/>. Note: if the current game object also contains
|
|
/// a <seealso cref="OVRSceneVolume"/>, then the volume's offset will take precedence.</remarks>
|
|
public bool OffsetChildren
|
|
{
|
|
get => _offsetChildren;
|
|
set
|
|
{
|
|
_offsetChildren = value;
|
|
if (_offsetChildren && _sceneAnchor.Space.Valid)
|
|
{
|
|
SetChildOffset();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Tooltip("When enabled, scales the child transforms according to the dimensions of this plane. " +
|
|
"If both Volume and Plane components exist on the game object, the volume takes precedence.")]
|
|
[SerializeField]
|
|
internal bool _scaleChildren = true;
|
|
|
|
[Tooltip("When enabled, offsets the child transforms according to the offset of this plane. " +
|
|
"If both Volume and Plane components exist on the game object, the volume takes precedence.")]
|
|
[SerializeField]
|
|
internal bool _offsetChildren = true;
|
|
|
|
internal JobHandle? _jobHandle;
|
|
|
|
private NativeArray<Vector2> _previousBoundary;
|
|
|
|
private NativeArray<int> _boundaryLength;
|
|
|
|
private NativeArray<Vector2> _boundaryBuffer;
|
|
|
|
private bool _boundaryRequested;
|
|
|
|
private OVRSceneAnchor _sceneAnchor;
|
|
|
|
private readonly List<Vector2> _boundary = new List<Vector2>();
|
|
|
|
private void SetChildScale()
|
|
{
|
|
var hasVolume = TryGetComponent<OVRSceneVolume>(out var volume);
|
|
|
|
// we scale the child if we have the TransformType.Plane
|
|
// or if the sibling volume component will not scale
|
|
for (var i = 0; i < transform.childCount; i++)
|
|
{
|
|
var child = transform.GetChild(i);
|
|
if (child.TryGetComponent<OVRSceneObjectTransformType>(out var transformType))
|
|
{
|
|
if (transformType.TransformType != OVRSceneObjectTransformType.Transformation.Plane)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// if there's no TransformType, then we only don't apply transform
|
|
// if the volume will take care of it instead
|
|
if (hasVolume && volume.ScaleChildren)
|
|
continue;
|
|
}
|
|
|
|
child.localScale = new Vector3(Width, Height, child.localScale.z);
|
|
}
|
|
}
|
|
|
|
private void SetChildOffset()
|
|
{
|
|
var hasVolume = TryGetComponent<OVRSceneVolume>(out var volume);
|
|
|
|
// we offset the child if we have the TransformType.Plane
|
|
// or if the sibling volume component will not offset
|
|
for (var i = 0; i < transform.childCount; i++)
|
|
{
|
|
var child = transform.GetChild(i);
|
|
if (child.TryGetComponent<OVRSceneObjectTransformType>(out var transformType))
|
|
{
|
|
if (transformType.TransformType != OVRSceneObjectTransformType.Transformation.Plane)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// if there's no TransformType, then we only don't apply transform
|
|
// if the volume will take care of it instead
|
|
if (hasVolume && volume.OffsetChildren)
|
|
continue;
|
|
}
|
|
|
|
child.localPosition = new Vector3(Offset.x, Offset.y, 0);
|
|
}
|
|
}
|
|
|
|
internal void UpdateTransform()
|
|
{
|
|
if (OVRPlugin.GetSpaceBoundingBox2D(GetComponent<OVRSceneAnchor>().Space, out var rect))
|
|
{
|
|
Width = rect.Size.w;
|
|
Height = rect.Size.h;
|
|
|
|
Vector2 planePivot = transform.TransformPoint(
|
|
rect.Pos.FromVector2f() + (rect.Size.FromSizef() / 2));
|
|
var anchorPivot = new Vector2(transform.position.x, transform.position.y);
|
|
Offset = planePivot - anchorPivot;
|
|
|
|
OVRSceneManager.Development.Log(nameof(OVRScenePlane),
|
|
$"[{_sceneAnchor.Uuid}] Plane has dimensions {Dimensions} " +
|
|
$"and offset {Offset}.", gameObject);
|
|
|
|
if (ScaleChildren)
|
|
SetChildScale();
|
|
if (OffsetChildren)
|
|
SetChildOffset();
|
|
}
|
|
else
|
|
{
|
|
OVRSceneManager.Development.LogError(nameof(OVRScenePlane),
|
|
$"[{GetComponent<OVRSceneAnchor>().Uuid}] Failed to retrieve plane's information.",
|
|
gameObject);
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
_sceneAnchor = GetComponent<OVRSceneAnchor>();
|
|
if (_sceneAnchor.Space.Valid)
|
|
{
|
|
((IOVRSceneComponent)this).Initialize();
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
RequestBoundary();
|
|
}
|
|
|
|
void IOVRSceneComponent.Initialize()
|
|
{
|
|
UpdateTransform();
|
|
}
|
|
|
|
internal void ScheduleGetLengthJob()
|
|
{
|
|
// Don't schedule if already running
|
|
if (_jobHandle != null) return;
|
|
|
|
if (!OVRPlugin.GetSpaceComponentStatus(_sceneAnchor.Space,
|
|
OVRPlugin.SpaceComponentType.Bounded2D, out var isEnabled, out var isChangePending))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!isEnabled || isChangePending) return;
|
|
|
|
// Scratch buffer to store single value on the heap
|
|
_boundaryLength = new NativeArray<int>(1, Allocator.TempJob);
|
|
|
|
// Two-call idiom: first call gets the length
|
|
_jobHandle = new GetBoundaryLengthJob
|
|
{
|
|
Length = _boundaryLength,
|
|
Space = _sceneAnchor.Space
|
|
}.Schedule();
|
|
_boundaryRequested = false;
|
|
}
|
|
|
|
internal void RequestBoundary()
|
|
{
|
|
_boundaryRequested = true;
|
|
if (enabled)
|
|
{
|
|
// If enabled, we can go ahead and start right away
|
|
ScheduleGetLengthJob();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_jobHandle?.IsCompleted == true)
|
|
{
|
|
_jobHandle.Value.Complete();
|
|
_jobHandle = null;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_boundaryLength.IsCreated)
|
|
{
|
|
var length = _boundaryLength[0];
|
|
_boundaryLength.Dispose();
|
|
|
|
if (length < 3)
|
|
{
|
|
// This means that we failed to get the boundary length, so try again
|
|
ScheduleGetLengthJob();
|
|
return;
|
|
}
|
|
|
|
using (new OVRProfilerScope("Schedule " + nameof(GetBoundaryJob)))
|
|
{
|
|
_boundaryBuffer = new NativeArray<Vector2>(length, Allocator.TempJob);
|
|
if (!_previousBoundary.IsCreated)
|
|
{
|
|
_previousBoundary = new NativeArray<Vector2>(length, Allocator.Persistent);
|
|
}
|
|
|
|
_jobHandle = new GetBoundaryJob
|
|
{
|
|
Space = _sceneAnchor.Space,
|
|
Boundary = _boundaryBuffer,
|
|
PreviousBoundary = _previousBoundary,
|
|
}.Schedule();
|
|
}
|
|
}
|
|
else if (_boundaryBuffer.IsCreated)
|
|
{
|
|
using (new OVRProfilerScope("Copy boundary"))
|
|
{
|
|
if (_previousBoundary.Length == 0 || float.IsNaN(_previousBoundary[0].x))
|
|
{
|
|
if (_previousBoundary.IsCreated)
|
|
{
|
|
_previousBoundary.Dispose();
|
|
}
|
|
|
|
_previousBoundary = new NativeArray<Vector2>(_boundaryBuffer.Length,
|
|
Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
|
_previousBoundary.CopyFrom(_boundaryBuffer);
|
|
|
|
// Finally, copy it to the publicly accessible list.
|
|
_boundary.Clear();
|
|
foreach (var vertex in _previousBoundary)
|
|
{
|
|
_boundary.Add(new Vector2(-vertex.x, vertex.y));
|
|
}
|
|
}
|
|
}
|
|
|
|
_boundaryBuffer.Dispose();
|
|
|
|
if (TryGetComponent<OVRScenePlaneMeshFilter>(out var planeMeshFilter))
|
|
{
|
|
// Notify mesh filter that there's a new boundary
|
|
planeMeshFilter.RequestMeshGeneration();
|
|
}
|
|
}
|
|
else if (_boundaryRequested)
|
|
{
|
|
ScheduleGetLengthJob();
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
// Job completed but we may not yet have consumed the data
|
|
if (_boundaryLength.IsCreated) _boundaryLength.Dispose(_jobHandle ?? default);
|
|
if (_boundaryBuffer.IsCreated) _boundaryBuffer.Dispose(_jobHandle ?? default);
|
|
if (_previousBoundary.IsCreated) _previousBoundary.Dispose(_jobHandle ?? default);
|
|
|
|
_previousBoundary = default;
|
|
_boundaryBuffer = default;
|
|
_boundaryLength = default;
|
|
_jobHandle = null;
|
|
}
|
|
|
|
private struct GetBoundaryLengthJob : IJob
|
|
{
|
|
/// <summary>This is an internal member.</summary>
|
|
public OVRSpace Space;
|
|
|
|
/// <summary>This is an internal member.</summary>
|
|
[WriteOnly]
|
|
public NativeArray<int> Length;
|
|
|
|
/// <summary>This is an internal member.</summary>
|
|
public void Execute() => Length[0] = OVRPlugin.GetSpaceBoundary2DCount(Space, out var count)
|
|
? count
|
|
: 0;
|
|
}
|
|
|
|
private struct GetBoundaryJob : IJob
|
|
{
|
|
/// <summary>This is an internal member.</summary>
|
|
public OVRSpace Space;
|
|
|
|
/// <summary>This is an internal member.</summary>
|
|
public NativeArray<Vector2> Boundary;
|
|
|
|
/// <summary>This is an internal member.</summary>
|
|
public NativeArray<Vector2> PreviousBoundary;
|
|
|
|
private bool HasBoundaryChanged()
|
|
{
|
|
if (!PreviousBoundary.IsCreated) return true;
|
|
if (Boundary.Length != PreviousBoundary.Length) return true;
|
|
|
|
var length = Boundary.Length;
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
if (Vector2.SqrMagnitude(Boundary[i] - PreviousBoundary[i]) > 1e-6f) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void SetNaN(NativeArray<Vector2> array)
|
|
{
|
|
// Set a NaN to indicate failure
|
|
if (array.Length > 0)
|
|
{
|
|
array[0] = new Vector2(float.NaN, float.NaN);
|
|
}
|
|
}
|
|
|
|
/// <summary>This is an internal member.</summary>
|
|
public void Execute()
|
|
{
|
|
if (OVRPlugin.GetSpaceBoundary2D(Space, Boundary) && HasBoundaryChanged())
|
|
{
|
|
// Invalid old boundary
|
|
SetNaN(PreviousBoundary);
|
|
}
|
|
else
|
|
{
|
|
// Invalid boundary
|
|
SetNaN(Boundary);
|
|
}
|
|
}
|
|
}
|
|
}
|