Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e7c54edf3d288044e813e05f8f5bbba6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5eceb167892f49a48a0f8e1ee2bd4e02
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e33e5c146f3dad247a398a5c52de790e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1ca019a7ed88b84792a6d0610555b21
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36623bf873a22fc498789172d2842726
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
{
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc7f55566ebbfa845a8c4e2b83070915
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5d0a6eb3cf408a4dbab4ae40f82517c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d24718d7dbd471d4b91ff903813c15b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b12c97ae506923d4db53d6211b74bb7e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0be459a7d52f69342a549e7e1ffa0b02
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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.");
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 056241bc1d3002f45b6bd743c76ddaf3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
{
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6de5669300861c945a02294dfbab89cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -101
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09770b1619250a043bae7ef5b2c882e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46359bb1bfe3bc54e812938f0638c227
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1df47408ad3487e4c966dbda60669323
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 39b243a9b1035c94e9f75c4e11283893
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85f4ec0cb9fef7b4e9223c280e418389
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9d27dc1003ace84a9fad4eeeaaf896f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 337d67c8752f4d542bc432fdeba8f797
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: