// add extra methods here void MyScene::rayTrace(Point3 point, Vector3 ray){ HitRecord* hits = new HitRecord(); transformStack.clear(); transformStack.push_back(Matrix4::identity()); castRayAgainstSubgraph(point, ray, root, hits); bool gotHit; double tH, uH, vH; Point3 pH; Vector3 nH; gotHit = hits->getFirstHit(tH, uH, vH, pH, nH); delete hits; int col, row; getScreenCoord(point+ray, col, row); int idx = pxIdx(col, row); if (gotHit){ for (Light& l : lights){ Point3 lPos = l.getPos(); castLight(lPos, pH, l, 0, 0, NULL); } } else{ pixelArray[idx] = (unsigned char)(background[0] * 255); pixelArray[idx + 1] = (unsigned char)(background[1] * 255); pixelArray[idx + 2] = (unsigned char)(background[2] * 255); } }
void map::castLight( int row, float start, float end, int xx, int xy, int yx, int yy, const int offsetX, const int offsetY, const int offsetDistance ) { float newStart = 0.0f; float radius = 60.0f - offsetDistance; if( start < end ) { return; } bool blocked = false; for( int distance = row; distance <= radius && !blocked; distance++ ) { int deltaY = -distance; for( int deltaX = -distance; deltaX <= 0; deltaX++ ) { int currentX = offsetX + deltaX * xx + deltaY * xy; int currentY = offsetY + deltaX * yx + deltaY * yy; float leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f); float rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f); if( !(currentX >= 0 && currentY >= 0 && currentX < SEEX * my_MAPSIZE && currentY < SEEY * my_MAPSIZE) || start < rightSlope ) { continue; } else if( end > leftSlope ) { break; } //check if it's within the visible area and mark visible if so if( rl_dist(0, 0, deltaX, deltaY) <= radius ) { /* float bright = (float) (1 - (rStrat.radius(deltaX, deltaY) / radius)); lightMap[currentX][currentY] = bright; */ seen_cache[currentX][currentY] = true; } if( blocked ) { //previous cell was a blocking one if( light_transparency(currentX, currentY) == LIGHT_TRANSPARENCY_SOLID ) { //hit a wall newStart = rightSlope; continue; } else { blocked = false; start = newStart; } } else { if( light_transparency(currentX, currentY) == LIGHT_TRANSPARENCY_SOLID && distance < radius ) { //hit a wall within sight line blocked = true; castLight(distance + 1, start, leftSlope, xx, xy, yx, yy, offsetX, offsetY, offsetDistance); newStart = rightSlope; } } } } }
/** * Calculates the Field Of View for the provided map from the given x, y * coordinates. Returns a lightmap for a result where the values represent a * percentage of fully lit. * * A value equal to or below 0 means that cell is not in the * field of view, whereas a value equal to or above 1 means that cell is * in the field of view. * * @param startx the horizontal component of the starting location * @param starty the vertical component of the starting location * @param radius the maximum distance to draw the FOV */ void map::build_seen_cache( game *g ) { memset(seen_cache, false, sizeof(seen_cache)); seen_cache[g->u.posx][g->u.posy] = true; castLight( g, 1, 1.0f, 0.0f, 0, 1, 1, 0 ); castLight( g, 1, 1.0f, 0.0f, 1, 0, 0, 1 ); castLight( g, 1, 1.0f, 0.0f, 0, -1, 1, 0 ); castLight( g, 1, 1.0f, 0.0f, -1, 0, 0, 1 ); castLight( g, 1, 1.0f, 0.0f, 0, 1, -1, 0 ); castLight( g, 1, 1.0f, 0.0f, 1, 0, 0, -1 ); castLight( g, 1, 1.0f, 0.0f, 0, -1, -1, 0 ); castLight( g, 1, 1.0f, 0.0f, -1, 0, 0, -1 ); }
/** * Calculates the Field Of View for the provided map from the given x, y * coordinates. Returns a lightmap for a result where the values represent a * percentage of fully lit. * * A value equal to or below 0 means that cell is not in the * field of view, whereas a value equal to or above 1 means that cell is * in the field of view. * * @param startx the horizontal component of the starting location * @param starty the vertical component of the starting location * @param radius the maximum distance to draw the FOV */ void map::build_seen_cache() { memset(seen_cache, false, sizeof(seen_cache)); seen_cache[g->u.posx][g->u.posy] = true; const int offsetX = g->u.posx; const int offsetY = g->u.posy; castLight( 1, 1.0f, 0.0f, 0, 1, 1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, 1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, -1, 1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, 1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, 1, -1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, -1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, -1, -1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, -1, offsetX, offsetY, 0 ); if (vehicle *veh = veh_at(offsetX, offsetY)) { // We're inside a vehicle. Do mirror calcs. std::vector<int> mirrors = veh->all_parts_with_feature(VPFLAG_MIRROR, true); // Do all the sight checks first to prevent fake multiple reflection // from happening due to mirrors becoming visible due to processing order. for (std::vector<int>::iterator m_it = mirrors.begin(); m_it != mirrors.end(); /* noop */) { const int mirrorX = veh->global_x() + veh->parts[*m_it].precalc_dx[0]; const int mirrorY = veh->global_y() + veh->parts[*m_it].precalc_dy[0]; // We can utilize the current state of the seen cache to determine // if the player can see the mirror from their position. if (!g->u.sees(mirrorX, mirrorY)) { m_it = mirrors.erase(m_it); } else { ++m_it; } } for (std::vector<int>::iterator m_it = mirrors.begin(); m_it != mirrors.end(); ++m_it) { const int mirrorX = veh->global_x() + veh->parts[*m_it].precalc_dx[0]; const int mirrorY = veh->global_y() + veh->parts[*m_it].precalc_dy[0]; // Determine how far the light has already traveled so mirrors // don't cheat the light distance falloff. int offsetDistance = rl_dist(offsetX, offsetY, mirrorX, mirrorY); // @todo: Factor in the mirror facing and only cast in the // directions the player's line of sight reflects to. // // The naive solution of making the mirrors act like a second player // at an offset appears to give reasonable results though. castLight( 1, 1.0f, 0.0f, 0, 1, 1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, 1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, -1, 1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, 1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, 1, -1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, -1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, -1, -1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, -1, mirrorX, mirrorY, offsetDistance ); } } }
void MyScene::castLight(const Point3& src, const Point3& dst, Light& l, int depth, double distance, Color* reflectedColor){ Vector3 ray = dst - src; ray.normalize(); HitRecord* hits = new HitRecord(); castRayAgainstSubgraph(src, ray, root, hits); double t, u, v; Point3 p; Vector3 n; hits->getFirstHit(t, u, v, p, n); Object* o = (Object*)hits->getFirstHitObj(); delete hits; int col, row; getScreenCoord(dst, col, row); int idx = pxIdx(col, row); Color screenColor = Color(pixelArray[idx] / (float)255, pixelArray[idx+1] / (float)255, pixelArray[idx+2] / (float)255); //ambient screenColor[0] += o->ambient[0]; screenColor[1] += o->ambient[1]; screenColor[2] += o->ambient[2]; //not in shadow double diff = (dst - p).length(); if (diff < EPSILON){ Color lightColor = l.getColor(); distance += (dst-src).length(); Point3 falloff = l.getFalloff(); double attenuation = 1 / (double)(falloff[0] + falloff[1] * distance + falloff[2] * distance*distance); //diffuse double dot = n*(-ray); if (dot > 0){ screenColor[0] += (GLfloat)(o->diffuse[0] * lightColor[0] * attenuation*dot); screenColor[1] += (GLfloat)(o->diffuse[1] * lightColor[1] * attenuation*dot); screenColor[2] += (GLfloat)(o->diffuse[2] * lightColor[2] * attenuation*dot); } //reflect ++depth; if ((depth < MAX_RECURSION_DEPTH) && (attenuation > EPSILON)){ Vector3 reflectedRay = 2 * (ray * n) * n - ray; reflectedRay.normalize(); Point3 newSrc = p + EPSILON*reflectedRay; Point3 newDst = p + reflectedRay; double r = o->reflect[0] * lightColor[0] * attenuation*dot; double g = o->reflect[1] * lightColor[1] * attenuation*dot; double b = o->reflect[2] * lightColor[2] * attenuation*dot; if (r > INVISIBLE || g > INVISIBLE || b > INVISIBLE){ Color* reflect = new Color((GLfloat)r, (GLfloat)g, (GLfloat)b); castLight(newSrc, newDst, l, depth, distance, reflect); delete reflect; } } //specular Vector3 reflection = ray - 2 * n*(ray*n); reflection.normalize(); Vector3 look = camera.getLook(); look.normalize(); dot = reflection*(-look); if (dot > 0){ screenColor[0] += (GLfloat)(o->specular[0] * lightColor[0] * attenuation * pow(dot, o->shine)); screenColor[1] += (GLfloat)(o->specular[1] * lightColor[1] * attenuation * pow(dot, o->shine)); screenColor[2] += (GLfloat)(o->specular[2] * lightColor[2] * attenuation * pow(dot, o->shine)); } } if (screenColor[0] > 1){ screenColor[0] = 1; } if (screenColor[1] > 1){ screenColor[1] = 1; } if (screenColor[2] > 1){ screenColor[2] = 1; } pixelArray[idx] = (unsigned char)roundToNearest(screenColor[0]*255); pixelArray[idx + 1] = (unsigned char)roundToNearest(screenColor[1] * 255); pixelArray[idx + 2] = (unsigned char)roundToNearest(screenColor[2] * 255); }
/** * Calculates the Field Of View for the provided map from the given x, y * coordinates. Returns a lightmap for a result where the values represent a * percentage of fully lit. * * A value equal to or below 0 means that cell is not in the * field of view, whereas a value equal to or above 1 means that cell is * in the field of view. * * @param startx the horizontal component of the starting location * @param starty the vertical component of the starting location * @param radius the maximum distance to draw the FOV */ void map::build_seen_cache() { memset(seen_cache, false, sizeof(seen_cache)); seen_cache[g->u.posx][g->u.posy] = true; const int offsetX = g->u.posx; const int offsetY = g->u.posy; castLight( 1, 1.0f, 0.0f, 0, 1, 1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, 1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, -1, 1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, 1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, 1, -1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, -1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, -1, -1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, -1, offsetX, offsetY, 0 ); int part; if ( vehicle *veh = veh_at( offsetX, offsetY, part ) ) { // We're inside a vehicle. Do mirror calcs. std::vector<int> mirrors = veh->all_parts_with_feature(VPFLAG_EXTENDS_VISION, true); // Do all the sight checks first to prevent fake multiple reflection // from happening due to mirrors becoming visible due to processing order. // Cameras are also handled here, so that we only need to get through all veh parts once int cam_control = -1; for (std::vector<int>::iterator m_it = mirrors.begin(); m_it != mirrors.end(); /* noop */) { const int mirrorX = veh->global_x() + veh->parts[*m_it].precalc_dx[0]; const int mirrorY = veh->global_y() + veh->parts[*m_it].precalc_dy[0]; // We can utilize the current state of the seen cache to determine // if the player can see the mirror from their position. if( !veh->part_info( *m_it ).has_flag( "CAMERA" ) && !g->u.sees(mirrorX, mirrorY)) { m_it = mirrors.erase(m_it); } else if( !veh->part_info( *m_it ).has_flag( "CAMERA_CONTROL" ) ) { ++m_it; } else { if( offsetX == mirrorX && offsetY == mirrorY && veh->camera_on ) { cam_control = *m_it; } m_it = mirrors.erase( m_it ); } } for( size_t i = 0; i < mirrors.size(); i++ ) { const int &mirror = mirrors[i]; bool is_camera = veh->part_info( mirror ).has_flag( "CAMERA" ); if( is_camera && cam_control < 0 ) { continue; // Player not at camera control, so cameras don't work } const int mirrorX = veh->global_x() + veh->parts[mirror].precalc_dx[0]; const int mirrorY = veh->global_y() + veh->parts[mirror].precalc_dy[0]; // Determine how far the light has already traveled so mirrors // don't cheat the light distance falloff. int offsetDistance; if( !is_camera ) { offsetDistance = rl_dist(offsetX, offsetY, mirrorX, mirrorY); } else { offsetDistance = 60 - veh->part_info( mirror ).bonus * veh->parts[mirror].hp / veh->part_info( mirror ).durability; seen_cache[mirrorX][mirrorY] = true; } // @todo: Factor in the mirror facing and only cast in the // directions the player's line of sight reflects to. // // The naive solution of making the mirrors act like a second player // at an offset appears to give reasonable results though. castLight( 1, 1.0f, 0.0f, 0, 1, 1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, 1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, -1, 1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, 1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, 1, -1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, -1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, -1, -1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, -1, mirrorX, mirrorY, offsetDistance ); } } }