/// :CodeDoc: Guides Using Hands /// ## Pointers /// /// And lastly, StereoKit also has a pointer system! This applies to /// more than just hands. Head, mouse, and other devices will also /// create pointers into the scene. You can filter pointers based on /// source family and device capabilities, so this is a great way to /// abstract a few more input sources nicely! public static void DrawPointers() { int hands = Input.PointerCount(InputSource.Hand); for (int i = 0; i < hands; i++) { Pointer pointer = Input.Pointer(i, InputSource.Hand); Lines.Add(pointer.ray, 0.5f, Color.White, Units.mm2m); Lines.AddAxis(pointer.Pose); } }
/// Now all we need to do is show the QR codes! In this case, /// we're just displaying an axis widget, and the contents of /// the QR code as text. /// /// With the text, all we're doing is squeezing the text into /// the bounds of the QR code, and shifting it to be a little /// forward, in front of the code! public void Update() { foreach (QRData d in poses.Values) { Lines.AddAxis(d.pose, d.size); Text.Add( d.text, Matrix.TRS( d.pose.position + d.pose.Forward * d.size * 0.1f, Quat.FromAngles(0, 0, 180) * d.pose.orientation), Vec2.One * d.size, TextFit.Squeeze, TextAlign.XLeft | TextAlign.YTop); } }
/// Now all we need to do is show the QR codes! In this case, /// we're just displaying an axis widget, and the contents of /// the QR code as text. /// /// With the text, all we're doing is squeezing the text into /// the bounds of the QR code, and shifting it to be a little /// forward, in front of the code! public void Update() { foreach (QRData d in poses.Values) { Lines.AddAxis(d.pose, d.size); Text.Add( d.text, d.pose.ToMatrix(), Vec2.One * d.size, TextFit.Squeeze, TextAlign.XLeft | TextAlign.YTop, TextAlign.Center, d.size, d.size); } }
public void Update() { Vec3 wandTip = wandModel.Bounds.center + wandModel.Bounds.dimensions.y * 0.5f * Vec3.Up; UI.HandleBegin("wand", ref wandPose, wandModel.Bounds); wandModel.Draw(Matrix.Identity); wandTip = Hierarchy.ToWorld(wandTip); UI.HandleEnd(); Lines.AddAxis(new Pose(wandTip, Quat.Identity), 0.05f); UI.WindowBegin("Anchors", ref pose); if (anchorStore != null && UI.Button("Create New")) { CreateAnchor(wandTip); } // List options for the selected anchor if (selected != null) { UI.HSeparator(); UI.Label(selected); if (UI.Button("Delete")) { DestroyAnchor(selected); } } UI.WindowEnd(); // Show anchors, and do selection logic on them foreach (var p in anchorPoses) { Lines.AddAxis(p.Value, 0.1f); if (p.Value.position.InRadius(wandTip, 0.05f)) { selected = p.Key; } } // Outline the selected anchor if (selected != null) { Pose p = anchorPoses[selected]; Mesh.Cube.Draw(Material.UIBox, p.ToMatrix(0.1f)); } }
/// :End: public static void DrawAxes() { for (int i = 0; i < (int)Handed.Max; i++) { Hand hand = Input.Hand((Handed)i); if (!hand.IsTracked) { continue; } for (int finger = 0; finger < 5; finger++) { for (int joint = 0; joint < 5; joint++) { Lines.AddAxis(hand[finger, joint].Pose); } } Lines.AddAxis(hand.palm); } }
/// :End: public static void DrawAxes() { for (int i = 0; i < (int)Handed.Max; i++) { Hand hand = Input.Hand((Handed)i); if (!hand.IsTracked) { continue; } for (int f = 0; f < 5; f++) { for (int j = 0; j < 5; j++) { Lines.AddAxis(hand[f, j].Pose); } } Lines.AddAxis(hand.palm); } }
static void HandInBounds() { /// :CodeSample: Input.Hand Bounds.Contains Lines.AddAxis Hand Handed FingerId JointId /// Here's a small example of checking to see if a finger joint is inside /// a box, and drawing an axis gizmo when it is! // A volume for checking inside of! 10cm on each side, at the origin Bounds testArea = new Bounds(Vec3.One * 0.1f); // This is a decent way to show we're working with both hands for (int h = 0; h < (int)Handed.Max; h++) { // Get the pose for the index fingertip Hand hand = Input.Hand((Handed)h); Pose fingertip = hand[FingerId.Index, JointId.Tip].Pose; // Draw the fingertip pose axis if it's inside the volume if (testArea.Contains(fingertip.position)) { Lines.AddAxis(fingertip); } } /// :End: }
public void Update() { Tests.Screenshot("GuideUserInterface.jpg", 600, 400, new Vec3(-0.363f, 0.010f, 0.135f), new Vec3(-0.743f, -0.414f, -0.687f)); Tests.Screenshot("GuideUserInterfaceCustom.jpg", 400, 600, new Vec3(0.225f, 0.0f, .175f), new Vec3(.4f, 0.0f, 0)); /// :CodeDoc: Guides User Interface /// Then we'll move over to the application step where we'll do the /// rest of the UI code! /// /// We'll start with a window titled "Window" that's 20cm wide, and /// auto-resizes on the y-axis. The U class is pretty helpful here, /// as it allows us to reason more visually about the units we're /// using! StereoKit uses meters as its base unit, which look a /// little awkward as raw floats, especially in the millimeter range. /// /// We'll also use a toggle to turn the window's header on and off! /// The value from that toggle is passed in here via the showHeader /// field. /// UI.WindowBegin("Window", ref windowPose, new Vec2(20, 0) * U.cm, showHeader?UIWin.Normal:UIWin.Body); /// /// When you begin a window, all visual elements are now relative to /// that window! UI takes advantage of the Hierarchy class and pushes /// the window's pose onto the Hierarchy stack. Ending the window /// will pop the pose off the hierarchy stack, and return things to /// normal! /// /// Here's that toggle button! You'll also notice our use of 'ref' /// values in a lot of the UI code. UI functions typically follow the /// pattern of returning true/false to indicate they've been /// interacted with during the frame, so you can nicely wrap them in /// 'if' statements to react to change! /// /// Then with the 'ref' parameter, we let you pass in the current /// state of the UI element. The UI element will update that value /// for you based on user interaction, but you can also change it /// yourself whenever you want to! /// UI.Toggle("Show Header", ref showHeader); /// /// Here's an example slider! We start off with a label element, and /// tell the UI to keep the next item on the same line. The slider /// clamps to the range [0,1], and will step at intervals of 0.2. If /// you want it to slide continuously, you can just set the `step` /// value to 0! /// UI.Label("Slide"); UI.SameLine(); UI.HSlider("slider", ref slider, 0, 1, 0.2f, 72 * U.mm); /// /// Here's how you use a simple button! Just check it with an 'if'. /// Any UI method will return true on the frame when their value or /// state has changed. /// if (UI.ButtonRound("Exit", powerSprite)) { SK.Quit(); } /// /// And for every begin, there must also be an end! StereoKit will /// log errors when this occurs, so keep your eyes peeled for that! /// UI.WindowEnd(); /// /// ## Custom Windows /// /// ![Simple UI]({{site.url}}/img/screenshots/GuideUserInterfaceCustom.jpg) /// /// Mixed Reality also provides us with the opportunity to turn /// objects into interfaces! Instead of using the old 'window' /// paradigm, we can create 3D models and apply UI elements to their /// surface! StereoKit uses 'handles' to accomplish this, a grabbable /// area that behaves much like a window, but with a few more options /// for customizing layout and size. /// /// We'll load up a clipboard, so we can attach an interface to that! /// /// ```csharp /// Model clipboard = Model.FromFile("Clipboard.glb"); /// ``` /// /// And, similar to the window previously, here's how you would turn /// it into a grabbable interface! This behaves the same, except /// we're defining where the grabbable region is specifically, and /// then drawing our own model instead of a plain bar. You'll also /// notice we're drawing using an identity matrix. This takes /// advantage of how HandleBegin pushes the handle's pose onto the /// Hierarchy transform stack! /// UI.HandleBegin("Clip", ref clipboardPose, clipboard.Bounds); Renderer.Add(clipboard, Matrix.Identity); /// /// Once we've done that, we also need to define the layout area of /// the model, where UI elements will go. This is different for each /// model, so you'll need to plan this around the size of your /// object! /// UI.LayoutArea(new Vec3(12, 15, 0) * U.cm, new Vec2(24, 30) * U.cm); /// /// Then after that? We can just add UI elements like normal! /// UI.Image(logoSprite, new Vec2(22, 0) * U.cm); UI.Toggle("Toggle", ref clipToggle); UI.HSlider("Slide", ref clipSlider, 0, 1, 0, 22 * U.cm); /// /// And while we're at it, here's a quick example of doing a radio /// button group! Not much 'radio' actually happening, but it's still /// pretty simple. Pair it with an enum, or an integer, and have fun! /// if (UI.Radio("Radio1", clipOption == 1)) { clipOption = 1; } UI.SameLine(); if (UI.Radio("Radio2", clipOption == 2)) { clipOption = 2; } UI.SameLine(); if (UI.Radio("Radio3", clipOption == 3)) { clipOption = 3; } /// /// As with windows, Handles need an End call. /// UI.HandleEnd(); /// :End: // Just moving this UI out of the way so it doesn't show in // screenshots or anything. UI.PushSurface(new Pose(0, 1000, 0, Quat.Identity)); /// :CodeDoc: Guides User Interface /// /// ## An Important Note About IDs /// /// StereoKit does store a small amount of information about the UI's /// state behind the scenes, like which elements are active and for /// how long. This internal data is attached to the UI elements via /// a combination of their own ids, and the parent Window/Handle's /// id! /// /// This means you should be careful to NOT re-use ids within a /// Window/Handle, otherwise you may find ghost interactions with /// elements that share the same ids. If you need to have elements /// with the same id, or if perhaps you don't know in advance that /// all your elements will certainly be unique, UI.PushId and /// UI.PopId can be used to mitigate the issue by using the same /// hierarchy id mixing that the Windows use to prevent collisions /// with the same ids in other Windows/Handles. /// /// Here's the same set of radio options, but all of them have the /// same name/id! /// UI.PushId(1); if (UI.Radio("Radio", clipOption == 1)) { clipOption = 1; } UI.PopId(); UI.SameLine(); UI.PushId(2); if (UI.Radio("Radio", clipOption == 2)) { clipOption = 2; } UI.PopId(); UI.SameLine(); UI.PushId(3); if (UI.Radio("Radio", clipOption == 3)) { clipOption = 3; } UI.PopId(); /// :End: UI.PopSurface(); /// :CodeDoc: Guides User Interface /// ## What's Next? /// /// And there you go! That's how UI works in StereoKit, pretty /// simple, huh? For further reference, and more UI methods, check /// out the [UI class documentation]({{site.url}}/Pages/Reference/UI.html). /// /// If you'd like to see the complete code for this sample, /// [check it out on Github](https://github.com/maluoi/StereoKit/blob/master/Examples/StereoKitTest/Demos/DemoUI.cs)! /// :End: /// :CodeSample: UI.VolumeAt /// This code will draw an axis at the index finger's location when /// the user pinches while inside a VolumeAt. /// /// ![UI.InteractVolume]({{site.screen_url}}/InteractVolume.jpg) /// // Draw a transparent volume so the user can see this space Vec3 volumeAt = new Vec3(0, 0.2f, -0.4f); float volumeSize = 0.2f; Default.MeshCube.Draw(Default.MaterialUIBox, Matrix.TS(volumeAt, volumeSize)); BtnState volumeState = UI.VolumeAt("Volume", new Bounds(volumeAt, Vec3.One * volumeSize), UIConfirm.Pinch, out Handed hand); if (volumeState != BtnState.Inactive) { // If it just changed interaction state, make it jump in size float scale = volumeState.IsChanged() ? 0.1f : 0.05f; Lines.AddAxis(Input.Hand(hand)[FingerId.Index, JointId.Tip].Pose, scale); } /// :End: Tests.Screenshot("InteractVolume.jpg", 1, 600, 600, 90, new Vec3(-0.102f, 0.306f, -0.240f), new Vec3(0.410f, -0.248f, -0.897f)); }