mirror of
https://github.com/k4zmu2a/SpaceCadetPinball.git
synced 2024-11-17 15:20:17 +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 "render.h"
|
||||
#include "options.h"
|
||||
#include "Sound.h"
|
||||
|
||||
|
||||
gdrv_bitmap8* DebugOverlay::dbScreen = nullptr;
|
||||
|
@ -109,10 +110,14 @@ void DebugOverlay::DrawOverlay()
|
|||
if (options::Options.DebugOverlayAllEdges)
|
||||
DrawAllEdges();
|
||||
|
||||
// Draw ball collision
|
||||
// Draw ball collision info
|
||||
if (options::Options.DebugOverlayBallPosition || options::Options.DebugOverlayBallEdges)
|
||||
DrawBallInfo();
|
||||
|
||||
// Draw positions associated with currently playing sound channels
|
||||
if (options::Options.DebugOverlaySounds)
|
||||
DrawSoundPositions();
|
||||
|
||||
// Restore render target
|
||||
SDL_SetRenderTarget(winmain::Renderer, initialRenderTarget);
|
||||
SDL_SetRenderDrawColor(winmain::Renderer,
|
||||
|
@ -133,18 +138,18 @@ void DebugOverlay::DrawBoxGrid()
|
|||
SDL_SetRenderDrawColor(winmain::Renderer, 0, 255, 0, 255);
|
||||
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);
|
||||
boxPt.Y = edgeMan.MaxBoxY * edgeMan.AdvanceY + edgeMan.Y;
|
||||
boxPt.Y = edgeMan.MaxBoxY * edgeMan.AdvanceY + edgeMan.MinY;
|
||||
auto pt2 = proj::xform_to_2d(boxPt);
|
||||
|
||||
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.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);
|
||||
boxPt.X = edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.X;
|
||||
boxPt.X = edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.MinX;
|
||||
auto pt2 = proj::xform_to_2d(boxPt);
|
||||
|
||||
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)
|
||||
{
|
||||
vector2 linePt{ circle.Center.X + sqrt(circle.RadiusSq), circle.Center.Y };
|
||||
|
|
|
@ -20,4 +20,5 @@ private:
|
|||
static void DrawAllEdges();
|
||||
static void DrawBallInfo();
|
||||
static void DrawAllSprites();
|
||||
static void DrawSoundPositions();
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
int Sound::num_channels;
|
||||
bool Sound::enabled_flag = false;
|
||||
int* Sound::TimeStamps = nullptr;
|
||||
std::vector<ChannelInfo> Sound::Channels{};
|
||||
int Sound::Volume = MIX_MAX_VOLUME;
|
||||
|
||||
bool Sound::Init(int channels, bool enableFlag, int volume)
|
||||
|
@ -37,106 +37,79 @@ void Sound::Deactivate()
|
|||
|
||||
void Sound::Close()
|
||||
{
|
||||
delete[] TimeStamps;
|
||||
TimeStamps = nullptr;
|
||||
Channels.clear();
|
||||
Mix_CloseAudio();
|
||||
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 (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);
|
||||
}
|
||||
|
||||
auto channel = Mix_PlayChannel(-1, wavePtr, 0);
|
||||
if (channel != -1) {
|
||||
TimeStamps[channel] = time;
|
||||
if (options::Options.SoundStereo) {
|
||||
/* Think 3D sound positioning, where:
|
||||
* - x goes from 0 to 1, left to right on the screen,
|
||||
* - y goes from 0 to 1, top to bottom on the screen,
|
||||
* - z goes from 0 to infinity, from table-level to the sky.
|
||||
*
|
||||
* 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).
|
||||
*/
|
||||
if (channel != -1)
|
||||
{
|
||||
Channels[channel].TimeStamp = time;
|
||||
if (options::Options.SoundStereo)
|
||||
{
|
||||
// Positional audio uses collision grid 2D coordinates normalized to [0, 1]
|
||||
// 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.
|
||||
|
||||
/* Get the sound source position. */
|
||||
vector2 coordinates;
|
||||
/* Some sounds are unpositioned; for that case the caller sends
|
||||
* a NULL pointer as a soundSource; in those cases we position
|
||||
* the sound at the center top of the table.
|
||||
*/
|
||||
if (!soundSource) {
|
||||
coordinates.X = 0.5f;
|
||||
coordinates.Y = 0.0f;
|
||||
// Get the source sound position.
|
||||
// Sound without position are assumed to be at the center top of the table.
|
||||
vector3 soundPos{};
|
||||
if (soundSource)
|
||||
{
|
||||
auto soundPos2D = soundSource->get_coordinates();
|
||||
soundPos = {soundPos2D.X, soundPos2D.Y, 0.0f};
|
||||
}
|
||||
else {
|
||||
coordinates = soundSource->get_coordinates();
|
||||
};
|
||||
|
||||
/* 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);
|
||||
else
|
||||
{
|
||||
soundPos = {0.5f, 1.0f, 0.0f};
|
||||
}
|
||||
Channels[channel].Position = soundPos;
|
||||
|
||||
/* Distance from listener to the ball (ptos) is roughly
|
||||
* in the [0.5,1.55] range; remap to 50-155 by multiplying
|
||||
* by 100 and cast to an integer. */
|
||||
Uint8 distance = (Uint8)(100.0f * ptos);
|
||||
Mix_SetPosition(channel, angle, distance);
|
||||
// Listener is positioned at the bottom center of the table,
|
||||
// at 0.5 height, so roughly a table half - length.
|
||||
vector3 playerPos = {0.5f, 0.0f, 0.5f};
|
||||
auto soundDir = maths::vector_sub(soundPos, playerPos);
|
||||
|
||||
/* Output position of each sound emitted so we can verify
|
||||
* the sanity of the implementation.
|
||||
*/
|
||||
/*
|
||||
printf("X: %3.3f Y: %3.3f Angle: %3d Distance: %3d, Object: %s\n",
|
||||
coordinates.X,
|
||||
coordinates.Y,
|
||||
angle,
|
||||
distance,
|
||||
info
|
||||
);
|
||||
*/
|
||||
// Find sound angle from positive Y axis in clockwise direction with atan2
|
||||
// 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;
|
||||
auto angleSdl = static_cast<Sint16>(angleDeg);
|
||||
|
||||
// Distance from listener to the sound position is roughly in the [0, ~1.22] range.
|
||||
// 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,
|
||||
info
|
||||
);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,8 +137,7 @@ void Sound::SetChannels(int channels)
|
|||
channels = 8;
|
||||
|
||||
num_channels = channels;
|
||||
delete[] TimeStamps;
|
||||
TimeStamps = new int[num_channels]();
|
||||
Channels.resize(num_channels);
|
||||
Mix_AllocateChannels(num_channels);
|
||||
SetVolume(Volume);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
#pragma once
|
||||
#include "maths.h"
|
||||
#include "TPinballComponent.h"
|
||||
|
||||
struct ChannelInfo
|
||||
{
|
||||
int TimeStamp;
|
||||
vector2 Position;
|
||||
};
|
||||
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
static std::vector<ChannelInfo> Channels;
|
||||
|
||||
static bool Init(int channels, bool enableFlag, int volume);
|
||||
static void Enable(bool enableFlag);
|
||||
static void Activate();
|
||||
|
@ -18,6 +26,5 @@ public:
|
|||
private:
|
||||
static int num_channels;
|
||||
static bool enabled_flag;
|
||||
static int* TimeStamps;
|
||||
static int Volume;
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "proj.h"
|
||||
#include "render.h"
|
||||
#include "TPinballTable.h"
|
||||
#include "TTableLayer.h"
|
||||
|
||||
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 coordinates;
|
||||
vector2i pos2D = proj::xform_to_2d(Position);
|
||||
coordinates.X = (float)pos2D.X / PinballTable->Width;
|
||||
coordinates.Y = (float)pos2D.Y / PinballTable->Height;
|
||||
return coordinates;
|
||||
return TTableLayer::edge_manager->NormalizeBox(Position);
|
||||
}
|
||||
|
|
|
@ -8,16 +8,18 @@
|
|||
#include "TEdgeSegment.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;
|
||||
Y = posY;
|
||||
Width = width;
|
||||
Height = height;
|
||||
MinX = xMin;
|
||||
MinY = yMin;
|
||||
MaxX = MinX + width;
|
||||
MaxY = MinY + height;
|
||||
MaxBoxX = 10;
|
||||
MaxBoxY = 15;
|
||||
AdvanceX = width / static_cast<float>(MaxBoxX);
|
||||
AdvanceY = height / static_cast<float>(MaxBoxY);
|
||||
AdvanceXInv = 1.0f / AdvanceX;
|
||||
AdvanceYInv = 1.0f / AdvanceY;
|
||||
BoxArray = new TEdgeBox[MaxBoxX * MaxBoxY];
|
||||
}
|
||||
|
||||
|
@ -28,12 +30,12 @@ TEdgeManager::~TEdgeManager()
|
|||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -180,8 +182,8 @@ float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegme
|
|||
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
|
||||
{
|
||||
// Calculate y from indexY and from line formula
|
||||
auto yDiscrete = (indexY + yBias) * AdvanceY + Y;
|
||||
auto ylinear = ((indexX + xBias) * AdvanceX + X) * dyDx + precomp;
|
||||
auto yDiscrete = (indexY + yBias) * AdvanceY + MinY;
|
||||
auto ylinear = ((indexX + xBias) * AdvanceX + MinX) * dyDx + precomp;
|
||||
if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete)
|
||||
{
|
||||
// Advance indexY when discrete value is ahead/behind
|
||||
|
@ -207,3 +209,23 @@ float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegme
|
|||
|
||||
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
|
||||
{
|
||||
public:
|
||||
TEdgeManager(float posX, float posY, float width, float height);
|
||||
TEdgeManager(float xMin, float yMin, float width, float height);
|
||||
~TEdgeManager();
|
||||
void FieldEffects(TBall* ball, struct vector2* dstVec);
|
||||
int box_x(float x);
|
||||
|
@ -25,15 +25,19 @@ public:
|
|||
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);
|
||||
float FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegment** edge);
|
||||
vector2 NormalizeBox(vector2 pt) const;
|
||||
vector2 DeNormalizeBox(vector2 pt) const;
|
||||
|
||||
float AdvanceX;
|
||||
float AdvanceY;
|
||||
float AdvanceXInv;
|
||||
float AdvanceYInv;
|
||||
int MaxBoxX;
|
||||
int MaxBoxY;
|
||||
float X;
|
||||
float Y;
|
||||
float MinX;
|
||||
float MinY;
|
||||
float MaxX;
|
||||
float MaxY;
|
||||
float Width;
|
||||
float Height;
|
||||
TEdgeBox* BoxArray;
|
||||
TEdgeSegment* EdgeArray[1000]{};
|
||||
};
|
||||
|
|
|
@ -102,8 +102,8 @@ void TLine::place_in_grid()
|
|||
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
|
||||
{
|
||||
// Calculate y from indexY and from line formula
|
||||
auto yDiscrete = (indexY + yBias) * edgeMan->AdvanceY + edgeMan->Y;
|
||||
auto ylinear = ((indexX + xBias) * edgeMan->AdvanceX + edgeMan->X) * dyDx + precomp;
|
||||
auto yDiscrete = (indexY + yBias) * edgeMan->AdvanceY + edgeMan->MinY;
|
||||
auto ylinear = ((indexX + xBias) * edgeMan->AdvanceX + edgeMan->MinX) * dyDx + precomp;
|
||||
if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete)
|
||||
{
|
||||
// Advance indexY when discrete value is ahead/behind
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#include "pch.h"
|
||||
#include "TPinballComponent.h"
|
||||
#include "loader.h"
|
||||
#include "proj.h"
|
||||
#include "render.h"
|
||||
#include "TPinballTable.h"
|
||||
#include "TTableLayer.h"
|
||||
|
||||
TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals)
|
||||
{
|
||||
|
@ -72,9 +74,13 @@ TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool
|
|||
rootBmp->YPosition - table->YOffset,
|
||||
&bmp1Rect);
|
||||
|
||||
// Sound position = center of root visual, reverse-projected, normalized.
|
||||
auto& rect = RenderSprite->BmpRect;
|
||||
VisualPosNormX = (rect.XPosition + (rect.Width / 2.0f)) / PinballTable->Width;
|
||||
VisualPosNormY = (rect.YPosition + (rect.Height / 2.0f)) / PinballTable->Height;
|
||||
vector2i pos2D{ rect.XPosition + rect.Width / 2, rect.YPosition + rect.Height / 2 };
|
||||
auto pos3D = proj::ReverseXForm(pos2D);
|
||||
auto posNorm = TTableLayer::edge_manager->NormalizeBox(pos3D);
|
||||
VisualPosNormX = posNorm.X;
|
||||
VisualPosNormY = posNorm.Y;
|
||||
}
|
||||
}
|
||||
GroupIndex = groupIndex;
|
||||
|
|
|
@ -71,37 +71,33 @@ TTableLayer::TTableLayer(TPinballTable* table): TCollisionComponent(table, -1, f
|
|||
Threshold = visual.Kicker.Threshold;
|
||||
Boost = 15.0f;
|
||||
|
||||
auto visArrPtr = visual.FloatArr;
|
||||
Unknown1F = std::min(visArrPtr[0], std::min(visArrPtr[2], visArrPtr[4]));
|
||||
Unknown2F = std::min(visArrPtr[1], std::min(visArrPtr[3], visArrPtr[5]));
|
||||
Unknown3F = std::max(visArrPtr[0], std::max(visArrPtr[2], visArrPtr[4]));
|
||||
Unknown4F = std::max(visArrPtr[1], std::max(visArrPtr[3], visArrPtr[5]));
|
||||
auto a2 = Unknown4F - Unknown2F;
|
||||
auto a1 = Unknown3F - Unknown1F;
|
||||
edge_manager = new TEdgeManager(Unknown1F, Unknown2F, a1, a2);
|
||||
auto edgePoints = reinterpret_cast<vector2*>(visual.FloatArr);
|
||||
XMin = std::min(edgePoints[0].X, std::min(edgePoints[1].X, edgePoints[2].X));
|
||||
YMin = std::min(edgePoints[0].Y, std::min(edgePoints[1].Y, edgePoints[2].Y));
|
||||
XMax = std::max(edgePoints[0].X, std::max(edgePoints[1].X, edgePoints[2].X));
|
||||
YMax = std::max(edgePoints[0].Y, std::max(edgePoints[1].Y, edgePoints[2].Y));
|
||||
|
||||
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,
|
||||
&ActiveFlag,
|
||||
visual.CollisionGroup,
|
||||
visArrPtr[2],
|
||||
visArrPtr[3],
|
||||
visArrPtr[0],
|
||||
visArrPtr[1]);
|
||||
if (line)
|
||||
{
|
||||
line->place_in_grid();
|
||||
EdgeList.push_back(line);
|
||||
}
|
||||
|
||||
visArrPtr += 2;
|
||||
edgePoints[i + 1].X,
|
||||
edgePoints[i + 1].Y,
|
||||
edgePoints[i].X,
|
||||
edgePoints[i].Y);
|
||||
line->place_in_grid();
|
||||
EdgeList.push_back(line);
|
||||
}
|
||||
|
||||
Field.CollisionGroup = -1;
|
||||
Field.ActiveFlag = &ActiveFlag;
|
||||
Field.CollisionComp = this;
|
||||
edges_insert_square(Unknown2F, Unknown1F, Unknown4F, Unknown3F, nullptr,
|
||||
edges_insert_square(YMin, XMin, YMax, XMax, nullptr,
|
||||
&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 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
gdrv_bitmap8* VisBmp;
|
||||
float Unknown1F;
|
||||
float Unknown2F;
|
||||
float Unknown3F;
|
||||
float Unknown4F;
|
||||
float XMin;
|
||||
float YMin;
|
||||
float XMax;
|
||||
float YMax;
|
||||
float GraityDirX;
|
||||
float GraityDirY;
|
||||
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 };
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return { vec1.X * val, vec1.Y * val };
|
||||
|
|
|
@ -108,6 +108,7 @@ public:
|
|||
static float magnitude(const vector3& vec);
|
||||
static void vector_add(vector2& vec1Dst, 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 float basic_collision(TBall* ball, vector2* nextPosition, vector2* direction, float elasticity,
|
||||
float smoothness,
|
||||
|
|
|
@ -112,6 +112,7 @@ void options::InitPrimary()
|
|||
Options.DebugOverlayBallEdges = get_int("Debug Overlay Ball Edges", true);
|
||||
Options.DebugOverlayCollisionMask = get_int("Debug Overlay Collision Mask", true);
|
||||
Options.DebugOverlaySprites = get_int("Debug Overlay Sprites", true);
|
||||
Options.DebugOverlaySounds = get_int("Debug Overlay Sounds", true);
|
||||
}
|
||||
|
||||
void options::InitSecondary()
|
||||
|
@ -159,6 +160,7 @@ void options::uninit()
|
|||
set_int("Debug Overlay Ball Edges", Options.DebugOverlayBallEdges);
|
||||
set_int("Debug Overlay Collision Mask", Options.DebugOverlayCollisionMask);
|
||||
set_int("Debug Overlay Sprites", Options.DebugOverlaySprites);
|
||||
get_int("Debug Overlay Sounds", Options.DebugOverlaySounds);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ struct optionsStruct
|
|||
bool DebugOverlayBallEdges;
|
||||
bool DebugOverlayCollisionMask;
|
||||
bool DebugOverlaySprites;
|
||||
bool DebugOverlaySounds;
|
||||
};
|
||||
|
||||
struct ControlRef
|
||||
|
|
|
@ -111,4 +111,6 @@ constexpr const char* PlatformDataPaths[2] =
|
|||
#endif
|
||||
};
|
||||
|
||||
constexpr float Pi = 3.14159265358979323846f;
|
||||
|
||||
#endif //PCH_H
|
||||
|
|
|
@ -48,6 +48,40 @@ vector2i proj::xform_to_2d(const vector2& vec)
|
|||
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)
|
||||
{
|
||||
float projCoef;
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
static float z_distance(const vector3& vec);
|
||||
static vector2i xform_to_2d(const vector3& vec);
|
||||
static vector2i xform_to_2d(const vector2& vec);
|
||||
static vector3 ReverseXForm(const vector2i& vec);
|
||||
static void recenter(float centerX, float centerY);
|
||||
private:
|
||||
static mat4_row_major matrix;
|
||||
|
|
|
@ -608,6 +608,8 @@ void winmain::RenderUi()
|
|||
Options.DebugOverlayBallPosition ^= true;
|
||||
if (ImGui::MenuItem("Ball Box Edges", nullptr, Options.DebugOverlayBallEdges))
|
||||
Options.DebugOverlayBallEdges ^= true;
|
||||
if (ImGui::MenuItem("Sound Positions", nullptr, Options.DebugOverlaySounds))
|
||||
Options.DebugOverlaySounds ^= true;
|
||||
if (ImGui::MenuItem("Apply Collision Mask", nullptr, Options.DebugOverlayCollisionMask))
|
||||
Options.DebugOverlayCollisionMask ^= true;
|
||||
ImGui::EndMenu();
|
||||
|
|
Loading…
Reference in a new issue