diff --git a/main/modes/games/pinball/mode_pinball.c b/attic/pinball/mode_pinball.c similarity index 53% rename from main/modes/games/pinball/mode_pinball.c rename to attic/pinball/mode_pinball.c index 49f4a05df..2a41750dc 100644 --- a/main/modes/games/pinball/mode_pinball.c +++ b/attic/pinball/mode_pinball.c @@ -6,10 +6,19 @@ #include #include "mode_pinball.h" -#include "pinball_zones.h" +#include "pinball_game.h" #include "pinball_physics.h" #include "pinball_draw.h" -#include "pinball_test.h" + +//============================================================================== +// Structs +//============================================================================== + +typedef struct +{ + pbScene_t scene; + font_t ibm; +} pinball_t; //============================================================================== // Function Prototypes @@ -60,38 +69,10 @@ static void pinEnterMode(void) // Allocate all the memory pinball = calloc(sizeof(pinball_t), 1); - pinball->balls = heap_caps_calloc(MAX_NUM_BALLS, sizeof(pbCircle_t), MALLOC_CAP_SPIRAM); - pinball->bumpers = heap_caps_calloc(MAX_NUM_BUMPERS, sizeof(pbCircle_t), MALLOC_CAP_SPIRAM); - pinball->walls = heap_caps_calloc(MAX_NUM_WALLS, sizeof(pbLine_t), MALLOC_CAP_SPIRAM); - pinball->flippers = heap_caps_calloc(MAX_NUM_FLIPPERS, sizeof(pbFlipper_t), MALLOC_CAP_SPIRAM); - - pinball->ballsTouching = heap_caps_calloc(MAX_NUM_BALLS, sizeof(pbTouchRef_t*), MALLOC_CAP_SPIRAM); - for (uint32_t i = 0; i < MAX_NUM_BALLS; i++) - { - pinball->ballsTouching[i] = heap_caps_calloc(MAX_NUM_TOUCHES, sizeof(pbTouchRef_t), MALLOC_CAP_SPIRAM); - } - - // Split the table into zones - createTableZones(pinball); - - // Create random balls - createRandomBalls(pinball, 0); - pbCreateBall(pinball, 6, 114); - pbCreateBall(pinball, 274, 114); - pbCreateBall(pinball, 135, 10); + loadFont("ibm_vga8.font", &pinball->ibm, false); - // Create random bumpers - createRandomBumpers(pinball, 0); - - // Create random walls - createRandomWalls(pinball, 0); - - // Create flippers - createFlipper(pinball, TFT_WIDTH / 2 - 50, 200, true); - createFlipper(pinball, TFT_WIDTH / 2 + 50, 200, false); - - // Load font - loadFont("ibm_vga8.font", &pinball->ibm_vga8, false); + pbSceneInit(&pinball->scene); + pbStartBall(&pinball->scene); } /** @@ -100,19 +81,8 @@ static void pinEnterMode(void) */ static void pinExitMode(void) { - for (uint32_t i = 0; i < MAX_NUM_BALLS; i++) - { - free(pinball->ballsTouching[i]); - } - free(pinball->ballsTouching); - - free(pinball->balls); - free(pinball->walls); - free(pinball->bumpers); - free(pinball->flippers); - // Free font - freeFont(&pinball->ibm_vga8); - // Free the rest of the state + freeFont(&pinball->ibm); + pbSceneDestroy(&pinball->scene); free(pinball); } @@ -123,37 +93,25 @@ static void pinExitMode(void) */ static void pinMainLoop(int64_t elapsedUs) { - // Make a local copy for speed - pinball_t* p = pinball; - - // Check all queued button events - buttonEvt_t evt; + // Handle inputs + buttonEvt_t evt = {0}; while (checkButtonQueueWrapper(&evt)) { - if (PB_RIGHT == evt.button) + if (evt.down && PB_START == evt.button) { - p->flippers[1].buttonHeld = evt.down; + pbSceneDestroy(&pinball->scene); + pbSceneInit(&pinball->scene); } - else if (PB_LEFT == evt.button) + else { - p->flippers[0].buttonHeld = evt.down; + pbButtonPressed(&pinball->scene, &evt); } } - // Only check physics once per frame - p->frameTimer += elapsedUs; - while (p->frameTimer >= PIN_US_PER_FRAME) - { - p->frameTimer -= PIN_US_PER_FRAME; - updatePinballPhysicsFrame(pinball); - } - - // Always draw foreground to prevent flicker - pinballDrawForeground(pinball); - - // Log frame time for FPS - p->frameTimesIdx = (p->frameTimesIdx + 1) % NUM_FRAME_TIMES; - p->frameTimes[p->frameTimesIdx] = esp_timer_get_time(); + pbSimulate(&pinball->scene, elapsedUs); + pbGameTimers(&pinball->scene, elapsedUs); + pbAdjustCamera(&pinball->scene); + pbSceneDraw(&pinball->scene, &pinball->ibm); } /** @@ -168,5 +126,4 @@ static void pinMainLoop(int64_t elapsedUs) */ static void pinBackgroundDrawCallback(int16_t x, int16_t y, int16_t w, int16_t h, int16_t up, int16_t upNum) { - pinballDrawBackground(pinball, x, y, w, h); } diff --git a/attic/pinball/mode_pinball.h b/attic/pinball/mode_pinball.h new file mode 100644 index 000000000..e170e8c86 --- /dev/null +++ b/attic/pinball/mode_pinball.h @@ -0,0 +1,28 @@ +#pragma once + +//============================================================================== +// Includes +//============================================================================== + +#include +#include "swadge2024.h" + +//============================================================================== +// Defines +//============================================================================== + +#define PIN_US_PER_FRAME 16667 + +//============================================================================== +// Enums +//============================================================================== + +//============================================================================== +// Structs +//============================================================================== + +//============================================================================== +// Extern variables +//============================================================================== + +extern swadgeMode_t pinballMode; diff --git a/attic/pinball/pinball_circle.c b/attic/pinball/pinball_circle.c new file mode 100644 index 000000000..a606799d6 --- /dev/null +++ b/attic/pinball/pinball_circle.c @@ -0,0 +1,80 @@ +#include "pinball_circle.h" +#include "pinball_rectangle.h" + +/** + * @brief TODO doc + * + * @param tableData + * @param scene + * @return uint32_t + */ +uint32_t readCircleFromFile(uint8_t* tableData, pbScene_t* scene) +{ + pbCircle_t* circle = &scene->circles[scene->numCircles++]; + + uint32_t dIdx = 0; + circle->id = readInt16(tableData, &dIdx); + circle->groupId = readInt8(tableData, &dIdx); + circle->group = addToGroup(scene, circle, circle->groupId); + circle->pos.x = readInt16(tableData, &dIdx); + circle->pos.y = readInt16(tableData, &dIdx); + circle->radius = readInt8(tableData, &dIdx); + circle->type = readInt8(tableData, &dIdx); + circle->pushVel = readInt8(tableData, &dIdx); + + return dIdx; +} + +/** + * @brief Simulate a ball's motion + * + * @param ball + * @param dt + * @param scene + */ +void pbBallSimulate(pbBall_t* ball, int32_t elapsedUs, float dt, pbScene_t* scene) +{ + if (ball->scoopTimer <= 0) + { + ball->vel = addVecFl2d(ball->vel, mulVecFl2d(scene->gravity, dt)); + ball->pos = addVecFl2d(ball->pos, mulVecFl2d(ball->vel, dt)); + } + else + { + ball->scoopTimer -= elapsedUs; + + if (ball->scoopTimer <= 0) + { + // Respawn in the launch tube + for (int32_t pIdx = 0; pIdx < scene->numPoints; pIdx++) + { + pbPoint_t* point = &scene->points[pIdx]; + if (PB_BALL_SPAWN == point->type) + { + ball->pos = point->pos; + break; + } + } + + pbOpenLaunchTube(scene, true); + + // Give the ball initial velocity + ball->vel.x = 0; + ball->vel.y = MAX_LAUNCHER_VELOCITY; + } + } +} + +/** + * @brief TODO + * + * @param circle + * @param elapsedUs + */ +void pbCircleTimer(pbCircle_t* circle, int32_t elapsedUs) +{ + if (circle->litTimer > 0) + { + circle->litTimer -= elapsedUs; + } +} diff --git a/attic/pinball/pinball_circle.h b/attic/pinball/pinball_circle.h new file mode 100644 index 000000000..53132f731 --- /dev/null +++ b/attic/pinball/pinball_circle.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pinball_typedef.h" +#include "pinball_game.h" + +#define PINBALL_RADIUS 8 + +uint32_t readCircleFromFile(uint8_t* tableData, pbScene_t* scene); +void pbBallSimulate(pbBall_t* ball, int32_t elapsedUs, float dt, pbScene_t* scene); +void pbCircleTimer(pbCircle_t* circle, int32_t elapsedUs); diff --git a/attic/pinball/pinball_draw.c b/attic/pinball/pinball_draw.c new file mode 100644 index 000000000..155741306 --- /dev/null +++ b/attic/pinball/pinball_draw.c @@ -0,0 +1,153 @@ +#include "hdw-tft.h" +#include "shapes.h" + +#include "pinball_draw.h" +#include "pinball_line.h" +#include "pinball_circle.h" +#include "pinball_rectangle.h" +#include "pinball_flipper.h" + +/** + * @brief TODO doc + * + * @param scene + */ +void pbAdjustCamera(pbScene_t* scene) +{ + // No balls? No camera adjustment! + if (0 == scene->balls.length) + { + return; + } + + // Find the ball lowest on the table + float lowestBallX = 0; + float lowestBallY = 0; + + node_t* ballNode = scene->balls.first; + while (ballNode) + { + pbBall_t* ball = ballNode->val; + if (ball->pos.y > lowestBallY) + { + lowestBallX = ball->pos.x; + lowestBallY = ball->pos.y; + } + ballNode = ballNode->next; + } + + // Adjust the lowest ball's position to screen coordinates + lowestBallY -= scene->cameraOffset.y; + +#define PIN_CAMERA_BOUND_UPPER ((TFT_HEIGHT) / 4) +#define PIN_CAMERA_BOUND_LOWER ((3 * TFT_HEIGHT) / 4) + + // If the lowest ball is lower than the boundary + if (lowestBallY > PIN_CAMERA_BOUND_LOWER) + { + // Pan the camera down + if (scene->cameraOffset.y < scene->tableDim.y - TFT_HEIGHT) + { + scene->cameraOffset.y += (lowestBallY - PIN_CAMERA_BOUND_LOWER); + } + } + // If the lowest ball is higher than the other boundary + else if (lowestBallY < PIN_CAMERA_BOUND_UPPER) + { + // Pan the camera up + if (scene->cameraOffset.y > 0) + { + scene->cameraOffset.y -= (PIN_CAMERA_BOUND_UPPER - lowestBallY); + } + } + + // Pan in the X direction to view the launch tube + int16_t xEnd = lowestBallX + PINBALL_RADIUS + 40; + if (xEnd > TFT_WIDTH) + { + scene->cameraOffset.x = xEnd - TFT_WIDTH; + } + else + { + scene->cameraOffset.x = 0; + } +} + +/** + * @brief TODO doc + * + * @param scene + */ +void pbSceneDraw(pbScene_t* scene, font_t* font) +{ + clearPxTft(); + + // Draw an indicator for the ball save + if (scene->saveTimer > 0) + { + const char text[] = "SAVE"; + int16_t tWidth = textWidth(font, text); + drawText(font, c555, text, ((280 - tWidth) / 2) - scene->cameraOffset.x, 400 - scene->cameraOffset.y); + } + + // Triangle indicators + for (int32_t i = 0; i < scene->numTriangles; i++) + { + pbTriangle_t* tri = &scene->triangles[i]; + drawTriangleOutlined(tri->p1.x - scene->cameraOffset.x, tri->p1.y - scene->cameraOffset.y, // + tri->p2.x - scene->cameraOffset.x, tri->p2.y - scene->cameraOffset.y, // + tri->p3.x - scene->cameraOffset.x, tri->p3.y - scene->cameraOffset.y, // + tri->isOn ? c550 : cTransparent, c220); + } + + // Lines + for (int32_t i = 0; i < scene->numLines; i++) + { + pinballDrawLine(&scene->lines[i], &scene->cameraOffset); + } + + // balls + node_t* bNode = scene->balls.first; + while (bNode) + { + pbBall_t* ball = bNode->val; + + // Don't draw when scooped + if (ball->scoopTimer <= 0) + { + vecFl_t* pos = &ball->pos; + drawCircleFilled(pos->x - scene->cameraOffset.x, pos->y - scene->cameraOffset.y, ball->radius, c500); + } + + bNode = bNode->next; + } + + // circles + for (int32_t i = 0; i < scene->numCircles; i++) + { + if (PB_BUMPER == scene->circles[i].type) + { + vecFl_t* pos = &scene->circles[i].pos; + drawCircleFilled(pos->x - scene->cameraOffset.x, pos->y - scene->cameraOffset.y, scene->circles[i].radius, + (scene->circles[i].litTimer > 0) ? c252 : c131); + } + } + + // flippers + for (int32_t i = 0; i < scene->numFlippers; i++) + { + pinballDrawFlipper(&scene->flippers[i], &scene->cameraOffset); + } + + // launchers + for (int32_t i = 0; i < scene->numLaunchers; i++) + { + pbLauncher_t* l = &scene->launchers[i]; + int compression = l->height * l->impulse; + vec_t offsetPos = { + .x = l->pos.x - scene->cameraOffset.x, + .y = l->pos.y + compression - scene->cameraOffset.y, + }; + drawRect(offsetPos.x, offsetPos.y, offsetPos.x + l->width, offsetPos.y + l->height - compression, c330); + } +} diff --git a/attic/pinball/pinball_draw.h b/attic/pinball/pinball_draw.h new file mode 100644 index 000000000..848c2645b --- /dev/null +++ b/attic/pinball/pinball_draw.h @@ -0,0 +1,7 @@ +#pragma once + +#include "font.h" +#include "pinball_typedef.h" + +void pbAdjustCamera(pbScene_t* scene); +void pbSceneDraw(pbScene_t* scene, font_t* font); diff --git a/attic/pinball/pinball_flipper.c b/attic/pinball/pinball_flipper.c new file mode 100644 index 000000000..04d007d9b --- /dev/null +++ b/attic/pinball/pinball_flipper.c @@ -0,0 +1,106 @@ +#include + +#include "macros.h" +#include "shapes.h" + +#include "pinball_flipper.h" + +/** + * @brief TODO doc + * + * @param tableData + * @param scene + * @return uint32_t + */ +uint32_t readFlipperFromFile(uint8_t* tableData, pbScene_t* scene) +{ + pbFlipper_t* flipper = &scene->flippers[scene->numFlippers++]; + uint32_t dIdx = 0; + + flipper->pos.x = readInt16(tableData, &dIdx); + flipper->pos.y = readInt16(tableData, &dIdx); + flipper->radius = readInt8(tableData, &dIdx); + flipper->length = readInt8(tableData, &dIdx); + flipper->facingRight = readInt8(tableData, &dIdx) != 0; + + flipper->maxRotation = 1.0f; + flipper->restAngle = 0.523599f; // 30 degrees + flipper->angularVelocity = 20.0f; + + if (!flipper->facingRight) + { + flipper->restAngle = M_PI - flipper->restAngle; + flipper->maxRotation = -flipper->maxRotation; + } + flipper->sign = (flipper->maxRotation >= 0) ? -1 : 1; + flipper->maxRotation = ABS(flipper->maxRotation); + + // changing + flipper->rotation = 0; + flipper->currentAngularVelocity = 0; + flipper->buttonHeld = false; + + return dIdx; +} + +/** + * @brief TODO doc + * + * @param flipper + * @param dt + */ +void pbFlipperSimulate(pbFlipper_t* flipper, float dt) +{ + float prevRotation = flipper->rotation; + + if (flipper->buttonHeld) + { + flipper->rotation = flipper->rotation + dt * flipper->angularVelocity; + if (flipper->rotation > flipper->maxRotation) + { + flipper->rotation = flipper->maxRotation; + } + } + else + { + flipper->rotation = flipper->rotation - dt * flipper->angularVelocity; + if (flipper->rotation < 0) + { + flipper->rotation = 0; + } + } + flipper->currentAngularVelocity = flipper->sign * (flipper->rotation - prevRotation) / dt; +} + +/** + * @brief TODO doc + * + * @param flipper + * @return vecFl_t + */ +vecFl_t pbFlipperGetTip(pbFlipper_t* flipper) +{ + float angle = flipper->restAngle + flipper->sign * flipper->rotation; + vecFl_t dir = {.x = cosf(angle), .y = sinf(angle)}; + return addVecFl2d(flipper->pos, mulVecFl2d(dir, flipper->length)); +} + +/** + * @brief TODO doc + * + * @param flipper + */ +void pinballDrawFlipper(pbFlipper_t* flipper, vec_t* cameraOffset) +{ + vecFl_t pos = { + .x = flipper->pos.x - cameraOffset->x, + .y = flipper->pos.y - cameraOffset->y, + }; + drawCircleFilled(pos.x, pos.y, flipper->radius, c115); + vecFl_t tip = pbFlipperGetTip(flipper); + tip.x -= cameraOffset->x; + tip.y -= cameraOffset->y; + drawCircleFilled(tip.x, tip.y, flipper->radius, c115); + drawLine(pos.x, pos.y + flipper->radius, tip.x, tip.y + flipper->radius, c115, 0); + drawLine(pos.x, pos.y - flipper->radius, tip.x, tip.y - flipper->radius, c115, 0); +} diff --git a/attic/pinball/pinball_flipper.h b/attic/pinball/pinball_flipper.h new file mode 100644 index 000000000..d52429deb --- /dev/null +++ b/attic/pinball/pinball_flipper.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pinball_typedef.h" +#include "pinball_game.h" + +uint32_t readFlipperFromFile(uint8_t* tableData, pbScene_t* scene); +void pinballDrawFlipper(pbFlipper_t* flipper, vec_t* cameraOffset); +void pbFlipperSimulate(pbFlipper_t* flipper, float dt); +vecFl_t pbFlipperGetTip(pbFlipper_t* flipper); diff --git a/attic/pinball/pinball_game.c b/attic/pinball/pinball_game.c new file mode 100644 index 000000000..1b7bb4267 --- /dev/null +++ b/attic/pinball/pinball_game.c @@ -0,0 +1,500 @@ +#include +#include +#include +#include +#include "heatshrink_helper.h" + +#include "pinball_game.h" + +#include "pinball_line.h" +#include "pinball_circle.h" +#include "pinball_rectangle.h" +#include "pinball_flipper.h" +#include "pinball_triangle.h" +#include "pinball_point.h" + +/** + * @brief TODO doc + * + * @param data + * @param idx + * @return uint8_t + */ +uint8_t readInt8(uint8_t* data, uint32_t* idx) +{ + return data[(*idx)++]; +} + +/** + * @brief TODO doc + * + * @param data + * @param idx + * @return uint16_t + */ +uint16_t readInt16(uint8_t* data, uint32_t* idx) +{ + int16_t ret = (data[*idx] << 8) | (data[(*idx) + 1]); + (*idx) += 2; + return ret; +} + +/** + * @brief TODO + * + * @param scene + * @param obj + * @param groupId + * @return list_t* + */ +list_t* addToGroup(pbScene_t* scene, void* obj, uint8_t groupId) +{ + push(&scene->groups[groupId], obj); + return &scene->groups[groupId]; +} + +/** + * @brief TODO doc + * + * @param scene + */ +void pbSceneInit(pbScene_t* scene) +{ + scene->gravity.x = 0; + scene->gravity.y = 180; + scene->score = 0; + scene->paused = false; + + uint32_t decompressedSize = 0; + uint8_t* tableData = (uint8_t*)readHeatshrinkFile("pinball.raw", &decompressedSize, true); + uint32_t dIdx = 0; + + // Allocate groups + scene->numGroups = readInt8(tableData, &dIdx) + 1; + scene->groups = (list_t*)calloc(scene->numGroups, sizeof(list_t)); + + uint16_t linesInFile = readInt16(tableData, &dIdx); + scene->lines = calloc(linesInFile, sizeof(pbLine_t)); + scene->numLines = 0; + for (uint16_t lIdx = 0; lIdx < linesInFile; lIdx++) + { + dIdx += readLineFromFile(&tableData[dIdx], scene); + pbLine_t* newLine = &scene->lines[scene->numLines - 1]; + + // Record the table dimension + float maxX = MAX(newLine->p1.x, newLine->p2.x); + float maxY = MAX(newLine->p1.y, newLine->p2.y); + + if (maxX > scene->tableDim.x) + { + scene->tableDim.x = maxX; + } + if (maxY > scene->tableDim.y) + { + scene->tableDim.y = maxY; + } + } + + uint16_t circlesInFile = readInt16(tableData, &dIdx); + scene->circles = calloc(circlesInFile, sizeof(pbCircle_t)); + scene->numCircles = 0; + for (uint16_t cIdx = 0; cIdx < circlesInFile; cIdx++) + { + dIdx += readCircleFromFile(&tableData[dIdx], scene); + } + + uint16_t rectanglesInFile = readInt16(tableData, &dIdx); + scene->launchers = calloc(1, sizeof(pbLauncher_t)); + scene->numLaunchers = 0; + for (uint16_t rIdx = 0; rIdx < rectanglesInFile; rIdx++) + { + dIdx += readRectangleFromFile(&tableData[dIdx], scene); + } + + uint16_t flippersInFile = readInt16(tableData, &dIdx); + scene->flippers = calloc(flippersInFile, sizeof(pbFlipper_t)); + scene->numFlippers = 0; + for (uint16_t fIdx = 0; fIdx < flippersInFile; fIdx++) + { + dIdx += readFlipperFromFile(&tableData[dIdx], scene); + } + + uint16_t trianglesInFile = readInt16(tableData, &dIdx); + scene->triangles = calloc(trianglesInFile, sizeof(pbTriangle_t)); + scene->numTriangles = 0; + for (uint16_t tIdx = 0; tIdx < trianglesInFile; tIdx++) + { + dIdx += readTriangleFromFile(&tableData[dIdx], scene); + } + + uint16_t pointsInFile = readInt16(tableData, &dIdx); + scene->points = calloc(pointsInFile, sizeof(pbPoint_t)); + scene->numPoints = 0; + for (uint16_t pIdx = 0; pIdx < pointsInFile; pIdx++) + { + dIdx += readPointFromFile(&tableData[dIdx], scene); + } + + free(tableData); + + // Reset the camera + scene->cameraOffset.x = 0; + scene->cameraOffset.y = 0; + + // Start with three balls + scene->ballCount = 3; +} + +/** + * @brief TODO + * + * @param scene + */ +void pbStartBall(pbScene_t* scene) +{ + // Set the state + pbSetState(scene, PBS_WAIT_TO_LAUNCH); + + // Clear loop history + memset(scene->loopHistory, 0, sizeof(scene->loopHistory)); + + // Reset targets + for (uint16_t lIdx = 0; lIdx < scene->numLines; lIdx++) + { + pbLine_t* line = &scene->lines[lIdx]; + if (PB_DROP_TARGET == line->type) + { + line->isUp = true; + } + } + + // Open the launch tube + pbOpenLaunchTube(scene, true); + + clear(&scene->balls); + for (uint16_t pIdx = 0; pIdx < scene->numPoints; pIdx++) + { + if (PB_BALL_SPAWN == scene->points[pIdx].type) + { + pbBall_t* ball = calloc(1, sizeof(pbBall_t)); + ball->pos = scene->points[pIdx].pos; + ball->vel.x = 0; + ball->vel.y = 0; + ball->radius = PINBALL_RADIUS; + ball->mass = M_PI * 4.0f * 4.0f; + ball->restitution = 0.2f; + push(&scene->balls, ball); + return; + } + } +} + +/** + * @brief + * + * @param scene + */ +void pbStartMultiball(pbScene_t* scene) +{ + // Don't start multiball if there are already three balls + if (3 == scene->balls.length) + { + return; + } + + // Ignore the first spawn point (tube) + bool ignoreFirst = true; + + // For each point + for (uint16_t pIdx = 0; pIdx < scene->numPoints; pIdx++) + { + // If this is a spawn point + if (PB_BALL_SPAWN == scene->points[pIdx].type) + { + // Ignore the first + if (ignoreFirst) + { + ignoreFirst = false; + } + else + { + // Spawn a ball here + // TODO check if space is empty first + pbBall_t* ball = calloc(1, sizeof(pbBall_t)); + ball->pos = scene->points[pIdx].pos; + ball->vel.x = 0; + ball->vel.y = 0; + ball->radius = PINBALL_RADIUS; + ball->mass = M_PI * 4.0f * 4.0f; + ball->restitution = 0.2f; + push(&scene->balls, ball); + + // All balls spawned + if (3 == scene->balls.length) + { + return; + } + } + } + } +} + +/** + * @brief TODO + * + * @param scene + */ +void pbSceneDestroy(pbScene_t* scene) +{ + if (scene->groups) + { + // Free the rest of the state + free(scene->lines); + free(scene->circles); + free(scene->launchers); + free(scene->flippers); + free(scene->triangles); + free(scene->points); + + node_t* bNode = scene->balls.first; + while (bNode) + { + free(bNode->val); + bNode = bNode->next; + } + clear(&scene->balls); + + for (int32_t gIdx = 0; gIdx < scene->numGroups; gIdx++) + { + clear(&scene->groups[gIdx]); + } + free(scene->groups); + scene->groups = NULL; + } +} + +// ------------------------ user interaction --------------------------- + +/** + * @brief TODO doc + * + * @param scene + * @param event + */ +void pbButtonPressed(pbScene_t* scene, buttonEvt_t* event) +{ + if (event->down) + { + switch (event->button) + { + case PB_LEFT: + { + for (int32_t fIdx = 0; fIdx < scene->numFlippers; fIdx++) + { + if (scene->flippers[fIdx].facingRight) + { + scene->flippers[fIdx].buttonHeld = true; + } + } + break; + } + case PB_RIGHT: + { + for (int32_t fIdx = 0; fIdx < scene->numFlippers; fIdx++) + { + if (!scene->flippers[fIdx].facingRight) + { + scene->flippers[fIdx].buttonHeld = true; + } + } + for (int32_t rIdx = 0; rIdx < scene->numLaunchers; rIdx++) + { + scene->launchers[rIdx].buttonHeld = true; + } + break; + } + default: + { + break; + } + } + } + else + { + switch (event->button) + { + case PB_LEFT: + { + for (int32_t fIdx = 0; fIdx < scene->numFlippers; fIdx++) + { + if (scene->flippers[fIdx].facingRight) + { + scene->flippers[fIdx].buttonHeld = false; + } + } + break; + } + case PB_RIGHT: + { + for (int32_t fIdx = 0; fIdx < scene->numFlippers; fIdx++) + { + if (!scene->flippers[fIdx].facingRight) + { + scene->flippers[fIdx].buttonHeld = false; + } + } + for (int32_t rIdx = 0; rIdx < scene->numLaunchers; rIdx++) + { + scene->launchers[rIdx].buttonHeld = false; + } + break; + } + default: + { + break; + } + } + } +} + +/** + * @brief + * + * @param ball + * @param scene + */ +void pbRemoveBall(pbBall_t* ball, pbScene_t* scene) +{ + // Clear loop history + memset(scene->loopHistory, 0, sizeof(scene->loopHistory)); + + // If the save timer is running + if (scene->saveTimer > 0 && 1 == scene->balls.length) + { + // Save the ball by scooping it back + printf("Ball Saved\n"); + ball->scoopTimer = 2000000; + } + else + { + // Find the ball in the list + node_t* bNode = scene->balls.first; + while (bNode) + { + if (ball == bNode->val) + { + // Remove the ball from the list + free(bNode->val); + removeEntry(&scene->balls, bNode); + break; + } + bNode = bNode->next; + } + + // If there are no active balls left + if (0 == scene->balls.length) + { + // Decrement the overall ball count + scene->ballCount--; + + // If there are balls left + if (0 < scene->ballCount) + { + pbSetState(scene, PBS_BALL_OVER); + // TODO show bonus set up for next ball, etc. + pbStartBall(scene); + } + else + { + // No balls left + pbSetState(scene, PBS_GAME_OVER); + } + } + } +} + +/** + * @brief TODO + * + * @param scene + * @param elapsedUs + */ +void pbGameTimers(pbScene_t* scene, int32_t elapsedUs) +{ + if (scene->saveTimer > 0) + { + scene->saveTimer -= elapsedUs; + } +} + +/** + * @brief TODO + * + * @param scene + * @param open + */ +void pbOpenLaunchTube(pbScene_t* scene, bool open) +{ + if (open != scene->launchTubeClosed) + { + scene->launchTubeClosed = open; + + if (!open) + { + // Start a 15s timer to save the ball when the door closes + scene->saveTimer = 15000000; + pbSetState(scene, PBS_GAME_NO_EVENT); + } + + for (int32_t lIdx = 0; lIdx < scene->numLines; lIdx++) + { + pbLine_t* line = &scene->lines[lIdx]; + if (PB_LAUNCH_DOOR == line->type) + { + line->isUp = !open; + } + } + } +} + +/** + * @brief TODO + * + * @param scene + * @param state + */ +void pbSetState(pbScene_t* scene, pbGameState_t state) +{ + if (scene->state != state) + { + scene->state = state; + switch (state) + { + case PBS_WAIT_TO_LAUNCH: + { + printf("Ball Start\n"); + break; + } + case PBS_GAME_NO_EVENT: + { + printf("Event Finished\n"); + break; + } + case PBS_GAME_EVENT: + { + printf("Event Started\n"); + break; + } + case PBS_BALL_OVER: + { + printf("Ball Lost\n"); + break; + } + case PBS_GAME_OVER: + { + printf("Game Over\n"); + break; + } + } + } +} diff --git a/attic/pinball/pinball_game.h b/attic/pinball/pinball_game.h new file mode 100644 index 000000000..78cfc0867 --- /dev/null +++ b/attic/pinball/pinball_game.h @@ -0,0 +1,19 @@ +#pragma once + +#include "hdw-btn.h" +#include "macros.h" +#include "pinball_typedef.h" + +uint8_t readInt8(uint8_t* data, uint32_t* idx); +uint16_t readInt16(uint8_t* data, uint32_t* idx); +list_t* addToGroup(pbScene_t* scene, void* obj, uint8_t groupId); + +void pbSceneInit(pbScene_t* scene); +void pbSceneDestroy(pbScene_t* scene); +void pbButtonPressed(pbScene_t* scene, buttonEvt_t* event); +void pbRemoveBall(pbBall_t* ball, pbScene_t* scene); +void pbStartBall(pbScene_t* scene); +void pbGameTimers(pbScene_t* scene, int32_t elapsedUs); +void pbOpenLaunchTube(pbScene_t* scene, bool open); +void pbStartMultiball(pbScene_t* scene); +void pbSetState(pbScene_t* scene, pbGameState_t state); diff --git a/attic/pinball/pinball_line.c b/attic/pinball/pinball_line.c new file mode 100644 index 000000000..10e163c3f --- /dev/null +++ b/attic/pinball/pinball_line.c @@ -0,0 +1,140 @@ +#include "shapes.h" +#include "palette.h" + +#include "pinball_line.h" +#include "pinball_physics.h" + +/** + * @brief TODO doc + * + * @param tableData + * @param scene + * @return int32_t + */ +int32_t readLineFromFile(uint8_t* tableData, pbScene_t* scene) +{ + pbLine_t* line = &scene->lines[scene->numLines++]; + uint32_t dIdx = 0; + line->id = readInt16(tableData, &dIdx); + line->groupId = readInt8(tableData, &dIdx); + line->group = addToGroup(scene, line, line->groupId); + line->p1.x = readInt16(tableData, &dIdx); + line->p1.y = readInt16(tableData, &dIdx); + line->p2.x = readInt16(tableData, &dIdx); + line->p2.y = readInt16(tableData, &dIdx); + line->type = readInt8(tableData, &dIdx); + line->pushVel = readInt8(tableData, &dIdx); + line->isUp = readInt8(tableData, &dIdx); + + return dIdx; +} + +/** + * @brief TODO doc + * + * @param line + */ +void pinballDrawLine(pbLine_t* line, vec_t* cameraOffset) +{ + paletteColor_t color = c555; + switch (line->type) + { + case PB_WALL: + case PB_BALL_LOST: + case PB_LAUNCH_DOOR: + { + if (!line->isUp) + { + return; + } + color = c555; + break; + } + case PB_SLINGSHOT: + { + color = line->litTimer > 0 ? c500 : c300; + break; + } + case PB_DROP_TARGET: + { + if (line->isUp) + { + color = c050; + } + else + { + color = c010; + } + break; + } + case PB_STANDUP_TARGET: + { + color = line->litTimer > 0 ? c004 : c002; + break; + } + case PB_SPINNER: + { + color = c123; + break; + } + case PB_SCOOP: + { + color = c202; + break; + } + } + + drawLine(line->p1.x - cameraOffset->x, line->p1.y - cameraOffset->y, line->p2.x - cameraOffset->x, + line->p2.y - cameraOffset->y, color, 0); +} + +/** + * @brief TODO + * + * @param line + * @param elapsedUs + */ +void pbLineTimer(pbLine_t* line, int32_t elapsedUs, pbScene_t* scene) +{ + // Decrement the lit timer + if (line->litTimer > 0) + { + line->litTimer -= elapsedUs; + } + + // Decrement the reset timer + if (line->resetTimer > 0) + { + line->resetTimer -= elapsedUs; + + if (line->resetTimer <= 0) + { + // Make sure the line isn't intersecting a ball before popping up + bool intersecting = false; + + node_t* bNode = scene->balls.first; + while (bNode) + { + pbBall_t* ball = bNode->val; + if (ballLineIntersection(ball, line)) + { + intersecting = true; + break; + } + bNode = bNode->next; + } + + // If there are no intersections + if (!intersecting) + { + // Raise the target + line->isUp = true; + } + else + { + // Try next frame + line->resetTimer = 1; + } + } + } +} diff --git a/attic/pinball/pinball_line.h b/attic/pinball/pinball_line.h new file mode 100644 index 000000000..bb22992bf --- /dev/null +++ b/attic/pinball/pinball_line.h @@ -0,0 +1,8 @@ +#pragma once + +#include "pinball_typedef.h" +#include "pinball_game.h" + +int32_t readLineFromFile(uint8_t* tableData, pbScene_t* scene); +void pinballDrawLine(pbLine_t* line, vec_t* cameraOffset); +void pbLineTimer(pbLine_t* line, int32_t elapsedUs, pbScene_t* scene); diff --git a/attic/pinball/pinball_physics.c b/attic/pinball/pinball_physics.c new file mode 100644 index 000000000..e99939999 --- /dev/null +++ b/attic/pinball/pinball_physics.c @@ -0,0 +1,461 @@ +#include +#include +#include +#include "pinball_line.h" +#include "pinball_circle.h" +#include "pinball_rectangle.h" +#include "pinball_flipper.h" +#include "pinball_triangle.h" +#include "pinball_physics.h" + +static void handleBallBallCollision(pbBall_t* ball1, pbBall_t* ball2); +static void handleBallCircleCollision(pbScene_t* scene, pbBall_t* ball, pbCircle_t* circle); +static void handleBallFlipperCollision(pbBall_t* ball, pbFlipper_t* flipper); +static bool handleBallLineCollision(pbBall_t* ball, pbScene_t* scene); +static void handleBallLauncherCollision(pbLauncher_t* launcher, pbBall_t* ball, float dt); + +/** + * @brief TODO + * + * @param scene + * @param elapsedUs + */ +void pbSimulate(pbScene_t* scene, int32_t elapsedUs) +{ + float elapsedUsFl = elapsedUs / 1000000.0f; + + for (int32_t i = 0; i < scene->numFlippers; i++) + { + pbFlipperSimulate(&scene->flippers[i], elapsedUsFl); + } + + for (int32_t i = 0; i < scene->numLaunchers; i++) + { + pbLauncherSimulate(&scene->launchers[i], &scene->balls, elapsedUsFl); + } + + node_t* bNode = scene->balls.first; + while (bNode) + { + pbBall_t* ball = bNode->val; + + pbBallSimulate(ball, elapsedUs, elapsedUsFl, scene); + + node_t* bNode2 = bNode->next; + while (bNode2) + { + pbBall_t* ball2 = bNode2->val; + handleBallBallCollision(ball, ball2); + bNode2 = bNode2->next; + } + + for (int32_t cIdx = 0; cIdx < scene->numCircles; cIdx++) + { + handleBallCircleCollision(scene, ball, &scene->circles[cIdx]); + } + + for (int32_t fIdx = 0; fIdx < scene->numFlippers; fIdx++) + { + handleBallFlipperCollision(ball, &scene->flippers[fIdx]); + } + + for (int32_t lIdx = 0; lIdx < scene->numLaunchers; lIdx++) + { + handleBallLauncherCollision(&scene->launchers[lIdx], ball, elapsedUs); + } + + // Collide ball with lines + if (handleBallLineCollision(ball, scene)) + { + // Iterate to the next ball node + bNode = bNode->next; + + // Then remove the ball + pbRemoveBall(ball, scene); + } + else + { + // Iterate to the next ball + bNode = bNode->next; + } + } + + for (int32_t cIdx = 0; cIdx < scene->numCircles; cIdx++) + { + pbCircleTimer(&scene->circles[cIdx], elapsedUs); + } + + for (int32_t tIdx = 0; tIdx < scene->numTriangles; tIdx++) + { + pbTriangleTimer(&scene->triangles[tIdx], elapsedUs); + } + + for (int32_t lIdx = 0; lIdx < scene->numLines; lIdx++) + { + pbLineTimer(&scene->lines[lIdx], elapsedUs, scene); + } +} + +/** + * @brief Find the closest point to point p on a line segment between a and b + * + * @param p A point + * @param a One end of a line segment + * @param b The other end of a line segment + * @return A point on the line segment closest to p + */ +static vecFl_t closestPointOnSegment(vecFl_t p, vecFl_t a, vecFl_t b) +{ + vecFl_t ab = subVecFl2d(b, a); + float t = sqMagVecFl2d(ab); + + if (t == 0.0f) + { + return a; + } + + t = (dotVecFl2d(p, ab) - dotVecFl2d(a, ab)) / t; + if (t > 1) + { + t = 1; + } + else if (t < 0) + { + t = 0; + } + + return addVecFl2d(a, mulVecFl2d(ab, t)); +} + +/** + * @brief TODO doc + * + * @param ball1 + * @param ball2 + */ +static void handleBallBallCollision(pbBall_t* ball1, pbBall_t* ball2) +{ + float restitution = MIN(ball1->restitution, ball2->restitution); + vecFl_t dir = subVecFl2d(ball2->pos, ball1->pos); + float d = magVecFl2d(dir); + if (0 == d || d > (ball1->radius + ball2->radius)) + { + return; + } + + dir = divVecFl2d(dir, d); + + float corr = (ball1->radius + ball2->radius - d) / 2.0f; + ball1->pos = addVecFl2d(ball1->pos, mulVecFl2d(dir, -corr)); + ball2->pos = addVecFl2d(ball2->pos, mulVecFl2d(dir, corr)); + + float v1 = dotVecFl2d(ball1->vel, dir); + float v2 = dotVecFl2d(ball2->vel, dir); + + float m1 = ball1->mass; + float m2 = ball2->mass; + + float newV1 = (m1 * v1 + m2 * v2 - m2 * (v1 - v2) * restitution) / (m1 + m2); + float newV2 = (m1 * v1 + m2 * v2 - m1 * (v2 - v1) * restitution) / (m1 + m2); + + ball1->vel = addVecFl2d(ball1->vel, mulVecFl2d(dir, newV1 - v1)); + ball2->vel = addVecFl2d(ball2->vel, mulVecFl2d(dir, newV2 - v2)); +} + +/** + * @brief TODO doc + * + * @param scene + * @param ball + * @param circle + */ +static void handleBallCircleCollision(pbScene_t* scene, pbBall_t* ball, pbCircle_t* circle) +{ + vecFl_t dir = subVecFl2d(ball->pos, circle->pos); + float d = magVecFl2d(dir); + if (d == 0.0 || d > (ball->radius + circle->radius)) + { + if (circle->id == scene->touchedLoopId) + { + scene->touchedLoopId = PIN_INVALID_ID; + } + return; + } + + if (PB_BUMPER == circle->type) + { + // Normalize the direction + dir = divVecFl2d(dir, d); + + // Move ball backwards to not clip + float corr = ball->radius + circle->radius - d; + ball->pos = addVecFl2d(ball->pos, mulVecFl2d(dir, corr)); + + // Adjust the velocity + float v = dotVecFl2d(ball->vel, dir); + ball->vel = addVecFl2d(ball->vel, mulVecFl2d(dir, circle->pushVel - v)); + + circle->litTimer = 250000; + } + else if (PB_ROLLOVER == circle->type) + { + if (circle->id != scene->touchedLoopId) + { + scene->touchedLoopId = circle->id; + + memmove(&scene->loopHistory[1], &scene->loopHistory[0], + sizeof(scene->loopHistory) - sizeof(scene->loopHistory[0])); + scene->loopHistory[0] = circle->id; + + if (scene->loopHistory[0] + 1 == scene->loopHistory[1] + && scene->loopHistory[1] + 1 == scene->loopHistory[2]) + { + printf("Loop Counter Clockwise\n"); + } + else if (scene->loopHistory[2] + 1 == scene->loopHistory[1] + && scene->loopHistory[1] + 1 == scene->loopHistory[0]) + { + printf("Loop Clockwise\n"); + } + } + // Group two rollovers should close the launch tube + // TODO hardcoding a group ID is gross + if (5 == circle->groupId) + { + pbOpenLaunchTube(scene, false); + } + } +} + +/** + * @brief TODO doc + * + * @param ball + * @param flipper + */ +static void handleBallFlipperCollision(pbBall_t* ball, pbFlipper_t* flipper) +{ + vecFl_t closest = closestPointOnSegment(ball->pos, flipper->pos, pbFlipperGetTip(flipper)); + vecFl_t dir = subVecFl2d(ball->pos, closest); + float d = magVecFl2d(dir); + if (d == 0.0 || d > ball->radius + flipper->radius) + { + return; + } + + dir = divVecFl2d(dir, d); + + float corr = (ball->radius + flipper->radius - d); + ball->pos = addVecFl2d(ball->pos, mulVecFl2d(dir, corr)); + + // update velocity + + vecFl_t radius = closest; + radius = addVecFl2d(radius, mulVecFl2d(dir, flipper->radius)); + radius = subVecFl2d(radius, flipper->pos); + vecFl_t surfaceVel = perpendicularVecFl2d(radius); + surfaceVel = mulVecFl2d(surfaceVel, flipper->currentAngularVelocity); + + float v = dotVecFl2d(ball->vel, dir); + float vNew = dotVecFl2d(surfaceVel, dir); + + ball->vel = addVecFl2d(ball->vel, mulVecFl2d(dir, vNew - v)); +} + +/** + * @brief TODO + * + * @param ball + * @param line + * @return true + * @return false + */ +bool ballLineIntersection(pbBall_t* ball, pbLine_t* line) +{ + // Get the line segment from the list of walls + vecFl_t a = line->p1; + vecFl_t b = line->p2; + // Get the closest point on the segment to the center of the ball + vecFl_t c = closestPointOnSegment(ball->pos, a, b); + // Find the distance between the center of the ball and the closest point on the line + vecFl_t d = subVecFl2d(ball->pos, c); + float dist = magVecFl2d(d); + // If the distance is less than the radius, and the distance is less + // than the minimum distance, its the best collision + return (dist < ball->radius); +} + +/** + * @brief TODO doc + * + * @param ball + * @param lines + * @param true if the ball should be deleted, false if not + */ +static bool handleBallLineCollision(pbBall_t* ball, pbScene_t* scene) +{ + // find closest segment; + vecFl_t ballToClosest; + vecFl_t ab; + vecFl_t normal; + float minDist = FLT_MAX; + pbLine_t* cLine = NULL; + + // For each segment of the wall + for (int32_t i = 0; i < scene->numLines; i++) + { + pbLine_t* line = &scene->lines[i]; + + if (line->isUp) + { + // Get the line segment from the list of walls + vecFl_t a = line->p1; + vecFl_t b = line->p2; + // Get the closest point on the segment to the center of the ball + vecFl_t c = closestPointOnSegment(ball->pos, a, b); + // Find the distance between the center of the ball and the closest point on the line + vecFl_t d = subVecFl2d(ball->pos, c); + float dist = magVecFl2d(d); + // If the distance is less than the radius, and the distance is less + // than the minimum distance, its the best collision + if ((dist < ball->radius) && (dist < minDist)) + { + minDist = dist; + ballToClosest = d; + ab = subVecFl2d(b, a); + normal = perpendicularVecFl2d(ab); + cLine = line; + } + } + } + + // Check if there were any collisions + if (NULL == cLine) + { + return false; + } + + // push out to not clip + if (0 == minDist) + { + ballToClosest = normal; + minDist = magVecFl2d(normal); + } + ballToClosest = divVecFl2d(ballToClosest, minDist); + ball->pos = addVecFl2d(ball->pos, mulVecFl2d(ballToClosest, ball->radius - minDist)); // TODO epsilon here? + + float v = dotVecFl2d(ball->vel, ballToClosest); + if (cLine->pushVel) + { + // Adjust the velocity + ball->vel = addVecFl2d(ball->vel, mulVecFl2d(ballToClosest, cLine->pushVel - v)); + } + else + { + // update velocity + float vNew = ABS(v) * ball->restitution; // TODO care about wall's restitution? + ball->vel = addVecFl2d(ball->vel, mulVecFl2d(ballToClosest, vNew - v)); + } + + switch (cLine->type) + { + default: + case PB_WALL: + case PB_SPINNER: + case PB_LAUNCH_DOOR: + { + break; + } + case PB_STANDUP_TARGET: + { + pbSetState(scene, PBS_GAME_EVENT); + } + // Fall through + case PB_SLINGSHOT: + { + cLine->litTimer = 250000; + break; + } + case PB_DROP_TARGET: + { + cLine->isUp = false; + + // Check if all targets in the group are hit + bool someLineUp = false; + list_t* group = cLine->group; + node_t* node = group->first; + while (NULL != node) + { + pbLine_t* groupLine = node->val; + if (groupLine->isUp) + { + someLineUp = true; + break; + } + node = node->next; + } + + // If all lines are down + if (!someLineUp) + { + // Reset them + // TODO start a timer for this? Make sure a ball isn't touching the line before resetting? + node = group->first; + while (NULL != node) + { + // Start a timer to reset the target + ((pbLine_t*)node->val)->resetTimer = 3000000; + node = node->next; + } + } + break; + } + case PB_SCOOP: + { + // Count the scoop + scene->scoopCount++; + printf("Ball %" PRId32 " locked\n", scene->scoopCount); + if (3 == scene->scoopCount) + { + printf("Multiball!!!\n"); + pbStartMultiball(scene); + } + ball->scoopTimer = 2000000; + break; + } + case PB_BALL_LOST: + { + return true; + } + } + return false; +} + +/** + * @brief TODO + * + * @param launcher + * @param balls + * @param dt + */ +static void handleBallLauncherCollision(pbLauncher_t* launcher, pbBall_t* ball, float dt) +{ + if (ball->vel.y >= 0) + { + // Get the compressed Y level + float posY = launcher->pos.y + (launcher->impulse * launcher->height); + + // Check Y + if ((ball->pos.y + ball->radius > posY) && (ball->pos.y - ball->radius < posY)) + { + // Check X + if ((ball->pos.x > launcher->pos.x) && (ball->pos.x < launcher->pos.x + launcher->width)) + { + // Collision, set the position to be slightly touching + ball->pos.y = posY - ball->radius + 0.1f; + // Bounce a little + ball->vel = mulVecFl2d(ball->vel, -0.3f); + } + } + } +} diff --git a/attic/pinball/pinball_physics.h b/attic/pinball/pinball_physics.h new file mode 100644 index 000000000..8b03470c0 --- /dev/null +++ b/attic/pinball/pinball_physics.h @@ -0,0 +1,6 @@ +#pragma once + +#include "pinball_typedef.h" + +void pbSimulate(pbScene_t* scene, int32_t elapsedUs); +bool ballLineIntersection(pbBall_t* ball, pbLine_t* line); diff --git a/attic/pinball/pinball_point.c b/attic/pinball/pinball_point.c new file mode 100644 index 000000000..5ffa19c20 --- /dev/null +++ b/attic/pinball/pinball_point.c @@ -0,0 +1,25 @@ +#include "shapes.h" +#include "palette.h" + +#include "pinball_point.h" + +/** + * @brief TODO doc + * + * @param tableData + * @param scene + * @return int32_t + */ +int32_t readPointFromFile(uint8_t* tableData, pbScene_t* scene) +{ + pbPoint_t* point = &scene->points[scene->numPoints++]; + uint32_t dIdx = 0; + point->id = readInt16(tableData, &dIdx); + point->groupId = readInt8(tableData, &dIdx); + point->group = addToGroup(scene, point, point->groupId); + point->pos.x = readInt16(tableData, &dIdx); + point->pos.y = readInt16(tableData, &dIdx); + point->type = readInt8(tableData, &dIdx); + + return dIdx; +} diff --git a/attic/pinball/pinball_point.h b/attic/pinball/pinball_point.h new file mode 100644 index 000000000..d79dc6c9d --- /dev/null +++ b/attic/pinball/pinball_point.h @@ -0,0 +1,6 @@ +#pragma once + +#include "pinball_typedef.h" +#include "pinball_game.h" + +int32_t readPointFromFile(uint8_t* tableData, pbScene_t* scene); diff --git a/attic/pinball/pinball_rectangle.c b/attic/pinball/pinball_rectangle.c new file mode 100644 index 000000000..89df5560d --- /dev/null +++ b/attic/pinball/pinball_rectangle.c @@ -0,0 +1,65 @@ +#include +#include "geometryFl.h" + +#include "pinball_rectangle.h" + +/** + * @brief TODO doc + * + * @param tableData + * @param scene + * @return uint32_t + */ +uint32_t readRectangleFromFile(uint8_t* tableData, pbScene_t* scene) +{ + uint32_t dIdx = 0; + pbLauncher_t* launcher = &scene->launchers[scene->numLaunchers++]; + launcher->id = readInt16(tableData, &dIdx); + launcher->groupId = readInt8(tableData, &dIdx); + launcher->group = addToGroup(scene, launcher, launcher->groupId); + launcher->pos.x = readInt16(tableData, &dIdx); + launcher->pos.y = readInt16(tableData, &dIdx); + launcher->width = readInt16(tableData, &dIdx); + launcher->height = readInt16(tableData, &dIdx); + launcher->buttonHeld = false; + launcher->impulse = 0; + return dIdx; +} + +/** + * @brief TODO doc + * + * @param launcher + * @param balls + * @param dt + */ +void pbLauncherSimulate(pbLauncher_t* launcher, list_t* balls, float dt) +{ + if (launcher->buttonHeld) + { + launcher->impulse += (dt / 3); + if (launcher->impulse > 0.99f) + { + launcher->impulse = 0.99f; + } + } + else if (launcher->impulse) + { + rectangleFl_t r = {.pos = launcher->pos, .width = launcher->width, .height = launcher->height}; + // If touching a ball, transfer to a ball + node_t* bNode = balls->first; + while (bNode) + { + pbBall_t* ball = bNode->val; + circleFl_t b = {.pos = ball->pos, .radius = ball->radius}; + if (circleRectFlIntersection(b, r, NULL)) + { + ball->vel.y = (MAX_LAUNCHER_VELOCITY * launcher->impulse); + } + + bNode = bNode->next; + } + + launcher->impulse = 0; + } +} \ No newline at end of file diff --git a/attic/pinball/pinball_rectangle.h b/attic/pinball/pinball_rectangle.h new file mode 100644 index 000000000..dda040bfa --- /dev/null +++ b/attic/pinball/pinball_rectangle.h @@ -0,0 +1,9 @@ +#pragma once + +#include "pinball_typedef.h" +#include "pinball_game.h" + +#define MAX_LAUNCHER_VELOCITY -600 + +uint32_t readRectangleFromFile(uint8_t* tableData, pbScene_t* scene); +void pbLauncherSimulate(pbLauncher_t* launcher, list_t* balls, float dt); diff --git a/attic/pinball/pinball_triangle.c b/attic/pinball/pinball_triangle.c new file mode 100644 index 000000000..0f7011f77 --- /dev/null +++ b/attic/pinball/pinball_triangle.c @@ -0,0 +1,45 @@ +#include +#include "pinball_triangle.h" + +/** + * @brief TODO doc + * + * @param tableData + * @param scene + * @return uint32_t + */ +uint32_t readTriangleFromFile(uint8_t* tableData, pbScene_t* scene) +{ + pbTriangle_t* triangle = &scene->triangles[scene->numTriangles++]; + + uint32_t dIdx = 0; + triangle->id = readInt16(tableData, &dIdx); + triangle->groupId = readInt8(tableData, &dIdx); + triangle->group = addToGroup(scene, triangle, triangle->groupId); + triangle->p1.x = readInt16(tableData, &dIdx); + triangle->p1.y = readInt16(tableData, &dIdx); + triangle->p2.x = readInt16(tableData, &dIdx); + triangle->p2.y = readInt16(tableData, &dIdx); + triangle->p3.x = readInt16(tableData, &dIdx); + triangle->p3.y = readInt16(tableData, &dIdx); + + triangle->blinkTimer = 0; + triangle->isOn = false; + triangle->isBlinking = true; + + return dIdx; +} + +/** + * @brief + * + * @param tri + * @param elapsedUs + */ +void pbTriangleTimer(pbTriangle_t* tri, int32_t elapsedUs) +{ + if (tri->isBlinking) + { + RUN_TIMER_EVERY(tri->blinkTimer, 333333, elapsedUs, tri->isOn = !tri->isOn;); + } +} diff --git a/attic/pinball/pinball_triangle.h b/attic/pinball/pinball_triangle.h new file mode 100644 index 000000000..162a25cc1 --- /dev/null +++ b/attic/pinball/pinball_triangle.h @@ -0,0 +1,7 @@ +#pragma once + +#include "pinball_typedef.h" +#include "pinball_game.h" + +uint32_t readTriangleFromFile(uint8_t* tableData, pbScene_t* scene); +void pbTriangleTimer(pbTriangle_t* tri, int32_t elapsedUs); diff --git a/attic/pinball/pinball_typedef.h b/attic/pinball/pinball_typedef.h new file mode 100644 index 000000000..840f54168 --- /dev/null +++ b/attic/pinball/pinball_typedef.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include +#include + +#include "vector2d.h" +#include "vectorFl2d.h" +#include "macros.h" +#include "linked_list.h" + +#define PIN_INVALID_ID 0xFFFF + +typedef enum +{ + PBS_WAIT_TO_LAUNCH, + PBS_GAME_NO_EVENT, + PBS_GAME_EVENT, + PBS_BALL_OVER, + PBS_GAME_OVER, +} pbGameState_t; + +typedef enum +{ + PB_WALL, + PB_SLINGSHOT, + PB_DROP_TARGET, + PB_STANDUP_TARGET, + PB_SPINNER, + PB_SCOOP, + PB_BALL_LOST, + PB_LAUNCH_DOOR, +} pbLineType_t; + +typedef enum +{ + PB_BUMPER, + PB_ROLLOVER +} pbCircleType_t; + +typedef enum +{ + PB_BALL_SPAWN, + PB_ITEM_SPAWN, +} pbPointType_t; + +typedef struct +{ + uint16_t id; + uint8_t groupId; + list_t* group; + pbPointType_t type; + vecFl_t pos; +} pbPoint_t; + +typedef struct +{ + uint16_t id; + uint8_t groupId; + list_t* group; + pbLineType_t type; + vecFl_t p1; + vecFl_t p2; + float pushVel; + bool isUp; + int32_t litTimer; + int32_t resetTimer; +} pbLine_t; + +typedef struct +{ + // fixed + float radius; + vecFl_t pos; + float length; + float restAngle; + float maxRotation; + float sign; + float angularVelocity; + // changing + float rotation; + float currentAngularVelocity; + bool buttonHeld; + bool facingRight; +} pbFlipper_t; + +// TODO merge these +typedef struct +{ + float radius; + float mass; + float restitution; + vecFl_t pos; + vecFl_t vel; + int32_t scoopTimer; +} pbBall_t; + +typedef struct +{ + uint16_t id; + uint8_t groupId; + list_t* group; + float radius; + vecFl_t pos; + pbCircleType_t type; + float pushVel; + int32_t litTimer; +} pbCircle_t; + +typedef struct +{ + uint16_t id; + uint8_t groupId; + list_t* group; + vecFl_t p1; + vecFl_t p2; + vecFl_t p3; + bool isBlinking; + bool isOn; + int32_t blinkTimer; +} pbTriangle_t; + +typedef struct +{ + uint16_t id; + uint8_t groupId; + list_t* group; + vecFl_t pos; + float width; + float height; + bool buttonHeld; + float impulse; +} pbLauncher_t; + +typedef struct +{ + vecFl_t gravity; + int32_t score; + bool paused; + int32_t numGroups; + list_t* groups; + pbLine_t* lines; + int32_t numLines; + list_t balls; + pbCircle_t* circles; + int32_t numCircles; + pbFlipper_t* flippers; + int32_t numFlippers; + pbLauncher_t* launchers; + int32_t numLaunchers; + pbTriangle_t* triangles; + int32_t numTriangles; + pbPoint_t* points; + int32_t numPoints; + vec_t cameraOffset; + vecFl_t tableDim; + bool launchTubeClosed; + uint16_t touchedLoopId; + uint16_t loopHistory[3]; + int32_t saveTimer; + int32_t scoopCount; + int32_t ballCount; + pbGameState_t state; +} pbScene_t; \ No newline at end of file diff --git a/emulator/src/extensions/modes/ext_modes.c b/emulator/src/extensions/modes/ext_modes.c index c1b072b05..8bc83d92a 100644 --- a/emulator/src/extensions/modes/ext_modes.c +++ b/emulator/src/extensions/modes/ext_modes.c @@ -29,7 +29,6 @@ #include "mode_2048.h" #include "mode_bigbug.h" #include "mode_credits.h" -#include "mode_pinball.h" #include "mode_synth.h" #include "touchTest.h" #include "tunernome.h" @@ -71,7 +70,6 @@ static swadgeMode_t* allSwadgeModes[] = { &keebTestMode, &mainMenuMode, &modeCredits, - &pinballMode, &synthMode, &t48Mode, &timerMode, diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 548295324..70b675d68 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,10 +1,10 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" - "asset_loaders/heatshrink_decoder.c" - "asset_loaders/heatshrink_helper.c" "asset_loaders/fs_font.c" "asset_loaders/fs_json.c" "asset_loaders/fs_txt.c" "asset_loaders/fs_wsg.c" + "asset_loaders/heatshrink_decoder.c" + "asset_loaders/heatshrink_helper.c" "colorchord/DFT32.c" "colorchord/embeddedNf.c" "colorchord/embeddedOut.c" @@ -30,16 +30,11 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" "modes/games/bigbug/soundManager_bigbug.c" "modes/games/bigbug/tilemap_bigbug.c" "modes/games/bigbug/pathfinding_bigbug.c" - "modes/games/pinball/mode_pinball.c" - "modes/games/pinball/pinball_draw.c" - "modes/games/pinball/pinball_physics.c" - "modes/games/pinball/pinball_test.c" - "modes/games/pinball/pinball_zones.c" "modes/games/ultimateTTT/ultimateTTT.c" "modes/games/ultimateTTT/ultimateTTTgame.c" "modes/games/ultimateTTT/ultimateTTThowTo.c" - "modes/games/ultimateTTT/ultimateTTTp2p.c" "modes/games/ultimateTTT/ultimateTTTmarkerSelect.c" + "modes/games/ultimateTTT/ultimateTTTp2p.c" "modes/games/ultimateTTT/ultimateTTTresult.c" "modes/games/2048/2048_game.c" "modes/games/2048/2048_menus.c" @@ -57,16 +52,16 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" "modes/system/quickSettings/quickSettings.c" "modes/test/accelTest/accelTest.c" "modes/test/factoryTest/factoryTest.c" - "modes/test/touchTest/touchTest.c" "modes/test/keebTest/keebTest.c" + "modes/test/touchTest/touchTest.c" "modes/utilities/dance/dance.c" "modes/utilities/dance/portableDance.c" "modes/utilities/gamepad/gamepad.c" "modes/utilities/timer/modeTimer.c" "swadge2024.c" - "utils/color_utils.c" "utils/cnfs.c" "utils/cnfs_image.c" + "utils/color_utils.c" "utils/dialogBox.c" "utils/fl_math/geometryFl.c" "utils/fl_math/vectorFl2d.c" @@ -97,7 +92,7 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" crashwrap REQUIRES esp_timer spi_flash - INCLUDE_DIRS "." + INCLUDE_DIRS "./" "./asset_loaders" "./asset_loaders/common" "./colorchord" @@ -106,15 +101,14 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" "./midi" "./modes" "./modes/games" - "./modes/games/pinball" "./modes/games/bigbug" "./modes/games/ultimateTTT" "./modes/games/2048/" "./modes/music" "./modes/music/colorchord" "./modes/music/jukebox" - "./modes/music/usbsynth" "./modes/music/tunernome" + "./modes/music/usbsynth" "./modes/system" "./modes/system/credits" "./modes/system/intro" @@ -123,8 +117,8 @@ idf_component_register(SRCS "asset_loaders/common/heatshrink_encoder.c" "./modes/test" "./modes/test/accelTest" "./modes/test/factoryTest" - "./modes/test/touchTest" "./modes/test/keebTest" + "./modes/test/touchTest" "./modes/utilities" "./modes/utilities/dance" "./modes/utilities/gamepad" diff --git a/main/modes/games/pinball/mode_pinball.h b/main/modes/games/pinball/mode_pinball.h deleted file mode 100644 index 20546c649..000000000 --- a/main/modes/games/pinball/mode_pinball.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -//============================================================================== -// Includes -//============================================================================== - -#include -#include "swadge2024.h" - -//============================================================================== -// Defines -//============================================================================== - -#define PIN_US_PER_FRAME 16667 -#define NUM_ZONES 32 - -#define MAX_NUM_BALLS 512 -#define MAX_NUM_WALLS 1024 -#define MAX_NUM_BUMPERS 10 -#define MAX_NUM_TOUCHES 16 -#define MAX_NUM_FLIPPERS 6 - -#define NUM_FRAME_TIMES 60 - -//============================================================================== -// Enums -//============================================================================== - -typedef enum -{ - PIN_NO_SHAPE, - PIN_CIRCLE, - PIN_LINE, - PIN_RECT, - PIN_FLIPPER, -} pbShapeType_t; - -//============================================================================== -// Structs -//============================================================================== - -typedef struct -{ - circleFl_t c; - vecFl_t vel; // Velocity is in pixels per frame (@ 60fps, so pixels per 16.7ms) - vecFl_t accel; // Acceleration is pixels per frame squared - vecFl_t lastPos; // The previous postion, used to compare actual positional change to velocity - bool bounce; // true if the ball bounced this frame, false otherwise - uint32_t zoneMask; - paletteColor_t color; - bool filled; -} pbCircle_t; - -typedef struct -{ - lineFl_t l; - float length; - uint32_t zoneMask; - paletteColor_t color; -} pbLine_t; - -typedef struct -{ - rectangleFl_t r; - uint32_t zoneMask; - paletteColor_t color; -} pbRect_t; - -typedef struct -{ - pbCircle_t cPivot; ///< The circle that the flipper pivots on - pbCircle_t cTip; ///< The circle at the tip of the flipper - pbLine_t sideL; ///< The left side of the flipper when pointing upward - pbLine_t sideR; ///< The right side of the flipper when pointing upward - int32_t length; ///< The length of the flipper, from pivot center to tip center - float angle; ///< The current angle of the flipper - bool facingRight; ///< True if the flipper is facing right, false if left - bool buttonHeld; ///< True if the button is being held down, false if it is released - uint32_t zoneMask; ///< The zones this flipper is in -} pbFlipper_t; - -typedef struct -{ - const void* obj; - pbShapeType_t type; -} pbTouchRef_t; - -typedef struct -{ - pbCircle_t* balls; - uint32_t numBalls; - pbTouchRef_t** ballsTouching; - pbLine_t* walls; - uint32_t numWalls; - pbCircle_t* bumpers; - uint32_t numBumpers; - pbFlipper_t* flippers; - uint32_t numFlippers; - int32_t frameTimer; - pbRect_t zones[NUM_ZONES]; - font_t ibm_vga8; - - uint32_t frameTimes[NUM_FRAME_TIMES]; - uint32_t frameTimesIdx; -} pinball_t; - -//============================================================================== -// Extern variables -//============================================================================== - -extern swadgeMode_t pinballMode; diff --git a/main/modes/games/pinball/pinball_draw.c b/main/modes/games/pinball/pinball_draw.c deleted file mode 100644 index 0cdd62265..000000000 --- a/main/modes/games/pinball/pinball_draw.c +++ /dev/null @@ -1,134 +0,0 @@ -//============================================================================== -// Includes -//============================================================================== - -#include "pinball_draw.h" - -//============================================================================== -// Function Declarations -//============================================================================== - -static void drawPinCircle(pbCircle_t* c); -static void drawPinLine(pbLine_t* l); -// static void drawPinRect(pbRect_t* r); -static void drawPinFlipper(pbFlipper_t* f); - -//============================================================================== -// Functions -//============================================================================== - -/** - * @brief Draw a section of the background - * - * @param p Pinball state - * @param x The x coordinate of the background to draw - * @param y The y coordinate of the background to draw - * @param w The width of the background to draw - * @param h The height of the background to draw - */ -void pinballDrawBackground(pinball_t* p, int16_t x, int16_t y, int16_t w, int16_t h) -{ - // Fill with black - fillDisplayArea(x, y, x + w, y + h, c000); -} - -/** - * @brief Draw the foreground - * - * @param p Pinball state - */ -void pinballDrawForeground(pinball_t* p) -{ - // Draw walls - for (uint32_t wIdx = 0; wIdx < p->numWalls; wIdx++) - { - drawPinLine(&p->walls[wIdx]); - } - - // Draw bumpers - for (uint32_t uIdx = 0; uIdx < p->numBumpers; uIdx++) - { - drawPinCircle(&p->bumpers[uIdx]); - } - - // Draw balls - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - drawPinCircle(&p->balls[bIdx]); - } - - // Draw flippers - for (uint32_t fIdx = 0; fIdx < p->numFlippers; fIdx++) - { - drawPinFlipper(&p->flippers[fIdx]); - } - - // Debug draw zones - // for (int32_t i = 0; i < NUM_ZONES; i++) - // { - // drawPinRect(&p->zones[i]); - // } - - // Calculate and draw FPS - int32_t startIdx = (p->frameTimesIdx + 1) % NUM_FRAME_TIMES; - uint32_t tElapsed = p->frameTimes[p->frameTimesIdx] - p->frameTimes[startIdx]; - if (0 != tElapsed) - { - uint32_t fps = (1000000 * NUM_FRAME_TIMES) / tElapsed; - - char tmp[16]; - snprintf(tmp, sizeof(tmp) - 1, "%" PRIu32, fps); - drawText(&p->ibm_vga8, c555, tmp, 35, 2); - } -} - -/** - * @brief Draw a pinball circle - * - * @param c The circle to draw - */ -void drawPinCircle(pbCircle_t* circ) -{ - if (circ->filled) - { - drawCircleFilled((circ->c.pos.x), (circ->c.pos.y), (circ->c.radius), circ->color); - } - else - { - drawCircle((circ->c.pos.x), (circ->c.pos.y), (circ->c.radius), circ->color); - } -} - -/** - * @brief Draw a pinball line - * - * @param l The line to draw - */ -void drawPinLine(pbLine_t* line) -{ - drawLineFast((line->l.p1.x), (line->l.p1.y), (line->l.p2.x), (line->l.p2.y), line->color); -} - -/** - * @brief Draw a pinball rectangle - * - * @param r The rectangle to draw - */ -// void drawPinRect(pbRect_t* rect) -// { -// drawRect(rect->r.pos.x, rect->r.pos.y, rect->r.pos.x + rect->r.width, rect->r.pos.y + rect->r.height, -// rect->color); -// } - -/** - * @brief Draw a pinball flipper - * - * @param f The flipper to draw - */ -void drawPinFlipper(pbFlipper_t* f) -{ - drawPinCircle(&f->cPivot); - drawPinCircle(&f->cTip); - drawPinLine(&f->sideL); - drawPinLine(&f->sideR); -} diff --git a/main/modes/games/pinball/pinball_draw.h b/main/modes/games/pinball/pinball_draw.h deleted file mode 100644 index 2e10f138e..000000000 --- a/main/modes/games/pinball/pinball_draw.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "mode_pinball.h" - -void pinballDrawBackground(pinball_t* p, int16_t x, int16_t y, int16_t w, int16_t h); -void pinballDrawForeground(pinball_t* p); diff --git a/main/modes/games/pinball/pinball_physics.c b/main/modes/games/pinball/pinball_physics.c deleted file mode 100644 index 54568988f..000000000 --- a/main/modes/games/pinball/pinball_physics.c +++ /dev/null @@ -1,771 +0,0 @@ -//============================================================================== -// Includes -//============================================================================== - -#include -#include "pinball_physics.h" -#include "pinball_zones.h" - -//============================================================================== -// Function Declarations -//============================================================================== - -bool checkBallPbCircleCollision(pbCircle_t* ball, pbCircle_t* circle, pbTouchRef_t* touchRef); -bool checkBallPbLineCollision(pbCircle_t* ball, pbLine_t* line, pbTouchRef_t* touchRef); - -void checkBallBallCollisions(pinball_t* p); -void checkBallStaticCollision(pinball_t* p); -void sweepCheckFlippers(pinball_t* p); - -void moveBalls(pinball_t* p); - -void checkBallsNotTouching(pinball_t* p); -void setBallTouching(pbTouchRef_t* ballTouching, const void* obj, pbShapeType_t type); -pbShapeType_t ballIsTouching(pbTouchRef_t* ballTouching, const void* obj); - -void checkBallsAtRest(pinball_t* p); - -void moveBallBackFromLine(pbCircle_t* ball, pbLine_t* line, vecFl_t* collisionVec); -void moveBallBackFromCircle(pbCircle_t* ball, pbCircle_t* fixed); - -//============================================================================== -// Functions -//============================================================================== - -/** - * @brief TODO - * - * @param p - */ -void updatePinballPhysicsFrame(pinball_t* p) -{ - // Move balls along new vectors - moveBalls(p); - - // Move flippers rotationally - sweepCheckFlippers(p); - - // If there are multiple balls - if (1 < p->numBalls) - { - // Check for ball-ball collisions - checkBallBallCollisions(p); - } - - // Check for collisions between balls and static objects - checkBallStaticCollision(p); - - // Check if balls are actually at rest - checkBallsAtRest(p); - - // Clear references to balls touching things after moving - checkBallsNotTouching(p); -} - -/** - * @brief TODO - * - * @param p - */ -void checkBallBallCollisions(pinball_t* p) -{ - // For each ball, check collisions with other balls - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - pbCircle_t* ball = &p->balls[bIdx]; - for (uint32_t obIdx = bIdx + 1; obIdx < p->numBalls; obIdx++) - { - pbCircle_t* otherBall = &p->balls[obIdx]; - vecFl_t centerToCenter; - // Check for a new collision - if ((ball->zoneMask & otherBall->zoneMask) // In the same zone - && circleCircleFlIntersection(ball->c, otherBall->c, NULL, ¢erToCenter)) // and intersecting - { - // Move balls backwards equally from the midpoint to not clip - float halfDistM = (ball->c.radius + otherBall->c.radius - EPSILON) / 2.0f; - vecFl_t midwayPoint = divVecFl2d(addVecFl2d(ball->c.pos, otherBall->c.pos), 2.0f); - vecFl_t vecFromMid = mulVecFl2d(normVecFl2d(centerToCenter), halfDistM); - - // Move both balls - ball->c.pos = addVecFl2d(midwayPoint, vecFromMid); - otherBall->c.pos = subVecFl2d(midwayPoint, vecFromMid); - - // If the balls aren't touching yet, adjust velocities (bounce) - if (PIN_NO_SHAPE == ballIsTouching(p->ballsTouching[bIdx], otherBall)) - { - // Math for the first ball - vecFl_t v1 = ball->vel; - vecFl_t x1 = ball->c.pos; - vecFl_t v2 = otherBall->vel; - vecFl_t x2 = otherBall->c.pos; - vecFl_t x1_x2 = subVecFl2d(x1, x2); - vecFl_t v1_v2 = subVecFl2d(v1, v2); - float xSqMag = sqMagVecFl2d(x1_x2); - vecFl_t ballNewVel = ball->vel; - if (xSqMag > 0) - { - ballNewVel = subVecFl2d(v1, mulVecFl2d(x1_x2, (dotVecFl2d(v1_v2, x1_x2) / xSqMag))); - } - - // Flip everything for the other ball - v1 = otherBall->vel; - x1 = otherBall->c.pos; - v2 = ball->vel; - x2 = ball->c.pos; - x1_x2 = subVecFl2d(x1, x2); - v1_v2 = subVecFl2d(v1, v2); - xSqMag = sqMagVecFl2d(x1_x2); - if (xSqMag > 0) - { - otherBall->vel - = subVecFl2d(v1, mulVecFl2d(x1_x2, (dotVecFl2d(v1_v2, x1_x2) / sqMagVecFl2d(x1_x2)))); - } - - // Set the new velocity for the first ball after finding the second's - ball->vel = ballNewVel; - - // The balls are touching each other - setBallTouching(p->ballsTouching[bIdx], otherBall, PIN_CIRCLE); - setBallTouching(p->ballsTouching[obIdx], ball, PIN_CIRCLE); - - // Mark both balls as bounced - ball->bounce = true; - otherBall->bounce = true; - } - } - } - } -} - -/** - * @brief TODO - * - * @param ball - * @param circle - * @param touchRef - * @return true - * @return false - */ -bool checkBallPbCircleCollision(pbCircle_t* ball, pbCircle_t* circle, pbTouchRef_t* touchRef) -{ - bool bounced = false; - vecFl_t collisionVec; - - // Check for a collision - if ((ball->zoneMask & circle->zoneMask) // In the same zone - && circleCircleFlIntersection(ball->c, circle->c, NULL, &collisionVec)) // and intersecting - { - // Find the normalized vector along the collision normal - vecFl_t reflVec = normVecFl2d(collisionVec); - - // If the ball isn't already touching the circle - if (PIN_NO_SHAPE == ballIsTouching(touchRef, circle)) - { - // Bounced on a circle - ball->bounce = true; - bounced = true; - // Reflect the velocity vector along the normal between the two radii - // See http://www.sunshine2k.de/articles/coding/vectorreflection/vectorreflection.html - ball->vel = subVecFl2d(ball->vel, mulVecFl2d(reflVec, (2 * dotVecFl2d(ball->vel, reflVec)))); - // Lose some speed on the bounce - ball->vel = mulVecFl2d(ball->vel, WALL_BOUNCINESS); - // printf("%d,%.4f,%.4f\n", __LINE__, ball->vel.x, ball->vel.y); - // Mark this circle as being touched to not double-bounce - setBallTouching(touchRef, circle, PIN_CIRCLE); - } - - // Move ball back to not clip into the circle - moveBallBackFromCircle(ball, circle); - // ball->c.pos = addVecFl2d(circle->c.pos, mulVecFl2d(reflVec, ball->c.radius + circle->c.radius - EPSILON)); - } - return bounced; -} - -/** - * @brief TODO - * - * @param ball - * @param line - * @param touchRef - * @return true - * @return false - */ -bool checkBallPbLineCollision(pbCircle_t* ball, pbLine_t* line, pbTouchRef_t* touchRef) -{ - bool bounced = false; - vecFl_t collisionVec; - vecFl_t cpOnLine; - // Check for a collision - if ((ball->zoneMask & line->zoneMask) // In the same zone - && circleLineFlIntersection(ball->c, line->l, true, &cpOnLine, &collisionVec)) // and intersecting - { - /* TODO this reflection can have bad results when colliding with the tip of a line. - * The center-center vector can get weird if the ball moves fast and clips into the tip. - * The solution is probably to binary-search-move the ball as far as it'll go without clipping - */ - - // Find the normalized vector along the collision normal - vecFl_t reflVec = normVecFl2d(collisionVec); - - // If the ball isn't already touching the line - if (PIN_NO_SHAPE == ballIsTouching(touchRef, line)) - { - // The dot product with the collision normal is how much Y velocity component there is. - // If this value is small the ball should slide down the line (i.e. don't lose velocity on the bounce) - float velDotColNorm = dotVecFl2d(ball->vel, reflVec); - - // Bounce it by reflecting across the collision normal - ball->vel = subVecFl2d(ball->vel, mulVecFl2d(reflVec, (2 * velDotColNorm))); - - // Check if the ball should slide (i.e. not lose velocity) or bounce (i.e. lose velocity) - // 0 means the ball's velocity is parallel to the wall (slide) - // -mag(vel) means the ball's velocity is perpendicular to the wall (bounce) - if (velDotColNorm < -0.2f) - { - // Lose some speed on the bounce. - ball->vel = mulVecFl2d(ball->vel, WALL_BOUNCINESS); - } - - // Mark this line as being touched to not double-bounce - setBallTouching(touchRef, line, PIN_LINE); - - // Bounced off a line - ball->bounce = true; - bounced = true; - } - - // Move ball back to not clip into the bumper - // TODO accommodate line end collisions (circle) - moveBallBackFromLine(ball, line, &reflVec); - // ball->c.pos = addVecFl2d(cpOnLine, mulVecFl2d(reflVec, ball->c.radius - EPSILON)); - } - return bounced; -} - -/** - * @brief TODO - * - * @param p - */ -void checkBallStaticCollision(pinball_t* p) -{ - // For each ball, check collisions with static objects - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - // Reference and integer representation - pbCircle_t* ball = &p->balls[bIdx]; - - // Iterate over all bumpers - for (uint32_t uIdx = 0; uIdx < p->numBumpers; uIdx++) - { - checkBallPbCircleCollision(ball, &p->bumpers[uIdx], p->ballsTouching[bIdx]); - } - - // Iterate over all walls - for (uint32_t wIdx = 0; wIdx < p->numWalls; wIdx++) - { - checkBallPbLineCollision(ball, &p->walls[wIdx], p->ballsTouching[bIdx]); - } - } -} - -/** - * @brief TODO - * - * @param p - */ -void sweepCheckFlippers(pinball_t* p) -{ - // For each flipper - for (uint32_t fIdx = 0; fIdx < p->numFlippers; fIdx++) - { - pbFlipper_t* flipper = &p->flippers[fIdx]; - - // Check if the flipper is moving up or down - float angularVel = 0; - if (flipper->buttonHeld) - { - angularVel = FLIPPER_UP_DEGREES_PER_FRAME; - } - else - { - angularVel = -FLIPPER_DOWN_DEGREES_PER_FRAME; - } - - // Find the bounds for the flipper depending on the direction it's facing - float lBound = 0; - float uBound = 0; - if (flipper->facingRight) - { - lBound = M_PI_2 - FLIPPER_UP_ANGLE; - uBound = M_PI_2 + FLIPPER_DOWN_ANGLE; - // Flip velocity if facing right - angularVel *= -1; - } - else - { - lBound = (M_PI + M_PI_2) - FLIPPER_DOWN_ANGLE; - uBound = (M_PI + M_PI_2) + FLIPPER_UP_ANGLE; - } - - // Flipper starts here - float sweepStart = flipper->angle; - // Flipper ends here, bounded - float sweepEnd = flipper->angle + angularVel; - sweepEnd = CLAMP((sweepEnd), lBound, uBound); - - // Find sweep steps if in motion - float sweepStep = 0.0f; - int numSteps = 0; - if (sweepStart == sweepEnd) - { - // Flipper not in motion - angularVel = 0; - sweepStep = 0; - numSteps = 1; - } - else - { - // Flipper in motion - // TODO large sweep steps kill framerate.... - numSteps = 8; - sweepStep = (sweepEnd - sweepStart) / (float)numSteps; - } - - // Move the flipper a little, then check for collisions - for (int32_t step = 0; step < numSteps; step++) - { - // Sweep the flipper a little - flipper->angle += sweepStep; - updateFlipperPos(flipper); - - // Normal collision checks - // For each ball, check collisions with flippers objects - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - // Reference and integer representation - pbCircle_t* ball = &p->balls[bIdx]; - pbTouchRef_t* touchRef = p->ballsTouching[bIdx]; - - if (ball->zoneMask & flipper->zoneMask) - { - // Check if the ball is touching any part of the flipper - bool touching = false; - vecFl_t colPoint, colVec; - if (circleLineFlIntersection(ball->c, flipper->sideL.l, false, &colPoint, &colVec)) - { - // Move ball back to not clip into the flipper - colVec = normVecFl2d(colVec); - moveBallBackFromLine(ball, &flipper->sideL, &colVec); - // ball->c.pos = addVecFl2d(colPoint, mulVecFl2d(normVecFl2d(colVec), ball->c.radius - - // EPSILON)); - touching = true; - } - if (circleLineFlIntersection(ball->c, flipper->sideR.l, false, &colPoint, &colVec)) - { - // Move ball back to not clip into the flipper - colVec = normVecFl2d(colVec); - moveBallBackFromLine(ball, &flipper->sideR, &colVec); - // ball->c.pos = addVecFl2d(colPoint, mulVecFl2d(normVecFl2d(colVec), ball->c.radius - - // EPSILON)); - touching = true; - } - if (circleCircleFlIntersection(ball->c, flipper->cPivot.c, &colPoint, &colVec)) - { - // Move ball back to not clip into the flipper - moveBallBackFromCircle(ball, &flipper->cPivot); - // ball->c.pos = addVecFl2d(colPoint, mulVecFl2d(normVecFl2d(colVec), ball->c.radius - - // EPSILON)); - touching = true; - } - if (circleCircleFlIntersection(ball->c, flipper->cTip.c, &colPoint, &colVec)) - { - // Move ball back to not clip into the flipper - moveBallBackFromCircle(ball, &flipper->cTip); - // ball->c.pos = addVecFl2d(colPoint, mulVecFl2d(normVecFl2d(colVec), ball->c.radius - - // EPSILON)); - touching = true; - } - - // If the ball is touching the flipper for the first time - if (touching && (PIN_NO_SHAPE == ballIsTouching(touchRef, flipper))) - { - // Mark them as in contact - setBallTouching(touchRef, flipper, PIN_FLIPPER); - - // Bounce the ball - vecFl_t reflVec = normVecFl2d(colVec); - ball->vel = subVecFl2d(ball->vel, mulVecFl2d(reflVec, (2 * dotVecFl2d(ball->vel, reflVec)))); - - // If the flipper is in motion - if (0 != angularVel) - { - // Get the distance between the pivot and the ball - float dist = magVecFl2d(subVecFl2d(flipper->cPivot.c.pos, ball->c.pos)); - // Convert angular velocity of the flipper to linear velocity at that point - float impulseMag = (ABS(angularVel) * dist); - - // Impart an impulse on the ball along the collision normal - ball->vel = addVecFl2d(ball->vel, mulVecFl2d(reflVec, impulseMag)); - } - } - } - } - } - - // Make sure the final angle is correct - flipper->angle = sweepEnd; - } -} - -/** - * @brief TODO - * - * @param p - */ -void moveBalls(pinball_t* p) -{ - // For each ball, check collisions with static objects - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - pbCircle_t* ball = &p->balls[bIdx]; - - // Acceleration changes velocity - // TODO adjust gravity vector when on top of a line - ball->vel = addVecFl2d(ball->vel, ball->accel); - // printf("%d,%.4f,%.4f\n", __LINE__, ball->vel.x, ball->vel.y); - - // Save the last position to check if the ball is at rest - ball->lastPos = ball->c.pos; - - // Move the ball - ball->c.pos.x += (ball->vel.x); - ball->c.pos.y += (ball->vel.y); - - // Update zone mask - // TODO update this after nudging ball too? - ball->zoneMask = pinZoneCircle(p, *ball); - } -} - -/** - * @brief TODO - * - * @param p - */ -void checkBallsNotTouching(pinball_t* p) -{ - // For each ball - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - pbCircle_t* ball = &p->balls[bIdx]; - // For each thing it could be touching - for (uint32_t tIdx = 0; tIdx < MAX_NUM_TOUCHES; tIdx++) - { - pbTouchRef_t* tr = &p->ballsTouching[bIdx][tIdx]; - // If it's touching a thing - if (NULL != tr->obj) - { - bool setNotTouching = false; - switch (tr->type) - { - case PIN_CIRCLE: - { - const pbCircle_t* other = (const pbCircle_t*)tr->obj; - if ((0 == (ball->zoneMask & other->zoneMask)) // Not in the same zone - || !circleCircleFlIntersection(ball->c, other->c, NULL, NULL)) // or not touching - { - setNotTouching = true; - } - break; - } - case PIN_LINE: - { - const pbLine_t* other = (const pbLine_t*)tr->obj; - if ((0 == (ball->zoneMask & other->zoneMask)) // Not in the same zone - || !circleLineFlIntersection(ball->c, other->l, true, NULL, NULL)) // or not touching - { - setNotTouching = true; - } - break; - } - case PIN_RECT: - { - const pbRect_t* other = (const pbRect_t*)tr->obj; - if ((0 == (ball->zoneMask & other->zoneMask)) // Not in the same zone - || !circleRectFlIntersection(ball->c, other->r, NULL)) // or not touching - { - setNotTouching = true; - } - break; - } - case PIN_FLIPPER: - { - const pbFlipper_t* flipper = (const pbFlipper_t*)tr->obj; - if ((0 == (ball->zoneMask & flipper->zoneMask)) // Not in the same zone - || !(circleCircleFlIntersection(ball->c, flipper->cPivot.c, NULL, NULL) // or not touching - || circleCircleFlIntersection(ball->c, flipper->cTip.c, NULL, NULL) - || circleLineFlIntersection(ball->c, flipper->sideL.l, false, NULL, NULL) - || circleLineFlIntersection(ball->c, flipper->sideR.l, false, NULL, NULL))) - { - setNotTouching = true; - } - break; - } - default: - case PIN_NO_SHAPE: - { - // Not touching anything... - break; - } - } - - // If the object is no longer touching - if (setNotTouching) - { - // Clear the reference - tr->obj = NULL; - tr->type = PIN_NO_SHAPE; - } - } - } - } -} - -/** - * @brief TODO - * - * @param ballTouching - * @param obj - * @param type - */ -void setBallTouching(pbTouchRef_t* ballTouching, const void* obj, pbShapeType_t type) -{ - for (uint32_t i = 0; i < MAX_NUM_TOUCHES; i++) - { - if (NULL == ballTouching->obj) - { - ballTouching->obj = obj; - ballTouching->type = type; - return; - } - } -} - -/** - * @brief TODO - * - * @param ballTouching - * @param obj - * @return pbShapeType_t - */ -pbShapeType_t ballIsTouching(pbTouchRef_t* ballTouching, const void* obj) -{ - for (uint32_t i = 0; i < MAX_NUM_TOUCHES; i++) - { - if (ballTouching->obj == obj) - { - return ballTouching->type; - } - } - return PIN_NO_SHAPE; -} - -/** - * @brief TODO - * - * @param p - */ -void checkBallsAtRest(pinball_t* p) -{ - // For each ball - for (uint32_t bIdx = 0; bIdx < p->numBalls; bIdx++) - { - pbCircle_t* ball = &p->balls[bIdx]; - - // If the ball didn't bounce this frame (which can adjust position to not clip) - if (false == ball->bounce) - { - // And the ball is traveling downward - if (ball->vel.y > 0) - { - // See how far the ball actually traveled - float velM = sqMagVecFl2d(ball->vel); - - // If the ball is moving slowly - if (velM < 1.0f) - { - // And it didn't move as much as it should have - float posDeltaM = sqMagVecFl2d(subVecFl2d(ball->c.pos, ball->lastPos)); - if ((velM - posDeltaM) > 0.01f) - { - // Stop the ball altogether to not accumulate velocity - ball->vel.x = 0; - ball->vel.y = 0; - } - } - } - } - else - { - // Clear the bounce flag - ball->bounce = false; - } - } -} - -/** - * @brief Update the position of a flipper's tip circle and line walls depending on the flipper's angle. The pivot - * circle never changes. - * - * @param f The flipper to update - */ -void updateFlipperPos(pbFlipper_t* f) -{ - // Make sure the angle is between 0 and 360 - while (f->angle < 0) - { - f->angle += (2 * M_PI); - } - while (f->angle >= (2 * M_PI)) - { - f->angle -= (2 * M_PI); - } - - // This is the set of points to rotate - vecFl_t points[] = { - { - // Center of the tip of the flipper - .x = 0, - .y = -f->length, - }, - { - // Bottom point of the right side - .x = f->cPivot.c.radius, - .y = 0, - }, - { - // Top point of the right side - .x = f->cTip.c.radius, - .y = -f->length, - }, - { - // Bottom point of the left side - .x = -f->cPivot.c.radius, - .y = 0, - }, - { - // Top point of the left side - .x = -f->cTip.c.radius, - .y = -f->length, - }, - }; - - // This is where to write the rotated points - vecFl_t* dests[] = { - &f->cTip.c.pos, &f->sideR.l.p1, &f->sideR.l.p2, &f->sideL.l.p1, &f->sideL.l.p2, - }; - - // Get the trig values for all rotations, just once - float sinA = sinf(f->angle); - float cosA = cosf(f->angle); - - // For each point - for (int32_t idx = 0; idx < ARRAY_SIZE(points); idx++) - { - // Rotate the point - float oldX = points[idx].x; - float oldY = points[idx].y; - float newX = (oldX * cosA) - (oldY * sinA); - float newY = (oldX * sinA) + (oldY * cosA); - - // Translate relative to the pivot point - dests[idx]->x = f->cPivot.c.pos.x + newX; - dests[idx]->y = f->cPivot.c.pos.y + newY; - } -} - -/** - * @brief TODO - * - * see - * https://github.com/AEFeinstein/Super-2024-Swadge-FW/blob/4d7d41d9ab0e3a7670a967a0a4cd72364a8c39ac/main/modes/pinball/pinball_physics.c - * - * @param ball - * @param line - * @param collisionVec - */ -void moveBallBackFromLine(pbCircle_t* ball, pbLine_t* line, vecFl_t* collisionNorm) -{ - // Do a bunch of work to adjust the ball's position to not clip into this line. - // First create a copy of the line - lineFl_t barrierLine = line->l; - - // Then find the normal vector to the barrier, pointed towards the ball - vecFl_t barrierOffset = mulVecFl2d(*collisionNorm, ball->c.radius); - - // Translate the along the normal vector, the distance of the radius - // This creates a line parallel to the wall where the ball's center could be - barrierLine.p1 = addVecFl2d(barrierLine.p1, barrierOffset); - barrierLine.p2 = addVecFl2d(barrierLine.p2, barrierOffset); - - // Create a line for the ball's motion - lineFl_t ballLine = { - .p1 = ball->c.pos, - .p2 = addVecFl2d(ball->c.pos, ball->vel), - }; - - // Find the intersection between where the ball's center could be and the ball's trajectory. - // Set the ball's position to that point - ball->c.pos = infLineIntersectionPoint(barrierLine, ballLine); -} - -/** - * @brief TODO - * - * @param ball - * @param fixed - */ -void moveBallBackFromCircle(pbCircle_t* ball, pbCircle_t* fixed) -{ - // Create a barrier circle around the fixed that the ball's center can't pass through - circleFl_t barrier = fixed->c; - barrier.radius += ball->c.radius; - - // Create a line for the ball's motion - lineFl_t ballLine = { - .p1 = ball->c.pos, - .p2 = addVecFl2d(ball->c.pos, ball->vel), - }; - - vecFl_t intersection_1; - vecFl_t intersection_2; - switch (circleLineFlIntersectionPoints(barrier, ballLine, &intersection_1, &intersection_2)) - { - default: - case 0: - { - // No intersection? - break; - } - case 1: - { - ball->c.pos = intersection_1; - break; - } - case 2: - { - // Two intersection points, use the one closer to ball->c.pos - float diff1 = sqMagVecFl2d(subVecFl2d(ball->c.pos, intersection_1)); - float diff2 = sqMagVecFl2d(subVecFl2d(ball->c.pos, intersection_2)); - if (diff1 < diff2) - { - ball->c.pos = intersection_1; - } - else - { - ball->c.pos = intersection_2; - } - } - } -} \ No newline at end of file diff --git a/main/modes/games/pinball/pinball_physics.h b/main/modes/games/pinball/pinball_physics.h deleted file mode 100644 index ac33bb964..000000000 --- a/main/modes/games/pinball/pinball_physics.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "mode_pinball.h" - -#define PINBALL_GRAVITY (1 / 60.0f) ///< Gravitational constant - -#define WALL_BOUNCINESS 0.5f - -#define FLIPPER_UP_DEGREES_PER_FRAME 0.296705972839036f ///< Number of degrees (17) to move a flipper up per 60fps frame -#define FLIPPER_DOWN_DEGREES_PER_FRAME \ - 0.174532925199433f ///< Number of degrees (10) to move a flipper down per 60fps frame -#define FLIPPER_UP_ANGLE 0.349065850398866f ///< Angle of a flipper (20) when actuated -#define FLIPPER_DOWN_ANGLE 0.523598775598299f ///< Angle of a flipper (30) when idle - -void updateFlipperPos(pbFlipper_t* f); -void updatePinballPhysicsFrame(pinball_t* p); diff --git a/main/modes/games/pinball/pinball_test.c b/main/modes/games/pinball/pinball_test.c deleted file mode 100644 index 3af6cb4d4..000000000 --- a/main/modes/games/pinball/pinball_test.c +++ /dev/null @@ -1,316 +0,0 @@ -//============================================================================== -// Includes -//============================================================================== - -#include -#include "pinball_test.h" -#include "pinball_zones.h" -#include "pinball_physics.h" - -//============================================================================== -// Functions -//============================================================================== - -/** - * @brief TODO - * - * @param p - * @param x - * @param y - */ -void pbCreateBall(pinball_t* p, float x, float y) -{ - pbCircle_t* ball = &p->balls[p->numBalls++]; -#define BALL_RAD 5 - ball->c.radius = (BALL_RAD); - ball->c.pos.x = x; - ball->c.pos.y = y; - ball->lastPos.x = x; - ball->lastPos.y = y; - // #define MAX_VEL 128 - ball->vel.x = 0; - ball->vel.y = 0; - ball->accel.x = 0; - ball->accel.y = PINBALL_GRAVITY; - ball->color = c500; - ball->filled = true; -} - -/** - * @brief Create balls with random positions and velocities - * - * @param p The pinball state - * @param numBalls The number of balls to create - */ -void createRandomBalls(pinball_t* p, int32_t numBalls) -{ - // Don't overflow - if (numBalls > MAX_NUM_BALLS) - { - numBalls = MAX_NUM_BALLS; - } - p->numBalls = 0; - - // Make some balls - for (int32_t i = 0; i < numBalls; i++) - { - pbCircle_t* ball = &p->balls[p->numBalls++]; -#define BALL_RAD 5 - ball->c.radius = (BALL_RAD); - ball->c.pos.x = ((BALL_RAD + 1) + (esp_random() % (TFT_WIDTH - 2 * (BALL_RAD + 1)))); - ball->c.pos.y = ((BALL_RAD + 1) + (esp_random() % (TFT_HEIGHT - 2 * (BALL_RAD + 1)))); - ball->lastPos.x = ball->c.pos.x; - ball->lastPos.y = ball->c.pos.x; - ball->vel.x = 0; - ball->vel.y = 5 / 60.0f; - ball->accel.x = 0; - ball->accel.y = PINBALL_GRAVITY; - ball->color = esp_random() % cTransparent; - ball->filled = true; - } -} - -/** - * @brief TODO - * - * @param p - * @param numBumpers - */ -void createRandomBumpers(pinball_t* p, int32_t numBumpers) -{ - int fixedBumpersPlaced = 0; - vecFl_t fixedBumpers[] = { - { - .x = 140, - .y = 120, - }, - { - .x = 100, - .y = 80, - }, - { - .x = 180, - .y = 80, - }, - }; - numBumpers += ARRAY_SIZE(fixedBumpers); - - // Don't overflow - if (numBumpers > MAX_NUM_BUMPERS) - { - numBumpers = MAX_NUM_BUMPERS; - } - p->numBumpers = 0; - - // Make some balls - while (numBumpers > p->numBumpers) - { - pbCircle_t bumper = {0}; -#define BUMPER_RAD 10 - bumper.c.radius = (BUMPER_RAD); - if (fixedBumpersPlaced < ARRAY_SIZE(fixedBumpers)) - { - bumper.c.pos = fixedBumpers[fixedBumpersPlaced]; - fixedBumpersPlaced++; - } - else - { - bumper.c.pos.x = ((BUMPER_RAD + 1) + (esp_random() % (TFT_WIDTH - 2 * (BUMPER_RAD + 1)))); - bumper.c.pos.y = ((BUMPER_RAD + 1) + (esp_random() % (TFT_HEIGHT - 2 * (BUMPER_RAD + 1)))); - } - bumper.color = c050; - bumper.filled = false; - bumper.zoneMask = pinZoneCircle(p, bumper); - - bool intersection = false; - for (int32_t ol = 0; ol < p->numWalls; ol++) - { - if (circleLineFlIntersection(bumper.c, p->walls[ol].l, true, NULL, NULL)) - { - intersection = true; - break; - } - } - - for (int32_t ob = 0; ob < p->numBumpers; ob++) - { - if (circleCircleFlIntersection(bumper.c, p->bumpers[ob].c, NULL, NULL)) - { - intersection = true; - break; - } - } - - if (!intersection) - { - memcpy(&p->bumpers[p->numBumpers], &bumper, sizeof(pbCircle_t)); - p->numBumpers++; - } - } -} - -/** - * @brief Create random static walls - * - * @param p The pinball state - * @param numWalls The number of walls to create - */ -void createRandomWalls(pinball_t* p, int32_t numWalls) -{ - // Always Create a boundary - lineFl_t corners[] = { - { - .p1 = {.x = (0), .y = (0)}, - .p2 = {.x = (TFT_WIDTH - 1), .y = (0)}, - }, - { - .p1 = {.x = (TFT_WIDTH - 1), .y = (0)}, - .p2 = {.x = (TFT_WIDTH - 1), .y = (TFT_HEIGHT - 1)}, - }, - { - .p1 = {.x = (TFT_WIDTH - 1), .y = (TFT_HEIGHT - 1)}, - .p2 = {.x = (0), .y = (TFT_HEIGHT - 1)}, - }, - { - .p1 = {.x = (0), .y = (TFT_HEIGHT - 1)}, - .p2 = {.x = (0), .y = (0)}, - }, - // { - // .p1 = {.x = 0, .y = 90}, - // .p2 = {.x = 50, .y = 110}, - // }, - // { - // .p1 = {.x = 140, .y = 70}, - // .p2 = {.x = 210, .y = 80}, - // }, - - { - .p1 = {.x = 0, .y = 120}, - .p2 = {.x = 94, .y = 188}, - }, - { - .p1 = {.x = 279, .y = 120}, - .p2 = {.x = 186, .y = 188}, - }, - }; - - // Don't overflow - if (numWalls > MAX_NUM_WALLS - ARRAY_SIZE(corners)) - { - numWalls = MAX_NUM_WALLS - ARRAY_SIZE(corners); - } - p->numWalls = 0; - - for (int32_t i = 0; i < ARRAY_SIZE(corners); i++) - { - pbLine_t* pbl = &p->walls[p->numWalls++]; - pbl->l.p1.x = corners[i].p1.x; - pbl->l.p1.y = corners[i].p1.y; - pbl->l.p2.x = corners[i].p2.x; - pbl->l.p2.y = corners[i].p2.y; - vecFl_t delta = { - .x = pbl->l.p2.x - pbl->l.p1.x, - .y = pbl->l.p2.y - pbl->l.p1.y, - }; - pbl->length = magVecFl2d(delta); - pbl->color = c555; - pbl->zoneMask = pinZoneLine(p, *pbl); - } - - // Make a bunch of random lines - while (numWalls > p->numWalls) - { - pbLine_t pbl = {0}; // = &p->walls[p->numWalls++]; - -#define L_LEN 12 - - pbl.l.p1.x = (L_LEN + (esp_random() % (TFT_WIDTH - (L_LEN * 2)))); - pbl.l.p1.y = (L_LEN + (esp_random() % (TFT_HEIGHT - (L_LEN * 2)))); - pbl.l.p2.x = pbl.l.p1.x + ((esp_random() % (L_LEN * 2)) - L_LEN); - pbl.l.p2.y = pbl.l.p1.y + ((esp_random() % (L_LEN * 2)) - L_LEN); - vecFl_t delta = { - .x = pbl.l.p2.x - pbl.l.p1.x, - .y = pbl.l.p2.y - pbl.l.p1.y, - }; - pbl.length = magVecFl2d(delta); - pbl.color = c005; // esp_random() % cTransparent; - - if (pbl.l.p1.x == pbl.l.p2.x && pbl.l.p1.y == pbl.l.p2.y) - { - if (esp_random() % 2) - { - pbl.l.p2.x = (pbl.l.p2.x) + ((1)); - } - else - { - pbl.l.p2.y = (pbl.l.p2.y) + ((1)); - } - } - - pbl.zoneMask = pinZoneLine(p, pbl); - - bool intersection = false; - for (int32_t ol = 0; ol < p->numWalls; ol++) - { - if (lineLineFlIntersection(pbl.l, p->walls[ol].l)) - { - intersection = true; - } - } - - for (int32_t ob = 0; ob < p->numBumpers; ob++) - { - if (circleLineFlIntersection(p->bumpers[ob].c, pbl.l, true, NULL, NULL)) - { - intersection = true; - } - } - - if (!intersection) - { - memcpy(&p->walls[p->numWalls], &pbl, sizeof(pbLine_t)); - p->numWalls++; - } - } -} - -/** - * @brief Create a Flipper - * - * @param p The pinball state - * @param pivot_x - * @param pivot_y - * @param facingRight - */ -void createFlipper(pinball_t* p, int32_t pivot_x, int32_t pivot_y, bool facingRight) -{ - pbFlipper_t* f = &p->flippers[p->numFlippers]; - - f->cPivot.color = c505; - f->cTip.color = c505; - f->sideL.color = c505; - f->sideR.color = c505; - - f->cPivot.c.pos.x = pivot_x; - f->cPivot.c.pos.y = pivot_y; - f->cPivot.c.radius = 10; - f->length = 40; - f->cTip.c.radius = 5; - f->facingRight = facingRight; - - f->zoneMask = pinZoneFlipper(p, f); - - // Update angle and position after setting zone - if (f->facingRight) - { - f->angle = M_PI_2 + FLIPPER_DOWN_ANGLE; - } - else - { - f->angle = M_PI + M_PI_2 - FLIPPER_DOWN_ANGLE; - } - updateFlipperPos(f); - - // Update flipper count - p->numFlippers++; -} diff --git a/main/modes/games/pinball/pinball_test.h b/main/modes/games/pinball/pinball_test.h deleted file mode 100644 index 75903a64f..000000000 --- a/main/modes/games/pinball/pinball_test.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "mode_pinball.h" - -void pbCreateBall(pinball_t* p, float x, float y); -void createRandomBalls(pinball_t* p, int32_t numBalls); -void createRandomWalls(pinball_t* p, int32_t numWalls); -void createRandomBumpers(pinball_t* p, int32_t numBumpers); -void createFlipper(pinball_t* p, int32_t pivot_x, int32_t pivot_y, bool facingRight); diff --git a/main/modes/games/pinball/pinball_zones.c b/main/modes/games/pinball/pinball_zones.c deleted file mode 100644 index a6ea07ba7..000000000 --- a/main/modes/games/pinball/pinball_zones.c +++ /dev/null @@ -1,178 +0,0 @@ -//============================================================================== -// Includes -//============================================================================== - -#include -#include "pinball_zones.h" -#include "pinball_physics.h" - -//============================================================================== -// Functions -//============================================================================== - -/** - * @brief Split a table up into zones. Each object is assigned to one or more zones for a very quick first-pass - * collision check. - * - * @param p The pinball state - */ -void createTableZones(pinball_t* p) -{ - // Split the space into zones. Start with one big rectangle - int32_t splitOffset = (NUM_ZONES >> 1); - p->zones[0].r.pos.x = 0; - p->zones[0].r.pos.y = 0; - p->zones[0].r.width = (TFT_WIDTH); - p->zones[0].r.height = (TFT_HEIGHT); - p->zones[0].color = c505; - - // While more zoning needs to happen - while (splitOffset) - { - // Iterate over current zones, back to front - for (int32_t i = NUM_ZONES - 1; i >= 0; i--) - { - // If this is a real zone - if (0 < p->zones[i].r.height) - { - // Split it either vertically or horizontally, depending on which is larger - if (p->zones[i].r.height > p->zones[i].r.width) - { - // Split vertically - int32_t newHeight_1 = p->zones[i].r.height / 2; - int32_t newHeight_2 = p->zones[i].r.height - newHeight_1; - - // Shrink the original zone - p->zones[i].r.height = newHeight_1; - - // Create the new zone - p->zones[i + splitOffset].r.height = newHeight_2; - p->zones[i + splitOffset].r.pos.y = p->zones[i].r.pos.y + p->zones[i].r.height; - - p->zones[i + splitOffset].r.width = p->zones[i].r.width; - p->zones[i + splitOffset].r.pos.x = p->zones[i].r.pos.x; - } - else - { - // Split horizontally - int32_t newWidth_1 = p->zones[i].r.width / 2; - int32_t newWidth_2 = p->zones[i].r.width - newWidth_1; - - // Shrink the original zone - p->zones[i].r.width = newWidth_1; - - // Create the new zone - p->zones[i + splitOffset].r.width = newWidth_2; - p->zones[i + splitOffset].r.pos.x = p->zones[i].r.pos.x + p->zones[i].r.width; - - p->zones[i + splitOffset].r.height = p->zones[i].r.height; - p->zones[i + splitOffset].r.pos.y = p->zones[i].r.pos.y; - } - - // Give it a random color, just because - p->zones[i + splitOffset].color = esp_random() % cTransparent; - } - } - - // Half the split offset - splitOffset /= 2; - } -} - -/** - * @brief Determine which table zones a rectangle is in - * - * @param p The pinball state - * @param r The rectangle to zone - * @return A bitmask of the zones the rectangle is in - */ -uint32_t pinZoneRect(pinball_t* p, pbRect_t rect) -{ - uint32_t zoneMask = 0; - for (int16_t z = 0; z < NUM_ZONES; z++) - { - if (rectRectFlIntersection(p->zones[z].r, rect.r, NULL)) - { - zoneMask |= (1 << z); - } - } - return zoneMask; -} - -/** - * @brief Determine which table zones a line is in - * - * @param p The pinball state - * @param l The line to zone - * @return A bitmask of the zones the line is in - */ -uint32_t pinZoneLine(pinball_t* p, pbLine_t line) -{ - uint32_t zoneMask = 0; - for (int16_t z = 0; z < NUM_ZONES; z++) - { - if (rectLineFlIntersection(p->zones[z].r, line.l, NULL)) - { - zoneMask |= (1 << z); - } - } - return zoneMask; -} - -/** - * @brief Determine which table zones a circle is in - * - * @param p The pinball state - * @param r The circle to zone - * @return A bitmask of the zones the circle is in - */ -uint32_t pinZoneCircle(pinball_t* p, pbCircle_t circ) -{ - uint32_t zoneMask = 0; - for (int16_t z = 0; z < NUM_ZONES; z++) - { - if (circleRectFlIntersection(circ.c, p->zones[z].r, NULL)) - { - zoneMask |= (1 << z); - } - } - return zoneMask; -} - -/** - * @brief Determine which table zones a flipper is in. Note, this function will modify the flipper's angle - * - * @param p The pinball state - * @param f The flipper to zone - * @return A bitmask of the zones the circle is in - */ -uint32_t pinZoneFlipper(pinball_t* p, pbFlipper_t* f) -{ - pbRect_t boundingBox = {0}; - if (f->facingRight) - { - // Record the X position - boundingBox.r.pos.x = (f->cPivot.c.pos.x - f->cPivot.c.radius); - } - else - { - // Record the X position - boundingBox.r.pos.x = (f->cPivot.c.pos.x - f->length - f->cTip.c.radius); - } - - // Width is the same when facing left and right - boundingBox.r.width = (f->length + f->cPivot.c.radius + f->cTip.c.radius); - - // Height is the same too. Move the flipper up and record the Y start - f->angle = M_PI_2 - FLIPPER_UP_ANGLE; - updateFlipperPos(f); - boundingBox.r.pos.y = (f->cTip.c.pos.y - f->cTip.c.radius); - - // Move the flipper down and record the Y end - f->angle = M_PI_2 + FLIPPER_DOWN_ANGLE; - updateFlipperPos(f); - boundingBox.r.height = (f->cTip.c.pos.y + f->cTip.c.radius) - boundingBox.r.pos.y; - - // Return the zones of the bounding box - return pinZoneRect(p, boundingBox); -} \ No newline at end of file diff --git a/main/modes/games/pinball/pinball_zones.h b/main/modes/games/pinball/pinball_zones.h deleted file mode 100644 index c080c60ae..000000000 --- a/main/modes/games/pinball/pinball_zones.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "mode_pinball.h" - -void createTableZones(pinball_t* p); -uint32_t pinZoneRect(pinball_t* p, pbRect_t r); -uint32_t pinZoneLine(pinball_t* p, pbLine_t l); -uint32_t pinZoneCircle(pinball_t* p, pbCircle_t c); -uint32_t pinZoneFlipper(pinball_t* p, pbFlipper_t* f); diff --git a/main/modes/music/jukebox/jukebox.c b/main/modes/music/jukebox/jukebox.c index 4288dbe0c..6f8957037 100644 --- a/main/modes/music/jukebox/jukebox.c +++ b/main/modes/music/jukebox/jukebox.c @@ -32,7 +32,6 @@ #include "mainMenu.h" #include "modeTimer.h" #include "mode_credits.h" -#include "mode_pinball.h" #include "touchTest.h" #include "tunernome.h" #include "midiPlayer.h" diff --git a/main/modes/system/mainMenu/mainMenu.c b/main/modes/system/mainMenu/mainMenu.c index 9c6ac3bdd..ca0b8100b 100644 --- a/main/modes/system/mainMenu/mainMenu.c +++ b/main/modes/system/mainMenu/mainMenu.c @@ -16,7 +16,6 @@ #include "mainMenu.h" #include "modeTimer.h" #include "mode_credits.h" -#include "mode_pinball.h" #include "mode_bigbug.h" #include "mode_synth.h" #include "ultimateTTT.h" @@ -152,7 +151,6 @@ static void mainMenuEnterMode(void) // Add single items mainMenu->menu = startSubMenu(mainMenu->menu, "Games"); addSingleItemToMenu(mainMenu->menu, tttMode.modeName); - addSingleItemToMenu(mainMenu->menu, pinballMode.modeName); addSingleItemToMenu(mainMenu->menu, t48Mode.modeName); addSingleItemToMenu(mainMenu->menu, bigbugMode.modeName); mainMenu->menu = endSubMenu(mainMenu->menu); @@ -360,10 +358,6 @@ static void mainMenuCb(const char* label, bool selected, uint32_t settingVal) { switchToSwadgeMode(&bigbugMode); } - else if (label == pinballMode.modeName) - { - switchToSwadgeMode(&pinballMode); - } else if (label == tttMode.modeName) { switchToSwadgeMode(&tttMode); diff --git a/main/utils/fl_math/vectorFl2d.c b/main/utils/fl_math/vectorFl2d.c index 10aba55c0..bc5a0b938 100644 --- a/main/utils/fl_math/vectorFl2d.c +++ b/main/utils/fl_math/vectorFl2d.c @@ -134,4 +134,19 @@ vecFl_t normVecFl2d(vecFl_t in) .y = in.y / len, }; return norm; -} \ No newline at end of file +} + +/** + * @brief Return a vector perpendicular to the input + * + * @param in The input vector + * @return The perpendicular vector + */ +vecFl_t perpendicularVecFl2d(vecFl_t in) +{ + vecFl_t perp = { + .x = -in.y, + .y = in.x, + }; + return perp; +} diff --git a/main/utils/fl_math/vectorFl2d.h b/main/utils/fl_math/vectorFl2d.h index b4cc03248..fe75eced2 100644 --- a/main/utils/fl_math/vectorFl2d.h +++ b/main/utils/fl_math/vectorFl2d.h @@ -49,5 +49,6 @@ vecFl_t rotateVecFl2d(vecFl_t vector, float radians); float magVecFl2d(vecFl_t vector); float sqMagVecFl2d(vecFl_t vector); vecFl_t normVecFl2d(vecFl_t in); +vecFl_t perpendicularVecFl2d(vecFl_t in); #endif \ No newline at end of file diff --git a/main/utils/macros.h b/main/utils/macros.h index 34793c526..23026f2a5 100644 --- a/main/utils/macros.h +++ b/main/utils/macros.h @@ -47,9 +47,9 @@ */ #ifdef __APPLE__ /* Force a compilation error if condition is true, but also produce a - result (of value 0 and type size_t), so the expression can be used - e.g. in a structure initializer (or where-ever else comma expressions - aren't permitted). */ + result (of value 0 and type size_t), so the expression can be used + e.g. in a structure initializer (or where-ever else comma expressions + aren't permitted). */ #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int : -!!(e); })) /// Helper macro to determine the number of elements in an array. Should not be used directly @@ -79,4 +79,25 @@ */ #define POS_MODULO_ADD(a, b, d) ((a + (b % d) + d) % d) -#endif \ No newline at end of file +/** + * @brief Run timer_code every period, using tracking it with timer + * + * @param timer The accumulator variable, must persist between calls + * @param period The period at which timer_code should be run + * @param elapsed The time elapsed since this was last called + * @param timer_code The code to execute every period + */ +#define RUN_TIMER_EVERY(timer, period, elapsed, timer_code) \ + do \ + { \ + timer += elapsed; \ + while (timer > period) \ + { \ + timer -= period; \ + { \ + timer_code \ + } \ + } \ + } while (0) + +#endif diff --git a/makefile b/makefile index bede280b0..60555451d 100644 --- a/makefile +++ b/makefile @@ -322,7 +322,7 @@ assets: ./tools/assets_preprocessor/assets_preprocessor -i ./assets/ -o ./assets_image/ # To build the main file, you have to compile the objects -$(EXECUTABLE): $(OBJECTS) +$(EXECUTABLE): $(CNFS_FILE) $(OBJECTS) $(CC) $(OBJECTS) $(LIBRARY_FLAGS) -o $@ # This compiles each c file into an o file diff --git a/tools/svg-to-pinball/.gitignore b/tools/svg-to-pinball/.gitignore new file mode 100755 index 000000000..dfe5670df --- /dev/null +++ b/tools/svg-to-pinball/.gitignore @@ -0,0 +1,4 @@ +*.xml +*.xsd +*.svg +*.bin \ No newline at end of file diff --git a/tools/svg-to-pinball/.vscode/launch.json b/tools/svg-to-pinball/.vscode/launch.json new file mode 100755 index 000000000..2f59b07d4 --- /dev/null +++ b/tools/svg-to-pinball/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: svg-to-pinball.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/svg-to-pinball.py", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/tools/svg-to-pinball/.vscode/settings.json b/tools/svg-to-pinball/.vscode/settings.json new file mode 100755 index 000000000..1c83c6cb0 --- /dev/null +++ b/tools/svg-to-pinball/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "cSpell.words": [ + "Bezier", + "Subpaths", + "svgelements" + ] +} \ No newline at end of file diff --git a/tools/svg-to-pinball/README.md b/tools/svg-to-pinball/README.md new file mode 100755 index 000000000..2798c4065 --- /dev/null +++ b/tools/svg-to-pinball/README.md @@ -0,0 +1,55 @@ +If objects aren't where they're supposed to be, svgelements may not be applying transforms correctly. Use this Inkscape plugin to apply transforms to objects before saving the SVG: https://github.com/Klowner/inkscape-applytransforms + +## File Format + +1. Number of Groups +1. Number of lines + * Line objects +1. Number of circles + * circles objects +1. Number of rectangles + * rectangles objects +1. Number of flippers + * flippers objects + +### Line +**Byte**|**Size**|**Value** +:-----:|:-----:|:-----: +0|2|ID +2|1|Group ID +3|2|p1.x +5|2|p1.y +7|2|p2.x +9|2|p2.y +11|1|Type +12|1|Push Velocity +13|1|Is Solid + +### Circle +**Byte**|**Size**|**Value** +:-----:|:-----:|:-----: +0|2|ID +2|1|Group ID +3|2|x +5|2|y +7|1|radius +8|1|Push Velocity + +### Rectangle +**Byte**|**Size**|**Value** +:-----:|:-----:|:-----: +0|2|ID +2|1|Group ID +3|2|x +5|2|y +7|2|width +9|2|height + +### Flipper +**Byte**|**Size**|**Value** +:-----:|:-----:|:-----: +0|2|x +2|2|y +4|1|Radius +5|1|Length +6|1|Facing Right \ No newline at end of file diff --git a/tools/svg-to-pinball/gentable.sh b/tools/svg-to-pinball/gentable.sh new file mode 100755 index 000000000..38168d84c --- /dev/null +++ b/tools/svg-to-pinball/gentable.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +find ../../ -iname cnfs_image* -delete +find ../../ -iname table.bin -delete +find ../../ -iname pinball.raw -delete +make -C ../cnfs/ clean +python svg-to-pinball.py +cp table.bin ../../assets/pinball.raw diff --git a/tools/svg-to-pinball/pinball.svg b/tools/svg-to-pinball/pinball.svg new file mode 100755 index 000000000..88b9b5383 --- /dev/null +++ b/tools/svg-to-pinball/pinball.svg @@ -0,0 +1,778 @@ + + + + diff --git a/tools/svg-to-pinball/requirements.txt b/tools/svg-to-pinball/requirements.txt new file mode 100755 index 000000000..56a41c393 --- /dev/null +++ b/tools/svg-to-pinball/requirements.txt @@ -0,0 +1 @@ +svgelements==1.9.6 diff --git a/tools/svg-to-pinball/svg-to-pinball.py b/tools/svg-to-pinball/svg-to-pinball.py new file mode 100755 index 000000000..5f48dd7a3 --- /dev/null +++ b/tools/svg-to-pinball/svg-to-pinball.py @@ -0,0 +1,487 @@ +from svgelements import SVG +from svgelements import Group +from svgelements import Path +from svgelements import Point +from svgelements import Circle +from svgelements import Rect +from math import sqrt, pow +from enum import Enum + +groups = [] + + +def getIntGroupId(gId: str) -> int: + try: + if gId.startswith("group_"): + gInt = int(gId.split("_")[1]) + if gInt not in groups: + groups.append(gInt) + return gInt + return 0 + except: + return 0 + + +def getIntId(id: str) -> int: + try: + return int(id) + except: + return 0 + + +class LineType(Enum): + JS_WALL = 0 + JS_SLINGSHOT = 1 + JS_DROP_TARGET = 2 + JS_STANDUP_TARGET = 3 + JS_SPINNER = 4 + JS_SCOOP = 5 + JS_BALL_LOST = 6 + JS_LAUNCH_DOOR = 7 + + +class CircleType(Enum): + JS_BUMPER = 0 + JS_ROLLOVER = 1 + + +class PointType(Enum): + JS_BALL_SPAWN = 0 + JS_ITEM_SPAWN = 1 + + +class xyPoint: + def __init__(self, p: Point = None, x: int = 0, y: int = 0) -> None: + if p is not None: + self.x: int = int(p.x) + self.y: int = int(p.y) + else: + self.x: int = int(x) + self.y: int = int(y) + + def toBytes(self) -> bytearray: + return bytearray( + [(self.x >> 8) & 0xFF, self.x & 0xFF, (self.y >> 8) & 0xFF, self.y & 0xFF] + ) + + def __eq__(self, other: object) -> bool: + return self.x == other.x and self.y == other.y + + +class pbPoint: + def __init__(self, pos: xyPoint, type: LineType, gId: str, id: str) -> None: + self.pos = pos + self.type: int = type.value + self.gId: int = getIntGroupId(gId) + self.id: int = getIntId(id) + + def toBytes(self) -> bytearray: + b = bytearray([(self.id >> 8), self.id & 0xFF, self.gId]) + b.extend(self.pos.toBytes()) + b.append(self.type) + return b + + +class pbLine: + def __init__( + self, p1: xyPoint, p2: xyPoint, type: LineType, gId: str, id: str + ) -> None: + self.p1 = p1 + self.p2 = p2 + self.type: int = type.value + self.gId: int = getIntGroupId(gId) + self.id: int = getIntId(id) + + match (type): + case LineType.JS_WALL: + self.isSolid = True + self.pushVel = 0 + pass + case LineType.JS_SLINGSHOT: + self.isSolid = True + self.pushVel = 80 + pass + case LineType.JS_DROP_TARGET: + self.isSolid = True + self.pushVel = 40 + pass + case LineType.JS_STANDUP_TARGET: + self.isSolid = True + self.pushVel = 0 + pass + case LineType.JS_SPINNER: + self.isSolid = False + self.pushVel = 0 + pass + case LineType.JS_SCOOP: + self.isSolid = True + self.pushVel = 0 + pass + case LineType.JS_BALL_LOST: + self.isSolid = True + self.pushVel = 0 + pass + case LineType.JS_LAUNCH_DOOR: + self.isSolid = False + self.pushVel = 0 + + def __str__(self) -> str: + return "{.p1 = {.x = %d, .y = %d}, .p2 = {.x = %d, .y = %d}}," % ( + self.p1.x, + self.p1.y, + self.p2.x, + self.p2.y, + ) + + def toBytes(self) -> bytearray: + b = bytearray([(self.id >> 8), self.id & 0xFF, self.gId]) + b.extend(self.p1.toBytes()) + b.extend(self.p2.toBytes()) + b.append(self.type) + b.append(self.pushVel) + b.append(self.isSolid) + # print(' '.join(['%02X' % x for x in b])) + return b + + +class pbCircle: + def __init__( + self, + pos: xyPoint, + radius: int, + type: CircleType, + pushVel: int, + gId: str, + id: str, + ) -> None: + self.position = pos + self.radius = int(radius) + self.type = type.value + self.gId: int = getIntGroupId(gId) + self.id: int = getIntId(id) + self.pushVel = int(pushVel) + + def __str__(self) -> str: + return "{.pos = {.x = %d, .y = %d}, .radius = %d}," % ( + self.position.x, + self.position.y, + self.radius, + ) + + def toBytes(self) -> bytearray: + b = bytearray([(self.id >> 8), self.id & 0xFF, self.gId]) + b.extend(self.position.toBytes()) + b.append(self.radius) + b.append(self.type) + b.append(self.pushVel) + # print(' '.join(['%02X' % x for x in b])) + return b + + +class pbRectangle: + def __init__(self, position: xyPoint, size: xyPoint, gId: str, id: str) -> None: + self.position = position + self.size = size + self.gId: int = getIntGroupId(gId) + self.id: int = getIntId(id) + + def __str__(self) -> str: + return "{.pos = {.x = %d, .y = %d}, .width = %d, .height = %d}," % ( + self.position.x, + self.position.y, + self.size.x, + self.size.y, + ) + + def toBytes(self) -> bytearray: + b = bytearray([(self.id >> 8), self.id & 0xFF, self.gId]) + b.extend(self.position.toBytes()) + b.extend(self.size.toBytes()) + # print(' '.join(['%02X' % x for x in b])) + return b + + +class pbTriangle: + def __init__(self, vertices: list[xyPoint], gId: str, id: str) -> None: + self.vertices = vertices + self.gId: int = getIntGroupId(gId) + self.id: int = getIntId(id) + + def toBytes(self) -> bytearray: + b = bytearray([(self.id >> 8), self.id & 0xFF, self.gId]) + for point in self.vertices: + b.extend(point.toBytes()) + return b + + +class pbFlipper: + def __init__( + self, pivot: xyPoint, radius: int, length: int, facingRight: bool + ) -> None: + self.pivot = pivot + self.radius = int(radius) + self.length = int(length) + self.facingRight = bool(facingRight) + + def __str__(self) -> str: + return ( + "{.cPivot = {.pos = {.x = %d, .y = %d}, .radius = %d}, .len = %d, .facingRight = %s}," + % ( + self.pivot.x, + self.pivot.y, + self.radius, + self.length, + "true" if self.facingRight else "false", + ) + ) + + def toBytes(self) -> bytearray: + b = bytearray() + b.extend(self.pivot.toBytes()) + b.append(self.radius) + b.append(self.length) + b.append(self.facingRight) + # print(' '.join(['%02X' % x for x in b])) + return b + + +def extractCircles(gs: list, type: CircleType, gId: str) -> list[pbCircle]: + """Recursively extract all circles from this list of SVG things + + Args: + gs (list): A list that contains Group and Circle + + Returns: + list[str]: A list of C strings for the circles + """ + circles = [] + for g in gs: + if isinstance(g, Circle): + circles.append( + pbCircle( + xyPoint(x=g.cx, y=g.cy), (g.rx + g.ry) / 2, type, 120, gId, g.id + ) + ) + elif isinstance(g, Group): + circles.extend(extractCircles(g, type, g.id)) + else: + print("Found " + str(type(g)) + " when extracting Circles") + return circles + + +def extractPoints(gs: list, type: PointType, gId: str) -> list[pbPoint]: + """Recursively extract all points from this list of SVG things + + Args: + gs (list): A list that contains Group and Point + + Returns: + list[str]: A list of C strings for the points + """ + points = [] + for g in gs: + if isinstance(g, Circle): + points.append(pbPoint(xyPoint(x=g.cx, y=g.cy), type, gId, g.id)) + elif isinstance(g, Group): + points.extend(extractPoints(g, type, g.id)) + else: + print("Found " + str(type(g)) + " when extracting Points") + return points + + +def extractRectangles(gs: list, gId: str) -> list[pbRectangle]: + """Recursively extract all circles from this list of SVG things + + Args: + gs (list): A list that contains Group and Circle + + Returns: + list[str]: A list of C strings for the circles + """ + rectangles = [] + for g in gs: + if isinstance(g, Rect): + rectangles.append( + pbRectangle( + xyPoint(x=g.x, y=g.y), xyPoint(x=g.width, y=g.height), gId, g.id + ) + ) + elif isinstance(g, Group): + rectangles.extend(extractRectangles(g, g.id)) + else: + print("Found " + str(type(g)) + " when extracting Rects") + return rectangles + + +def extractPaths(gs: list, lineType: LineType, gId: str) -> list[pbLine]: + """Recursively extract all paths from this list of SVG things + + Args: + gs (list): A list that contains Group and Path + + Returns: + list[str]: A list of C strings for the path segments + """ + lines = [] + for g in gs: + if isinstance(g, Path): + lastPoint: Point = None + point: Point + for point in g.as_points(): + if lastPoint is not None and lastPoint != point: + lines.append( + pbLine( + xyPoint(p=lastPoint), xyPoint(p=point), lineType, gId, g.id + ) + ) + lastPoint = point + elif isinstance(g, Group): + lines.extend(extractPaths(g, lineType, g.id)) + else: + print("Found " + str(type(g)) + " when extracting Paths") + return lines + + +def extractTriangles(gs: list, gId: str) -> list[pbTriangle]: + """Recursively extract all triangles from this list of SVG things + + Args: + gs (list): A list that contains Group and Path + + Returns: + list[str]: A list of C strings for the path segments + """ + triangles = [] + vertices = [] + for g in gs: + if isinstance(g, Path): + point: Point + for point in g.as_points(): + pbp = xyPoint(p=point) + + if 3 == len(vertices) and pbp == vertices[0]: + # Save the triangle + triangles.append(pbTriangle(vertices, gId, g.id)) + # Start a new one + vertices = [] + elif len(vertices) == 0 or pbp != vertices[-1]: + vertices.append(pbp) + elif isinstance(g, Group): + triangles.extend(extractTriangles(g, g.id)) + else: + print("Found " + str(type(g)) + " when extracting Triangles") + return triangles + + +def extractFlippers(gs: list, gId: str) -> list[pbFlipper]: + """Recursively extract all flippers (groups of circles and paths) from this list of SVG things + + Args: + gs (list): A list that contains stuff + + Returns: + list[str]: A list of C strings for the path segments + """ + lines = [] + flipperParts: list[Circle] = [] + for g in gs: + if isinstance(g, Circle): + flipperParts.append(g) + elif isinstance(g, Path): + pass + elif isinstance(g, Group): + lines.extend(extractFlippers(g, g.id)) + else: + print("Found " + str(type(g)) + " when extracting Flippers") + + if 2 == len(flipperParts): + if "pivot" in flipperParts[0].id.lower(): + pivot = flipperParts[0] + tip = flipperParts[1] + else: + pivot = flipperParts[1] + tip = flipperParts[0] + + if pivot.cx < tip.cx: + facingRight = True + else: + facingRight = False + + flipperLen = sqrt(pow(pivot.cx - tip.cx, 2) + pow(pivot.cy - tip.cy, 2)) + + lines.append( + pbFlipper( + xyPoint(x=pivot.cx, y=pivot.cy), pivot.rx, flipperLen, facingRight + ) + ) + + return lines + + +def addLength(tableData: bytearray, array: int): + length = len(array) + b = [(length >> 8) & 0xFF, (length) & 0xFF] + tableData.extend(b) + # print(' '.join(['%02X' % x for x in b])) + + +def main(): + # Load the SVG + g: Group = SVG().parse("pinball.svg") + + lines: list[pbLine] = [] + lines.extend(extractPaths(g.objects["350_Walls"], LineType.JS_WALL, None)) + lines.extend(extractPaths(g.objects["300_Scoops"], LineType.JS_SCOOP, None)) + lines.extend(extractPaths(g.objects["250_Slingshots"], LineType.JS_SLINGSHOT, None)) + lines.extend(extractPaths(g.objects["200_Drop_Targets"], LineType.JS_DROP_TARGET, None)) + lines.extend( + extractPaths(g.objects["150_Standup_Targets"], LineType.JS_STANDUP_TARGET, None) + ) + lines.extend(extractPaths(g.objects["400_Ball_Lost"], LineType.JS_BALL_LOST, None)) + lines.extend(extractPaths(g.objects["650_Launch_Door"], LineType.JS_LAUNCH_DOOR, None)) + + circles: list[pbCircle] = [] + circles.extend(extractCircles(g.objects["450_Rollovers"], CircleType.JS_ROLLOVER, None)) + circles.extend(extractCircles(g.objects["500_Bumpers"], CircleType.JS_BUMPER, None)) + + points: list[pbPoint] = [] + points.extend(extractPoints(g.objects["050_Ball_Spawn"], PointType.JS_BALL_SPAWN, None)) + points.extend(extractPoints(g.objects["100_Item_Spawn"], PointType.JS_ITEM_SPAWN, None)) + + launchers = extractRectangles(g.objects["600_Launchers"], None) + flippers = extractFlippers(g.objects["550_Flippers"], None) + triangles = extractTriangles(g.objects["000_Indicators"], None) + + tableData: bytearray = bytearray() + tableData.append(max(groups)) + + addLength(tableData, lines) + for line in sorted(lines, key=lambda x: x.id): + tableData.extend(line.toBytes()) + + addLength(tableData, circles) + for circle in sorted(circles, key=lambda x: x.id): + tableData.extend(circle.toBytes()) + + addLength(tableData, launchers) + for launcher in sorted(launchers, key=lambda x: x.id): + tableData.extend(launcher.toBytes()) + + addLength(tableData, flippers) + for flipper in flippers: + tableData.extend(flipper.toBytes()) + + addLength(tableData, triangles) + for triangle in sorted(triangles, key=lambda x: x.id): + tableData.extend(triangle.toBytes()) + + addLength(tableData, points) + for point in sorted(points, key=lambda x: x.id): + tableData.extend(point.toBytes()) + + with open("table.bin", "wb") as outFile: + outFile.write(tableData) + + +if __name__ == "__main__": + main()