diff options
Diffstat (limited to 'Source/Graphics/terrain.cpp')
-rw-r--r-- | Source/Graphics/terrain.cpp | 1061 |
1 files changed, 1061 insertions, 0 deletions
diff --git a/Source/Graphics/terrain.cpp b/Source/Graphics/terrain.cpp new file mode 100644 index 00000000..d1b84e7f --- /dev/null +++ b/Source/Graphics/terrain.cpp @@ -0,0 +1,1061 @@ +//----------------------------------------------------------------------------- +// Name: terrain.cpp +// Developer: Wolfire Games LLC +// Description: +// License: Read below +//----------------------------------------------------------------------------- +// +// +// Copyright 2022 Wolfire Games LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------------- +#include "terrain.h" + +#include <Graphics/textures.h> +#include <Graphics/camera.h> +#include <Graphics/graphics.h> +#include <Graphics/geometry.h> +#include <Graphics/bytecolor.h> +#include <Graphics/shaders.h> +#include <Graphics/sky.h> +#include <Graphics/models.h> +#include <Graphics/ColorWheel.h> +#include <Graphics/pxdebugdraw.h> + +#include <Internal/common.h> +#include <Internal/checksum.h> +#include <Internal/collisiondetection.h> +#include <Internal/filesystem.h> + +#include <Images/texture_data.h> +#include <Images/image_export.hpp> + +#include <Math/triangle.h> +#include <Math/vec3math.h> + +#include <Timing/timingevent.h> +#include <Timing/intel_gl_perf.h> + +#include <Main/scenegraph.h> +#include <Main/engine.h> + +#include <Physics/physics.h> +#include <Compat/fileio.h> +#include <Logging/logdata.h> +#include <Memory/stack_allocator.h> +#include <Asset/Asset/averagecolorasset.h> +#include <GUI/widgetframework.h> +#include <Memory/allocation.h> + +#include <cfloat> +#include <set> +#include <algorithm> +#include <cmath> + +extern SceneLight* primary_light; + +static const uint16_t _terrain_cache_file_version_number = 27; +static const float kHeightMapHeight = 140.0f; +static const float kUniformScale = 2.0f; +static const float kVerticalScale = 0.1f; + + +Terrain::Terrain(): + model_id(-1), + framebuffer(INVALID_FRAMEBUFFER), + shader("secondterrain") +{} + +//Dispose of terrain +Terrain::~Terrain() { + Dispose(); +} + +void Terrain::Dispose() { + LOGI << "Disposing of terrain" << std::endl; + terrain_patches.clear(); + LOGI << "Clearing color path" << std::endl; + color_path.clear(); + LOGI << "Clearing detail texture ref" << std::endl; + detail_texture_ref.clear(); + LOGI << "Clearing detail normal texture ref" << std::endl; + detail_normal_texture_ref.clear(); + LOGI << "Clearing detail texture color" << std::endl; + detail_texture_color.clear(); + LOGI << "Clearing texture color srgb" << std::endl; + detail_texture_color_srgb.clear(); + LOGI << "Clearing detail maps" << std::endl; + detail_maps_info.clear(); + LOGI << "Clearing detail object surfaces" << std::endl; + for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin(); + iter != detail_object_surfaces.end(); ++iter) + { + delete (*iter); + } + detail_object_surfaces.clear(); + detail_maps_info.clear(); + LOGI << "Done with Dispose()" << std::endl; +} + +void Terrain::GLInit(Sky* sky) { + if(minimal) + return; + + // Create framebuffer object + Graphics* graphics = Graphics::Instance(); + if(framebuffer == INVALID_FRAMEBUFFER){ + graphics->PushFramebuffer(); + graphics->genFramebuffers(&framebuffer, "sky"); + graphics->bindFramebuffer(framebuffer); + graphics->PopFramebuffer(); + } + if(!normal_map_ref.valid()) { + CHECK_GL_ERROR(); + + // Create lighting textures + Textures* textures = Textures::Instance(); + baked_texture_ref = textures->makeTexture(terrain_texture_size,terrain_texture_size,GL_RGBA,GL_RGBA,true); + textures->SetTextureName(baked_texture_ref, "Terrain Baked Texture"); + + // Create skybox without terrain + sky->BakeFirstPass(); + + { // Draw to texture + + CHECK_GL_ERROR(); + // Get normal map write path + std::string normal_map_path = heightmap_.path() + "_normal.png"; + std::string w_nmp = GetWritePath(heightmap_.modsource_)+normal_map_path; + if(GetDateModifiedInt64(w_nmp.c_str()) >= GetDateModifiedInt64(normal_map_path.c_str())){ + normal_map_path = w_nmp; + } + + // Bake normal map + FILE *test_file = my_fopen(normal_map_path.c_str(), "rb"); + if(test_file){ + fclose(test_file); + } else { + float res_scale = 512.0f/heightmap_.width(); + int size = heightmap_.width(); + std::vector<unsigned char> color(size*size*4); + vec3 points[5]; + vec3 normals[4]; + vec3 normal; + for(int j=0; j<size; ++j){ + for(int i=0; i<size; ++i){ + for(int k=0; k<5; ++k){ + points[k][0] = (float)(i); + points[k][2] = (float)(j); + switch(k){ + case 0: break; + case 1: points[k][2] -= 1.0f; break; + case 2: points[k][0] += 1.0f; break; + case 3: points[k][2] += 1.0f; break; + case 4: points[k][0] -= 1.0f; break; + } + points[k][1] = heightmap_.GetHeight((int)points[k][0],(int)points[k][2])*kVerticalScale / res_scale; + } + + for(int k=0; k<4; ++k){ + normals[k] = cross(normalize(points[(k+1)%4+1] - points[0]), normalize(points[k+1]-points[0])); + } + + normal = normalize(normals[0] + normals[1] + normals[2] + normals[3]); + + color[j*size*4+i*4+0] = (unsigned char)((normal[2]+1.0f)*0.5f*255.0f); + color[j*size*4+i*4+1] = (unsigned char)((normal[1]+1.0f)*0.5f*255.0f); + color[j*size*4+i*4+2] = (unsigned char)((normal[0]+1.0f)*0.5f*255.0f); + color[j*size*4+i*4+3] = 255; + } + } + ImageExport::SavePNG(normal_map_path.c_str(), &color[0], size, size); + } + normal_map_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(heightmap_.path() + "_normal.png"); + + // Get lighting texture write path + std::string baked_map_path = heightmap_.path() + "_" + level_name + "_baked.png"; + std::string w_bmp = GetWritePath(heightmap_.modsource_)+baked_map_path; + if(GetDateModifiedInt64(w_bmp.c_str()) >= GetDateModifiedInt64(baked_map_path.c_str())){ + baked_map_path = w_bmp; + } + + test_file = my_fopen(baked_map_path.c_str(), "rb"); + if(test_file){ // Load texture if already baked + fclose(test_file); + baked_texture_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(heightmap_.path() + "_" + level_name + "_baked.png", PX_SRGB, 0x0); + baked_texture_ref = baked_texture_asset->GetTextureRef(); + } else { // Otherwise calculate it + BakeTerrainTexture(framebuffer, sky->GetSpecularCubeMapTexture()); + } + CHECK_GL_ERROR(); + } + + sky->BakeSecondPass(&baked_texture_ref); + heightmap_.LoadData(heightmap_.path(), HeightmapImage::DOWNSAMPLED); + for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin(); + iter != detail_object_surfaces.end(); ++iter) + { + (*iter)->SetBaseTextures(color_texture_ref, normal_map_ref); + } + CHECK_GL_ERROR(); + } +} + +//Draw terrain +bool TriangleSquareIntersection2D(vec3 &min, vec3 &max, vec3 *tri_point[3]); +bool PointInTriangle(vec3 &point, vec3 *tri_point[3]); +void Terrain::drawLayer(int which) { + if(which==0){ + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + terrain_simplified_model.Draw(); + } +} + +int Terrain::lineCheck(const vec3& start, const vec3& end, vec3* point, vec3 *normal) { + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + return terrain_simplified_model.lineCheckNoBackface(start, end, point, normal); +} + +const int _down_sample = 1; + +void Terrain::CalculateHighResVertices(Model &terrain_high_detail_model) { + float res_scale = 512.0f/heightmap_.width(); + int size = heightmap_.width(); + int down_sample_size = size/_down_sample; + int num_verts = size*size/_down_sample/_down_sample; + terrain_high_detail_model.vertices.resize(num_verts*3); + #pragma omp parallel for + for(int i=0; i<down_sample_size; i++){ + int index; + int fixed_i; + int fixed_j; + for(int j=0; j<down_sample_size; j++) + { + fixed_i = i*_down_sample; + fixed_j = j*_down_sample; + index = (i*down_sample_size + j)*3; + terrain_high_detail_model.vertices[index+0] = (fixed_i - size/2) * kUniformScale * res_scale; + terrain_high_detail_model.vertices[index+1] = heightmap_.GetHeight(fixed_i, fixed_j) * kVerticalScale * kUniformScale; + terrain_high_detail_model.vertices[index+2] = (fixed_j - size/2) * kUniformScale * res_scale; + } + } +} + +void Terrain::CalculateHighResFaces(Model &terrain_high_detail_model) { + int size = heightmap_.width(); + int down_sample_size = size/_down_sample; + //terrain_high_detail_model.ResizeFaces((down_sample_size-1)*(down_sample_size-1)*2); + int num_faces = (down_sample_size-1)*(down_sample_size-1)*2; + terrain_high_detail_model.faces.resize(num_faces*3); + #pragma omp parallel for + for(int i=0; i<down_sample_size-1; i++){ + int index; + for(int j=0; j<down_sample_size-1; j++) { + index = (i*(down_sample_size-1)+j)*6; + terrain_high_detail_model.faces[index+0] = i + j*down_sample_size; + terrain_high_detail_model.faces[index+1] = i+1 + j*down_sample_size; + terrain_high_detail_model.faces[index+2] = i+1 + (j+1)*down_sample_size; + terrain_high_detail_model.faces[index+3] = i + j*down_sample_size; + terrain_high_detail_model.faces[index+4] = i+1 + (j+1)*down_sample_size; + terrain_high_detail_model.faces[index+5] = i + (j+1)*down_sample_size; + } + } +} + +bool Terrain::LoadCachedSimplifiedTerrain() { + bool success = false; + bool rewrite_cache = false; + + char cache_rel_path[kPathSize]; + FormatString(cache_rel_path, kPathSize, "%s.cache", heightmap_.path().c_str()); + + char uv2_rel_path[kPathSize]; + FormatString(uv2_rel_path, kPathSize, "%s.obj_UV2", heightmap_.path().c_str()); + + char abs_uv2_path[kPathSize]; + //bool found_uv2 = false; + unsigned short uv2_checksum = 0; + if(FindFilePath(uv2_rel_path, abs_uv2_path, kPathSize, kDataPaths | kModPaths, false, NULL) == 0){ + //found_uv2 = true; + uv2_checksum = Checksum(abs_uv2_path); + } + + const int kMaxPaths = 5; + char abs_cache_paths[kPathSize * kMaxPaths]; + int num_paths_found = FindFilePaths(cache_rel_path, abs_cache_paths, kPathSize, kMaxPaths, kAnyPath, true, NULL, NULL ); + + if(num_paths_found > 0){ + for(int path_index=0; path_index<num_paths_found; ++path_index){ + char* curr_path = &abs_cache_paths[kPathSize * path_index]; + FILE* cache_file = my_fopen(curr_path, "rb"); + if(cache_file) { // bug: sometimes cache_file is not null when fopen fails + uint16_t version; + fread(&version, sizeof(version), 1, cache_file); + if(version == _terrain_cache_file_version_number) { + uint16_t checksum = 0; + fread(&checksum, sizeof(checksum), 1, cache_file); + if(checksum == heightmap_.checksum()) { + uint16_t uv2_checksum_read = 0; + fread(&uv2_checksum_read, sizeof(uv2_checksum_read), 1, cache_file); + if(uv2_checksum_read == uv2_checksum){ + AddLoadingText("Loading cached terrain..."); + if(model_id == -1){ + model_id = Models::Instance()->AddModel(); + } + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + terrain_simplified_model.Dispose(); + terrain_simplified_model.ReadFromFile(cache_file); + terrain_simplified_model.calcBoundingBox(); + terrain_simplified_model.calcBoundingSphere(); // TO DO: This should be cached + terrain_simplified_model.vbo_enabled = true; + success = true; + LOGI << "Loaded cached terrain: \"" << cache_rel_path << "\"" << std::endl; + } + } + } + fclose(cache_file); + } + if(success){ + break; + } + } + } + return success; +} + +void TextureTerrainModel(Model &model, float size) { + model.tex_coords.resize(model.vertices.size()); + model.tex_coords2.resize(model.vertices.size()); + for(int i=0, len=model.vertices.size()/3; i<len; i++){ + model.tex_coords[i*2+0] = model.vertices[i*3+0]/size+0.5f; + model.tex_coords[i*2+1] = model.vertices[i*3+2]/size+0.5f; + model.tex_coords2[i*2+0] = model.vertices[i*3+0]; + model.tex_coords2[i*2+1] = model.vertices[i*3+2]; + } +} + +void RemoveDoubledTriangles(Model *model) { + // Used to find faces that share all three vertices + std::map<int,std::map<int,std::map<int,std::set<int> > > > face_vertex_map; + + // Stores which faces each vertex is part of + std::map<int,std::set<int> > vertex_connections; + + // Mark duplicate triangles as possibly bad + std::vector<int> possible_bad_faces; + std::vector<int> face_vertices(3); + for(int i=0, len=model->faces.size()/3; i<len; i++){ + face_vertices[0] = model->faces[i*3+0]; + face_vertices[1] = model->faces[i*3+1]; + face_vertices[2] = model->faces[i*3+2]; + vertex_connections[face_vertices[0]].insert(i); + vertex_connections[face_vertices[1]].insert(i); + vertex_connections[face_vertices[2]].insert(i); + std::sort(face_vertices.begin(), face_vertices.end()); + if(face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].size()==1) { + possible_bad_faces.push_back(*(face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].begin())); + } + if(!face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].empty()) { + possible_bad_faces.push_back(i); + } + face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].insert(i); + } + + // If possibly-bad triangles have one vertex that is only shared by two triangles, + // mark as bad + std::vector<int> bad_faces; + for(unsigned int i=0; i<possible_bad_faces.size(); i++){ + if(vertex_connections[model->faces[i*3+0]].size()<=2||vertex_connections[model->faces[i*3+1]].size()<=2||vertex_connections[model->faces[i*3+2]].size()<=2) { + bad_faces.push_back(possible_bad_faces[i]); + } + } + + // Mark triangles with normals facing down as bad + for(unsigned i=0; i<possible_bad_faces.size(); i++){ + if(model->face_normals[possible_bad_faces[i]][1]<0){ + bad_faces.push_back(possible_bad_faces[i]); + } + } + + for(unsigned i=0; i<bad_faces.size(); i++){ + model->faces[bad_faces[i]*3+0] = 0; + model->faces[bad_faces[i]*3+1] = 0; + model->faces[bad_faces[i]*3+2] = 0; + } +} + +void Terrain::CalculateSimplifiedTerrain() { + //Model terrain_high_detail_model; + if(model_id == -1){ + model_id = Models::Instance()->AddModel(); + } + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + CalculateHighResVertices(terrain_simplified_model); + CalculateHighResFaces(terrain_simplified_model); + /*{ + terrain_simplified_model.tex_coords.resize(terrain_simplified_model.vertices.size()/3*2); + float size = (float)m_heightmap.width(); + for(int i=0, len=terrain_simplified_model.vertices.size()/3; i<len; i++){ + terrain_simplified_model.tex_coords[i*2+0] = terrain_simplified_model.vertices[i*3+0]/size+0.5f; + terrain_simplified_model.tex_coords[i*2+1] = terrain_simplified_model.vertices[i*3+2]/size+0.5f; + } + std::fstream file; + file.open(GetWritePath("terrain_high.obj").c_str(), std::fstream::out); + for(unsigned i=0; i<terrain_simplified_model.vertices.size(); i+=3){ + file << "v " << terrain_simplified_model.vertices[i+0] << " " + << terrain_simplified_model.vertices[i+1] << " " + << terrain_simplified_model.vertices[i+2] << "\n"; + } + for(unsigned i=0; i<terrain_simplified_model.tex_coords.size(); i+=2){ + file << "vt " << terrain_simplified_model.tex_coords[i+0] << " " + << terrain_simplified_model.tex_coords[i+1] << "\n"; + } + for(unsigned i=0; i<terrain_simplified_model.faces.size(); i+=3){ + file << "f " << terrain_simplified_model.faces[i+0]+1 << "/" + << terrain_simplified_model.faces[i+0]+1 << " " + << terrain_simplified_model.faces[i+1]+1 << "/" + << terrain_simplified_model.faces[i+1]+1 << " " + << terrain_simplified_model.faces[i+2]+1 << "/" + << terrain_simplified_model.faces[i+2]+1 << "\n"; + } + file.close(); + }*/ +#ifndef _DEPLOY + AddLoadingText("Simplifying terrain. If it gets stuck, start without debugger (ctrl-F5 in MSVC)."); +#else + AddLoadingText("Simplifying terrain. This may take a while, please be patient!"); +#endif + SimplifyModel("Data/Temp/terrain",terrain_simplified_model,70000); + char abs_path[kPathSize]; + if(FindFilePath("Data/Temp/terrainlow.obj", abs_path, kPathSize, kWriteDir) == -1){ + FatalError("Error", "Could not find: Data/Temp/terrainlow.obj"); + } + LOGI << "Loading terrain hard coded model: " << abs_path << std::endl; + terrain_simplified_model.LoadObj(abs_path,0,"",kAbsPath); + RemoveDoubledTriangles(&terrain_simplified_model); + TextureTerrainModel(terrain_simplified_model, (float)heightmap_.width()); + + int index = 1; + for(int i=0, len=terrain_simplified_model.vertices.size()/3; i<len; i++){ + terrain_simplified_model.vertices[index] -= kHeightMapHeight; + index += 3; + } + + AddLoadingText("Calculating terrain normals..."); + terrain_simplified_model.calcNormals(); + AddLoadingText("Calculating terrain tangents..."); + terrain_simplified_model.calcTangents(); + + terrain_simplified_model.calcBoundingBox(); + terrain_simplified_model.calcBoundingSphere(); + + terrain_simplified_model.CalcTexelDensity(); + terrain_simplified_model.CalcAverageTriangleEdge(); + + char uv2_rel_path[kPathSize]; + FormatString(uv2_rel_path, kPathSize, "%s.obj_UV2", heightmap_.path().c_str()); + + char abs_uv2_path[kPathSize]; + //bool found_uv2 = false; + uint16_t uv2_checksum = 0; + + if(FindFilePath(uv2_rel_path, abs_uv2_path, kPathSize, kDataPaths | kModPaths, false, NULL) == 0){ + //found_uv2 = true; + uv2_checksum = Checksum(abs_uv2_path); + + Model temp; + temp.SimpleLoadTriangleCutObj(abs_uv2_path); + CopyTexCoords2(terrain_simplified_model, temp); + for(unsigned i=0; i<terrain_simplified_model.tex_coords2.size(); ++i){ + terrain_simplified_model.tex_coords2[i] *= 2048.0f; + } + } + + std::string path = GetWritePath(heightmap_.modsource_)+heightmap_.path() + ".cache"; + FILE *cache_file = my_fopen(path.c_str(), "wb"); + if(cache_file){ + AddLoadingText("Writing terrain cache file..."); + fwrite(&_terrain_cache_file_version_number, sizeof(_terrain_cache_file_version_number), 1, cache_file); + uint16_t checksum = heightmap_.checksum(); + fwrite(&checksum, sizeof(checksum), 1, cache_file); + fwrite(&uv2_checksum, sizeof(uv2_checksum), 1, cache_file); + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + terrain_simplified_model.WriteToFile(cache_file); + fclose(cache_file); + } + + terrain_simplified_model.SaveObj(heightmap_.path() + ".obj"); + + terrain_simplified_model.vbo_enabled = true; +} + +void Terrain::CalculateMinimalTerrain() { + if(model_id == -1) { + model_id = Models::Instance()->AddModel(); + } + Model& terrain_minimal_model = Models::Instance()->GetModel(model_id); + CalculateHighResVertices(terrain_minimal_model); + CalculateHighResFaces(terrain_minimal_model); + TextureTerrainModel(terrain_minimal_model, (float)heightmap_.width()); + + int index = 1; + for(int i=0, len=terrain_minimal_model.vertices.size()/3; i<len; i++){ + terrain_minimal_model.vertices[index] -= kHeightMapHeight; + index += 3; + } + + terrain_minimal_model.calcNormals(); + terrain_minimal_model.calcTangents(); + terrain_minimal_model.calcBoundingBox(); + terrain_minimal_model.calcBoundingSphere(); + terrain_minimal_model.texel_density = 0.0f; + terrain_minimal_model.average_triangle_edge_length = 0.0f; + terrain_minimal_model.vbo_enabled = true; +} + +Model& Terrain::GetModel() const { + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + return terrain_simplified_model; +} +const float _light_offset = 0.002f; + +void Terrain::BakeTerrainTexture(GLuint framebuffer, const TextureRef& light_cube) { + if(minimal) + return; + + Shaders* shaders = Shaders::Instance(); + Textures* textures = Textures::Instance(); + Graphics* graphics = Graphics::Instance(); + + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + if(!textures->IsRenderable(baked_texture_ref)) { + baked_texture_ref = textures->makeTexture(terrain_texture_size,terrain_texture_size,GL_RGBA,GL_RGBA,true); + textures->SetTextureName(baked_texture_ref, "Terrain Baked Texture"); + } + graphics->PushFramebuffer(); + graphics->RenderFramebufferToTexture(framebuffer, baked_texture_ref); + graphics->PushViewport(); + graphics->setViewport(0,0,terrain_texture_size,terrain_texture_size); + + GLState gl_state; + gl_state.blend = false; + gl_state.cull_face = false; + gl_state.depth_test = false; + gl_state.depth_write = false; + graphics->setGLState(gl_state); + + glClearColor(0.0f,0.0f,0.0f,0.0f); + glClear( GL_COLOR_BUFFER_BIT); + + int prepare_shader_id = shaders->returnProgram(shader); + shaders->setProgram(prepare_shader_id); + + vec3 light_pos = primary_light->pos; + shaders->SetUniformVec3("light_pos",light_pos); + shaders->SetUniformVec4("primary_light_color",vec4(primary_light->color, primary_light->intensity)); + + textures->bindTexture(color_texture_ref, 0); + textures->bindTexture(light_cube, 3); + textures->bindTexture(normal_map_ref, 4); + + { + CHECK_GL_ERROR(); + if(!terrain_simplified_model.vbo_loaded){ + terrain_simplified_model.createVBO(); + } + terrain_simplified_model.VBO_tex_coords.Bind(); + int vert_attrib_id = shaders->returnShaderAttrib("uv", prepare_shader_id); + + CHECK_GL_ERROR(); + graphics->EnableVertexAttribArray(vert_attrib_id); + CHECK_GL_ERROR(); + glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0); + CHECK_GL_ERROR(); + terrain_simplified_model.VBO_faces.Bind(); + CHECK_GL_ERROR(); + graphics->DrawElements(GL_TRIANGLES, terrain_simplified_model.faces.size(), GL_UNSIGNED_INT, 0); + CHECK_GL_ERROR(); + graphics->ResetVertexAttribArrays(); + CHECK_GL_ERROR(); + graphics->BindArrayVBO(0); + CHECK_GL_ERROR(); + graphics->BindElementVBO(0); + CHECK_GL_ERROR(); + } + + graphics->PopViewport(); + graphics->PopFramebuffer(); + + Textures::Instance()->GenerateMipmap(baked_texture_ref); +} + +//Load terrain +void Terrain::Load(const char* name, const std::string& model_override) { + minimal = false; + heightmap_.LoadData(name, HeightmapImage::DOWNSAMPLED); + + LOGI << "Loading terrain \"" << name << "\"" << std::endl; + AddLoadingText("Checking for terrain cache file..."); + + if(!LoadCachedSimplifiedTerrain()) { + LOGI << "Failed to load cached terrain, calculating a simplified terrain" << std::endl; + CalculateSimplifiedTerrain(); + } + + if(!model_override.empty()){ + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + terrain_simplified_model.LoadObj(model_override, 0); + for(unsigned i=0; i<terrain_simplified_model.tex_coords2.size(); ++i){ + terrain_simplified_model.tex_coords2[i] *= 2048.0f; + } + } + + LOGI << "*****************" << std::endl; + + CalculatePatches(); + + heightmap_.LoadData(heightmap_.path(), HeightmapImage::ORIGINAL_RES); + + terrain_texture_size = heightmap_.width() / Graphics::Instance()->config_.texture_reduction_factor(); +} + +void Terrain::LoadMinimal(const char* name, const std::string& model_override) { + minimal = true; + heightmap_.LoadData(name, HeightmapImage::DOWNSAMPLED); + CalculateMinimalTerrain(); + CalculatePatches(); + terrain_texture_size = heightmap_.width() / Graphics::Instance()->config_.texture_reduction_factor(); +} + +void Terrain::GetShaderNames(std::map<std::string, int>& preload_shaders) { + preload_shaders[shader] = 0; +} + +void Terrain::CalcDetailTextures() +{ + LOGI << "Calculating detail textures." << std::endl; + unsigned num_colors = detail_maps_info.size(); + + Textures::Instance()->setWrap(GL_REPEAT, GL_REPEAT); + detail_texture_ref = Textures::Instance()->makeArrayTexture(num_colors, PX_SRGB); + Textures::Instance()->SetTextureName(detail_texture_ref, "Terrain Detail Texture Array - Color"); + detail_texture_color.resize(num_colors); + detail_texture_color_srgb.resize(num_colors); + detail_normal_texture_ref = Textures::Instance()->makeArrayTexture(num_colors); + Textures::Instance()->SetTextureName(detail_normal_texture_ref, "Terrain Detail Texture Array - Normals"); + + Textures::Instance()->setWrap(GL_REPEAT, GL_REPEAT); + + // Get average color of each detail texture + std::vector<ByteColor> average_color(num_colors); + for(unsigned i=0; i<num_colors; i++){ + Textures::Instance()->loadArraySlice(detail_texture_ref, i, detail_maps_info[i].colorpath); + Textures::Instance()->loadArraySlice(detail_normal_texture_ref, i, detail_maps_info[i].normalpath); + + //detail_texture_color[i] = AverageColors::Instance()->ReturnRef(detail_maps_info[i].colorpath)->color(); + AverageColorRef color_ref = Engine::Instance()->GetAssetManager()->LoadSync<AverageColor>(detail_maps_info[i].colorpath); + average_colors.insert(color_ref); + detail_texture_color[i] = color_ref->color(); + for(int channel=0; channel<3; ++channel){ + average_color[i].color[channel] = (int)(detail_texture_color[i][channel]*255.0f); + detail_texture_color_srgb[i][channel] = pow(detail_texture_color[i][channel], 2.2f); + } + detail_texture_color_srgb[i][3] = detail_texture_color[i][3]; + } + + char abs_weight_map_path[kPathSize]; + bool found_weight_path = false; + if(!weight_map_path.empty()){ + found_weight_path = + (FindFilePath(weight_map_path.c_str(), abs_weight_map_path, kPathSize, kDataPaths | kModPaths,false) != -1); + } + + if(found_weight_path){ + detail_texture_weights = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(weight_map_path); + //weight_bitmap = ImageSamplers::Instance()->ReturnRef(weight_map_path); + weight_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(weight_map_path); + return; + } + + std::string path = heightmap_.path() + "_" + level_name + "_weights.png"; + found_weight_path = + (FindImagePath(path.c_str(), abs_weight_map_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs,false) != -1); + + if(found_weight_path){ + detail_texture_weights = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path); + //weight_bitmap = ImageSamplers::Instance()->ReturnRef(path); + weight_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path); + } else { + LOGI << "Calculating detail texture weights" << std::endl; + // Load terrain color map + TextureData texture_data; + char abs_path[kPathSize]; + if(FindFilePath(color_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1){ + //Fall back on finding the .dds if the raw is missing. + if(FindImagePath(color_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1) { + FatalError("Error", "Could not find color path: %s", color_path.c_str()); + } + } + texture_data.Load(abs_path); + // TODO: check this + unsigned total_bytes = texture_data.GetWidth() * + texture_data.GetHeight() * + 32 / 8; + + // Compare each pixel to each average color + // Create weight map and tint texture + //#pragma omp parallel for + std::vector<unsigned char> image_data; + image_data.resize(total_bytes); + texture_data.GetUncompressedData(&image_data[0]); + for(int i=0; i<(int)total_bytes; i+=4){ + ByteColor color; + color.Set(image_data[i+0], + image_data[i+1], + image_data[i+2]); + + std::vector<float> distances(num_colors); + for(unsigned j=0; j<num_colors; j++){ + distances[j] = hue_saturation_distance_squared(color,average_color[j]); + } + + float lowest_distance = FLT_MIN; + unsigned int which_lowest = (unsigned int)-1; + for(unsigned int j=0; j<num_colors; j++){ + if(which_lowest == (unsigned int)-1 || distances[j] < lowest_distance){ + which_lowest = j; + lowest_distance = distances[j]; + } + } + + // Create weight map + image_data[i+0] = which_lowest==0?255:0; + image_data[i+1] = which_lowest==1?255:0; + image_data[i+2] = which_lowest==2?255:0; + image_data[i+3] = 255; + //texture_data.m_nImageData[i+3] = which_lowest==3?255:0; + } + + for(unsigned i=0; i<total_bytes; i+=4){ + std::swap(image_data[i+0], + image_data[i+2]); + } + + LOGI << "Saving detail texture weights " << path << std::endl; + // Save weight and tint maps as textures + path = heightmap_.path() + "_" + level_name + "_weights.png"; + std::string write_path = GetWritePath(heightmap_.modsource_)+heightmap_.path() + "_" + level_name + "_weights.png"; + ImageExport::SavePNG(write_path.c_str(), &image_data[0], texture_data.GetWidth(), texture_data.GetHeight()); + //SavePNG("detail_tint_no_compress.png", tint_texture_data.m_nImageData, tint_texture_data.m_nImageWidth, tint_texture_data.m_nImageHeight); + + // Load weight and tint map textures + detail_texture_weights = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path); + //weight_bitmap = ImageSamplers::Instance()->ReturnRef(path); + weight_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path); + + //detail_texture_tint = Textures::Instance()->returnTextureAssetRef("detail_tint_no_compress.png"); + } +} + +const float terrain_size = 500.0; +const float fade_distance = 50.0; +const float fade_mult = 1.0f / fade_distance; +float GetAlpha(const vec3& v){ + float alpha = min(1.0f,(terrain_size-v[0])*fade_mult)* + min(1.0f,(v[0]+500.0f)*fade_mult)* + min(1.0f,(terrain_size-v[2])*fade_mult)* + min(1.0f,(v[2]+500.0f)*fade_mult); + + alpha = max(0.0f,alpha); + + return alpha; +} + +void Terrain::CalculatePatches() +{ + const int patch_resolution = 6; + + // Create 3-dimensional array to store triangles for each patch, e.g.: + // patch_vertex_ids[0][2][61] would give 62nd triangle id for patch (0,2) + std::vector<std::vector<std::vector<int> > > patch_vertex_ids; + patch_vertex_ids.resize(patch_resolution); + for(int i=0; i<patch_resolution; i++){ + patch_vertex_ids[i].resize(patch_resolution); + } + + std::vector<std::vector<std::vector<int> > > edge_patch_vertex_ids; + edge_patch_vertex_ids.resize(patch_resolution); + for(int i=0; i<patch_resolution; i++){ + edge_patch_vertex_ids[i].resize(patch_resolution); + } + + // Loop through terrain faces to find out which patch it belongs to + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + const Model& model = terrain_simplified_model; + const vec3 dimensions = (model.max_coords - model.min_coords); + + // added an epsilon so the edge is not exceeded. + const float patch_resolution_f = ((float)patch_resolution) - 0.01f; + int face_index, vert_index[3]; + static const float one_third = 1.0f/3.0f; + for(int i=0, len=model.faces.size()/3; i<len; i++){ + face_index = i*3; + for(unsigned j=0; j<3; ++j){ + vert_index[j] = model.faces[face_index+j]*3; + } + vec3 midpoint; + for(unsigned j=0; j<3; ++j){ + midpoint[j] = (model.vertices[vert_index[0]+j] + + model.vertices[vert_index[1]+j] + + model.vertices[vert_index[2]+j])*one_third; + } + + const vec3 unit_midpoint = (midpoint - model.min_coords) / + dimensions; + + const unsigned x_coord = (unsigned)(unit_midpoint[0] * + patch_resolution_f); + + const unsigned z_coord = (unsigned)(unit_midpoint[2] * + patch_resolution_f); + + float alpha = min(GetAlpha(vec3(model.vertices[vert_index[0]+0], + model.vertices[vert_index[0]+1], + model.vertices[vert_index[0]+2])), + min(GetAlpha(vec3(model.vertices[vert_index[1]+0], + model.vertices[vert_index[1]+1], + model.vertices[vert_index[1]+2])), + GetAlpha(vec3(model.vertices[vert_index[2]+0], + model.vertices[vert_index[2]+1], + model.vertices[vert_index[2]+2])))); + if(alpha == 1.0f){ + patch_vertex_ids[x_coord][z_coord].push_back(i); + } else { + edge_patch_vertex_ids[x_coord][z_coord].push_back(i); + } + } + + terrain_patches.clear(); + edge_terrain_patches.clear(); + for(int i=0; i<patch_resolution; i++){ + for(int j=0; j<patch_resolution; j++){ + terrain_patches.resize(terrain_patches.size()+1); + Model &patch_model = terrain_patches.back(); + patch_model.CopyFacesFromModel(terrain_simplified_model, + patch_vertex_ids[i][j]); + patch_model.calcBoundingBox(); + if(patch_model.vertices.empty()){ + terrain_patches.resize(terrain_patches.size()-1); + } + + if(!edge_patch_vertex_ids[i][j].empty()){ + edge_terrain_patches.resize(edge_terrain_patches.size()+1); + Model &edge_patch_model = edge_terrain_patches.back(); + edge_patch_model.CopyFacesFromModel(terrain_simplified_model, + edge_patch_vertex_ids[i][j]); + edge_patch_model.calcBoundingBox(); + if(edge_patch_model.vertices.empty()){ + edge_terrain_patches.resize(edge_terrain_patches.size()-1); + } + } + } + } +} + +vec2 Terrain::GetUVAtPoint(const vec3 &point, int* tri) const{ + int face = -1; + vec3 intersection_point; + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + const Model& model = terrain_simplified_model; + //vec3 normal; + if(!tri){ + vec3 point_high(point[0], point[1]+1000.0f, point[2]); + vec3 point_low(point[0], point[1]-1000.0f, point[2]); + face = model.lineCheckNoBackface(point_high, + point_low, + &intersection_point); + } else { + face = *tri; + intersection_point = point; + } + + if(face == -1){ + return vec2(0.0f); + } + + int index[3]; + index[0] = model.faces[face*3+0]; + index[1] = model.faces[face*3+1]; + index[2] = model.faces[face*3+2]; + + vec3 points[3]; + for(unsigned j=0; j<3; ++j){ + points[j][0] = model.vertices[index[j]*3+0]; + points[j][1] = model.vertices[index[j]*3+1]; + points[j][2] = model.vertices[index[j]*3+2]; + } + + + //DebugDraw::Instance()->AddLine(point_high, point_low, vec4(1.0f), _delete_on_update); + //DebugDraw::Instance()->AddWireSphere(intersection_point, 0.1f, vec4(1.0f), _delete_on_update); + + //DebugDraw::Instance()->AddLine(points[0], points[1], vec4(1.0f), _fade, _DD_XRAY); + //DebugDraw::Instance()->AddLine(points[1], points[2], vec4(1.0f), _fade, _DD_XRAY); + //DebugDraw::Instance()->AddLine(points[2], points[0], vec4(1.0f), _fade, _DD_XRAY); + + + vec3 normal = normalize(cross(points[1] - points[0], points[2] - points[0])); + + vec3 barycentric_coords = barycentric(intersection_point, + normal, points[0], points[1], points[2]); + + float total = 0.0f; + for(unsigned i=0; i<3; ++i){ + barycentric_coords[i] = min(1.0f, max(0.0f, barycentric_coords[i])); + total += barycentric_coords[i]; + } + + if(total == 0.0f){ + barycentric_coords = vec3(1.0f/3.0f,1.0f/3.0f,1.0f/3.0f); + } else { + barycentric_coords *= (1.0f / total); + } + + vec2 uv; + uv[0] = model.tex_coords[index[0]*2+0]*barycentric_coords[0]+ + model.tex_coords[index[1]*2+0]*barycentric_coords[1]+ + model.tex_coords[index[2]*2+0]*barycentric_coords[2]; + + uv[1] = model.tex_coords[index[0]*2+1]*barycentric_coords[0]+ + model.tex_coords[index[1]*2+1]*barycentric_coords[1]+ + model.tex_coords[index[2]*2+1]*barycentric_coords[2]; + return uv; +} + +vec4 Terrain::SampleWeightMapAtPoint( vec3 point, int* tri ) +{ + vec2 uv = GetUVAtPoint(point, tri); + + vec3 color = weight_bitmap->GetInterpolatedColorUV(uv[0], uv[1]).xyz(); + + //DebugDraw::Instance()->AddWireSphere(point, 0.2f, vec4(color, 1.0f), _fade); + + float missing_component = 1.0f - (color[0] + color[1] + color[2]); + vec4 final_color = vec4(color, missing_component); + return final_color; +} + + +vec3 Terrain::SampleColorMapAtPoint( vec3 point, int *tri ) { + vec2 uv = GetUVAtPoint(point, tri); + vec3 color = color_bitmap->GetInterpolatedColorUV(uv[0], uv[1]).xyz(); + for(int i=0; i<3; ++i){ + color[i] = pow(color[i], 2.2f); + } + return color; +} + + +const MaterialRef Terrain::GetMaterialAtPoint( vec3 point, int* tri ) { + vec4 weights = SampleWeightMapAtPoint(point, tri); + + int strongest_weight = 0; + float strongest_weight_amount = weights[0]; + for(int i=1; i<4; i++){ + if(weights[i]>strongest_weight_amount){ + strongest_weight = i; + strongest_weight_amount = weights[i]; + } + } + + const std::string &path = detail_maps_info[strongest_weight].materialpath; + LOGS << path << std::endl; + //Materials* materials = Materials::Instance(); + //MaterialRef material = materials->ReturnRef(path); + MaterialRef material = Engine::Instance()->GetAssetManager()->LoadSync<Material>(path); + return material; +} + +void Terrain::HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri ) +{ + MaterialRef material_ref = GetMaterialAtPoint(event_pos, tri); + Material& material = (*material_ref); + material.HandleEvent(the_event, event_pos); +} + +void Terrain::SetDetailObjectLayers( const std::vector<DetailObjectLayer> &_detail_object_layers ) +{ + if(detail_object_layers == _detail_object_layers || minimal){ + return; + } + detail_object_layers = _detail_object_layers; + detail_object_surfaces.clear(); + detail_object_surfaces.resize(detail_object_layers.size()); + int counter = 0; + Model &terrain_simplified_model = Models::Instance()->GetModel(model_id); + for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin(); + iter != detail_object_surfaces.end(); ++iter) + { + static const mat4 identity; + *iter = new DetailObjectSurface(); + DetailObjectSurface& dos = *(*iter); + dos.AttachTo(terrain_simplified_model, identity); + dos.GetTrisInPatches(identity); + dos.LoadDetailModel(detail_object_layers[counter].obj_path); + dos.LoadWeightMap(detail_object_layers[counter].weight_path); + dos.SetDensity(detail_object_layers[counter].density); + dos.tint_weight = (detail_object_layers[counter].tint_weight); + dos.SetNormalConform(detail_object_layers[counter].normal_conform); + dos.SetMinEmbed(detail_object_layers[counter].min_embed); + dos.SetMaxEmbed(detail_object_layers[counter].max_embed); + dos.SetMinScale(detail_object_layers[counter].min_scale); + dos.SetMaxScale(detail_object_layers[counter].max_scale); + dos.SetViewDist(detail_object_layers[counter].view_dist); + dos.SetJitterDegrees(detail_object_layers[counter].jitter_degrees); + dos.SetOverbright(detail_object_layers[counter].overbright); + dos.SetCollisionType(detail_object_layers[counter].collision_type); + ++counter; + } + if(normal_map_ref.valid()){ + for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin(); + iter != detail_object_surfaces.end(); ++iter) + { + (*iter)->SetBaseTextures(color_texture_ref, normal_map_ref); + } + } +} + +void Terrain::SetDetailTextures( const std::vector<DetailMapInfo> &_detail_maps_info ) +{ + if(normal_map_ref.valid() && (_detail_maps_info != detail_maps_info)) { + detail_maps_info = _detail_maps_info; + CalcDetailTextures(); + } else { + detail_maps_info = _detail_maps_info; + } +} + +void Terrain::SetColorTexture(const char* path) +{ + color_path = path; + color_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path); + color_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path, PX_SRGB, 0x0); +} + +void Terrain::SetWeightTexture(const char* path) +{ + weight_map_path = path; + CalcDetailTextures(); +} |