Switched positional audio to collision coordinate system.

Refactored positional audio.
This commit is contained in:
Muzychenko Andrey 2022-06-01 16:19:27 +03:00
parent c93e11ee6b
commit 8017734de4
19 changed files with 214 additions and 143 deletions

View File

@ -15,6 +15,7 @@
#include "TBall.h" #include "TBall.h"
#include "render.h" #include "render.h"
#include "options.h" #include "options.h"
#include "Sound.h"
gdrv_bitmap8* DebugOverlay::dbScreen = nullptr; gdrv_bitmap8* DebugOverlay::dbScreen = nullptr;
@ -109,10 +110,14 @@ void DebugOverlay::DrawOverlay()
if (options::Options.DebugOverlayAllEdges) if (options::Options.DebugOverlayAllEdges)
DrawAllEdges(); DrawAllEdges();
// Draw ball collision // Draw ball collision info
if (options::Options.DebugOverlayBallPosition || options::Options.DebugOverlayBallEdges) if (options::Options.DebugOverlayBallPosition || options::Options.DebugOverlayBallEdges)
DrawBallInfo(); DrawBallInfo();
// Draw positions associated with currently playing sound channels
if (options::Options.DebugOverlaySounds)
DrawSoundPositions();
// Restore render target // Restore render target
SDL_SetRenderTarget(winmain::Renderer, initialRenderTarget); SDL_SetRenderTarget(winmain::Renderer, initialRenderTarget);
SDL_SetRenderDrawColor(winmain::Renderer, SDL_SetRenderDrawColor(winmain::Renderer,
@ -133,18 +138,18 @@ void DebugOverlay::DrawBoxGrid()
SDL_SetRenderDrawColor(winmain::Renderer, 0, 255, 0, 255); SDL_SetRenderDrawColor(winmain::Renderer, 0, 255, 0, 255);
for (int x = 0; x <= edgeMan.MaxBoxX; x++) for (int x = 0; x <= edgeMan.MaxBoxX; x++)
{ {
vector2 boxPt{ x * edgeMan.AdvanceX + edgeMan.X , edgeMan.Y }; vector2 boxPt{ x * edgeMan.AdvanceX + edgeMan.MinX , edgeMan.MinY };
auto pt1 = proj::xform_to_2d(boxPt); auto pt1 = proj::xform_to_2d(boxPt);
boxPt.Y = edgeMan.MaxBoxY * edgeMan.AdvanceY + edgeMan.Y; boxPt.Y = edgeMan.MaxBoxY * edgeMan.AdvanceY + edgeMan.MinY;
auto pt2 = proj::xform_to_2d(boxPt); auto pt2 = proj::xform_to_2d(boxPt);
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y); SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
} }
for (int y = 0; y <= edgeMan.MaxBoxY; y++) for (int y = 0; y <= edgeMan.MaxBoxY; y++)
{ {
vector2 boxPt{ edgeMan.X, y * edgeMan.AdvanceY + edgeMan.Y }; vector2 boxPt{ edgeMan.MinX, y * edgeMan.AdvanceY + edgeMan.MinY };
auto pt1 = proj::xform_to_2d(boxPt); auto pt1 = proj::xform_to_2d(boxPt);
boxPt.X = edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.X; boxPt.X = edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.MinX;
auto pt2 = proj::xform_to_2d(boxPt); auto pt2 = proj::xform_to_2d(boxPt);
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y); SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
@ -225,6 +230,19 @@ void DebugOverlay::DrawAllSprites()
} }
} }
void DebugOverlay::DrawSoundPositions()
{
auto& edgeMan = *TTableLayer::edge_manager;
SDL_SetRenderDrawColor(winmain::Renderer, 200, 0, 200, 255);
for (auto& posNorm : Sound::Channels)
{
auto pos3D = edgeMan.DeNormalizeBox(posNorm.Position);
auto pos2D = proj::xform_to_2d(pos3D);
SDL_RenderDrawCircle(winmain::Renderer, pos2D.X, pos2D.Y, 7);
}
}
void DebugOverlay::DrawCicleType(circle_type& circle) void DebugOverlay::DrawCicleType(circle_type& circle)
{ {
vector2 linePt{ circle.Center.X + sqrt(circle.RadiusSq), circle.Center.Y }; vector2 linePt{ circle.Center.X + sqrt(circle.RadiusSq), circle.Center.Y };

View File

@ -20,4 +20,5 @@ private:
static void DrawAllEdges(); static void DrawAllEdges();
static void DrawBallInfo(); static void DrawBallInfo();
static void DrawAllSprites(); static void DrawAllSprites();
static void DrawSoundPositions();
}; };

View File

@ -5,7 +5,7 @@
int Sound::num_channels; int Sound::num_channels;
bool Sound::enabled_flag = false; bool Sound::enabled_flag = false;
int* Sound::TimeStamps = nullptr; std::vector<ChannelInfo> Sound::Channels{};
int Sound::Volume = MIX_MAX_VOLUME; int Sound::Volume = MIX_MAX_VOLUME;
bool Sound::Init(int channels, bool enableFlag, int volume) bool Sound::Init(int channels, bool enableFlag, int volume)
@ -37,106 +37,79 @@ void Sound::Deactivate()
void Sound::Close() void Sound::Close()
{ {
delete[] TimeStamps; Channels.clear();
TimeStamps = nullptr;
Mix_CloseAudio(); Mix_CloseAudio();
Mix_Quit(); Mix_Quit();
} }
void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent *soundSource, const char* info) void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent* soundSource, const char* info)
{ {
if (wavePtr && enabled_flag) if (wavePtr && enabled_flag)
{ {
if (Mix_Playing(-1) == num_channels) if (Mix_Playing(-1) == num_channels)
{ {
auto oldestChannel = std::min_element(TimeStamps, TimeStamps + num_channels) - TimeStamps; auto cmp = [](const ChannelInfo& a, const ChannelInfo& b)
{
return a.TimeStamp < b.TimeStamp;
};
auto min = std::min_element(Channels.begin(), Channels.end(), cmp);
auto oldestChannel = std::distance(Channels.begin(), min);
Mix_HaltChannel(oldestChannel); Mix_HaltChannel(oldestChannel);
} }
auto channel = Mix_PlayChannel(-1, wavePtr, 0); auto channel = Mix_PlayChannel(-1, wavePtr, 0);
if (channel != -1) { if (channel != -1)
TimeStamps[channel] = time; {
if (options::Options.SoundStereo) { Channels[channel].TimeStamp = time;
/* Think 3D sound positioning, where: if (options::Options.SoundStereo)
* - x goes from 0 to 1, left to right on the screen, {
* - y goes from 0 to 1, top to bottom on the screen, // Positional audio uses collision grid 2D coordinates normalized to [0, 1]
* - z goes from 0 to infinity, from table-level to the sky. // Point (0, 0) is bottom left table corner; point (1, 1) is top right table corner.
* // Z is defined as: 0 at table level, positive axis goes up from table surface.
* We position the listener at the bottom center of the table,
* at 0.5 height, so roughly a table half-length. Coords of
* the listener are thus {0.5, 1.0, 0.5}.
*
* We use basic trigonometry to calculate the angle and distance
* from a sound source to the listener.
*
* Mix_SetPosition expects an angle in (Sint16)degrees, where
* 0 degrees is in front, 90 degrees is to the right, and so on.
* Mix_SetPosition expects a (Uint8)distance from 0 (near) to 255 (far).
*/
/* Get the sound source position. */ // Get the source sound position.
vector2 coordinates; // Sound without position are assumed to be at the center top of the table.
/* Some sounds are unpositioned; for that case the caller sends vector3 soundPos{};
* a NULL pointer as a soundSource; in those cases we position if (soundSource)
* the sound at the center top of the table. {
*/ auto soundPos2D = soundSource->get_coordinates();
if (!soundSource) { soundPos = {soundPos2D.X, soundPos2D.Y, 0.0f};
coordinates.X = 0.5f;
coordinates.Y = 0.0f;
} }
else { else
coordinates = soundSource->get_coordinates(); {
}; soundPos = {0.5f, 1.0f, 0.0f};
/* Player position. */
auto pX = 0.5f;
auto pY = 1.0f;
auto pZ = 0.5f;
/* Calculate lengths of three sides of a triangle.
* ptos (Player-to-sound): distance from listener to the sound source,
* ptom (player-to-middle): distance from listener to the sound source
* when the latter is repositioned to the
* X center,
* stom (sound-to-middle): distance from ptos to ptom.
*/
auto ptos = sqrt(((coordinates.X - pX) * (coordinates.X - pX)) + ((coordinates.Y - pY) * (coordinates.Y - pY)) + (pZ * pZ));
auto ptom = sqrt(((coordinates.Y - pY) * (coordinates.Y - pY)) + (pZ * pZ));
auto stom = fabs(coordinates.X - 0.5);
/* Calculate the angle using the law of cosines and acos().
* That will return an angle in radians, e.g. in the [0,PI] range;
* we remap to [0,180], and cast to an integer.
*/
Sint16 angle = (Sint16)(acos(((stom * stom) - (ptos * ptos) - (ptom * ptom)) / (-2.0f * ptos * ptom)) * 180.0f / IM_PI);
/* Because we are using distances to calculate the angle,
* we now have no clue if the sound is to the right or the
* left. If the sound is to the right, the current value
* is good, but to the left, we need substract it from 360.
*/
if (coordinates.X < 0.5) {
angle = (360 - angle);
} }
Channels[channel].Position = soundPos;
/* Distance from listener to the ball (ptos) is roughly // Listener is positioned at the bottom center of the table,
* in the [0.5,1.55] range; remap to 50-155 by multiplying // at 0.5 height, so roughly a table half - length.
* by 100 and cast to an integer. */ vector3 playerPos = {0.5f, 0.0f, 0.5f};
Uint8 distance = (Uint8)(100.0f * ptos); auto soundDir = maths::vector_sub(soundPos, playerPos);
Mix_SetPosition(channel, angle, distance);
/* Output position of each sound emitted so we can verify // Find sound angle from positive Y axis in clockwise direction with atan2
* the sanity of the implementation. // Remap atan2 output from (-Pi, Pi] to [0, 2 * Pi)
*/ auto angle = fmodf(atan2(soundDir.X, soundDir.Y) + Pi * 2, Pi * 2);
/* auto angleDeg = angle * 180.0f / Pi;
printf("X: %3.3f Y: %3.3f Angle: %3d Distance: %3d, Object: %s\n", auto angleSdl = static_cast<Sint16>(angleDeg);
coordinates.X,
coordinates.Y, // Distance from listener to the sound position is roughly in the [0, ~1.22] range.
angle, // Remap to [0, 122] by multiplying by 100 and cast to an integer.
auto distance = static_cast<Uint8>(100.0f * maths::magnitude(soundDir));
// Mix_SetPosition expects an angle in (Sint16)degrees, where
// angle 0 is due north, and rotates clockwise as the value increases.
// Mix_SetPosition expects a (Uint8)distance from 0 (near) to 255 (far).
Mix_SetPosition(channel, angleSdl, distance);
// Output position of each sound emitted so we can verify
// the sanity of the implementation.
/*printf("X: %3.3f Y: %3.3f Angle: %3.3f Distance: %3d, Object: %s\n",
soundPos.X,
soundPos.Y,
angleDeg,
distance, distance,
info info
); );*/
*/
} }
} }
} }
@ -164,8 +137,7 @@ void Sound::SetChannels(int channels)
channels = 8; channels = 8;
num_channels = channels; num_channels = channels;
delete[] TimeStamps; Channels.resize(num_channels);
TimeStamps = new int[num_channels]();
Mix_AllocateChannels(num_channels); Mix_AllocateChannels(num_channels);
SetVolume(Volume); SetVolume(Volume);
} }

View File

@ -1,10 +1,18 @@
#pragma once #pragma once
#include "maths.h"
#include "TPinballComponent.h" #include "TPinballComponent.h"
struct ChannelInfo
{
int TimeStamp;
vector2 Position;
};
class Sound class Sound
{ {
public: public:
static std::vector<ChannelInfo> Channels;
static bool Init(int channels, bool enableFlag, int volume); static bool Init(int channels, bool enableFlag, int volume);
static void Enable(bool enableFlag); static void Enable(bool enableFlag);
static void Activate(); static void Activate();
@ -18,6 +26,5 @@ public:
private: private:
static int num_channels; static int num_channels;
static bool enabled_flag; static bool enabled_flag;
static int* TimeStamps;
static int Volume; static int Volume;
}; };

View File

@ -9,6 +9,7 @@
#include "proj.h" #include "proj.h"
#include "render.h" #include "render.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TTableLayer.h"
TBall::TBall(TPinballTable* table) : TPinballComponent(table, -1, false) TBall::TBall(TPinballTable* table) : TPinballComponent(table, -1, false)
{ {
@ -138,9 +139,5 @@ void TBall::throw_ball(TBall* ball, vector3* direction, float angleMult, float s
vector2 TBall::get_coordinates() vector2 TBall::get_coordinates()
{ {
vector2 coordinates; return TTableLayer::edge_manager->NormalizeBox(Position);
vector2i pos2D = proj::xform_to_2d(Position);
coordinates.X = (float)pos2D.X / PinballTable->Width;
coordinates.Y = (float)pos2D.Y / PinballTable->Height;
return coordinates;
} }

View File

@ -8,16 +8,18 @@
#include "TEdgeSegment.h" #include "TEdgeSegment.h"
#include "TTableLayer.h" #include "TTableLayer.h"
TEdgeManager::TEdgeManager(float posX, float posY, float width, float height) TEdgeManager::TEdgeManager(float xMin, float yMin, float width, float height)
{ {
X = posX; Width = width;
Y = posY; Height = height;
MinX = xMin;
MinY = yMin;
MaxX = MinX + width;
MaxY = MinY + height;
MaxBoxX = 10; MaxBoxX = 10;
MaxBoxY = 15; MaxBoxY = 15;
AdvanceX = width / static_cast<float>(MaxBoxX); AdvanceX = width / static_cast<float>(MaxBoxX);
AdvanceY = height / static_cast<float>(MaxBoxY); AdvanceY = height / static_cast<float>(MaxBoxY);
AdvanceXInv = 1.0f / AdvanceX;
AdvanceYInv = 1.0f / AdvanceY;
BoxArray = new TEdgeBox[MaxBoxX * MaxBoxY]; BoxArray = new TEdgeBox[MaxBoxX * MaxBoxY];
} }
@ -28,12 +30,12 @@ TEdgeManager::~TEdgeManager()
int TEdgeManager::box_x(float x) int TEdgeManager::box_x(float x)
{ {
return std::max(0, std::min(static_cast<int>(floor((x - X) * AdvanceXInv)), MaxBoxX - 1)); return std::max(0, std::min(static_cast<int>(floor((x - MinX) / AdvanceX)), MaxBoxX - 1));
} }
int TEdgeManager::box_y(float y) int TEdgeManager::box_y(float y)
{ {
return std::max(0, std::min(static_cast<int>(floor((y - Y) * AdvanceYInv)), MaxBoxY - 1)); return std::max(0, std::min(static_cast<int>(floor((y - MinY) / AdvanceY)), MaxBoxY - 1));
} }
int TEdgeManager::increment_box_x(int x) int TEdgeManager::increment_box_x(int x)
@ -180,8 +182,8 @@ float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegme
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;) for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
{ {
// Calculate y from indexY and from line formula // Calculate y from indexY and from line formula
auto yDiscrete = (indexY + yBias) * AdvanceY + Y; auto yDiscrete = (indexY + yBias) * AdvanceY + MinY;
auto ylinear = ((indexX + xBias) * AdvanceX + X) * dyDx + precomp; auto ylinear = ((indexX + xBias) * AdvanceX + MinX) * dyDx + precomp;
if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete) if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete)
{ {
// Advance indexY when discrete value is ahead/behind // Advance indexY when discrete value is ahead/behind
@ -207,3 +209,23 @@ float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegme
return distance; return distance;
} }
vector2 TEdgeManager::NormalizeBox(vector2 pt) const
{
// Standard PB Box ranges: X [-8, 8]; Y [-14, 15]; Top right corner: (-8, -14)
// Bring them to: X [0, 16]; Y [0, 29]; Top right corner: (0, 0)
auto x = Clamp(pt.X, MinX, MaxX) + abs(MinX);
auto y = Clamp(pt.Y, MinY, MaxY) + abs(MinY);
// Normalize and invert to: X [0, 1]; Y [0, 1]; Top right corner: (1, 1)
x /= Width; y /= Height;
return vector2{ 1 - x, 1 - y };
}
vector2 TEdgeManager::DeNormalizeBox(vector2 pt) const
{
// Undo normalization by applying steps in reverse
auto x = (1 - pt.X) * Width - abs(MinX);
auto y = (1 - pt.Y) * Height - abs(MinY);
return vector2{ x, y };
}

View File

@ -14,7 +14,7 @@ struct field_effect_type
class TEdgeManager class TEdgeManager
{ {
public: public:
TEdgeManager(float posX, float posY, float width, float height); TEdgeManager(float xMin, float yMin, float width, float height);
~TEdgeManager(); ~TEdgeManager();
void FieldEffects(TBall* ball, struct vector2* dstVec); void FieldEffects(TBall* ball, struct vector2* dstVec);
int box_x(float x); int box_x(float x);
@ -25,15 +25,19 @@ public:
void add_field_to_box(int x, int y, field_effect_type* field); void add_field_to_box(int x, int y, field_effect_type* field);
int TestGridBox(int x, int y, float* distPtr, TEdgeSegment** edgeDst, ray_type* ray, TBall* ball, int edgeIndex); int TestGridBox(int x, int y, float* distPtr, TEdgeSegment** edgeDst, ray_type* ray, TBall* ball, int edgeIndex);
float FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegment** edge); float FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegment** edge);
vector2 NormalizeBox(vector2 pt) const;
vector2 DeNormalizeBox(vector2 pt) const;
float AdvanceX; float AdvanceX;
float AdvanceY; float AdvanceY;
float AdvanceXInv;
float AdvanceYInv;
int MaxBoxX; int MaxBoxX;
int MaxBoxY; int MaxBoxY;
float X; float MinX;
float Y; float MinY;
float MaxX;
float MaxY;
float Width;
float Height;
TEdgeBox* BoxArray; TEdgeBox* BoxArray;
TEdgeSegment* EdgeArray[1000]{}; TEdgeSegment* EdgeArray[1000]{};
}; };

View File

@ -102,8 +102,8 @@ void TLine::place_in_grid()
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;) for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
{ {
// Calculate y from indexY and from line formula // Calculate y from indexY and from line formula
auto yDiscrete = (indexY + yBias) * edgeMan->AdvanceY + edgeMan->Y; auto yDiscrete = (indexY + yBias) * edgeMan->AdvanceY + edgeMan->MinY;
auto ylinear = ((indexX + xBias) * edgeMan->AdvanceX + edgeMan->X) * dyDx + precomp; auto ylinear = ((indexX + xBias) * edgeMan->AdvanceX + edgeMan->MinX) * dyDx + precomp;
if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete) if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete)
{ {
// Advance indexY when discrete value is ahead/behind // Advance indexY when discrete value is ahead/behind

View File

@ -1,8 +1,10 @@
#include "pch.h" #include "pch.h"
#include "TPinballComponent.h" #include "TPinballComponent.h"
#include "loader.h" #include "loader.h"
#include "proj.h"
#include "render.h" #include "render.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TTableLayer.h"
TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals) TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals)
{ {
@ -72,9 +74,13 @@ TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool
rootBmp->YPosition - table->YOffset, rootBmp->YPosition - table->YOffset,
&bmp1Rect); &bmp1Rect);
// Sound position = center of root visual, reverse-projected, normalized.
auto& rect = RenderSprite->BmpRect; auto& rect = RenderSprite->BmpRect;
VisualPosNormX = (rect.XPosition + (rect.Width / 2.0f)) / PinballTable->Width; vector2i pos2D{ rect.XPosition + rect.Width / 2, rect.YPosition + rect.Height / 2 };
VisualPosNormY = (rect.YPosition + (rect.Height / 2.0f)) / PinballTable->Height; auto pos3D = proj::ReverseXForm(pos2D);
auto posNorm = TTableLayer::edge_manager->NormalizeBox(pos3D);
VisualPosNormX = posNorm.X;
VisualPosNormY = posNorm.Y;
} }
} }
GroupIndex = groupIndex; GroupIndex = groupIndex;

View File

@ -71,37 +71,33 @@ TTableLayer::TTableLayer(TPinballTable* table): TCollisionComponent(table, -1, f
Threshold = visual.Kicker.Threshold; Threshold = visual.Kicker.Threshold;
Boost = 15.0f; Boost = 15.0f;
auto visArrPtr = visual.FloatArr; auto edgePoints = reinterpret_cast<vector2*>(visual.FloatArr);
Unknown1F = std::min(visArrPtr[0], std::min(visArrPtr[2], visArrPtr[4])); XMin = std::min(edgePoints[0].X, std::min(edgePoints[1].X, edgePoints[2].X));
Unknown2F = std::min(visArrPtr[1], std::min(visArrPtr[3], visArrPtr[5])); YMin = std::min(edgePoints[0].Y, std::min(edgePoints[1].Y, edgePoints[2].Y));
Unknown3F = std::max(visArrPtr[0], std::max(visArrPtr[2], visArrPtr[4])); XMax = std::max(edgePoints[0].X, std::max(edgePoints[1].X, edgePoints[2].X));
Unknown4F = std::max(visArrPtr[1], std::max(visArrPtr[3], visArrPtr[5])); YMax = std::max(edgePoints[0].Y, std::max(edgePoints[1].Y, edgePoints[2].Y));
auto a2 = Unknown4F - Unknown2F;
auto a1 = Unknown3F - Unknown1F;
edge_manager = new TEdgeManager(Unknown1F, Unknown2F, a1, a2);
for (auto visFloatArrCount = visual.FloatArrCount; visFloatArrCount > 0; visFloatArrCount--) auto height = YMax - YMin;
auto width = XMax - XMin;
edge_manager = new TEdgeManager(XMin, YMin, width, height);
for (auto i = 0; i < visual.FloatArrCount; i++)
{ {
auto line = new TLine(this, auto line = new TLine(this,
&ActiveFlag, &ActiveFlag,
visual.CollisionGroup, visual.CollisionGroup,
visArrPtr[2], edgePoints[i + 1].X,
visArrPtr[3], edgePoints[i + 1].Y,
visArrPtr[0], edgePoints[i].X,
visArrPtr[1]); edgePoints[i].Y);
if (line)
{
line->place_in_grid(); line->place_in_grid();
EdgeList.push_back(line); EdgeList.push_back(line);
} }
visArrPtr += 2;
}
Field.CollisionGroup = -1; Field.CollisionGroup = -1;
Field.ActiveFlag = &ActiveFlag; Field.ActiveFlag = &ActiveFlag;
Field.CollisionComp = this; Field.CollisionComp = this;
edges_insert_square(Unknown2F, Unknown1F, Unknown4F, Unknown3F, nullptr, edges_insert_square(YMin, XMin, YMax, XMax, nullptr,
&Field); &Field);
} }
@ -134,10 +130,10 @@ void TTableLayer::edges_insert_square(float y0, float x0, float y1, float x1, TE
int xMaxBox = edge_manager->box_x(xMax); int xMaxBox = edge_manager->box_x(xMax);
int yMaxBox = edge_manager->box_y(yMax); int yMaxBox = edge_manager->box_y(yMax);
float boxX = static_cast<float>(xMinBox) * edge_manager->AdvanceX + edge_manager->X; float boxX = static_cast<float>(xMinBox) * edge_manager->AdvanceX + edge_manager->MinX;
for (int indexX = xMinBox; indexX <= xMaxBox; ++indexX) for (int indexX = xMinBox; indexX <= xMaxBox; ++indexX)
{ {
float boxY = static_cast<float>(yMinBox) * edge_manager->AdvanceY + edge_manager->Y; float boxY = static_cast<float>(yMinBox) * edge_manager->AdvanceY + edge_manager->MinY;
for (int indexY = yMinBox; indexY <= yMaxBox; ++indexY) for (int indexY = yMinBox; indexY <= yMaxBox; ++indexY)
{ {
if (xMax >= boxX && xMin <= boxX + edge_manager->AdvanceX && if (xMax >= boxX && xMin <= boxX + edge_manager->AdvanceX &&
@ -182,10 +178,10 @@ void TTableLayer::edges_insert_circle(circle_type* circle, TEdgeSegment* edge, f
xMaxBox = edge_manager->increment_box_x(xMaxBox); xMaxBox = edge_manager->increment_box_x(xMaxBox);
yMaxBox = edge_manager->increment_box_y(yMaxBox); yMaxBox = edge_manager->increment_box_y(yMaxBox);
vec1.X = static_cast<float>(dirX) * edge_manager->AdvanceX + edge_manager->X; vec1.X = static_cast<float>(dirX) * edge_manager->AdvanceX + edge_manager->MinX;
for (auto indexX = dirX; indexX <= xMaxBox; ++indexX) for (auto indexX = dirX; indexX <= xMaxBox; ++indexX)
{ {
vec1.Y = static_cast<float>(dirY) * edge_manager->AdvanceY + edge_manager->Y; vec1.Y = static_cast<float>(dirY) * edge_manager->AdvanceY + edge_manager->MinY;
for (int indexY = dirY; indexY <= yMaxBox; ++indexY) for (int indexY = dirY; indexY <= yMaxBox; ++indexY)
{ {
auto vec1XAdv = vec1.X + edge_manager->AdvanceX; auto vec1XAdv = vec1.X + edge_manager->AdvanceX;

View File

@ -21,10 +21,10 @@ public:
static void edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field); static void edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field);
gdrv_bitmap8* VisBmp; gdrv_bitmap8* VisBmp;
float Unknown1F; float XMin;
float Unknown2F; float YMin;
float Unknown3F; float XMax;
float Unknown4F; float YMax;
float GraityDirX; float GraityDirX;
float GraityDirY; float GraityDirY;
float GraityMult; float GraityMult;

View File

@ -220,6 +220,11 @@ vector2 maths::vector_sub(const vector2& vec1, const vector2& vec2)
return { vec1.X - vec2.X, vec1.Y - vec2.Y }; return { vec1.X - vec2.X, vec1.Y - vec2.Y };
} }
vector3 maths::vector_sub(const vector3& vec1, const vector3& vec2)
{
return { vec1.X - vec2.X, vec1.Y - vec2.Y, vec1.Z - vec2.Z };
}
vector2 maths::vector_mul(const vector2& vec1, float val) vector2 maths::vector_mul(const vector2& vec1, float val)
{ {
return { vec1.X * val, vec1.Y * val }; return { vec1.X * val, vec1.Y * val };

View File

@ -108,6 +108,7 @@ public:
static float magnitude(const vector3& vec); static float magnitude(const vector3& vec);
static void vector_add(vector2& vec1Dst, const vector2& vec2); static void vector_add(vector2& vec1Dst, const vector2& vec2);
static vector2 vector_sub(const vector2& vec1, const vector2& vec2); static vector2 vector_sub(const vector2& vec1, const vector2& vec2);
static vector3 vector_sub(const vector3& vec1, const vector3& vec2);
static vector2 vector_mul(const vector2& vec1, float val); static vector2 vector_mul(const vector2& vec1, float val);
static float basic_collision(TBall* ball, vector2* nextPosition, vector2* direction, float elasticity, static float basic_collision(TBall* ball, vector2* nextPosition, vector2* direction, float elasticity,
float smoothness, float smoothness,

View File

@ -112,6 +112,7 @@ void options::InitPrimary()
Options.DebugOverlayBallEdges = get_int("Debug Overlay Ball Edges", true); Options.DebugOverlayBallEdges = get_int("Debug Overlay Ball Edges", true);
Options.DebugOverlayCollisionMask = get_int("Debug Overlay Collision Mask", true); Options.DebugOverlayCollisionMask = get_int("Debug Overlay Collision Mask", true);
Options.DebugOverlaySprites = get_int("Debug Overlay Sprites", true); Options.DebugOverlaySprites = get_int("Debug Overlay Sprites", true);
Options.DebugOverlaySounds = get_int("Debug Overlay Sounds", true);
} }
void options::InitSecondary() void options::InitSecondary()
@ -159,6 +160,7 @@ void options::uninit()
set_int("Debug Overlay Ball Edges", Options.DebugOverlayBallEdges); set_int("Debug Overlay Ball Edges", Options.DebugOverlayBallEdges);
set_int("Debug Overlay Collision Mask", Options.DebugOverlayCollisionMask); set_int("Debug Overlay Collision Mask", Options.DebugOverlayCollisionMask);
set_int("Debug Overlay Sprites", Options.DebugOverlaySprites); set_int("Debug Overlay Sprites", Options.DebugOverlaySprites);
get_int("Debug Overlay Sounds", Options.DebugOverlaySounds);
} }

View File

@ -89,6 +89,7 @@ struct optionsStruct
bool DebugOverlayBallEdges; bool DebugOverlayBallEdges;
bool DebugOverlayCollisionMask; bool DebugOverlayCollisionMask;
bool DebugOverlaySprites; bool DebugOverlaySprites;
bool DebugOverlaySounds;
}; };
struct ControlRef struct ControlRef

View File

@ -111,4 +111,6 @@ constexpr const char* PlatformDataPaths[2] =
#endif #endif
}; };
constexpr float Pi = 3.14159265358979323846f;
#endif //PCH_H #endif //PCH_H

View File

@ -48,6 +48,40 @@ vector2i proj::xform_to_2d(const vector2& vec)
return xform_to_2d(vec3); return xform_to_2d(vec3);
} }
vector3 proj::ReverseXForm(const vector2i& vec)
{
// Pinball perspective projection matrix, the same for all tables resolutions:
// X: 1.000000 Y: 0.000000 Z: 0.000000 W: 0.000000
// X: 0.000000 Y: -0.913545 Z: 0.406737 W: 3.791398
// X: 0.000000 Y: -0.406737 Z: -0.913545 W: 24.675402
// X: 0.000000 Y: 0.000000 Z: 0.000000 W: 1.000000
// Let A = -0.913545, B = 0.406737, F = 3.791398, G = 24.675402
// Then forward projection can be expressed as:
// x1 = x0
// y1 = y0 * A + z0 * B + F
// z1 = -y0 * B + z0 * A + G
// x2 = x1 / z1 = x0 / z1
// y2 = y1 / z1
// z2 = z1 / z1 = 1
// Reverse projection: find x0, y0, z0 given x2 and y2
// y0 from y2 and z0, based on substitution in y2 = y1 / z1
// y0 = (y2 * (A * z0 + G) - B * z0 - F)/(A + B * y2)
// x0 from x2, y0 and z0, based on substitution in x2 = x0 / z1
// x0 = (x2 * (A * z0 - B * y0 + G)
// This pair of equations is solvable with fixed z0; most commonly z0 = 0
// PB projection also includes scaling and offset, this can be undone before the main calculations
// x2 = x0 * D / z1 + cX
// x0 = ((x2 - cX) / D) * z1
const auto A = matrix.Row1.Y, B = matrix.Row1.Z, F = matrix.Row1.W, G = matrix.Row2.W, D = d_;
const auto x2 = (vec.X - centerx) /D, y2 = (vec.Y - centery)/D, z0 = 0.0f;
auto y0 = (y2 * (A * z0 + G) - B * z0 - F) / (A + B * y2);
auto x0 = x2 * (A * z0 - B * y0 + G);
return {x0, y0, z0};
}
vector2i proj::xform_to_2d(const vector3& vec) vector2i proj::xform_to_2d(const vector3& vec)
{ {
float projCoef; float projCoef;

View File

@ -26,6 +26,7 @@ public:
static float z_distance(const vector3& vec); static float z_distance(const vector3& vec);
static vector2i xform_to_2d(const vector3& vec); static vector2i xform_to_2d(const vector3& vec);
static vector2i xform_to_2d(const vector2& vec); static vector2i xform_to_2d(const vector2& vec);
static vector3 ReverseXForm(const vector2i& vec);
static void recenter(float centerX, float centerY); static void recenter(float centerX, float centerY);
private: private:
static mat4_row_major matrix; static mat4_row_major matrix;

View File

@ -608,6 +608,8 @@ void winmain::RenderUi()
Options.DebugOverlayBallPosition ^= true; Options.DebugOverlayBallPosition ^= true;
if (ImGui::MenuItem("Ball Box Edges", nullptr, Options.DebugOverlayBallEdges)) if (ImGui::MenuItem("Ball Box Edges", nullptr, Options.DebugOverlayBallEdges))
Options.DebugOverlayBallEdges ^= true; Options.DebugOverlayBallEdges ^= true;
if (ImGui::MenuItem("Sound Positions", nullptr, Options.DebugOverlaySounds))
Options.DebugOverlaySounds ^= true;
if (ImGui::MenuItem("Apply Collision Mask", nullptr, Options.DebugOverlayCollisionMask)) if (ImGui::MenuItem("Apply Collision Mask", nullptr, Options.DebugOverlayCollisionMask))
Options.DebugOverlayCollisionMask ^= true; Options.DebugOverlayCollisionMask ^= true;
ImGui::EndMenu(); ImGui::EndMenu();