using UnityEngine; using System.Collections.Generic; using UnityEngine.Windows.Speech; using Academy.HoloToolkit.Unity; using Academy.HoloToolkit.Sharing; public class HologramPlacement : Singleton { /// /// Tracks if we have been sent a transform for the model. /// The model is rendered relative to the actual anchor. /// public bool GotTransform { get; private set; } /// /// When the experience starts, we disable all of the rendering of the model. /// List disabledRenderers = new List(); /// /// We use a voice command to enable moving the target. /// KeywordRecognizer keywordRecognizer; void Start() { // When we first start, we need to disable the model to avoid it obstructing the user picking a hat. DisableModel(); // We care about getting updates for the model transform. CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.StageTransform] = this.OnStageTransfrom; // And when a new user join we will send the model transform we have. SharingSessionTracker.Instance.SessionJoined += Instance_SessionJoined; // And if the users want to reset the stage transform. CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.ResetStage] = this.OnResetStage; // Setup a keyword recognizer to enable resetting the target location. List keywords = new List(); keywords.Add("Reset Target"); keywordRecognizer = new KeywordRecognizer(keywords.ToArray()); keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized; keywordRecognizer.Start(); } /// /// When the keyword recognizer hears a command this will be called. /// In this case we only have one keyword, which will re-enable moving the /// target. /// /// information to help route the voice command. private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args) { ResetStage(); } /// /// Resets the stage transform, so users can place the target again. /// public void ResetStage() { GotTransform = false; // AppStateManager needs to know about this so that // the right objects get input routed to them. AppStateManager.Instance.ResetStage(); // Other devices in the experience need to know about this as well. CustomMessages.Instance.SendResetStage(); // And we need to reset the object to its start animation state. GetComponent().ResetAnimation(); } /// /// When a new user joins we want to send them the relative transform for the model if we have it. /// /// /// private void Instance_SessionJoined(object sender, SharingSessionTracker.SessionJoinedEventArgs e) { if (GotTransform) { CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation); } } /// /// Turns off all renderers for the model. /// void DisableModel() { foreach (MeshRenderer renderer in gameObject.GetComponentsInChildren()) { if (renderer.enabled) { renderer.enabled = false; disabledRenderers.Add(renderer); } } foreach (MeshCollider collider in gameObject.GetComponentsInChildren()) { collider.enabled = false; } } /// /// Turns on all renderers that were disabled. /// void EnableModel() { foreach (MeshRenderer renderer in disabledRenderers) { renderer.enabled = true; } foreach (MeshCollider collider in gameObject.GetComponentsInChildren()) { collider.enabled = true; } disabledRenderers.Clear(); } void Update() { // Wait till users pick an avatar to enable renderers. if (disabledRenderers.Count > 0) { if (!PlayerAvatarStore.Instance.PickerActive && ImportExportAnchorManager.Instance.AnchorEstablished) { // After which we want to start rendering. EnableModel(); // And if we've already been sent the relative transform, we will use it. if (GotTransform) { // This triggers the animation sequence for the model and // puts the cool materials on the model. GetComponent().SendMessage("OnSelect"); } } } else if (GotTransform == false) { transform.position = Vector3.Lerp(transform.position, ProposeTransformPosition(), 0.2f); } } Vector3 ProposeTransformPosition() { Vector3 retval; // We need to know how many users are in the experience with good transforms. Vector3 cumulatedPosition = Camera.main.transform.position; int playerCount = 1; foreach (RemotePlayerManager.RemoteHeadInfo remoteHead in RemotePlayerManager.Instance.remoteHeadInfos) { if (remoteHead.Anchored && remoteHead.Active) { playerCount++; cumulatedPosition += remoteHead.HeadObject.transform.position; } } // If we have more than one player ... if (playerCount > 1) { // Put the transform in between the players. retval = cumulatedPosition / playerCount; RaycastHit hitInfo; // And try to put the transform on a surface below the midpoint of the players. if (Physics.Raycast(retval, Vector3.down, out hitInfo, 5, SpatialMappingManager.Instance.LayerMask)) { retval = hitInfo.point; } } // If we are the only player, have the model act as the 'cursor' ... else { // We prefer to put the model on a real world surface. RaycastHit hitInfo; if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hitInfo, 30, SpatialMappingManager.Instance.LayerMask)) { retval = hitInfo.point; } else { // But if we don't have a ray that intersects the real world, just put the model 2m in // front of the user. retval = Camera.main.transform.position + Camera.main.transform.forward * 2; } } return retval; } public void OnSelect() { // Note that we have a transform. GotTransform = true; // And send it to our friends. CustomMessages.Instance.SendStageTransform(transform.localPosition, transform.localRotation); } /// /// When a remote system has a transform for us, we'll get it here. /// /// void OnStageTransfrom(NetworkInMessage msg) { // We read the user ID but we don't use it here. msg.ReadInt64(); transform.localPosition = CustomMessages.Instance.ReadVector3(msg); transform.localRotation = CustomMessages.Instance.ReadQuaternion(msg); // The first time, we'll want to send the message to the model to do its animation and // swap its materials. if (disabledRenderers.Count == 0 && GotTransform == false) { GetComponent().SendMessage("OnSelect"); } GotTransform = true; } /// /// When a remote system has a transform for us, we'll get it here. /// void OnResetStage(NetworkInMessage msg) { GotTransform = false; GetComponent().ResetAnimation(); AppStateManager.Instance.ResetStage(); } }