mirror of
https://github.com/k4zmu2a/SpaceCadetPinball.git
synced 2024-12-18 10:37:53 +01:00
Switched positional audio to collision coordinate system.
Refactored positional audio.
This commit is contained in:
parent
c93e11ee6b
commit
8017734de4
19 changed files with 214 additions and 143 deletions
|
@ -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 };
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
|
@ -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,8 +37,7 @@ void Sound::Deactivate()
|
||||||
|
|
||||||
void Sound::Close()
|
void Sound::Close()
|
||||||
{
|
{
|
||||||
delete[] TimeStamps;
|
Channels.clear();
|
||||||
TimeStamps = nullptr;
|
|
||||||
Mix_CloseAudio();
|
Mix_CloseAudio();
|
||||||
Mix_Quit();
|
Mix_Quit();
|
||||||
}
|
}
|
||||||
|
@ -49,94 +48,68 @@ void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent *soundSour
|
||||||
{
|
{
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
|
|
@ -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]{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ struct optionsStruct
|
||||||
bool DebugOverlayBallEdges;
|
bool DebugOverlayBallEdges;
|
||||||
bool DebugOverlayCollisionMask;
|
bool DebugOverlayCollisionMask;
|
||||||
bool DebugOverlaySprites;
|
bool DebugOverlaySprites;
|
||||||
|
bool DebugOverlaySounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ControlRef
|
struct ControlRef
|
||||||
|
|
|
@ -111,4 +111,6 @@ constexpr const char* PlatformDataPaths[2] =
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr float Pi = 3.14159265358979323846f;
|
||||||
|
|
||||||
#endif //PCH_H
|
#endif //PCH_H
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue