mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 08:00:25 +02:00
Implement Bullet Bill model visibility and cockpit recentering improvements
- Added support for hiding model arrays in the rendering pipeline to optimize performance during VR gameplay. - Introduced new functions in `gx_model_visibility` to manage hidden model arrays and their visibility based on game state. - Enhanced cockpit recentering logic to ensure accurate seat measurements and eye positioning during gameplay. - Updated `FirstPersonState` to track cockpit height and forward direction, improving VR experience. - Added tests for Bullet Bill model visibility and cockpit height adjustments to ensure functionality and stability. - Refactored existing code to accommodate new features and improve overall code organization.
This commit is contained in:
1 parent
8c5a7126da
commit
3c20f677eb
17 files changed
+592
-38
No files matched your search
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mkw::vr {
|
||||
|
||||
struct BulletBillBodyArray {
|
||||
uint32_t offset = 0;
|
||||
uint32_t size = 0;
|
||||
};
|
||||
|
||||
// kart_killer's body, eye and exhaust shapes are rigid on matrix 0. Its arms
|
||||
// use other matrices. Select only position arrays used exclusively by rigid
|
||||
// body shapes, so even a mod sharing positions with an arm keeps those draws.
|
||||
// Offsets are relative to the MDL0; no names or game assets are embedded here.
|
||||
inline std::vector<BulletBillBodyArray> ReadBulletBillBodyArrays(const uint8_t* mdl, size_t size) {
|
||||
if (!mdl || size < 0x40) return {};
|
||||
const auto contains = [size](size_t at, size_t length) { return at <= size && length <= size - at; };
|
||||
const auto read32 = [mdl](size_t at) {
|
||||
return (uint32_t(mdl[at]) << 24) | (uint32_t(mdl[at + 1]) << 16) |
|
||||
(uint32_t(mdl[at + 2]) << 8) | mdl[at + 3];
|
||||
};
|
||||
const auto read16 = [mdl](size_t at) { return (uint32_t(mdl[at]) << 8) | mdl[at + 1]; };
|
||||
const uint32_t version = read32(8);
|
||||
if (read32(0) != 0x4d444c30 || read32(4) != size || version < 8 || version > 11) return {};
|
||||
const size_t positions = read32(0x18), shapes = read32(version >= 10 ? 0x38 : 0x30);
|
||||
if (!positions || !shapes || !contains(positions, 8) || !contains(shapes, 8)) return {};
|
||||
const uint32_t positionCount = read32(positions + 4), shapeCount = read32(shapes + 4);
|
||||
if (positionCount > 64 || shapeCount > 4096 ||
|
||||
!contains(positions + 8, size_t(positionCount + 1) * 16) ||
|
||||
!contains(shapes + 8, size_t(shapeCount + 1) * 16)) return {};
|
||||
std::vector<BulletBillBodyArray> result;
|
||||
for (uint32_t i = 1; i <= positionCount; ++i) {
|
||||
const size_t array = positions + size_t(read32(positions + 8 + i * 16 + 12));
|
||||
if (!contains(array, 0x38)) return {};
|
||||
const uint32_t id = read32(array + 0x10);
|
||||
bool body = false, other = false;
|
||||
for (uint32_t j = 1; j <= shapeCount; ++j) {
|
||||
const size_t shape = shapes + size_t(read32(shapes + 8 + j * 16 + 12));
|
||||
if (!contains(shape, 0x60)) return {};
|
||||
if (read16(shape + 0x48) != id) continue;
|
||||
const bool rigidBody = read32(shape + 8) == 0 && (read32(shape + 0xc) & 1u) == 0;
|
||||
body |= rigidBody;
|
||||
other |= !rigidBody;
|
||||
}
|
||||
if (!body || other) continue;
|
||||
const size_t data = array + size_t(read32(array + 8));
|
||||
const uint32_t bytes = uint32_t(mdl[array + 0x1d]) * read16(array + 0x1e);
|
||||
if (!bytes || !contains(data, bytes)) return {};
|
||||
result.push_back({static_cast<uint32_t>(data), bytes});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace mkw::vr
|
||||
@@ -206,6 +206,17 @@ inline float EyeBehindControls(float eyeForward, float controlsForward, float un
|
||||
return std::min(eyeForward, controlsForward - clearance);
|
||||
}
|
||||
|
||||
// Once the eye is pulled behind the controls, a long neck or snout must not
|
||||
// leave them down at the player's knees. Keep the measured character scale,
|
||||
// but cap the seated eye at 40 cm above the neutral hand targets.
|
||||
inline float EyeAboveControls(float eyeHeight, float controlsHeight, float units) noexcept {
|
||||
if (!detail::IsFiniteFloat(&controlsHeight) || !detail::IsFiniteFloat(&units) || units <= 0.0f) {
|
||||
return eyeHeight;
|
||||
}
|
||||
const float limit = controlsHeight + 0.40f * units;
|
||||
return limit >= 5.0f ? std::min(eyeHeight, limit) : eyeHeight;
|
||||
}
|
||||
|
||||
// Tall characters sit higher; normalise them to a comfortable perceived cockpit
|
||||
// height by growing the world scale with the measured eye height.
|
||||
inline float CharacterCockpitScale(float eyeHeight) noexcept {
|
||||
@@ -237,6 +248,15 @@ inline bool NeutralPlayerScale(const std::array<float, 3>& scale) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ValidSeatedEye(const std::array<float, 3>& eye) noexcept {
|
||||
for (const auto& value : eye) {
|
||||
if (!detail::IsFiniteFloat(&value) || std::abs(value) > 500.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return eye[1] >= 5.0f;
|
||||
}
|
||||
|
||||
// Eye position resources are in the face bone's local coordinates, whose axes
|
||||
// differ between characters. Transform their centre through the complete bind
|
||||
// matrix before applying the vehicle-specific driver placement.
|
||||
@@ -258,12 +278,7 @@ inline bool ComputeDriverEyeFromBounds(const Mtx34& face, const Mtx34& placement
|
||||
(minimum.z + maximum.z) * 0.5f);
|
||||
const auto seat = detail::TransformPoint(placement, model.x, model.y, model.z);
|
||||
const std::array<float, 3> result{seat.x, seat.y, seat.z};
|
||||
for (const auto& value : result) {
|
||||
if (!detail::IsFiniteFloat(&value) || std::abs(value) > 500.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (seat.y < 5.0f) {
|
||||
if (!ValidSeatedEye(result)) {
|
||||
return false;
|
||||
}
|
||||
eye = result;
|
||||
@@ -288,29 +303,50 @@ inline bool ComputeSeatedEye(const Mtx34& faceWorld, const Mtx34& bodyWorld, det
|
||||
const detail::Vec3 delta{world.x - bodyWorld[3], world.y - bodyWorld[7], world.z - bodyWorld[11]};
|
||||
const std::array<float, 3> result{detail::Dot(bc, delta) / det, detail::Dot(ca, delta) / det,
|
||||
detail::Dot(ab, delta) / det};
|
||||
for (const auto& value : result) {
|
||||
if (!detail::IsFiniteFloat(&value) || std::abs(value) > 500.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (result[1] < 5.0f) {
|
||||
if (!ValidSeatedEye(result)) {
|
||||
return false;
|
||||
}
|
||||
eye = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mods may have no separately named eye geometry. Estimate just above/ahead
|
||||
// of the evaluated head, in vehicle axes, not the head bone's rotated axes.
|
||||
// The animated pose includes the character animation's scale and placement;
|
||||
// adding the driver's placement again would put some mods below the vehicle.
|
||||
inline bool ComputeDriverEyeFromHead(const Mtx34& headWorld, const Mtx34& bodyWorld,
|
||||
std::array<float, 3>& eye) noexcept {
|
||||
std::array<float, 3> head{};
|
||||
if (!ComputeSeatedEye(headWorld, bodyWorld, {0, 0, 0}, head)) {
|
||||
return false;
|
||||
}
|
||||
head[1] += 8.0f;
|
||||
head[2] += 8.0f;
|
||||
if (!ValidSeatedEye(head)) {
|
||||
return false;
|
||||
}
|
||||
eye = head;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The neutral seated eye, accepted once eight consecutive safe samples agree
|
||||
// within two units, then frozen until the driver or the race changes.
|
||||
// within two units of the first sample, then frozen until the driver or the
|
||||
// race changes, or a recenter asks for another calibration. Keep the previous
|
||||
// seat while waiting for a safe replacement.
|
||||
struct SeatedEyeReference {
|
||||
std::array<float, 3> value{}, candidate{};
|
||||
unsigned stable = 0;
|
||||
bool valid = false;
|
||||
bool recalibrating = false;
|
||||
void Recalibrate() noexcept {
|
||||
stable = 0;
|
||||
recalibrating = true;
|
||||
}
|
||||
void Observe(const std::array<float, 3>& sample, bool safe, bool freeze) {
|
||||
if (freeze && valid) {
|
||||
if (freeze && valid && !recalibrating) {
|
||||
return;
|
||||
}
|
||||
if (!safe) {
|
||||
if (!safe || !ValidSeatedEye(sample)) {
|
||||
stable = 0;
|
||||
return;
|
||||
}
|
||||
@@ -318,11 +354,16 @@ struct SeatedEyeReference {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
delta = std::max(delta, std::abs(sample[i] - candidate[i]));
|
||||
}
|
||||
stable = stable && delta < 2.0f ? stable + 1 : 1;
|
||||
candidate = sample;
|
||||
if (stable && delta < 2.0f) {
|
||||
++stable;
|
||||
} else {
|
||||
stable = 1;
|
||||
candidate = sample;
|
||||
}
|
||||
if (stable >= 8) {
|
||||
value = sample;
|
||||
valid = true;
|
||||
recalibrating = false;
|
||||
stable = 8;
|
||||
}
|
||||
}
|
||||
@@ -521,6 +562,10 @@ void MkwVRFirstPersonCommit() noexcept;
|
||||
// Drops every captured pointer and the held anchor. Call on race entry/exit.
|
||||
void MkwVRFirstPersonReset() noexcept;
|
||||
|
||||
// Thread-safe request; the guest thread remeasures the cockpit on subsequent
|
||||
// neutral frames. Keeps the current seat until a replacement is ready.
|
||||
void MkwVRFirstPersonRecenter() noexcept;
|
||||
|
||||
// Producer-side read. Thread-safe. A valid anchor is also what marks the mode
|
||||
// as engaged, and so what selects the first-person world scale: it is invalid
|
||||
// whenever the mode is off, the race has not produced a usable anchor, or the
|
||||
|
||||
Reference in new issue
Block a user