mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-25 00:06:18 +02:00
- reskate.input.hide hides the right stick and the stick clicks, and reskate.input.mouse reads the mouse's movement and wheel. - reskate.skater.cruise: cruise control up to a speed, optionally easing down to it. - reskate.skater.reach and hand: an arm reaching for a point (two-bone IK, Extension/Skater/arm_reach), in the pose drawn and sent, and where a hand joint is. - reskate.entities.set's attach: an object kept on a joint of your skater or another player's, moved every frame. - reskate.screen.look and slap: the Filmer branch's VX1000 post-process as a fixed-parameter camcorder look drawn by the overlay.
124 lines
6.4 KiB
C++
124 lines
6.4 KiB
C++
#include "arm_reach.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace dingosdk::arm_reach {
|
|
namespace {
|
|
using anim_studio::conjugate;
|
|
using anim_studio::multiply;
|
|
using anim_studio::normalised;
|
|
using anim_studio::rotate;
|
|
|
|
Vec3 add(const Vec3& a, const Vec3& b) { return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; }
|
|
Vec3 sub(const Vec3& a, const Vec3& b) { return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; }
|
|
Vec3 scaled(const Vec3& a, float k) { return {a[0] * k, a[1] * k, a[2] * k}; }
|
|
float dot(const Vec3& a, const Vec3& b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
|
|
Vec3 cross(const Vec3& a, const Vec3& b) { return {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]}; }
|
|
float length(const Vec3& a) { return std::sqrt(dot(a, a)); }
|
|
Vec3 unit(const Vec3& a, const Vec3& fallback) {
|
|
const float l = length(a);
|
|
return l > 1e-6f && std::isfinite(l) ? scaled(a, 1.0f / l) : fallback;
|
|
}
|
|
bool finite(const Vec3& v) { return std::isfinite(v[0]) && std::isfinite(v[1]) && std::isfinite(v[2]); }
|
|
bool finite(const Quat& q) { return std::isfinite(q[0]) && std::isfinite(q[1]) && std::isfinite(q[2]) && std::isfinite(q[3]); }
|
|
// The shortest turn taking direction `from` onto direction `to`.
|
|
Quat arc(const Vec3& from, const Vec3& to) {
|
|
const Vec3 a = unit(from, {0, 0, 1}), b = unit(to, {0, 0, 1});
|
|
const float d = dot(a, b);
|
|
if (d < -0.9999f) { // half a turn: about any axis square to `from`
|
|
Vec3 axis = cross({1, 0, 0}, a);
|
|
if (length(axis) < 1e-3f) axis = cross({0, 1, 0}, a);
|
|
axis = unit(axis, {0, 1, 0});
|
|
return {axis[0], axis[1], axis[2], 0.0f};
|
|
}
|
|
const Vec3 c = cross(a, b);
|
|
return normalised({c[0], c[1], c[2], 1.0f + d});
|
|
}
|
|
// Part way from a to b, the short way round.
|
|
Quat blend(const Quat& a, Quat b, float t) {
|
|
t = std::clamp(t, 0.0f, 1.0f);
|
|
if (a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3] < 0.0f)
|
|
for (auto& v : b) v = -v;
|
|
return normalised({a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t, a[3] + (b[3] - a[3]) * t});
|
|
}
|
|
// In a chain: chest, shoulder, upper arm, forearm, hand.
|
|
constexpr std::size_t chest = 6, shoulder = 7, upper = 8, fore = 9, hand_at = 10;
|
|
static_assert(right_chain.size() == hand_at + 1 && left_chain.size() == hand_at + 1);
|
|
} // namespace
|
|
|
|
bool compose(std::span<const Joint> pose, std::span<const std::uint16_t> joints, std::span<Placed> out) noexcept {
|
|
if (out.size() < joints.size()) return false;
|
|
Placed at;
|
|
for (std::size_t i = 0; i < joints.size(); ++i) {
|
|
if (joints[i] >= pose.size()) return false;
|
|
const auto& bone = pose[joints[i]];
|
|
if (!finite(bone.position) || !finite(bone.rotation) || !finite(bone.scale)) return false;
|
|
Placed next;
|
|
next.position = add(at.position, rotate(at.rotation, scaled(bone.position, at.scale)));
|
|
next.rotation = normalised(multiply(at.rotation, bone.rotation));
|
|
next.scale = at.scale * (bone.scale[0] + bone.scale[1] + bone.scale[2]) / 3.0f;
|
|
if (!finite(next.position) || !(std::abs(next.position[0]) < 1e6f && std::abs(next.position[1]) < 1e6f && std::abs(next.position[2]) < 1e6f))
|
|
return false;
|
|
out[i] = next;
|
|
at = next;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Reached reach(std::span<Joint> pose, const Goal& goal) noexcept {
|
|
const auto joints = chain(goal.hand);
|
|
std::array<Placed, right_chain.size()> w{};
|
|
if (!compose(pose, joints, w) || !finite(goal.at) || (goal.rotation && !finite(*goal.rotation))) return {};
|
|
const Vec3 s = w[upper].position, e = w[fore].position, h = w[hand_at].position;
|
|
const float a = length(sub(e, s)), b = length(sub(h, e));
|
|
if (!(a > 0.05f && b > 0.05f && a < 1.0f && b < 1.0f)) return {};
|
|
const float reach_length = (a + b) * 0.995f;
|
|
const float wanted = length(sub(goal.at, s));
|
|
const float d = std::clamp(wanted, std::abs(a - b) + 0.02f, reach_length);
|
|
const Vec3 direction = unit(sub(goal.at, s), {0, -1, 0});
|
|
const Vec3 target = add(s, scaled(direction, d));
|
|
// The elbow out from the body and a little down and back: the shoulder's side of the chest.
|
|
Vec3 side = sub(s, w[chest].position);
|
|
side[1] = 0.0f;
|
|
side = unit(side, {goal.hand == Hand::left ? -1.0f : 1.0f, 0, 0});
|
|
Vec3 ahead{direction[0], 0.0f, direction[2]};
|
|
ahead = unit(ahead, cross(side, {0, 1, 0}));
|
|
Vec3 pole = add(add(scaled(side, 0.8f), Vec3{0.0f, -0.45f, 0.0f}), scaled(ahead, -0.35f));
|
|
pole = sub(pole, scaled(direction, dot(pole, direction)));
|
|
pole = unit(pole, side);
|
|
const float cos_a = std::clamp((a * a + d * d - b * b) / (2.0f * a * d), -1.0f, 1.0f);
|
|
const float sin_a = std::sqrt(std::max(0.0f, 1.0f - cos_a * cos_a));
|
|
const Vec3 elbow = add(s, add(scaled(direction, a * cos_a), scaled(pole, a * sin_a)));
|
|
const Vec3 wrist = add(elbow, scaled(unit(sub(target, elbow), direction), b));
|
|
// The upper arm turned onto the new elbow, then the forearm (as the upper arm left it) onto the wrist.
|
|
const Quat turn_upper = arc(sub(e, s), sub(elbow, s));
|
|
const Quat upper_world = normalised(multiply(turn_upper, w[upper].rotation));
|
|
const Quat turn_fore = arc(rotate(turn_upper, sub(h, e)), sub(wrist, elbow));
|
|
const Quat fore_world = normalised(multiply(turn_fore, multiply(turn_upper, w[fore].rotation)));
|
|
// Back to each joint's own rotation, part way by the weight.
|
|
const float weight = std::clamp(goal.weight, 0.0f, 1.0f);
|
|
auto& upper_bone = pose[joints[upper]];
|
|
auto& fore_bone = pose[joints[fore]];
|
|
upper_bone.rotation = blend(upper_bone.rotation, normalised(multiply(conjugate(w[shoulder].rotation), upper_world)), weight);
|
|
fore_bone.rotation = blend(fore_bone.rotation, normalised(multiply(conjugate(upper_world), fore_world)), weight);
|
|
if (goal.rotation) {
|
|
// The hand, from the forearm as it now is, turned to the rotation asked for.
|
|
if (!compose(pose, joints, w)) return {};
|
|
auto& hand_bone = pose[joints[hand_at]];
|
|
hand_bone.rotation = blend(hand_bone.rotation, normalised(multiply(conjugate(w[fore].rotation), normalised(*goal.rotation))), weight);
|
|
}
|
|
if (!compose(pose, joints, w)) return {};
|
|
Reached out;
|
|
out.valid = finite(w[hand_at].position);
|
|
out.hand = w[hand_at];
|
|
out.reached = wanted <= reach_length + 0.01f;
|
|
return out;
|
|
}
|
|
|
|
Reached hand_of(std::span<const Joint> pose, Hand hand) noexcept {
|
|
std::array<Placed, right_chain.size()> w{};
|
|
if (!compose(pose, chain(hand), w)) return {};
|
|
return {finite(w[hand_at].position), w[hand_at], true};
|
|
}
|
|
}
|