/* Starshatter: The Open Source Project Copyright (c) 2021-2024, Starshatter: The Open Source Project Contributors Copyright (c) 2011-2012, Starshatter OpenSource Distribution Contributors Copyright (c) 1997-2006, Destroyer Studios LLC. AUTHOR: John DiCamillo OVERVIEW ======== */ #include "Terrain.h" #include "TerrainLayer.h" #include "TerrainPatch.h" #include "TerrainRegion.h" #include "Sim.h" #include "Light.h" #include "CameraView.h" #include "Projector.h" #include "Bitmap.h" #include "Game.h" #include "Scene.h" #include "Water.h" // +====================================================================+ // #define DEBUG_DETAIL 1 // +====================================================================+ const int MAX_DETAIL = 4; const int PATCH_SIZE = 17; const int HALF_PATCH_SIZE = 8; const int MAX_VERTS = PATCH_SIZE * PATCH_SIZE; static bool illuminating = false; // +--------------------------------------------------------------------+ TerrainPatch::TerrainPatch(Terrain* terr,const Bitmap* patch, const Rect& r, const Point& p1, const Point& p2) : ndetail(0), terrain(terr), rect(r), water(0), min_height(1e9), max_height(-1e9) { luminous = true; // we will do our own lighting own_model = false; // manage the model lifetimes in this derived class max_detail = Terrain::DetailLevel(); scale = fabs(p1.x - p2.x) / (PATCH_SIZE-1); mtnscale = p2.y - p1.y; base = p1.y; size = p2.x - p1.x; ZeroMemory(detail_levels, sizeof(detail_levels)); terrain_width = patch->Width(); loc = (p1 + p2) * 0.5; loc.y = base; radius = (float) (size * 0.75); heights = new float[MAX_VERTS]; float* pHeight = heights; int tscale = rect.w / (PATCH_SIZE-1); int i, j; for (i = 0; i < PATCH_SIZE; i++) { int ty = rect.y + i * tscale; if (ty < 0) ty = 0; if (ty > patch->Height()-1) ty = patch->Height()-1; for (j = 0; j < PATCH_SIZE; j++) { int tx = rect.x + (PATCH_SIZE-1 - j) * tscale; if (tx < 0) tx = 0; if (tx > patch->Width()-1) tx = patch->Width()-1; int red = patch->GetColor(tx,ty).Red(); float alt = (float) (red * mtnscale); if (alt < min_height) min_height = alt; if (alt > max_height) max_height = alt; if (terrain->WaterTexture() && red < 2) alt = -5000.0f; *pHeight++ = alt; } } Material* mtl = new Material; mtl->Ka = ColorValue(0.5f, 0.5f, 0.5f); mtl->Kd = ColorValue(0.3f, 0.6f, 0.2f); mtl->Ks = Color::Black; mtl->tex_diffuse = terrain->Texture(); materials.append(mtl); List& layers = terrain->GetLayers(); for (i = 0; i < layers.size(); i++) { Bitmap* tex0 = layers.at(i)->GetTileTexture(); Bitmap* tex1 = 0; Bitmap* texd = layers.at(i)->GetDetailTexture(); if (i < layers.size()-1) tex1 = layers.at(i+1)->GetTileTexture(); if (!texd) texd = terrain->DetailTexture(0); mtl = new Material; mtl->Ka = ColorValue(0.5f, 0.5f, 0.5f); mtl->Kd = ColorValue(0.3f, 0.6f, 0.2f); mtl->Ks = Color::Black; if ((i & 1) != 0) { mtl->tex_diffuse = tex1; mtl->tex_alternate = tex0; } else { mtl->tex_diffuse = tex0; mtl->tex_alternate = tex1; } mtl->tex_detail = texd; materials.append(mtl); } for (i = 0; i <= max_detail; i++) BuildDetailLevel(i); model = detail_levels[1]; } // +--------------------------------------------------------------------+ TerrainPatch::TerrainPatch(Terrain* terr, const Rect& r, const Point& p1, const Point& p2, double sea_level) : ndetail(0), terrain(terr), rect(r), water(0) { luminous = true; // water is lit by the graphics engine own_model = false; // manage the model lifetimes in this derived class max_detail = Terrain::DetailLevel(); scale = fabs(p1.x - p2.x) / (PATCH_SIZE-1); mtnscale = 0; base = sea_level; size = p2.x - p1.x; ZeroMemory(detail_levels, sizeof(detail_levels)); terrain_width = 0; loc = (p1 + p2) * 0.5; loc.y = base; radius = (float) (size * 0.75); heights = new float[MAX_VERTS]; float* pHeight = heights; int i, j; for (i = 0; i < PATCH_SIZE; i++) { for (j = 0; j < PATCH_SIZE; j++) { *pHeight++ = (float) sea_level; } } Material* mtl = new Material; mtl->Ka = Color::Gray; mtl->Kd = Color::White; mtl->Ks = Color::White; mtl->power = 30.0f; mtl->shadow = false; mtl->tex_diffuse = terrain->WaterTexture(); //strcpy_s(mtl->shader, "WaterReflections"); materials.append(mtl); water = terrain->GetWater(1); for (i = 0; i <= max_detail; i++) BuildDetailLevel(i); model = detail_levels[1]; } // +--------------------------------------------------------------------+ TerrainPatch::~TerrainPatch() { delete [] heights; for (int i = 0; i < MAX_LOD; i++) { Model* m = detail_levels[i]; if (m) { m->GetMaterials().clear(); delete m; } } materials.destroy(); } // +--------------------------------------------------------------------+ void TerrainPatch::SetScales(double s, double m, double b) { scale = s; mtnscale = m; base = b; } // +--------------------------------------------------------------------+ static int mcomp(const void* a, const void* b) { Poly* pa = (Poly*) a; Poly* pb = (Poly*) b; if (pa->sortval == pb->sortval) return 0; if (pa->sortval < pb->sortval) return 1; return -1; } static void bisect(VertexSet* vset, int v[4]) { double d1 = fabs(vset->loc[ v[0] ].y - vset->loc[ v[3] ].y); double d2 = fabs(vset->loc[ v[1] ].y - vset->loc[ v[2] ].y); if (d2 < 0.7*d1) { int odd[4] = { v[1], v[3], v[0], v[2] }; for (int i = 0; i < 4; i++) v[i] = odd[i]; } } bool TerrainPatch::BuildDetailLevel(int level) { int i, j; int detail_size = 1 << level; int ds1 = detail_size+1; if (detail_size > PATCH_SIZE) return false; Model* model = new Model; detail_levels[level] = model; model->SetLuminous(luminous); model->SetDynamic(true); const int NUM_STRIPS = 4; const int NUM_INDICES_TRI = 3; const int NUM_INDICES_QUAD = 6; int nverts = ds1*ds1 + ds1*2*NUM_STRIPS; int npolys = detail_size*detail_size*2; int strip_len = detail_size; int total = npolys + strip_len*NUM_STRIPS; if (water) { nverts = ds1*ds1; strip_len = 0; total = npolys; } Surface* s = new Surface; VertexSet* vset = 0; if (s) { s->SetName("default"); s->CreateVerts(nverts); s->CreatePolys(total); s->AddIndices(npolys*NUM_INDICES_TRI + strip_len*NUM_STRIPS*NUM_INDICES_QUAD); vset = s->GetVertexSet(); if (!water) vset->CreateAdditionalTexCoords(); ZeroMemory(vset->loc, nverts * sizeof(Vec3)); ZeroMemory(vset->diffuse, nverts * sizeof(DWORD)); ZeroMemory(vset->specular, nverts * sizeof(DWORD)); ZeroMemory(vset->tu, nverts * sizeof(float)); ZeroMemory(vset->tv, nverts * sizeof(float)); if (!water) { ZeroMemory(vset->tu1, nverts * sizeof(float)); ZeroMemory(vset->tv1, nverts * sizeof(float)); } ZeroMemory(vset->rw, nverts * sizeof(float)); // initialize vertices Vec3* pVert = vset->loc; float* pTu = vset->tu; float* pTv = vset->tv; float* pTu1 = vset->tu1; float* pTv1 = vset->tv1; DWORD* pSpec = vset->specular; int dscale = (PATCH_SIZE-1)/detail_size; double dt = 0.0625 / (ds1-1); // terrain texture scale double dtt = 2.0000 / (ds1-1); // tile texture scale double tu0 = (double) rect.x / rect.w / 16.0 + 1.0/16.0; double tv0 = (double) rect.y / rect.h / 16.0; // surface verts for (i = 0; i < ds1; i++) { for (j = 0; j < ds1; j++) { *pVert = Vec3((float) (j* scale * dscale - (HALF_PATCH_SIZE*scale)), (float) (heights[i*dscale*PATCH_SIZE + j*dscale]), (float) (i* scale * dscale - (HALF_PATCH_SIZE*scale))); if (level >= 2) { *pTu++ = (float) (-j*dtt); *pTv++ = (float) ( i*dtt); if (level >= 4 && !water) { *pTu1++ = (float) (-j*dtt*3); *pTv1++ = (float) ( i*dtt*3); } *pSpec++ = BlendValue(pVert->y); } else { *pTu++ = (float) (tu0 - j*dt); *pTv++ = (float) (tv0 + i*dt); } pVert++; } } if (!water) { // strip 1 & 2 verts for (i = 0; i < ds1; i += detail_size) { for (j = 0; j < ds1; j++) { Vec3 vl = Vec3((float) (j* scale * dscale - (HALF_PATCH_SIZE*scale)), (float) (heights[i*dscale*PATCH_SIZE + j*dscale]), (float) (i* scale * dscale - (HALF_PATCH_SIZE*scale))); *pVert++ = vl; DWORD blend = 0; if (level >= 2) { blend = BlendValue(vl.y); *pSpec++ = blend; *pTu++ = (float) (-j*dtt); *pTv++ = (float) ( i*dtt); } else { *pTu++ = (float) (tu0 - j*dt); *pTv++ = (float) (tv0 + i*dt); } vl.y = -5000.0f; *pVert++ = vl; if (level >= 2) { *pSpec++ = blend; *pTu++ = (float) (-j*dtt); *pTv++ = (float) ( i*dtt); } else { *pTu++ = (float) (tu0 - j*dt); *pTv++ = (float) (tv0 + i*dt); } } } // strip 3 & 4 verts for (j = 0; j < ds1; j += detail_size) { for (i = 0; i < ds1; i++) { Vec3 vl = Vec3((float) (j* scale * dscale - (HALF_PATCH_SIZE*scale)), (float) (heights[i*dscale*PATCH_SIZE + j*dscale]), (float) (i* scale * dscale - (HALF_PATCH_SIZE*scale))); *pVert++ = vl; DWORD blend = 0; if (level >= 2) { blend = BlendValue(vl.y); *pSpec++ = blend; *pTu++ = (float) (-j*dtt); *pTv++ = (float) ( i*dtt); } else { *pTu++ = (float) (tu0 - j*dt); *pTv++ = (float) (tv0 + i*dt); } vl.y = -5000.0f; *pVert++ = vl; if (level >= 2) { *pSpec++ = blend; *pTu++ = (float) (-j*dtt); *pTv++ = (float) ( i*dtt); } else { *pTu++ = (float) (tu0 - j*dt); *pTv++ = (float) (tv0 + i*dt); } } } } Material* m = materials.first(); // initialize the polys for (i = 0; i < npolys; i++) { Poly* p = s->GetPolys() + i; p->nverts = 3; p->vertex_set = vset; p->visible = 1; p->sortval = 0; p->material = m; if (level >= 2 && !water) { p->material = materials.at(1); p->sortval = 1; } } for (i = npolys; i < total; i++) { Poly* p = s->GetPolys() + i; p->nverts = 4; p->vertex_set = vset; p->visible = 1; p->sortval = 0; p->material = m; } int index = 0; // build main patch polys: for (i = 0; i < detail_size; i++) { for (j = 0; j < detail_size; j++) { int v[4] = { (ds1 * (i ) + (j )), (ds1 * (i ) + (j+1)), (ds1 * (i+1) + (j )), (ds1 * (i+1) + (j+1)) }; bisect(vset, v); // first triangle Poly* p = s->GetPolys() + index++; p->verts[0] = v[0]; p->verts[1] = v[1]; p->verts[2] = v[3]; if (level >= 2 && !water) { int layer = CalcLayer(p) + 1; p->material = materials.at(layer); p->sortval = layer; } // second triangle p = s->GetPolys() + index++; p->verts[0] = v[0]; p->verts[1] = v[3]; p->verts[2] = v[2]; if (level >= 2 && !water) { int layer = CalcLayer(p) + 1; p->material = materials.at(layer); p->sortval = layer; } } } // build vertical edge strip polys: if (!water) { for (i = 0; i < NUM_STRIPS; i++) { Poly* p = s->GetPolys() + npolys + i*strip_len; int base_index = ds1*ds1 + ds1*2*i; for (j = 0; j < strip_len; j++) { int v = base_index + j * 2; p->nverts = 4; if (i == 1 || i == 2) { p->verts[0] = v; p->verts[1] = v+2; p->verts[2] = v+3; p->verts[3] = v+1; } else { p->verts[0] = v; p->verts[1] = v+1; p->verts[2] = v+3; p->verts[3] = v+2; } if (level >= 2) { int layer = CalcLayer(p) + 1; p->material = materials.at(layer); p->sortval = layer; } p++; } } } // update the poly planes: for (i = 0; i < total; i++) { Poly* p = s->GetPolys() + i; Plane& plane = p->plane; WORD* v = p->verts; plane = Plane(vset->loc[v[0]] + loc, vset->loc[v[1]] + loc, vset->loc[v[2]] + loc); } s->Normalize(); // create continguous segments for each material: // sort the polys by material index: qsort((void*) s->GetPolys(), s->NumPolys(), sizeof(Poly), mcomp); // then assign them to cohesive segments: Segment* segment = 0; Poly* spolys = s->GetPolys(); for (int n = 0; n < s->NumPolys(); n++) { if (segment && segment->material == spolys[n].material) { segment->npolys++; } else { segment = 0; } if (!segment) { segment = new Segment; segment->npolys = 1; segment->polys = &spolys[n]; segment->material = segment->polys->material; segment->model = model; s->GetSegments().append(segment); } } Solid::EnableCollision(false); model->AddSurface(s); Solid::EnableCollision(true); // copy vertex normals: const Vec3B* tnorms = terrain->Normals(); for (i = 0; i < ds1; i++) { for (j = 0; j < ds1; j++) { if (water) { vset->nrm[i*ds1+j] = Point(0,1,0); } // blend adjacent normals: else if (dscale > 1) { Point normal; // but don't blend more than 16 normals per vertex: int step = 1; if (dscale > 4) step = dscale / 4; for (int dy = -dscale/2; dy < dscale/2; dy += step) { for (int dx = -dscale/2; dx < dscale/2; dx += step) { int ix = rect.x + (ds1-1-j)*dscale + dx; int iy = rect.y + i*dscale + dy; if (ix < 0) ix = 0; if (ix > terrain_width-1) ix = terrain_width-1; if (iy < 0) iy = 0; if (iy > terrain_width-1) iy = terrain_width-1; Vec3B vbn = tnorms[iy*terrain_width + ix]; normal += Point((128-vbn.x)/127.0, (vbn.z-128)/127.0, (vbn.y-128)/127.0); } } normal.Normalize(); vset->nrm[i*ds1+j] = normal; } // just copy the one normal: else { Vec3B vbn = tnorms[(rect.y + i*dscale)*terrain_width + (rect.x + (ds1-1-j) * dscale)]; Point normal = Point((128-vbn.x)/127.0, (vbn.z-128)/127.0, (vbn.y-128)/127.0); vset->nrm[i*ds1+j] = normal; } } } if (!water) { pVert = &vset->nrm[ds1*ds1]; // strip 1 & 2 verts for (i = 0; i < ds1; i += detail_size) { for (j = 0; j < ds1; j++) { Vec3 vn = vset->nrm[i*ds1 + j]; *pVert++ = vn; *pVert++ = vn; } } // strip 3 & 4 verts for (j = 0; j < ds1; j += detail_size) { for (i = 0; i < ds1; i++) { Vec3 vn = vset->nrm[i*ds1 + j]; *pVert++ = vn; *pVert++ = vn; } } } } if (level > max_detail) max_detail = level; return true; } // +--------------------------------------------------------------------+ DWORD TerrainPatch::BlendValue(double y) { if (terrain && y >= 0 && !water) { // find the proper layer: for (int i = 0; i < terrain->GetLayers().size(); i++) { TerrainLayer* layer = terrain->GetLayers().at(i); if (y >= layer->GetMinHeight() && y < layer->GetMaxHeight()) { double scale = (y-layer->GetMinHeight()) / (layer->GetMaxHeight()-layer->GetMinHeight()); if (scale < 0.2) scale = 0; else if (scale > 0.8) scale = 1; else scale = (scale - 0.2) / 0.6; if ((i & 1) == 0) { scale = 1 - scale; } DWORD val = (DWORD) (scale*255); return val << 24; } } } return 0; } int TerrainPatch::CalcLayer(Poly* poly) { if (terrain && poly) { if (water) return 0; double y = 1e6; for (int i = 0; i < poly->nverts; i++) { double h = poly->vertex_set->loc[ poly->verts[i] ].y; if (h >= 0 && h < y) { y = h; } } if (y <= terrain->GetLayers().first()->GetMinHeight()) return 0; for (int i = 0; i < terrain->GetLayers().size(); i++) { TerrainLayer* layer = terrain->GetLayers().at(i); if (y >= layer->GetMinHeight() && y < layer->GetMaxHeight()) { return i; } } } return -1; } // +--------------------------------------------------------------------+ void TerrainPatch::UpdateSurfaceWaves(Vec3& eyePos) { if (water && model && model->NumVerts() > 1) { Surface* s = model->GetSurfaces().first(); if (s) { VertexSet* vset = s->GetVertexSet(); for (int i = 0; i < vset->nverts; i++) vset->loc[i].y = 0.0f; water->UpdateSurface(eyePos, vset); } } } // +--------------------------------------------------------------------+ int TerrainPatch::CollidesWith(Graphic& o) { return 0; } // +--------------------------------------------------------------------+ void TerrainPatch::SelectDetail(Projector* projector) { // This is where all the work is done, // Delegate to the overall terrain to // compute a detail level for every patch: if (terrain) { terrain->SelectDetail(projector); } // Build detail levels on demand: if (detail_levels[ndetail] == 0) BuildDetailLevel(ndetail); } void TerrainPatch::SetDetailLevel(int nd) { if (nd >= 0 && nd <= max_detail) { if (ndetail != nd) DeletePrivateData(); ndetail = nd; // build detail levels on demand: if (detail_levels[ndetail] == 0) BuildDetailLevel(ndetail); model = detail_levels[ndetail]; if (water) water = terrain->GetWater(ndetail); } } // +--------------------------------------------------------------------+ void TerrainPatch::Illuminate(Color ambient, List& lights) { if (!model || model->NumVerts() < 1) return; Surface* s = model->GetSurfaces().first(); if (!s) return; illuminating = true; // clear the solid lights to ambient: VertexSet* vset = s->GetVertexSet(); int nverts = vset->nverts; DWORD aval = ambient.Value(); for (int i = 0; i < nverts; i++) { vset->diffuse[i] = aval; } TerrainRegion* trgn = terrain->GetRegion(); bool eclipsed = false; bool first = terrain->IsFirstPatch(this); if (trgn && !first) { eclipsed = trgn->IsEclipsed(); } // for sun and back lights: ListIter iter = lights; while (++iter) { Light* light = iter.value(); if (!light->IsDirectional()) // only do sun and continue; // back lights if (light->CastsShadow() && first) { eclipsed = light->Location().y < -100 || // has sun set, or scene->IsLightObscured(vset->loc[0], // is sun in eclipse light->Location(), // by orbital body radius); // such as a moon? } if (!light->CastsShadow() || !eclipsed) { Vec3 vl = light->Location(); vl.Normalize(); for (int i = 0; i < nverts; i++) { Vec3& nrm = vset->nrm[i]; double val = 0; double gain = vl * nrm; if (gain > 0) { val = light->Intensity() * (0.85 * gain); if (val > 1) val = 1; } if (val > 0.01) vset->diffuse[i] = ((light->GetColor().dim(val)) + vset->diffuse[i]).Value(); } } } // combine blend weights: if (ndetail >= 2) { for (int i = 0; i < nverts; i++) { vset->diffuse[i] = vset->specular[i] | (vset->diffuse[i] & 0x00ffffff); } } if (trgn && first) { trgn->SetEclipsed(eclipsed); } InvalidateSurfaceData(); illuminating = false; } // +--------------------------------------------------------------------+ void TerrainPatch::Render(Video* video, DWORD flags) { if (max_height < 0) return; if (flags & RENDER_ADDITIVE) return; if (!model) model = detail_levels[0]; if (scene) { /*** *** TWO PASS LIGHTING FOR TERRAIN SHADOWS DOESN'T *** WORK - IT MESSES UP THE HARDWARE FOG CALCS *** *** PLUS, IT'S INCREDIBLY SLOW!!! ***/ if ((flags & RENDER_ADD_LIGHT) != 0) return; if (water) { Vec3 vec(0, 0, 0); UpdateSurfaceWaves(vec); Illuminate(scene->Ambient(), scene->Lights()); } else { Illuminate(scene->Ambient(), scene->Lights()); } } else { if ((flags & RENDER_ADD_LIGHT) != 0) return; } Bitmap* details[16]; ZeroMemory(details, sizeof(details)); if (ndetail < 3) { for (int i = 0; i < 16 && i < materials.size(); i++) { Material* mtl = materials[i]; if (mtl->tex_detail) { details[i] = mtl->tex_detail; mtl->tex_detail = 0; } } } double visibility = terrain->GetRegion()->GetWeather().Visibility(); FLOAT fog_density = (FLOAT) (terrain->GetRegion()->FogDensity() * 2.5e-5 * 1/visibility); video->SetRenderState(Video::LIGHTING_ENABLE, false); video->SetRenderState(Video::SPECULAR_ENABLE, false); //water != 0); video->SetRenderState(Video::FOG_ENABLE, true); video->SetRenderState(Video::FOG_COLOR, terrain->GetRegion()->FogColor().Value()); video->SetRenderState(Video::FOG_DENSITY, *((DWORD*) &fog_density)); // Too bad this doesn't work right. But it makes the // ground mostly disappear. :-( // //video->SetRenderState(Video::Z_BIAS, -2); Solid::Render(video, flags); video->SetRenderState(Video::LIGHTING_ENABLE, true); video->SetRenderState(Video::SPECULAR_ENABLE, true); video->SetRenderState(Video::FOG_ENABLE, false); //video->SetRenderState(Video::Z_BIAS, 0); if (ndetail < 3) { for (int i = 0; i < 16 && i < materials.size(); i++) { Material* mtl = materials[i]; if (details[i] && !mtl->tex_detail) { mtl->tex_detail = details[i]; } } } } // +--------------------------------------------------------------------+ int TerrainPatch::CheckRayIntersection(Point obj_pos, Point dir, double len, Point& ipt, bool ttpas) { Point light_pos = obj_pos + dir * len; int impact = light_pos.y < -100; // Special case for illumination - // just check if sun is above or below the horizon: if (illuminating || impact) { return impact; } if (obj_pos.x != 0 || obj_pos.y != 0 || obj_pos.z != 0) { return impact; } // the rest of this code is only used for eclipsing // the solar lens flare: // check right angle spherical distance: Point d0 = loc; Point d1 = d0.cross(dir); double dlen = d1.length(); // distance of point from line if (dlen > radius) // clean miss return 0; // (no impact) // make sure some part of this patch falls between // the camera and the sun: Point closest = loc + dir * radius; if (closest * dir < 0) return 0; // probable hit at this point... // test for polygon intersection: if (!model) return 0; Surface* s = model->GetSurfaces().first(); if (!s) return 0; // transform ray into object space: Matrix xform(Orientation()); Vec3 tmp = dir; dir.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2)); dir.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2)); dir.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2)); tmp = obj_pos-loc; obj_pos.x = tmp * Vec3(xform(0,0), xform(0,1), xform(0,2)); obj_pos.y = tmp * Vec3(xform(1,0), xform(1,1), xform(1,2)); obj_pos.z = tmp * Vec3(xform(2,0), xform(2,1), xform(2,2)); double min = 2 * len; // check each polygon: Poly* p = s->GetPolys(); for (int i = 0; i < s->NumPolys(); i++) { Point v = p->plane.normal; double d = p->plane.distance; double denom = dir*v; if (denom < -1.0e-5) { Point P = v * d; double ilen = ((P-obj_pos)*v)/denom; if (ilen > 0 && ilen < min) { Point intersect = obj_pos + dir * ilen; if (p->Contains(intersect)) { ipt = intersect; min = ilen; impact = 1; } } } p++; } // xform impact point back into world coordinates: if (impact) { ipt = (ipt * Orientation()) + loc; } return impact; } // +--------------------------------------------------------------------+ double TerrainPatch::Height(double x, double z) const { if (water) return base; double height = 0; x /= scale; z /= scale; int x0 = (int) x; int z0 = (int) z; if (x0 >= 0 && x0 < PATCH_SIZE && z0 >= 0 && z0 < PATCH_SIZE) { double dx = x-x0; double dz = z-z0; double h[4]; h[0] = heights[(z0*PATCH_SIZE + x0)]; h[1] = heights[((z0+1)*PATCH_SIZE + x0)]; h[2] = heights[(z0*PATCH_SIZE + x0+1)]; h[3] = heights[((z0+1)*PATCH_SIZE + x0+1)]; // if level, just return height of one vertex: if (h[0] == h[1] && h[1] == h[2] && h[2] == h[3]) { height = h[0]; } // if sloped, interpolate between vertex heights: else { double hl = h[0]*(1-dz) + h[1]*dz; double hr = h[2]*(1-dz) + h[3]*dz; height = hl*(1-dx) + hr*dx + 5; // fudge } } if (height < 0) height = 0; return height; }