Commit 9307bdfc authored by Adam H. Abbas's avatar Adam H. Abbas
Browse files

Initial commit

parents
Showing with 944 additions and 0 deletions
+944 -0
{
"configurations": [
// Windows-specific
{
"name": "Win32",
"intelliSenseMode": "msvc-x64",
"compilerPath": "cl.exe",
"cStandard": "c11",
"includePath": [
"${workspaceFolder}/**",
"C:/Users/${env:USERNAME}/msvc/include"
],
"defines": [
"_USE_MATH_DEFINES",
"_WIN32",
"_CRT_SECURE_NO_WARNINGS"
]
},
// Default to clang for other systems
{
"name": "Mac",
"intelliSenseMode": "clang-x64",
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"includePath": [
"${workspaceFolder}/**",
"/usr/local/Cellar",
"/opt/homebrew/include",
"/usr/local/include"
]
},
{
"name": "Linux",
"intelliSenseMode": "clang-x64",
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"includePath": [
"${workspaceFolder}/**",
"/usr/local/include"
]
}
],
"version": 4
}
\ No newline at end of file
{
"version": "0.2.0",
"configurations": [
// Need to separate debug configurations because
// they use different debuggers
// If you want to create a configuration for a specific binary,
// it would look like these two, with the corresponding stuff in tasks
{
"name": "(Windows) Debug bin/bounce",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/bounce.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "newExternalWindow",
"preLaunchTask": "make bin/bounce",
},
{
"name": "(OS X / Linux) Debug bin/bounce",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/bounce",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "make bin/bounce",
},
// On the other hand, there's a _lot_ of test suites, and I don't
// want to write a configuration for all of them.
// This config actually works for demos too!
{
"name": "(Windows) Debug active file",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "newExternalWindow",
"preLaunchTask": "make active file",
},
{
"name": "(OS X / Linux) Debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/path/to/gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "make active file",
},
]
}
{
// Easier to go through VsDevCmd than to hack PATH
"terminal.integrated.shell.windows": "cmd.exe",
"terminal.integrated.env.windows": {
"VsDevCmd": "\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat\""
},
"terminal.integrated.shellArgs.windows": [
"/k",
"%VsDevCmd%",
"&&",
// CMD _sucks_, drop into PS immediately
"powershell.exe"
],
"terminal.integrated.profiles.windows": {
// Other profiles that should probably be present for Reasons TM
"PowerShell": {
"source": "PowerShell",
"icon": "terminal-powershell"
},
"Command Prompt": {
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [],
"icon": "terminal-cmd"
},
"Visual Studio Developer Prompt": {
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [
"/k",
// Easier to go through vcvars64 than to hack PATH
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat",
"&&",
// CMD _sucks_, drop into PS immediately
"powershell.exe"
],
"icon": "terminal-powershell",
},
},
// User-level only... Just delete when you're done with the course.
"terminal.integrated.defaultProfile.windows": "Visual Studio Developer Prompt"
}
{
"version": "2.0.0",
// Not _really_ necessary to run , but it's easier. Maybe.
"windows": {
"options": {
"env": {
"VsDevCmd": "\"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat\""
},
// All tasks that use "shell" will pass through this shell
"shell": {
// ... I can't really explain this.
// Just go with it. Maybe someone else knows more.
"executable": "cmd.exe",
"args": [
"/k", "%VsDevCmd%", "&&"
]
}
},
},
"tasks": [
{
"label": "make all",
"type": "process",
"command": "make",
"args": [
// Close the shell to make sure it can be reused
// by repeated tasks
"all", "&", "exit"
],
"group": "build",
"problemMatcher": ["$gcc", "$msCompile"]
},
{
"label": "make bin/bounce",
"type": "shell",
"command": "make",
"group": "build",
"args": [
"bin/bounce",
"&", "exit"
],
"problemMatcher": ["$gcc", "$msCompile"],
"promptOnClose": true,
},
{
"label": "test",
"type": "shell",
"command": "make",
"group": {
"kind": "test",
"isDefault": true
},
"args": [
"test", "&", "exit"
],
"problemMatcher": ["$gcc", "$msCompile"]
},
// Works on any file that has a corresponding binary
{
"label": "make active file",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
},
"args": [
"bin/${fileBasenameNoExtension}",
"&", "exit"
],
"problemMatcher": ["$gcc", "$msCompile"],
"promptOnClose": true,
},
]
}
# List of demo programs
DEMOS = bounce gravity pacman nbodies damping spaceinvaders
# List of C files in "libraries" that we provide
STAFF_LIBS = test_util sdl_wrapper
# List of C files in "libraries" that you will write.
# This also defines the order in which the tests are run.
STUDENT_LIBS = vector list polygon color body scene forces collision
# If we're not on Windows...
ifneq ($(OS), Windows_NT)
# Use clang as the C compiler
CC = clang
# Flags to pass to clang:
# -Iinclude tells clang to look for #include files in the "include" folder
# -Wall turns on all warnings
# -g adds filenames and line numbers to the executable for useful stack traces
# -fno-omit-frame-pointer allows stack traces to be generated
# (take CS 24 for a full explanation)
# -fsanitize=address enables asan
CFLAGS = -Iinclude $(shell sdl2-config --cflags | sed -e "s/include\/SDL2/include/") -Wall -g -fno-omit-frame-pointer -fsanitize=address -Wno-nullability-completeness
# Compiler flag that links the program with the math library
LIB_MATH = -lm
# Compiler flags that link the program with the math and SDL libraries.
# Note that $(...) substitutes a variable's value, so this line is equivalent to
# LIBS = -lm -lSDL2 -lSDL2_gfx
LIBS = $(LIB_MATH) $(shell sdl2-config --libs) -lSDL2_gfx
# List of compiled .o files corresponding to STUDENT_LIBS, e.g. "out/vector.o".
# Don't worry about the syntax; it's just adding "out/" to the start
# and ".o" to the end of each value in STUDENT_LIBS.
STUDENT_OBJS = $(addprefix out/,$(STUDENT_LIBS:=.o))
# List of test suite executables, e.g. "bin/test_suite_vector"
TEST_BINS = $(addprefix bin/test_suite_,$(STUDENT_LIBS))
# List of demo executables, i.e. "bin/bounce".
DEMO_BINS = $(addprefix bin/,$(DEMOS))
# All executables (the concatenation of TEST_BINS and DEMO_BINS)
BINS = $(TEST_BINS) $(DEMO_BINS)
# The first Make rule. It is relatively simple:
# "To build 'all', make sure all files in BINS are up to date."
# You can execute this rule by running the command "make all", or just "make".
all: $(BINS)
# Any .o file in "out" is built from the corresponding C file.
# Although .c files can be directly compiled into an executable, first building
# .o files reduces the amount of work needed to rebuild the executable.
# For example, if only list.c was modified since the last build, only list.o
# gets recompiled, and clang reuses the other .o files to build the executable.
#
# "%" means "any string".
# Unlike "all", this target has a build command.
# "$^" is a special variable meaning "the source files"
# and $@ means "the target file", so the command tells clang
# to compile the source C file into the target .o file.
out/%.o: library/%.c # source file may be found in "library"
$(CC) -c $(CFLAGS) $^ -o $@
out/%.o: demo/%.c # or "demo"
$(CC) -c $(CFLAGS) $^ -o $@
out/%.o: tests/%.c # or "tests"
$(CC) -c $(CFLAGS) $^ -o $@
# Builds bin/bounce by linking the necessary .o files.
# Unlike the out/%.o rule, this uses the LIBS flags and omits the -c flag,
# since it is building a full executable.
bin/bounce: out/bounce.o out/sdl_wrapper.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
bin/gravity: out/gravity.o out/sdl_wrapper.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
bin/pacman: out/pacman.o out/sdl_wrapper.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
bin/nbodies: out/nbodies.o out/sdl_wrapper.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
bin/damping: out/damping.o out/sdl_wrapper.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
bin/spaceinvaders: out/spaceinvaders.o out/sdl_wrapper.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
# Builds the test suite executables from the corresponding test .o file
# and the library .o files. The only difference from the demo build command
# is that it doesn't link the SDL libraries.
bin/test_suite_%: out/test_suite_%.o out/test_util.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIB_MATH) $^ -o $@
bin/student_tests: out/student_tests.o out/test_util.o $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIB_MATH) $^ -o $@
# Runs the tests. "$(TEST_BINS)" requires the test executables to be up to date.
# The command is a simple shell script:
# "set -e" configures the shell to exit if any of the tests fail
# "for f in $(TEST_BINS); do ...; done" loops over the test executables,
# assigning the variable f to each one
# "echo $$f" prints the test suite
# "$$f" runs the test; "$$" escapes the $ character,
# and "$f" tells the shell to substitute the value of the variable f
# "echo" prints a newline after each test's output, for readability
test: $(TEST_BINS)
set -e; for f in $(TEST_BINS); do echo $$f; $$f; echo; done
# Removes all compiled files.
# find <dir> is the command to find files in a directory
# ! -name .gitignore tells find to ignore the .gitignore
# -type f only finds files
# -delete deletes all the files found
clean:
find out/ ! -name .gitignore -type f -delete && \
find bin/ ! -name .gitignore -type f -delete
# This special rule tells Make that "all", "clean", and "test" are rules
# that don't build a file.
.PHONY: all clean test
# Tells Make not to delete the .o files after the executable is built
.PRECIOUS: out/%.o
# Windows is _special_
# Define a completely separate set of rules, because syntax and shell
else
# Make normally uses sh, which isn't on Windows by default
# Some systems might have it though...
# So, explicitly use cmd (ew)
# I want to rewrite the test and clean portions in Powershell but
# don't have the time now.
SHELL = cmd.exe
# Use MSVC cl.exe as the C compiler
CC = cl.exe
# Flags to pass to cl.exe:
# -I"C:/Users/$(USERNAME)/msvc/include"
# - include files that would normally be in /usr/include or something
# -Iinclude = -Iinclude
# -Zi = -g (with debug info in a separate file)
# -W3 turns on warnings (W4 is overkill for this class)
# -Oy- = -fno-omit-frame-pointer. May be unnecessary.
# -fsanitize=address = ...
CFLAGS := -I"C:/Users/$(USERNAME)/msvc/include"
CFLAGS += -Iinclude -Zi -W3 -Oy-
# You may want to turn this off for certain types of debugging.
CFLAGS += -fsanitize=address
# Define _WIN32, telling the programs that they are running on Windows.
CFLAGS += -D_WIN32
# Math constants are not in the standard
CFLAGS += -D_USE_MATH_DEFINES
# Some functions are """unsafe""", like snprintf. We don't care.
CFLAGS += -D_CRT_SECURE_NO_WARNINGS
# Include the full path for the msCompile problem matcher
C_FLAGS += -FC
# Libraries that we are linking against.
# Note that a lot of the base Windows ones are missing - the
# libraries I've distributed are _dynamically linked_, because otherwise,
# we'd need to manually link a lot of crap.
LIBS = SDL2main.lib SDL2.lib SDL2_gfx.lib shell32.lib
# Tell cl to look for lib files in this folder
LINKEROPTS = -LIBPATH:"C:/Users/$(USERNAME)/msvc/lib"
# If SDL2 is included in a file with main, it takes over main with its own def.
# We need to explicitly indicate the application type.
# NOTE: CONSOLE is single-threaded. Multithreading needs to use WINDOWS.
LINKEROPTS += -SUBSYSTEM:CONSOLE
# WHY IS LNK4098 HAPPENING (no ill effects from brief checks)
LINKEROPTS += -NODEFAULTLIB:msvcrt.lib
# List of compiled .obj files corresponding to STUDENT_LIBS,
# e.g. "out/vector.obj".
# Don't worry about the syntax; it's just adding "out/" to the start
# and ".obj" to the end of each value in STUDENT_LIBS.
STUDENT_OBJS = $(addprefix out/,$(STUDENT_LIBS:=.obj))
# List of test suite executables, e.g. "bin/test_suite_vector.exe"
TEST_BINS = $(addsuffix .exe,$(addprefix bin/test_suite_,$(STUDENT_LIBS)))
# List of demo executables, i.e. "bin/bounce.exe".
DEMO_BINS = $(addsuffix .exe,$(addprefix bin/,$(DEMOS)))
# All executables (the concatenation of TEST_BINS and DEMO_BINS)
BINS = $(TEST_BINS) $(DEMO_BINS)
# The first Make rule. It is relatively simple:
# "To build 'all', make sure all files in BINS are up to date."
# You can execute this rule by running the command "make all", or just "make".
all: $(BINS)
# Any .o file in "out" is built from the corresponding C file.
# Although .c files can be directly compiled into an executable, first building
# .o files reduces the amount of work needed to rebuild the executable.
# For example, if only list.c was modified since the last build, only list.o
# gets recompiled, and clang reuses the other .o files to build the executable.
#
# "%" means "any string".
# Unlike "all", this target has a build command.
# "$^" is a special variable meaning "the source files"
# and $@ means "the target file", so the command tells clang
# to compile the source C file into the target .obj file. (via -Fo)
out/%.obj: library/%.c # source file may be found in "library"
$(CC) -c $^ $(CFLAGS) -Fo"$@"
out/%.obj: demo/%.c # or "demo"
$(CC) -c $^ $(CFLAGS) -Fo"$@"
out/%.obj: tests/%.c # or "tests"
$(CC) -c $^ $(CFLAGS) -Fo"$@"
bin/bounce.exe bin\bounce.exe: out/bounce.obj out/sdl_wrapper.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) $(LIBS) -out:"$@"
bin/gravity.exe bin\gravity.exe: out/gravity.obj out/sdl_wrapper.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) $(LIBS) -out:"$@"
bin/pacman.exe bin\pacman.exe: out/pacman.obj out/sdl_wrapper.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) $(LIBS) -out:"$@"
bin/nbodies.exe: out/nbodies.obj out/sdl_wrapper.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) $(LIBS) -out:"$@"
bin/damping.exe: out/damping.obj out/sdl_wrapper.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) $(LIBS) -out:"$@"
bin/spaceinvaders.exe: out/spaceinvaders.obj out/sdl_wrapper.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) $(LIBS) -out:"$@"
# Builds the test suite executables from the corresponding test .o file
# and the library .o files. The only difference from the demo build command
# is that it doesn't link the SDL libraries.
bin/test_suite_%.exe bin\test_suite_%.exe: out/test_suite_%.obj out/test_util.obj $(STUDENT_OBJS)
$(CC) $^ $(CFLAGS) -link $(LINKEROPTS) -out:"$@"
# Empty recipes for cross-OS task compatibility.
bin/bounce bin\bounce: bin/bounce.exe ;
bin/gravity bin\gravity: bin/gravity.exe ;
bin/pacman bin\pacman: bin/pacman.exe ;
bin/nbodies bin\nbodies: bin/nbodies.exe;
bin/damping bin\damping: bin/damping.exe;
bin/spaceinvaders bin\spaceinvaders: bin/spaceinvaders.exe
bin/test_suite_% bin\test_suite_%: bin/test_suite_%.exe ;
# CMD commands to test and clean
bin/student_tests.exe: out/student_tests.obj out/test_util.obj $(STUDENT_OBJS)
$(CC) $(CFLAGS) $(LIB_MATH) $^ -o $@
# "$(subst /,\, $(TEST_BINS))" replaces "/" with "\" for
# Windows paths,
# "echo %%i.exe" prints the test suite,
# "cmd /c %%i.exe" runs the test,
# "|| exit /b" causes the session to exit if any of the tests fail,
# "echo." prints a newline.
test: $(TEST_BINS)
for %%i in ($(subst /,\, $(TEST_BINS))) \
do ((echo %%i) && ((cmd /c %%i) || exit /b) && (echo.))
# Explicitly iterate on files in out\* and bin\*, and
# delete if it's not .gitignore
clean:
for %%i in (out\* bin\*) \
do (if not "%%~xi" == ".gitignore" del %%~i)
# This special rule tells Make that "all", "clean", and "test" are rules
# that don't build a file.
.PHONY: all clean test
# Tells Make not to delete the .obj files after the executable is built
.PRECIOUS: out/%.obj
endif
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include "forces.h"
#include "polygon.h"
#include "scene.h"
#include "sdl_wrapper.h"
#define CIRCLE_POINTS 40
#define MAX ((vector_t) {.x = 80.0, .y = 80.0})
#define N_ROWS 11
#define ROW_SPACING 3.6
#define COL_SPACING 3.5
#define WALL_ANGLE atan2(ROW_SPACING, COL_SPACING / 2)
#define WALL_LENGTH hypot(MAX.x / 2, MAX.y)
#define PEG_RADIUS 0.5
#define BALL_RADIUS 1.0
#define DROP_INTERVAL 1.0 // s
#define PEG_ELASTICITY 0.3
#define BALL_ELASTICITY 0.7
#define WALL_WIDTH 1.0
#define DELTA_X 1.0
#define DROP_Y (MAX.y - 3.0)
#define START_VELOCITY ((vector_t) {.x = 0.0, .y = -8.0})
#define BALL_MASS 2.0
#define BALL_COLOR ((rgb_color_t) {1, 0, 0})
#define PEG_COLOR ((rgb_color_t) {0, 1, 0})
#define WALL_COLOR ((rgb_color_t) {0, 0, 1})
#define G 6.67E-11 // N m^2 / kg^2
#define M 6E24 // kg
#define g 9.8 // m / s^2
#define R (sqrt(G * M / g)) // m
typedef enum {
BALL,
FROZEN,
WALL, // or peg
GRAVITY
} body_type_t;
body_type_t *make_type_info(body_type_t type) {
body_type_t *info = malloc(sizeof(*info));
*info = type;
return info;
}
body_type_t get_type(body_t *body) {
return *(body_type_t *) body_get_info(body);
}
/** Generates a random number between 0 and 1 */
double rand_double(void) {
return (double) rand() / RAND_MAX;
}
/** Constructs a rectangle with the given dimensions centered at (0, 0) */
list_t *rect_init(double width, double height) {
vector_t half_width = {.x = width / 2, .y = 0.0},
half_height = {.x = 0.0, .y = height / 2};
list_t *rect = list_init(4, free);
vector_t *v = malloc(sizeof(*v));
*v = vec_add(half_width, half_height);
list_add(rect, v);
v = malloc(sizeof(*v));
*v = vec_subtract(half_height, half_width);
list_add(rect, v);
v = malloc(sizeof(*v));
*v = vec_negate(*(vector_t *) list_get(rect, 0));
list_add(rect, v);
v = malloc(sizeof(*v));
*v = vec_subtract(half_width, half_height);
list_add(rect, v);
return rect;
}
/** Constructs a circles with the given radius centered at (0, 0) */
list_t *circle_init(double radius) {
list_t *circle = list_init(CIRCLE_POINTS, free);
double arc_angle = 2 * M_PI / CIRCLE_POINTS;
vector_t point = {.x = radius, .y = 0.0};
for (size_t i = 0; i < CIRCLE_POINTS; i++) {
vector_t *v = malloc(sizeof(*v));
*v = point;
list_add(circle, v);
point = vec_rotate(point, arc_angle);
}
return circle;
}
/** Computes the center of the peg in the given row and column */
vector_t get_peg_center(size_t row, size_t col) {
vector_t center = {
.x = MAX.x / 2 + (col - row * 0.5) * COL_SPACING,
.y = MAX.y - (row + 1) * ROW_SPACING
};
return center;
}
/** Creates an Earth-like mass to accelerate the balls */
void add_gravity_body(scene_t *scene) {
// Will be offscreen, so shape is irrelevant
list_t *gravity_ball = rect_init(1, 1);
body_t *body = body_init_with_info(
gravity_ball,
M,
WALL_COLOR,
make_type_info(GRAVITY),
free
);
// Move a distnace R below the scene
vector_t gravity_center = {.x = MAX.x / 2, .y = -R};
body_set_centroid(body, gravity_center);
scene_add_body(scene, body);
}
/** Creates a ball with the given starting position and velocity */
body_t *get_ball(vector_t center, vector_t velocity) {
list_t *shape = circle_init(BALL_RADIUS);
body_t *ball = body_init_with_info(
shape,
BALL_MASS,
BALL_COLOR,
make_type_info(BALL),
free
);
body_set_centroid(ball, center);
body_set_velocity(ball, velocity);
return ball;
}
/** Collision handler to freeze a ball when it collides with a frozen body */
void freeze(body_t *ball, body_t *target, vector_t axis, void *aux) {
// Skip body if it was already frozen
if (body_is_removed(ball)) return;
// Replace the ball with a frozen version
body_remove(ball);
body_t *frozen = get_ball(body_get_centroid(ball), VEC_ZERO);
*((body_type_t *) body_get_info(frozen)) = FROZEN;
scene_t *scene = aux;
scene_add_body(scene, frozen);
// Make other falling bodies freeze when they collide with this body
size_t body_count = scene_bodies(scene);
for (size_t i = 0; i < body_count; i++) {
body_t *body = scene_get_body(scene, i);
if (get_type(body) == BALL) {
create_collision(scene, body, frozen, freeze, scene, NULL);
}
}
}
/** Adds a ball to the scene */
void add_ball(scene_t *scene) {
// Add the ball to the scene.
vector_t ball_center = {
.x = MAX.x / 2 + (rand_double() - 0.5) * DELTA_X,
.y = DROP_Y
};
body_t *ball = get_ball(ball_center, START_VELOCITY);
size_t body_count = scene_bodies(scene);
scene_add_body(scene, ball);
// Add force creators with other bodies
for (size_t i = 0; i < body_count; i++) {
body_t *body = scene_get_body(scene, i);
switch (get_type(body)) {
case BALL:
// Bounce off other balls
create_physics_collision(scene, BALL_ELASTICITY, ball, body);
break;
case WALL:
// Bounce off walls and pegs
create_physics_collision(scene, PEG_ELASTICITY, ball, body);
break;
case FROZEN:
// Freeze when hitting the ground or frozen balls
create_collision(scene, ball, body, freeze, scene, NULL);
break;
case GRAVITY:
// Simulate earth's gravity acting on the ball
create_newtonian_gravity(scene, G, body, ball);
}
}
}
/** Adds the pegs to the scene */
void add_pegs(scene_t *scene) {
// Add N_ROWS and N_COLS of pegs.
for (size_t i = 1; i <= N_ROWS; i++) {
for (size_t j = 0; j <= i; j++) {
list_t *polygon = circle_init(PEG_RADIUS);
body_t *body = body_init_with_info(
polygon,
INFINITY,
PEG_COLOR,
make_type_info(WALL),
free
);
body_set_centroid(body, get_peg_center(i, j));
scene_add_body(scene, body);
}
}
}
/** Adds the walls to the scene */
void add_walls(scene_t *scene) {
// Add walls
list_t *rect = rect_init(WALL_LENGTH, WALL_WIDTH);
polygon_translate(rect, (vector_t) {.x = WALL_LENGTH / 2, .y = 0.0});
polygon_rotate(rect, WALL_ANGLE, VEC_ZERO);
body_t *body = body_init_with_info(
rect,
INFINITY,
WALL_COLOR,
make_type_info(WALL),
free
);
scene_add_body(scene, body);
rect = rect_init(WALL_LENGTH, WALL_WIDTH);
polygon_translate(rect, (vector_t) {.x = MAX.x - WALL_LENGTH / 2, .y = 0.0});
polygon_rotate(rect, -WALL_ANGLE, (vector_t) {.x = MAX.x, .y = 0.0});
body = body_init_with_info(rect, INFINITY, WALL_COLOR, make_type_info(WALL), free);
scene_add_body(scene, body);
// Ground is special; it freezes balls when they touch it
rect = rect_init(MAX.x, WALL_WIDTH);
body = body_init_with_info(rect, INFINITY, WALL_COLOR, make_type_info(FROZEN), free);
body_set_centroid(body, (vector_t) {.x = MAX.x / 2, .y = WALL_WIDTH / 2});
scene_add_body(scene, body);
}
int main(void) {
// Initialize the random number generator
srand(time(NULL));
// Initialize scene
sdl_init(VEC_ZERO, MAX);
scene_t *scene = scene_init();
// Add elements to the scene
add_gravity_body(scene);
add_pegs(scene);
add_walls(scene);
// Repeatedly render scene
double time_since_drop = INFINITY;
while (!sdl_is_done()) {
double dt = time_since_last_tick();
// Add a new ball every DROP_INTERVAL seconds
time_since_drop += dt;
if (time_since_drop > DROP_INTERVAL) {
add_ball(scene);
time_since_drop = 0.0;
}
scene_tick(scene, dt);
sdl_render_scene(scene);
}
// Clean up scene
scene_free(scene);
}
.DS_Store
#ifndef __COLLISION_H__
#define __COLLISION_H__
#include <stdbool.h>
#include "list.h"
#include "vector.h"
/**
* Represents the status of a collision between two shapes.
* The shapes are either not colliding, or they are colliding along some axis.
*/
typedef struct {
/** Whether the two shapes are colliding */
bool collided;
/**
* If the shapes are colliding, the axis they are colliding on.
* This is a unit vector pointing from the first shape towards the second.
* Normal impulses are applied along this axis.
* If collided is false, this value is undefined.
*/
vector_t axis;
} collision_info_t;
/**
* Computes the status of the collision between two convex polygons.
* The shapes are given as lists of vertices in counterclockwise order.
* There is an edge between each pair of consecutive vertices,
* and one between the first vertex and the last vertex.
*
* @param shape1 the first shape
* @param shape2 the second shape
* @return whether the shapes are colliding, and if so, the collision axis.
* The axis should be a unit vector pointing from shape1 towards shape2.
*/
collision_info_t find_collision(list_t *shape1, list_t *shape2);
#endif // #ifndef __COLLISION_H__
#ifndef __FORCES_H__
#define __FORCES_H__
#include "scene.h"
/**
* A function called when a collision occurs.
* @param body1 the first body passed to create_collision()
* @param body2 the second body passed to create_collision()
* @param axis a unit vector pointing from body1 towards body2
* that defines the direction the two bodies are colliding in
* @param aux the auxiliary value passed to create_collision()
*/
typedef void (*collision_handler_t)
(body_t *body1, body_t *body2, vector_t axis, void *aux);
/**
* Adds a force creator to a scene that applies gravity between two bodies.
* The force creator will be called each tick
* to compute the Newtonian gravitational force between the bodies.
* See https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation#Vector_form.
* The force should not be applied when the bodies are very close,
* because its magnitude blows up as the distance between the bodies goes to 0.
*
* @param scene the scene containing the bodies
* @param G the gravitational proportionality constant
* @param body1 the first body
* @param body2 the second body
*/
void create_newtonian_gravity(scene_t *scene, double G, body_t *body1, body_t *body2);
/**
* Adds a force creator to a scene that acts like a spring between two bodies.
* The force creator will be called each tick
* to compute the Hooke's-Law spring force between the bodies.
* See https://en.wikipedia.org/wiki/Hooke%27s_law.
*
* @param scene the scene containing the bodies
* @param k the Hooke's constant for the spring
* @param body1 the first body
* @param body2 the second body
*/
void create_spring(scene_t *scene, double k, body_t *body1, body_t *body2);
/**
* Adds a force creator to a scene that applies a drag force on a body.
* The force creator will be called each tick
* to compute the drag force on the body proportional to its velocity.
* The force points opposite the body's velocity.
*
* @param scene the scene containing the bodies
* @param gamma the proportionality constant between force and velocity
* (higher gamma means more drag)
* @param body the body to slow down
*/
void create_drag(scene_t *scene, double gamma, body_t *body);
/**
* Adds a force creator to a scene that calls a given collision handler
* function each time two bodies collide.
* This generalizes create_destructive_collision() from last week,
* allowing different things to happen on a collision.
* The handler is passed the bodies, the collision axis, and an auxiliary value.
* It should only be called once while the bodies are still colliding.
*
* @param scene the scene containing the bodies
* @param body1 the first body
* @param body2 the second body
* @param handler a function to call whenever the bodies collide
* @param aux an auxiliary value to pass to the handler
* @param freer if non-NULL, a function to call in order to free aux
*/
void create_collision(
scene_t *scene,
body_t *body1,
body_t *body2,
collision_handler_t handler,
void *aux,
free_func_t freer
);
/**
* Adds a force creator to a scene that destroys two bodies when they collide.
* The bodies should be destroyed by calling body_remove().
* This should be represented as an on-collision callback
* registered with create_collision().
*
* @param scene the scene containing the bodies
* @param body1 the first body
* @param body2 the second body
*/
void create_destructive_collision(scene_t *scene, body_t *body1, body_t *body2);
/**
* Adds a force creator to a scene that applies impulses
* to resolve collisions between two bodies in the scene.
* This should be represented as an on-collision callback
* registered with create_collision().
*
* You may remember from project01 that you should avoid applying impulses
* multiple times while the bodies are still colliding.
* You should also have a special case that allows either body1 or body2
* to have mass INFINITY, as this is useful for simulating walls.
*
* @param scene the scene containing the bodies
* @param elasticity the "coefficient of restitution" of the collision;
* 0 is a perfectly inelastic collision and 1 is a perfectly elastic collision
* @param body1 the first body
* @param body2 the second body
*/
void create_physics_collision(
scene_t *scene,
double elasticity,
body_t *body1,
body_t *body2
);
#endif // #ifndef __FORCES_H__
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment