oriels/app/dofs/trackballer/Trackballer.cs
2022-11-04 16:12:35 -04:00

219 lines
No EOL
5.9 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
distinct interactions to account for (relative to palm orientation)
y swipe
z swipe
x spin
how reliable is the provided palm orientation?
more boolean visual and audio feeback
*/