/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "core/camera.h" #include "core/terrain.h" #include "core/geometry.h" #include "core/resource_factories.h" #include "core/render_resource_factory.h" #include "picture/pict.h" #include "picture/pict_io.h" #include "filesystem/filesystem.h" #include "log/log.h" using namespace GS; using namespace GS::Core; //------------------------------------------------------------------------------ void Terrain::RenderSetup(ResourceFactories *f) { if (LoadHeightmap(heightmap_path)) { ComputeNormals(); BuildQuadtree(); } ComputeNormals(); BuildQuadtree(); if (render_data = new RenderData) if (f && f->render) { render_data->blendmap = f->render->LoadTexture(blendmap_path); render_data->material = f->render->LoadMaterial(material); for (uint n = 0; n < 4; ++n) { render_data->layer[n].diffuse = f->render->LoadTexture(layer[n].diffuse); render_data->layer[n].specular = f->render->LoadTexture(layer[n].specular); render_data->layer[n].normal = f->render->LoadTexture(layer[n].normal); render_data->layer[n].self = f->render->LoadTexture(layer[n].self); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Terrain::LocalToTexture(const Vector4 &p, float w, float h, float &u, float &v) const { float lx = p.x + width * 0.5f, lz = p.z + depth * 0.5f; u = (lx / width) * w; v = (lz / depth) * h; return asbool((u >= 0) && (u < w) && (v >= 0) && (v < h)); } bool Terrain::LocalToHeightmap(const Vector4 &p, float &u, float &v) const { return LocalToTexture(p, (float)heightmap_w, (float)heightmap_h, u, v); } float Terrain::SampleHeight(float u, float v) const { // Fetch samples coordinates. int s_u[4], s_v[4]; s_u[0] = Types::Clamp(int(u), 0, heightmap_w); s_v[0] = Types::Clamp(int(v), 0, heightmap_h); s_u[1] = Types::Clamp(s_u[0] + 1, 0, heightmap_w); s_v[1] = Types::Clamp(s_v[0] + 0, 0, heightmap_h); s_u[2] = Types::Clamp(s_u[0] + 0, 0, heightmap_w); s_v[2] = Types::Clamp(s_v[0] + 1, 0, heightmap_h); s_u[3] = Types::Clamp(s_u[0] + 1, 0, heightmap_w); s_v[3] = Types::Clamp(s_v[0] + 1, 0, heightmap_h); // Fetch samples. float s[4]; for (int n = 0; n < 4; ++n) s[n] = heightmap[s_v[n] * GetHeightmapPitch() + s_u[n]]; // Bilinear. float k_u = u - int(u), k_v = v - (int)v; return (s[0] * (1 - k_u) + s[1] * k_u) * (1 - k_v) + (s[2] * (1 - k_u) + s[3] * k_u) * k_v; } float Terrain::SampleHeight(const Vector4 &p) const { float u, v; if (!LocalToHeightmap(p, u, v)) return 0; return SampleHeight(u, v); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Terrain::ComputeRenderableMinMax(MinMax &mm) { if (root_node) { OBB obb(root_node->minmax); obb.Transform(GetMatrix()); obb.ComputeMinMax(mm); } } void Terrain::CullRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack &list, Context context, Patch *node) { MinMax &minmax = node->minmax; if (view.frustum.ClassifyMinMax(minmax, &GetMatrix()) == Frustum::Outside) return; // Check error for the current level. Vector4 center = minmax.GetCenter() * GetMatrix(); float lod_d = (minmax.mx - minmax.mn).Len() * 1.5f, /*v_d = nVector::Dist(center, view.GetMatrix().GetRow(3)),*/ d_d = Vector4::Dist(center, default_view.GetMatrix().GetRow(3)); float d = d_d;// Types::Min(v_d, d_d); // Ensure high-resolution close to the viewer and the light source. if ((d > lod_d) || !node->children[0]) list.Push(new Render::Primitive(node, this, 0)); else for (int n = 0; n < 4; ++n) if (node->children[n]) CullRenderablePrimitiveList(view, default_view, list, context, node->children[n]); } uint Terrain::GetRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack &list, Context context, bool nUnused(cull)) { if (root_node) CullRenderablePrimitiveList(view, default_view, list, context, root_node); return node_count; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Geometry *Terrain::CreateNodeGeometry(const Patch *node) { Geometry *geometry = new Geometry; int patch_w = node->w / node->decimation, patch_h = node->h / node->decimation; // Create vertices. geometry->vtx.Allocate((patch_w + 1) * (patch_h + 1)); float step_w = width / heightmap_w, step_h = depth / heightmap_h; Vector4 p(node->u * step_w * GetUnit(), 0, -node->v * step_h * GetUnit()), *pv = geometry->vtx; for (int _v = node->v; _v <= (node->v + node->h); _v += node->decimation) { float *ph = &heightmap[_v * heightmap_w + node->u]; for (int _u = node->u; _u <= (node->u + node->w); _u += node->decimation) { pv->Set(p.x, ph[0], p.z); pv++; ph += node->decimation; p.x += step_w * node->decimation * GetUnit(); } p.x = node->u * step_w * GetUnit(); p.z += step_h * node->decimation * GetUnit(); } // Create polygons. geometry->AllocatePolygon(patch_w * patch_h); for (uint n = 0; n < geometry->pol.GetCount(); ++n) { geometry->pol[n].vtx_count = 4; geometry->pol[n].material = 0; } geometry->AllocatePolygonBinding(); Polygon *polygon = geometry->pol; for (int _v = 0; _v < patch_h; ++_v) for (int _u = 0; _u < patch_w; ++_u) { polygon->vtx_count = 4; polygon->material = 0; polygon->binding[0] = _v * (patch_w + 1) + _u; polygon->binding[1] = _v * (patch_w + 1) + _u + 1; polygon->binding[2] = (_v + 1) * (patch_w + 1) + _u + 1; polygon->binding[3] = (_v + 1) * (patch_w + 1) + _u; polygon++; } geometry->ComputeVertexNormal(); return geometry; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Terrain::UpdatePatchMinMax(Patch *node) const { float *ph = &heightmap[node->v * GetHeightmapPitch() + node->u]; float min_height, max_height; min_height = max_height = ph[0]; for (int _v = 0; _v < node->h; _v += node->decimation) { for (int _u = 0; _u < node->w; _u += node->decimation) { if (ph[_u] < min_height) min_height = ph[_u]; if (ph[_u] > max_height) max_height = ph[_u]; } ph += GetHeightmapPitch() * node->decimation; } node->minmax.mn.Set(node->u * GetUnit() - width * 0.5f, min_height, (node->v + node->h) * GetUnit() - depth * 0.5f); node->minmax.mx.Set((node->u + node->w) * GetUnit() - width * 0.5f, max_height, node->v * GetUnit() - depth * 0.5f); } Patch *Terrain::BuildTerrainStaticQuadtree(int u, int v, int w, int h, int decimation, int tree_depth) { if (!decimation || (tree_depth == 8)) return NULL; if (!w || !h) return NULL; // Create a new node. Patch *node = new Patch; node->terrain = this; node->u = u; node->v = v; node->w = w; node->h = h; node->decimation = decimation; // Create node geometry (helper function, should normally be done on the fly by the renderer). // CreateNodeGeometry(node); // Update patch minmax. UpdatePatchMinMax(node); // Split to create children. int hw = w / 2, hh = h / 2; node->children[0] = BuildTerrainStaticQuadtree(node->u, node->v, hw, hh, decimation / 2, tree_depth + 1); node->children[1] = BuildTerrainStaticQuadtree(node->u + hw, node->v, node->w - hw, hh, decimation / 2, tree_depth + 1); node->children[2] = BuildTerrainStaticQuadtree(node->u, node->v + hh, hw, node->h - hh, decimation / 2, tree_depth + 1); node->children[3] = BuildTerrainStaticQuadtree(node->u + hw, node->v + hh, node->w - hw, node->h - hh, decimation / 2, tree_depth + 1); node_count++; return node; } Patch *Terrain::BuildQuadtree() { node_count = 0; return root_node = BuildTerrainStaticQuadtree(0, 0, heightmap_w, heightmap_h, heightmap_w / 64, 0); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Terrain::UpdateQuadtreePatch(Patch *patch, iRect &update_rect) { iRect patch_rect = patch->GetRect(); if (!patch_rect.Intersect(update_rect)) return; UpdatePatchMinMax(patch); for (int n = 0; n < 4; ++n) if (patch->children[n]) UpdateQuadtreePatch(patch->children[n], update_rect); } void Terrain::UpdateQuadtree(int u, int v, int w, int h) { iRect update_rect(u, v, u + w, v + h); UpdateQuadtreePatch(root_node, update_rect); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Terrain::ComputeNormals(int u, int v, int w, int h) { if (!attribmap) return; if (!u && !v && !w && !h) { w = heightmap_w; h = heightmap_h; } u = Types::Clamp(u, 0, heightmap_w); v = Types::Clamp(v, 0, heightmap_h); if (u + w > heightmap_w) w = heightmap_w - u; if (v + h > heightmap_h) h = heightmap_h - v; float *ph = &heightmap[v * GetHeightmapPitch() + u]; Attrib *pv = &attribmap[v * GetHeightmapPitch() + u]; for (int _v = 0; _v < h; ++_v) { float *sh = ph; Attrib *sv = pv; for (int _u = 0; _u < w; ++_u) { Vector4 vu(unit, sh[1] - sh[0], 0), vv(0, sh[GetHeightmapPitch()] - sh[0], unit), n = vv.Normalized().Cross(vu.Normalized()); sv[0].nx = (char)(n.x * 127.f); sv[0].ny = (char)(n.y * 127.f); sv[0].nz = (char)(n.z * 127.f); ++sh; ++sv; } pv += GetHeightmapPitch(); ph += GetHeightmapPitch(); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Terrain::Allocate(uint w_res, uint h_res, float _unit) { Free(); // Global heightmap. heightmap_w = w_res; heightmap_h = h_res; if (!attribmap.Allocate((heightmap_w + 1) * (heightmap_h + 1)) || !heightmap.Allocate((heightmap_w + 1) * (heightmap_h + 1))) __ERR__(__LOG_E__ << "Failed to allocate terrain heightmap.\n", false) Memory::Set(&attribmap[0], 0, (heightmap_w + 1) * (heightmap_h + 1) * sizeof(Attrib)); Memory::Set(&heightmap[0], 0, sizeof(float) * (heightmap_w + 1) * (heightmap_h + 1)); width = w_res * _unit; depth = h_res * _unit; unit = _unit; return true; } void Terrain::Free() { width = 0; depth = 0; attribmap.Free(); heightmap.Free(); heightmap_w = 0; heightmap_h = 0; node_count = 0; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool Terrain::Raytrace(const Vector4 &w_s, const Vector4 &w_d, TraceResult &trace, float len) { trace.has_hit = false; // Move ray to terrain space. Vector4 s = w_s * GetInverseMatrix(), d = w_d * GetRotationMatrix().Transposed(); // Clip ray against the terrain bounding box. float tmin, tmax; if (!root_node->minmax.IntersectRay(s, d, tmin, tmax)) return false; tmin -= 0.1f; tmax += 0.1f; Vector4 e = s + d * tmax; if (tmin > 0) s += d * tmin; // Walk along the ray, looking for an intersection point. bool side = SampleHeight(s) < s.y; Vector4 dt = e - s; float max_dist = dt.Len(); dt /= max_dist; dt *= 1.f; // minimum step size is 1 meters (so as not to spend too much time stepping) float dt_len = dt.Len(); float dist = 0.f; for (Vector4 p = s + dt; dist < max_dist; p += dt) { if ((SampleHeight(p) < p.y) != side) { #if 1 // [EJ] Extra precision at terrain crossing boundary. s = p - dt; for (int n = 0; n < 24; ++n) { Vector4 m = (s + p) * 0.5f; if ((SampleHeight(m) < m.y) != side) p = m; else s = m; } #endif // Final hit. trace.has_hit = true; trace.w_i = p * GetMatrix(); LocalToHeightmap(p, trace.uv.x, trace.uv.y); return true; } dist += dt_len; } return false; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void Terrain::ApplyBlur(int pass_count) { if (heightmap) for (int p = 0; p < pass_count; ++p) { int n = (heightmap_w + 1) + 1; for (int v = 1; v < heightmap_h; ++v) { for (int u = 1; u < heightmap_w; ++u) { heightmap[n] = (heightmap[n] + heightmap[n - 1] + heightmap[n - (heightmap_w + 1)] + heightmap[n + 1] + heightmap[n + (heightmap_w + 1)]) / 5.f; ++n; } n += 2; } } } bool Terrain::FromPicture(const char *path, int blur_pass_count) { // Initial values. Picture pic; PictureIO::Get().Load(pic, path); int n = 0; for (int v = 0; v < (heightmap_h + 1); ++v) for (int u = 0; u < (heightmap_w + 1); ++u) heightmap[n++] = pic.SampleColor((float)u / heightmap_w, (float)v / heightmap_h).x * 16.f; ApplyBlur(blur_pass_count); return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Terrain::Attrib *Terrain::GetAttributesMapAt(int u, int v) const { return &attribmap[(heightmap_w + 1) * v + u]; } Vector4 Terrain::GetNormalAt(int u, int v) const { Attrib *n = GetAttributesMapAt(u, v); return Vector4((float)n->nx / 127.f, (float)n->ny / 127.f, (float)n->nz / 127.f); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Terrain::Terrain() { width = 0; depth = 0; heightmap_w = 0; heightmap_h = 0; node_count = 0; for (uint n = 1; n < 4; ++n) layer[n].enabled = false; } Terrain::~Terrain() { Free(); } //------------------------------------------------------------------------------