From 5ac44c7448548064590f696dda3fd48ce92a508e Mon Sep 17 00:00:00 2001 From: A-Larsen Date: Wed, 14 Oct 2026 22:34:59 -0400 Subject: [PATCH 1/1] first commit --- 2d-projectile-motion/Makefile | 11 + 2d-projectile-motion/balls.c | 244 +++++ 2d-projectile-motion/docs/formulas.adoc | 28 + 2d-projectile-motion/docs/formulas.html | 550 ++++++++++ LICENSE | 339 +++++++ Makehtml | 24 + README.adoc | 52 + bounce/Makefile | 11 + bounce/balls | Bin 0 -> 45136 bytes bounce/balls.c | 336 +++++++ .../chatgpt_dump/circle-circle-collision.md | 151 +++ .../circle-rectange-SDL2-collision.md | 119 +++ .../circle-rectangle-collision.md | 171 ++++ bounce/docs/chatgpt_dump/physics.md | 227 +++++ bounce/docs/formulas.adoc | 32 + bounce/docs/formulas.html | 564 +++++++++++ collisions/Makefile | 11 + collisions/balls | Bin 0 -> 62216 bytes collisions/balls.c | 622 ++++++++++++ .../Collision-Resolution-Loop-Freezes.adoc | 192 ++++ .../Collision-Resolution-Loop-Freezes.html | 738 ++++++++++++++ .../Collision-Resolution-Loop-Freezes2.adoc | 95 ++ .../Collision-Resolution-Loop-Freezes2.html | 609 +++++++++++ collisions/docs/formulas.adoc | 126 +++ collisions/docs/formulas.html | 745 ++++++++++++++ docinfo.conf | 3 + notes.adoc | 12 + rendered.html | 951 ++++++++++++++++++ 28 files changed, 6963 insertions(+) create mode 100644 2d-projectile-motion/Makefile create mode 100644 2d-projectile-motion/balls.c create mode 100644 2d-projectile-motion/docs/formulas.adoc create mode 100644 2d-projectile-motion/docs/formulas.html create mode 100644 LICENSE create mode 100755 Makehtml create mode 100644 README.adoc create mode 100644 bounce/Makefile create mode 100755 bounce/balls create mode 100644 bounce/balls.c create mode 100644 bounce/docs/chatgpt_dump/circle-circle-collision.md create mode 100644 bounce/docs/chatgpt_dump/circle-rectange-SDL2-collision.md create mode 100644 bounce/docs/chatgpt_dump/circle-rectangle-collision.md create mode 100644 bounce/docs/chatgpt_dump/physics.md create mode 100644 bounce/docs/formulas.adoc create mode 100644 bounce/docs/formulas.html create mode 100644 collisions/Makefile create mode 100755 collisions/balls create mode 100644 collisions/balls.c create mode 100644 collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.adoc create mode 100644 collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.html create mode 100644 collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.adoc create mode 100644 collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.html create mode 100644 collisions/docs/formulas.adoc create mode 100644 collisions/docs/formulas.html create mode 100644 docinfo.conf create mode 100644 notes.adoc create mode 100644 rendered.html diff --git a/2d-projectile-motion/Makefile b/2d-projectile-motion/Makefile new file mode 100644 index 0000000..0ddb263 --- /dev/null +++ b/2d-projectile-motion/Makefile @@ -0,0 +1,11 @@ +LIBS = -lSDL2 -lSDL2_ttf -lm + +PROG = balls + +build: $(PROG).c + gcc -g -o $(PROG) $(PROG).c $(LIBS) + +clean: + rm -rf $(PROG) + +.PHONY: clean diff --git a/2d-projectile-motion/balls.c b/2d-projectile-motion/balls.c new file mode 100644 index 0000000..0846ca3 --- /dev/null +++ b/2d-projectile-motion/balls.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2024 Austin Larsen + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include +#include +#include +#include + +#define SCREEN_WIDTH_PX 1600 +#define SCREEN_HEIGHT_PX 800 +#define ACC_GRAVITY_MPS 9.81f +#define GROUND_HEIGHT_PX 750 + +typedef struct _Mouse { + int x; + int y; + uint32_t button; +} Mouse; + +enum /* color */ {COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_ORANGE, COLOR_GREY, + COLOR_WHITE, COLOR_BLACK, COLOR_SIZE}; + +void getMouse(Mouse *mouse); +void update(SDL_Renderer *renderer, uint64_t frame, float seconds, + SDL_KeyCode key, Mouse *mouse); +void +setColor(SDL_Renderer *renderer, uint8_t color); + + + +void +drawBall(SDL_Renderer *renderer, int px, int py, uint8_t radius, uint8_t color) +// (x - h)^2 + (y - k)^2 = r^2 +// Drawing a circle with the standard circle formula. +// There may be a more performant way to do this... +{ + setColor(renderer, color); + + // diameter is radius * 2 + for (int x = 0; x < radius * 2; x++) { + for (int y = 0; y < radius * 2; y++) { + if ( ((x - radius) * (x - radius)) + + ((y - radius) * (y - radius)) <= + radius * radius) { + int tx = px - radius + x; + int ty = py - radius + y; + + // TODO: wrap circle around screen + SDL_RenderDrawPoint(renderer, tx, ty); + + } + } + } +} + +int main(void) +{ + SDL_Window *window; + SDL_Renderer *renderer; + + printf("\033[2J"); // clear entire screen escape sequence + + { // SDL Initialization + if (SDL_Init(SDL_INIT_VIDEO) != 0) + fprintf(stderr, "could not init video\n%s", SDL_GetError()); + + window = SDL_CreateWindow("Projectile", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX, + SDL_WINDOW_SHOWN); + + renderer = SDL_CreateRenderer(window, 0, SDL_RENDERER_SOFTWARE); + } // SDL Initialization + + + { // game loop + bool quit = false; + const uint16_t fps = 60; + const float mspd = (1.0f / (float)fps) * 1000.0f; + uint64_t frame = 0; + float seconds = 0; + + while (!quit) { + uint32_t loop_start = SDL_GetTicks(); + + SDL_Event event; + SDL_KeyCode key = 0; + Mouse mouse; + bool keydown = false; + + getMouse(&mouse); + + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_KEYDOWN: { + if (event.key.repeat == 0) { + key = event.key.keysym.sym; + keydown = true; + } + break; + } + case SDL_KEYUP: keydown = false; break; + case SDL_QUIT: quit = true; break; + } + } + + setColor(renderer, COLOR_BLACK); + SDL_RenderClear(renderer); + update(renderer, frame, seconds, key, &mouse); + SDL_RenderPresent(renderer); + frame++; + seconds = ((float)frame / (float)fps); + + uint32_t loop_end = SDL_GetTicks(); + uint32_t elapsed_time = loop_end - loop_start; + + uint32_t delay = ceilf(mspd - (float)elapsed_time); + + if (delay > 0) { + SDL_Delay(delay); + } + } + + } // game loop + + + { // quit + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + } // quit + +} + +void getMouse(Mouse * mouse) +{ + mouse->button = SDL_GetMouseState(&mouse->x, &mouse->y); +} + +void drawPath(SDL_Renderer *renderer, SDL_Point *point, float velocity, + float angle, float seconds) + // velocity in meters per second +{ + // REMEMBER! negative is upward and positive is downward. That is why + // the initial velocity for y (vi_y) is negative and acceleration due to + // gravity (ACC_GRAVITY_MPS) is positive + float vi_y = -velocity * sinf(angle); + float vi_x = velocity * cosf(angle); + float delta_x = (float)vi_x * (float)seconds; + float delta_y = (float)vi_y * (float)seconds + (0.5f * ACC_GRAVITY_MPS + * (seconds * seconds)); + + // SDL_Rect rect = { + // .x = point->x + delta_x, + // .y = point->y + delta_y, + // .w = 10, + // .h = 10 + // }; + int tx = point->x + delta_x; + int ty = point->y + delta_y; + + if (delta_y <= -1) { + printf("\033[H"); // clear and set to home position escape sequence + printf("seconds: %f\n", seconds); + printf("y displacement: %f\n", delta_y); + printf("x displacement: %f\n", delta_x); + printf("x: %d\n", tx); + printf("y: %d\n", ty); + // SDL_RenderFillRect(renderer, &rect); + drawBall(renderer, tx, ty, 20, COLOR_BLUE); + } + +} + +void update(SDL_Renderer *renderer, uint64_t frame, float seconds, + SDL_KeyCode key, Mouse *mouse) +{ + static float launch_start = 0; + static float launch_angle = 0; + static float launch_initial = 0; + + SDL_Point point = { + .x = 10, + .y = GROUND_HEIGHT_PX + }; + + // 0 to 90 degrees (1/2 pi) + setColor(renderer, COLOR_BLUE); + SDL_RenderDrawLine(renderer, point.x, point.y, mouse->x, mouse->y); + + + int opposite = (mouse->y - point.y); + int adjacent = (mouse->x - point.x); + float angle = -atanf((float)opposite / (float)adjacent); + + setColor(renderer, COLOR_RED); + SDL_RenderDrawLine(renderer, 0, GROUND_HEIGHT_PX, SCREEN_WIDTH_PX, + GROUND_HEIGHT_PX); + + if (mouse->button == 1) { + launch_start = seconds; + launch_angle = angle; + // hypotenuse is velocity + launch_initial = sqrtf((opposite * opposite) + (adjacent * adjacent)); + } + + if (launch_start > 0) { + drawPath(renderer, &point, launch_initial, launch_angle, seconds - + launch_start); + } +} + +void +setColor(SDL_Renderer *renderer, uint8_t color) +{ + const SDL_Color colors[] = { + [COLOR_RED] = {.r = 217, .g = 100, .b = 89, .a = 255}, + [COLOR_WHITE] = {.r = 255, .g = 255, .b = 255, .a = 255}, + [COLOR_GREEN] = {.r = 88, .g = 140, .b = 126, .a = 255}, + [COLOR_BLUE] = {.r = 146, .g = 161, .b = 185, .a = 255}, + [COLOR_ORANGE] = {.r = 242, .g = 174, .b = 114, .a = 255}, + [COLOR_GREY] = {.r = 89, .g = 89, .b = 89, .a = 89}, + [COLOR_BLACK] = {.r = 0, .g = 0, .b = 0, .a = 0}, + }; + + SDL_SetRenderDrawColor(renderer, colors[color].r, colors[color].g, + colors[color].b, colors[color].a); +} + diff --git a/2d-projectile-motion/docs/formulas.adoc b/2d-projectile-motion/docs/formulas.adoc new file mode 100644 index 0000000..735bb81 --- /dev/null +++ b/2d-projectile-motion/docs/formulas.adoc @@ -0,0 +1,28 @@ +:stem: +:source-highlighter: pygments +:pygments-style: monokai +:wrap: normal + += Formulas + +* Velocity of y + - stem:[\Deltay = V_{0}t + 1/2at^2] + +* Velocity of x + - stem:[\Deltax = V_{0}t] + +* Angle of trojectory + - stem:[\theta = \arctan(frac{opposite}{adjactent})] + +* Getting initial y velocity (going downward increases in value) + - stem:[y_{0} = -l_0 * \sin(\theta)] + +* Getting initial x velocity + - stem:[x_{0} = l_0 * \cos(\theta)] + +* Pathagorean Theorem + - stem:[a^2 + b^2 = c^2] + +NOTE: launch_initial is just an intial, almost arbitrary, value. It is just a + distance the launch pad is from the mouse and is used to control the + amount of velocity you want. diff --git a/2d-projectile-motion/docs/formulas.html b/2d-projectile-motion/docs/formulas.html new file mode 100644 index 0000000..a226e81 --- /dev/null +++ b/2d-projectile-motion/docs/formulas.html @@ -0,0 +1,550 @@ + + + + + + + +Formulas + + + + + +
+
+
    +
  • +

    Velocity of y

    +
    +
      +
    • +

      \$\Deltay = V_{0}t + 1/2at^2\$

      +
    • +
    +
    +
  • +
  • +

    Velocity of x

    +
    +
      +
    • +

      \$\Deltax = V_{0}t\$

      +
    • +
    +
    +
  • +
  • +

    Angle of trojectory

    +
    +
      +
    • +

      \$\theta = \arctan(frac{opposite}{adjactent})\$

      +
    • +
    +
    +
  • +
  • +

    Getting initial y velocity (going downward increases in value)

    +
    +
      +
    • +

      \$y_{0} = -l_0 * \sin(\theta)\$

      +
    • +
    +
    +
  • +
  • +

    Getting initial x velocity

    +
    +
      +
    • +

      \$x_{0} = l_0 * \cos(\theta)\$

      +
    • +
    +
    +
  • +
  • +

    Pathagorean Theorem

    +
    +
      +
    • +

      \$a^2 + b^2 = c^2\$

      +
    • +
    +
    +
  • +
+
+
+ + + + + +
+
Note
+
+launch_initial is just an intial, almost arbitrary, value. It is just a + distance the launch pad is from the mouse and is used to control the + amount of velocity you want. +
+
+
+ + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makehtml b/Makehtml new file mode 100755 index 0000000..a7e355a --- /dev/null +++ b/Makehtml @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +. docinfo.conf + +clean () { +rm -rf diag*.png +rm -rf $(find . \ + -type f \ + -regex '.*\.html' \ + -not -name "rendered.html" \ + | grep -v docinfo) +} + +build () { +FILENAME="$(find . -type f -name "*.adoc" -not -name "README.adoc" -not -name "notes.adoc")" +asciidoctor -b html5 $FILENAME +} + +make () { + clean + build +} + +# use this to call one of the above functions +$@ diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..42730b0 --- /dev/null +++ b/README.adoc @@ -0,0 +1,52 @@ += Balls + +This repository has physics examples using balls. Each folder has a different +example, with a link to the formulas used in the readme's. + +== 2D Projectile Motion +checkout this link +https://nyquistdev.net/git/?p=balls.git;a=blob_plain;f=2d-projectile-motion/docs/formulas.html;hb=HEAD[formulas]. +for a list of forumlas used. + +[%header,cols="1,1"] +|=== +| control | action +| right-click and drag | determine direction and velocity of ball +| right-click release | launch ball +|=== + +== Bouncing +check out this link +https://nyquistdev.net/git/?p=balls.git;a=blob_plain;f=bounce/docs/formulas.html;hb=HEAD[formulas]. +for the formulas used. + +== Collisions +Creates random balls. These balls will resolve any collision issues. Launching a +ball will show you an example of elastic collision. + +checkout this link +https://nyquistdev.net/git/?p=balls.git;a=blob_plain;f=collisions/docs/formulas.html;hb=HEAD[formulas]. +for a list of forumlas used. + +[%header,cols="1,1"] +|=== +| control | action +| right-click | select ball +| right-click and left-click and drag | flings ball on release, the distance + away from the ball determines the amount + of velocity to launch the ball at +|=== + +== Building html from formula _.adoc_ files + +---- +./Makehtml make +---- + +== Links +* https://www.studyplan.dev/sdl2/sdl2-relative-mode[sdl2-relative-mouse-mode] +* https://www.youtube.com/watch?v=XJnIdRXUi7A&t=315s[permutations and combinations] +* https://www.youtube.com/watch?v=CFbo_nBdBco[OCT | Elastic Collisions] +* https://www.wikihow.com/Normalize-a-Vector[Normalizing a Vector] +* https://www.youtube.com/watch?v=Fp7D5D8Bqjc[conservation of Momentum] + diff --git a/bounce/Makefile b/bounce/Makefile new file mode 100644 index 0000000..0ddb263 --- /dev/null +++ b/bounce/Makefile @@ -0,0 +1,11 @@ +LIBS = -lSDL2 -lSDL2_ttf -lm + +PROG = balls + +build: $(PROG).c + gcc -g -o $(PROG) $(PROG).c $(LIBS) + +clean: + rm -rf $(PROG) + +.PHONY: clean diff --git a/bounce/balls b/bounce/balls new file mode 100755 index 0000000000000000000000000000000000000000..51a3a1c4f960cef6243db4bd6b1720f4b25317a2 GIT binary patch literal 45136 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 literal 0 HcmV?d00001 diff --git a/bounce/balls.c b/bounce/balls.c new file mode 100644 index 0000000..c794912 --- /dev/null +++ b/bounce/balls.c @@ -0,0 +1,336 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// NOTE: +// This is a less accurate depiction of gravity. I am using a different number +// as the mesurement in meters per second because you cannot have fractions of a +// pixel. In order to depict this more accurately I would need an approximate +// size of a pixel, and not every pixel is the same size. I have decided not to +// be THAT accurate. +#define METER_AS_PIXELS 3779U +// #define GRAVITY 0.5f + +#define END(check, str1, str2) \ + if (check) { \ + assert(check); \ + fprintf(stderr, "%s\n%s", str1, str2); \ + exit(1); \ + } \ + +#define SDL_main main + + +typedef struct _Game { + SDL_Renderer *renderer; + SDL_Window *window; + bool out_of_bounds; + uint16_t fps; + float gravity; + float terminal_velocity; + SDL_Rect screen_rect; +} Game; + +typedef struct _Ball { + SDL_Point center; + SDL_Point velocities; + uint16_t radius; + bool down; // check if the ball is + // these two varaibles reset with each fall + float time_falling; // time it takes to fall from peak + float height_falling; // max height for each fall + float e; // restition value +} Ball; + +typedef uint8_t (*Update_callback) (Game *game, + float seconds, + uint64_t frame, + SDL_KeyCode key, + bool keydown); + +enum {UPDATE_MAIN, UPDATE_NOTHING}; + +enum {COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_ORANGE, COLOR_GREY, + COLOR_BLACK, COLOR_SIZE}; + +void +setColor(SDL_Renderer *renderer, + uint8_t color) +{ + const SDL_Color colors[] = { + [COLOR_RED] = {.r = 217, .g = 100, .b = 89, .a = 255}, + [COLOR_GREEN] = {.r = 88, .g = 140, .b = 126, .a = 255}, + [COLOR_BLUE] = {.r = 146, .g = 161, .b = 185, .a = 255}, + [COLOR_ORANGE] = {.r = 242, .g = 174, .b = 114, .a = 255}, + [COLOR_GREY] = {.r = 89, .g = 89, .b = 89, .a = 89}, + [COLOR_BLACK] = {.r = 0, .g = 0, .b = 0, .a = 0}, + }; + + SDL_SetRenderDrawColor(renderer, colors[color].r, colors[color].g, + colors[color].b, colors[color].a); +} + +void +drawCircle(SDL_Renderer *renderer, + int radius, + SDL_Point center, + uint8_t color) +// (x - h)^2 + (y - k)^2 = r^2 +// Drawing a circle with the standard circle formula. +// There may be a more performant way to do this... +{ + int size = radius; + setColor(renderer, color); + + // diameter is radius * 2 + for (int x = 0; x < radius * 2; x++) { + for (int y = 0; y < radius * 2; y++) { + if (((x - size)*(x - size)) + ((y - size)*(y - size)) <= radius + * radius) + SDL_RenderDrawPoint(renderer, center.x - radius + x, center.y - radius + y); + } + } +} + +float clamp(float v, float min, float max) { + if (v < min) return min; + if (v > max) return max; + return v; +} + +bool circleRectCollide(SDL_Point circle, + float r, + SDL_Rect rect) +{ + float closestX = clamp(circle.x, rect.x, rect.x + rect.w); + float closestY = clamp(circle.y, rect.y, rect.y + rect.h); + + // this gets the distance form the center of the circle to a side of a + // rectangle. It could be negative or positive. Because we are squaring them + // it doesn't matter. + float dx = circle.x - closestX; + float dy = circle.y - closestY; + + // use the dot product to determine collision + return (dx*dx + dy*dy) <= (r*r); +} + +static uint8_t +updateNothing(Game *game, + float seconds, + uint64_t frame, + SDL_KeyCode key, + bool keydown) +{ + return UPDATE_NOTHING; +} + +void +worldBoundry(Game *game, Ball *ball) +{ + int padding = 1; + + if (ball->center.x - ball->radius <= 0) { + ball->center.x = ball->radius + padding; + } + + if (ball->center.y - ball->radius <= 0) { + ball->center.y = ball->radius + padding; + } + + if (ball->center.x + ball->radius >= game->screen_rect.w) { + ball->center.x = game->screen_rect.w - ball->radius - padding; + } + + if (ball->center.y + ball->radius >= game->screen_rect.h) { + ball->center.y = game->screen_rect.h - ball->radius - padding; + } +} + +static uint8_t +updateMain(Game *game, + float seconds, + uint64_t frame, + SDL_KeyCode key, + bool keydown) +{ + // as long as gravity and time are using the same units the fomrulas will + // work. in this case we will do frames as out unit + // * Rubber ball: (e \approx 0.8 - 0.9) + // * Basketball: (e \approx 0.75) + // * Tennis ball: (e \approx 0.6) + static Ball ball = { + .center = {.x = 400, .y = 400}, + .radius = 30, + .down = true, + .e = 0.9f + }; + + static float initial_y = 400; + static float y = 400; + static float restitution = 1; + static float f = 0; + static float acceleration = 0; + + + if (ball.down) // falling downward + acceleration += (game->gravity * f); + else + acceleration -= restitution - (game->gravity * f); + + // clip this to world border + ball.center.y = y + acceleration; + + // ball.height_falling = initial_y + (0.5f * game->gravity) * (f * f); + + // ball.time_falling = sqrt(2 * (initial_y - ball.radius) / game->gravity); + // + // This if statement cannot be used because + // if (f > time && ball.down) { + if ((ball.center.y + ball.radius >= game->screen_rect.h) && + restitution >= 0) { + f = 0; + ball.down = false; + restitution *= ball.e; + } + + worldBoundry(game, &ball); + + // Should not go out of bounds. but check if it does + game->out_of_bounds = + !circleRectCollide(ball.center, ball.radius, game->screen_rect); + + drawCircle(game->renderer, ball.radius, ball.center, COLOR_RED); + f++; + + return UPDATE_MAIN; +} + +void +Game_Update(Game *game) +// The main game loop. Sets up which callback will be used in the function loop. +// Each update callback determines what update callback will be called next by +// returning the appropriate enum value +{ + uint64_t frame = 0; + bool quit = false; + bool keydown = false; + uint8_t update_id = 0; + Update_callback update; + float mspd = (1.0f / (float)game->fps) * 1000.0f; + float seconds = 0; + SDL_Event event; + SDL_KeyCode key = 0; + uint32_t end = 0; + uint32_t elapsed_time = 0; + uint8_t color = COLOR_BLACK; + + while (!quit) { + uint32_t start = SDL_GetTicks(); + // clear screen + if (game->out_of_bounds) color = COLOR_GREEN; + setColor(game->renderer, color); + SDL_RenderClear(game->renderer); + // TODO: + // find milliseconds so you can update gravity more acurately + // (1 / frame) + seconds = ((float)frame / (float)game->fps); + // printf("%lu: %f\n", frame, seconds); + + + // Place update functions here + switch (update_id) { + case UPDATE_MAIN: update = updateMain; break; + case UPDATE_NOTHING: update = updateNothing; break; + // case UPDATE_LOSE: update = updateLose; break; + } + + + + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_KEYDOWN: { + if (event.key.repeat == 0) { + key = event.key.keysym.sym; + keydown = true; + } + + break; + } + + case SDL_KEYUP: keydown = false; break; + case SDL_QUIT: quit = true; break; + } + } + + update_id = update(game, seconds, frame, key, keydown); + + end = SDL_GetTicks(); + elapsed_time = end - start; + + if (elapsed_time < mspd) { + elapsed_time = mspd - elapsed_time; + SDL_Delay(elapsed_time); + } + + SDL_RenderPresent(game->renderer); + frame++; + } +} + +void +Game_Init(Game *game) +// All the variable and data initialization needed for SDL and perhaps game +// variables +{ + memset(game, 0, sizeof(Game)); + game->screen_rect.x = 0; + game->screen_rect.y = 0; + game->screen_rect.w = 800; + game->screen_rect.h = 800; + + END(SDL_Init(SDL_INIT_VIDEO) != 0, "Could not create texture", + SDL_GetError()); + + END(TTF_Init() != 0, "Could not initialize TTF", TTF_GetError()); + + + + game->window = SDL_CreateWindow("balls", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, game->screen_rect.w, + game->screen_rect.h, SDL_WINDOW_SHOWN); + + END(game->window == NULL, "Could not create window", SDL_GetError()); + + game->renderer = SDL_CreateRenderer(game->window, 0, + SDL_RENDERER_SOFTWARE); + + END(game->renderer == NULL, "Could not create renderer", SDL_GetError()); + game->fps = 400; + // game->terminal_velocity = + game->gravity = 0.004f; +} + +void +Game_Quit(Game *game) +{ + SDL_DestroyWindow(game->window); + SDL_DestroyRenderer(game->renderer); + TTF_Quit(); + SDL_Quit(); +} + +int +main(void) +{ + Game game; + Game_Init(&game); + Game_Update(&game); + Game_Quit(&game); + return 0; +} diff --git a/bounce/docs/chatgpt_dump/circle-circle-collision.md b/bounce/docs/chatgpt_dump/circle-circle-collision.md new file mode 100644 index 0000000..cb838ac --- /dev/null +++ b/bounce/docs/chatgpt_dump/circle-circle-collision.md @@ -0,0 +1,151 @@ +Below is a clear explanation of **circle–circle collision detection**, including the **math formulas** and **C code examples**. + +--- + +# ✅ **Math Behind Circle Collision** + +Two circles collide (intersect or touch) when the **distance between their centers** is **less than or equal to** the **sum of their radii**. + +Let: + +* Circle 1 center: ((x_1, y_1)), radius (r_1) +* Circle 2 center: ((x_2, y_2)), radius (r_2) + +### **1. Compute distance between centers** + +[ +d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} +] + +### **2. Collision condition** + +[ +d \le r_1 + r_2 +] + +A common optimization is to avoid the expensive `sqrt()`: + +### **Squared distance method (no sqrt)** + +[ +(x_2 - x_1)^2 + (y_2 - y_1)^2 \le (r_1 + r_2)^2 +] + +This is identical mathematically, but faster. + +--- + +# ✅ **C Example: Basic Circle Collision Check** + +```c +#include +#include +#include + +typedef struct { + float x; + float y; + float r; +} Circle; + +bool circlesCollide(Circle a, Circle b) { + float dx = b.x - a.x; + float dy = b.y - a.y; + float distance = sqrtf(dx * dx + dy * dy); + + return distance <= (a.r + b.r); +} + +int main() { + Circle c1 = {0.0f, 0.0f, 5.0f}; + Circle c2 = {6.0f, 0.0f, 5.0f}; + + if (circlesCollide(c1, c2)) { + printf("Circles collide!\n"); + } else { + printf("No collision.\n"); + } + + return 0; +} +``` + +--- + +# ✅ **C Example: Optimized (No sqrt)** + +```c +#include +#include + +typedef struct { + float x; + float y; + float r; +} Circle; + +bool circlesCollideFast(Circle a, Circle b) { + float dx = b.x - a.x; + float dy = b.y - a.y; + + float distanceSquared = dx * dx + dy * dy; + float radiusSum = a.r + b.r; + + return distanceSquared <= radiusSum * radiusSum; +} + +int main() { + Circle c1 = {0.0f, 0.0f, 5.0f}; + Circle c2 = {6.0f, 0.0f, 5.0f}; + + if (circlesCollideFast(c1, c2)) { + printf("Circles collide!\n"); + } else { + printf("No collision.\n"); + } + + return 0; +} +``` + +--- + +# 🚀 **Example Output** + +For the sample circles, they collide because distance = 6 and r1 + r2 = 10. + +``` +Circles collide! +``` + +--- + +# 🧠 Optional: Collision Penetration Depth (Extra Useful) + +Sometimes you want *how much* they overlap: + +[ +\text{penetration} = (r_1 + r_2) - d +] + +C example: + +```c +float penetrationDepth(Circle a, Circle b) { + float dx = b.x - a.x; + float dy = b.y - a.y; + float d = sqrtf(dx * dx + dy * dy); + return (a.r + b.r) - d; +} +``` + +--- + +If you'd like, I can also show: + +✅ Circle vs Rectangle collision +✅ Circle movement simulation +✅ Game-loop example using SDL2 +✅ Visual ASCII output + +Just tell me! diff --git a/bounce/docs/chatgpt_dump/circle-rectange-SDL2-collision.md b/bounce/docs/chatgpt_dump/circle-rectange-SDL2-collision.md new file mode 100644 index 0000000..d0c85ad --- /dev/null +++ b/bounce/docs/chatgpt_dump/circle-rectange-SDL2-collision.md @@ -0,0 +1,119 @@ +```c +#include +#include +#include + +// ----------------------------------------- +// Utility functions +// ----------------------------------------- +float clamp(float v, float min, float max) { + if (v < min) return min; + if (v > max) return max; + return v; +} + +bool circleRectCollide(float cx, float cy, float r, + float rx, float ry, float rw, float rh) +{ + float closestX = clamp(cx, rx, rx + rw); + float closestY = clamp(cy, ry, ry + rh); + + float dx = cx - closestX; + float dy = cy - closestY; + + return (dx*dx + dy*dy) <= (r*r); +} + +// ----------------------------------------- +// Main SDL demo +// ----------------------------------------- +int main() { + SDL_Init(SDL_INIT_VIDEO); + + SDL_Window* window = SDL_CreateWindow( + "Circle vs Rectangle Collision - SDL2", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 800, 600, + SDL_WINDOW_SHOWN + ); + + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + + // Circle properties + float cx = 100.0f; + float cy = 100.0f; + float cr = 25.0f; + float speed = 200.0f; // pixels per second + + // Rectangle properties + float rx = 400.0f; + float ry = 250.0f; + float rw = 150.0f; + float rh = 100.0f; + + Uint32 last = SDL_GetTicks(); + bool running = true; + + while (running) { + Uint32 now = SDL_GetTicks(); + float dt = (now - last) / 1000.0f; + last = now; + + // ------------------ + // Input + // ------------------ + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) running = false; + } + + const Uint8* keys = SDL_GetKeyboardState(NULL); + if (keys[SDL_SCANCODE_ESCAPE]) running = false; + + // Move circle + if (keys[SDL_SCANCODE_W]) cy -= speed * dt; + if (keys[SDL_SCANCODE_S]) cy += speed * dt; + if (keys[SDL_SCANCODE_A]) cx -= speed * dt; + if (keys[SDL_SCANCODE_D]) cx += speed * dt; + + // ------------------ + // Collision check + // ------------------ + bool hit = circleRectCollide(cx, cy, cr, rx, ry, rw, rh); + + // ------------------ + // Render + // ------------------ + SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255); + SDL_RenderClear(renderer); + + // Rectangle (turn red when hit) + if (hit) + SDL_SetRenderDrawColor(renderer, 255, 50, 50, 255); + else + SDL_SetRenderDrawColor(renderer, 100, 100, 255, 255); + + SDL_Rect rect = { (int)rx, (int)ry, (int)rw, (int)rh }; + SDL_RenderFillRect(renderer, &rect); + + // Draw circle (simple filled circle) + SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); + for (int w = 0; w < cr * 2; w++) { + for (int h = 0; h < cr * 2; h++) { + int dx = cr - w; + int dy = cr - h; + if ((dx*dx + dy*dy) <= (cr * cr)) { + SDL_RenderDrawPoint(renderer, cx + dx, cy + dy); + } + } + } + + SDL_RenderPresent(renderer); + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} +``` diff --git a/bounce/docs/chatgpt_dump/circle-rectangle-collision.md b/bounce/docs/chatgpt_dump/circle-rectangle-collision.md new file mode 100644 index 0000000..332d5b9 --- /dev/null +++ b/bounce/docs/chatgpt_dump/circle-rectangle-collision.md @@ -0,0 +1,171 @@ +Below is a clear explanation of **Circle vs Rectangle collision detection**, including **math formulas**, **edge-case handling**, and **C programming examples**. + +--- + +# 🟢 **Circle vs Axis-Aligned Rectangle Collision** + +We assume the rectangle is **axis-aligned (AABB)** and defined by: + +* Rectangle min corner: ((rx, ry)) +* Width (w), height (h) +* So max corner is ((rx+w,; ry+h)) + +Circle: + +* Center: ((cx, cy)) +* Radius (r) + +--- + +# ✅ **Math Formula (Shortest Distance Method)** + +The basic idea: + +1. **Clamp** the circle center to the edges of the rectangle +2. Compute the closest point **inside the rectangle** +3. Compute distance from the circle center to that point +4. If distance ≤ radius → collision + +### 1. Clamp function + +[ +\text{clamp}(v, min, max) = \max(min,;\min(v, max)) +] + +### 2. Closest point on rectangle to circle + +[ +\text{closestX} = \text{clamp}(cx,; rx,; rx+w) +] +[ +\text{closestY} = \text{clamp}(cy,; ry,; ry+h) +] + +### 3. Distance from circle to closest point + +[ +dx = cx - \text{closestX} +] +[ +dy = cy - \text{closestY} +] + +### 4. Collision rule + +[ +dx^2 + dy^2 \le r^2 +] + +No square root needed → fast. + +--- + +# 🟦 **C Code Example: Circle vs Rectangle Collision** + +```c +#include +#include + +float clamp(float value, float min, float max) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +typedef struct { + float x, y; // top-left corner + float w, h; +} Rect; + +typedef struct { + float x, y; // center + float r; +} Circle; + +bool circleRectCollide(Circle c, Rect r) { + // Find the closest point on the rectangle to the circle + float closestX = clamp(c.x, r.x, r.x + r.w); + float closestY = clamp(c.y, r.y, r.y + r.h); + + // Compute distance from circle center to that point + float dx = c.x - closestX; + float dy = c.y - closestY; + + // If within radius, collision + return (dx * dx + dy * dy) <= (c.r * c.r); +} + +int main() { + Circle c = { 5.0f, 5.0f, 3.0f }; + Rect r = { 7.0f, 4.0f, 6.0f, 4.0f }; + + if (circleRectCollide(c, r)) { + printf("Collision!\n"); + } else { + printf("No collision.\n"); + } +} +``` + +--- + +# 🧪 **Explanation of the above example** + +Circle at (5,5), radius 3 +Rectangle at (7,4), size 6×4 + +The closest point on the rect to the circle is (7,5). +Distance = 2 → less than radius 3 → collision. + +--- + +# 🧠 Extra: Detecting Penetration Vector / Push-out Direction + +If you're making a game, you often need to **push the circle out** of the rectangle. + +Short version: + +1. Compute closest point +2. Compute distance +3. Compute penetration vector + +Example: + +```c +#include + +void circleRectPenetration(Circle c, Rect r, float *outX, float *outY) { + float closestX = clamp(c.x, r.x, r.x + r.w); + float closestY = clamp(c.y, r.y, r.y + r.h); + + float dx = c.x - closestX; + float dy = c.y - closestY; + + float distSq = dx*dx + dy*dy; + float dist = sqrtf(distSq); + + float penetration = c.r - dist; + + if (penetration > 0) { + *outX = (dx / dist) * penetration; + *outY = (dy / dist) * penetration; + } else { + *outX = *outY = 0.0f; + } +} +``` + +This gives the shortest vector needed to push the circle out of the rectangle. + +--- + +# Want more? + +I can also show: + +✔ **Circle vs rotated rectangle (OBB)** +✔ **Collision response (velocity reflection)** +✔ **Graphical ASCII demo** +✔ **SDL2 example with moving circle and rectangle** + +Just tell me! diff --git a/bounce/docs/chatgpt_dump/physics.md b/bounce/docs/chatgpt_dump/physics.md new file mode 100644 index 0000000..66e51d9 --- /dev/null +++ b/bounce/docs/chatgpt_dump/physics.md @@ -0,0 +1,227 @@ +Below is a **clear physics explanation**, the **math**, and **C programming examples** showing how to simulate a bouncing ball. + +--- + +# 🌟 **Physics of a Bouncing Ball** + +A bouncing ball is controlled by three main ideas: + +--- + +# 1️⃣ **Gravity Accelerates the Ball Downward** + +The ball’s vertical motion follows: + +[ +h(t) = h_0 - \tfrac{1}{2} g t^2 +] + +Velocity increases downward as: + +[ +v(t) = -gt +] + +Where: + +* (h) = height +* (g = 9.8\ \text{m/s}^2) +* (v) = velocity +* Downward is negative + +--- + +# 2️⃣ **Impact With the Ground** + +When the ball hits the ground, it changes direction. + +A real ball **does NOT return to full height**; some energy is lost. + +This is described using the **coefficient of restitution**, (e): + +[ +e = \frac{v_{\text{after}}}{v_{\text{before}}} +] + +Typical values: + +* Rubber ball: (e \approx 0.8 - 0.9) +* Basketball: (e \approx 0.75) +* Tennis ball: (e \approx 0.6) + +Meaning: + +* If velocity before impact is (-10\ \text{m/s}) +* With (e = 0.8), after impact: + [ + v_{\text{after}} = -e \cdot v_{\text{before}} = 8\ \text{m/s} + ] + +(The sign flips because the direction reverses.) + +--- + +# 3️⃣ **Each Bounce Gets Shorter** + +Because energy is lost each bounce, the maximum height shrinks: + +[ +h_{\text{after}} = e^2 \cdot h_{\text{before}} +] + +--- + +# ⭐ **MATHEMATICAL MODEL** + +Let: + +* (h) = current height +* (v) = velocity +* (dt) = time step + +Update each frame: + +### Gravity: + +[ +v = v - g \cdot dt +] + +### Position: + +[ +h = h + v \cdot dt +] + +### Bounce: + +If (h < 0): +[ +h = 0 +] +[ +v = -e \cdot v +] + +--- + +# 🖥️ **C Programming Example — Simple Bounce Simulation** + +This uses a time-step loop to simulate the ball in text mode. + +```c +#include + +int main() { + double h = 10.0; // initial height in meters + double v = 0.0; // initial velocity + double g = 9.8; // gravity + double e = 0.8; // coefficient of restitution + double dt = 0.01; // time step (seconds) + + for (int i = 0; i < 5000; i++) { + // apply gravity + v = v - g * dt; + + // update position + h = h + v * dt; + + // bounce + if (h < 0) { + h = 0; + v = -v * e; + } + + printf("time = %.2f s, height = %.3f m, velocity = %.3f m/s\n", + i * dt, h, v); + } + + return 0; +} +``` + +This produces output like: + +``` +time = 0.00 s, height = 9.999 m, velocity = -0.098 m/s +time = 0.01 s, height = 9.997 m, velocity = -0.196 m/s +... +Bounce! +... +``` + +A very realistic-looking bounce. + +--- + +# 🖥️ **C Example — Detecting Ground Collision Inside a Loop** + +```c +if (h <= 0) { + h = 0; + v = -v * e; // reverse and reduce velocity +} +``` + +This is the typical “bounce line” used in game loops. + +--- + +# 🖥️ **C Example — Continuous Simulation with ASCII Display** + +```c +#include +#include + +int main() { + double h = 20.0; + double v = 0.0; + double g = 9.8; + double e = 0.85; + double dt = 0.02; + + while (1) { + v = v - g * dt; + h = h + v * dt; + + if (h < 0) { + h = 0; + v = -v * e; + } + + int screen_height = 25; + int pos = (int)(screen_height - h); + + printf("\033[2J\033[1;1H"); // clear screen + for (int i = 0; i < pos; i++) printf("\n"); + printf(" o\n"); // the ball + fflush(stdout); + + usleep(20000); + } +} +``` + +This animates the bouncing ball in your terminal. + +--- + +# 🎯 Summary + +| Concept | Equation | +| --------------------------- | ------------------------------------------ | +| Gravity acceleration | (v = v - g \cdot dt) | +| Position update | (h = h + v \cdot dt) | +| Bounce | (v = -e v) | +| New max height after bounce | (h_{\text{after}} = e^2 h_{\text{before}}) | + +This combination gives a physically accurate bouncing ball. + +--- + +If you want, I can also show: + +* horizontal + vertical motion (parabolic bounce) +* collisions with walls +* 2D physics with vectors +* full game loop frameworks for SDL, Raylib, or OpenGL diff --git a/bounce/docs/formulas.adoc b/bounce/docs/formulas.adoc new file mode 100644 index 0000000..d106b7e --- /dev/null +++ b/bounce/docs/formulas.adoc @@ -0,0 +1,32 @@ +:stem: latexmath +:source-highlighter: pygments +:pygments-style: monokai +:wrap: normal + += Formulas + +* velocity downward + - stem:[v = g t] + +* height + - stem:[h = h_0 - \frac{1}{2}t^2] + +* how long any object takes to fall from a given height + - stem:[t = \sqrt{\frac{2h}{g}}] + +* restitution + - stem:[v_1 = e * v_0] + +* rising + - stem:[ v = e * v - g t ] + +* falling + - stem:[ v = g t ] + +NOTE: the coefficent of restitution (e) represents energy loss. + +Typical values for restitution: + +* Rubber ball: stem:[e \approx 0.8 - 0.9] +* Basketball: stem:[e \approx 0.75] +* Tennis ball: stem:[e \approx 0.6] diff --git a/bounce/docs/formulas.html b/bounce/docs/formulas.html new file mode 100644 index 0000000..30e9175 --- /dev/null +++ b/bounce/docs/formulas.html @@ -0,0 +1,564 @@ + + + + + + + +Formulas + + + + + +
+
+
    +
  • +

    velocity downward

    +
    +
      +
    • +

      \(v = g t\)

      +
    • +
    +
    +
  • +
  • +

    height

    +
    +
      +
    • +

      \(h = h_0 - \frac{1}{2}t^2\)

      +
    • +
    +
    +
  • +
  • +

    how long any object takes to fall from a given height

    +
    +
      +
    • +

      \(t = \sqrt{\frac{2h}{g}}\)

      +
    • +
    +
    +
  • +
  • +

    restitution

    +
    +
      +
    • +

      \(v_1 = e * v_0\)

      +
    • +
    +
    +
  • +
  • +

    rising

    +
    +
      +
    • +

      \( v = e * v - g t \)

      +
    • +
    +
    +
  • +
  • +

    falling

    +
    +
      +
    • +

      \( v = g t \)

      +
    • +
    +
    +
  • +
+
+
+ + + + + +
+
Note
+
+the coefficent of restitution (e) represents energy loss. +
+
+
+

Typical values for restitution:

+
+
+
    +
  • +

    Rubber ball: \(e \approx 0.8 - 0.9\)

    +
  • +
  • +

    Basketball: \(e \approx 0.75\)

    +
  • +
  • +

    Tennis ball: \(e \approx 0.6\)

    +
  • +
+
+
+ + + + + \ No newline at end of file diff --git a/collisions/Makefile b/collisions/Makefile new file mode 100644 index 0000000..0ddb263 --- /dev/null +++ b/collisions/Makefile @@ -0,0 +1,11 @@ +LIBS = -lSDL2 -lSDL2_ttf -lm + +PROG = balls + +build: $(PROG).c + gcc -g -o $(PROG) $(PROG).c $(LIBS) + +clean: + rm -rf $(PROG) + +.PHONY: clean diff --git a/collisions/balls b/collisions/balls new file mode 100755 index 0000000000000000000000000000000000000000..2ed592f46c7db45d2f655cbc424d579db10b6f01 GIT binary patch literal 62216 zcmeFad3;nw)<1mf-mavRbkd!q2}xMe>|syBzDc$YojqiM#H|TQheSgXk`9XtAOR&t zG*QQKLC0-;oOu{!97hp1R1|R3!5tkP8MjGfjS(%QSxkI<7ksH;? zCiH)4Q2Egd1$>xy&|4w^ldOdTtloOgiCCQ4B8|^Jc zJ+jM>eT-o)*V{YSIBeGYZRCcZO>b^mIeX^x=K874O)Z`4r>>tnd+O|&(>hwGWe62W zF9-h9@GmK;QZOw0h^C8E)Xfq%c<3R1eaLeH0o`e6}bMWV$gTDP7^kwIefA2Zyo#&9#cMkfe=aBQ_ zIrw8RsAseDFVDe$`x}4VLR!PqFba>laHw3ZfH8nSvd+MYpSwfY{TANxVZHdS0Y;V^k zl(jZDJJ)*jBFSFX?&&ZnOFMO?^rn_KwbZw+3siGSc3|(Mgq&tit<=!3Kv7eR>?hi< zY^uAoBS1)iR9V5-iOR}6x6pwAVWEyS?cN5Zqp77qscY?Mz+hk;>MnKHHD2m&sBLOi z8enk6vmSc8-L)MZ9*mKodz)6%&|yKU(}!+#ZBvWV;e}b-m4>!<7zl#fYg_8UR=2*^ z-O$uh+uU@SN2#lAZf>npP!%JXF*Rda8m(Yy2Epz1HUtq@Q}Jvnu9MJ`qWtU}cgD1g zX|ogqp6P>TkW(`JQ$-pK5k@5{)-V$wO~(x`F3JqdUn*BSqAvhQ>!AbxJCmCtXf-UA z#X_D_FGsOrZ)%FB)%XIP_GsTNSRPG^N5^G-)mPuxj~Vl3#SoIBDBJb@mO*#ubn^;P ztQd3yKjM~jT8n87HlCva^mv1aygz_GCV+k-fIcpOekOoEK7ek_d)j|Y44@ypSR&!b z5F&if;Q+dBLQ!@kfKGc2<2f2Yr+ta>^as%Ot*~8bF7`_@1-?dW?^X{d54`I8sn?W&qvTYZBcR zK#wJY{*(pK1K0iP0J?tU5;ax?&~+CS^u_>sf*~AvTL67%0DXM`Ju!g3Ie?xNK;Irf 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define METER_AS_PIXELS 3779U +#define BALL_COUNT 30 + +// (BALL_COUNT * (BALL_COUNT - 1)) / 2 +// #define BALL_DISTINCT_UNORDERED_PAIRS 435 + +#define END(check, str1, str2) \ + if (check) { \ + assert(check); \ + fprintf(stderr, "%s\n%s", str1, str2); \ + exit(1); \ + } \ + +#define SDL_main main + +typedef struct _Ball { + float px, py, vx, vy, ax, ay; + float radius; + uint8_t color; + float mass; +} Ball; + +typedef struct _Mouse { + SDL_Point p; + bool down; + uint8_t button; +} Mouse; + +typedef struct _Game { + SDL_Renderer *renderer; + SDL_Surface *backbuffer; + SDL_Window *window; + const SDL_Rect screen_rect; + Ball balls[BALL_COUNT]; + uint32_t fps; + float terminal_velocity; + uint8_t ball_size_min; + uint8_t ball_size_max; + Ball **balls_colliding; + uint8_t collision_count; +} Game; + + +typedef uint8_t (*Update_callback) (Game *game, + float seconds, + uint32_t milliseconds, + SDL_KeyCode key, + Mouse mouse, + bool keydown); + +enum {UPDATE_MAIN, UPDATE_NOTHING}; + +// Make sure last color is always black +enum {COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_ORANGE, COLOR_GREY, + COLOR_PURPLE, COLOR_NEON_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_WHITE, + COLOR_BLACK, COLOR_SIZE}; + +const SDL_Color colors[] = { + [COLOR_RED] = {.r = 217, .g = 100, .b = 89, .a = 255}, + [COLOR_GREEN] = {.r = 88, .g = 140, .b = 126, .a = 255}, + [COLOR_BLUE] = {.r = 39, .g = 211, .b = 245, .a = 255}, + [COLOR_ORANGE] = {.r = 242, .g = 174, .b = 114, .a = 255}, + [COLOR_GREY] = {.r = 89, .g = 89, .b = 89, .a = 89}, + [COLOR_YELLOW] = {.r = 230, .g = 240, .b = 0, .a = 255}, + [COLOR_PINK] = {.r = 245, .g = 39, .b = 108, .a = 255}, + [COLOR_NEON_GREEN] = {.r = 39, .g = 245, .b = 176, .a = 255}, + [COLOR_PURPLE] = {.r = 176, .g = 39, .b = 245, .a = 255}, + [COLOR_WHITE] = {.r = 255, .g = 255, .b = 255, .a = 255}, + [COLOR_BLACK] = {.r = 0, .g = 0, .b = 0, .a = 0}, +}; + +void +setColor(SDL_Renderer *renderer, + uint8_t color) +{ + + SDL_SetRenderDrawColor(renderer, colors[color].r, colors[color].g, + colors[color].b, colors[color].a); +} + +float getHyp(float x1, float y1, float x2, float y2) { + return sqrtf((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); +} + +bool ballCollide(Ball b1, + Ball b2) +{ + return fabs((b1.px - b2.px) * (b1.px - b2.px) + + (b1.py - b2.py) * (b1.py - b2.py)) <= + (b1.radius + b2.radius) * (b1.radius + b2.radius); +} + +bool pointInBall(Ball b, + SDL_Point p) +{ + return fabs((b.px - p.x) * (b.px - p.x) + (b.py - p.y) * (b.py - p.y)) + <= (b.radius * b.radius); +} + +void +drawCircle(SDL_Renderer *renderer, + SDL_Rect border, + float radius, + int px, + int py, + int w, + uint8_t color) +{ + + float x = 0; + float y = 0; + for(float i = 0; i < 2 * M_PI; i += 0.001f) + { + x = cosf(i); + y = sinf(i); + int rx = (x * radius); + int ry = (y * radius); + int tx = rx + px; + int ty = ry + py; + // wraping + // This works except in case where a ball is in the corner. Than the + // ball will apear in all four courners + // SDL_Rect point = { + // .x = (tx > border.w) ? tx -= border.w : (tx < 0) ? tx += border.w : tx, + // .y = (ty > border.h) ? ty -= border.h : (ty < 0) ? ty += border.h : ty, + // w, + // w + // }; + SDL_Rect point = { + .x = tx, + .y = ty, + w, + w + }; + setColor(renderer, color); + SDL_RenderFillRect(renderer, &point); + } +} + +void +drawBall(SDL_Renderer *renderer, + Ball ball) +// (x - h)^2 + (y - k)^2 = r^2 +// Drawing a circle with the standard circle formula. +// There may be a more performant way to do this... +{ + setColor(renderer, ball.color); + + // diameter is radius * 2 + for (int x = 0; x < ball.radius * 2; x++) { + for (int y = 0; y < ball.radius * 2; y++) { + if ( ((x - ball.radius) * (x - ball.radius)) + + ((y - ball.radius) * (y - ball.radius)) <= + ball.radius * ball.radius) { + int px = ball.px - ball.radius + x; + int py = ball.py - ball.radius + y; + + // TODO: wrap circle around screen + SDL_RenderDrawPoint(renderer, px, py); + + } + } + } +} + +void +drawBall2(SDL_Surface *surface, + Ball ball) +// (x - h)^2 + (y - k)^2 = r^2 +// Drawing a circle with the standard circle formula. +// There may be a more performant way to do this... +{ + // setColor(renderer, ball.color); + + // diameter is radius * 2 + for (int x = 0; x < ball.radius * 2; x++) { + for (int y = 0; y < ball.radius * 2; y++) { + if ( ((x - ball.radius) * (x - ball.radius)) + + ((y - ball.radius) * (y - ball.radius)) <= + ball.radius * ball.radius) { + int px = ball.px - ball.radius + x; + int py = ball.py - ball.radius + y; + + // TODO: wrap circle around screen + // SDL_RenderDrawPoint(renderer, px, py); + SDL_Rect r = {.x = px, .y = py, .w = 1, .h = 1}; + uint32_t color = + ((colors[ball.color].r << 24) | (colors[ball.color].g << 16) | + (colors[ball.color].b << 8) | colors[ball.color].a); + SDL_FillRect(surface, &r, color); + + } + } + } +} + +float clamp(float v, + float min, + float max) { + if (v < min) return min; + if (v > max) return max; + return v; +} + +bool circleRectCollide(SDL_Point circle, + float r, + SDL_Rect rect) +{ + float closestX = clamp(circle.x, rect.x, rect.x + rect.w); + float closestY = clamp(circle.y, rect.y, rect.y + rect.h); + + // this gets the distance form the center of the circle to a side of a + // rectangle. It could be negative or positive. Because we are squaring them + // it doesn't matter. + float dx = circle.x - closestX; + float dy = circle.y - closestY; + + // use the dot product to determine collision + return (dx*dx + dy*dy) <= (r*r); +} + + +static uint8_t +updateNothing(Game *game, + float seconds, + uint32_t milliseconds, + SDL_KeyCode key, + Mouse mouse, + bool keydown) +{ + return UPDATE_NOTHING; +} + +void +worldBoundry(Game *game, Ball *ball) +{ + int padding = 1; + + if (ball->px - ball->radius <= 0) { + ball->px = ball->radius + padding; + } + + if (ball->py - ball->radius <= 0) { + ball->py = ball->radius + padding; + } + + if (ball->px + ball->radius >= game->screen_rect.w) { + ball->px = game->screen_rect.w - ball->radius - padding; + } + + if (ball->py + ball->radius >= game->screen_rect.h) { + ball->py = game->screen_rect.h - ball->radius - padding; + } +} + +void +drawCursor(SDL_Renderer *renderer, SDL_Point p) { + SDL_Rect r = { + .x = p.x - 5, + .y = p.y - 5, + .w = 15, + .h = 15 + }; + setColor(renderer, COLOR_WHITE); + SDL_RenderFillRect(renderer, &r); +} + +static uint8_t +updateMain(Game *game, + float seconds, + uint32_t milliseconds, + SDL_KeyCode key, + Mouse mouse, + bool keydown) +{ + static float elapsedTime = 0; + static int selected = -1; + + // Ball *colliding[BALL_DISTINCT_UNORDERED_PAIRS] = {0}; + game->balls_colliding = NULL; + game->collision_count = 0; + + + // game->ball_distinct_unordered_pairs is the total number of possible + // collisions, it is multiplied was two because there needed to be two values + // for each collision, a ball at the target ball + + // TODO + // Add a middle click feature that lets you look around. Then you could see + // balls that went off screen + + // NOTE: + // issues arise when mouse movement is too fast + // don't check mouse click more than needed + if(mouse.button == SDL_BUTTON_LEFT && (selected < 0)) { + for (int i = 0; i < BALL_COUNT; ++i) { + Ball *b = &game->balls[i]; + if (pointInBall(*b, mouse.p)) selected = i; + } + } + + if((!mouse.down) && (mouse.button == SDL_BUTTON_RIGHT) && (selected >= 0)) { + Ball *b = &game->balls[selected]; + b->vx = 5.0f * (b->px - (float)mouse.p.x); + b->vy = 5.0f * (b->py - (float)mouse.p.y); + } + + if (!mouse.down) selected = -1; + + if(selected >= 0 && mouse.button != SDL_BUTTON_RIGHT) { + elapsedTime = 0; + Ball *b = &game->balls[selected]; + b->px = mouse.p.x; + b->py = mouse.p.y; + } + + for (int i = 0; i < BALL_COUNT; ++i) { + Ball *b1 = &game->balls[i]; + // drag + b1->ax = -b1->vx * 0.8f; + b1->ay = -b1->vy * 0.8f; + + b1->vx += b1->ax * elapsedTime; + b1->vy += b1->ay * elapsedTime; + b1->px += b1->vx * elapsedTime; + b1->py += b1->vy * elapsedTime; + + // wrap around screen + // Looks really bad, I'm removing this + // if (b1->px < 0) b1->px += (float)game->screen_rect.w; + // if (b1->px >= game->screen_rect.w) b1->px -= (float)game->screen_rect.w; + // if (b1->py < 0) b1->py += (float)game->screen_rect.h; + // if (b1->py >= game->screen_rect.h) b1->py -= (float)game->screen_rect.h; + + if (fabs(b1->vx * b1->vx + b1->vy * b1->vy) < 0.01f) { + b1->vx = 0; + b1->vy = 0; + } + + } + + setColor(game->renderer, COLOR_BLACK); + SDL_RenderClear(game->renderer); + + for (int i = 0; i < BALL_COUNT; ++i) { + Ball *b1 = &game->balls[i]; + if (i == selected) drawBall(game->renderer, *b1); + else drawCircle(game->renderer, game->screen_rect, b1->radius, b1->px, + b1->py, 2, b1->color); + + } + + // TODO + // set up a list of values that will not conflict for i and j in gameInit + + // TODO + // there is an error were the collision_count varaible will not reset to + // zero + for (int i = 0; i < BALL_COUNT; ++i) { + Ball *b1 = &game->balls[i]; + + for (int j = i + 1; j < BALL_COUNT; ++j) { + Ball *b2 = &game->balls[j]; + + if (!ballCollide(*b1, *b2)) continue; + game->balls_colliding = calloc(sizeof(Ball *), + (game->collision_count + 2)); + END((!game->balls_colliding), "calloc()", "could not allocate game->balls_colliding()" ); + game->balls_colliding[game->collision_count++] = b1; + game->balls_colliding[game->collision_count++] = b2; + float distance = fabs(getHyp(b1->px, b1->py, b2->px, b2->py)); + + float overlap = (distance - b1->radius - b2->radius); + + if (distance == 0) continue; + + b1->px -= overlap * (b1->px - b2->px) / distance; + b1->py -= overlap * (b1->py - b2->py) / distance; + b2->px += overlap * (b1->px - b2->px) / distance; + b2->py += overlap * (b1->py - b2->py) / distance; + // } + } + } + // printf("collision count: %d\n", game->collision_count); + for (int i = 0; i < game->collision_count; ++i) { + Ball *b1 = game->balls_colliding[i]; + Ball *b2 = game->balls_colliding[i + 1]; + if (!b1 || !b2) continue; + float distance = getHyp(b1->px, b1->py, b2->px, b2->py); + + // normal + float nx = (b2->px - b1->px) / distance; + float ny = (b2->py - b1->py) / distance; + + // tangent + float tx = -ny; + float ty = nx; + + float dpTan1 = b1->vx * tx + b1->vy * ty; + float dpTan2 = b2->vx * tx + b2->vy * ty; + + float dpNorm1 = b1->vx * nx + b1->vy * ny; + float dpNorm2 = b2->vx * nx + b2->vy * ny; + + float m1 = + (dpNorm1 * (b1->mass - b2->mass) + 2.0f * b2->mass * dpNorm2) + / (b1->mass + b2->mass); + + float m2 = + (dpNorm2 * (b2->mass - b1->mass) + 2.0f * b1->mass * dpNorm1) + / (b1->mass + b2->mass); + + b1->vx = tx * dpTan1 + nx * m1; + b1->vy = ty * dpTan1 + ny * m1; + b2->vx = tx * dpTan2 + nx * m2; + b2->vy = ty * dpTan2 + ny * m2; + + } + + if((mouse.down) && (mouse.button == SDL_BUTTON_RIGHT) && (selected >= 0)) { + Ball *b = &game->balls[selected]; + setColor(game->renderer, COLOR_WHITE); + SDL_RenderDrawLine(game->renderer, b->px, b->py, mouse.p.x, mouse.p.y); + } + if (selected < 0) drawCursor(game->renderer, mouse.p); + + if (game->collision_count > 0) { + // I do not have free each individual ball in ball colliding because + // they are all on the stack + free(game->balls_colliding); + game->balls_colliding = NULL; + } + + elapsedTime += 0.0008f; + + return UPDATE_MAIN; +} + +void +Game_Update(Game *game) +// The main game loop. Sets up which callback will be used in the function loop. +// Each update callback determines what update callback will be called next by +// returning the appropriate enum value +{ + uint8_t update_id = 0; + uint64_t frame = 0; + bool quit = false; + bool keydown = false; + float mspf = (1.0f / (float)game->fps) * 1000.0f; + SDL_Event event; + SDL_KeyCode key = 0; + Update_callback update; + uint32_t ticks_start = SDL_GetTicks(); + Mouse mouse; + + // TODO + // add backbuffer + + while (!quit) { + + // Place update functions here + switch (update_id) { + case UPDATE_MAIN: update = updateMain; break; + case UPDATE_NOTHING: update = updateNothing; break; + } + + while (SDL_PollEvent(&event)) { + + switch (event.type) { + case SDL_KEYDOWN: { + if (event.key.repeat == 0) { + key = event.key.keysym.sym; + keydown = true; + } + + break; + } + case SDL_MOUSEBUTTONDOWN: { + mouse.button = event.button.button; + mouse.down = true; + break; + } + case SDL_MOUSEBUTTONUP: { + mouse.button = event.button.button; + mouse.down = false; + break; + } + case SDL_MOUSEMOTION: { + mouse.p.x = event.motion.x; + mouse.p.y = event.motion.y; + break; + } + + case SDL_KEYUP: keydown = false; break; + case SDL_QUIT: quit = true; break; + } + } + + update_id = update(game, SDL_GetTicks(), frame, key, mouse,keydown); + + if ((SDL_GetTicks() - ticks_start) >= mspf) { + ticks_start = SDL_GetTicks(); + // SDL_SetRenderDrawBlendMode(game->renderer, SDL_BLENDMODE_NONE); + // SDL_Texture *texture = + // SDL_CreateTextureFromSurface(game->renderer, game->backbuffer); + // SDL_RenderCopyEx(game->renderer, texture, NULL, &game->screen_rect, + // 0, NULL, SDL_FLIP_NONE); + SDL_RenderPresent(game->renderer); + // SDL_DestroyTexture(texture); + } + + frame++; + } +} + +void createBalls(Game *game) { + for (int i = 0; i < BALL_COUNT; ++i) { + Ball *b = &game->balls[i]; + b->vx = 0; + b->vy = 0; + b->ax = 0; + b->ay = 0; + b->radius = ((rand() / (float)RAND_MAX) * (game->ball_size_max - + game->ball_size_min)) + game->ball_size_min; + + b->px = (rand() / (float)RAND_MAX) * (float)game->screen_rect.w; + + b->py = (rand() / (float)RAND_MAX) * (float)game->screen_rect.h; + + b->color = (rand() / (float)RAND_MAX) * ((float)COLOR_SIZE - 2); + + b->mass = b->radius * 10; + } + +} + +Game * +Game_Init() +// All the variable and data initialization needed for SDL and perhaps game +// variables +{ + static Game game = { + .screen_rect = {.x = 0, .y = 0, .w = 800, .h = 800}, + .fps = 60, + .ball_size_min = 15, + .ball_size_max = 50, + .backbuffer = NULL, + }; + + END(SDL_Init(SDL_INIT_VIDEO) != 0, "Could not create texture", + SDL_GetError()); + + END(TTF_Init() != 0, "Could not initialize TTF", TTF_GetError()); + + + + game.window = SDL_CreateWindow("balls", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, game.screen_rect.w, + game.screen_rect.h, SDL_WINDOW_SHOWN); + + END(game.window == NULL, "Could not create window", SDL_GetError()); + + game.renderer = + SDL_CreateRenderer(game.window, 0, SDL_RENDERER_ACCELERATED); + + END(game.renderer == NULL, "Could not create renderer", SDL_GetError()); + + SDL_SetRelativeMouseMode(true); + + // setColor(game->backbuffer, COLOR_BLACK); + // SDL_RenderClear(game->renderer); + game.backbuffer = + SDL_CreateRGBSurface(0, game.screen_rect.w, game.screen_rect.h, + 32, 0xFF000000, 0x00FF0000, 0x0000FF00, + 0x000000FF); + // fill backbuffer with black + SDL_FillRect(game.backbuffer, &game.screen_rect, 0x00000000); + + // print system information + printf("big ending = %s\n", SDL_BYTEORDER == SDL_BIG_ENDIAN ? + "true": "False"); + + srand(time(NULL)); + createBalls(&game); + + return &game; +} + +void +Game_Quit(Game *game) +{ + if (game->collision_count > 0) { + free(game->balls_colliding); + game->balls_colliding = NULL; + } + SDL_DestroyWindow(game->window); + SDL_DestroyRenderer(game->renderer); + SDL_FreeSurface(game->backbuffer); + TTF_Quit(); + SDL_Quit(); +} + +int +main(void) +{ + Game *game = Game_Init(); + Game_Update(game); + Game_Quit(game); + return 0; +} diff --git a/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.adoc b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.adoc new file mode 100644 index 0000000..2f303ba --- /dev/null +++ b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.adoc @@ -0,0 +1,192 @@ += Why Your Collision-Resolution Loop Freezes and How to Fix It +:wrap: normal + +[source,c] +---- +for (int i = 0; i < BALL_COUNT; ++i) { + Ball *b1 = &game->balls[i]; + + for (int j = i + 1; j < BALL_COUNT; ++j) { + Ball *b2 = &game->balls[j]; + + if (!ballCollide(*b1, *b2)) continue; + colliding = calloc(sizeof(Ball *), (collision_count + 2)); + END((!colliding), "calloc()", "could not allocate colliding()" ); + colliding[collision_count++] = b1; + colliding[collision_count++] = b2; + float distance = getHyp(b1->px, b1->py, b2->px, b2->py); + + float overlap = 0.5f * (distance - (float)b1->radius - + (float)b2->radius); + b1->px -= + overlap * (float)(b1->px - b2->px) / distance; + b1->py -= + overlap * (float)(b1->py - b2->py) / distance; + b2->px += + overlap * (float)(b1->px - b2->px) / distance; + b2->py += + overlap * (float)(b1->py - b2->py) / distance; + + // all collisions must be resolved + // TODO + // fix issue that freezes program from this loop + while(ballCollide(*b1, *b2)) { + b1->px -= (float)(b1->px - b2->px) / distance; + b1->py -= (float)(b1->py - b2->py) / distance; + b2->px += (float)(b1->px - b2->px) / distance; + b2->py += (float)(b1->py - b2->py) / distance; + } + } +} +---- + +Your code contains the classic problem of **over-correcting** and then +**rechecking using new positions**, which makes the loop never terminate. +Let’s walk through why this happens and how to fix it. + + +== 1. The critical problem: using updated positions inside the loop + +Inside your main collision-resolution: + +[source,c] +---- +b1->px -= overlap * (b1->px - b2->px) / distance; +b1->py -= overlap * (b1->py - b2->py) / distance; +b2->px += overlap * (b1->px - b2->px) / distance; +b2->py += overlap * (b1->py - b2->py) / distance; +---- + +This is correct. + +But then you do: + +[source,c] +---- +while (ballCollide(*b1, *b2)) { + b1->px -= (b1->px - b2->px) / distance; + b1->py -= (b1->py - b2->py) / distance; + b2->px += (b1->px - b2->px) / distance; + b2->py += (b1->py - b2->py) / distance; +} +---- + +== ❌ Why this freezes + +1. `distance` stays the **old** value (before modifying positions). +2. But `(b1->px - b2->px)` is updated each iteration. +3. This causes steps that: + * overshoot + * oscillate + * sometimes increase overlap instead of decreasing it +4. So `ballCollide()` keeps returning true forever → **infinite loop**. + +This is a well-known bug in naive iterative separation. + +== 2. Mathematical explanation + +Your update step behaves like: + + x ← x − (x / constant) + +This only converges to zero if: + + |1 − 1/constant| < 1 + +But because the denominator (`distance`) is incorrect after the first +shift, the term can jump back and forth. + +In other words: + +You are using a **fixed-length correction step based on old geometry**, +but applying it to **new geometry**. + +This makes the correction unstable. + + +== 3. Proper fix: recompute distance each iteration OR limit iterations + +Method A — Recompute distance every pass: + +[source,c] +---- +int loop_guard = 0; +while (ballCollide(*b1, *b2) && loop_guard++ < 4) { + float dx = b1->px - b2->px; + float dy = b1->py - b2->py; + float d = sqrtf(dx*dx + dy*dy); + + if (d == 0) break; + + float overlap = 0.5f * (d - b1->radius - b2->radius); + float nx = dx / d; + float ny = dy / d; + + b1->px -= overlap * nx; + b1->py -= overlap * ny; + b2->px += overlap * nx; + b2->py += overlap * ny; +} +---- + +Why this works: + +* `dx`, `dy`, and `distance` are recalculated from the new positions. +* The correction scales perfectly with the current geometry. +* The loop is guaranteed to converge. + +Method B — Avoid loops entirely +Just do **one correction step per frame**, which is how most engines and +the Jonathan Blow code handle it. + +== 4. Even better fix: use Blow’s displacement formula directly + +His code displaces balls only **once** per collision pass: + +[source,c] +---- +float fDistance = sqrtf(dx*dx + dy*dy); +float fOverlap = 0.5f * (fDistance - r1 - r2); + +b1->px -= fOverlap * (dx / fDistance); +b1->py -= fOverlap * (dy / fDistance); + +b2->px += fOverlap * (dx / fDistance); +b2->py += fOverlap * (dy / fDistance); +---- + +This resolves overlap in **one stable pass**, so you don’t need a loop. + +== 5. Bonus: Your calloc() usage is also wrong + +You do: + +[source,c] +---- +colliding = calloc(sizeof(Ball *), (collision_count + 2)); +---- + +Correct `calloc` order is: + + calloc(num_elements, element_size) + +So you should use: + +[source,c] +---- +colliding = calloc(collision_count + 2, sizeof(Ball *)); +---- + +== 6. Final Summary + +* The infinite loop comes from using **outdated distance values** while + updating positions. +* This causes oscillation → `ballCollide()` never returns false. +* Fixes: + - recompute distance each iteration, or + - apply the correct one-shot overlap resolution (recommended). +* Also fix your `calloc` order. + +If you want, I can rewrite your entire collision loop into a clean, +correct, minimal version similar to Jonathan Blow’s system. + diff --git a/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.html b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.html new file mode 100644 index 0000000..371d920 --- /dev/null +++ b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes.html @@ -0,0 +1,738 @@ + + + + + + + +Why Your Collision-Resolution Loop Freezes and How to Fix It + + + + + +
+
+
+
+
+
for (int i = 0; i < BALL_COUNT; ++i) {
+    Ball *b1 = &game->balls[i];
+
+    for (int j = i + 1; j < BALL_COUNT; ++j) {
+        Ball *b2 = &game->balls[j];
+
+        if (!ballCollide(*b1, *b2)) continue;
+            colliding = calloc(sizeof(Ball *), (collision_count + 2));
+            END((!colliding), "calloc()", "could not allocate colliding()" );
+            colliding[collision_count++] = b1;
+            colliding[collision_count++] = b2;
+            float distance = getHyp(b1->px, b1->py, b2->px, b2->py);
+
+            float overlap = 0.5f * (distance - (float)b1->radius -
+                            (float)b2->radius);
+            b1->px -=
+                overlap * (float)(b1->px - b2->px) / distance;
+            b1->py -=
+                overlap * (float)(b1->py - b2->py) / distance;
+            b2->px +=
+                overlap * (float)(b1->px - b2->px) / distance;
+            b2->py +=
+                overlap * (float)(b1->py - b2->py) / distance;
+
+        // all collisions must be resolved
+        // TODO
+        // fix issue that freezes program from this loop
+        while(ballCollide(*b1, *b2)) {
+            b1->px -= (float)(b1->px - b2->px) / distance;
+            b1->py -= (float)(b1->py - b2->py) / distance;
+            b2->px += (float)(b1->px - b2->px) / distance;
+            b2->py += (float)(b1->py - b2->py) / distance;
+        }
+    }
+}
+
+
+
+

Your code contains the classic problem of over-correcting and then +rechecking using new positions, which makes the loop never terminate. +Let’s walk through why this happens and how to fix it.

+
+
+
+
+

1. The critical problem: using updated positions inside the loop

+
+
+

Inside your main collision-resolution:

+
+
+
+
b1->px -= overlap * (b1->px - b2->px) / distance;
+b1->py -= overlap * (b1->py - b2->py) / distance;
+b2->px += overlap * (b1->px - b2->px) / distance;
+b2->py += overlap * (b1->py - b2->py) / distance;
+
+
+
+

This is correct.

+
+
+

But then you do:

+
+
+
+
while (ballCollide(*b1, *b2)) {
+    b1->px -= (b1->px - b2->px) / distance;
+    b1->py -= (b1->py - b2->py) / distance;
+    b2->px += (b1->px - b2->px) / distance;
+    b2->py += (b1->py - b2->py) / distance;
+}
+
+
+
+
+
+

❌ Why this freezes

+
+
+
    +
  1. +

    distance stays the old value (before modifying positions).

    +
  2. +
  3. +

    But (b1→px - b2→px) is updated each iteration.

    +
  4. +
  5. +

    This causes steps that:

    +
    +
      +
    • +

      overshoot

      +
    • +
    • +

      oscillate

      +
    • +
    • +

      sometimes increase overlap instead of decreasing it

      +
    • +
    +
    +
  6. +
  7. +

    So ballCollide() keeps returning true forever → infinite loop.

    +
  8. +
+
+
+

This is a well-known bug in naive iterative separation.

+
+
+
+
+

2. Mathematical explanation

+
+
+

Your update step behaves like:

+
+
+
+
x ← x − (x / constant)
+
+
+
+

This only converges to zero if:

+
+
+
+
|1 − 1/constant| < 1
+
+
+
+

But because the denominator (distance) is incorrect after the first +shift, the term can jump back and forth.

+
+
+

In other words:

+
+
+

You are using a fixed-length correction step based on old geometry, +but applying it to new geometry.

+
+
+

This makes the correction unstable.

+
+
+
+
+

3. Proper fix: recompute distance each iteration OR limit iterations

+
+
+

Method A — Recompute distance every pass:

+
+
+
+
int loop_guard = 0;
+while (ballCollide(*b1, *b2) && loop_guard++ < 4) {
+    float dx = b1->px - b2->px;
+    float dy = b1->py - b2->py;
+    float d  = sqrtf(dx*dx + dy*dy);
+
+    if (d == 0) break;
+
+    float overlap = 0.5f * (d - b1->radius - b2->radius);
+    float nx = dx / d;
+    float ny = dy / d;
+
+    b1->px -= overlap * nx;
+    b1->py -= overlap * ny;
+    b2->px += overlap * nx;
+    b2->py += overlap * ny;
+}
+
+
+
+

Why this works:

+
+
+
    +
  • +

    dx, dy, and distance are recalculated from the new positions.

    +
  • +
  • +

    The correction scales perfectly with the current geometry.

    +
  • +
  • +

    The loop is guaranteed to converge.

    +
  • +
+
+
+

Method B — Avoid loops entirely +Just do one correction step per frame, which is how most engines and +the Jonathan Blow code handle it.

+
+
+
+
+

4. Even better fix: use Blow’s displacement formula directly

+
+
+

His code displaces balls only once per collision pass:

+
+
+
+
float fDistance = sqrtf(dx*dx + dy*dy);
+float fOverlap = 0.5f * (fDistance - r1 - r2);
+
+b1->px -= fOverlap * (dx / fDistance);
+b1->py -= fOverlap * (dy / fDistance);
+
+b2->px += fOverlap * (dx / fDistance);
+b2->py += fOverlap * (dy / fDistance);
+
+
+
+

This resolves overlap in one stable pass, so you don’t need a loop.

+
+
+
+
+

5. Bonus: Your calloc() usage is also wrong

+
+
+

You do:

+
+
+
+
colliding = calloc(sizeof(Ball *), (collision_count + 2));
+
+
+
+

Correct calloc order is:

+
+
+
+
calloc(num_elements, element_size)
+
+
+
+

So you should use:

+
+
+
+
colliding = calloc(collision_count + 2, sizeof(Ball *));
+
+
+
+
+
+

6. Final Summary

+
+
+
    +
  • +

    The infinite loop comes from using outdated distance values while +updating positions.

    +
  • +
  • +

    This causes oscillation → ballCollide() never returns false.

    +
  • +
  • +

    Fixes:

    +
    +
      +
    • +

      recompute distance each iteration, or

      +
    • +
    • +

      apply the correct one-shot overlap resolution (recommended).

      +
    • +
    +
    +
  • +
  • +

    Also fix your calloc order.

    +
  • +
+
+
+

If you want, I can rewrite your entire collision loop into a clean, +correct, minimal version similar to Jonathan Blow’s system.

+
+
+
+
+ + + \ No newline at end of file diff --git a/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.adoc b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.adoc new file mode 100644 index 0000000..4c2fcdb --- /dev/null +++ b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.adoc @@ -0,0 +1,95 @@ += Why Recomputing Distance Still Freezes the Loop +Even when recomputing `distance` inside the loop, it can still freeze. This +happens when: + +* The distance between ball centers becomes **zero or extremely close to zero**. +* The overlap becomes **very tiny** but never fully resolves because floating- + point error prevents the condition from becoming false. +* The collision logic attempts to normalize a zero-length vector. + +Below is the real reason and the correct fix. + +== The Real Crash Scenario: Distance == 0 +If two balls overlap **perfectly**, meaning: + +---- +b1->px == b2->px +b1->py == b2->py +---- + +Then: + +---- +dx = 0 +dy = 0 +distance = sqrt(0 + 0) = 0 +---- + +Normalizing `(dx, dy)` becomes: + +---- +nx = dx / distance --> division by zero +---- + +This produces `NaN`, and once NaNs enter your coordinates, your loop **never +terminates**, because every test becomes nonsense. + +== Why This Happens +Your simulation allows balls to be pushed so close together (or initialized too +close) that they fully overlap before you start resolving. + +Your while-loop approach *cannot* recover when distance reaches 0. + +== The Fix: Replace the Loop With One Guaranteed Separation Step +Physics engines never use repeated loops; they use one mathematically correct +position correction. + +Here is the correct safe displacement code: + +[source,c] +---- +float dx = b1->px - b2->px; +float dy = b1->py - b2->py; +float d2 = dx*dx + dy*dy; + +// If centers are identical, push them apart arbitrarily +if (d2 == 0.0f) { + dx = 1.0f; + dy = 0.0f; + d2 = 1.0f; +} + +float distance = sqrtf(d2); +float overlap = (b1->radius + b2->radius) - distance; + +if (overlap > 0.0f) { + float nx = dx / distance; + float ny = dy / distance; + + float half = overlap * 0.5f; + + b1->px += nx * half; + b1->py += ny * half; + + b2->px -= nx * half; + b2->py -= ny * half; +} +---- + +== Why This Works +* **No loop**, so it can’t freeze. +* Handles the `distance == 0` case safely. +* Fully resolves the overlap mathematically. +* Guaranteed to stop balls from colliding in a single step. +* `collision_count` will now drop to zero. + +== Summary +Recomputing distance didn't fix the freeze because the loop becomes invalid +whenever the distance is zero or extremely tiny. +The correct solution is to completely remove the loop and use one safe, +mathematically correct separation step. + +== Want the Full Fixed Version? +If you want, I can rewrite your entire collision detection and resolution block +into clean, safe, minimal C code. + diff --git a/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.html b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.html new file mode 100644 index 0000000..bdd17be --- /dev/null +++ b/collisions/docs/chatgpt_dump/Collision-Resolution-Loop-Freezes2.html @@ -0,0 +1,609 @@ + + + + + + + + +Why Recomputing Distance Still Freezes the Loop + + + + + +
+
+
+
+
    +
  • +

    The distance between ball centers becomes zero or extremely close to zero.

    +
  • +
  • +

    The overlap becomes very tiny but never fully resolves because floating- +point error prevents the condition from becoming false.

    +
  • +
  • +

    The collision logic attempts to normalize a zero-length vector.

    +
  • +
+
+
+

Below is the real reason and the correct fix.

+
+
+
+
+

The Real Crash Scenario: Distance == 0

+
+
+

If two balls overlap perfectly, meaning:

+
+
+
+
b1->px == b2->px
+b1->py == b2->py
+
+
+
+

Then:

+
+
+
+
dx = 0
+dy = 0
+distance = sqrt(0 + 0) = 0
+
+
+
+

Normalizing (dx, dy) becomes:

+
+
+
+
nx = dx / distance  --> division by zero
+
+
+
+

This produces NaN, and once NaNs enter your coordinates, your loop never +terminates, because every test becomes nonsense.

+
+
+
+
+

Why This Happens

+
+
+

Your simulation allows balls to be pushed so close together (or initialized too +close) that they fully overlap before you start resolving.

+
+
+

Your while-loop approach cannot recover when distance reaches 0.

+
+
+
+
+

The Fix: Replace the Loop With One Guaranteed Separation Step

+
+
+

Physics engines never use repeated loops; they use one mathematically correct +position correction.

+
+
+

Here is the correct safe displacement code:

+
+
+
+
float dx = b1->px - b2->px;
+float dy = b1->py - b2->py;
+float d2 = dx*dx + dy*dy;
+
+// If centers are identical, push them apart arbitrarily
+if (d2 == 0.0f) {
+    dx = 1.0f;
+    dy = 0.0f;
+    d2 = 1.0f;
+}
+
+float distance = sqrtf(d2);
+float overlap  = (b1->radius + b2->radius) - distance;
+
+if (overlap > 0.0f) {
+    float nx = dx / distance;
+    float ny = dy / distance;
+
+    float half = overlap * 0.5f;
+
+    b1->px += nx * half;
+    b1->py += ny * half;
+
+    b2->px -= nx * half;
+    b2->py -= ny * half;
+}
+
+
+
+
+
+

Why This Works

+
+
+
    +
  • +

    No loop, so it can’t freeze.

    +
  • +
  • +

    Handles the distance == 0 case safely.

    +
  • +
  • +

    Fully resolves the overlap mathematically.

    +
  • +
  • +

    Guaranteed to stop balls from colliding in a single step.

    +
  • +
  • +

    collision_count will now drop to zero.

    +
  • +
+
+
+
+
+

Summary

+
+
+

Recomputing distance didn’t fix the freeze because the loop becomes invalid +whenever the distance is zero or extremely tiny. +The correct solution is to completely remove the loop and use one safe, +mathematically correct separation step.

+
+
+
+
+

Want the Full Fixed Version?

+
+
+

If you want, I can rewrite your entire collision detection and resolution block +into clean, safe, minimal C code.

+
+
+
+
+ + + \ No newline at end of file diff --git a/collisions/docs/formulas.adoc b/collisions/docs/formulas.adoc new file mode 100644 index 0000000..91e3ad5 --- /dev/null +++ b/collisions/docs/formulas.adoc @@ -0,0 +1,126 @@ +:stem: latexmath +:source-highlighter: pygments +:pygments-style: monokai +:wrap: normal + += Formulas + +* Dot Product + +[stem] +++++ +\overrightarrow{a} \cdot \overrightarrow{b} \\ +\left[ \begin{array}{c} +a_{1} \\ +a_{2} \\ +\vdots \\ +a_{n} +\end{array} \right] + +\cdot + +\left[ \begin{array}{c} +b_{1} \\ +b_{2} \\ +\vdots \\ +b_{n} +\end{array} \right] += +a_{1} * b_{1} + a_{2} * b_{2} + * a_{n} * b_{n} +++++ + +* Circle Overlapping + +[stem] +++++ +|\sqrt{a^2 + b^2}| <= r_1 + r_2 +++++ + +NOTE: a and a is the opposite and adjacent sides found from + the centers of both circles + + +* Pythagrean Thereom to find distancce between balls centers + +[stem] +++++ +a^2 + b^2 = c^2 +++++ + +* Vector Normalization + +[stem] +++++ +\hat{u} = \frac{\overrightarrow{v}}{|\overrightarrow{v}|} +++++ + + +* Magnitude of a Vector + +[stem] +++++ +|\overrightarrow{a}| = \sqrt{a_{1}^2 + a_{2}^2 + \dots a_{n}^2} +++++ + +* Finding tangent vector + +[stem] +++++ +x = -y, y = x +++++ + +* 1D Conservation of Momentum + +[stem] +++++ +m_1 v_1 + m_2 v_2 = m_1 v_1 + m_2 v_2 +++++ + +* 1D Elastic Collision + +[stem] +++++ +v_1' = (v_1 * (m_1-m_2) + 2 * m_2 * v_2)) / (m_1 + m_2) \\ +v_2' = (v_2 * (m_2-m_1) + 2 * m_1 * v_1)) / (m_1 + m_2) +++++ + +* Finding magnitude in a specific direction + +[stem] +++++ +|\overrightarrow{v}| = \overrightarrow{n} \cdot \overrightarrow{v} +++++ + +* Distinct unordered pairs + +[stem] +++++ +(n,2) = \frac{n(n-1)}{2}] +++++ + + +== Seeing if circle collide +Instead of doing this: + +[source,c] +---- +sqrtf((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= (r1 + r2); +---- + +We can save some computing speed by removing the square root and just +multiplying the left side by itself: + +[source,c] +---- +fabs((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= (r1 + r2)*(r1 + r2); +---- + +== Clamping Velocity + +We can use the dot product to find the velocity in specific direction and then +clamp it + +[source,c] +---- +if (fabs(ball.vx*ball.vx + ball.vy*ball.vy) < 0.01f) +---- diff --git a/collisions/docs/formulas.html b/collisions/docs/formulas.html new file mode 100644 index 0000000..efb1004 --- /dev/null +++ b/collisions/docs/formulas.html @@ -0,0 +1,745 @@ + + + + + + + +Formulas + + + + + + +
+
+
+
+
    +
  • +

    Dot Product

    +
  • +
+
+
+
+\[\overrightarrow{a} \cdot \overrightarrow{b} \\ +\left[ \begin{array}{c} +a_{1} \\ +a_{2} \\ +\vdots \\ +a_{n} +\end{array} \right] + +\cdot + +\left[ \begin{array}{c} +b_{1} \\ +b_{2} \\ +\vdots \\ +b_{n} +\end{array} \right] += +a_{1} * b_{1} + a_{2} * b_{2} + * a_{n} * b_{n}\] +
+
+
+
    +
  • +

    Circle Overlapping

    +
  • +
+
+
+
+\[|\sqrt{a^2 + b^2}| <= r_1 + r_2\] +
+
+
+ + + + + +
+
Note
+
+a and a is the opposite and adjacent sides found from + the centers of both circles +
+
+
+
    +
  • +

    Pythagrean Thereom to find distancce between balls centers

    +
  • +
+
+
+
+\[a^2 + b^2 = c^2\] +
+
+
+
    +
  • +

    Vector Normalization

    +
  • +
+
+
+
+\[\hat{u} = \frac{\overrightarrow{v}}{|\overrightarrow{v}|}\] +
+
+
+
    +
  • +

    Magnitude of a Vector

    +
  • +
+
+
+
+\[|\overrightarrow{a}| = \sqrt{a_{1}^2 + a_{2}^2 + \dots a_{n}^2}\] +
+
+
+
    +
  • +

    Finding tangent vector

    +
  • +
+
+
+
+\[x = -y, y = x\] +
+
+
+
    +
  • +

    1D Conservation of Momentum

    +
  • +
+
+
+
+\[m_1 v_1 + m_2 v_2 = m_1 v_1 + m_2 v_2\] +
+
+
+
    +
  • +

    1D Elastic Collision

    +
  • +
+
+
+
+\[v_1' = (v_1 * (m_1-m_2) + 2 * m_2 * v_2)) / (m_1 + m_2) \\ +v_2' = (v_2 * (m_2-m_1) + 2 * m_1 * v_1)) / (m_1 + m_2)\] +
+
+
+
    +
  • +

    Finding magnitude in a specific direction

    +
  • +
+
+
+
+\[|\overrightarrow{v}| = \overrightarrow{n} \cdot \overrightarrow{v}\] +
+
+
+
    +
  • +

    Distinct unordered pairs

    +
  • +
+
+
+
+\[(n,2) = \frac{n(n-1)}{2}]\] +
+
+
+
+
+

Seeing if circle collide

+
+
+

Instead of doing this:

+
+
+
+
sqrtf((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= (r1 + r2);
+
+
+
+

We can save some computing speed by removing the square root and just +multiplying the left side by itself:

+
+
+
+
fabs((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)) <= (r1 + r2)*(r1 + r2);
+
+
+
+
+
+

Clamping Velocity

+
+
+

We can use the dot product to find the velocity in specific direction and then +clamp it

+
+
+
+
if (fabs(ball.vx*ball.vx + ball.vy*ball.vy) < 0.01f)
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/docinfo.conf b/docinfo.conf new file mode 100644 index 0000000..57fa5b9 --- /dev/null +++ b/docinfo.conf @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +HOSTNAME=nyquistdev.net +DOCINFODIR=/usr/local/share/readme-to-html diff --git a/notes.adoc b/notes.adoc new file mode 100644 index 0000000..3288e63 --- /dev/null +++ b/notes.adoc @@ -0,0 +1,12 @@ +== GDB + +remember that conditional break points exist + +== Links +* https://www.youtube.com/watch?v=nXrEX6j-Mws&list=PLkhTWnF-dZA7fqD7CF8koSTsRbmfBfr2_ +* https://www.unitconverters.net/typography/pixel-x-to-meter.htm[pixels to meters] +* https://simulationshub.com/ball-drop/[interactive ball] +* https://www.physicslens.com/simulation-of-a-bouncing-ball/ +* https://www.youtube.com/watch?v=hG9SzQxaCm8 +* https://en.wikipedia.org/wiki/Bouncing_ball +* https://youtu.be/LPzyNOHY3A4?si=Vv4pVQ0cbLwPI9Zt[javidx9] diff --git a/rendered.html b/rendered.html new file mode 100644 index 0000000..77e5c8c --- /dev/null +++ b/rendered.html @@ -0,0 +1,951 @@ + + + + + + + +Balls + + + + + + + + + + + + + + + + + + + + +
+
+
+
+

This repository has physics examples using balls. Each folder has a different +example, with a link to the formulas used in the readme’s.

+
+
+
+
+

2D Projectile Motion

+
+
+

checkout this link +formulas. +for a list of forumlas used.

+
+ ++++ + + + + + + + + + + + + + + + + +
controlaction

right-click and drag

determine direction and velocity of ball

right-click release

launch ball

+
+
+
+

Bouncing

+
+
+

check out this link +formulas. +for the formulas used.

+
+
+
+
+

Collisions

+
+
+

Creates random balls. These balls will resolve any collision issues. Launching a +ball will show you an example of elastic collision.

+
+
+

checkout this link +formulas. +for a list of forumlas used.

+
+ ++++ + + + + + + + + + + + + + + + + +
controlaction

right-click

select ball

right-click and left-click and drag

flings ball on release, the distance + away from the ball determines the amount + of velocity to launch the ball at

+
+
+
+

Building html from formula .adoc files

+
+
+
+
./Makehtml make
+
+
+
+
+ +
+ + + + \ No newline at end of file -- 2.43.0