oriels/app/dofs/trackballer/Trackballer.cs
2022-11-12 20:20:29 -05:00

235 lines
No EOL
6.3 KiB
C#

namespace Oriels;
class Trackballer : dof {
public bool Active { get; set; }
// input
public Handed handed = Handed.Left;
// data
public Btn btnPull = new Btn();
public Btn btnPush = new Btn();
bool onTheBall;
public Quat ori = Quat.Identity;
Quat momentum = Quat.Identity;
Quat delta = Quat.Identity;
Matrix pad = Matrix.Identity;
Matrix oldPad = Matrix.Identity;
int lastClosestIndex;
PullRequest.Vec3PID compliance = new PullRequest.Vec3PID();
Model model = Model.FromFile("thumb_pad.glb");
Mesh mesh;
public void Init() {
mesh = model.GetMesh("Pad");
}
public void Frame() {
Hand hand = Input.Hand(handed);
if (hand.tracked.IsActive() && !hand.tracked.IsJustActive()) {
UpdateMomentum(hand);
}
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.matHoloframe, pad, new Color(0, 1, 1));
// Ball anchor
HandJoint ballJoint = hand.Get(FingerId.Index, JointId.KnuckleMajor);
Vec3 anchorOrigin = ballJoint.position + hand.palm.orientation * new Vec3(
aX.value * (handed == Handed.Left ? -1 : 1),
aY.value,
aZ.value
);
Vec3 anchorPos = anchorOrigin + compliance.Update(
Vec3.Zero,
onTheBall ? 1f : 10f,
onTheBall ? 0.5f : 5f // 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)
);
// Pull
float pull = point.Length / pullClick.value;
btnPull.Frame(pull > 1f, pull > 0.333f); // magic sticky var
float pullScalar = btnPull.held ? MathF.Max((pull - 0.333f) / 0.666f, 0) : MathF.Max(1 - pull, 0);
Mesh.Sphere.Draw(Mono.inst.matHoloframe,
Matrix.TRS(anchorPos, thumbJoint.orientation, pullScalar * radius.value),
new Color(0, 1, 1) * (btnPull.held ? 1f : 0.0666f)
);
Lines.Add(
anchor.Transform(point), anchorPos,
new Color(0, 1, 1), 1f * U.mm
);
Mesh.Sphere.Draw(Mono.inst.matHoloframe,
Matrix.TRS(anchor.Transform(point), thumbJoint.orientation, 2f * U.mm),
new Color(0, 1, 1)
);
// Push
float push = compliance.value.Length / pushClick.value;
btnPush.Frame(push > 1f, push > 0.333f); // magic sticky var
float pushScalar = btnPush.held ? MathF.Max((MathF.Min(push, 1f) - 0.333f) / 0.666f, 0) : MathF.Max(1 - push, 0);
Mesh.Sphere.Draw(Mono.inst.matHoloframe,
Matrix.TRS(anchorPos, ori, (radius.value * 2) * pushScalar),
new Color(1, 0, 0) * (btnPush.held ? 1f : 0.2f)
);
onTheBall = point.Length < radius.value;
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 * radius.value;
Vec3 offset = point - contact;
// no z axis
// offset.z = 0;
offset = hand.palm.orientation * offset;
compliance.value += offset * compliant.value;
compliance.integral = Vec3.Zero;
} else {
PullRequest.ToAxisAngle(momentum, out Vec3 axis, out float angle);
if (angle < stop.value) {
momentum = Quat.Slerp(momentum, Quat.Identity, Time.Elapsedf * 10f);
}
}
// Draw ball result
Mesh.Sphere.Draw(
Mono.inst.matHoloframe,
Matrix.TRS(anchorPos, ori, radius.value * 2),
new Color(0.8f, 0, 0)
);
}
// design
public Design radius = new Design { str="2", term=">0cm", unit=U.cm, min=0.5f };
public Design pullClick = new Design { str="6.66", term=">0cm", unit=U.cm, min=0.1f };
public Design pushClick = new Design { str="1.5", term=">0cm", unit=U.cm, min=0.1f };
public Design aX = new Design { str=" 1.0", term="-0+cm", unit=U.cm, min=-10f, max=10f };
public Design aY = new Design { str=" 2.0", term="-0+cm", unit=U.cm, min=-10f, max=10f };
public Design aZ = new Design { str="-4.0", term="-0+cm", unit=U.cm, min=-10f, max=10f };
public Design compliant = new Design { str="0.2", term="0+1t", min=0, max=1 };
public Design stop = new Design { str="0.05", term="0+", min=0 };
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.matHoloframe,
Matrix.S(new Vec3(width, height, 1)) * panel,
new Color(1, 1, 1)
);
cursorPos.x = PullRequest.Clamp(
cursorPos.x + (momentum * Vec3.Right).z * 0.1f,
width / -2f,
width / 2f
);
cursorPos.y = PullRequest.Clamp(
cursorPos.y + (momentum * 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)
);
}
}
/*
COMMENTS
press in is not reliable
full thumb mesh and contact point lerp
pullbtn mouse click scale anim
thumb inside instead
so the natural thumb position doesn't affect the trackballer
sens for cursor
be able to stop it easily without jittering
use another fingers flexion to be able to brake the trackballer
finger gun interaction (on the other hand) to move the trackballer
distinct interactions to account for (relative to palm orientation)
y swipe (move x)
z swipe (move y)
x spin (scroll)
how reliable is the provided palm orientation?
more boolean visual and audio feeback
*/