// http://www.apache.org/licenses/LICENSE-2.0 // Copyright 2014 Perttu Ahola #include "ground_plane_lighting/api.h" // TODO: Clean up #include "network/api.h" #include "client_file/api.h" #include "voxelworld/api.h" #include "replicate/api.h" #include "network/api.h" #include "core/log.h" #include "interface/module.h" #include "interface/server.h" #include "interface/event.h" #include "interface/mesh.h" #include "interface/voxel.h" #include "interface/block.h" #include "interface/voxel_volume.h" #include "interface/polyvox_numeric.h" #include "interface/polyvox_cereal.h" #include "interface/os.h" #include #include #include #include #include #include #include #include #include using interface::Event; namespace magic = Urho3D; namespace pv = PolyVox; using namespace Urho3D; using interface::VoxelInstance; using interface::container_coord; using interface::container_coord16; namespace std { // TODO: Move to a header (core/types_polyvox.h or something) template<> struct hash>{ std::size_t operator()(const pv::Vector<2u, int16_t> &v) const { return ((std::hash() (v.getX()) << 0) ^ (std::hash() (v.getY()) << 1)); } }; } // TODO: Move to a header (core/types_polyvox.h or something) #define PV3I_FORMAT "(%i, %i, %i)" #define PV3I_PARAMS(p) p.getX(), p.getY(), p.getZ() namespace ground_plane_lighting { // Y-seethrough data of a sector (size defined by voxelworld) struct YSTSector { static constexpr const char *MODULE = "YSTSector"; pv::Vector<2, int16_t> sector_p; // In sectors pv::Vector<2, int16_t> sector_size; // In voxels sp_> volume; // Voxel columns (region in global coords) YSTSector(): sector_size(0, 0) // This is used to detect uninitialized instance {} YSTSector(const pv::Vector<2, int16_t> §or_p, const pv::Vector<2, int16_t> §or_size): sector_p(sector_p), sector_size(sector_size), volume(new pv::RawVolume(pv::Region( sector_p.getX() * sector_size.getX(), 0, sector_p.getY() * sector_size.getY(), sector_p.getX() * sector_size.getX() + sector_size.getX() - 1, 0, sector_p.getY() * sector_size.getY() + sector_size.getY() - 1 ))) { pv::Region region = volume->getEnclosingRegion(); auto lc = region.getLowerCorner(); auto uc = region.getUpperCorner(); /*log_v(MODULE, "YSTSector volume lc=" PV3I_FORMAT ", uc=" PV3I_FORMAT ", size=%zu", PV3I_PARAMS(lc), PV3I_PARAMS(uc), volume->m_dataSize);*/ for(int z = lc.getZ(); z <= uc.getZ(); z++){ for(int x = lc.getX(); x <= uc.getX(); x++){ volume->setVoxelAt(x, 0, z, INT_MIN); } } } }; // Global Y-seethrough map struct GlobalYSTMap { pv::Vector<2, int16_t> m_sector_size; sm_, YSTSector> m_sectors; // Cache of last used sectors (add to end, remove from beginning) std::deque m_last_used_sectors; // Set of sectors that have been modified // (as a sorted array in descending order) sv_ m_dirty_sectors; GlobalYSTMap(const pv::Vector<2, int16_t> §or_size): m_sector_size(sector_size) {} YSTSector* get_sector(pv::Vector<2, int16_t> §or_p, bool create) { // Check cache for(YSTSector *sector : m_last_used_sectors){ if(sector->sector_p == sector_p) return sector; } // Not in cache YSTSector *sector = nullptr; auto it = m_sectors.find(sector_p); if(it == m_sectors.end()){ if(!create) return nullptr; sector = &m_sectors[sector_p]; *sector = YSTSector(sector_p, m_sector_size); } else { sector = &it->second; } // Add to cache and return m_last_used_sectors.push_back(sector); if(m_last_used_sectors.size() > 2) // 2 is maybe optimal-ish m_last_used_sectors.pop_front(); return sector; } pv::Vector<2, int16_t> get_sector_p(int32_t x, int32_t z) { if(m_sector_size.getX() == 0) throw Exception("GlobalYSTMap not initialized"); return container_coord16(pv::Vector<2, int32_t>(x, z), m_sector_size); } void set_yst(int32_t x, int32_t z, int32_t yst) { auto sector_p = get_sector_p(x, z); YSTSector *sector = get_sector(sector_p, true); sector->volume->setVoxelAt(x, 0, z, yst); // Set sector dirty flag auto it = std::lower_bound(m_dirty_sectors.begin(), m_dirty_sectors.end(), sector, std::greater()); // position in descending order if(it == m_dirty_sectors.end() || *it != sector) m_dirty_sectors.insert(it, sector); } int32_t get_yst(int32_t x, int32_t z) { auto sector_p = get_sector_p(x, z); YSTSector *sector = get_sector(sector_p, false); if(!sector) return INT_MIN; return sector->volume->getVoxelAt(x, 0, z); } }; struct Module: public interface::Module, public ground_plane_lighting::Interface { interface::Server *m_server; up_ m_global_yst; Module(interface::Server *server): interface::Module("ground_plane_lighting"), m_server(server) { } ~Module() { } void init() { m_server->sub_event(this, Event::t("core:start")); m_server->sub_event(this, Event::t("core:unload")); m_server->sub_event(this, Event::t("core:continue")); m_server->sub_event(this, Event::t("core:tick")); m_server->sub_event(this, Event::t("network:client_connected")); m_server->sub_event(this, Event::t("client_file:files_transmitted")); m_server->sub_event(this, Event::t("voxelworld:node_volume_updated")); voxelworld::access(m_server, [&](voxelworld::Interface *ivoxelworld) { pv::Vector3DInt16 section_size = ivoxelworld->get_section_size_voxels(); pv::Vector<2, int16_t> sector_size( section_size.getX(), section_size.getZ()); m_global_yst.reset(new GlobalYSTMap(sector_size)); }); } void event(const Event::Type &type, const Event::Private *p) { EVENT_VOIDN("core:start", on_start) EVENT_VOIDN("core:unload", on_unload) EVENT_VOIDN("core:continue", on_continue) EVENT_TYPEN("core:tick", on_tick, interface::TickEvent) EVENT_TYPEN("network:client_connected", on_client_connected, network::NewClient) EVENT_TYPEN("client_file:files_transmitted", on_files_transmitted, client_file::FilesTransmitted) EVENT_TYPEN("voxelworld:node_volume_updated", on_node_volume_updated, voxelworld::NodeVolumeUpdated) } void on_start() { } void on_unload() { } void on_continue() { } void on_tick(const interface::TickEvent &event) { if(!m_global_yst->m_dirty_sectors.empty()){ sv_ dirty_sectors; dirty_sectors.swap(m_global_yst->m_dirty_sectors); for(YSTSector *sector : dirty_sectors){ ss_ s = interface::serialize_volume_compressed(*sector->volume); std::ostringstream os(std::ios::binary); { cereal::PortableBinaryOutputArchive ar(os); ar(sector->sector_p); ar(s); } network::access(m_server, [&](network::Interface *inetwork){ sv_ peers = inetwork->list_peers(); for(auto &peer: peers){ inetwork->send(peer, "ground_plane_lighting:update", os.str()); } }); } } } void on_client_connected(const network::NewClient &client_connected) { } void on_files_transmitted(const client_file::FilesTransmitted &event) { int peer = event.recipient; network::access(m_server, [&](network::Interface *inetwork){ inetwork->send(peer, "core:run_script", "require(\"buildat/module/ground_plane_lighting\")"); }); std::ostringstream os(std::ios::binary); { cereal::PortableBinaryOutputArchive ar(os); ar(m_global_yst->m_sector_size); } network::access(m_server, [&](network::Interface *inetwork){ inetwork->send(peer, "ground_plane_lighting:init", os.str()); }); send_initial_sectors(peer); } void on_node_volume_updated(const voxelworld::NodeVolumeUpdated &event) { if(!event.is_static_chunk) return; log_v(MODULE, "Checking ground level in chunk " PV3I_FORMAT, PV3I_PARAMS(event.chunk_p)); const pv::Vector3DInt32 &chunk_p = event.chunk_p; voxelworld::access(m_server, [&](voxelworld::Interface *ivoxelworld) { interface::VoxelRegistry *voxel_reg = ivoxelworld->get_voxel_reg(); //const auto &chunk_size_voxels = ivoxelworld->get_chunk_size_voxels(); pv::Region chunk_region = ivoxelworld->get_chunk_region_voxels(chunk_p); ground_plane_lighting::access(m_server, [&](ground_plane_lighting::Interface *igpl) { auto lc = chunk_region.getLowerCorner(); auto uc = chunk_region.getUpperCorner(); //log_nv(MODULE, "yst=["); for(int z = lc.getZ(); z <= uc.getZ(); z++){ for(int x = lc.getX(); x <= uc.getX(); x++){ int32_t yst0 = igpl->get_yst(x, z); if(yst0 > uc.getY()){ // Y-seethrough doesn't reach here continue; } int y = uc.getY(); for(;; y--){ VoxelInstance v = ivoxelworld->get_voxel( pv::Vector3DInt32(x, y, z), true); if(v.get_id() == interface::VOXELTYPEID_UNDEFINED){ // NOTE: This leaves the chunks below unhandled; // there would have to be some kind of a dirty // flag based on which this seach would be // continued at a later point when the chunk // gets loaded break; } const auto *def = voxel_reg->get_cached(v); if(!def) throw Exception(ss_()+"Undefined voxel: "+ itos(v.get_id())); bool light_passes = (!def || !def->physically_solid); if(!light_passes) break; } // The first voxel downwards from the top of the world that // doesn't pass light int32_t yst1 = y; //log_nv(MODULE, "%i -> %i, ", yst0, yst1); igpl->set_yst(x, z, yst1); } } //log_v(MODULE, "]"); }); }); } void send_initial_sectors(int peer) { for(auto &pair : m_global_yst->m_sectors){ YSTSector *sector = &pair.second; ss_ s = interface::serialize_volume_compressed(*sector->volume); std::ostringstream os(std::ios::binary); { cereal::PortableBinaryOutputArchive ar(os); ar(sector->sector_p); ar(s); } network::access(m_server, [&](network::Interface *inetwork){ inetwork->send(peer, "ground_plane_lighting:update", os.str()); }); } } // Interface void set_yst(int32_t x, int32_t z, int32_t yst) { return m_global_yst->set_yst(x, z, yst); } int32_t get_yst(int32_t x, int32_t z) { return m_global_yst->get_yst(x, z); } void* get_interface() { return dynamic_cast(this); } }; extern "C" { BUILDAT_EXPORT void* createModule_ground_plane_lighting( interface::Server *server){ return (void*)(new Module(server)); } } } // vim: set noet ts=4 sw=4: