Commit 3998b451 authored by Adam H. Abbas's avatar Adam H. Abbas
Browse files

Initial Commit

parents
No related merge requests found
Showing with 1547 additions and 0 deletions
+1547 -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"
],
}
{
"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
# 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
.DS_Store
#ifndef __BODY_H__
#define __BODY_H__
#include <stdbool.h>
#include "color.h"
#include "list.h"
#include "vector.h"
/**
* A rigid body constrained to the plane.
* Implemented as a polygon with uniform density.
* Bodies can accumulate forces and impulses during each tick.
* Angular physics (i.e. torques) are not currently implemented.
*/
typedef struct body body_t;
/**
* Initializes a body without any info.
* Acts like body_init_with_info() where info and info_freer are NULL.
*/
body_t *body_init(list_t *shape, double mass, rgb_color_t color);
/**
* Allocates memory for a body with the given parameters.
* The body is initially at rest.
* Asserts that the mass is positive and that the required memory is allocated.
*
* @param shape a list of vectors describing the initial shape of the body
* @param mass the mass of the body (if INFINITY, stops the body from moving)
* @param color the color of the body, used to draw it on the screen
* @param info additional information to associate with the body,
* e.g. its type if the scene has multiple types of bodies
* @param info_freer if non-NULL, a function call on the info to free it
* @return a pointer to the newly allocated body
*/
body_t *body_init_with_info(
list_t *shape,
double mass,
rgb_color_t color,
void *info,
free_func_t info_freer
);
/**
* Releases the memory allocated for a body.
*
* @param body a pointer to a body returned from body_init()
*/
void body_free(body_t *body);
/**
* Gets the current shape of a body.
* Returns a newly allocated vector list, which must be list_free()d.
*
* @param body a pointer to a body returned from body_init()
* @return the polygon describing the body's current position
*/
list_t *body_get_shape(body_t *body);
/**
* Gets the current center of mass of a body.
* While this could be calculated with polygon_centroid(), that becomes too slow
* when this function is called thousands of times every tick.
* Instead, the body should store its current centroid.
*
* @param body a pointer to a body returned from body_init()
* @return the body's center of mass
*/
vector_t body_get_centroid(body_t *body);
/**
* Gets the current velocity of a body.
*
* @param body a pointer to a body returned from body_init()
* @return the body's velocity vector
*/
vector_t body_get_velocity(body_t *body);
/**
* Gets the mass of a body.
*
* @param body a pointer to a body returned from body_init()
* @return the mass passed to body_init(), which must be greater than 0
*/
double body_get_mass(body_t *body);
/**
* Gets the display color of a body.
*
* @param body a pointer to a body returned from body_init()
* @return the color passed to body_init(), as an (R, G, B) tuple
*/
rgb_color_t body_get_color(body_t *body);
/**
* Gets the information associated with a body.
*
* @param body a pointer to a body returned from body_init()
* @return the info passed to body_init()
*/
void *body_get_info(body_t *body);
/**
* Translates a body to a new position.
* The position is specified by the position of the body's center of mass.
*
* @param body a pointer to a body returned from body_init()
* @param x the body's new centroid
*/
void body_set_centroid(body_t *body, vector_t x);
/**
* Changes a body's velocity (the time-derivative of its position).
*
* @param body a pointer to a body returned from body_init()
* @param v the body's new velocity
*/
void body_set_velocity(body_t *body, vector_t v);
/**
* Changes a body's orientation in the plane.
* The body is rotated about its center of mass.
* Note that the angle is *absolute*, not relative to the current orientation.
*
* @param body a pointer to a body returned from body_init()
* @param angle the body's new angle in radians. Positive is counterclockwise.
*/
void body_set_rotation(body_t *body, double angle);
/**
* Applies a force to a body over the current tick.
* If multiple forces are applied in the same tick, they should be added.
* Should not change the body's position or velocity; see body_tick().
*
* @param body a pointer to a body returned from body_init()
* @param force the force vector to apply
*/
void body_add_force(body_t *body, vector_t force);
/**
* Applies an impulse to a body.
* An impulse causes an instantaneous change in velocity,
* which is useful for modeling collisions.
* If multiple impulses are applied in the same tick, they should be added.
* Should not change the body's position or velocity; see body_tick().
*
* @param body a pointer to a body returned from body_init()
* @param impulse the impulse vector to apply
*/
void body_add_impulse(body_t *body, vector_t impulse);
/**
* Updates the body after a given time interval has elapsed.
* Sets acceleration and velocity according to the forces and impulses
* applied to the body during the tick.
* The body should be translated at the *average* of the velocities before
* and after the tick.
* Resets the forces and impulses accumulated on the body.
*
* @param body the body to tick
* @param dt the number of seconds elapsed since the last tick
*/
void body_tick(body_t *body, double dt);
/**
* Marks a body for removal--future calls to body_is_removed() will return true.
* Does not free the body.
* If the body is already marked for removal, does nothing.
*
* @param body the body to mark for removal
*/
void body_remove(body_t *body);
/**
* Returns whether a body has been marked for removal.
* This function returns false until body_remove() is called on the body,
* and returns true afterwards.
*
* @param body the body to check
* @return whether body_remove() has been called on the body
*/
bool body_is_removed(body_t *body);
#endif // #ifndef __BODY_H__
#ifndef __COLLISION_H__
#define __COLLISION_H__
#include <stdbool.h>
#include "list.h"
/**
* Determines whether two convex polygons intersect.
* The polygons 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
*/
bool find_collision(list_t *shape1, list_t *shape2);
#endif // #ifndef __COLLISION_H__
#ifndef __FORCES_H__
#define __FORCES_H__
#include "scene.h"
/**
* 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 destroys two bodies when they collide.
* The bodies should be destroyed by calling body_remove().
*
* @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);
#endif // #ifndef __FORCES_H__
#ifndef __SCENE_H__
#define __SCENE_H__
#include "body.h"
#include "list.h"
/**
* A collection of bodies and force creators.
* The scene automatically resizes to store
* arbitrarily many bodies and force creators.
*/
typedef struct scene scene_t;
/**
* A function which adds some forces or impulses to bodies,
* e.g. from collisions, gravity, or spring forces.
* Takes in an auxiliary value that can store parameters or state.
*/
typedef void (*force_creator_t)(void *aux);
/**
* Allocates memory for an empty scene.
* Makes a reasonable guess of the number of bodies to allocate space for.
* Asserts that the required memory is successfully allocated.
*
* @return the new scene
*/
scene_t *scene_init(void);
/**
* Releases memory allocated for a given scene
* and all the bodies and force creators it contains.
*
* @param scene a pointer to a scene returned from scene_init()
*/
void scene_free(scene_t *scene);
/**
* Gets the number of bodies in a given scene.
*
* @param scene a pointer to a scene returned from scene_init()
* @return the number of bodies added with scene_add_body()
*/
size_t scene_bodies(scene_t *scene);
/**
* Gets the body at a given index in a scene.
* Asserts that the index is valid.
*
* @param scene a pointer to a scene returned from scene_init()
* @param index the index of the body in the scene (starting at 0)
* @return a pointer to the body at the given index
*/
body_t *scene_get_body(scene_t *scene, size_t index);
/**
* Adds a body to a scene.
*
* @param scene a pointer to a scene returned from scene_init()
* @param body a pointer to the body to add to the scene
*/
void scene_add_body(scene_t *scene, body_t *body);
/**
* @deprecated Use body_remove() instead
*
* Removes and frees the body at a given index from a scene.
* Asserts that the index is valid.
*
* @param scene a pointer to a scene returned from scene_init()
* @param index the index of the body in the scene (starting at 0)
*/
void scene_remove_body(scene_t *scene, size_t index);
/**
* @deprecated Use scene_add_bodies_force_creator() instead
* so the scene knows which bodies the force creator depends on
*/
void scene_add_force_creator(
scene_t *scene,
force_creator_t forcer,
void *aux,
free_func_t freer
);
/**
* Adds a force creator to a scene,
* to be invoked every time scene_tick() is called.
* The auxiliary value is passed to the force creator each time it is called.
* The force creator is registered with a list of bodies it applies to,
* so it can be removed when any one of the bodies is removed.
*
* @param scene a pointer to a scene returned from scene_init()
* @param forcer a force creator function
* @param aux an auxiliary value to pass to forcer when it is called
* @param bodies the list of bodies affected by the force creator.
* The force creator will be removed if any of these bodies are removed.
* This list does not own the bodies, so its freer should be NULL.
* @param freer if non-NULL, a function to call in order to free aux
*/
void scene_add_bodies_force_creator(
scene_t *scene,
force_creator_t forcer,
void *aux,
list_t *bodies,
free_func_t freer
);
/**
* Executes a tick of a given scene over a small time interval.
* This requires executing all the force creators
* and then ticking each body (see body_tick()).
* If any bodies are marked for removal, they should be removed from the scene
* and freed, along with any force creators acting on them.
*
* @param scene a pointer to a scene returned from scene_init()
* @param dt the time elapsed since the last tick, in seconds
*/
void scene_tick(scene_t *scene, double dt);
#endif // #ifndef __SCENE_H__
leak:X11
#include "body.h"
#include "test_util.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
void test_body_init() {
vector_t v[] = {{1, 1}, {2, 1}, {2, 2}, {1, 2}};
const size_t VERTICES = sizeof(v) / sizeof(*v);
list_t *shape = list_init(0, free);
for (size_t i = 0; i < VERTICES; i++) {
vector_t *list_v = malloc(sizeof(*list_v));
*list_v = v[i];
list_add(shape, list_v);
}
rgb_color_t color = {0, 0.5, 1};
body_t *body = body_init(shape, 3, color);
list_t *shape2 = body_get_shape(body);
assert(list_size(shape2) == VERTICES);
for (size_t i = 0; i < VERTICES; i++) {
assert(vec_isclose(*(vector_t *) list_get(shape2, i), v[i]));
}
list_free(shape2);
assert(vec_isclose(body_get_centroid(body), (vector_t) {1.5, 1.5}));
assert(vec_equal(body_get_velocity(body), VEC_ZERO));
assert(body_get_color(body).r == color.r);
assert(body_get_color(body).g == color.g);
assert(body_get_color(body).b == color.b);
assert(body_get_mass(body) == 3);
body_free(body);
}
void test_body_setters() {
list_t *shape = list_init(3, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, 0};
list_add(shape, v);
body_t *body = body_init(shape, 1, (rgb_color_t) {0, 0, 0});
body_set_velocity(body, (vector_t) {+5, -5});
assert(vec_equal(body_get_velocity(body), (vector_t) {+5, -5}));
assert(vec_isclose(body_get_centroid(body), (vector_t) {0, 1.0 / 3.0}));
body_set_centroid(body, (vector_t) {1, 2});
assert(vec_isclose(body_get_centroid(body), (vector_t) {1, 2}));
shape = body_get_shape(body);
assert(list_size(shape) == 3);
assert(vec_isclose(*(vector_t *) list_get(shape, 0), (vector_t) {2, 5.0 / 3.0}));
assert(vec_isclose(*(vector_t *) list_get(shape, 1), (vector_t) {1, 8.0 / 3.0}));
assert(vec_isclose(*(vector_t *) list_get(shape, 2), (vector_t) {0, 5.0 / 3.0}));
list_free(shape);
body_set_rotation(body, M_PI / 2);
assert(vec_isclose(body_get_centroid(body), (vector_t) {1, 2}));
shape = body_get_shape(body);
assert(list_size(shape) == 3);
assert(vec_isclose(*(vector_t *) list_get(shape, 0), (vector_t) {4.0 / 3.0, 3}));
assert(vec_isclose(*(vector_t *) list_get(shape, 1), (vector_t) {1.0 / 3.0, 2}));
assert(vec_isclose(*(vector_t *) list_get(shape, 2), (vector_t) {4.0 / 3.0, 1}));
list_free(shape);
body_set_centroid(body, (vector_t) {3, 4});
assert(vec_isclose(body_get_centroid(body), (vector_t) {3, 4}));
shape = body_get_shape(body);
assert(list_size(shape) == 3);
assert(vec_isclose(*(vector_t *) list_get(shape, 0), (vector_t) {10.0 / 3.0, 5}));
assert(vec_isclose(*(vector_t *) list_get(shape, 1), (vector_t) {7.0 / 3.0, 4}));
assert(vec_isclose(*(vector_t *) list_get(shape, 2), (vector_t) {10.0 / 3.0, 3}));
list_free(shape);
body_free(body);
}
void test_body_tick() {
const vector_t A = {1, 2};
const double DT = 1e-6;
const int STEPS = 1000000;
list_t *shape = list_init(4, free);
vector_t *v = malloc(sizeof(*v));
v->x = v->y = -1;
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
v->x = v->y = +1;
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, +1};
list_add(shape, v);
body_t *body = body_init(shape, 1, (rgb_color_t) {0, 0, 0});
// Apply constant acceleration and ensure position is (a / 2) * t ** 2
for (int i = 0; i < STEPS; i++) {
double t = i * DT;
assert(vec_isclose(body_get_centroid(body), vec_multiply(t * t / 2, A)));
body_set_velocity(body, vec_multiply(t + DT / 2, A));
body_tick(body, DT);
}
double t = STEPS * DT;
vector_t new_x = vec_multiply(t * t / 2, A);
shape = body_get_shape(body);
assert(vec_isclose(*(vector_t *) list_get(shape, 0), vec_add((vector_t) {-1, -1}, new_x)));
assert(vec_isclose(*(vector_t *) list_get(shape, 1), vec_add((vector_t) {+1, -1}, new_x)));
assert(vec_isclose(*(vector_t *) list_get(shape, 2), vec_add((vector_t) {+1, +1}, new_x)));
assert(vec_isclose(*(vector_t *) list_get(shape, 3), vec_add((vector_t) {-1, +1}, new_x)));
list_free(shape);
body_free(body);
}
void test_infinite_mass() {
list_t *shape = list_init(10, free);
vector_t *v = malloc(sizeof(*v));
*v = VEC_ZERO;
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {0, +1};
list_add(shape, v);
body_t *body = body_init(shape, INFINITY, (rgb_color_t) {0, 0, 0});
body_set_velocity(body, (vector_t) {2, 3});
assert(body_get_mass(body) == INFINITY);
body_add_force(body, (vector_t) {1, 1});
body_tick(body, 1.0);
assert(vec_equal(body_get_velocity(body), (vector_t) {2, 3}));
assert(vec_isclose(body_get_centroid(body), (vector_t) {2.5, 3.5}));
body_free(body);
}
void test_forces() {
const double MASS = 10;
const double DT = 0.1;
list_t *shape = list_init(3, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, 0};
list_add(shape, v);
body_t *body = body_init(shape, MASS, (rgb_color_t) {0, 0, 0});
body_set_centroid(body, VEC_ZERO);
vector_t old_velocity = {1, -2};
body_set_velocity(body, old_velocity);
body_add_force(body, (vector_t) {MASS * 3, MASS * 4});
body_add_impulse(body, (vector_t) {MASS * 10, MASS * 5});
body_add_force(body, (vector_t) {MASS * 3, MASS * 4});
body_tick(body, DT);
vector_t new_velocity =
vec_add(old_velocity, (vector_t) {10 + 6 * DT, 5 + 8 * DT});
assert(vec_isclose(body_get_velocity(body), new_velocity));
vector_t new_centroid =
vec_multiply(DT / 2, vec_add(old_velocity, new_velocity));
assert(vec_isclose(body_get_centroid(body), new_centroid));
body_tick(body, DT);
assert(vec_isclose(
body_get_centroid(body),
vec_add(new_centroid, vec_multiply(DT, new_velocity))
));
body_free(body);
}
void test_body_remove() {
list_t *shape = list_init(3, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, 0};
list_add(shape, v);
body_t *body = body_init(shape, 1, (rgb_color_t) {0, 0, 0});
assert(!body_is_removed(body));
body_remove(body);
assert(body_is_removed(body));
body_remove(body);
assert(body_is_removed(body));
body_free(body);
}
void test_body_info() {
list_t *shape = list_init(3, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, 0};
list_add(shape, v);
int *info = malloc(sizeof(*info));
*info = 123;
body_t *body = body_init_with_info(shape, 1, (rgb_color_t) {0, 0, 0}, info, NULL);
assert(*(int *) body_get_info(body) == 123);
body_free(body);
free(info);
}
void test_body_info_freer() {
list_t *shape = list_init(3, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, 0};
list_add(shape, v);
list_t *info = list_init(3, free);
int *info_elem = malloc(sizeof(*info_elem));
*info_elem = 10;
list_add(info, info_elem);
info_elem = malloc(sizeof(*info_elem));
*info_elem = 20;
list_add(info, info_elem);
info_elem = malloc(sizeof(*info_elem));
*info_elem = 30;
list_add(info, info_elem);
body_t *body = body_init_with_info(
shape,
1,
(rgb_color_t) {0, 0, 0},
info,
(free_func_t) list_free
);
assert(*(int *) list_get(body_get_info(body), 0) == 10);
assert(*(int *) list_get(body_get_info(body), 1) == 20);
assert(*(int *) list_get(body_get_info(body), 2) == 30);
body_free(body);
}
int main(int argc, char *argv[]) {
// Run all tests if there are no command-line arguments
bool all_tests = argc == 1;
// Read test name from file
char testname[100];
if (!all_tests) {
read_testname(argv[1], testname, sizeof(testname));
}
DO_TEST(test_body_init)
DO_TEST(test_body_setters)
DO_TEST(test_body_tick)
DO_TEST(test_infinite_mass)
DO_TEST(test_forces)
DO_TEST(test_body_remove)
DO_TEST(test_body_info)
DO_TEST(test_body_info_freer)
puts("body_test PASS");
}
#include "forces.h"
#include "test_util.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
list_t *make_shape() {
list_t *shape = list_init(4, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {-1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, +1};
list_add(shape, v);
return shape;
}
// Tests that a mass on a spring oscillates like A cos(sqrt(K / M) * t)
void test_spring_sinusoid() {
const double M = 10;
const double K = 2;
const double A = 3;
const double DT = 1e-6;
const int STEPS = 1000000;
scene_t *scene = scene_init();
body_t *mass = body_init(make_shape(), M, (rgb_color_t) {0, 0, 0});
body_set_centroid(mass, (vector_t) {A, 0});
scene_add_body(scene, mass);
body_t *anchor = body_init(make_shape(), INFINITY, (rgb_color_t) {0, 0, 0});
scene_add_body(scene, anchor);
create_spring(scene, K, mass, anchor);
for (int i = 0; i < STEPS; i++) {
assert(vec_isclose(
body_get_centroid(mass),
(vector_t) {A * cos(sqrt(K / M) * i * DT), 0}
));
assert(vec_equal(body_get_centroid(anchor), VEC_ZERO));
scene_tick(scene, DT);
}
scene_free(scene);
}
double gravity_potential(double G, body_t *body1, body_t *body2) {
vector_t r = vec_subtract(body_get_centroid(body2), body_get_centroid(body1));
return -G * body_get_mass(body1) * body_get_mass(body2) / sqrt(vec_dot(r, r));
}
double kinetic_energy(body_t *body) {
vector_t v = body_get_velocity(body);
return body_get_mass(body) * vec_dot(v, v) / 2;
}
// Tests that a conservative force (gravity) conserves K + U
void test_energy_conservation() {
const double M1 = 4.5, M2 = 7.3;
const double G = 1e3;
const double DT = 1e-6;
const int STEPS = 1000000;
scene_t *scene = scene_init();
body_t *mass1 = body_init(make_shape(), M1, (rgb_color_t) {0, 0, 0});
scene_add_body(scene, mass1);
body_t *mass2 = body_init(make_shape(), M2, (rgb_color_t) {0, 0, 0});
body_set_centroid(mass2, (vector_t) {10, 20});
scene_add_body(scene, mass2);
create_newtonian_gravity(scene, G, mass1, mass2);
double initial_energy = gravity_potential(G, mass1, mass2);
for (int i = 0; i < STEPS; i++) {
assert(body_get_centroid(mass1).x < body_get_centroid(mass2).x);
double energy = gravity_potential(G, mass1, mass2) +
kinetic_energy(mass1) + kinetic_energy(mass2);
assert(within(1e-4, energy / initial_energy, 1));
scene_tick(scene, DT);
}
scene_free(scene);
}
body_t *make_triangle_body() {
list_t *shape = list_init(3, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-0.5, +sqrt(3) / 2};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-0.5, -sqrt(3) / 2};
list_add(shape, v);
return body_init(shape, 1, (rgb_color_t) {0, 0, 0});
}
// Tests that destructive collisions remove bodies from the scene
void test_collisions() {
const double DT = 0.1;
const double V = 1.23;
const double SEPARATION_AT_COLLISION = 1.5;
const int TICKS_TO_COLLISION = 10;
scene_t *scene = scene_init();
body_t *body1 = make_triangle_body();
vector_t initial_separation =
{SEPARATION_AT_COLLISION + V * DT * (TICKS_TO_COLLISION - 0.5), 0};
body_set_centroid(body1, vec_negate(initial_separation));
body_set_velocity(body1, (vector_t) {+V, 0});
scene_add_body(scene, body1);
body_t *body2 = make_triangle_body();
scene_add_body(scene, body2);
body_t *body3 = make_triangle_body();
body_set_velocity(body3, (vector_t) {-V, 0});
body_set_centroid(body3, initial_separation);
scene_add_body(scene, body3);
create_destructive_collision(scene, body1, body2);
create_destructive_collision(scene, body1, body3);
create_destructive_collision(scene, body2, body3);
for (int i = 0; i < TICKS_TO_COLLISION * 2; i++) {
scene_tick(scene, DT);
// Before collision, there should be 2 bodies; after, there should be 0
if (i < TICKS_TO_COLLISION) {
assert(scene_bodies(scene) == 3);
}
else {
assert(scene_bodies(scene) == 0);
}
}
scene_free(scene);
}
// Tests that force creators properly register their list of affected bodies.
// If they don't, asan will report a heap-use-after-free failure.
void test_forces_removed() {
scene_t *scene = scene_init();
for (int i = 0; i < 10; i++) {
body_t *body = body_init(make_shape(), 1, (rgb_color_t) {0, 0, 0});
body_set_centroid(body, (vector_t) {i, i});
scene_add_body(scene, body);
for (int j = 0; j < i; j++) {
create_newtonian_gravity(scene, 1, body, scene_get_body(scene, j));
create_spring(scene, 1, body, scene_get_body(scene, j));
}
create_drag(scene, 1, body);
}
while (scene_bodies(scene) > 0) {
scene_remove_body(scene, 0);
scene_tick(scene, 1);
}
scene_free(scene);
}
int main(int argc, char *argv[]) {
// Run all tests if there are no command-line arguments
bool all_tests = argc == 1;
// Read test name from file
char testname[100];
if (!all_tests) {
read_testname(argv[1], testname, sizeof(testname));
}
DO_TEST(test_spring_sinusoid)
DO_TEST(test_energy_conservation)
DO_TEST(test_collisions)
DO_TEST(test_forces_removed)
puts("forces_test PASS");
}
#include "scene.h"
#include "test_util.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
void scene_get_first(void *scene) {
scene_get_body(scene, 0);
}
void scene_remove_first(void *scene) {
scene_remove_body(scene, 0);
}
void test_empty_scene() {
scene_t *scene = scene_init();
assert(scene_bodies(scene) == 0);
for (int i = 0; i < 10; i++) scene_tick(scene, 1);
assert(test_assert_fail(scene_get_first, scene));
assert(test_assert_fail(scene_remove_first, scene));
scene_free(scene);
}
list_t *make_shape() {
list_t *shape = list_init(4, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {-1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, +1};
list_add(shape, v);
return shape;
}
void test_scene() {
// Build a scene with 3 bodies
scene_t *scene = scene_init();
assert(scene_bodies(scene) == 0);
body_t *body1 = body_init(make_shape(), 1, (rgb_color_t) {1, 1, 1});
scene_add_body(scene, body1);
assert(scene_bodies(scene) == 1);
assert(scene_get_body(scene, 0) == body1);
body_t *body2 = body_init(make_shape(), 2, (rgb_color_t) {1, 1, 1});
scene_add_body(scene, body2);
assert(scene_bodies(scene) == 2);
assert(scene_get_body(scene, 0) == body1);
assert(scene_get_body(scene, 1) == body2);
body_t *body3 = body_init(make_shape(), 3, (rgb_color_t) {1, 1, 1});
scene_add_body(scene, body3);
assert(scene_bodies(scene) == 3);
assert(scene_get_body(scene, 0) == body1);
assert(scene_get_body(scene, 1) == body2);
assert(scene_get_body(scene, 2) == body3);
// Set the bodies' positions with no velocity and ensure they match
body_set_centroid(body1, (vector_t) {1, 1});
body_set_centroid(body2, (vector_t) {2, 2});
body_set_centroid(body3, (vector_t) {3, 3});
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (vector_t) {1, 1}));
assert(vec_isclose(body_get_centroid(body2), (vector_t) {2, 2}));
assert(vec_isclose(body_get_centroid(body3), (vector_t) {3, 3}));
body_set_velocity(body1, (vector_t) {+1, 0});
body_set_velocity(body2, (vector_t) {-1, 0});
body_set_velocity(body3, (vector_t) {0, +1});
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (vector_t) {2, 1}));
assert(vec_isclose(body_get_centroid(body2), (vector_t) {1, 2}));
assert(vec_isclose(body_get_centroid(body3), (vector_t) {3, 4}));
// Try removing the second body
scene_remove_body(scene, 1);
assert(scene_bodies(scene) == 3); // removal is deferred until the next tick
scene_tick(scene, 0);
assert(scene_bodies(scene) == 2);
assert(scene_get_body(scene, 0) == body1);
assert(scene_get_body(scene, 1) == body3);
// Tick the remaining bodies
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (vector_t) {3, 1}));
assert(vec_isclose(body_get_centroid(body3), (vector_t) {3, 5}));
scene_free(scene);
}
// A force creator that moves a body in uniform circular motion about the origin
void centripetal_force(void *aux) {
body_t *body = aux;
vector_t v = body_get_velocity(body);
vector_t r = body_get_centroid(body);
assert(isclose(vec_dot(v, r), 0));
vector_t force =
vec_multiply(-body_get_mass(body) * vec_dot(v, v) / vec_dot(r, r), r);
body_add_force(body, force);
}
void test_force_creator() {
const double OMEGA = 3;
const double R = 2;
const double DT = 1e-6;
const int STEPS = 1000000;
scene_t *scene = scene_init();
body_t *body = body_init(make_shape(), 123, (rgb_color_t) {0, 0, 0});
vector_t radius = {R, 0};
body_set_centroid(body, radius);
body_set_velocity(body, (vector_t) {0, OMEGA * R});
scene_add_body(scene, body);
scene_add_force_creator(scene, centripetal_force, body, NULL);
for (int i = 0; i < STEPS; i++) {
vector_t expected_x = vec_rotate(radius, OMEGA * i * DT);
assert(vec_within(1e-4, body_get_centroid(body), expected_x));
scene_tick(scene, DT);
}
scene_free(scene);
}
typedef struct {
scene_t *scene;
double coefficient;
} force_aux_t;
// A force creator that applies constant downwards gravity to all bodies in a scene
void constant_gravity(void *aux) {
force_aux_t *gravity_aux = aux;
size_t body_count = scene_bodies(gravity_aux->scene);
for (size_t i = 0; i < body_count; i++) {
body_t *body = scene_get_body(gravity_aux->scene, i);
vector_t force = {0, -gravity_aux->coefficient * body_get_mass(body)};
body_add_force(body, force);
}
}
// A force creator that applies drag proportional to v ** 2 to all bodies in a scene
void air_drag(void *aux) {
force_aux_t *drag_aux = aux;
size_t body_count = scene_bodies(drag_aux->scene);
for (size_t i = 0; i < body_count; i++) {
body_t *body = scene_get_body(drag_aux->scene, i);
vector_t v = body_get_velocity(body);
vector_t force =
vec_multiply(-drag_aux->coefficient * sqrt(vec_dot(v, v)), v);
body_add_force(body, force);
}
}
void test_force_creator_aux() {
const double LIGHT_MASS = 10, HEAVY_MASS = 20;
const double GRAVITY = 9.8, DRAG = 3;
const double DT = 1e-3;
const int STEPS = 100000;
scene_t *scene = scene_init();
body_t *light = body_init(make_shape(), LIGHT_MASS, (rgb_color_t) {0, 0, 0});
scene_add_body(scene, light);
body_t *heavy = body_init(make_shape(), HEAVY_MASS, (rgb_color_t) {0, 0, 0});
scene_add_body(scene, heavy);
force_aux_t *gravity_aux = malloc(sizeof(*gravity_aux));
gravity_aux->scene = scene;
gravity_aux->coefficient = GRAVITY;
scene_add_force_creator(scene, constant_gravity, gravity_aux, free);
force_aux_t *drag_aux = malloc(sizeof(*drag_aux));
drag_aux->scene = scene;
drag_aux->coefficient = DRAG;
scene_add_force_creator(scene, air_drag, drag_aux, free);
for (int i = 0; i < STEPS; i++) scene_tick(scene, DT);
assert(vec_isclose(
body_get_velocity(light),
(vector_t) {0, -sqrt(GRAVITY * LIGHT_MASS / DRAG)
}));
assert(vec_isclose(
body_get_velocity(heavy),
(vector_t) {0, -sqrt(GRAVITY * HEAVY_MASS / DRAG)
}));
scene_free(scene);
}
/*
This test checks that a force creator is no longer called after
one of its bodies has been removed.
There are initially 3 bodies in the scene.
remove_body() removes the last remaining body each tick.
count_calls() depends on the first and second bodies,
so it should only be called during the first two ticks.
*/
void remove_body(void *aux) {
scene_t *scene = aux;
size_t body_count = scene_bodies(scene);
if (body_count > 0) {
body_remove(scene_get_body(scene, body_count - 1));
}
}
typedef struct {
int count;
scene_t *scene;
} count_aux_t;
void count_calls(void *aux) {
count_aux_t *count_aux = aux;
// Every time count_calls() is called, the body count should decrease by 1
assert(scene_bodies(count_aux->scene) == 3 - count_aux->count);
// Record that count_calls() was called an additional time
count_aux->count++;
}
void test_reaping() {
scene_t *scene = scene_init();
for (int i = 0; i < 3; i++) {
scene_add_body(scene, body_init(make_shape(), 1, (rgb_color_t) {0, 0, 0}));
}
scene_add_bodies_force_creator(scene, remove_body, scene, list_init(0, NULL), NULL);
count_aux_t *count_aux = malloc(sizeof(*count_aux));
count_aux->count = 0;
count_aux->scene = scene;
list_t *required_bodies = list_init(2, NULL);
list_add(required_bodies, scene_get_body(scene, 0));
list_add(required_bodies, scene_get_body(scene, 1));
scene_add_bodies_force_creator(scene, count_calls, count_aux, required_bodies, NULL);
while (scene_bodies(scene) > 0) {
scene_tick(scene, 1);
}
assert(count_aux->count == 2);
free(count_aux);
scene_free(scene);
}
int main(int argc, char *argv[]) {
// Run all tests if there are no command-line arguments
bool all_tests = argc == 1;
// Read test name from file
char testname[100];
if (!all_tests) {
read_testname(argv[1], testname, sizeof(testname));
}
DO_TEST(test_empty_scene)
DO_TEST(test_scene)
DO_TEST(test_force_creator)
DO_TEST(test_force_creator_aux)
DO_TEST(test_reaping)
puts("scene_test PASS");
}
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