Added debug overlay v1.

It features various collision info perspective projected and overlayed on the table.
This commit is contained in:
Muzychenko Andrey 2022-05-19 14:17:31 +03:00
parent 0cb75ecf7f
commit 5461483bb5
15 changed files with 388 additions and 29 deletions

View file

@ -170,6 +170,8 @@ set(SOURCE_FILES
SpaceCadetPinball/imstb_textedit.h
SpaceCadetPinball/imstb_rectpack.h
SpaceCadetPinball/imstb_truetype.h
SpaceCadetPinball/DebugOverlay.cpp
SpaceCadetPinball/DebugOverlay.h
)
# On Windows, include resource file with the icon

View file

@ -0,0 +1,255 @@
#include "pch.h"
#include "DebugOverlay.h"
#include "maths.h"
#include "proj.h"
#include "winmain.h"
#include "TFlipperEdge.h"
#include "TFlipper.h"
#include "pb.h"
#include "TLine.h"
#include "TCircle.h"
#include "TPinballTable.h"
#include "TEdgeBox.h"
#include "TTableLayer.h"
#include "TBall.h"
#include "render.h"
#include "options.h"
gdrv_bitmap8* DebugOverlay::dbScreen = nullptr;
int SDL_RenderDrawCircle(SDL_Renderer* renderer, int x, int y, int radius)
{
int offsetx, offsety, d;
int status;
offsetx = 0;
offsety = radius;
d = radius - 1;
status = 0;
while (offsety >= offsetx) {
status += SDL_RenderDrawPoint(renderer, x + offsetx, y + offsety);
status += SDL_RenderDrawPoint(renderer, x + offsety, y + offsetx);
status += SDL_RenderDrawPoint(renderer, x - offsetx, y + offsety);
status += SDL_RenderDrawPoint(renderer, x - offsety, y + offsetx);
status += SDL_RenderDrawPoint(renderer, x + offsetx, y - offsety);
status += SDL_RenderDrawPoint(renderer, x + offsety, y - offsetx);
status += SDL_RenderDrawPoint(renderer, x - offsetx, y - offsety);
status += SDL_RenderDrawPoint(renderer, x - offsety, y - offsetx);
if (status < 0) {
status = -1;
break;
}
if (d >= 2 * offsetx) {
d -= 2 * offsetx + 1;
offsetx += 1;
}
else if (d < 2 * (radius - offsety)) {
d += 2 * offsety - 1;
offsety -= 1;
}
else {
d += 2 * (offsety - offsetx - 1);
offsety -= 1;
offsetx += 1;
}
}
return status;
}
void DebugOverlay::UnInit()
{
delete dbScreen;
dbScreen = nullptr;
}
void DebugOverlay::DrawOverlay()
{
if (dbScreen == nullptr)
{
dbScreen = new gdrv_bitmap8(render::vscreen->Width, render::vscreen->Height, false, false);
dbScreen->CreateTexture("nearest", SDL_TEXTUREACCESS_TARGET);
SDL_SetTextureBlendMode(dbScreen->Texture, SDL_BLENDMODE_BLEND);
}
// Setup overlay rendering
Uint8 initialR, initialG, initialB, initialA;
auto initialRenderTarget = SDL_GetRenderTarget(winmain::Renderer);
SDL_GetRenderDrawColor(winmain::Renderer, &initialR, &initialG, &initialB, &initialA);
SDL_SetRenderTarget(winmain::Renderer, dbScreen->Texture);
SDL_SetRenderDrawColor(winmain::Renderer, 0, 0, 0, 0);
SDL_RenderClear(winmain::Renderer);
// Draw EdgeManager box grid
if (options::Options.DebugOverlayGrid)
DrawBoxGrid();
// Draw all edges registered in TCollisionComponent.EdgeList + flippers
if (options::Options.DebugOverlayAllEdges)
DrawAllEdges();
// Draw ball collision
if (options::Options.DebugOverlayBallPosition || options::Options.DebugOverlayBallEdges)
DrawBallInfo();
// Restore render target
SDL_SetRenderTarget(winmain::Renderer, initialRenderTarget);
SDL_SetRenderDrawColor(winmain::Renderer,
initialR, initialG, initialB, initialA);
// Copy overlay with alpha blending
SDL_BlendMode blendMode;
SDL_GetRenderDrawBlendMode(winmain::Renderer, &blendMode);
SDL_SetRenderDrawBlendMode(winmain::Renderer, SDL_BLENDMODE_BLEND);
SDL_RenderCopy(winmain::Renderer, dbScreen->Texture, nullptr, &render::DestinationRect);
SDL_SetRenderDrawBlendMode(winmain::Renderer, blendMode);
}
void DebugOverlay::DrawBoxGrid()
{
auto& edgeMan = *TTableLayer::edge_manager;
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 };
auto pt1 = proj::xform_to_2d(boxPt);
boxPt.Y = edgeMan.MaxBoxY * edgeMan.AdvanceY + edgeMan.Y;
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 };
auto pt1 = proj::xform_to_2d(boxPt);
boxPt.X = edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.X;
auto pt2 = proj::xform_to_2d(boxPt);
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
}
}
void DebugOverlay::DrawAllEdges()
{
SDL_SetRenderDrawColor(winmain::Renderer, 0, 200, 200, 255);
for (auto cmp : pb::MainTable->ComponentList)
{
auto collCmp = dynamic_cast<TCollisionComponent*>(cmp);
if (collCmp)
{
for (auto edge : collCmp->EdgeList)
{
DrawEdge(edge);
}
}
auto flip = dynamic_cast<TFlipper*>(cmp);
if (flip)
{
DrawEdge(flip->FlipperEdge);
}
}
}
void DebugOverlay::DrawBallInfo()
{
auto& edgeMan = *TTableLayer::edge_manager;
for (auto ball : pb::MainTable->BallList)
{
if (ball->ActiveFlag)
{
vector2 ballPosition = { ball->Position.X, ball->Position.Y };
if (options::Options.DebugOverlayBallEdges)
{
SDL_SetRenderDrawColor(winmain::Renderer, 255, 0, 0, 255);
auto x = edgeMan.box_x(ballPosition.X), y = edgeMan.box_y(ballPosition.Y);
auto& box = edgeMan.BoxArray[x + y * edgeMan.MaxBoxX];
for (auto edge : box.EdgeList)
{
DrawEdge(edge);
}
}
if (options::Options.DebugOverlayBallPosition)
{
SDL_SetRenderDrawColor(winmain::Renderer, 0, 0, 255, 255);
auto pt1 = proj::xform_to_2d(ballPosition);
SDL_RenderDrawCircle(winmain::Renderer, pt1.X, pt1.Y, 10);
auto nextPos = ballPosition;
maths::vector_add(nextPos, maths::vector_mul(ball->Acceleration, ball->Speed / 10.0f));
auto pt2 = proj::xform_to_2d(nextPos);
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
}
}
}
}
void DebugOverlay::DrawCicleType(circle_type& circle)
{
vector2 linePt{ circle.Center.X + sqrt(circle.RadiusSq), circle.Center.Y };
auto pt1 = proj::xform_to_2d(circle.Center);
auto pt2 = proj::xform_to_2d(linePt);
auto radius = abs(pt2.X - pt1.X);
SDL_RenderDrawCircle(winmain::Renderer, pt1.X, pt1.Y, radius);
}
void DebugOverlay::DrawLineType(line_type& line)
{
auto pt1 = proj::xform_to_2d(line.Origin);
auto pt2 = proj::xform_to_2d(line.End);
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
}
void DebugOverlay::DrawEdge(TEdgeSegment* edge)
{
if (options::Options.DebugOverlayCollisionMask)
{
TBall* refBall = nullptr;
for (auto ball : pb::MainTable->BallList)
{
if (ball->ActiveFlag)
{
refBall = ball;
break;
}
}
if (refBall != nullptr && (refBall->FieldFlag & edge->CollisionGroup) == 0)
return;
}
auto line = dynamic_cast<TLine*>(edge);
if (line)
{
DrawLineType(line->Line);
return;
}
auto circle = dynamic_cast<TCircle*>(edge);
if (circle)
{
DrawCicleType(circle->Circle);
return;
}
auto flip = dynamic_cast<TFlipperEdge*>(edge);
if (flip)
{
flip->set_control_points(pb::time_now);
flip->build_edges_in_motion();
DrawLineType(TFlipperEdge::lineA);
DrawLineType(TFlipperEdge::lineB);
DrawCicleType(TFlipperEdge::circlebase);
DrawCicleType(TFlipperEdge::circleT1);
}
}

View file

@ -0,0 +1,22 @@
#pragma once
struct gdrv_bitmap8;
struct circle_type;
struct line_type;
class TEdgeSegment;
class DebugOverlay
{
public:
static void UnInit();
static void DrawOverlay();
private:
static gdrv_bitmap8* dbScreen;
static void DrawCicleType(circle_type& circle);
static void DrawLineType(line_type& line);
static void DrawEdge(TEdgeSegment* edge);
static void DrawBoxGrid();
static void DrawAllEdges();
static void DrawBallInfo();
};

View file

@ -47,7 +47,7 @@ void GroupData::AddEntry(EntryData* entry)
if (srcBmp->BitmapType == BitmapTypes::Spliced)
{
// Get rid of spliced bitmap early on, to simplify render pipeline
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, srcBmp->Width);
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, true);
auto zMap = new zmap_header_type(srcBmp->Width, srcBmp->Height, srcBmp->Width);
SplitSplicedBitmap(*srcBmp, *bmp, *zMap);

View file

@ -50,7 +50,7 @@ TBall::TBall(TPinballTable* table) : TPinballComponent(table, -1, false)
if (ListBitmap)
ListBitmap->push_back(visual.Bitmap);
auto visVec = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, index, 501));
auto zDepth = proj::z_distance(visVec);
auto zDepth = proj::z_distance(*visVec);
VisualZArray[index] = zDepth;
}
RenderSprite = render::create_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr);
@ -60,8 +60,6 @@ TBall::TBall(TPinballTable* table) : TPinballComponent(table, -1, false)
void TBall::Repaint()
{
int pos2D[2];
if (CollisionFlag)
{
Position.Z =
@ -70,8 +68,8 @@ void TBall::Repaint()
Offset + CollisionOffset.Z;
}
proj::xform_to_2d(&Position, pos2D);
auto zDepth = proj::z_distance(&Position);
auto pos2D = proj::xform_to_2d(Position);
auto zDepth = proj::z_distance(Position);
auto zArrPtr = VisualZArray;
auto index = 0u;
@ -85,8 +83,8 @@ void TBall::Repaint()
RenderSprite,
bmp,
zDepth,
pos2D[0] - bmp->Width / 2,
pos2D[1] - bmp->Height / 2);
pos2D.X - bmp->Width / 2,
pos2D.Y - bmp->Height / 2);
}
void TBall::not_again(TEdgeSegment* edge)

View file

@ -9,7 +9,15 @@
ColorRgba gdrv::current_palette[256]{};
gdrv_bitmap8::gdrv_bitmap8(int width, int height, bool indexed)
gdrv_bitmap8::gdrv_bitmap8(int width, int height) : gdrv_bitmap8(width, height, true, true)
{
}
gdrv_bitmap8::gdrv_bitmap8(int width, int height, bool indexed) : gdrv_bitmap8(width, height, indexed, true)
{
}
gdrv_bitmap8::gdrv_bitmap8(int width, int height, bool indexed, bool bmpBuff)
{
assertm(width >= 0 && height >= 0, "Negative bitmap8 dimensions");
@ -20,13 +28,15 @@ gdrv_bitmap8::gdrv_bitmap8(int width, int height, bool indexed)
BitmapType = BitmapTypes::DibBitmap;
Texture = nullptr;
IndexedBmpPtr = nullptr;
BmpBufPtr1 = nullptr;
XPosition = 0;
YPosition = 0;
Resolution = 0;
if (indexed)
IndexedBmpPtr = new char[Height * IndexedStride];
BmpBufPtr1 = new ColorRgba[Height * Stride];
if (bmpBuff)
BmpBufPtr1 = new ColorRgba[Height * Stride];
}
gdrv_bitmap8::gdrv_bitmap8(const dat8BitBmpHeader& header)

View file

@ -47,7 +47,9 @@ static_assert(sizeof(ColorRgba) == 4, "Wrong size of RGBA color");
struct gdrv_bitmap8
{
gdrv_bitmap8(int width, int height);
gdrv_bitmap8(int width, int height, bool indexed);
gdrv_bitmap8(int width, int height, bool indexed, bool bmpBuff);
gdrv_bitmap8(const struct dat8BitBmpHeader& header);
~gdrv_bitmap8();
void ScaleIndexed(float scaleX, float scaleY);

View file

@ -132,6 +132,7 @@ float maths::normalize_2d(vector2& vec)
void maths::line_init(line_type& line, float x0, float y0, float x1, float y1)
{
line.Origin = { x0, y0 };
line.End = { x1, y1 };
line.Direction.X = x1 - x0;
line.Direction.Y = y1 - y0;
normalize_2d(line.Direction);
@ -219,6 +220,11 @@ vector2 maths::vector_sub(const vector2& vec1, const vector2& vec2)
return { vec1.X - vec2.X, vec1.Y - vec2.Y };
}
vector2 maths::vector_mul(const vector2& vec1, float val)
{
return { vec1.X * val, vec1.Y * val };
}
float maths::basic_collision(TBall* ball, vector2* nextPosition, vector2* direction, float elasticity, float smoothness,
float threshold, float boost)
{

View file

@ -19,9 +19,18 @@ struct vector2
struct vector3 :vector2
{
vector3() = default;
vector3(float x, float y) : vector3{ x, y, 0 } {}
vector3(float x, float y, float z) : vector2{ x, y }, Z(z) {}
float Z;
};
struct vector2i
{
int X;
int Y;
};
struct rectangle_type
{
int XPosition;
@ -52,6 +61,7 @@ struct line_type
vector2 PerpendicularC;
vector2 Direction;
vector2 Origin;
vector2 End;
float MinCoord;
float MaxCoord;
vector2 RayIntersect;
@ -98,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 vector2 vector_mul(const vector2& vec1, float val);
static float basic_collision(TBall* ball, vector2* nextPosition, vector2* direction, float elasticity,
float smoothness,
float threshold, float boost);

View file

@ -104,6 +104,12 @@ void options::InitPrimary()
Options.IntegerScaling = get_int("Integer Scaling", false);
Options.SoundVolume = Clamp(get_int("Sound Volume", DefVolume), MinVolume, MaxVolume);
Options.MusicVolume = Clamp(get_int("Music Volume", DefVolume), MinVolume, MaxVolume);
Options.DebugOverlay = get_int("Debug Overlay", false);
Options.DebugOverlayGrid = get_int("Debug Overlay Grid", true);
Options.DebugOverlayAllEdges = get_int("Debug Overlay All Edges", true);
Options.DebugOverlayBallPosition = get_int("Debug Overlay Ball Position", true);
Options.DebugOverlayBallEdges = get_int("Debug Overlay Ball Edges", true);
Options.DebugOverlayCollisionMask = get_int("Debug Overlay Collision Mask", true);
}
void options::InitSecondary()
@ -143,6 +149,12 @@ void options::uninit()
set_int("Integer Scaling", Options.IntegerScaling);
set_int("Sound Volume", Options.SoundVolume);
set_int("Music Volume", Options.MusicVolume);
set_int("Debug Overlay", Options.DebugOverlay);
set_int("Debug Overlay Grid", Options.DebugOverlayGrid);
set_int("Debug Overlay All Edges", Options.DebugOverlayAllEdges);
set_int("Debug Overlay Ball Position", Options.DebugOverlayBallPosition);
set_int("Debug Overlay Ball Edges", Options.DebugOverlayBallEdges);
set_int("Debug Overlay Collision Mask", Options.DebugOverlayCollisionMask);
}

View file

@ -80,6 +80,12 @@ struct optionsStruct
bool IntegerScaling;
int SoundVolume;
int MusicVolume;
bool DebugOverlay;
bool DebugOverlayGrid;
bool DebugOverlayAllEdges;
bool DebugOverlayBallPosition;
bool DebugOverlayBallEdges;
bool DebugOverlayCollisionMask;
};
struct ControlRef

View file

@ -26,33 +26,42 @@ void proj::init(float* mat4x3, float d, float centerX, float centerY)
centery = centerY;
}
void proj::matrix_vector_multiply(mat4_row_major* mat, vector3* vec, vector3* dstVec)
vector3 proj::matrix_vector_multiply(const mat4_row_major& mat, const vector3& vec)
{
const float x = vec->X, y = vec->Y, z = vec->Z;
dstVec->X = z * mat->Row0.Z + y * mat->Row0.Y + x * mat->Row0.X + mat->Row0.W;
dstVec->Y = z * mat->Row1.Z + y * mat->Row1.Y + x * mat->Row1.X + mat->Row1.W;
dstVec->Z = z * mat->Row2.Z + y * mat->Row2.Y + x * mat->Row2.X + mat->Row2.W;
vector3 dstVec;
const float x = vec.X, y = vec.Y, z = vec.Z;
dstVec.X = z * mat.Row0.Z + y * mat.Row0.Y + x * mat.Row0.X + mat.Row0.W;
dstVec.Y = z * mat.Row1.Z + y * mat.Row1.Y + x * mat.Row1.X + mat.Row1.W;
dstVec.Z = z * mat.Row2.Z + y * mat.Row2.Y + x * mat.Row2.X + mat.Row2.W;
return dstVec;
}
float proj::z_distance(vector3* vec)
float proj::z_distance(const vector3& vec)
{
vector3 dstVec{};
matrix_vector_multiply(&matrix, vec, &dstVec);
return maths::magnitude(dstVec);
auto projVec = matrix_vector_multiply(matrix, vec);
return maths::magnitude(projVec);
}
void proj::xform_to_2d(vector3* vec, int* dst)
vector2i proj::xform_to_2d(const vector2& vec)
{
vector3 vec3{ vec.X, vec.Y, 0 };
return xform_to_2d(vec3);
}
vector2i proj::xform_to_2d(const vector3& vec)
{
float projCoef;
vector3 dstVec2{};
matrix_vector_multiply(&matrix, vec, &dstVec2);
if (dstVec2.Z == 0.0f)
auto projVec = matrix_vector_multiply(matrix, vec);
if (projVec.Z == 0.0f)
projCoef = 999999.88f;
else
projCoef = d_ / dstVec2.Z;
dst[0] = static_cast<int>(dstVec2.X * projCoef + centerx);
dst[1] = static_cast<int>(dstVec2.Y * projCoef + centery);
projCoef = d_ / projVec.Z;
return
{
static_cast<int>(projVec.X * projCoef + centerx),
static_cast<int>(projVec.Y * projCoef + centery)
};
}
void proj::recenter(float centerX, float centerY)

View file

@ -22,9 +22,10 @@ class proj
{
public:
static void init(float* mat4x3, float d, float centerX, float centerY);
static void matrix_vector_multiply(mat4_row_major* mat, vector3* vec, vector3* dstVec);
static float z_distance(vector3* vec);
static void xform_to_2d(vector3* vec, int* dst);
static vector3 matrix_vector_multiply(const mat4_row_major& mat, const vector3& vec);
static float z_distance(const vector3& vec);
static vector2i xform_to_2d(const vector3& vec);
static vector2i xform_to_2d(const vector2& vec);
static void recenter(float centerX, float centerY);
private:
static mat4_row_major matrix;

View file

@ -8,6 +8,7 @@
#include "score.h"
#include "TPinballTable.h"
#include "winmain.h"
#include "DebugOverlay.h"
std::vector<render_sprite_type_struct*> render::dirty_list, render::sprite_list, render::ball_list;
zmap_header_type* render::background_zmap;
@ -57,6 +58,7 @@ void render::uninit()
ball_list.clear();
dirty_list.clear();
sprite_list.clear();
DebugOverlay::UnInit();
}
void render::recreate_screen_texture()
@ -585,4 +587,9 @@ void render::PresentVScreen()
SDL_RenderCopy(winmain::Renderer, vscreen->Texture, &srcSidebarRect, &dstSidebarRect);
#endif
}
if (options::Options.DebugOverlay)
{
DebugOverlay::DrawOverlay();
}
}

View file

@ -577,6 +577,24 @@ void winmain::RenderUi()
{
DispGRhistory ^= true;
}
if (ImGui::MenuItem("Debug Overlay", nullptr, Options.DebugOverlay))
{
Options.DebugOverlay ^= true;
}
if (Options.DebugOverlay && ImGui::BeginMenu("Overlay Options"))
{
if (ImGui::MenuItem("Box Grid", nullptr, Options.DebugOverlayGrid))
Options.DebugOverlayGrid ^= true;
if (ImGui::MenuItem("All Edges", nullptr, Options.DebugOverlayAllEdges))
Options.DebugOverlayAllEdges ^= true;
if (ImGui::MenuItem("Ball Position", nullptr, Options.DebugOverlayBallPosition))
Options.DebugOverlayBallPosition ^= true;
if (ImGui::MenuItem("Ball Box Edges", nullptr, Options.DebugOverlayBallEdges))
Options.DebugOverlayBallEdges ^= true;
if (ImGui::MenuItem("Apply Collision Mask", nullptr, Options.DebugOverlayCollisionMask))
Options.DebugOverlayCollisionMask ^= true;
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Cheats"))
{
if (ImGui::MenuItem("hidden test", nullptr, pb::cheat_mode))