From ed65d58af178205cfa5793a9f16291fdd3589090 Mon Sep 17 00:00:00 2001 From: "rderbier@gmail.com" Date: Fri, 12 Nov 2021 08:25:05 -0800 Subject: [PATCH] refactor Target class and Game constructor --- src/hideNseek/Game.cs | 344 +++++++-------------------------- src/hideNseek/Target.cs | 241 +++++++++++++++++++++++ src/hideNseek/hideNseek.csproj | 1 + 3 files changed, 315 insertions(+), 271 deletions(-) create mode 100644 src/hideNseek/Target.cs diff --git a/src/hideNseek/Game.cs b/src/hideNseek/Game.cs index ee35c1f..ca946d9 100644 --- a/src/hideNseek/Game.cs +++ b/src/hideNseek/Game.cs @@ -9,231 +9,7 @@ namespace hideNseek { - - class Target - { - public Pose pose; - public Model model; - public float scale; - public string name; - public Boolean isDetected; - public Boolean isHandled; - public Sound memo; - public Boolean isSelected; // true for the currently selected target - public float gazeTime; // howlong this target has the gaze - private String anchorID; - private SpatialAnchor anchor; - - public Target(Pose pose, Model target, float scale, String name, ref SpatialAnchor anchor) - { - this.pose = new Pose(pose.position, pose.orientation); - this.model = target; - this.scale = scale; - - this.name = (name != null) ? name : "target-" + Guid.NewGuid().ToString(); - this.anchor = anchor; - if (anchor != null) - { - this.anchorID = this.name; - anchor.RawCoordinateSystemAdjusted += this.OnCoordinateSystemAdjusted; - } - this.memo = null; - this.gazeTime = 0f; - tryRestore(); - } - private void OnCoordinateSystemAdjusted(SpatialAnchor sender, SpatialAnchorRawCoordinateSystemAdjustedEventArgs args) - { - if (World.FromPerceptionAnchor(this.anchor, out Pose at)) - { - this.pose = at; - } - } - - public Boolean isTargetDetected(float gazeDuration, Material hide, Material seen, Material selected, Boolean isDraggable=true, float distance = 2.0f, Boolean gazeIndicator = false) - { - // draw the target and detect if the target is seen - // - - Boolean detected = false; - Material mat = hide; - Matrix targetTransform = this.pose.ToMatrix(scale); // move and scale - - if (Input.EyesTracked.IsActive()) - { - //UI.Label("Eye tracking active"); - // Intersect the eye Ray with the objects - // user must be close enough - if (Vec3.Distance(Input.Head.position, this.pose.position) < distance) - { - Sphere zone = new Sphere(this.pose.position, this.scale); - if (Input.Eyes.Ray.Intersect(zone, out Vec3 at)) - { - if (gazeIndicator) - { - Default.MeshSphere.Draw(Default.Material, Matrix.TS(at, .02f)); - } - gazeTime += Time.Elapsedf; - if (gazeTime > gazeDuration) - { - detected = true; - mat = seen; // change target material - - } - } - else - { - gazeTime = 0f; - } - } - } - if (isSelected) { mat = selected; } - - // set target Material - this.model.RootNode.Material = mat; - // draw target - Bounds scaledBounds = new Bounds(this.model.Bounds.center, this.model.Bounds.dimensions * scale); - if (isDraggable) - { - this.isHandled = UI.Handle(this.name, ref this.pose, scaledBounds); - } - this.model.Draw(targetTransform); - return detected; - } - public async void saveAsync() - { - //save target info to file and to Anchor. - if (this.memo != null) - { - try - { - SoundInst s = this.memo.Play(Vec3.Zero); - s.Stop(); - - float[] floatBuffer = new float[this.memo.TotalSamples]; - this.memo.ReadSamples(ref floatBuffer); - byte[] byteBuffer = new byte[floatBuffer.Length * 4]; - System.Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length); - Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder; - Windows.Storage.StorageFile memoFile = - await storageFolder.CreateFileAsync( - "memo-" + this.name, - Windows.Storage.CreationCollisionOption.ReplaceExisting); - if (memoFile != null) - { - await Windows.Storage.FileIO.WriteBytesAsync(memoFile, byteBuffer); - } - // saving metadata - Windows.Storage.StorageFile confiFile = - await storageFolder.CreateFileAsync( - "config-" + this.name + ".json", - Windows.Storage.CreationCollisionOption.ReplaceExisting); - //String json = String.Format("{ \"scale\":\"{0:F4}\" }", this.scale); - JObject o = new JObject(); - o["scale"] = this.scale; - await Windows.Storage.FileIO.WriteTextAsync(confiFile, o.ToString()); - } catch (Exception ex) - { - // ignore file errors for now ! - } - } - - } - public async void clean() - { - Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder; - Windows.Storage.StorageFile dataFile = await storageFolder.GetFileAsync("memo-" + this.name); - if (dataFile != null) - { - await dataFile.DeleteAsync(); - } - Windows.Storage.StorageFile confiFile = await storageFolder.GetFileAsync("config-" + this.name + ".json"); - if (confiFile != null) - { - await confiFile.DeleteAsync(); - } - - } - public void removeAnchor(SpatialAnchorStore anchorStore) - { - if (this.anchorID != null) - { - anchorStore.Remove(this.anchorID); - this.anchorID = null; - } - } - public String anchorTarget(SpatialAnchorStore anchorStore, SpatialLocator locator, SpatialStationaryFrameOfReference referenceFrame = null , SpatialAnchor originAnchor = null) - { - if (anchorStore != null) - { - // create a world locked anchor where the origin is the current position of the Hololens - if (referenceFrame == null) - { - referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation(); - } - if (originAnchor == null) - { - originAnchor = SpatialAnchor.TryCreateRelativeTo(referenceFrame.CoordinateSystem); - } - - Pose originPose = World.FromPerceptionAnchor(originAnchor); - Pose poseInReferenceFrame = originPose.ToMatrix().Inverse.Transform(new Pose(this.pose.position, this.pose.orientation)); - SpatialAnchor targetAnchor = SpatialAnchor.TryCreateRelativeTo(referenceFrame.CoordinateSystem, poseInReferenceFrame.position, poseInReferenceFrame.orientation); - - removeAnchor(anchorStore); - - if (targetAnchor != null) - { - anchorID = this.name; - this.anchorID = anchorStore.TrySave(anchorID, targetAnchor) ? anchorID : null; - } - return this.anchorID; - } else - { - return null; - } - - } - public async void tryRestore() - { - // try to get info from file - // restore memo - try - { - Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder; - Windows.Storage.StorageFile dataFile = await storageFolder.GetFileAsync("memo-" + this.name); - if (dataFile != null) - { - IBuffer buffer = await FileIO.ReadBufferAsync(dataFile); - - // Use a dataReader object to read from the buffer - using (DataReader dataReader = DataReader.FromBuffer(buffer)) - { - byte[] byteBuffer = new byte[buffer.Length]; - dataReader.ReadBytes(byteBuffer); - var floatArray = new float[byteBuffer.Length / 4]; - System.Buffer.BlockCopy(byteBuffer, 0, floatArray, 0, byteBuffer.Length); - this.memo = Sound.CreateStream((float)floatArray.Length / 48000f); // in seconds - this.memo.WriteSamples(floatArray); - } - } - Windows.Storage.StorageFile configFile = await storageFolder.GetFileAsync("config-" + this.name+".json"); - if (configFile != null) - { - string text = await Windows.Storage.FileIO.ReadTextAsync(configFile); - var details = JObject.Parse(text); - if (details["scale"]!= null) - { - this.scale = (float)details["scale"]; - } - - } - } catch (Exception e) - { - // ignore errors - } - } - } class Game { private const Int32 MAX_SOUND_LENGTH = 48000 * 5; @@ -263,42 +39,28 @@ class Game private Double huntingDuration = 0f; private SpatialAnchorStore anchorStore; private SpatialLocator locator; + private Boolean isRecording = false; - private void tryRestoreAnchors() + + public Game() { - - IReadOnlyDictionary anchors = anchorStore.GetAllSavedAnchors(); - - foreach (KeyValuePair anchor in anchors) - { - if (World.FromPerceptionAnchor(anchor.Value, out Pose at)) - { - addTarget(at, targetDiameter, anchor.Key, anchor.Value); - } - } + initMaterials(); - } - private async Task InitStoreAsync() - { - locator = SpatialLocator.GetDefault(); - anchorStore = await SpatialAnchorManager.RequestStoreAsync(); - if (anchorStore != null) - { - tryRestoreAnchors(); - } - } + initSharedResources(); + initHolograms(); + initUI(); + + adminMode(); - public Game() - { - - // Create assets used by the game - micBuffer = new float[MAX_SOUND_LENGTH]; // 5 seconds - soundChunk = new float[MAX_SOUND_LENGTH]; // 0.5 second max - micIndex = 0; + World.OcclusionEnabled = true; + InitStoreAsync(); - + initMicrophoneRecording(); + } + private void initMaterials() + { targetMaterial = Default.Material.Copy(); //matAlphaBlend targetMaterial.Transparency = Transparency.Blend; targetMaterial.DepthWrite = false; @@ -328,17 +90,17 @@ public Game() hintMaterial.Transparency = Transparency.Blend; hintMaterial.DepthWrite = false; hintMaterial[MatParamName.ColorTint] = new Color(1f, 1f, 1f, 0.1f); - - windowAdminPose = new Pose(-.2f, 0, -0.65f, Quat.LookAt(new Vec3(-.2f, 0, -0.5f), Input.Head.position, Vec3.Up)); - windowUserPose = new Pose(+.4f, 0, -0.65f, Quat.LookAt(new Vec3(+.4f, 0, -0.5f), Input.Head.position, Vec3.Up)); + } + private void initSharedResources() + { + // init Sounds, Sprites ... foundSound = Sound.FromFile("sound_success.wav"); winSound = Sound.FromFile("welldone.wav"); promptFind = Sound.FromFile("tofind.wav"); promptNext = Sound.FromFile("nextfind.wav"); targetSound = Sound.FromFile("radar-sound.wav"); - Material compassMaterial = Material.Default.Copy(); - compass = Model.FromMesh(MeshUtils.createArrow(0.01f, 0.005f, 0.06f), compassMaterial); + powerSprite = Sprite.FromFile("power.png", SpriteType.Single); micSprite = Sprite.FromFile("microphone.png", SpriteType.Single); @@ -346,26 +108,57 @@ public Game() trashSprite = Sprite.FromFile("trash.png", SpriteType.Single); onSprite = Sprite.FromFile("on.png", SpriteType.Single); offSprite = Sprite.FromFile("off.png", SpriteType.Single); - adminMode(); - - // init UI - - + } + private void initHolograms() + { + Material compassMaterial = Material.Default.Copy(); + compass = Model.FromMesh(MeshUtils.createArrow(0.01f, 0.005f, 0.06f), compassMaterial); + } + private void initUI() + { + // // set UI scheme Color uiColor = Color.HSV(.83f, 0.33f, 1f, 0.8f); UI.ColorScheme = uiColor; + windowAdminPose = new Pose(-.2f, 0, -0.65f, Quat.LookAt(new Vec3(-.2f, 0, -0.5f), Input.Head.position, Vec3.Up)); + windowUserPose = new Pose(+.4f, 0, -0.65f, Quat.LookAt(new Vec3(+.4f, 0, -0.5f), Input.Head.position, Vec3.Up)); + } + private void initMicrophoneRecording() + { + // Create assets used by the game + micBuffer = new float[MAX_SOUND_LENGTH]; // 5 seconds + soundChunk = new float[MAX_SOUND_LENGTH]; // 0.5 second max + micIndex = 0; + // Mic Issue : Start Recording but ignore the first memo + isRecording = false; // not saving the memo + Microphone.Start(); // but start the Mic to drain the buffer once ! + } - World.OcclusionEnabled = true; - InitStoreAsync(); - + private void tryRestoreAnchors() + { - } - + IReadOnlyDictionary anchors = anchorStore.GetAllSavedAnchors(); + + foreach (KeyValuePair anchor in anchors) + { - public void clear() + if (World.FromPerceptionAnchor(anchor.Value, out Pose at)) + { + addTarget(at, targetDiameter, anchor.Key, anchor.Value); + } + } + + } + private async Task InitStoreAsync() { - targetList.Clear(); + locator = SpatialLocator.GetDefault(); + anchorStore = await SpatialAnchorManager.RequestStoreAsync(); + if (anchorStore != null) + { + tryRestoreAnchors(); + } } + public void startHunt() { reset(); @@ -514,6 +307,7 @@ public void deleteCurrentTarget() private void stopRecording(Target target) { Microphone.Stop(); + isRecording = false; if (target != null) { target.memo = Sound.CreateStream((float)micIndex / 24000f); @@ -553,7 +347,14 @@ private void handleRecording() } if (micIndex >= MAX_SOUND_LENGTH) { - stopRecording(currentTarget); + if (isRecording == true) + { + stopRecording(currentTarget); + } else + { + Microphone.Stop(); + // Mic issue workaround : start game by recording a bit and ignore the first 5s + } } } } @@ -624,6 +425,7 @@ public Boolean Step() if (UI.ButtonRound("StartRecord", micSprite)) { micIndex = 0; + isRecording = true; Microphone.Start(); } UI.SameLine(); UI.Label("record a short name"); diff --git a/src/hideNseek/Target.cs b/src/hideNseek/Target.cs new file mode 100644 index 0000000..e77ccfe --- /dev/null +++ b/src/hideNseek/Target.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using StereoKit; +using Windows.Storage; +using Windows.Storage.Streams; +using Newtonsoft.Json.Linq; +using Windows.Perception.Spatial; +using System.Threading.Tasks; + +namespace hideNseek +{ + + class Target + { + public Pose pose; + public Model model; + public float scale; + public string name; + public Boolean isDetected; + public Boolean isHandled; + public Sound memo; + public Boolean isSelected; // true for the currently selected target + public float gazeTime; // howlong this target has the gaze + private String anchorID; + private SpatialAnchor anchor; + + public Target(Pose pose, Model target, float scale, String name, ref SpatialAnchor anchor) + { + this.pose = new Pose(pose.position, pose.orientation); + this.model = target; + this.scale = scale; + + this.name = (name != null) ? name : "target-" + Guid.NewGuid().ToString(); + this.anchor = anchor; + if (anchor != null) + { + this.anchorID = this.name; + anchor.RawCoordinateSystemAdjusted += this.OnCoordinateSystemAdjusted; + } + this.memo = null; + this.gazeTime = 0f; + tryRestore(); + } + private void OnCoordinateSystemAdjusted(SpatialAnchor sender, SpatialAnchorRawCoordinateSystemAdjustedEventArgs args) + { + if (World.FromPerceptionAnchor(this.anchor, out Pose at)) + { + this.pose = at; + } + + } + + public Boolean isTargetDetected(float gazeDuration, Material hide, Material seen, Material selected, Boolean isDraggable = true, float distance = 2.0f, Boolean gazeIndicator = false) + { + // draw the target and detect if the target is seen + // + + Boolean detected = false; + Material mat = hide; + Matrix targetTransform = this.pose.ToMatrix(scale); // move and scale + + if (Input.EyesTracked.IsActive()) + { + //UI.Label("Eye tracking active"); + // Intersect the eye Ray with the objects + // user must be close enough + if (Vec3.Distance(Input.Head.position, this.pose.position) < distance) + { + Sphere zone = new Sphere(this.pose.position, this.scale); + if (Input.Eyes.Ray.Intersect(zone, out Vec3 at)) + { + if (gazeIndicator) + { + Default.MeshSphere.Draw(Default.Material, Matrix.TS(at, .02f)); + } + gazeTime += Time.Elapsedf; + if (gazeTime > gazeDuration) + { + detected = true; + mat = seen; // change target material + + } + } + else + { + gazeTime = 0f; + } + } + } + if (isSelected) { mat = selected; } + + // set target Material + this.model.RootNode.Material = mat; + // draw target + Bounds scaledBounds = new Bounds(this.model.Bounds.center, this.model.Bounds.dimensions * scale); + if (isDraggable) + { + this.isHandled = UI.Handle(this.name, ref this.pose, scaledBounds); + } + this.model.Draw(targetTransform); + return detected; + } + public async void saveAsync() + { + //save target info to file and to Anchor. + if (this.memo != null) + { + try + { + SoundInst s = this.memo.Play(Vec3.Zero); + s.Stop(); + + float[] floatBuffer = new float[this.memo.TotalSamples]; + this.memo.ReadSamples(ref floatBuffer); + byte[] byteBuffer = new byte[floatBuffer.Length * 4]; + System.Buffer.BlockCopy(floatBuffer, 0, byteBuffer, 0, byteBuffer.Length); + Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder; + Windows.Storage.StorageFile memoFile = + await storageFolder.CreateFileAsync( + "memo-" + this.name, + Windows.Storage.CreationCollisionOption.ReplaceExisting); + if (memoFile != null) + { + await Windows.Storage.FileIO.WriteBytesAsync(memoFile, byteBuffer); + } + // saving metadata + Windows.Storage.StorageFile confiFile = + await storageFolder.CreateFileAsync( + "config-" + this.name + ".json", + Windows.Storage.CreationCollisionOption.ReplaceExisting); + //String json = String.Format("{ \"scale\":\"{0:F4}\" }", this.scale); + JObject o = new JObject(); + o["scale"] = this.scale; + await Windows.Storage.FileIO.WriteTextAsync(confiFile, o.ToString()); + } + catch (Exception ex) + { + // ignore file errors for now ! + } + } + + } + public async void clean() + { + Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder; + Windows.Storage.StorageFile dataFile = await storageFolder.GetFileAsync("memo-" + this.name); + if (dataFile != null) + { + await dataFile.DeleteAsync(); + } + Windows.Storage.StorageFile confiFile = await storageFolder.GetFileAsync("config-" + this.name + ".json"); + if (confiFile != null) + { + await confiFile.DeleteAsync(); + } + + } + public void removeAnchor(SpatialAnchorStore anchorStore) + { + if (this.anchorID != null) + { + anchorStore.Remove(this.anchorID); + this.anchorID = null; + } + } + public String anchorTarget(SpatialAnchorStore anchorStore, SpatialLocator locator, SpatialStationaryFrameOfReference referenceFrame = null, SpatialAnchor originAnchor = null) + { + if (anchorStore != null) + { + // create a world locked anchor where the origin is the current position of the Hololens + if (referenceFrame == null) + { + referenceFrame = locator.CreateStationaryFrameOfReferenceAtCurrentLocation(); + } + if (originAnchor == null) + { + originAnchor = SpatialAnchor.TryCreateRelativeTo(referenceFrame.CoordinateSystem); + } + + Pose originPose = World.FromPerceptionAnchor(originAnchor); + Pose poseInReferenceFrame = originPose.ToMatrix().Inverse.Transform(new Pose(this.pose.position, this.pose.orientation)); + SpatialAnchor targetAnchor = SpatialAnchor.TryCreateRelativeTo(referenceFrame.CoordinateSystem, poseInReferenceFrame.position, poseInReferenceFrame.orientation); + + removeAnchor(anchorStore); + + if (targetAnchor != null) + { + anchorID = this.name; + this.anchorID = anchorStore.TrySave(anchorID, targetAnchor) ? anchorID : null; + } + return this.anchorID; + } + else + { + return null; + } + + } + public async void tryRestore() + { + // try to get info from file + // restore memo + try + { + Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder; + Windows.Storage.StorageFile dataFile = await storageFolder.GetFileAsync("memo-" + this.name); + if (dataFile != null) + { + IBuffer buffer = await FileIO.ReadBufferAsync(dataFile); + + // Use a dataReader object to read from the buffer + using (DataReader dataReader = DataReader.FromBuffer(buffer)) + { + byte[] byteBuffer = new byte[buffer.Length]; + dataReader.ReadBytes(byteBuffer); + var floatArray = new float[byteBuffer.Length / 4]; + System.Buffer.BlockCopy(byteBuffer, 0, floatArray, 0, byteBuffer.Length); + this.memo = Sound.CreateStream((float)floatArray.Length / 48000f); // in seconds + this.memo.WriteSamples(floatArray); + } + } + Windows.Storage.StorageFile configFile = await storageFolder.GetFileAsync("config-" + this.name + ".json"); + if (configFile != null) + { + string text = await Windows.Storage.FileIO.ReadTextAsync(configFile); + var details = JObject.Parse(text); + if (details["scale"] != null) + { + this.scale = (float)details["scale"]; + } + + } + } + catch (Exception e) + { + // ignore errors + } + } + } + +} \ No newline at end of file diff --git a/src/hideNseek/hideNseek.csproj b/src/hideNseek/hideNseek.csproj index 2bdcb0f..5c4e800 100644 --- a/src/hideNseek/hideNseek.csproj +++ b/src/hideNseek/hideNseek.csproj @@ -85,6 +85,7 @@ +