188 lines
5.9 KiB
C
188 lines
5.9 KiB
C
#include "material.h"
|
|
#include "pattern.h"
|
|
#include "yamlloader.h"
|
|
#include <assert.h>
|
|
#include <logger.h>
|
|
#include <math.h>
|
|
#include <memory.h>
|
|
|
|
MATERIAL_Material *MATERIAL_new(void) {
|
|
MATERIAL_Material *m = malloc(sizeof(MATERIAL_Material));
|
|
TUPLES_init_color(&m->color, 1, 1, 1);
|
|
m->ambient = 0.1;
|
|
m->diffuse = 0.9;
|
|
m->specular = 0.9;
|
|
m->shininess = 200.0;
|
|
m->reflective = 0.0;
|
|
m->transparency = 0.0;
|
|
m->refractive_index = 1.0;
|
|
m->pattern = NULL;
|
|
m->shadow_calc = true;
|
|
return m;
|
|
}
|
|
void MATERIAL_delete(MATERIAL_Material *m) {
|
|
assert(m);
|
|
TUPLES_destroy(&m->color);
|
|
if (m->pattern) {
|
|
PATTERN_delete(m->pattern);
|
|
}
|
|
free(m);
|
|
}
|
|
|
|
bool MATERIAL_is_equal(const MATERIAL_Material *m1, const MATERIAL_Material *m2) {
|
|
return TUPLES_is_equal(&m1->color, &m2->color) && double_equal(m1->diffuse, m2->diffuse) && double_equal(m1->shininess, m2->shininess) &&
|
|
double_equal(m1->ambient, m2->ambient) && double_equal(m1->specular, m2->specular);
|
|
}
|
|
|
|
MATERIAL_Material *MATERIAL_new_copy(const MATERIAL_Material *src) {
|
|
assert(src);
|
|
MATERIAL_Material *dest = MATERIAL_new();
|
|
*dest = *src;
|
|
if (src->pattern) {
|
|
dest->pattern = PATTERN_new_copy(src->pattern);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
struct calculate_lighting_context {
|
|
TUPLES_Color sum;
|
|
double intensity;
|
|
const MATERIAL_Material *material;
|
|
const TUPLES_Point *position;
|
|
const TUPLES_Vector *normal_vector;
|
|
const TUPLES_Vector *eye_vector;
|
|
const TUPLES_Color *effective_color;
|
|
const TUPLES_Color *light_color;
|
|
};
|
|
|
|
static void calculate_lighting(TUPLES_Point *light_point, void *context) {
|
|
struct calculate_lighting_context *c = (struct calculate_lighting_context *)context;
|
|
|
|
TUPLES_Color diffuse;
|
|
TUPLES_Color specular;
|
|
|
|
TUPLES_Vector lightv;
|
|
TUPLES_subtract(&lightv, light_point, c->position);
|
|
TUPLES_normalize(&lightv);
|
|
|
|
double light_dot_normal = TUPLES_dot(&lightv, c->normal_vector);
|
|
if (light_dot_normal < 0) {
|
|
// negative means light is on other side of surface
|
|
TUPLES_init_color(&diffuse, 0, 0, 0);
|
|
TUPLES_init_color(&specular, 0, 0, 0);
|
|
} else {
|
|
TUPLES_multiply(&diffuse, c->effective_color, c->material->diffuse * light_dot_normal);
|
|
|
|
TUPLES_negate(&lightv);
|
|
TUPLES_Vector reflectv;
|
|
TUPLES_reflect(&reflectv, &lightv, c->normal_vector);
|
|
double reflect_dot_eye = TUPLES_dot(&reflectv, c->eye_vector);
|
|
|
|
if (reflect_dot_eye < 0 || double_equal(0.0, reflect_dot_eye)) {
|
|
// light is reflecting away
|
|
TUPLES_init_color(&specular, 0, 0, 0);
|
|
} else {
|
|
double factor = pow(reflect_dot_eye, c->material->shininess);
|
|
TUPLES_multiply(&specular, c->light_color, c->material->specular * factor);
|
|
}
|
|
}
|
|
|
|
TUPLES_add(&c->sum, &c->sum, &diffuse);
|
|
TUPLES_add(&c->sum, &c->sum, &specular);
|
|
}
|
|
|
|
void MATERIAL_lighting(TUPLES_Color *dest, const SHAPE_Shape *shape, const LIGHTS_Light *light, const TUPLES_Point *position, const TUPLES_Vector *eye_vector,
|
|
const TUPLES_Vector *normal_vector, double intensity) {
|
|
assert(dest);
|
|
assert(shape);
|
|
assert(light);
|
|
assert(position);
|
|
assert(eye_vector);
|
|
assert(normal_vector);
|
|
assert(shape->material);
|
|
|
|
TUPLES_Color color_or_pattern;
|
|
const MATERIAL_Material *material = shape->material;
|
|
if (material->pattern) {
|
|
PATTERN_color_at_shape(&color_or_pattern, material->pattern, shape, position);
|
|
} else {
|
|
TUPLES_copy(&color_or_pattern, &shape->material->color);
|
|
}
|
|
|
|
TUPLES_Color effective_color;
|
|
TUPLES_multiply_colors(&effective_color, &color_or_pattern, &light->intensity);
|
|
|
|
/* calculate ambient contribution */
|
|
TUPLES_multiply(dest, &effective_color, material->ambient);
|
|
|
|
struct calculate_lighting_context context = (struct calculate_lighting_context){
|
|
.intensity = intensity,
|
|
.material = material,
|
|
.position = position,
|
|
.normal_vector = normal_vector,
|
|
.eye_vector = eye_vector,
|
|
.effective_color = &effective_color,
|
|
.light_color = &light->intensity,
|
|
};
|
|
TUPLES_init_color(&context.sum, 0, 0, 0);
|
|
|
|
LIGHTS_iterate_points_on_light(light, &calculate_lighting, &context);
|
|
|
|
TUPLES_divide(&context.sum, &context.sum, light->samples);
|
|
TUPLES_multiply(&context.sum, &context.sum, intensity);
|
|
TUPLES_add(dest, dest, &context.sum);
|
|
}
|
|
|
|
void MATERIAL_set_pattern(MATERIAL_Material *material, const PATTERN_Pattern *pattern) {
|
|
assert(material);
|
|
assert(pattern);
|
|
if (material->pattern) {
|
|
PATTERN_delete(material->pattern);
|
|
}
|
|
material->pattern = PATTERN_new_copy(pattern);
|
|
}
|
|
|
|
bool MATERIAL_casts_shadow(const MATERIAL_Material *m) {
|
|
assert(m);
|
|
return m->shadow_calc;
|
|
}
|
|
|
|
void parse_material_map_entry(char *key, char *value, void *context) {
|
|
assert(key);
|
|
assert(value);
|
|
assert(context);
|
|
MATERIAL_Material *mat = (MATERIAL_Material *)context;
|
|
|
|
if (strcmp("color", key) == 0) {
|
|
YAMLLOADER_parse_color(value, &mat->color);
|
|
} else if (strcmp("ambient", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->ambient);
|
|
} else if (strcmp("diffuse", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->diffuse);
|
|
} else if (strcmp("specular", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->specular);
|
|
} else if (strcmp("shininess", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->shininess);
|
|
} else if (strcmp("reflective", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->reflective);
|
|
} else if (strcmp("transparency", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->transparency);
|
|
} else if (strcmp("refractive-index", key) == 0) {
|
|
YAMLLOADER_parse_double(value, &mat->refractive_index);
|
|
} else if (strcmp("shadow", key) == 0) {
|
|
YAMLLOADER_parse_bool(value, &mat->shadow_calc);
|
|
} else {
|
|
LOGGER_log(LOGGER_WARN, "Unrecognized map key while parsing material: %s", key);
|
|
}
|
|
}
|
|
|
|
MATERIAL_Material *MATERIAL_parse_material(char *data) {
|
|
assert(data);
|
|
|
|
MATERIAL_Material *material = MATERIAL_new();
|
|
//No validation, as there are no required fields...
|
|
YAMLLOADER_parse_map_entries(data, parse_material_map_entry, material);
|
|
|
|
return material;
|
|
}
|