diff --git a/SpaceCadetPinball/DebugOverlay.cpp b/SpaceCadetPinball/DebugOverlay.cpp index e06a6e4..0898ee7 100644 --- a/SpaceCadetPinball/DebugOverlay.cpp +++ b/SpaceCadetPinball/DebugOverlay.cpp @@ -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 }; diff --git a/SpaceCadetPinball/DebugOverlay.h b/SpaceCadetPinball/DebugOverlay.h index 56672c7..ca1d523 100644 --- a/SpaceCadetPinball/DebugOverlay.h +++ b/SpaceCadetPinball/DebugOverlay.h @@ -20,4 +20,5 @@ private: static void DrawAllEdges(); static void DrawBallInfo(); static void DrawAllSprites(); + static void DrawSoundPositions(); }; \ No newline at end of file diff --git a/SpaceCadetPinball/Sound.cpp b/SpaceCadetPinball/Sound.cpp index 1ef4e87..033793f 100644 --- a/SpaceCadetPinball/Sound.cpp +++ b/SpaceCadetPinball/Sound.cpp @@ -5,7 +5,7 @@ int Sound::num_channels; bool Sound::enabled_flag = false; -int* Sound::TimeStamps = nullptr; +std::vector 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(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(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); } diff --git a/SpaceCadetPinball/Sound.h b/SpaceCadetPinball/Sound.h index 000c503..97ddf9f 100644 --- a/SpaceCadetPinball/Sound.h +++ b/SpaceCadetPinball/Sound.h @@ -1,10 +1,18 @@ #pragma once +#include "maths.h" #include "TPinballComponent.h" +struct ChannelInfo +{ + int TimeStamp; + vector2 Position; +}; class Sound { public: + static std::vector 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; }; diff --git a/SpaceCadetPinball/TBall.cpp b/SpaceCadetPinball/TBall.cpp index d53dc1f..a7fad97 100644 --- a/SpaceCadetPinball/TBall.cpp +++ b/SpaceCadetPinball/TBall.cpp @@ -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); } diff --git a/SpaceCadetPinball/TEdgeManager.cpp b/SpaceCadetPinball/TEdgeManager.cpp index fedc6d4..8eeaa16 100644 --- a/SpaceCadetPinball/TEdgeManager.cpp +++ b/SpaceCadetPinball/TEdgeManager.cpp @@ -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(MaxBoxX); AdvanceY = height / static_cast(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(floor((x - X) * AdvanceXInv)), MaxBoxX - 1)); + return std::max(0, std::min(static_cast(floor((x - MinX) / AdvanceX)), MaxBoxX - 1)); } int TEdgeManager::box_y(float y) { - return std::max(0, std::min(static_cast(floor((y - Y) * AdvanceYInv)), MaxBoxY - 1)); + return std::max(0, std::min(static_cast(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 }; +} diff --git a/SpaceCadetPinball/TEdgeManager.h b/SpaceCadetPinball/TEdgeManager.h index cb5a3f6..2d17a7f 100644 --- a/SpaceCadetPinball/TEdgeManager.h +++ b/SpaceCadetPinball/TEdgeManager.h @@ -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]{}; }; diff --git a/SpaceCadetPinball/TLine.cpp b/SpaceCadetPinball/TLine.cpp index bb3e35e..625b28f 100644 --- a/SpaceCadetPinball/TLine.cpp +++ b/SpaceCadetPinball/TLine.cpp @@ -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 diff --git a/SpaceCadetPinball/TPinballComponent.cpp b/SpaceCadetPinball/TPinballComponent.cpp index 88d27a6..64664e2 100644 --- a/SpaceCadetPinball/TPinballComponent.cpp +++ b/SpaceCadetPinball/TPinballComponent.cpp @@ -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; diff --git a/SpaceCadetPinball/TTableLayer.cpp b/SpaceCadetPinball/TTableLayer.cpp index df2b09a..bfb5d28 100644 --- a/SpaceCadetPinball/TTableLayer.cpp +++ b/SpaceCadetPinball/TTableLayer.cpp @@ -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(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(xMinBox) * edge_manager->AdvanceX + edge_manager->X; + float boxX = static_cast(xMinBox) * edge_manager->AdvanceX + edge_manager->MinX; for (int indexX = xMinBox; indexX <= xMaxBox; ++indexX) { - float boxY = static_cast(yMinBox) * edge_manager->AdvanceY + edge_manager->Y; + float boxY = static_cast(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(dirX) * edge_manager->AdvanceX + edge_manager->X; + vec1.X = static_cast(dirX) * edge_manager->AdvanceX + edge_manager->MinX; for (auto indexX = dirX; indexX <= xMaxBox; ++indexX) { - vec1.Y = static_cast(dirY) * edge_manager->AdvanceY + edge_manager->Y; + vec1.Y = static_cast(dirY) * edge_manager->AdvanceY + edge_manager->MinY; for (int indexY = dirY; indexY <= yMaxBox; ++indexY) { auto vec1XAdv = vec1.X + edge_manager->AdvanceX; diff --git a/SpaceCadetPinball/TTableLayer.h b/SpaceCadetPinball/TTableLayer.h index d5ee6b6..ba03c7c 100644 --- a/SpaceCadetPinball/TTableLayer.h +++ b/SpaceCadetPinball/TTableLayer.h @@ -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; diff --git a/SpaceCadetPinball/maths.cpp b/SpaceCadetPinball/maths.cpp index e553760..fe3eaad 100644 --- a/SpaceCadetPinball/maths.cpp +++ b/SpaceCadetPinball/maths.cpp @@ -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 }; diff --git a/SpaceCadetPinball/maths.h b/SpaceCadetPinball/maths.h index 226b64a..9e2e829 100644 --- a/SpaceCadetPinball/maths.h +++ b/SpaceCadetPinball/maths.h @@ -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, diff --git a/SpaceCadetPinball/options.cpp b/SpaceCadetPinball/options.cpp index 0a75a06..baa2513 100644 --- a/SpaceCadetPinball/options.cpp +++ b/SpaceCadetPinball/options.cpp @@ -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); } diff --git a/SpaceCadetPinball/options.h b/SpaceCadetPinball/options.h index 2f4eefc..2308cd8 100644 --- a/SpaceCadetPinball/options.h +++ b/SpaceCadetPinball/options.h @@ -89,6 +89,7 @@ struct optionsStruct bool DebugOverlayBallEdges; bool DebugOverlayCollisionMask; bool DebugOverlaySprites; + bool DebugOverlaySounds; }; struct ControlRef diff --git a/SpaceCadetPinball/pch.h b/SpaceCadetPinball/pch.h index 7d7cb2e..e745878 100644 --- a/SpaceCadetPinball/pch.h +++ b/SpaceCadetPinball/pch.h @@ -111,4 +111,6 @@ constexpr const char* PlatformDataPaths[2] = #endif }; +constexpr float Pi = 3.14159265358979323846f; + #endif //PCH_H diff --git a/SpaceCadetPinball/proj.cpp b/SpaceCadetPinball/proj.cpp index 7491591..f8f4661 100644 --- a/SpaceCadetPinball/proj.cpp +++ b/SpaceCadetPinball/proj.cpp @@ -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; diff --git a/SpaceCadetPinball/proj.h b/SpaceCadetPinball/proj.h index d8cb728..4e57b7f 100644 --- a/SpaceCadetPinball/proj.h +++ b/SpaceCadetPinball/proj.h @@ -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; diff --git a/SpaceCadetPinball/winmain.cpp b/SpaceCadetPinball/winmain.cpp index 44c0e1c..57792ab 100644 --- a/SpaceCadetPinball/winmain.cpp +++ b/SpaceCadetPinball/winmain.cpp @@ -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();