Commit 1c7777b4 authored by Adam Blank's avatar Adam Blank
Browse files

Initial commit

parents
No related merge requests found
Showing with 1153 additions and 0 deletions
+1153 -0
# 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 -Wall -g -fno-omit-frame-pointer -fsanitize=address
# 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) -lSDL2 -lSDL2_gfx
# 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
STUDENT_LIBS = vector list \
shape color body scene \
forces
# 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)) bin/student_tests
# 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 vector.c was modified since the last build, only vector.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: tests/%.c # or "tests"
$(CC) -c $(CFLAGS) $^ -o $@
out/demo-%.o: demo/%.c # or "demo"; in this case, add "demo-" to the .o filename
$(CC) -c $(CFLAGS) $^ -o $@
# Builds the demos 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/%: out/demo-%.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 $@
# Builds your test suite executable from your test .o file and the library
# files. Once again we don't link SDL, so your test cannot use SDL either.
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
# "$$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 $$f; echo; done
# Removes all compiled files. "out/*" matches all files in the "out" directory
# and "bin/*" does the same for the "bin" directory.
# "rm" deletes the files; "-f" means "succeed even if no files were removed".
# Note that this target has no sources, which is perfectly valid.
clean:
rm -f out/* bin/*
# 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 out/demo-%.o
.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;
/**
* Initializes a body without any info.
* Acts like body_init_with_info() where info and info_freer are NULL.
*/
Body *body_init(List *shape, double mass, RGBColor 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, prevents 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 *body_init_with_info(
List *shape, double mass, RGBColor color, void *info, FreeFunc info_freer
);
/**
* Releases the memory allocated for a body.
*
* @param body a pointer to a body returned from body_init()
*/
void body_free(Body *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 *body_get_shape(Body *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 body_get_centroid(Body *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 body_get_velocity(Body *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 *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
*/
RGBColor body_get_color(Body *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 *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 *body, Vector 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 *body, Vector 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 *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 *body, Vector 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 *body, Vector 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 *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 *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 *body);
#endif // #ifndef __BODY_H__
#ifndef __COLLISION_H__
#define __COLLISION_H__
#include <stdbool.h>
#include "list.h"
#include "vector.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 *shape1, List *shape2);
#endif // #ifndef __COLLISION_H__
#ifndef __FORCES_H__
#define __FORCES_H__
#include "scene.h"
/**
* Adds a Newtonian gravitational force between two bodies in a scene.
* 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 *scene, double G, Body *body1, Body *body2);
/**
* Adds a Hooke's-Law spring force between two bodies in a scene.
* 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 *scene, double k, Body *body1, Body *body2);
/**
* Adds a drag force on a 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 *scene, double gamma, Body *body);
/**
* Adds a ForceCreator 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 *scene, Body *body1, Body *body2);
#endif // #ifndef __FORCES_H__
#ifndef __SCENE_H__
#define __SCENE_H__
#include <stdbool.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;
/**
* 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 (*ForceCreator)(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 *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 *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 *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 *scene_get_body(Scene *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 *scene, Body *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 *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 *scene, ForceCreator forcer, void *aux, FreeFunc 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 *scene, ForceCreator forcer, void *aux, List *bodies, FreeFunc 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 *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 v[] = {{1, 1}, {2, 1}, {2, 2}, {1, 2}};
const size_t VERTICES = sizeof(v) / sizeof(*v);
List *shape = list_init(0, free);
for (size_t i = 0; i < VERTICES; i++) {
Vector *list_v = malloc(sizeof(*list_v));
*list_v = v[i];
list_add(shape, list_v);
}
RGBColor color = {0, 0.5, 1};
Body *body = body_init(shape, 3, color);
List *shape2 = body_get_shape(body);
assert(list_size(shape2) == VERTICES);
for (size_t i = 0; i < VERTICES; i++) {
assert(vec_isclose(*(Vector *) list_get(shape2, i), v[i]));
}
list_free(shape2);
assert(vec_isclose(body_get_centroid(body), (Vector) {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 *shape = list_init(3, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-1, 0};
list_add(shape, v);
Body *body = body_init(shape, 1, (RGBColor) {0, 0, 0});
body_set_velocity(body, (Vector) {+5, -5});
assert(vec_equal(body_get_velocity(body), (Vector) {+5, -5}));
assert(vec_isclose(body_get_centroid(body), (Vector) {0, 1.0 / 3.0}));
body_set_centroid(body, (Vector) {1, 2});
assert(vec_isclose(body_get_centroid(body), (Vector) {1, 2}));
shape = body_get_shape(body);
assert(list_size(shape) == 3);
assert(vec_isclose(*(Vector *) list_get(shape, 0), (Vector) {2, 5.0 / 3.0}));
assert(vec_isclose(*(Vector *) list_get(shape, 1), (Vector) {1, 8.0 / 3.0}));
assert(vec_isclose(*(Vector *) list_get(shape, 2), (Vector) {0, 5.0 / 3.0}));
list_free(shape);
body_set_rotation(body, M_PI / 2);
assert(vec_isclose(body_get_centroid(body), (Vector) {1, 2}));
shape = body_get_shape(body);
assert(list_size(shape) == 3);
assert(vec_isclose(*(Vector *) list_get(shape, 0), (Vector) {4.0 / 3.0, 3}));
assert(vec_isclose(*(Vector *) list_get(shape, 1), (Vector) {1.0 / 3.0, 2}));
assert(vec_isclose(*(Vector *) list_get(shape, 2), (Vector) {4.0 / 3.0, 1}));
list_free(shape);
body_set_centroid(body, (Vector) {3, 4});
assert(vec_isclose(body_get_centroid(body), (Vector) {3, 4}));
shape = body_get_shape(body);
assert(list_size(shape) == 3);
assert(vec_isclose(*(Vector *) list_get(shape, 0), (Vector) {10.0 / 3.0, 5}));
assert(vec_isclose(*(Vector *) list_get(shape, 1), (Vector) {7.0 / 3.0, 4}));
assert(vec_isclose(*(Vector *) list_get(shape, 2), (Vector) {10.0 / 3.0, 3}));
list_free(shape);
body_free(body);
}
void test_body_tick() {
const Vector A = {1, 2};
const double DT = 1e-6;
const int STEPS = 1000000;
List *shape = list_init(4, free);
Vector *v = malloc(sizeof(*v));
v->x = v->y = -1;
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+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) {-1, +1};
list_add(shape, v);
Body *body = body_init(shape, 1, (RGBColor) {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 new_x = vec_multiply(t * t / 2, A);
shape = body_get_shape(body);
assert(vec_isclose(*(Vector *) list_get(shape, 0), vec_add((Vector) {-1, -1}, new_x)));
assert(vec_isclose(*(Vector *) list_get(shape, 1), vec_add((Vector) {+1, -1}, new_x)));
assert(vec_isclose(*(Vector *) list_get(shape, 2), vec_add((Vector) {+1, +1}, new_x)));
assert(vec_isclose(*(Vector *) list_get(shape, 3), vec_add((Vector) {-1, +1}, new_x)));
list_free(shape);
body_free(body);
}
void test_infinite_mass() {
List *shape = list_init(10, free);
Vector *v = malloc(sizeof(*v));
*v = VEC_ZERO;
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {0, +1};
list_add(shape, v);
Body *body = body_init(shape, INFINITY, (RGBColor) {0, 0, 0});
body_set_velocity(body, (Vector) {2, 3});
assert(body_get_mass(body) == INFINITY);
body_add_force(body, (Vector) {1, 1});
body_tick(body, 1.0);
assert(vec_equal(body_get_velocity(body), (Vector) {2, 3}));
assert(vec_isclose(body_get_centroid(body), (Vector) {2.5, 3.5}));
body_free(body);
}
void test_forces() {
const double MASS = 10;
const double DT = 0.1;
List *shape = list_init(3, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-1, 0};
list_add(shape, v);
Body *body = body_init(shape, MASS, (RGBColor) {0, 0, 0});
body_set_centroid(body, VEC_ZERO);
Vector old_velocity = {1, -2};
body_set_velocity(body, old_velocity);
body_add_force(body, (Vector) {MASS * 3, MASS * 4});
body_add_impulse(body, (Vector) {MASS * 10, MASS * 5});
body_add_force(body, (Vector) {MASS * 3, MASS * 4});
body_tick(body, DT);
Vector new_velocity =
vec_add(old_velocity, (Vector) {10 + 6 * DT, 5 + 8 * DT});
assert(vec_isclose(body_get_velocity(body), new_velocity));
Vector new_centroid =
vec_multiply(DT / 2.0, 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 *shape = list_init(3, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-1, 0};
list_add(shape, v);
Body *body = body_init(shape, 1, (RGBColor) {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 *shape = list_init(3, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-1, 0};
list_add(shape, v);
int *info = malloc(sizeof(*info));
*info = 123;
Body *body = body_init_with_info(shape, 1, (RGBColor) {0, 0, 0}, info, NULL);
assert(*(int *) body_get_info(body) == 123);
body_free(body);
free(info);
}
void test_body_info_freer() {
List *shape = list_init(3, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {+1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {0, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-1, 0};
list_add(shape, v);
List *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 *body = body_init_with_info(
shape, 1, (RGBColor) {0, 0, 0}, info, (FreeFunc) 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");
return 0;
}
#include "forces.h"
#include "test_util.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
List *make_shape() {
List *shape = list_init(4, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {-1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-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 *scene = scene_init();
Body *mass = body_init(make_shape(), M, (RGBColor) {0, 0, 0});
body_set_centroid(mass, (Vector) {A, 0});
scene_add_body(scene, mass);
Body *anchor = body_init(make_shape(), INFINITY, (RGBColor) {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) {A * cos(sqrt(K / M) * i * DT), 0}
));
scene_tick(scene, DT);
}
scene_free(scene);
}
double gravity_potential(double G, Body *body1, Body *body2) {
Vector 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 *body) {
Vector 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 *scene = scene_init();
Body *mass1 = body_init(make_shape(), M1, (RGBColor) {0, 0, 0});
scene_add_body(scene, mass1);
Body *mass2 = body_init(make_shape(), M2, (RGBColor) {0, 0, 0});
body_set_centroid(mass2, (Vector) {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-5, energy / initial_energy, 1));
scene_tick(scene, DT);
}
scene_free(scene);
}
Body *make_triangle_body() {
List *shape = list_init(3, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {1, 0};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-0.5, +sqrt(3) / 2};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-0.5, -sqrt(3) / 2};
list_add(shape, v);
return body_init(shape, 1, (RGBColor) {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 *scene = scene_init();
Body *body1 = make_triangle_body();
Vector 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) {+V, 0});
scene_add_body(scene, body1);
Body *body2 = make_triangle_body();
scene_add_body(scene, body2);
Body *body3 = make_triangle_body();
body_set_velocity(body3, (Vector) {-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 *scene = scene_init();
for (int i = 0; i < 10; i++) {
Body *body = body_init(make_shape(), 1, (RGBColor) {0, 0, 0});
body_set_centroid(body, (Vector) {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");
return 0;
}
#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 *) scene, 0);
}
void scene_remove_first(void *scene) {
scene_remove_body((Scene *) scene, 0);
}
void test_empty_scene() {
Scene *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 *make_shape() {
List *shape = list_init(4, free);
Vector *v = malloc(sizeof(*v));
*v = (Vector) {-1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (Vector) {-1, +1};
list_add(shape, v);
return shape;
}
void test_scene() {
// Build a scene with 3 bodies
Scene *scene = scene_init();
assert(scene_bodies(scene) == 0);
Body *body1 = body_init(make_shape(), 1, (RGBColor) {1, 1, 1});
scene_add_body(scene, body1);
assert(scene_bodies(scene) == 1);
assert(scene_get_body(scene, 0) == body1);
Body *body2 = body_init(make_shape(), 2, (RGBColor) {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 *body3 = body_init(make_shape(), 3, (RGBColor) {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) {1, 1});
body_set_centroid(body2, (Vector) {2, 2});
body_set_centroid(body3, (Vector) {3, 3});
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (Vector) {1, 1}));
assert(vec_isclose(body_get_centroid(body2), (Vector) {2, 2}));
assert(vec_isclose(body_get_centroid(body3), (Vector) {3, 3}));
body_set_velocity(body1, (Vector) {+1, 0});
body_set_velocity(body2, (Vector) {-1, 0});
body_set_velocity(body3, (Vector) {0, +1});
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (Vector) {2, 1}));
assert(vec_isclose(body_get_centroid(body2), (Vector) {1, 2}));
assert(vec_isclose(body_get_centroid(body3), (Vector) {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) {3, 1}));
assert(vec_isclose(body_get_centroid(body3), (Vector) {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 *body = (Body *) aux;
Vector v = body_get_velocity(body);
Vector r = body_get_centroid(body);
assert(isclose(vec_dot(v, r), 0));
Vector 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 *scene = scene_init();
Body *body = body_init(make_shape(), 123, (RGBColor) {0, 0, 0});
body_set_centroid(body, (Vector) {R, 0});
body_set_velocity(body, (Vector) {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 expected_x = vec_rotate((Vector) {R, 0}, OMEGA * i * DT);
assert(vec_within(1e-4, body_get_centroid(body), expected_x));
scene_tick(scene, DT);
}
scene_free(scene);
}
typedef struct {
Scene *scene;
double coefficient;
} ForceAux;
// A force creator that applies constant downwards gravity to all bodies in a scene
void constant_gravity(void *aux) {
ForceAux *gravity_aux = (ForceAux *) aux;
size_t body_count = scene_bodies(gravity_aux->scene);
for (size_t i = 0; i < body_count; i++) {
Body *body = scene_get_body(gravity_aux->scene, i);
Vector 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) {
ForceAux *drag_aux = (ForceAux *) aux;
size_t body_count = scene_bodies(drag_aux->scene);
for (size_t i = 0; i < body_count; i++) {
Body *body = scene_get_body(drag_aux->scene, i);
Vector v = body_get_velocity(body);
Vector 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 *scene = scene_init();
Body *light = body_init(make_shape(), LIGHT_MASS, (RGBColor) {0, 0, 0});
scene_add_body(scene, light);
Body *heavy = body_init(make_shape(), HEAVY_MASS, (RGBColor) {0, 0, 0});
scene_add_body(scene, heavy);
ForceAux *gravity_aux = malloc(sizeof(*gravity_aux));
gravity_aux->scene = scene;
gravity_aux->coefficient = GRAVITY;
scene_add_force_creator(scene, constant_gravity, gravity_aux, free);
ForceAux *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) {0, -sqrt(GRAVITY * LIGHT_MASS / DRAG)
}));
assert(vec_isclose(
body_get_velocity(heavy),
(Vector) {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 *scene = (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 *scene;
} CountAux;
void count_calls(void *aux) {
CountAux *count_aux = (CountAux *) 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 *scene = scene_init();
for (int i = 0; i < 3; i++) {
scene_add_body(scene, body_init(make_shape(), 1, (RGBColor) {0, 0, 0}));
}
scene_add_bodies_force_creator(
scene, remove_body, scene, list_init(0, NULL), NULL
);
CountAux *count_aux = malloc(sizeof(*count_aux));
count_aux->count = 0;
count_aux->scene = scene;
List *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");
return 0;
}
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