/*****************/ /* Shading stuff */ /*****************/ #include "config.h" #include #include #include #include "map-object-apply.h" #include "map-object-main.h" #include "map-object-image.h" #include "map-object-shade.h" static gdouble bx1, by1, bx2, by2; get_ray_color_func get_ray_color; typedef struct { gdouble u, v; gdouble t; GimpVector3 s; GimpVector3 n; gint face; } FaceIntersectInfo; /*****************/ /* Phong shading */ /*****************/ static GimpRGB phong_shade (GimpVector3 *pos, GimpVector3 *viewpoint, GimpVector3 *normal, GimpRGB *diff_col, GimpRGB *spec_col, LightType type) { GimpRGB ambientcolor, diffusecolor, specularcolor; gdouble NL, RV, dist; GimpVector3 L, NN, V, N; GimpVector3 *light; light = mapvals.lightsource.type == DIRECTIONAL_LIGHT ? &mapvals.lightsource.direction : &mapvals.lightsource.position, /* Compute ambient intensity */ /* ========================= */ N = *normal; ambientcolor = *diff_col; gimp_rgb_multiply (&ambientcolor, mapvals.material.ambient_int); /* Compute (N*L) term of Phong's equation */ /* ====================================== */ if (type == POINT_LIGHT) gimp_vector3_sub (&L, light, pos); else L = *light; dist = gimp_vector3_length (&L); if (dist != 0.0) gimp_vector3_mul (&L, 1.0 / dist); NL = 2.0 * gimp_vector3_inner_product (&N, &L); if (NL >= 0.0) { /* Compute (R*V)^alpha term of Phong's equation */ /* ============================================ */ gimp_vector3_sub (&V, viewpoint, pos); gimp_vector3_normalize (&V); gimp_vector3_mul (&N, NL); gimp_vector3_sub (&NN, &N, &L); RV = gimp_vector3_inner_product (&NN, &V); RV = 0.0 < RV ? pow (RV, mapvals.material.highlight) : 0.0; /* Compute diffuse and specular intensity contribution */ /* =================================================== */ diffusecolor = *diff_col; gimp_rgb_multiply (&diffusecolor, mapvals.material.diffuse_ref); gimp_rgb_multiply (&diffusecolor, NL); specularcolor = *spec_col; gimp_rgb_multiply (&specularcolor, mapvals.material.specular_ref); gimp_rgb_multiply (&specularcolor, RV); gimp_rgb_add (&diffusecolor, &specularcolor); gimp_rgb_multiply (&diffusecolor, mapvals.material.diffuse_int); gimp_rgb_clamp (&diffusecolor); gimp_rgb_add (&ambientcolor, &diffusecolor); } return ambientcolor; } static gint plane_intersect (GimpVector3 *dir, GimpVector3 *viewp, GimpVector3 *ipos, gdouble *u, gdouble *v) { static gdouble det, det1, det2, det3, t; imat[0][0] = dir->x; imat[1][0] = dir->y; imat[2][0] = dir->z; /* Compute determinant of the first 3x3 sub matrix (denominator) */ /* ============================================================= */ det = (imat[0][0] * imat[1][1] * imat[2][2] + imat[0][1] * imat[1][2] * imat[2][0] + imat[0][2] * imat[1][0] * imat[2][1] - imat[0][2] * imat[1][1] * imat[2][0] - imat[0][0] * imat[1][2] * imat[2][1] - imat[2][2] * imat[0][1] * imat[1][0]); /* If the determinant is non-zero, a intersection point exists */ /* =========================================================== */ if (det != 0.0) { /* Now, lets compute the numerator determinants (wow ;) */ /* ==================================================== */ det1 = (imat[0][3] * imat[1][1] * imat[2][2] + imat[0][1] * imat[1][2] * imat[2][3] + imat[0][2] * imat[1][3] * imat[2][1] - imat[0][2] * imat[1][1] * imat[2][3] - imat[1][2] * imat[2][1] * imat[0][3] - imat[2][2] * imat[0][1] * imat[1][3]); det2 = (imat[0][0] * imat[1][3] * imat[2][2] + imat[0][3] * imat[1][2] * imat[2][0] + imat[0][2] * imat[1][0] * imat[2][3] - imat[0][2] * imat[1][3] * imat[2][0] - imat[1][2] * imat[2][3] * imat[0][0] - imat[2][2] * imat[0][3] * imat[1][0]); det3 = (imat[0][0] * imat[1][1] * imat[2][3] + imat[0][1] * imat[1][3] * imat[2][0] + imat[0][3] * imat[1][0] * imat[2][1] - imat[0][3] * imat[1][1] * imat[2][0] - imat[1][3] * imat[2][1] * imat[0][0] - imat[2][3] * imat[0][1] * imat[1][0]); /* Now we have the simultaneous solutions. Lets compute the unknowns */ /* (skip u&v if t is <0, this means the intersection is behind us) */ /* ================================================================ */ t = det1 / det; if (t > 0.0) { *u = 1.0 + ((det2 / det) - 0.5); *v = 1.0 + ((det3 / det) - 0.5); ipos->x = viewp->x + t * dir->x; ipos->y = viewp->y + t * dir->y; ipos->z = viewp->z + t * dir->z; return TRUE; } } return FALSE; } /***************************************************************************** * These routines computes the color of the surface * of the plane at a given point *****************************************************************************/ GimpRGB get_ray_color_plane (GimpVector3 *pos) { GimpRGB color = background; static gint inside = FALSE; static GimpVector3 ray, spos; static gdouble vx, vy; /* Construct a line from our VP to the point */ /* ========================================= */ gimp_vector3_sub (&ray, pos, &mapvals.viewpoint); gimp_vector3_normalize (&ray); /* Check for intersection. This is a quasi ray-tracer. */ /* =================================================== */ if (plane_intersect (&ray, &mapvals.viewpoint, &spos, &vx, &vy) == TRUE) { color = get_image_color (vx, vy, &inside); if (color.a != 0.0 && inside == TRUE && mapvals.lightsource.type != NO_LIGHT) { /* Compute shading at this point */ /* ============================= */ color = phong_shade (&spos, &mapvals.viewpoint, &mapvals.normal, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); } } if (mapvals.transparent_background == FALSE && color.a < 1.0) { gimp_rgb_composite (&color, &background, GIMP_RGB_COMPOSITE_BEHIND); } return color; } /***********************************************************************/ /* Given the NorthPole, Equator and a third vector (normal) compute */ /* the conversion from spherical oordinates to image space coordinates */ /***********************************************************************/ static void sphere_to_image (GimpVector3 *normal, gdouble *u, gdouble *v) { static gdouble alpha, fac; static GimpVector3 cross_prod; alpha = acos (-gimp_vector3_inner_product (&mapvals.secondaxis, normal)); *v = alpha / G_PI; if (*v == 0.0 || *v == 1.0) { *u = 0.0; } else { fac = (gimp_vector3_inner_product (&mapvals.firstaxis, normal) / sin (alpha)); /* Make sure that we map to -1.0..1.0 (take care of rounding errors) */ /* ================================================================= */ fac = CLAMP (fac, -1.0, 1.0); *u = acos (fac) / (2.0 * G_PI); cross_prod = gimp_vector3_cross_product (&mapvals.secondaxis, &mapvals.firstaxis); if (gimp_vector3_inner_product (&cross_prod, normal) < 0.0) *u = 1.0 - *u; } } /***************************************************/ /* Compute intersection point with sphere (if any) */ /***************************************************/ static gint sphere_intersect (GimpVector3 *dir, GimpVector3 *viewp, GimpVector3 *spos1, GimpVector3 *spos2) { static gdouble alpha, beta, tau, s1, s2, tmp; static GimpVector3 t; gimp_vector3_sub (&t, &mapvals.position, viewp); alpha = gimp_vector3_inner_product (dir, &t); beta = gimp_vector3_inner_product (&t, &t); tau = alpha * alpha - beta + mapvals.radius * mapvals.radius; if (tau >= 0.0) { tau = sqrt (tau); s1 = alpha + tau; s2 = alpha - tau; if (s2 < s1) { tmp = s1; s1 = s2; s2 = tmp; } spos1->x = viewp->x + s1 * dir->x; spos1->y = viewp->y + s1 * dir->y; spos1->z = viewp->z + s1 * dir->z; spos2->x = viewp->x + s2 * dir->x; spos2->y = viewp->y + s2 * dir->y; spos2->z = viewp->z + s2 * dir->z; return TRUE; } return FALSE; } /***************************************************************************** * These routines computes the color of the surface * of the sphere at a given point *****************************************************************************/ GimpRGB get_ray_color_sphere (GimpVector3 *pos) { GimpRGB color = background; static GimpRGB color2; static gint inside = FALSE; static GimpVector3 normal, ray, spos1, spos2; static gdouble vx, vy; /* Check if ray is within the bounding box */ /* ======================================= */ if (pos->xx>bx2 || pos->yy>by2) return color; /* Construct a line from our VP to the point */ /* ========================================= */ gimp_vector3_sub (&ray, pos, &mapvals.viewpoint); gimp_vector3_normalize (&ray); /* Check for intersection. This is a quasi ray-tracer. */ /* =================================================== */ if (sphere_intersect (&ray, &mapvals.viewpoint, &spos1, &spos2) == TRUE) { /* Compute spherical to rectangular mapping */ /* ======================================== */ gimp_vector3_sub (&normal, &spos1, &mapvals.position); gimp_vector3_normalize (&normal); sphere_to_image (&normal, &vx, &vy); color = get_image_color (vx, vy, &inside); /* Check for total transparency... */ /* =============================== */ if (color.a < 1.0) { /* Hey, we can see through here! */ /* Lets see what's on the other side.. */ /* =================================== */ color = phong_shade (&spos1, &mapvals.viewpoint, &normal, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); gimp_vector3_sub (&normal, &spos2, &mapvals.position); gimp_vector3_normalize (&normal); sphere_to_image (&normal, &vx, &vy); color2 = get_image_color (vx, vy, &inside); /* Make the normal point inwards */ /* ============================= */ gimp_vector3_mul (&normal, -1.0); color2 = phong_shade (&spos2, &mapvals.viewpoint, &normal, &color2, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color2); /* Compute a mix of the first and second colors */ /* ============================================ */ gimp_rgb_composite (&color, &color2, GIMP_RGB_COMPOSITE_NORMAL); gimp_rgb_clamp (&color); } else if (color.a != 0.0 && inside == TRUE && mapvals.lightsource.type != NO_LIGHT) { /* Compute shading at this point */ /* ============================= */ color = phong_shade (&spos1, &mapvals.viewpoint, &normal, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); } } if (mapvals.transparent_background == FALSE && color.a < 1.0) { gimp_rgb_composite (&color, &background, GIMP_RGB_COMPOSITE_BEHIND); } return color; } /***************************************************/ /* Transform the corners of the bounding box to 2D */ /***************************************************/ void compute_bounding_box (void) { GimpVector3 p1, p2; gdouble t; GimpVector3 dir; p1 = mapvals.position; p1.x -= (mapvals.radius + 0.01); p1.y -= (mapvals.radius + 0.01); p2 = mapvals.position; p2.x += (mapvals.radius + 0.01); p2.y += (mapvals.radius + 0.01); gimp_vector3_sub (&dir, &p1, &mapvals.viewpoint); gimp_vector3_normalize (&dir); if (dir.z != 0.0) { t = (-1.0 * mapvals.viewpoint.z) / dir.z; p1.x = (mapvals.viewpoint.x + t * dir.x); p1.y = (mapvals.viewpoint.y + t * dir.y); } gimp_vector3_sub (&dir, &p2, &mapvals.viewpoint); gimp_vector3_normalize (&dir); if (dir.z != 0.0) { t = (-1.0 * mapvals.viewpoint.z) / dir.z; p2.x = (mapvals.viewpoint.x + t * dir.x); p2.y = (mapvals.viewpoint.y + t * dir.y); } bx1 = p1.x; by1 = p1.y; bx2 = p2.x; by2 = p2.y; } /* These two were taken from the Mesa source. Mesa is written */ /* and is (C) by Brian Paul. vecmulmat() performs a post-mul by */ /* a 4x4 matrix to a 1x4(3) vector. rotmat() creates a matrix */ /* that by post-mul will rotate a 1x4(3) vector the given angle */ /* about the given axis. */ /* ============================================================ */ void vecmulmat (GimpVector3 *u, GimpVector3 *v, gfloat m[16]) { gfloat v0=v->x, v1=v->y, v2=v->z; #define M(row,col) m[col*4+row] u->x = v0 * M(0,0) + v1 * M(1,0) + v2 * M(2,0) + M(3,0); u->y = v0 * M(0,1) + v1 * M(1,1) + v2 * M(2,1) + M(3,1); u->z = v0 * M(0,2) + v1 * M(1,2) + v2 * M(2,2) + M(3,2); #undef M } void rotatemat (gfloat angle, GimpVector3 *v, gfloat m[16]) { /* This function contributed by Erich Boleyn (erich@uruk.org) */ gfloat mag, s, c; gfloat xx, yy, zz, xy, yz, zx, xs, ys, zs, one_c; gfloat IdentityMat[16]; gint cnt; s = sin (angle * (G_PI / 180.0)); c = cos (angle * (G_PI / 180.0)); mag = sqrt (v->x*v->x + v->y*v->y + v->z*v->z); if (mag == 0.0) { /* generate an identity matrix and return */ for (cnt = 0; cnt < 16; cnt++) IdentityMat[cnt] = 0.0; IdentityMat[0] = 1.0; IdentityMat[5] = 1.0; IdentityMat[10] = 1.0; IdentityMat[15] = 1.0; memcpy (m, IdentityMat, sizeof (gfloat) * 16); return; } v->x /= mag; v->y /= mag; v->z /= mag; #define M(row,col) m[col*4+row] xx = v->x * v->x; yy = v->y * v->y; zz = v->z * v->z; xy = v->x * v->y; yz = v->y * v->z; zx = v->z * v->x; xs = v->x * s; ys = v->y * s; zs = v->z * s; one_c = 1.0F - c; M(0,0) = (one_c * xx) + c; M(0,1) = (one_c * xy) - zs; M(0,2) = (one_c * zx) + ys; M(0,3) = 0.0F; M(1,0) = (one_c * xy) + zs; M(1,1) = (one_c * yy) + c; M(1,2) = (one_c * yz) - xs; M(1,3) = 0.0F; M(2,0) = (one_c * zx) - ys; M(2,1) = (one_c * yz) + xs; M(2,2) = (one_c * zz) + c; M(2,3) = 0.0F; M(3,0) = 0.0F; M(3,1) = 0.0F; M(3,2) = 0.0F; M(3,3) = 1.0F; #undef M } /* Transpose the matrix m. If m is orthogonal (like a rotation matrix), */ /* this is equal to the inverse of the matrix. */ /* ==================================================================== */ void transpose_mat (gfloat m[16]) { gint i, j; gfloat t; for (i = 0; i < 4; i++) { for (j = 0; j < i; j++) { t = m[j*4+i]; m[j*4+i] = m[i*4+j]; m[i*4+j] = t; } } } /* Compute the matrix product c=a*b */ /* ================================ */ void matmul (gfloat a[16], gfloat b[16], gfloat c[16]) { gint i, j, k; gfloat value; #define A(row,col) a[col*4+row] #define B(row,col) b[col*4+row] #define C(row,col) c[col*4+row] for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { value = 0.0; for (k = 0; k < 4; k++) value += A(i,k) * B(k,j); C(i,j) = value; } } #undef A #undef B #undef C } void ident_mat (gfloat m[16]) { gint i, j; #define M(row,col) m[col*4+row] for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { if (i == j) M(i,j) = 1.0; else M(i,j) = 0.0; } } #undef M } static gboolean intersect_rect (gdouble u, gdouble v, gdouble w, GimpVector3 viewp, GimpVector3 dir, FaceIntersectInfo *face_info) { gboolean result = FALSE; gdouble u2, v2; if (dir.z!=0.0) { u2 = u / 2.0; v2 = v / 2.0; face_info->t = (w-viewp.z) / dir.z; face_info->s.x = viewp.x + face_info->t * dir.x; face_info->s.y = viewp.y + face_info->t * dir.y; face_info->s.z = w; if (face_info->s.x >= -u2 && face_info->s.x <= u2 && face_info->s.y >= -v2 && face_info->s.y <= v2) { face_info->u = (face_info->s.x + u2) / u; face_info->v = (face_info->s.y + v2) / v; result = TRUE; } } return result; } static gboolean intersect_box (GimpVector3 scale, GimpVector3 viewp, GimpVector3 dir, FaceIntersectInfo *face_intersect) { GimpVector3 v, d, tmp, axis[3]; FaceIntersectInfo face_tmp; gboolean result = FALSE; gfloat m[16]; gint i = 0; gimp_vector3_set (&axis[0], 1.0, 0.0, 0.0); gimp_vector3_set (&axis[1], 0.0, 1.0, 0.0); gimp_vector3_set (&axis[2], 0.0, 0.0, 1.0); /* Front side */ /* ========== */ if (intersect_rect (scale.x, scale.y, scale.z / 2.0, viewp, dir, &face_intersect[i]) == TRUE) { face_intersect[i].face = 0; gimp_vector3_set (&face_intersect[i++].n, 0.0, 0.0, 1.0); result = TRUE; } /* Back side */ /* ========= */ if (intersect_rect (scale.x, scale.y, -scale.z / 2.0, viewp, dir, &face_intersect[i]) == TRUE) { face_intersect[i].face = 1; face_intersect[i].u = 1.0 - face_intersect[i].u; gimp_vector3_set (&face_intersect[i++].n, 0.0, 0.0, -1.0); result = TRUE; } /* Check if we've found the two possible intersection points */ /* ========================================================= */ if (i < 2) { /* Top: Rotate viewpoint and direction into rectangle's local coordinate system */ /* ============================================================================ */ rotatemat (90, &axis[0], m); vecmulmat (&v, &viewp, m); vecmulmat (&d, &dir, m); if (intersect_rect (scale.x, scale.z, scale.y / 2.0, v, d, &face_intersect[i]) == TRUE) { face_intersect[i].face = 2; transpose_mat (m); vecmulmat(&tmp, &face_intersect[i].s, m); face_intersect[i].s = tmp; gimp_vector3_set (&face_intersect[i++].n, 0.0, -1.0, 0.0); result = TRUE; } } /* Check if we've found the two possible intersection points */ /* ========================================================= */ if (i < 2) { /* Bottom: Rotate viewpoint and direction into rectangle's local coordinate system */ /* =============================================================================== */ rotatemat (90, &axis[0], m); vecmulmat (&v, &viewp, m); vecmulmat (&d, &dir, m); if (intersect_rect (scale.x, scale.z, -scale.y / 2.0, v, d, &face_intersect[i]) == TRUE) { face_intersect[i].face = 3; transpose_mat (m); vecmulmat (&tmp, &face_intersect[i].s, m); face_intersect[i].s = tmp; face_intersect[i].v = 1.0 - face_intersect[i].v; gimp_vector3_set (&face_intersect[i++].n, 0.0, 1.0, 0.0); result = TRUE; } } /* Check if we've found the two possible intersection points */ /* ========================================================= */ if (i < 2) { /* Left side: Rotate viewpoint and direction into rectangle's local coordinate system */ /* ================================================================================== */ rotatemat (90, &axis[1], m); vecmulmat (&v, &viewp, m); vecmulmat (&d, &dir, m); if (intersect_rect (scale.z, scale.y, scale.x / 2.0, v, d, &face_intersect[i]) == TRUE) { face_intersect[i].face = 4; transpose_mat (m); vecmulmat (&tmp, &face_intersect[i].s, m); face_intersect[i].s = tmp; gimp_vector3_set (&face_intersect[i++].n, 1.0, 0.0, 0.0); result = TRUE; } } /* Check if we've found the two possible intersection points */ /* ========================================================= */ if (i < 2) { /* Right side: Rotate viewpoint and direction into rectangle's local coordinate system */ /* =================================================================================== */ rotatemat (90, &axis[1], m); vecmulmat (&v, &viewp, m); vecmulmat (&d, &dir, m); if (intersect_rect (scale.z, scale.y, -scale.x / 2.0, v, d, &face_intersect[i]) == TRUE) { face_intersect[i].face = 5; transpose_mat (m); vecmulmat (&tmp, &face_intersect[i].s, m); face_intersect[i].u = 1.0 - face_intersect[i].u; gimp_vector3_set (&face_intersect[i++].n, -1.0, 0.0, 0.0); result = TRUE; } } /* Sort intersection points */ /* ======================== */ if (face_intersect[0].t > face_intersect[1].t) { face_tmp = face_intersect[0]; face_intersect[0] = face_intersect[1]; face_intersect[1] = face_tmp; } return result; } GimpRGB get_ray_color_box (GimpVector3 *pos) { GimpVector3 lvp, ldir, vp, p, dir, ns, nn; GimpRGB color, color2; gfloat m[16]; gint i; FaceIntersectInfo face_intersect[2]; color = background; vp = mapvals.viewpoint; p = *pos; /* Translate viewpoint so that the box has its origin */ /* at its lower left corner. */ /* ================================================== */ vp.x = vp.x - mapvals.position.x; vp.y = vp.y - mapvals.position.y; vp.z = vp.z - mapvals.position.z; p.x = p.x - mapvals.position.x; p.y = p.y - mapvals.position.y; p.z = p.z - mapvals.position.z; /* Compute direction */ /* ================= */ gimp_vector3_sub (&dir, &p, &vp); gimp_vector3_normalize (&dir); /* Compute inverse of rotation matrix and apply it to */ /* the viewpoint and direction. This transforms the */ /* observer into the local coordinate system of the box */ /* ==================================================== */ memcpy (m, rotmat, sizeof (gfloat) * 16); transpose_mat (m); vecmulmat (&lvp, &vp, m); vecmulmat (&ldir, &dir, m); /* Ok. Now the observer is in the space where the box is located */ /* with its lower left corner at the origin and its axis aligned */ /* to the cartesian basis. Check if the transformed ray hits it. */ /* ============================================================= */ face_intersect[0].t = 1000000.0; face_intersect[1].t = 1000000.0; if (intersect_box (mapvals.scale, lvp, ldir, face_intersect) == TRUE) { /* We've hit the box. Transform the hit points and */ /* normals back into the world coordinate system */ /* =============================================== */ for (i = 0; i < 2; i++) { vecmulmat (&ns, &face_intersect[i].s, rotmat); vecmulmat (&nn, &face_intersect[i].n, rotmat); ns.x = ns.x + mapvals.position.x; ns.y = ns.y + mapvals.position.y; ns.z = ns.z + mapvals.position.z; face_intersect[i].s = ns; face_intersect[i].n = nn; } color = get_box_image_color (face_intersect[0].face, face_intersect[0].u, face_intersect[0].v); /* Check for total transparency... */ /* =============================== */ if (color.a < 1.0) { /* Hey, we can see through here! */ /* Lets see what's on the other side.. */ /* =================================== */ color = phong_shade (&face_intersect[0].s, &mapvals.viewpoint, &face_intersect[0].n, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); color2 = get_box_image_color (face_intersect[1].face, face_intersect[1].u, face_intersect[1].v); /* Make the normal point inwards */ /* ============================= */ gimp_vector3_mul (&face_intersect[1].n, -1.0); color2 = phong_shade (&face_intersect[1].s, &mapvals.viewpoint, &face_intersect[1].n, &color2, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color2); if (mapvals.transparent_background == FALSE && color2.a < 1.0) { gimp_rgb_composite (&color2, &background, GIMP_RGB_COMPOSITE_BEHIND); } /* Compute a mix of the first and second colors */ /* ============================================ */ gimp_rgb_composite (&color, &color2, GIMP_RGB_COMPOSITE_NORMAL); gimp_rgb_clamp (&color); } else if (color.a != 0.0 && mapvals.lightsource.type != NO_LIGHT) { color = phong_shade (&face_intersect[0].s, &mapvals.viewpoint, &face_intersect[0].n, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); } } else { if (mapvals.transparent_background == TRUE) gimp_rgb_set_alpha (&color, 0.0); } return color; } static gboolean intersect_circle (GimpVector3 vp, GimpVector3 dir, gdouble w, FaceIntersectInfo *face_info) { gboolean result = FALSE; gdouble r, d; #define sqr(a) (a*a) if (dir.y != 0.0) { face_info->t = (w-vp.y)/dir.y; face_info->s.x = vp.x + face_info->t*dir.x; face_info->s.y = w; face_info->s.z = vp.z + face_info->t*dir.z; r = sqrt (sqr (face_info->s.x) + sqr (face_info->s.z)); if (r <= mapvals.cylinder_radius) { d = 2.0 * mapvals.cylinder_radius; face_info->u = (face_info->s.x + mapvals.cylinder_radius) / d; face_info->v = (face_info->s.z + mapvals.cylinder_radius) / d; result = TRUE; } } #undef sqr return result; } static gboolean intersect_cylinder (GimpVector3 vp, GimpVector3 dir, FaceIntersectInfo *face_intersect) { gdouble a, b, c, d, e, f, tmp, l; gboolean result = FALSE; gint i; #define sqr(a) (a*a) a = sqr (dir.x) + sqr (dir.z); b = 2.0 * (vp.x * dir.x + vp.z * dir.z); c = sqr (vp.x) + sqr (vp.z) - sqr (mapvals.cylinder_radius); d = sqr (b) - 4.0 * a * c; if (d >= 0.0) { e = sqrt (d); f = 2.0 * a; if (f != 0.0) { result = TRUE; face_intersect[0].t = (-b+e)/f; face_intersect[1].t = (-b-e)/f; if (face_intersect[0].t>face_intersect[1].t) { tmp = face_intersect[0].t; face_intersect[0].t = face_intersect[1].t; face_intersect[1].t = tmp; } for (i = 0; i < 2; i++) { face_intersect[i].s.x = vp.x + face_intersect[i].t * dir.x; face_intersect[i].s.y = vp.y + face_intersect[i].t * dir.y; face_intersect[i].s.z = vp.z + face_intersect[i].t * dir.z; face_intersect[i].n = face_intersect[i].s; face_intersect[i].n.y = 0.0; gimp_vector3_normalize(&face_intersect[i].n); l = mapvals.cylinder_length/2.0; face_intersect[i].u = (atan2(face_intersect[i].s.x,face_intersect[i].s.z)+G_PI)/(2.0*G_PI); face_intersect[i].v = (face_intersect[i].s.y+l)/mapvals.cylinder_length; /* Mark hitpoint as on the cylinder hull */ /* ===================================== */ face_intersect[i].face = 0; /* Check if we're completely off the cylinder axis */ /* =============================================== */ if (face_intersect[i].s.y>l || face_intersect[i].s.y<-l) { /* Check if we've hit a cap */ /* ======================== */ if (face_intersect[i].s.y>l) { if (intersect_circle(vp,dir,l,&face_intersect[i])==FALSE) result = FALSE; else { face_intersect[i].face = 2; face_intersect[i].v = 1 - face_intersect[i].v; gimp_vector3_set(&face_intersect[i].n, 0.0, 1.0, 0.0); } } else { if (intersect_circle(vp,dir,-l,&face_intersect[i])==FALSE) result = FALSE; else { face_intersect[i].face = 1; gimp_vector3_set(&face_intersect[i].n, 0.0, -1.0, 0.0); } } } } } } #undef sqr return result; } static GimpRGB get_cylinder_color (gint face, gdouble u, gdouble v) { GimpRGB color; gint inside; if (face == 0) color = get_image_color (u, v, &inside); else color = get_cylinder_image_color (face - 1, u, v); return color; } GimpRGB get_ray_color_cylinder (GimpVector3 *pos) { GimpVector3 lvp, ldir, vp, p, dir, ns, nn; GimpRGB color, color2; gfloat m[16]; gint i; FaceIntersectInfo face_intersect[2]; color = background; vp = mapvals.viewpoint; p = *pos; vp.x = vp.x - mapvals.position.x; vp.y = vp.y - mapvals.position.y; vp.z = vp.z - mapvals.position.z; p.x = p.x - mapvals.position.x; p.y = p.y - mapvals.position.y; p.z = p.z - mapvals.position.z; /* Compute direction */ /* ================= */ gimp_vector3_sub (&dir, &p, &vp); gimp_vector3_normalize (&dir); /* Compute inverse of rotation matrix and apply it to */ /* the viewpoint and direction. This transforms the */ /* observer into the local coordinate system of the box */ /* ==================================================== */ memcpy (m, rotmat, sizeof (gfloat) * 16); transpose_mat (m); vecmulmat (&lvp, &vp, m); vecmulmat (&ldir, &dir, m); if (intersect_cylinder (lvp, ldir, face_intersect) == TRUE) { /* We've hit the cylinder. Transform the hit points and */ /* normals back into the world coordinate system */ /* ==================================================== */ for (i = 0; i < 2; i++) { vecmulmat (&ns, &face_intersect[i].s, rotmat); vecmulmat (&nn, &face_intersect[i].n, rotmat); ns.x = ns.x + mapvals.position.x; ns.y = ns.y + mapvals.position.y; ns.z = ns.z + mapvals.position.z; face_intersect[i].s = ns; face_intersect[i].n = nn; } color = get_cylinder_color (face_intersect[0].face, face_intersect[0].u, face_intersect[0].v); /* Check for transparency... */ /* ========================= */ if (color.a < 1.0) { /* Hey, we can see through here! */ /* Lets see what's on the other side.. */ /* =================================== */ color = phong_shade (&face_intersect[0].s, &mapvals.viewpoint, &face_intersect[0].n, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); color2 = get_cylinder_color (face_intersect[1].face, face_intersect[1].u, face_intersect[1].v); /* Make the normal point inwards */ /* ============================= */ gimp_vector3_mul (&face_intersect[1].n, -1.0); color2 = phong_shade (&face_intersect[1].s, &mapvals.viewpoint, &face_intersect[1].n, &color2, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color2); if (mapvals.transparent_background == FALSE && color2.a < 1.0) { gimp_rgb_composite (&color2, &background, GIMP_RGB_COMPOSITE_BEHIND); } /* Compute a mix of the first and second colors */ /* ============================================ */ gimp_rgb_composite (&color, &color2, GIMP_RGB_COMPOSITE_NORMAL); gimp_rgb_clamp (&color); } else if (color.a != 0.0 && mapvals.lightsource.type != NO_LIGHT) { color = phong_shade (&face_intersect[0].s, &mapvals.viewpoint, &face_intersect[0].n, &color, &mapvals.lightsource.color, mapvals.lightsource.type); gimp_rgb_clamp (&color); } } else { if (mapvals.transparent_background == TRUE) gimp_rgb_set_alpha (&color, 0.0); } return color; }