Files
AnyPS5/core/libs/prx/libSceNgs2.native/src/Geom.cpp
T
alosta b630e7897b fix(libSceNgs2): implement a minimal sceNgs2GeomApply pass-through
Implement sceNgs2GeomApply, which was previously an unimplemented
throwing stub and was blocking Dead Cells (PPSA15552).

The implementation validates null handles/output addresses and writes a
minimal Ngs2GeomAttribute: pitch_ratio 1.0, source position, source
volume/level maxima, and diagonal channel levels for the source's channel
count. It does not implement source attenuation, distance rolloff, panning
or Doppler.

The export lives in Geom.cpp's internal Ngs2GeomApply and is exposed through
the existing default-visibility wrapper in Voice.cpp. Unimplemented.cpp no
longer defines the stub. A passing test was added to Ngs2Geom.cpp.

Unblocks: PPSA15552 #<Game bring-up discussion number>
Depends on: none
AI assistance: yes
2026-10-16 21:59:08 +02:00

107 lines
4.5 KiB
C++

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
#include "prx/libc/include/General.hpp"
#include "Ngs2Internal.hpp"
static constexpr float DEFAULT_SOUND_SPEED = 343.0f;
namespace {
using Vector = std::array<double, 3>;
Vector ReadVector(const Ngs2GeomVector& value) {
if (!std::isfinite(value.x) || !std::isfinite(value.y) || !std::isfinite(value.z)) APS5_INVALID_ARG_EX;
return {value.x, value.y, value.z};
}
double Dot(const Vector& left, const Vector& right) {
return left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
}
Vector Unit(Vector value) {
const double length = std::hypot(value[0], value[1], value[2]);
if (!(length > 0.0)) APS5_INVALID_ARG_EX;
for (auto& component : value) component /= length;
return value;
}
}
int Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* outWork, std::uint32_t flags) {
if (param == nullptr) APS5_INVALID_ARG_EX;
if (outWork == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
if (flags > 1) throw std::runtime_error("NGS2: unknown listener coordinate flags");
if (!std::isfinite(param->sound_speed) || param->sound_speed <= 0.0f) APS5_INVALID_ARG_EX;
const Vector position = ReadVector(param->position);
ReadVector(param->velocity);
const Vector front = Unit(ReadVector(param->orient_front));
Vector up = Unit(ReadVector(param->orient_up));
Vector right = Unit({up[1] * front[2] - up[2] * front[1], up[2] * front[0] - up[0] * front[2], up[0] * front[1] - up[1] * front[0]});
up = {front[1] * right[2] - front[2] * right[1], front[2] * right[0] - front[0] * right[2], front[0] * right[1] - front[1] * right[0]};
if (flags == 1) for (auto& component : right) component = -component;
const std::array<Vector, 3> axes{right, up, front};
Ngs2GeomListenerWork work{};
for (std::size_t row = 0; row < axes.size(); row++) {
for (std::size_t column = 0; column < 3; column++) work.matrix[row][column] = static_cast<float>(axes[row][column]);
const double translation = -Dot(axes[row], position);
if (std::abs(translation) > std::numeric_limits<float>::max()) APS5_INVALID_ARG_EX;
work.matrix[row][3] = static_cast<float>(translation);
}
work.matrix[3][3] = 1.0f;
work.velocity = param->velocity;
work.sound_speed = param->sound_speed;
work.coordinate = flags;
*outWork = work;
return SCE_NGS2_OK;
}
int Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param) {
if (out_listener_param == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
*out_listener_param = {};
out_listener_param->orient_front.z = 1.0f;
out_listener_param->orient_up.y = 1.0f;
out_listener_param->sound_speed = DEFAULT_SOUND_SPEED;
return SCE_NGS2_OK;
}
int Ngs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param) {
if (out_source_param == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
*out_source_param = {};
out_source_param->direction.z = 1.0f;
out_source_param->cone.inner_level = 1.0f;
out_source_param->cone.inner_angle = 360.0f;
out_source_param->cone.outer_level = 1.0f;
out_source_param->cone.outer_angle = 360.0f;
out_source_param->rolloff.max_distance = 1000000.0f;
out_source_param->rolloff.rolloff_factor = 1.0f;
out_source_param->rolloff.reference_distance = 1.0f;
out_source_param->doppler_factor = 1.0f;
out_source_param->fbw_level = 1.0f;
out_source_param->lfe_level = 1.0f;
out_source_param->max_level = 1.0f;
out_source_param->num_speakers = 2;
out_source_param->matrix_format = 2;
return SCE_NGS2_OK;
}
int Ngs2GeomApply(const Ngs2GeomListenerWork* listener, const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib, std::uint32_t flags) {
if (listener == nullptr || source == nullptr) APS5_INVALID_ARG_EX;
if (out_attrib == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
(void)flags;
std::memset(out_attrib, 0, sizeof(*out_attrib));
out_attrib->pitch_ratio = 1.0f;
out_attrib->a3d_attrib.position = source->position;
out_attrib->a3d_attrib.volume = std::max(source->min_level, source->max_level);
const std::uint32_t channels = std::min(source->matrix_format == 0u ? 2u : source->matrix_format, 8u);
const float level = source->max_level > 0.0f ? source->max_level : 1.0f;
for (std::uint32_t channel = 0; channel < channels; channel++) {
out_attrib->level[static_cast<std::size_t>(channel) * 8 + channel] = level;
}
return SCE_NGS2_OK;
}