From 519d3b7813513b5d1c516b865932ea1f3dd362a7 Mon Sep 17 00:00:00 2001 From: spatialfree Date: Tue, 25 Oct 2022 03:31:27 -0400 Subject: [PATCH] design vars foothold --- app/Mono.cs | 66 ++++-- app/PullRequest.cs | 38 ++- app/Rig/Rig.cs | 21 +- app/Space.cs | 2 +- app/_Init.cs | 6 +- app/dofs/stretch-cursor/wave/WaveCursor.cs | 21 +- app/dofs/trackballer/Trackballer.cs | 254 +++++++++++++-------- oriels.csproj | 1 - res/thumb_pad.blend | Bin 6079621 -> 6079621 bytes 9 files changed, 264 insertions(+), 145 deletions(-) diff --git a/app/Mono.cs b/app/Mono.cs index 0986046..763e7f3 100644 --- a/app/Mono.cs +++ b/app/Mono.cs @@ -67,7 +67,7 @@ public class Mono { matHolo = Material.Default.Copy(); matHolo.Transparency = Transparency.Add; matHolo.DepthWrite = false; - matHolo.DepthTest = DepthTest.Always; + // matHolo.DepthTest = DepthTest.Always; matHolo.FaceCull = Cull.None; // matHolo.SetTexture("diffuse", Tex.DevTex); matHolo.Wireframe = true; @@ -93,6 +93,8 @@ public class Mono { compositor.Frame(); + // Input.Subscribe(InputSource.Hand, BtnState.Any, Action); + // ------------------------------------------------- // // dof.Frame(); @@ -109,6 +111,8 @@ public class Mono { lwc.Demo(ltb.ori); rwc.Demo(rtb.ori); + rtb.Demo(); + // rBlock.Step(); lBlock.Step(); // cubicCon.Step(); @@ -123,9 +127,15 @@ public class Mono { ShowWindowButton(); } - Pose windowPoseButton = new Pose(0, 0, 0.75f, Quat.Identity); + Pose windowPoseButton = new Pose(-0.5f, 1.3f, 0, Quat.FromAngles(0, -90f, 0)); + TextStyle style = Text.MakeStyle(Font.FromFile("add/fonts/DM-Mono.ttf"), 1f * U.cm, Color.White); + Vec2 fieldSize = new Vec2(6f * U.cm, 3f * U.cm); void ShowWindowButton() { - UI.WindowBegin("Window Button", ref windowPoseButton); + UI.WindowBegin("design vars", ref windowPoseButton); + UI.SetThemeColor(UIColor.Background, new Color(0f, 0f, 0f)); + UI.SetThemeColor(UIColor.Primary, new Color(0.5f, 0.5f, 0.5f)); + UI.PushTextStyle(style); + // if (UI.Button("Draw Oriel Axis")) { oriel.drawAxis = !oriel.drawAxis; } @@ -139,29 +149,49 @@ public class Mono { // UI.Label("pos.y"); // UI.HSlider("pos.y", ref playerY, -1f, 1f, 0.1f); - // UI.Label("trail.length"); - // UI.HSlider("trail.length", ref trailLen, 0.1f, 1f, 0.1f); - - // UI.Label("trail.scale"); - // UI.HSlider("trail.str", ref trailScl, 0.1f, 2f, 0.1f); - - // UI.Label("str"); - // UI.HSlider("str", ref stretchStr, 0.1f, 1f, 0.1f); - + UI.Label("wavecursor."); + UI.Input("wavecursor.reach", ref wcReach, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("reach"); + UI.Input("wavecursor.length", ref wcLength, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("length"); + UI.Input("wavecursor.scale", ref wcScale, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("scale"); + UI.Label("trackballer."); + UI.Input("trackballer.compliance", ref tbCompliance, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("compliance"); + UI.Input("trackballer.x", ref tbX, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("x"); + UI.Input("trackballer.y", ref tbY, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("y"); + UI.Input("trackballer.z", ref tbZ, fieldSize, TextContext.Number); + UI.SameLine(); UI.Label("z"); // flipIndex // flipGrip - - UI.WindowEnd(); } - public float trailLen = 0.666f; - public float trailScl = 0.333f; - public float stretchStr = 0.5f; - public float playerY = 0; + public string wcReach = "1.0"; + public string wcLength = "0.666"; + public string wcScale = "0.333"; + + public string tbCompliance = "0.0"; + public string tbX = "1.0"; + public string tbY = "2.0"; + public string tbZ = "-4.0"; + + + // public float playerY = 0; + +} + +// convert into a class +class Design { + public string txt; + public int integer; + public float floating; } diff --git a/app/PullRequest.cs b/app/PullRequest.cs index 0518a74..22c1840 100644 --- a/app/PullRequest.cs +++ b/app/PullRequest.cs @@ -67,6 +67,11 @@ public static class PullRequest { return (to * delta * to.Inverse).Normalized; } + // ? + public static Vec3 Relative(Quat to, Vec3 delta) { + return to * delta * to.Inverse; + } + // public static void LookDirection(this ref Quat q, Vec3 dir) { // Vec3 up = Vec3.Up; @@ -284,10 +289,27 @@ public static class PullRequest { return MathF.Max(min, MathF.Min(max, v)); } + public static float ToFloat( + ref string s, + float min = float.NegativeInfinity, + float max = float.PositiveInfinity + ) { + try { + float value = Clamp(float.Parse(s), min, max); + // if clamped, update string + if (value != float.Parse(s)) { + s = value.ToString(); + } + return value; + } catch { + return 0; + } + } + public class PID { public float p, i; - float integral = 0f; - float value = 0f; + public float value; + float integral; // float scalar = 1f; public PID(float p = 1, float i = 0.1f) { @@ -303,6 +325,18 @@ public static class PullRequest { } } + public class Vec3PID { + public Vec3 value, integral; + // float scalar = 1f; + + public Vec3 Update(Vec3 target, float p = 1, float i = 0.1f) { + Vec3 error = value - target; + integral += error; + Vec3 delta = ((p * error) + (i * integral)); + return value -= delta * Time.Elapsedf; + } + } + public class Lerper { public float t = 0; public float spring = 1; diff --git a/app/Rig/Rig.cs b/app/Rig/Rig.cs index ba360ef..fb07a86 100644 --- a/app/Rig/Rig.cs +++ b/app/Rig/Rig.cs @@ -5,13 +5,7 @@ public class Rig { public Vec3 pos = new Vec3(0, 0, 0); public Quat ori = Quat.Identity; - public Rig() { - if (World.HasBounds) { - // pos.XZ = World.BoundsPose.position.XZ; - // pos.y = World.BoundsPose.position.y; - // ori = World.BoundsPose.orientation; - } - } + public Rig() {} // public Vec3 center; // public void Recenter() { @@ -44,12 +38,19 @@ public class Rig { public Vec3 LocalPos(Vec3 p) { - return ori.Inverse * (p - (pos + Vec3.Up * Mono.inst.playerY)); + return ori.Inverse * (p - (pos)); } + bool gotBounds = false; + public void Step() { + Matrix bounds = Matrix.T(0, 1.3f, 0); + if (!gotBounds && World.HasBounds) { + gotBounds = true; + bounds = World.BoundsPose.ToMatrix(); + // Renderer.CameraRoot = World.BoundsPose.ToMatrix(); + } - public void Step() { - Renderer.CameraRoot = Matrix.TR((pos + Vec3.Up * Mono.inst.playerY), ori); + Renderer.CameraRoot = Matrix.TR(pos, ori) * bounds; // Controllers rCon.Step(true); diff --git a/app/Space.cs b/app/Space.cs index 31ba70a..b88dae1 100644 --- a/app/Space.cs +++ b/app/Space.cs @@ -76,7 +76,7 @@ public class Space { // World.BoundsPose.position.y - shed.Draw(Matrix.TRS(new Vec3(0, -1.6f, 0), Quat.Identity, Vec3.One)); + shed.Draw(Matrix.Identity); // draw a grid of cube pillars spaced out evenly along the XZ plane diff --git a/app/_Init.cs b/app/_Init.cs index 3662d3b..4b80087 100644 --- a/app/_Init.cs +++ b/app/_Init.cs @@ -20,7 +20,7 @@ Renderer.ClearColor = new Color(0f, 0f, 0f); Oriels.Mono mono = Oriels.Mono.inst; mono.Init(); -while (SK.Step(() => { +SK.Run(() => { mono.Frame(); -})); -SK.Shutdown(); \ No newline at end of file +}); +// SK.Shutdown(); \ No newline at end of file diff --git a/app/dofs/stretch-cursor/wave/WaveCursor.cs b/app/dofs/stretch-cursor/wave/WaveCursor.cs index 1729dfd..3c56f59 100644 --- a/app/dofs/stretch-cursor/wave/WaveCursor.cs +++ b/app/dofs/stretch-cursor/wave/WaveCursor.cs @@ -31,8 +31,8 @@ class WaveCursor : dof { hand.Get(FingerId.Index, JointId.Tip).position, hand.Get(FingerId.Index, JointId.KnuckleMajor).position ); - - cursor.raw = hand.Get(FingerId.Index, JointId.Tip).position + dir * stretch * strength * Mono.inst.stretchStr; + + cursor.raw = hand.Get(FingerId.Index, JointId.Tip).position + dir * stretch * strength; cursor.pos.x = (float)xF.Filter(cursor.raw.x, (double)Time.Elapsedf); cursor.pos.y = (float)yF.Filter(cursor.raw.y, (double)Time.Elapsedf); cursor.pos.z = (float)zF.Filter(cursor.raw.z, (double)Time.Elapsedf); @@ -45,7 +45,9 @@ class WaveCursor : dof { } public float deadzone = 0.3f; - public float strength = 3f; + public float strength { + get { return PullRequest.ToFloat(ref Mono.inst.wcReach, 0); } // 3f + } public Handed handed = Handed.Left; @@ -85,23 +87,24 @@ class WaveCursor : dof { } void Trail(Vec3[] points, Vec3 nextPos) { - while (Vec3.Distance(points[0], nextPos) > 0.03f * Mono.inst.trailScl) { + float scale = PullRequest.ToFloat(ref Mono.inst.wcScale, 0.001f); + while (Vec3.Distance(points[0], nextPos) > 0.03f * scale) { for (int i = points.Length - 1; i > 0; i--) { points[i] = points[i - 1]; } - points[0] += Vec3.Direction(nextPos, points[0]) * 0.02f * Mono.inst.trailScl; + points[0] += Vec3.Direction(nextPos, points[0]) * 0.02f * scale; } // points[0] = nextPos; - int len = (int)(points.Length * Mono.inst.trailLen); + int len = (int)(points.Length * PullRequest.ToFloat(ref Mono.inst.wcLength, 0f, 1f)); for (int i = 0; i < len; i++) { // if (i > 0) { // Vec3 dir = Vec3.Forward; // if (points[i].v != points[i - 1].v) { // dir = PullRequest.Direction(points[i], points[i - 1]); // } - // // points[i] = points[i - 1] + dir * 0.02f * Mono.inst.trailScl; + // // points[i] = points[i - 1] + dir * 0.02f * scale; // } Vec3 from = i > 0 ? points[i - 1] : nextPos; @@ -109,9 +112,9 @@ class WaveCursor : dof { Mesh.Cube.Draw( Mono.inst.matHolo, Matrix.TRS( - points[i] + ori * new Vec3(0, 0, 0.01f) * Mono.inst.trailScl, + points[i] + ori * new Vec3(0, 0, 0.01f) * scale, ori, - new Vec3(0.01f, 0.01f, 0.02f) * Mono.inst.trailScl + new Vec3(0.01f, 0.01f, 0.02f) * scale ), Color.HSV(i / (float)len, 1, 1) ); diff --git a/app/dofs/trackballer/Trackballer.cs b/app/dofs/trackballer/Trackballer.cs index fc6133a..b98c619 100644 --- a/app/dofs/trackballer/Trackballer.cs +++ b/app/dofs/trackballer/Trackballer.cs @@ -4,10 +4,16 @@ class Trackballer : dof { // data public Btn btnIn, btnOut; + bool onTheBall; public Quat ori = Quat.Identity; + Quat momentum = Quat.Identity; - Quat delta = Quat.Identity; - Matrix oldMeshMatrix = Matrix.Identity; + Quat delta = Quat.Identity; + Matrix pad = Matrix.Identity; + Matrix oldPad = Matrix.Identity; + int lastClosestIndex; + + PullRequest.Vec3PID compliance = new PullRequest.Vec3PID(); PullRequest.OneEuroFilter xF = new PullRequest.OneEuroFilter(0.0001f, 0.1f); PullRequest.OneEuroFilter yF = new PullRequest.OneEuroFilter(0.0001f, 0.1f); @@ -23,115 +29,161 @@ class Trackballer : dof { public void Frame() { Hand hand = Input.Hand(handed); if (hand.tracked.IsActive() && !hand.tracked.IsJustActive()) { - Vec3 anchor = hand.Get(FingerId.Index, JointId.KnuckleMajor).position; - anchor = anchor + hand.palm.orientation * new Vec3(handed == Handed.Left ? -0.006f : 0.006f, 0.01f, -0.04f); - Matrix mAnchor = Matrix.TR(anchor, hand.palm.orientation); - Matrix mAnchorInv = mAnchor.Inverse; - - Vec3 thumbTip = hand.Get(FingerId.Thumb, JointId.Tip).position; - // Vec3 tipDelta = mAnchorInv.Transform(thumbTip) - mAnchorInv.Transform(oldTip); - // oldTip = thumbTip; - Vec3 thumbKnuckle = hand.Get(FingerId.Thumb, JointId.KnuckleMinor).position; - - Quat thumbRot = hand.Get(FingerId.Thumb, JointId.Tip).orientation; - Matrix mMesh = Matrix.TRS( - thumbTip, - thumbRot, - new Vec3(handed == Handed.Left ? -1f : 1f, 1f, 1f) * 0.1666f - ); - mesh.Draw(Mono.inst.matHolo, mMesh, new Color(0, 1, 1)); - - // closest to anchor - float closest = 100000f; - int closestIndex = -1; - Vertex[] verts = mesh.GetVerts(); - for (int i = 0; i < verts.Length; i++) { - Vec3 v = mMesh.Transform(verts[i].pos); - float d = (v - anchor).LengthSq; - if (d < closest) { - closest = d; - closestIndex = i; - } - } - - Vec3 localPad = mAnchorInv.Transform(mMesh.Transform(verts[closestIndex].pos)); - Vec3 oldPad = mAnchorInv.Transform(oldMeshMatrix.Transform(verts[closestIndex].pos)); - - oldMeshMatrix = mMesh; - - // - // Vec3 pad = anchor.SnapToLine( - // thumbKnuckle, thumbTip, - // true, - // out float t, 0f, 1f - // ); - // // t = 1 - t; - // // scale to 0.666f - 1f - // // t = (t - 0.666f) / 0.334f; - // t = t * t; - // t = 1 - t; - // pad += hand.Get(FingerId.Thumb, JointId.Tip).orientation * -Vec3.Up * 0.00666f * t; - - - // Vec3 localPad = mAnchorInv.Transform(pad); - // Vec3 localPad = mAnchorInv.Transform(thumbTip); - - // ? - // localPad.x = (float)xF.Filter(localPad.x, (double)Time.Elapsedf); - // localPad.y = (float)yF.Filter(localPad.y, (double)Time.Elapsedf); - // localPad.z = (float)zF.Filter(localPad.z, (double)Time.Elapsedf); - - - - // Lines.Add(thumbTip, thumbKnuckle, Color.White, 0.002f); - Mesh.Sphere.Draw(Mono.inst.matHolo, Matrix.TRS(mAnchor.Transform(localPad), hand.palm.orientation, 0.002f), new Color(0, 1, 1)); - - - // if (btnIn.held) { - // btnIn.Step(localPad.Length < layer[1]); - // } else { - // btnIn.Step(localPad.Length < layer[0]); - // } - float inT = btnIn.held ? 1 : 0.333f; - - if (btnOut.held) { - btnOut.Step(localPad.Length > layer[1]); - } else { - btnOut.Step(localPad.Length > layer[2]); - } - float outT = btnOut.held ? 1 : 0.333f; - - if (btnIn.held) { - delta = momentum = Quat.Identity; - } else { - if (localPad.Length < layer[1]) { - delta = PullRequest.Relative( - hand.palm.orientation, - Quat.Delta( - oldPad.Normalized, - localPad.Normalized - ) - ).Normalized; - - momentum = Quat.Slerp(momentum, delta, Time.Elapsedf * 10f); - } - } - - // Draw - Mesh.Sphere.Draw(Mono.inst.matHolo, Matrix.TRS(anchor, ori, layer[1] * 2), new Color(inT, 0, 0)); - // Mesh.Cube.Draw(Mono.inst.matHolo, Matrix.TRS(anchor, ori, 0.04f), new Color(0, outT * 0.2f, 0)); + UpdateMomentum(hand); } - Quat newOri = momentum * ori; + Quat newOri = (momentum * ori).Normalized; if (new Vec3(newOri.x, newOri.y, newOri.z).LengthSq > 0) { ori = newOri; } } + public void UpdateMomentum(Hand hand) { + // Thumb pad + HandJoint thumbJoint = hand.Get(FingerId.Thumb, JointId.Tip); + oldPad = pad; + pad = Matrix.TRS( + thumbJoint.position, + thumbJoint.orientation, + new Vec3(handed == Handed.Left ? -1f : 1f, 1f, 1f) * 0.1666f + ); + mesh.Draw(Mono.inst.matHolo, pad, new Color(0, 1, 1)); + + // Ball anchor + HandJoint ballJoint = hand.Get(FingerId.Index, JointId.KnuckleMajor); + Vec3 anchorPos = ballJoint.position + hand.palm.orientation * new Vec3( + PullRequest.ToFloat(ref Mono.inst.tbX, -10f, 10f) * U.cm * (handed == Handed.Left ? -1 : 1), + PullRequest.ToFloat(ref Mono.inst.tbY, -10f, 10f) * U.cm, + PullRequest.ToFloat(ref Mono.inst.tbZ, -10f, 10f) * U.cm + ); + anchorPos += compliance.Update( + Vec3.Zero, + onTheBall ? 1f : 10f, + onTheBall ? 0.1f : 1f // 10x less integral when on the ball? + ); + // compliance; + // compliance = Vec3.Lerp(compliance, Vec3.Zero, Time.Elapsedf * 10f); + Matrix anchor = Matrix.TR(anchorPos, hand.palm.orientation); + Matrix anchorInv = anchor.Inverse; + + // Traction delta mesh matrix + Vertex[] verts = mesh.GetVerts(); + float oldClosest = ( + pad.Transform(verts[lastClosestIndex].pos) - anchorPos + ).LengthSq; + float closest = 100000f; + int closestIndex = lastClosestIndex; + for (int i = 0; i < verts.Length; i++) { + Vec3 v = pad.Transform(verts[i].pos); + float d = (v - anchorPos).LengthSq; + if (d < closest && d < oldClosest - 0.00002f) { + closest = d; + closestIndex = i; + } + } + lastClosestIndex = closestIndex; + + Vec3 point = anchorInv.Transform( + pad.Transform(verts[closestIndex].pos) + ); + Vec3 oldPoint = anchorInv.Transform( + oldPad.Transform(verts[closestIndex].pos) + ); + + // ? + // localPad.x = (float)xF.Filter(localPad.x, (double)Time.Elapsedf); + // localPad.y = (float)yF.Filter(localPad.y, (double)Time.Elapsedf); + // localPad.z = (float)zF.Filter(localPad.z, (double)Time.Elapsedf); + + // Lines.Add(thumbTip, thumbKnuckle, Color.White, 0.002f); + Mesh.Sphere.Draw( + Mono.inst.matHolo, + Matrix.TRS(anchor.Transform(point), hand.palm.orientation, 0.002f), + new Color(0, 1, 1) + ); + + float dist = point.Length; + // if (btnIn.held) { btnIn.Step(dist < layer[1]); } + // else { btnIn.Step(dist < layer[0]); } + float inT = btnIn.held ? 1 : 0.333f; + + if (btnOut.held) { btnOut.Step(dist > layer[1]); } + else { btnOut.Step(dist > layer[2]); } + float outT = btnOut.held ? 1 : 0.333f; + + if (btnIn.held) { + delta = momentum = Quat.Identity; + } else { + onTheBall = dist < layer[1]; + if (onTheBall) { + delta = Quat.Delta( + oldPoint.Normalized, + point.Normalized + ).Relative(hand.palm.orientation); + + momentum = Quat.Slerp(momentum, delta, Time.Elapsedf * 10f); + + Vec3 contact = point.Normalized * layer[1]; + Vec3 offset = point - contact; + + // no z axis + // offset.z = 0; + + offset = hand.palm.orientation * offset; + compliance.value += offset * PullRequest.ToFloat(ref Mono.inst.tbCompliance, 0f, 1f); + compliance.integral = Vec3.Zero; + } + } + + // Draw + Mesh.Sphere.Draw( + Mono.inst.matHolo, + Matrix.TRS(anchorPos, ori, layer[1] * 2), + new Color(inT, 0, 0) + ); + } + // design - public Handed handed = Handed.Left; + public Handed handed = Handed.Left; public float[] layer = new float[] { 0.00333f, 0.02f, 0.0666f }; + + Vec3 cursorPos = new Vec3(0f, 0f, 0f); + public void Demo() { + Matrix panel = Matrix.TR( + new Vec3( + 1.47f, + 1.145f, // - World.BoundsPose.position.y, + 1.08f), + Quat.FromAngles(-3.2f, 90f, 0) + ); + + float width = 52 * U.cm; + float height = 29f * U.cm; + Mesh.Quad.Draw( + Mono.inst.matHolo, + Matrix.S(new Vec3(width, height, 1)) * panel, + new Color(1, 1, 1) + ); + + + cursorPos.x = PullRequest.Clamp( + cursorPos.x + (delta * Vec3.Right).z * 0.1f, + width / -2f, + width / 2f + ); + cursorPos.y = PullRequest.Clamp( + cursorPos.y + (delta * Vec3.Right).y * -0.1f, + height / -2f, + height / 2f + ); + + Mesh.Quad.Draw( + Material.Unlit, + Matrix.TS(cursorPos, 1 * U.cm) * panel, + new Color(1, 1, 1) + ); + } } diff --git a/oriels.csproj b/oriels.csproj index 8c840c6..653b467 100644 --- a/oriels.csproj +++ b/oriels.csproj @@ -13,7 +13,6 @@ PreserveNewest - diff --git a/res/thumb_pad.blend b/res/thumb_pad.blend index 6c2af6545eab51692b4661ac473974b7857ca642..f13cd68c6630418b60468769f57bd384829b1a32 100644 GIT binary patch delta 5336 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