Initialer Upload neues Unity-Projekt
This commit is contained in:
@ -0,0 +1,91 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class BouncingBallLogic : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float TTL = 5.0f;
|
||||
[SerializeField] private AudioClip pop;
|
||||
[SerializeField] private AudioClip bounce;
|
||||
[SerializeField] private AudioClip loadball;
|
||||
[SerializeField] private Material visibleMat;
|
||||
[SerializeField] private Material hiddenMat;
|
||||
private AudioSource audioSource;
|
||||
private Transform centerEyeCamera;
|
||||
private bool isVisible = true;
|
||||
|
||||
private float timer = 0f;
|
||||
private bool isReleased = false;
|
||||
private bool isReadyForDestroy = false;
|
||||
|
||||
private void OnCollisionEnter() => audioSource.PlayOneShot(bounce);
|
||||
|
||||
private void Start()
|
||||
{
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
audioSource.PlayOneShot(loadball);
|
||||
centerEyeCamera = OVRManager.instance.GetComponentInChildren<OVRCameraRig>().centerEyeAnchor;
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isReleased) return;
|
||||
UpdateVisibility();
|
||||
timer += Time.deltaTime;
|
||||
if (!isReadyForDestroy && timer >= TTL)
|
||||
{
|
||||
isReadyForDestroy = true;
|
||||
float clipLength = pop.length;
|
||||
audioSource.PlayOneShot(pop);
|
||||
StartCoroutine(PlayPopCallback(clipLength));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisibility()
|
||||
{
|
||||
Vector3 displacement = centerEyeCamera.position - this.transform.position;
|
||||
Ray ray = new Ray(this.transform.position, displacement);
|
||||
RaycastHit info;
|
||||
if (Physics.Raycast(ray, out info, displacement.magnitude))
|
||||
{
|
||||
if (info.collider.gameObject != this.gameObject)
|
||||
{
|
||||
SetVisible(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetVisible(bool setVisible)
|
||||
{
|
||||
if (isVisible && !setVisible)
|
||||
{
|
||||
GetComponent<MeshRenderer>().material = hiddenMat;
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
if (!isVisible && setVisible)
|
||||
{
|
||||
GetComponent<MeshRenderer>().material = visibleMat;
|
||||
isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Release(Vector3 pos, Vector3 vel, Vector3 angVel)
|
||||
{
|
||||
isReleased = true;
|
||||
transform.position = pos; // set the orign to match target
|
||||
GetComponent<Rigidbody>().isKinematic = false;
|
||||
GetComponent<Rigidbody>().velocity = vel;
|
||||
GetComponent<Rigidbody>().angularVelocity = angVel;
|
||||
}
|
||||
|
||||
private IEnumerator PlayPopCallback(float clipLength)
|
||||
{
|
||||
yield return new WaitForSeconds(clipLength);
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7c54edf3d288044e813e05f8f5bbba6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,32 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class BouncingBallMgr : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Transform trackingspace;
|
||||
[SerializeField] private GameObject rightControllerPivot;
|
||||
[SerializeField] private OVRInput.RawButton actionBtn;
|
||||
[SerializeField] private GameObject ball;
|
||||
|
||||
private GameObject currentBall;
|
||||
private bool ballGrabbed = false;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!ballGrabbed && OVRInput.GetDown(actionBtn))
|
||||
{
|
||||
currentBall = Instantiate(ball, rightControllerPivot.transform.position, Quaternion.identity);
|
||||
currentBall.transform.parent = rightControllerPivot.transform;
|
||||
ballGrabbed = true;
|
||||
}
|
||||
|
||||
if (ballGrabbed && OVRInput.GetUp(actionBtn))
|
||||
{
|
||||
currentBall.transform.parent = null;
|
||||
var ballPos = currentBall.transform.position;
|
||||
var vel = trackingspace.rotation * OVRInput.GetLocalControllerVelocity(OVRInput.Controller.RTouch);
|
||||
var angVel = OVRInput.GetLocalControllerAngularVelocity(OVRInput.Controller.RTouch);
|
||||
currentBall.GetComponent<BouncingBallLogic>().Release(ballPos, vel, angVel);
|
||||
ballGrabbed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5eceb167892f49a48a0f8e1ee2bd4e02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e33e5c146f3dad247a398a5c52de790e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows you how to implement a scene manager with the following features:
|
||||
/// * Fetch all room scene anchors
|
||||
/// * Fetch all child scene anchors of a room
|
||||
/// * Set the location, and name of the object as label
|
||||
/// * Spawn primitive geometry to match the scene anchor's plane, volume or mesh data
|
||||
///
|
||||
/// There is a fallback for running scene capture if no rooms were found.
|
||||
/// </summary>
|
||||
public class BasicSceneManager : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
|
||||
LoadSceneAsync();
|
||||
}
|
||||
|
||||
async void LoadSceneAsync()
|
||||
{
|
||||
// fetch all rooms, with a SceneCapture fallback
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
if (rooms.Count == 0)
|
||||
{
|
||||
var sceneCaptured = await SceneManagerHelper.RequestSceneCapture();
|
||||
if (!sceneCaptured)
|
||||
return;
|
||||
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
}
|
||||
|
||||
// fetch room elements, create objects for them
|
||||
var tasks = rooms.Select(async room =>
|
||||
{
|
||||
var roomObject = new GameObject($"Room-{room.Uuid}");
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
return;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
await CreateSceneAnchors(roomObject, children);
|
||||
}).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
async Task CreateSceneAnchors(GameObject roomGameObject, List<OVRAnchor> anchors)
|
||||
{
|
||||
// we create tasks to iterate all anchors in parallel
|
||||
var tasks = anchors.Select(async anchor =>
|
||||
{
|
||||
// can we locate it in the world?
|
||||
if (!anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
return;
|
||||
await locatable.SetEnabledAsync(true);
|
||||
|
||||
// get semantic classification for object name
|
||||
var label = "other";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels))
|
||||
label = labels.Labels;
|
||||
|
||||
// create and parent Unity game object
|
||||
var gameObject = new GameObject(label);
|
||||
gameObject.transform.SetParent(roomGameObject.transform);
|
||||
|
||||
// set location and create objects for 2D, 3D, triangle mesh
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
|
||||
if (anchor.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
helper.CreatePlane(b2d);
|
||||
if (anchor.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
helper.CreateVolume(b3d);
|
||||
}).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1ca019a7ed88b84792a6d0610555b21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using DynamicSceneManagerHelper;
|
||||
|
||||
/// <summary>
|
||||
/// This sample expands on the snapshot scene manager and adds the ability
|
||||
/// to update Unity game objects as the data changes through the snapshots.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As per the anchor change rules, the UUID of a room changes when a child
|
||||
/// item is added or removed, so we match pairs of anchors that have similar
|
||||
/// child anchors between snapshots. We also check the geometric bounds of
|
||||
/// anchors across snapshots.
|
||||
///
|
||||
/// In order to achieve this, we save additional info in scene snapshots,
|
||||
/// and also provide a way to update existing Unity objects when
|
||||
/// their geometry changes.
|
||||
///
|
||||
/// This sample should serve as a guide only, as it relies on many heap
|
||||
/// allocations and inefficient search queries to help readability.
|
||||
/// </remarks>
|
||||
public class DynamicSceneManager : MonoBehaviour
|
||||
{
|
||||
public float UpdateFrequencySeconds = 5;
|
||||
|
||||
SceneSnapshot _snapshot = new SceneSnapshot();
|
||||
Dictionary<OVRAnchor, GameObject> _sceneGameObjects = new Dictionary<OVRAnchor, GameObject>();
|
||||
|
||||
Task _updateSceneTask;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
StartCoroutine(UpdateScenePeriodically());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (OVRInput.GetDown(OVRInput.RawButton.A))
|
||||
_ = SceneManagerHelper.RequestSceneCapture();
|
||||
}
|
||||
|
||||
IEnumerator UpdateScenePeriodically()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(UpdateFrequencySeconds);
|
||||
|
||||
_updateSceneTask = UpdateScene();
|
||||
yield return new WaitUntil(() => _updateSceneTask.IsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateScene()
|
||||
{
|
||||
// get current snapshot and compare to previous
|
||||
var currentSnapshot = await LoadSceneSnapshotAsync();
|
||||
var differences = new SnapshotComparer(
|
||||
_snapshot, currentSnapshot).Compare();
|
||||
|
||||
// update unity objects from the differences
|
||||
await UpdateUnityObjects(differences, currentSnapshot);
|
||||
|
||||
// update previous snapshot
|
||||
_snapshot = currentSnapshot;
|
||||
}
|
||||
|
||||
async Task<SceneSnapshot> LoadSceneSnapshotAsync()
|
||||
{
|
||||
// create snapshot from all rooms and their anchors
|
||||
// saving some of the anchor data to detect changes
|
||||
var snapshot = new SceneSnapshot();
|
||||
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
continue;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
snapshot.Anchors.Add(room, new SceneSnapshot.Data { Children = children});
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var data = new SceneSnapshot.Data();
|
||||
if (child.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
data.Rect = b2d.BoundingBox;
|
||||
if (child.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
data.Bounds = b3d.BoundingBox;
|
||||
snapshot.Anchors.Add(child, data);
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
async Task UpdateUnityObjects(List<(OVRAnchor, SnapshotComparer.ChangeType)> changes,
|
||||
SceneSnapshot newSnapshot)
|
||||
{
|
||||
if (!changes.Any())
|
||||
return;
|
||||
|
||||
var updater = new UnityObjectUpdater();
|
||||
|
||||
var changesNew = FilterChanges(changes, SnapshotComparer.ChangeType.New);
|
||||
var changesMissing = FilterChanges(changes, SnapshotComparer.ChangeType.Missing);
|
||||
var changesId = FilterChanges(changes, SnapshotComparer.ChangeType.ChangedId);
|
||||
var changesBounds = FilterChanges(changes, SnapshotComparer.ChangeType.ChangedBounds);
|
||||
|
||||
// create a new game object for all new changes
|
||||
foreach (var anchor in changesNew)
|
||||
{
|
||||
_sceneGameObjects.TryGetValue(GetParentAnchor(anchor, newSnapshot), out var parent);
|
||||
_sceneGameObjects.Add(anchor, await updater.CreateUnityObject(anchor, parent));
|
||||
}
|
||||
|
||||
// destroy game objects for all missing anchors
|
||||
foreach (var anchor in changesMissing)
|
||||
{
|
||||
Destroy(_sceneGameObjects[anchor]);
|
||||
_sceneGameObjects.Remove(anchor);
|
||||
}
|
||||
|
||||
// ChangedId means we need to find the pairs between the snapshots
|
||||
foreach (var (currentAnchor, newAnchor) in FindAnchorPairs(changesId, newSnapshot))
|
||||
{
|
||||
// we only need to update the reference in our scene game objects
|
||||
_sceneGameObjects.Add(newAnchor, _sceneGameObjects[currentAnchor]);
|
||||
_sceneGameObjects.Remove(currentAnchor);
|
||||
}
|
||||
|
||||
// geometry bounds means just updating an existing game object
|
||||
foreach (var currentAnchor in changesBounds)
|
||||
updater.UpdateUnityObject(currentAnchor, _sceneGameObjects[currentAnchor]);
|
||||
}
|
||||
|
||||
List<OVRAnchor> FilterChanges(List<(OVRAnchor, SnapshotComparer.ChangeType)> changes,
|
||||
SnapshotComparer.ChangeType changeType) =>
|
||||
changes.Where(tuple => tuple.Item2 == changeType).Select(tuple => tuple.Item1).ToList();
|
||||
|
||||
List<(OVRAnchor, OVRAnchor)> FindAnchorPairs(List<OVRAnchor> allAnchors, SceneSnapshot newSnapshot)
|
||||
{
|
||||
var currentAnchors = allAnchors.Where(_snapshot.Contains);
|
||||
var newAnchors = allAnchors.Where(newSnapshot.Contains);
|
||||
|
||||
var pairs = new List<(OVRAnchor, OVRAnchor)>();
|
||||
foreach (var currentAnchor in currentAnchors)
|
||||
{
|
||||
foreach (var newAnchor in newAnchors)
|
||||
{
|
||||
if (AreAnchorsEqual(_snapshot.Anchors[currentAnchor], newSnapshot.Anchors[newAnchor]))
|
||||
{
|
||||
pairs.Add((currentAnchor, newAnchor));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
bool AreAnchorsEqual(SceneSnapshot.Data anchor1Data, SceneSnapshot.Data anchor2Data)
|
||||
{
|
||||
// the only equal anchors with different UUIDs are when they are rooms.
|
||||
// so we will check if any of their child elements are the same
|
||||
if (anchor1Data.Children == null || anchor2Data.Children == null)
|
||||
return false;
|
||||
|
||||
return anchor1Data.Children.Any(anchor2Data.Children.Contains) ||
|
||||
anchor2Data.Children.Any(anchor1Data.Children.Contains);
|
||||
}
|
||||
|
||||
OVRAnchor GetParentAnchor(OVRAnchor childAnchor, SceneSnapshot snapshot)
|
||||
{
|
||||
foreach (var kvp in snapshot.Anchors)
|
||||
if (kvp.Value.Children?.Contains(childAnchor) == true)
|
||||
return kvp.Key;
|
||||
return OVRAnchor.Null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36623bf873a22fc498789172d2842726
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
// This namespace contains helper classes for the DynamicSceneManager.
|
||||
namespace DynamicSceneManagerHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that holds a list of anchors, along with data that
|
||||
/// can be used to identify when a change has occured.
|
||||
/// </summary>
|
||||
class SceneSnapshot
|
||||
{
|
||||
public class Data
|
||||
{
|
||||
public List<OVRAnchor> Children;
|
||||
public Rect? Rect;
|
||||
public Bounds? Bounds;
|
||||
}
|
||||
public Dictionary<OVRAnchor, Data> Anchors { get; } = new Dictionary<OVRAnchor, Data>();
|
||||
public bool Contains(OVRAnchor anchor) => Anchors.ContainsKey(anchor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the custom logic for scene snapshot comparison.
|
||||
/// </summary>
|
||||
class SnapshotComparer
|
||||
{
|
||||
public SceneSnapshot BaseSnapshot { get; }
|
||||
public SceneSnapshot NewSnapshot { get; }
|
||||
|
||||
public SnapshotComparer(SceneSnapshot baseSnapshot, SceneSnapshot newSnapshot)
|
||||
{
|
||||
BaseSnapshot = baseSnapshot;
|
||||
NewSnapshot = newSnapshot;
|
||||
}
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
New,
|
||||
Missing,
|
||||
ChangedId,
|
||||
ChangedBounds
|
||||
}
|
||||
|
||||
public List<(OVRAnchor, ChangeType)> Compare()
|
||||
{
|
||||
var changes = new List<(OVRAnchor, ChangeType)>();
|
||||
|
||||
// if in base but not in new, then missing
|
||||
// if in new but not in base, then new
|
||||
foreach (var anchor1 in BaseSnapshot.Anchors.Keys)
|
||||
if (!NewSnapshot.Contains(anchor1))
|
||||
changes.Add((anchor1, ChangeType.Missing));
|
||||
foreach (var anchor2 in NewSnapshot.Anchors.Keys)
|
||||
if (!BaseSnapshot.Contains(anchor2))
|
||||
changes.Add((anchor2, ChangeType.New));
|
||||
|
||||
// further checks
|
||||
CheckRoomChanges(changes);
|
||||
CheckBoundsChanges(changes);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
void CheckRoomChanges(List<(OVRAnchor, ChangeType)> changes)
|
||||
{
|
||||
// a room with new/deleted/edited child anchors is considered
|
||||
// a NEW anchor, so we will check if any child elements are
|
||||
// the same and mark both old and new room anchors as CHANGED
|
||||
for (var i = 0; i < changes.Count; i++)
|
||||
{
|
||||
var (anchor, change) = changes[i];
|
||||
|
||||
var isRoom = anchor.TryGetComponent(out OVRRoomLayout room) && room.IsEnabled;
|
||||
if (!isRoom || change == ChangeType.ChangedId)
|
||||
continue;
|
||||
|
||||
var isNewAnchor = NewSnapshot.Contains(anchor);
|
||||
var isOldAnchor = BaseSnapshot.Contains(anchor);
|
||||
if (!isNewAnchor && !isOldAnchor)
|
||||
continue;
|
||||
|
||||
var children = isNewAnchor ?
|
||||
NewSnapshot.Anchors[anchor].Children :
|
||||
BaseSnapshot.Anchors[anchor].Children;
|
||||
var comparisonSnapshot = change == ChangeType.New ?
|
||||
BaseSnapshot : NewSnapshot;
|
||||
|
||||
foreach (var child in children)
|
||||
if (comparisonSnapshot.Contains(child))
|
||||
changes[i] = (anchor, ChangeType.ChangedId);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckBoundsChanges(List<(OVRAnchor, ChangeType)> changes)
|
||||
{
|
||||
// first match pairs of base and new snapshots
|
||||
foreach (var baseAnchor in BaseSnapshot.Anchors.Keys)
|
||||
{
|
||||
var newAnchor = NewSnapshot.Anchors.Keys.FirstOrDefault(
|
||||
newAnchor => newAnchor.Uuid == baseAnchor.Uuid);
|
||||
|
||||
// we have a pair, now compare bounds data
|
||||
if (newAnchor.Uuid == baseAnchor.Uuid)
|
||||
{
|
||||
var baseData = BaseSnapshot.Anchors[baseAnchor];
|
||||
var newData = NewSnapshot.Anchors[newAnchor];
|
||||
|
||||
var changed2DBounds = Has2DBounds(baseData, newData) && Are2DBoundsDifferent(baseData, newData);
|
||||
var changed3DBounds = Has3DBounds(baseData, newData) && Are3DBoundsDifferent(baseData, newData);
|
||||
|
||||
if (changed2DBounds || changed3DBounds)
|
||||
changes.Add((baseAnchor, ChangeType.ChangedBounds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Has2DBounds(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Rect.HasValue && data2.Rect.HasValue;
|
||||
bool Are2DBoundsDifferent(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Rect?.min != data2.Rect?.min || data1.Rect?.max != data2.Rect?.max;
|
||||
bool Has3DBounds(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Bounds.HasValue && data2.Bounds.HasValue;
|
||||
bool Are3DBoundsDifferent(SceneSnapshot.Data data1, SceneSnapshot.Data data2)
|
||||
=> data1.Bounds?.min != data2.Bounds?.min || data1.Bounds?.max != data2.Bounds?.max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class wraps the logic for interacting with Unity
|
||||
/// game objects.
|
||||
/// </summary>
|
||||
class UnityObjectUpdater
|
||||
{
|
||||
public async Task<GameObject> CreateUnityObject(OVRAnchor anchor, GameObject parent)
|
||||
{
|
||||
// if this is a room, we only need to make a GameObject
|
||||
if (anchor.TryGetComponent(out OVRRoomLayout _))
|
||||
return new GameObject($"Room-{anchor.Uuid}");
|
||||
|
||||
// only interested in the anchors which are locatable
|
||||
if (!anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
return null;
|
||||
await locatable.SetEnabledAsync(true);
|
||||
|
||||
// get semantic classification for object name
|
||||
var label = "other";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels))
|
||||
label = labels.Labels;
|
||||
|
||||
// create and parent Unity game object if possible
|
||||
var gameObject = new GameObject(label);
|
||||
if (parent != null)
|
||||
gameObject.transform.SetParent(parent.transform);
|
||||
|
||||
// set location and create objects for 2D, 3D, triangle mesh
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
|
||||
if (anchor.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
helper.CreatePlane(b2d);
|
||||
if (anchor.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
helper.CreateVolume(b3d);
|
||||
if (anchor.TryGetComponent(out OVRTriangleMesh mesh) && mesh.IsEnabled)
|
||||
helper.CreateMesh(mesh);
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
public void UpdateUnityObject(OVRAnchor anchor, GameObject gameObject)
|
||||
{
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
if (anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
helper.SetLocation(locatable);
|
||||
if (anchor.TryGetComponent(out OVRBounded2D b2d) && b2d.IsEnabled)
|
||||
helper.UpdatePlane(b2d);
|
||||
if (anchor.TryGetComponent(out OVRBounded3D b3d) && b3d.IsEnabled)
|
||||
helper.UpdateVolume(b3d);
|
||||
if (anchor.TryGetComponent(out OVRTriangleMesh mesh) && mesh.IsEnabled)
|
||||
helper.UpdateMesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
static class ObjectPool
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc7f55566ebbfa845a8c4e2b83070915
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,148 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This sample expands on the basic scene manager and adds the following features:
|
||||
/// * Spawn a prefab for the wall, ceiling, floor elements and set 2D dimensions
|
||||
/// * Spawn a fallback object for all other semantic labels and set 3D dimensions
|
||||
/// * Update the location of all anchors at some frequency with new tracking info
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The prefabs in this class are scaled to dimensions 1, however, you will need to
|
||||
/// consider the dimension within your prefab to scale correctly. Additionally, in
|
||||
/// order to avoid stretching, you may consider 9-slicing.
|
||||
///
|
||||
/// Scene anchors are tracked independently and can change as new tracking information
|
||||
/// becomes available. It may be better for your use case to consider a single
|
||||
/// tracking point and only update that. This will keep the relative positions of
|
||||
/// all objects consistent, while also updating the location with new tracking data.
|
||||
/// </remarks>
|
||||
public class PrefabSceneManager : MonoBehaviour
|
||||
{
|
||||
public GameObject WallPrefab;
|
||||
public GameObject CeilingPrefab;
|
||||
public GameObject FloorPrefab;
|
||||
public GameObject FallbackPrefab;
|
||||
public float UpdateFrequencySeconds = 5;
|
||||
|
||||
List<(GameObject,OVRLocatable)> _locatableObjects = new List<(GameObject,OVRLocatable)>();
|
||||
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
|
||||
LoadSceneAsync();
|
||||
StartCoroutine(UpdateAnchorsPeriodically());
|
||||
}
|
||||
|
||||
async void LoadSceneAsync()
|
||||
{
|
||||
// fetch all rooms, with a SceneCapture fallback
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
if (rooms.Count == 0)
|
||||
{
|
||||
var sceneCaptured = await SceneManagerHelper.RequestSceneCapture();
|
||||
if (!sceneCaptured)
|
||||
return;
|
||||
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
}
|
||||
|
||||
// fetch room elements, create objects for them
|
||||
var tasks = rooms.Select(async room =>
|
||||
{
|
||||
var roomObject = new GameObject($"Room-{room.Uuid}");
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
return;
|
||||
if (!room.TryGetComponent(out OVRRoomLayout roomLayout))
|
||||
return;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
await CreateSceneAnchors(roomObject, roomLayout, children);
|
||||
}).ToList();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
async Task CreateSceneAnchors(GameObject roomGameObject,
|
||||
OVRRoomLayout roomLayout, List<OVRAnchor> anchors)
|
||||
{
|
||||
roomLayout.TryGetRoomLayout(out var ceilingUuid,
|
||||
out var floorUuid, out var wallUuids);
|
||||
|
||||
// iterate over all anchors as async tasks
|
||||
var tasks = anchors.Select(async anchor =>
|
||||
{
|
||||
// can we locate it in the world?
|
||||
if (!anchor.TryGetComponent(out OVRLocatable locatable))
|
||||
return;
|
||||
await locatable.SetEnabledAsync(true);
|
||||
|
||||
// check room layout information and assign prefab
|
||||
// it would also be possible to use the semantic label
|
||||
var prefab = FallbackPrefab;
|
||||
if (anchor.Uuid == floorUuid)
|
||||
prefab = FloorPrefab;
|
||||
else if (anchor.Uuid == ceilingUuid)
|
||||
prefab = CeilingPrefab;
|
||||
else if (wallUuids.Contains(anchor.Uuid))
|
||||
prefab = WallPrefab;
|
||||
|
||||
// get semantic classification for object name
|
||||
var label = "other";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels))
|
||||
label = labels.Labels;
|
||||
|
||||
// create container object
|
||||
var gameObject = new GameObject(label);
|
||||
gameObject.transform.SetParent(roomGameObject.transform);
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
|
||||
// instantiate prefab & set 2D dimensions
|
||||
var model = Instantiate(prefab, gameObject.transform);
|
||||
if (anchor.TryGetComponent(out OVRBounded2D bounds2D) &&
|
||||
bounds2D.IsEnabled)
|
||||
{
|
||||
model.transform.localScale = new Vector3(
|
||||
bounds2D.BoundingBox.size.x,
|
||||
bounds2D.BoundingBox.size.y,
|
||||
0.01f);
|
||||
}
|
||||
|
||||
// we will set volume dimensions for the non-room elements
|
||||
if (prefab == FallbackPrefab)
|
||||
{
|
||||
if (anchor.TryGetComponent(out OVRBounded3D bounds3D) &&
|
||||
bounds3D.IsEnabled)
|
||||
{
|
||||
model.transform.localPosition = new Vector3(0, 0,
|
||||
-bounds3D.BoundingBox.size.z / 2);
|
||||
model.transform.localScale = bounds3D.BoundingBox.size;
|
||||
}
|
||||
}
|
||||
|
||||
// save game object and locatable for updating later
|
||||
_locatableObjects.Add((gameObject, locatable));
|
||||
}).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
IEnumerator UpdateAnchorsPeriodically()
|
||||
{
|
||||
while (true) {
|
||||
foreach (var (gameObject, locatable) in _locatableObjects)
|
||||
{
|
||||
var helper = new SceneManagerHelper(gameObject);
|
||||
helper.SetLocation(locatable);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(UpdateFrequencySeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5d0a6eb3cf408a4dbab4ae40f82517c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A smaller helper class for Custom Scene Manager samples.
|
||||
/// </summary>
|
||||
public class SceneManagerHelper
|
||||
{
|
||||
public GameObject AnchorGameObject { get; }
|
||||
|
||||
public SceneManagerHelper(GameObject gameObject)
|
||||
{
|
||||
AnchorGameObject = gameObject;
|
||||
}
|
||||
|
||||
public void SetLocation(OVRLocatable locatable, Camera camera = null)
|
||||
{
|
||||
if (!locatable.TryGetSceneAnchorPose(out var pose))
|
||||
return;
|
||||
|
||||
var projectionCamera = camera == null ? Camera.main : camera;
|
||||
var position = pose.ComputeWorldPosition(projectionCamera);
|
||||
var rotation = pose.ComputeWorldRotation(projectionCamera);
|
||||
|
||||
if (position != null && rotation != null)
|
||||
AnchorGameObject.transform.SetPositionAndRotation(
|
||||
position.Value, rotation.Value);
|
||||
}
|
||||
|
||||
public void CreatePlane(OVRBounded2D bounds)
|
||||
{
|
||||
var planeGO = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
planeGO.name = "Plane";
|
||||
planeGO.transform.SetParent(AnchorGameObject.transform, false);
|
||||
planeGO.transform.localScale = new Vector3(
|
||||
bounds.BoundingBox.size.x,
|
||||
bounds.BoundingBox.size.y,
|
||||
0.01f);
|
||||
planeGO.GetComponent<MeshRenderer>().material.SetColor(
|
||||
"_Color", UnityEngine.Random.ColorHSV());
|
||||
}
|
||||
|
||||
public void UpdatePlane(OVRBounded2D bounds)
|
||||
{
|
||||
var planeGO = AnchorGameObject.transform.Find("Plane");
|
||||
if (planeGO == null)
|
||||
CreatePlane(bounds);
|
||||
else
|
||||
{
|
||||
planeGO.transform.localScale = new Vector3(
|
||||
bounds.BoundingBox.size.x,
|
||||
bounds.BoundingBox.size.y,
|
||||
0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateVolume(OVRBounded3D bounds)
|
||||
{
|
||||
var volumeGO = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
volumeGO.name = "Volume";
|
||||
volumeGO.transform.SetParent(AnchorGameObject.transform, false);
|
||||
volumeGO.transform.localPosition = new Vector3(
|
||||
0, 0, -bounds.BoundingBox.size.z / 2);
|
||||
volumeGO.transform.localScale = bounds.BoundingBox.size;
|
||||
volumeGO.GetComponent<MeshRenderer>().material.SetColor(
|
||||
"_Color", UnityEngine.Random.ColorHSV());
|
||||
}
|
||||
|
||||
public void UpdateVolume(OVRBounded3D bounds)
|
||||
{
|
||||
var volumeGO = AnchorGameObject.transform.Find("Volume");
|
||||
if (volumeGO == null)
|
||||
CreateVolume(bounds);
|
||||
else
|
||||
{
|
||||
volumeGO.transform.localPosition = new Vector3(
|
||||
0, 0, -bounds.BoundingBox.size.z / 2);
|
||||
volumeGO.transform.localScale = bounds.BoundingBox.size;
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateMesh(OVRTriangleMesh mesh)
|
||||
{
|
||||
if (!mesh.TryGetCounts(out var vcount, out var tcount)) return;
|
||||
using var vs = new NativeArray<Vector3>(vcount, Allocator.Temp);
|
||||
using var ts = new NativeArray<int>(tcount * 3, Allocator.Temp);
|
||||
if (!mesh.TryGetMesh(vs, ts)) return;
|
||||
|
||||
var trimesh = new Mesh();
|
||||
trimesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
trimesh.SetVertices(vs);
|
||||
trimesh.SetTriangles(ts.ToArray(), 0);
|
||||
|
||||
var meshGO = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||||
meshGO.name = "Mesh";
|
||||
meshGO.transform.SetParent(AnchorGameObject.transform, false);
|
||||
meshGO.GetComponent<MeshFilter>().sharedMesh = trimesh;
|
||||
meshGO.GetComponent<MeshCollider>().sharedMesh = trimesh;
|
||||
meshGO.GetComponent<MeshRenderer>().material.SetColor(
|
||||
"_Color", UnityEngine.Random.ColorHSV());
|
||||
}
|
||||
|
||||
public void UpdateMesh(OVRTriangleMesh mesh)
|
||||
{
|
||||
var meshGO = AnchorGameObject.transform.Find("Mesh");
|
||||
if (meshGO != null) UnityEngine.Object.Destroy(meshGO);
|
||||
CreateMesh(mesh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper function for requesting Scene Capture.
|
||||
/// </summary>
|
||||
public static async Task<bool> RequestSceneCapture()
|
||||
{
|
||||
if (SceneCaptureRunning) return false;
|
||||
SceneCaptureRunning = true;
|
||||
|
||||
var waiting = true;
|
||||
Action<ulong, bool> onCaptured = (id, success) => { waiting = false; };
|
||||
|
||||
// subscribe, make non-blocking call, yield and wait
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
OVRManager.SceneCaptureComplete += onCaptured;
|
||||
if (!OVRPlugin.RequestSceneCapture("", out var _))
|
||||
{
|
||||
OVRManager.SceneCaptureComplete -= onCaptured;
|
||||
SceneCaptureRunning = false;
|
||||
return false;
|
||||
}
|
||||
while (waiting) Task.Delay(200);
|
||||
OVRManager.SceneCaptureComplete -= onCaptured;
|
||||
SceneCaptureRunning = false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
private static bool SceneCaptureRunning = false; // single instance
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper function for requesting the Android
|
||||
/// permission for scene data.
|
||||
/// </summary>
|
||||
public static void RequestScenePermission()
|
||||
{
|
||||
const string permission = "com.oculus.permission.USE_SCENE";
|
||||
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(permission))
|
||||
UnityEngine.Android.Permission.RequestUserPermission(permission);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d24718d7dbd471d4b91ff903813c15b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,178 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows you how to listen to changes in the underlying scene.
|
||||
///
|
||||
/// At a given update frequency, we query all anchors in the scene and create
|
||||
/// a snapshot. This is compared to a previous snapshot, and the
|
||||
/// differences are logged to the console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In order to create new scene items, you need to run Scene Capture, so
|
||||
/// this sample will only work on-device (as PC Link does not support it).
|
||||
///
|
||||
/// The logic involves checking whether anchors exist in the base snapshot
|
||||
/// and new snapshot. Furthermore, this sample contains the logic for
|
||||
/// rooms to be compared, as the room anchor is NEW whenever a child element
|
||||
/// is added or removed.
|
||||
/// </remarks>
|
||||
public class SnapshotSceneManager : MonoBehaviour
|
||||
{
|
||||
public float UpdateFrequencySeconds = 5;
|
||||
|
||||
SceneSnapshot _snapshot = new SceneSnapshot();
|
||||
|
||||
void Start()
|
||||
{
|
||||
SceneManagerHelper.RequestScenePermission();
|
||||
StartCoroutine(UpdateScenePeriodically());
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (OVRInput.GetDown(OVRInput.RawButton.A))
|
||||
_ = SceneManagerHelper.RequestSceneCapture();
|
||||
}
|
||||
|
||||
IEnumerator UpdateScenePeriodically()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(UpdateFrequencySeconds);
|
||||
UpdateScene();
|
||||
}
|
||||
}
|
||||
|
||||
async void UpdateScene()
|
||||
{
|
||||
// get current snapshot and compare to previous
|
||||
var currentSnapshot = await LoadSceneSnapshotAsync();
|
||||
var differences = await new SnapshotComparer(
|
||||
_snapshot, currentSnapshot).Compare();
|
||||
|
||||
// inform user of changes
|
||||
var sb = new StringBuilder();
|
||||
if (differences.Count > 0)
|
||||
{
|
||||
sb.AppendLine("---- SCENE SNAPSHOT ----");
|
||||
foreach (var (anchor, change) in differences)
|
||||
sb.AppendLine($"{change}: {AnchorInfo(anchor)}");
|
||||
Debug.Log(sb.ToString());
|
||||
}
|
||||
|
||||
// update previous snapshot
|
||||
_snapshot = currentSnapshot;
|
||||
}
|
||||
|
||||
async Task<SceneSnapshot> LoadSceneSnapshotAsync()
|
||||
{
|
||||
// create snapshot from all rooms and their anchors
|
||||
var snapshot = new SceneSnapshot();
|
||||
|
||||
var rooms = new List<OVRAnchor>();
|
||||
await OVRAnchor.FetchAnchorsAsync<OVRRoomLayout>(rooms);
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (!room.TryGetComponent(out OVRAnchorContainer container))
|
||||
continue;
|
||||
|
||||
var children = new List<OVRAnchor>();
|
||||
await container.FetchChildrenAsync(children);
|
||||
snapshot.Anchors.Add(room);
|
||||
snapshot.Anchors.AddRange(children);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
string AnchorInfo(OVRAnchor anchor)
|
||||
{
|
||||
if (anchor.TryGetComponent(out OVRRoomLayout room) && room.IsEnabled)
|
||||
return $"{anchor.Uuid} - ROOM";
|
||||
if (anchor.TryGetComponent(out OVRSemanticLabels labels) && labels.IsEnabled)
|
||||
return $"{anchor.Uuid} - {labels.Labels}";
|
||||
return $"{anchor.Uuid}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A basic container class that holds a list of anchors.
|
||||
/// This class could be extended to optimize the comparison
|
||||
/// between snapshots (such as keeping the room-child
|
||||
/// relationship, or storing the location and/or bounds)
|
||||
/// </summary>
|
||||
class SceneSnapshot
|
||||
{
|
||||
public List<OVRAnchor> Anchors { get; } = new List<OVRAnchor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the custom logic for scene snapshot comparison.
|
||||
/// </summary>
|
||||
class SnapshotComparer
|
||||
{
|
||||
public SceneSnapshot BaseSnapshot { get; }
|
||||
public SceneSnapshot NewSnapshot { get; }
|
||||
|
||||
public SnapshotComparer(SceneSnapshot baseSnapshot, SceneSnapshot newSnapshot)
|
||||
{
|
||||
BaseSnapshot = baseSnapshot;
|
||||
NewSnapshot = newSnapshot;
|
||||
}
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
New,
|
||||
Missing,
|
||||
Changed
|
||||
}
|
||||
|
||||
public async Task<List<(OVRAnchor, ChangeType)>> Compare()
|
||||
{
|
||||
var changes = new List<(OVRAnchor, ChangeType)>();
|
||||
|
||||
// if in base but not in new, then missing
|
||||
// if in new but not in base, then new
|
||||
foreach (var anchor1 in BaseSnapshot.Anchors)
|
||||
if (!NewSnapshot.Anchors.Contains(anchor1))
|
||||
changes.Add((anchor1, ChangeType.Missing));
|
||||
foreach (var anchor2 in NewSnapshot.Anchors)
|
||||
if (!BaseSnapshot.Anchors.Contains(anchor2))
|
||||
changes.Add((anchor2, ChangeType.New));
|
||||
|
||||
// further custom checks
|
||||
await CheckRoomChanges(changes);
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async Task CheckRoomChanges(List<(OVRAnchor, ChangeType)> changes)
|
||||
{
|
||||
// a room with new/deleted/edited child anchors is considered
|
||||
// a NEW anchor, so we will check if any child elements are
|
||||
// the same and mark both old and new room anchors as CHANGED
|
||||
for (var i = 0; i < changes.Count; i++)
|
||||
{
|
||||
var (anchor, change) = changes[i];
|
||||
|
||||
var isRoom = anchor.TryGetComponent(out OVRRoomLayout room) &&
|
||||
room.IsEnabled;
|
||||
if (!isRoom || change == ChangeType.Changed)
|
||||
continue;
|
||||
|
||||
var childAnchors = new List<OVRAnchor>();
|
||||
if (!await room.FetchLayoutAnchorsAsync(childAnchors))
|
||||
continue;
|
||||
|
||||
var comparisonAnchors = change == ChangeType.New ?
|
||||
BaseSnapshot.Anchors : NewSnapshot.Anchors;
|
||||
foreach (var childAnchor in childAnchors)
|
||||
if (comparisonAnchors.Contains(childAnchor))
|
||||
changes[i] = (anchor, ChangeType.Changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b12c97ae506923d4db53d6211b74bb7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(OVRSceneAnchor))]
|
||||
[DefaultExecutionOrder(30)]
|
||||
public class FurnitureSpawner : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Add a point at ceiling.")]
|
||||
public GameObject RoomLightPrefab;
|
||||
|
||||
[Tooltip("This prefab will be used if the label is " +
|
||||
"not in the SpawnablesPrefabs")]
|
||||
public SimpleResizable FallbackPrefab;
|
||||
|
||||
public List<Spawnable> SpawnablePrefabs;
|
||||
|
||||
private OVRSceneAnchor _sceneAnchor;
|
||||
private OVRSemanticClassification _classification;
|
||||
private static GameObject _roomLightRef;
|
||||
|
||||
private int _frameCounter;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_sceneAnchor = GetComponent<OVRSceneAnchor>();
|
||||
_classification = GetComponent<OVRSemanticClassification>();
|
||||
AddRoomLight();
|
||||
SpawnSpawnable();
|
||||
}
|
||||
|
||||
private void SpawnSpawnable()
|
||||
{
|
||||
if (!FindValidSpawnable(out var currentSpawnable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current anchor's information
|
||||
Vector3 position = transform.position;
|
||||
Quaternion rotation = transform.rotation;
|
||||
Vector3 localScale = transform.localScale;
|
||||
|
||||
var plane = _sceneAnchor.GetComponent<OVRScenePlane>();
|
||||
var volume = _sceneAnchor.GetComponent<OVRSceneVolume>();
|
||||
|
||||
var dimensions = volume ? volume.Dimensions : Vector3.one;
|
||||
|
||||
if (_classification && plane)
|
||||
{
|
||||
dimensions = plane.Dimensions;
|
||||
dimensions.z = 1;
|
||||
|
||||
// Special case 01: Has only top plane
|
||||
if (_classification.Contains(OVRSceneManager.Classification.Table) ||
|
||||
_classification.Contains(OVRSceneManager.Classification.Couch))
|
||||
{
|
||||
GetVolumeFromTopPlane(
|
||||
transform,
|
||||
plane.Dimensions,
|
||||
transform.position.y,
|
||||
out position,
|
||||
out rotation,
|
||||
out localScale);
|
||||
|
||||
dimensions = localScale;
|
||||
// The pivot for the resizer is at the top
|
||||
position.y += localScale.y / 2.0f;
|
||||
}
|
||||
|
||||
// Special case 02: Set wall thickness to something small instead of default value (1.0m)
|
||||
if (_classification.Contains(OVRSceneManager.Classification.WallFace) ||
|
||||
_classification.Contains(OVRSceneManager.Classification.Ceiling) ||
|
||||
_classification.Contains(OVRSceneManager.Classification.Floor))
|
||||
{
|
||||
dimensions.z = 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
GameObject root = new GameObject("Root");
|
||||
root.transform.parent = transform;
|
||||
root.transform.SetPositionAndRotation(position, rotation);
|
||||
|
||||
SimpleResizer resizer = new SimpleResizer();
|
||||
resizer.CreateResizedObject(dimensions, root, currentSpawnable);
|
||||
}
|
||||
|
||||
private bool FindValidSpawnable(out SimpleResizable currentSpawnable)
|
||||
{
|
||||
currentSpawnable = null;
|
||||
|
||||
if (!_classification) return false;
|
||||
|
||||
var sceneManager = FindObjectOfType<OVRSceneManager>();
|
||||
if (!sceneManager) return false;
|
||||
|
||||
foreach (var spawnable in SpawnablePrefabs)
|
||||
{
|
||||
if (_classification.Contains(spawnable.ClassificationLabel))
|
||||
{
|
||||
currentSpawnable = spawnable.ResizablePrefab;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FallbackPrefab != null)
|
||||
{
|
||||
currentSpawnable = FallbackPrefab;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddRoomLight()
|
||||
{
|
||||
if (!RoomLightPrefab) return;
|
||||
if (_classification && _classification.Contains(OVRSceneManager.Classification.Ceiling) &&
|
||||
!_roomLightRef)
|
||||
{
|
||||
_roomLightRef = Instantiate(RoomLightPrefab, _sceneAnchor.transform);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetVolumeFromTopPlane(
|
||||
Transform plane,
|
||||
Vector2 dimensions,
|
||||
float height,
|
||||
out Vector3 position,
|
||||
out Quaternion rotation,
|
||||
out Vector3 localScale)
|
||||
{
|
||||
float halfHeight = height / 2.0f;
|
||||
position = plane.position - Vector3.up * halfHeight;
|
||||
rotation = Quaternion.LookRotation(-plane.up, Vector3.up);
|
||||
localScale = new Vector3(dimensions.x, halfHeight * 2.0f, dimensions.y);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0be459a7d52f69342a549e7e1ffa0b02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class MyCustomSceneModelLoader : OVRSceneModelLoader
|
||||
{
|
||||
IEnumerator DelayedLoad()
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
Debug.Log("[MyCustomSceneLoader] calling OVRSceneManager.LoadSceneModel() delayed by 1 second");
|
||||
SceneManager.LoadSceneModel();
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
// Don't load immediately, wait some time
|
||||
StartCoroutine(DelayedLoad());
|
||||
}
|
||||
|
||||
protected override void OnNoSceneModelToLoad()
|
||||
{
|
||||
// Don't trigger capture flow in case there is no scene, just log a message
|
||||
Debug.Log("[MyCustomSceneLoader] There is no scene to load, but we don't want to trigger scene capture.");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 056241bc1d3002f45b6bd743c76ddaf3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(OVRManager))]
|
||||
[System.Obsolete("This script is deprecated and will be removed in a future release")]
|
||||
public class PassthroughPlayInEditor : MonoBehaviour
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6de5669300861c945a02294dfbab89cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -101
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class RequestCaptureFlow : MonoBehaviour
|
||||
{
|
||||
public OVRInput.Button RequestCaptureBtn = OVRInput.Button.Two;
|
||||
private OVRSceneManager _sceneManager;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_sceneManager = FindObjectOfType<OVRSceneManager>();
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
private void Update()
|
||||
{
|
||||
if (OVRInput.GetUp(RequestCaptureBtn))
|
||||
{
|
||||
_sceneManager.RequestSceneCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09770b1619250a043bae7ef5b2c882e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,78 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This class will continuously check the mesh of a MeshFilter component
|
||||
/// and if it has changed to a new mesh, it will create barycentric
|
||||
/// coordinates and set this to the per vertex color information of the
|
||||
/// mesh.
|
||||
///
|
||||
/// We only need barycentric coordinates for our wireframe shader.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this will increase the number of vertices by 3. Avoid using
|
||||
/// this class for anything other than debugging.
|
||||
///
|
||||
/// The alternative is to use geometry shaders, however, they aren't
|
||||
/// well supported for non Multi-Pass rendering modes.
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(MeshFilter))]
|
||||
public class SetMeshBarycentricCoordinates : MonoBehaviour
|
||||
{
|
||||
MeshFilter _meshFilter;
|
||||
Mesh _mesh;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_meshFilter = GetComponent<MeshFilter>();
|
||||
|
||||
StartCoroutine(CheckMeshData());
|
||||
}
|
||||
|
||||
private IEnumerator CheckMeshData()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
if (_meshFilter.mesh == null ||
|
||||
_mesh == _meshFilter.mesh ||
|
||||
_meshFilter.mesh.vertexCount == 0)
|
||||
{
|
||||
yield return new WaitForSeconds(1);
|
||||
}
|
||||
|
||||
// we have a new mesh with data that we need to populate
|
||||
CreateBarycentricCoordinates();
|
||||
}
|
||||
|
||||
private void CreateBarycentricCoordinates()
|
||||
{
|
||||
// calculate the barycentric coordinate per vertex, and set
|
||||
// provide these in the color data of the mesh.
|
||||
var mesh = _meshFilter.mesh;
|
||||
|
||||
var vertices = mesh.vertices;
|
||||
var triangles = mesh.GetTriangles(0);
|
||||
|
||||
var c = new Color[triangles.Length];
|
||||
var v = new Vector3[triangles.Length];
|
||||
var idx = new int[triangles.Length];
|
||||
for (var i = 0; i < triangles.Length; i++)
|
||||
{
|
||||
c[i] = new Color(
|
||||
i % 3 == 0 ? 1.0f : 0.0f,
|
||||
i % 3 == 1 ? 1.0f : 0.0f,
|
||||
i % 3 == 2 ? 1.0f : 0.0f);
|
||||
v[i] = vertices[triangles[i]];
|
||||
idx[i] = i;
|
||||
}
|
||||
|
||||
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
mesh.SetVertices(v);
|
||||
mesh.SetColors(c);
|
||||
mesh.SetIndices(idx, MeshTopology.Triangles, 0, true, 0);
|
||||
|
||||
_mesh = mesh;
|
||||
_meshFilter.mesh = _mesh;
|
||||
_meshFilter.mesh.RecalculateNormals();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46359bb1bfe3bc54e812938f0638c227
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1df47408ad3487e4c966dbda60669323
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,162 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// A MonoBehavior that provides the ability for a mesh to
|
||||
/// be rescaled using 9-slice scaling (27-slicing in 3D).
|
||||
/// For more details, see <seealso cref="SimpleResizer"/>>.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class SimpleResizable : MonoBehaviour
|
||||
{
|
||||
public Vector3 PivotPosition => _pivotTransform.position;
|
||||
|
||||
[Space(15)] public Method ScalingX;
|
||||
[Range(0, 0.5f)] public float PaddingX;
|
||||
|
||||
[Range(-0.5f, 0)] public float PaddingXMax;
|
||||
|
||||
[Space(15)] public Method ScalingY;
|
||||
[Range(0, 0.5f)] public float PaddingY;
|
||||
|
||||
[Range(-0.5f, 0)] public float PaddingYMax;
|
||||
|
||||
[Space(15)] public Method ScalingZ;
|
||||
[Range(0, 0.5f)] public float PaddingZ;
|
||||
|
||||
[Range(-0.5f, 0)] public float PaddingZMax;
|
||||
|
||||
public enum Method
|
||||
{
|
||||
Adapt,
|
||||
AdaptWithAsymmetricalPadding,
|
||||
Scale,
|
||||
None
|
||||
}
|
||||
|
||||
public Vector3 DefaultSize { get; private set; }
|
||||
public Mesh OriginalMesh { get; private set; }
|
||||
|
||||
private Vector3 _oldSize;
|
||||
private MeshFilter _meshFilter;
|
||||
|
||||
[SerializeField] private Vector3 _newSize;
|
||||
[SerializeField] private bool _updateInPlayMode;
|
||||
[SerializeField] private Transform _pivotTransform;
|
||||
|
||||
public void SetNewSize(Vector3 newSize) => _newSize = newSize;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_meshFilter = GetComponent<MeshFilter>();
|
||||
OriginalMesh = GetComponent<MeshFilter>().sharedMesh;
|
||||
DefaultSize = OriginalMesh.bounds.size;
|
||||
_newSize = DefaultSize;
|
||||
_oldSize = _newSize;
|
||||
|
||||
if (!_pivotTransform)
|
||||
_pivotTransform = transform.Find("Pivot");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
DefaultSize = OriginalMesh.bounds.size;
|
||||
if (_newSize == Vector3.zero)
|
||||
_newSize = DefaultSize;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Application.isPlaying && !_updateInPlayMode)
|
||||
return;
|
||||
|
||||
if (_newSize != _oldSize)
|
||||
{
|
||||
_oldSize = _newSize;
|
||||
|
||||
var resizedMesh = SimpleResizer.ProcessVertices(this, _newSize, true);
|
||||
_meshFilter.sharedMesh = resizedMesh;
|
||||
_meshFilter.sharedMesh.RecalculateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!_pivotTransform)
|
||||
return;
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
float lineSize = 0.1f;
|
||||
|
||||
Vector3 startX = _pivotTransform.position + Vector3.left * lineSize * 0.5f;
|
||||
Vector3 startY = _pivotTransform.position + Vector3.down * lineSize * 0.5f;
|
||||
Vector3 startZ = _pivotTransform.position + Vector3.back * lineSize * 0.5f;
|
||||
|
||||
Gizmos.DrawRay(startX, Vector3.right * lineSize);
|
||||
Gizmos.DrawRay(startY, Vector3.up * lineSize);
|
||||
Gizmos.DrawRay(startZ, Vector3.forward * lineSize);
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
// The furniture piece was not customized yet, nothing to do here
|
||||
if (_meshFilter.sharedMesh == null)
|
||||
return;
|
||||
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Vector3 newCenter = _meshFilter.sharedMesh.bounds.center;
|
||||
|
||||
Gizmos.color = new Color(1, 0, 0, 0.5f);
|
||||
switch (ScalingX)
|
||||
{
|
||||
case Method.Adapt:
|
||||
Gizmos.DrawWireCube(newCenter, new Vector3(_newSize.x * PaddingX * 2, _newSize.y, _newSize.z));
|
||||
break;
|
||||
case Method.AdaptWithAsymmetricalPadding:
|
||||
Gizmos.DrawWireCube(newCenter + new Vector3(
|
||||
_newSize.x * PaddingX, 0, 0), new Vector3(0, _newSize.y, _newSize.z));
|
||||
Gizmos.DrawWireCube(newCenter + new Vector3(
|
||||
_newSize.x * PaddingXMax, 0, 0), new Vector3(0, _newSize.y, _newSize.z));
|
||||
break;
|
||||
case Method.None:
|
||||
Gizmos.DrawWireCube(newCenter, _newSize);
|
||||
break;
|
||||
}
|
||||
|
||||
Gizmos.color = new Color(0, 1, 0, 0.5f);
|
||||
switch (ScalingY)
|
||||
{
|
||||
case Method.Adapt:
|
||||
Gizmos.DrawWireCube(newCenter, new Vector3(_newSize.x, _newSize.y * PaddingY * 2, _newSize.z));
|
||||
break;
|
||||
case Method.AdaptWithAsymmetricalPadding:
|
||||
Gizmos.DrawWireCube(newCenter + new Vector3(0, _newSize.y * PaddingY, 0),
|
||||
new Vector3(_newSize.x, 0, _newSize.z));
|
||||
Gizmos.DrawWireCube(newCenter + new Vector3(0, _newSize.y * PaddingYMax, 0),
|
||||
new Vector3(_newSize.x, 0, _newSize.z));
|
||||
break;
|
||||
case Method.None:
|
||||
Gizmos.DrawWireCube(newCenter, _newSize);
|
||||
break;
|
||||
}
|
||||
|
||||
Gizmos.color = new Color(0, 0, 1, 0.5f);
|
||||
switch (ScalingZ)
|
||||
{
|
||||
case Method.Adapt:
|
||||
Gizmos.DrawWireCube(newCenter, new Vector3(_newSize.x, _newSize.y, _newSize.z * PaddingZ * 2));
|
||||
break;
|
||||
case Method.AdaptWithAsymmetricalPadding:
|
||||
Gizmos.DrawWireCube(newCenter + new Vector3(0, 0, _newSize.z * PaddingZ),
|
||||
new Vector3(_newSize.x, _newSize.y, 0));
|
||||
Gizmos.DrawWireCube(newCenter + new Vector3(0, 0, _newSize.z * PaddingZMax),
|
||||
new Vector3(_newSize.x, _newSize.y, 0));
|
||||
break;
|
||||
case Method.None:
|
||||
Gizmos.DrawWireCube(newCenter, _newSize);
|
||||
break;
|
||||
}
|
||||
|
||||
Gizmos.color = new Color(0, 1, 1, 1);
|
||||
Gizmos.DrawWireCube(newCenter, _newSize);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39b243a9b1035c94e9f75c4e11283893
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,152 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This class will create a mesh with vertices that have been scaled using
|
||||
/// 9-slice scaling in 3D (27-slicing) to solve issues that can arise
|
||||
/// when meshes have been stretched along 1 axis more than the others.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// While this can solve issues related to geometry stretching, it does
|
||||
/// not attempt to solve texture stretching issues, and should therefore be
|
||||
/// used more as a starting point to see how to modify 3D models dynamically.
|
||||
/// </remarks>
|
||||
public class SimpleResizer
|
||||
{
|
||||
public void CreateResizedObject(Vector3 newSize, GameObject parent, SimpleResizable sourcePrefab)
|
||||
{
|
||||
var prefab = MonoBehaviour.Instantiate(sourcePrefab.gameObject, Vector3.zero, Quaternion.identity);
|
||||
prefab.name = sourcePrefab.name;
|
||||
|
||||
var resizable = prefab.GetComponent<SimpleResizable>();
|
||||
resizable.SetNewSize(newSize);
|
||||
if (resizable == null)
|
||||
{
|
||||
Debug.LogError("Resizable component missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
var resizedMesh = ProcessVertices(resizable, newSize);
|
||||
|
||||
MeshFilter mf = prefab.GetComponent<MeshFilter>();
|
||||
mf.sharedMesh = resizedMesh;
|
||||
mf.sharedMesh.RecalculateBounds();
|
||||
|
||||
// child it after creation so the bounds math plays nicely
|
||||
prefab.transform.parent = parent.transform;
|
||||
prefab.transform.localPosition = Vector3.zero;
|
||||
prefab.transform.localRotation = Quaternion.identity;
|
||||
|
||||
// cleanup
|
||||
MonoBehaviour.Destroy(resizable);
|
||||
}
|
||||
|
||||
internal static Mesh ProcessVertices(SimpleResizable resizable, Vector3 newSize, bool pivot = false)
|
||||
{
|
||||
Mesh originalMesh = resizable.OriginalMesh;
|
||||
Vector3 originalBounds = resizable.DefaultSize;
|
||||
|
||||
// Force scaling if newSize is smaller than the original mesh
|
||||
SimpleResizable.Method methodX = (originalBounds.x < newSize.x)
|
||||
? resizable.ScalingX
|
||||
: SimpleResizable.Method.Scale;
|
||||
SimpleResizable.Method methodY = (originalBounds.y < newSize.y)
|
||||
? resizable.ScalingY
|
||||
: SimpleResizable.Method.Scale;
|
||||
SimpleResizable.Method methodZ = (originalBounds.z < newSize.z)
|
||||
? resizable.ScalingZ
|
||||
: SimpleResizable.Method.Scale;
|
||||
|
||||
Vector3[] resizedVertices = originalMesh.vertices;
|
||||
|
||||
// Transform pivot to object local space otherwise a
|
||||
// world-space transform can affect the resizer
|
||||
Vector3 localSpacePivot = resizable.transform.InverseTransformPoint(
|
||||
resizable.PivotPosition);
|
||||
|
||||
float pivotX = (1 / resizable.DefaultSize.x) * localSpacePivot.x;
|
||||
float pivotY = (1 / resizable.DefaultSize.y) * localSpacePivot.y;
|
||||
float pivotZ = (1 / resizable.DefaultSize.z) * localSpacePivot.z;
|
||||
|
||||
for (int i = 0; i < resizedVertices.Length; i++)
|
||||
{
|
||||
Vector3 vertexPosition = resizedVertices[i];
|
||||
vertexPosition.x = CalculateNewVertexPosition(
|
||||
methodX,
|
||||
vertexPosition.x,
|
||||
originalBounds.x,
|
||||
newSize.x,
|
||||
resizable.PaddingX,
|
||||
resizable.PaddingXMax,
|
||||
pivotX);
|
||||
|
||||
vertexPosition.y = CalculateNewVertexPosition(
|
||||
methodY,
|
||||
vertexPosition.y,
|
||||
originalBounds.y,
|
||||
newSize.y,
|
||||
resizable.PaddingY,
|
||||
resizable.PaddingYMax,
|
||||
pivotY);
|
||||
|
||||
vertexPosition.z = CalculateNewVertexPosition(
|
||||
methodZ,
|
||||
vertexPosition.z,
|
||||
originalBounds.z,
|
||||
newSize.z,
|
||||
resizable.PaddingZ,
|
||||
resizable.PaddingZMax,
|
||||
pivotZ);
|
||||
|
||||
if (pivot)
|
||||
vertexPosition += localSpacePivot;
|
||||
|
||||
resizedVertices[i] = vertexPosition;
|
||||
}
|
||||
|
||||
Mesh clonedMesh = MonoBehaviour.Instantiate(originalMesh);
|
||||
clonedMesh.vertices = resizedVertices;
|
||||
|
||||
return clonedMesh;
|
||||
}
|
||||
|
||||
private static float CalculateNewVertexPosition(
|
||||
SimpleResizable.Method resizeMethod,
|
||||
float currentPosition,
|
||||
float currentSize,
|
||||
float newSize,
|
||||
float padding,
|
||||
float paddingMax,
|
||||
float pivot)
|
||||
{
|
||||
float resizedRatio = currentSize / 2
|
||||
* (newSize / 2 * (1 / (currentSize / 2)))
|
||||
- currentSize / 2;
|
||||
|
||||
switch (resizeMethod)
|
||||
{
|
||||
case SimpleResizable.Method.Adapt:
|
||||
if (Mathf.Abs(currentPosition) >= padding)
|
||||
currentPosition = resizedRatio * Mathf.Sign(currentPosition) + currentPosition;
|
||||
break;
|
||||
|
||||
case SimpleResizable.Method.AdaptWithAsymmetricalPadding:
|
||||
if (currentPosition >= padding)
|
||||
currentPosition = resizedRatio * Mathf.Sign(currentPosition) + currentPosition;
|
||||
if (currentPosition <= paddingMax)
|
||||
currentPosition = resizedRatio * Mathf.Sign(currentPosition) + currentPosition;
|
||||
break;
|
||||
|
||||
case SimpleResizable.Method.Scale:
|
||||
currentPosition = newSize / (currentSize / currentPosition);
|
||||
break;
|
||||
|
||||
case SimpleResizable.Method.None:
|
||||
break;
|
||||
}
|
||||
|
||||
float pivotPos = newSize * (-pivot);
|
||||
currentPosition += pivotPos;
|
||||
|
||||
return currentPosition;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85f4ec0cb9fef7b4e9223c280e418389
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
[Serializable]
|
||||
public class Spawnable : ISerializationCallbackReceiver
|
||||
{
|
||||
public SimpleResizable ResizablePrefab;
|
||||
public string ClassificationLabel = "";
|
||||
|
||||
[SerializeField] private int _editorClassificationIndex;
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
if (ClassificationLabel != "")
|
||||
{
|
||||
int IndexOf(string label, IEnumerable<string> collection)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (item == label)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We do this every time we deserialize in case the classification options have been updated
|
||||
// This ensures that the label displayed
|
||||
_editorClassificationIndex = IndexOf(ClassificationLabel, OVRSceneManager.Classification.List);
|
||||
|
||||
if (_editorClassificationIndex < 0)
|
||||
{
|
||||
Debug.LogError($"[{nameof(Spawnable)}] OnAfterDeserialize() " + ClassificationLabel +
|
||||
" not found. The Classification list in OVRSceneManager has likely changed");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No classification was selected, so we can just assign a default
|
||||
// This typically happens this object was just created
|
||||
_editorClassificationIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(Spawnable))]
|
||||
internal class SpawnableEditor : PropertyDrawer
|
||||
{
|
||||
private static readonly string[] ClassificationList = OVRSceneManager.Classification.List.ToArray();
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return base.GetPropertyHeight(property, label) * 2.2f;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
SerializedProperty labelProperty = property.FindPropertyRelative(nameof(Spawnable.ClassificationLabel));
|
||||
SerializedProperty editorClassificationIndex = property.FindPropertyRelative("_editorClassificationIndex");
|
||||
SerializedProperty prefab = property.FindPropertyRelative(nameof(Spawnable.ResizablePrefab));
|
||||
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
float y = position.y;
|
||||
float h = position.height / 2;
|
||||
|
||||
Rect rect = new Rect(position.x, y, position.width, h);
|
||||
if (editorClassificationIndex.intValue == -1)
|
||||
{
|
||||
var list = new List<string>
|
||||
{
|
||||
labelProperty.stringValue + " (invalid)"
|
||||
};
|
||||
list.AddRange(OVRSceneManager.Classification.List);
|
||||
editorClassificationIndex.intValue = EditorGUI.Popup(rect, 0, list.ToArray()) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
editorClassificationIndex.intValue = EditorGUI.Popup(
|
||||
rect,
|
||||
editorClassificationIndex.intValue,
|
||||
ClassificationList);
|
||||
}
|
||||
|
||||
if (editorClassificationIndex.intValue >= 0 &&
|
||||
editorClassificationIndex.intValue < ClassificationList.Length)
|
||||
{
|
||||
labelProperty.stringValue = OVRSceneManager.Classification.List[editorClassificationIndex.intValue];
|
||||
}
|
||||
|
||||
EditorGUI.ObjectField(new Rect(position.x, y + EditorGUI.GetPropertyHeight(labelProperty), position.width, h),
|
||||
prefab);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9d27dc1003ace84a9fad4eeeaaf896f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,132 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(OVRSceneAnchor))]
|
||||
public class VolumeAndPlaneSwitcher : MonoBehaviour
|
||||
{
|
||||
public OVRSceneAnchor planePrefab;
|
||||
public OVRSceneAnchor volumePrefab;
|
||||
|
||||
public enum GeometryType
|
||||
{
|
||||
Plane,
|
||||
Volume,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct LabelGeometryPair
|
||||
{
|
||||
public string label;
|
||||
public GeometryType desiredGeometryType;
|
||||
}
|
||||
|
||||
public List<LabelGeometryPair> desiredSwitches;
|
||||
|
||||
private void ReplaceAnchor(OVRSceneAnchor prefab, Vector3 position, Quaternion rotation, Vector3 localScale)
|
||||
{
|
||||
var anchor = Instantiate(prefab, transform.parent);
|
||||
anchor.enabled = false; // disable so it won't update transform
|
||||
anchor.InitializeFrom(GetComponent<OVRSceneAnchor>());
|
||||
|
||||
anchor.transform.SetPositionAndRotation(position, rotation);
|
||||
foreach (Transform child in anchor.transform)
|
||||
{
|
||||
child.localScale = localScale;
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
var classification = GetComponent<OVRSemanticClassification>();
|
||||
if (!classification) return;
|
||||
|
||||
foreach (LabelGeometryPair pair in desiredSwitches)
|
||||
{
|
||||
if (classification.Contains(pair.label))
|
||||
{
|
||||
Vector3 position = Vector3.zero;
|
||||
Quaternion rotation = Quaternion.identity;
|
||||
Vector3 localScale = Vector3.zero;
|
||||
switch (pair.desiredGeometryType)
|
||||
{
|
||||
case GeometryType.Plane:
|
||||
{
|
||||
var volume = GetComponent<OVRSceneVolume>();
|
||||
if (!volume)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"Ignoring desired volume to plane switch for {pair.label} because it is not a volume.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Log($"IN Volume Position {transform.position}, Dimensions: {volume.Dimensions}");
|
||||
// This object is a volume, but we want a plane instead.
|
||||
GetTopPlaneFromVolume(
|
||||
transform,
|
||||
volume.Dimensions,
|
||||
out position,
|
||||
out rotation,
|
||||
out localScale);
|
||||
Debug.Log($"OUT Plane Position {position}, Dimensions: {localScale}");
|
||||
ReplaceAnchor(planePrefab, position, rotation, localScale);
|
||||
break;
|
||||
}
|
||||
case GeometryType.Volume:
|
||||
{
|
||||
var plane = GetComponent<OVRScenePlane>();
|
||||
if (!plane)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"Ignoring desired plane to volume switch for {pair.label} because it is not a plane.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Log($"IN Plane Position {transform.position}, Dimensions: {plane.Dimensions}");
|
||||
// This object is a plane, but we want a volume instead.
|
||||
GetVolumeFromTopPlane(
|
||||
transform,
|
||||
plane.Dimensions,
|
||||
transform.position.y,
|
||||
out position,
|
||||
out rotation,
|
||||
out localScale);
|
||||
Debug.Log($"OUT Volume Position {position}, Dimensions: {localScale}");
|
||||
ReplaceAnchor(volumePrefab, position, rotation, localScale);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IF we arrived here, no conversion was needed. Let's remove this component
|
||||
Destroy(this);
|
||||
}
|
||||
|
||||
private void GetVolumeFromTopPlane(
|
||||
Transform plane,
|
||||
Vector2 dimensions,
|
||||
float height,
|
||||
out Vector3 position,
|
||||
out Quaternion rotation,
|
||||
out Vector3 localScale)
|
||||
{
|
||||
position = plane.position;
|
||||
rotation = plane.rotation;
|
||||
localScale = new Vector3(dimensions.x, dimensions.y, height);
|
||||
}
|
||||
|
||||
private void GetTopPlaneFromVolume(
|
||||
Transform volume,
|
||||
Vector3 dimensions,
|
||||
out Vector3 position,
|
||||
out Quaternion rotation,
|
||||
out Vector3 localScale)
|
||||
{
|
||||
float halfHeight = dimensions.y / 2.0f;
|
||||
position = volume.position + Vector3.up * halfHeight;
|
||||
rotation = Quaternion.LookRotation(Vector3.up, -volume.forward);
|
||||
localScale = new Vector3(dimensions.x, dimensions.z, dimensions.y);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 337d67c8752f4d542bc432fdeba8f797
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user