/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "editor/editorTerrain.hpp" struct world_vertex; namespace scene { class basic_section; class basic_cell; } // the heightmap terrain of the scenery (scene/heightmapterrain.h), loaded around the camera. // // the pack files are read and the meshes made by worker threads; the main thread only puts finished meshes on the gpu, // within a few milliseconds of each frame. chunks are loaded around the camera and around the place the camera is heading // to, so a moving train finds its terrain loaded. each chunk is drawn with a grid as coarse as its distance allows. // // the editor changes the chunks in memory; they're written to the pack files when the scenery is saved, until then the // edited chunks stay in memory, also when they're far from the camera class terrain_streamer { public: using chunk_key = heightmap::chunk_key; terrain_streamer() = default; ~terrain_streamer(); // opens the terrain kept in terrain// (the folder may not exist yet). returns: true if there's terrain to show bool open(std::string const &Name); // stops showing the terrain void close(); bool active() const { return m_active; } std::string const &name() const { return m_name; } // the folder of the terrain, with a trailing slash std::string const &directory() const { return m_directory; } // loads and unloads the chunks around the camera, and puts finished meshes on the gpu void update(glm::dvec3 const &Camera); // hard reset used when a new scenery loads: drops everything WITHOUT touching scene sections // (the old region is being torn down, so its section pointers are already dangling) void reset(); // drawn detail: metres of distance per metre of grid spacing (larger: finer grid further away) void detail(float const Detail) { m_detail = std::max(10.f, Detail); } float detail() const { return m_detail; } // smallest distance (chunks) the terrain is loaded to, whatever the draw range void radius(int const Radius) { m_radius = std::max(0, Radius); } int radius() const { return m_radius; } // chunks held in memory void collect(std::vector &Out) const; // chunk covering (X,Z), or nullptr editor_terrain *terrain_at(double X, double Z) const; // height of the terrain at (X,Z); false where there's no chunk in memory bool height_at(double X, double Z, double &Height) const; // triangles of the full grid of the chunks in memory overlapping the rectangle (x, z) void gather_triangles(glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector> &Out) const; // true if the geometry belongs to the terrain (or its water) bool owns(gfx::geometry_handle const &Geometry) const; std::size_t resident() const { return m_chunks.size(); } // chunks in the pack files and in memory, less the removed ones; the files known so far bool exists(chunk_key const &Key) const; // editing; the chunks changed are written to the files by save() heightmap::manifest const &manifest() const { return m_manifest; } // a flat chunk at (Cx, Cz) of specified spacing and height, covered with the material of palette entry Layer; // returns false if there's a chunk already bool add_chunk(int Cx, int Cz, float Spacing, float Height, std::uint16_t Layer = 0); // a chunk at (Cx, Cz), heights given for each grid point by Height (from its world position) bool add_chunk(int Cx, int Cz, float Spacing, std::function const &Height, std::uint16_t Layer = 0); void remove_chunk(int Cx, int Cz); // changes the point spacing of the chunk at (Cx, Cz), heights resampled void spacing(int Cx, int Cz, float Spacing); // raises (Strength > 0) or lowers the ground within Radius of (X,Z), with a smooth falloff bool sculpt(double X, double Z, double Radius, double Strength); // evens out the ground within Radius of (X,Z), Amount (0..1) of the way at the centre bool smooth(double X, double Z, double Radius, double Amount); // gives every grid point within the rectangle to Shaper, with its world position and height (before the modifiers); // Shaper returns true if it changed the height bool reshape(glm::dvec2 const &Min, glm::dvec2 const &Max, std::function const &Shaper); // paints the material of the palette entry Layer within Radius of (X,Z), Strength (0..1) of the way at the centre bool paint(double X, double Z, double Radius, double Strength, std::uint16_t Layer); // palette entry of the material, added to the palette if needed std::uint16_t layer(std::string const &Material, float Size = 0.f); void layer_size(std::uint16_t Layer, float Size); void compress(bool State); void default_spacing(float Spacing); // bodies of water std::vector const &water() const { return m_manifest.water; } void water(std::vector Water); // modifiers: added or replaced by name void modifier(heightmap::modifier Modifier); bool remove_modifier(std::string const &Name); std::vector const &modifiers() const { return m_manifest.modifiers; } // changed since the last save bool modified() const; // writes the description and the changed pack files. returns: true on success, Error describing the failure bool save(std::string *Error = nullptr); private: struct pack_state { enum class state { unknown, loading, ready, missing } status{state::unknown}; std::shared_ptr file; std::uint32_t generation{0}; }; struct job { enum class kind { pack, chunk, mesh } type{kind::pack}; heightmap::pack_key pack{0, 0}; chunk_key chunk{0, 0}; std::shared_ptr file; std::uint32_t generation{0}; heightmap::chunk_ptr data; std::array neighbours; // -x, +x, -z, +z int stride{1}; glm::dvec3 origin{0.0}; std::uint64_t version{0}; double priority{0.0}; }; struct result { job::kind type{job::kind::pack}; heightmap::pack_key pack{0, 0}; chunk_key chunk{0, 0}; std::uint32_t generation{0}; std::shared_ptr file; std::shared_ptr data; heightmap::mesh mesh; int stride{1}; std::uint64_t version{0}; std::string error; }; struct geometry_slot { gfx::geometrybank_handle bank{0, 0}; gfx::geometry_handle geometry{0, 0}; int cells{0}; int stride{0}; bool used{false}; }; struct water_shape { gfx::geometry_handle geometry{0, 0}; scene::basic_section *section{nullptr}; scene::basic_cell *cell{nullptr}; }; void start_workers(); void stop_workers(); void worker(); void submit(job Job); void process(result &Result); // the chunk is drawn: its shape put in the scene, or put back with a new mesh void upload(editor_terrain &Chunk, heightmap::mesh &Mesh, int const Stride); void update_material(editor_terrain &Chunk); // puts the current material of the chunk in its shape void upload_shape_material(editor_terrain &Chunk); // takes the chunk out of the scene, its geometry slot and material given back void undraw(editor_terrain &Chunk); void request_mesh(editor_terrain &Chunk, int const Stride, double const Priority); int wanted_stride(editor_terrain const &Chunk, glm::dvec3 const &Camera) const; heightmap::chunk_ptr neighbour(chunk_key const &Key) const; void touch_neighbours(chunk_key const &Key); material_handle palette_material(std::uint16_t const Layer); // chunk loaded right away from its pack file, for editing far from the camera editor_terrain *load_now(chunk_key const &Key); // chunks in memory or in the files overlapping the rectangle, loaded if needed std::vector chunks_in(glm::dvec2 const &Min, glm::dvec2 const &Max, bool const Load); // leads the heights of the chunk from its base heights through the modifiers, within the rectangle void apply_modifiers(editor_terrain &Chunk, glm::dvec2 const &Min, glm::dvec2 const &Max); void apply_modifiers(glm::dvec2 const &Min, glm::dvec2 const &Max); void build_water(); void clear_water(bool const Touch); std::string pack_path(heightmap::pack_key const &Key) const; bool m_active{false}; std::string m_name; std::string m_directory; heightmap::manifest m_manifest; bool m_manifestchanged{false}; float m_detail{120.f}; int m_radius{2}; std::map m_packs; std::map> m_chunks; std::set m_loading; // asked from a worker std::set m_removed; // removed in the editor, still in the files until saved std::vector m_slots; std::map m_slotofgeometry; // geometry key -> slot std::map m_palette; std::vector m_watershapes; std::vector m_waterfree; // geometry of the water shown before, to be filled anew gfx::geometrybank_handle m_waterbank{0, 0}; bool m_waterchanged{false}; glm::dvec3 m_camera{0.0}; glm::dvec3 m_velocity{0.0}; double m_cameratime{0.0}; bool m_hascamera{false}; std::vector m_ring; // offsets of the chunks around a point, the nearest first int m_ringradius{-1}; // shared with the workers, guarded by m_mutex std::mutex m_mutex; std::condition_variable m_wakeup; std::vector m_jobs; std::deque m_results; std::size_t m_busy{0}; // jobs taken by the workers and not finished yet bool m_stop{false}; std::vector m_workers; // the pack files are read by the workers and written by save() std::mutex m_filemutex; }; // single, simulation-level instance shared by the editor (authoring) and the scenery loader, so the terrain // renders in every mode. opened by the `heightmap_terrain` scenery directive and updated each frame with the active camera. extern terrain_streamer EditorTerrain;