diff --git a/builtin/voxelworld/client_lua/module.lua b/builtin/voxelworld/client_lua/module.lua index 3039e6d..31e645a 100644 --- a/builtin/voxelworld/client_lua/module.lua +++ b/builtin/voxelworld/client_lua/module.lua @@ -8,10 +8,19 @@ local cereal = require("buildat/extension/cereal") local dump = buildat.dump local M = {} +local LOD_DISTANCE = 100 +--local LOD_DISTANCE = 140 +--local LOD_DISTANCE = 50 +local LOD_THRESHOLD = 0.2 + local camera_node = nil local update_counter = -1 -local camera_last_dir = magic.Vector3(0, 0, 0) -local camera_last_p = magic.Vector3(0, 0, 0) + +local camera_p = magic.Vector3(0, 0, 0) +local camera_dir = magic.Vector3(0, 0, 0) +local camera_last_p = camera_p +local camera_last_dir = camera_dir + local end_of_update_processing_us = 0 M.chunk_size_voxels = nil @@ -111,7 +120,16 @@ do end local function SpatialUpdateQueue() - local function fcomp(a, b) return a.d > b.d end + local function fcomp(a, b) + -- Always maintain all f<=1.0 items at the end of the table + if a.f > 1.0 and b.f <= 1.0 then + return true + end + if a.f <= 1.0 and b.f > 1.0 then + return false + end + return a.fw > b.fw + end local self = { p = buildat.Vector3(0, 0, 0), queue_oldest_p = buildat.Vector3(0, 0, 0), @@ -131,8 +149,7 @@ local function SpatialUpdateQueue() self.old_queue = nil break end - item.d = (item.p - self.p):length() - table_bininsert(self.queue, item, fcomp) + self:put_item(item) end end end, @@ -147,16 +164,83 @@ local function SpatialUpdateQueue() self.queue_oldest_p = self.p end end, - put = function(self, p, value) + put_item = function(self, item) + local d = (item.p - self.p):length() + item.f = nil + item.fw = nil + if item.near_trigger_d then + local f_near = d / item.near_trigger_d + local fw_near = f_near / item.near_weight + if item.fw == nil or (fw_near < item.fw and + (item.f == nil or f_near < item.f)) then + item.f = f_near + item.fw = fw_near + end + end + if item.far_trigger_d then + local f_far = item.far_trigger_d / d + local fw_far = f_far / item.far_weight + if item.fw == nil or (fw_far < item.fw and + (item.f == nil or f_far < item.f)) then + item.f = f_far + item.fw = fw_far + end + end + assert(item.f) + assert(item.fw) + log:verbose("put_item".. + ": d="..dump(d).. + ", near_weight="..dump(item.near_weight).. + ", near_trigger_d="..dump(item.near_trigger_d).. + ", far_weight="..dump(item.far_weight).. + ", far_trigger_d="..dump(item.far_trigger_d).. + " -> f="..item.f..", fw="..item.fw) + table_bininsert(self.queue, item, fcomp) + end, + -- Put something to be prioritized bidirectionally + put = function(self, p, near_weight, near_trigger_d, + far_weight, far_trigger_d, value) + if near_trigger_d and far_trigger_d then + assert(near_trigger_d < far_trigger_d) + assert(near_weight) + assert(far_weight) + else + assert((near_trigger_d and near_weight) or + (far_trigger_d and far_weight)) + end p = buildat.Vector3(p) -- Strip out the heavy Urho3D wrapper - local d = (p - self.p):length() - table_bininsert(self.queue, {p=p, d=d, value=value}, fcomp) + self:put_item({p=p, value=value, + near_weight=near_weight, near_trigger_d=near_trigger_d, + far_weight=far_weight, far_trigger_d=far_trigger_d}) + end, + -- Put something to be prioritized more the closer it is + put_near_priority = function(self, p, trigger_d, weight, value) + self:put(p, weight, trigger_d, nil, nil, value) + end, + -- Put something to be prioritized more the farther it is + put_far_priority = function(self, p, trigger_d, weight, value) + self:put(p, nil, nil, weight, trigger_d, value) end, get = function(self) local item = table.remove(self.queue) if not item then return nil end return item.value end, + -- item.f is the trigger_d value normalized so that f<1.0 means the item + -- has passed its trigger_d. + peek_next_f = function(self) + local item = self.queue[#self.queue] + if not item then return nil end + return item.f + end, + -- item.fw is a comparison value; both near_priority and far_priority + -- items are normalized so that values that have fw=1 have equal + -- priority. + peek_next_fw = function(self) + local item = self.queue[#self.queue] + if not item then return nil end + return item.fw + end, } return self end @@ -184,30 +268,85 @@ function M.init() M.chunk_size_voxels:mul_components(M.section_size_chunks) end) + local node_update_queue = SpatialUpdateQueue() + local function update_voxel_geometry(node) local data = node:GetVar("buildat_voxel_data"):GetBuffer() --local registry_name = node:GetVar("buildat_voxel_registry_name"):GetBuffer() - log:info(dump(node:GetName()).." voxel data size: "..data:GetSize()) - buildat.set_voxel_geometry(node, data, registry_name) + + -- TODO: node:GetWorldPosition() is not at center of node + local node_p = node:GetWorldPosition() + local d = (node_p - camera_p):Length() + local lod_fraction = d / LOD_DISTANCE + local lod = math.floor(1 + lod_fraction) + if lod > 3 then + lod = 3 + end + + log:verbose("update_voxel_geometry(): node="..dump(node:GetName()).. + ", #data="..data:GetSize()..", d="..d..", lod="..lod) + + local near_trigger_d = nil + local near_weight = nil + local far_trigger_d = nil + local far_weight = nil + + if lod == 1 then + buildat.set_voxel_geometry(node, data, registry_name) + + -- 1 -> 2 + far_trigger_d = LOD_DISTANCE * (1.0 + LOD_THRESHOLD) + far_weight = 0.4 + else + buildat.set_voxel_lod_geometry(lod, node, data, registry_name) + + if lod == 1 then + -- Shouldn't go here + elseif lod == 2 then + -- 2 -> 1 + near_trigger_d = LOD_DISTANCE * (1.0 - LOD_THRESHOLD) + near_weight = 2.0 + -- 2 -> 3 + far_trigger_d = 2 * LOD_DISTANCE * (1.0 + LOD_THRESHOLD) + far_weight = 0.3 + elseif lod == 3 then + -- 3 -> 2 + near_trigger_d = 2 * LOD_DISTANCE * (1.0 - LOD_THRESHOLD) + near_weight = 0.5 + end + end + node_update_queue:put(node_p, + near_weight, near_trigger_d, + far_weight, far_trigger_d, { + type = "geometry", + current_lod = lod, + node_id = node:GetID(), + }) end local function update_voxel_physics(node) local data = node:GetVar("buildat_voxel_data"):GetBuffer() --local registry_name = node:GetVar("buildat_voxel_registry_name"):GetBuffer() - log:info(dump(node:GetName()).." voxel data size: "..data:GetSize()) + + log:verbose("update_voxel_physics(): node="..dump(node:GetName()).. + ", #data="..data:GetSize()) + buildat.set_voxel_physics_boxes(node, data, registry_name) end - local node_update_queue = SpatialUpdateQueue() - magic.SubscribeToEvent("Update", function(event_type, event_data) --local t0 = buildat.get_time_us() --local dt = event_data:GetFloat("TimeStep") update_counter = update_counter + 1 + if camera_node then + camera_dir = camera_node.direction + camera_p = camera_node:GetWorldPosition() + end + if camera_node and M.section_size_voxels then -- TODO: How should position information be sent to the server? - local p = camera_node:GetWorldPosition() + local p = camera_p if update_counter % 60 == 0 then local section_p = buildat.Vector3(p):div_components( M.section_size_voxels):floor() @@ -223,47 +362,46 @@ function M.init() end end - local camera_dir = magic.Vector3(0, 0, 0) - local camera_p = magic.Vector3(0, 0, 0) - - if camera_node then - camera_dir = camera_node.direction - camera_p = camera_node:GetWorldPosition() - end - -- Node updates: Handle one or a few per frame local current_us = buildat.get_time_us() -- Spend time doing this proportionate to the rest of the update cycle local max_handling_time_us = (current_us - end_of_update_processing_us) / 2 local stop_at_us = current_us + max_handling_time_us - -- Scale queue updates to the same pace as the rest of the processing node_update_queue:set_p(camera_p) + -- Scale queue update operations according to the handling time of the + -- rest of the processing node_update_queue:update(max_handling_time_us / 200 + 1) for i = 1, 10 do -- Usually there is time only for a few - local node_update = node_update_queue:get() - if node_update then - --local d = (node_update.node:GetWorldPosition() - camera_p):Length() - --if d < 50 then - if true then - log:verbose("Handling node update #".. - #node_update_queue.queue) - local node = replicate.main_scene:GetNode(node_update.node_id) - if node_update.type == "geometry" then - update_voxel_geometry(node) - end - if node_update.type == "physics" then - update_voxel_physics(node) - end - else - log:verbose("Discarding node update #".. - #node_update_queue.queue) + local f = node_update_queue:peek_next_f() + local fw = node_update_queue:peek_next_fw() + local did_update = false + if f and f <= 1.0 then + local node_update = node_update_queue:get() + local node = replicate.main_scene:GetNode(node_update.node_id) + log:verbose("Node update #".. + #node_update_queue.queue.. + " (f="..(math.floor(f*100)/100).."".. + ", fw="..(math.floor(fw*100)/100)..")".. + ": "..node:GetName()) + if node_update.type == "geometry" then + update_voxel_geometry(node) end + if node_update.type == "physics" then + update_voxel_physics(node) + end + did_update = true + else + log:verbose("Poked update #".. + #node_update_queue.queue.. + " (f="..(math.floor((f or -1)*100)/100).."".. + ", fw="..(math.floor((fw or -1)*100)/100)..")") + node_update_queue.queue = {} end -- Check this at the end of the loop so at least one is handled - if buildat.get_time_us() >= stop_at_us then - log:info("Handled "..i.." node updates") + if not did_update or buildat.get_time_us() >= stop_at_us then + log:debug("Handled "..i.." node updates") break end end @@ -278,11 +416,17 @@ function M.init() replicate.sub_sync_node_added({}, function(node) if not node:GetVar("buildat_voxel_data"):IsEmpty() then - node_update_queue:put(node:GetWorldPosition(), { + -- TODO: node:GetWorldPosition() is not at center of node + node_update_queue:put(node:GetWorldPosition(), + 0.2, 1000.0, nil, nil, { type = "geometry", + current_lod = 0, node_id = node:GetID(), }) - node_update_queue:put(node:GetWorldPosition(), { + -- Create physics stuff when node comes closer than 100 + -- TODO: node:GetWorldPosition() is not at center of node + node_update_queue:put(node:GetWorldPosition(), + 1.0, 100.0, nil, nil, { type = "physics", node_id = node:GetID(), }) diff --git a/builtin/voxelworld/voxelworld.cpp b/builtin/voxelworld/voxelworld.cpp index 7e2f12e..3a03949 100644 --- a/builtin/voxelworld/voxelworld.cpp +++ b/builtin/voxelworld/voxelworld.cpp @@ -339,7 +339,8 @@ struct Module: public interface::Module, public voxelworld::Interface void on_start() { - pv::Region region(-3, -1, -3, 3, 1, 3); + //pv::Region region(-3, -1, -3, 3, 1, 3); + pv::Region region(-5, -1, -5, 5, 1, 5); //pv::Region region(-8, -1, -8, 8, 1, 8); auto lc = region.getLowerCorner(); auto uc = region.getUpperCorner(); diff --git a/client/api.lua b/client/api.lua index 9155326..0dc3609 100644 --- a/client/api.lua +++ b/client/api.lua @@ -71,6 +71,26 @@ function buildat.safe.set_voxel_geometry(safe_node, safe_buffer) __buildat_set_voxel_geometry(node, buffer) end +function buildat.safe.set_voxel_lod_geometry(lod, safe_node, safe_buffer) + if not getmetatable(safe_node) or + getmetatable(safe_node).type_name ~= "Node" then + error("node is not a sandboxed Node instance") + end + node = getmetatable(safe_node).unsafe + + buffer = nil + if type(safe_buffer) == 'string' then + buffer = safe_buffer + else + if not getmetatable(safe_buffer) or + getmetatable(safe_buffer).type_name ~= "VectorBuffer" then + error("safe_buffer is not a sandboxed VectorBuffer instance") + end + buffer = getmetatable(safe_buffer).unsafe + end + __buildat_set_voxel_lod_geometry(lod, node, buffer) +end + function buildat.safe.set_voxel_physics_boxes(safe_node, safe_buffer) if not getmetatable(safe_node) or getmetatable(safe_node).type_name ~= "Node" then diff --git a/games/digger/main/client_lua/init.lua b/games/digger/main/client_lua/init.lua index ceee053..409444b 100644 --- a/games/digger/main/client_lua/init.lua +++ b/games/digger/main/client_lua/init.lua @@ -9,7 +9,7 @@ local magic = require("buildat/extension/urho3d") local replicate = require("buildat/extension/replicate") local voxelworld = require("buildat/module/voxelworld") -local RENDER_DISTANCE = 160 +local RENDER_DISTANCE = 320 local PLAYER_HEIGHT = 1.7 local PLAYER_WIDTH = 0.9 diff --git a/src/impl/atlas.cpp b/src/impl/atlas.cpp index a901e0d..649f47b 100644 --- a/src/impl/atlas.cpp +++ b/src/impl/atlas.cpp @@ -16,7 +16,8 @@ bool AtlasSegmentDefinition::operator==(const AtlasSegmentDefinition &other) con return ( resource_name == other.resource_name && total_segments == other.total_segments && - select_segment == other.select_segment + select_segment == other.select_segment && + lod_simulation == other.lod_simulation ); } @@ -86,6 +87,8 @@ struct CTextureAtlasRegistry: public TextureAtlasRegistry atlas_img->SetSize(atlas_resolution.x_, atlas_resolution.y_, 4); // Create texture for new atlas magic::Texture2D *atlas_tex = new magic::Texture2D(m_context); + // TODO: Make this configurable + atlas_tex->SetFilterMode(magic::FILTER_NEAREST); // TODO: Use TEXTURE_STATIC or TEXTURE_DYNAMIC? atlas_tex->SetSize(atlas_resolution.x_, atlas_resolution.y_, magic::Graphics::GetRGBAFormat(), magic::TEXTURE_STATIC); @@ -190,15 +193,76 @@ struct CTextureAtlasRegistry: public TextureAtlasRegistry seg_img_size.y_ / def.total_segments.y_ * def.select_segment.y_ ); // Draw main texture - for(int y = 0; yGetPixel(src_p.x_, src_p.y_); - atlas.image->SetPixel(dst_p.x_, dst_p.y_, c); + if(def.lod_simulation == 0){ + for(int y = 0; yGetPixel(src_p.x_, src_p.y_); + atlas.image->SetPixel(dst_p.x_, dst_p.y_, c); + } + } + } else { + int lod = def.lod_simulation & 0x0f; + uint8_t flags = def.lod_simulation & 0xf0; + for(int y = 0; yGetPixel(src_p.x_, src_p.y_); + c.r_ *= 1.0f; + c.g_ *= 1.0f; + c.b_ *= 0.875f; + atlas.image->SetPixel(dst_p.x_, dst_p.y_, c); + } else { + // Simulate sides + magic::IntVector2 src_p = src_off + magic::IntVector2( + ((x + seg_size.x_ / 2) * lod) % seg_size.x_, + ((y + seg_size.y_ / 2) * lod) % seg_size.y_ + ); + magic::IntVector2 dst_p = dst_p00 + magic::IntVector2(x, y); + magic::Color c = seg_img->GetPixel(src_p.x_, src_p.y_); + /*c.r_ *= 1.0f; + c.g_ *= 1.0f; + c.b_ *= 1.0f;*/ + // Leave horizontal edges look like they are bright + // topsides + // TODO: This should be variable according to the + // camera's height relative to the thing the atlas + // segment is representing + int edge_size = lod * seg_size.y_ / 16; + /*bool is_final_edge = ( + y <= seg_size.y_/2 + edge_size / lod || + y >= seg_size.y_*3/2 - edge_size / lod + );*/ + bool is_edge = ( + src_p.y_ <= edge_size || + src_p.y_ >= seg_size.y_ - edge_size + ); + if(/*is_final_edge ||*/ !is_edge){ + //if((x*lod/seg_size.x_ + y*lod/seg_size.y_) % 2 == 0){ + if(flags & ATLAS_LOD_HALFBRIGHT_FACE){ + c.r_ *= 0.70f; + c.g_ *= 0.70f; + c.b_ *= 0.65f; + } else { + c.r_ *= 0.5f; + c.g_ *= 0.5f; + c.b_ *= 0.5f; + } + //} + } + atlas.image->SetPixel(dst_p.x_, dst_p.y_, c); + } + } } } // Update atlas texture from atlas image diff --git a/src/impl/mesh.cpp b/src/impl/mesh.cpp index 3a1a744..d40f5ab 100644 --- a/src/impl/mesh.cpp +++ b/src/impl/mesh.cpp @@ -343,6 +343,84 @@ struct TemporaryGeometry }; #endif +void assign_txcoords(size_t pv_vertex_i1, const AtlasSegmentCache *aseg, + CustomGeometryVertex &tg_vert) +{ + if(tg_vert.normal_.z_ > 0){ + if(pv_vertex_i1 == 3){ + // Top left (n=Z+) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 1){ + // Top right (n=Z+) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 0){ + // Bottom right (n=Z+) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } else if(pv_vertex_i1 == 2){ + // Bottom left (n=Z+) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } + } else if(tg_vert.normal_.x_ > 0){ + if(pv_vertex_i1 == 3){ + // Top right (n=X+) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 1){ + // Bottom right (n=X+) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } else if(pv_vertex_i1 == 0){ + // Bottom left (n=X+) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } else if(pv_vertex_i1 == 2){ + // Top left (n=X+) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } + } else if(tg_vert.normal_.x_ < 0){ + if(pv_vertex_i1 == 1){ + // Bottom left (n=X-) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } else if(pv_vertex_i1 == 3){ + // Top left (n=X-) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 2){ + // Top right (n=X-) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 0){ + // Bottom right (n=X-) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } + } else { + if(pv_vertex_i1 == 1){ + // Top left (n=Z-) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 3){ + // Top right (n=Z-) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord0.y_; + } else if(pv_vertex_i1 == 2){ + // Bottom right (n=Z-) + tg_vert.texCoord_.x_ = aseg->coord1.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } else if(pv_vertex_i1 == 0){ + // Bottom left (n=Z-) + tg_vert.texCoord_.x_ = aseg->coord0.x_; + tg_vert.texCoord_.y_ = aseg->coord1.y_; + } + } +} + // Set custom geometry from voxel volume, using a voxel registry // Volume should be padded by one voxel on each edge void set_voxel_geometry(CustomGeometry *cg, Context *context, @@ -475,19 +553,7 @@ void set_voxel_geometry(CustomGeometry *cg, Context *context, tg_vert.normal_.z_ = pv_vert.normal.getZ(); // Figure out texture coordinates size_t pv_vertex_i1 = pv_vertex_i - pv_vertex_i0; - if(pv_vertex_i1 == 0){ - tg_vert.texCoord_.x_ = aseg->coord0.x_; - tg_vert.texCoord_.y_ = aseg->coord1.y_; - } else if(pv_vertex_i1 == 1){ - tg_vert.texCoord_.x_ = aseg->coord1.x_; - tg_vert.texCoord_.y_ = aseg->coord1.y_; - } else if(pv_vertex_i1 == 2){ - tg_vert.texCoord_.x_ = aseg->coord0.x_; - tg_vert.texCoord_.y_ = aseg->coord0.y_; - } else if(pv_vertex_i1 == 3){ - tg_vert.texCoord_.x_ = aseg->coord1.x_; - tg_vert.texCoord_.y_ = aseg->coord0.y_; - } + assign_txcoords(pv_vertex_i1, aseg, tg_vert); } #endif } @@ -496,6 +562,191 @@ void set_voxel_geometry(CustomGeometry *cg, Context *context, ResourceCache *cache = context->GetSubsystem(); + cg->Clear(); + + cg->SetNumGeometries(temp_geoms.size()); + Vector> &cg_all_vertices = cg->GetVertices(); + + unsigned cg_i = 0; + for(auto &pair : temp_geoms){ + const TemporaryGeometry &tg = pair.second; + const TextureAtlasCache *atlas_cache = + atlas_reg->get_atlas_cache(tg.atlas_id); + if(atlas_cache == nullptr) + throw Exception("atlas_cache == nullptr"); + if(atlas_cache->texture == nullptr) + throw Exception("atlas_cache->texture == nullptr"); + cg->DefineGeometry(cg_i, TRIANGLE_LIST, tg.vertex_data.Size(), + true, false, true, false); + PODVector &cg_vertices = cg_all_vertices[cg_i]; + cg_vertices = tg.vertex_data; + Material *material = new Material(context); + material->SetTechnique(0, + cache->GetResource("Techniques/Diff.xml")); + material->SetTexture(TU_DIFFUSE, atlas_cache->texture); + cg->SetMaterial(cg_i, material); + cg_i++; + } + + cg->Commit(); +} + +// Set custom geometry from voxel volume, using a voxel registry +// Volume should be padded by one voxel on each edge +void set_voxel_lod_geometry(int lod, CustomGeometry *cg, Context *context, + pv::RawVolume &volume_orig, + VoxelRegistry *voxel_reg, TextureAtlasRegistry *atlas_reg) +{ + pv::Region region_orig = volume_orig.getEnclosingRegion(); + auto &lc_orig = region_orig.getLowerCorner(); + auto &uc_orig = region_orig.getUpperCorner(); + + pv::Region region(lc_orig / lod - pv::Vector3DInt32(1,1,1), + uc_orig / lod + pv::Vector3DInt32(1,1,1)); + auto &lc = region.getLowerCorner(); + auto &uc = region.getUpperCorner(); + + pv::RawVolume volume(region); + for(int x = lc.getX(); x <= uc.getX(); x++){ + for(int y = lc.getY(); y <= uc.getY(); y++){ + for(int z = lc.getZ(); z <= uc.getZ(); z++){ + VoxelInstance v_orig(interface::VOXELTYPEID_UNDEFINED); + for(int x1 = 0; x1 < lod; x1++){ + for(int y1 = 0; y1 < lod; y1++){ + for(int z1 = 0; z1 < lod; z1++){ + pv::Vector3DInt32 p_orig( + x * lod + x1, + y * lod + y1, + z * lod + z1 + ); + if(!region_orig.containsPoint(p_orig)) + continue; + VoxelInstance v1 = volume_orig.getVoxelAt(p_orig); + if(v1.getId() == interface::VOXELTYPEID_UNDEFINED) + continue; + // TODO: Prioritize voxel types better + // Higher is probably more interesting + if(v1.getId() > v_orig.getId()) + v_orig = v1; + } + } + } + /*const interface::CachedVoxelDefinition *def = + voxel_reg->get_cached(v_orig);*/ + volume.setVoxelAt(x, y, z, v_orig); + } + } + } + + IsQuadNeededByRegistry iqn(voxel_reg, atlas_reg); + pv::SurfaceMesh pv_mesh; + pv::CubicSurfaceExtractorWithNormals, + IsQuadNeededByRegistry> + surfaceExtractor(&volume, volume.getEnclosingRegion(), &pv_mesh, iqn); + surfaceExtractor.execute(); + + const sv_ &pv_indices = pv_mesh.getIndices(); + const sv_ &pv_vertices = pv_mesh.getVertices(); + + sm_ temp_geoms; + + int w = volume_orig.getWidth() - 2; + int h = volume_orig.getHeight() - 2; + int d = volume_orig.getDepth() - 2; + + // Handle vertices face-by-face in order to copy indices at the same time + for(size_t pv_face_i = 0; pv_face_i < pv_vertices.size() / 4; pv_face_i++){ + size_t pv_vertex_i0 = pv_face_i * 4; + VoxelTypeId voxel_id0 = (VoxelTypeId)pv_vertices[pv_vertex_i0].material; + // We need to get this definition only once per face + const interface::CachedVoxelDefinition *voxel_def0 = + voxel_reg->get_cached(voxel_id0); + if(voxel_def0 == nullptr) + throw Exception("Unknown voxel in generated geometry: "+ + itos(voxel_id0)); + // Figure out which face this is + uint face_id = 0; + const pv::Vector3DFloat &n = pv_vertices[pv_vertex_i0].normal; + if(n.getY() > 0) + face_id = 0; + else if(n.getY() < 0) + face_id = 1; + else if(n.getX() > 0) + face_id = 2; + else if(n.getX() < 0) + face_id = 3; + else if(n.getZ() > 0) + face_id = 4; + else if(n.getZ() < 0) + face_id = 5; + // Get texture coordinates (contained in AtlasSegmentCache) + size_t lod_i = lod - 2; + if(lod_i >= interface::VOXELDEF_NUM_LOD) + lod_i = interface::VOXELDEF_NUM_LOD - 1; + AtlasSegmentReference seg_ref = voxel_def0->lod_textures[lod_i][face_id]; + if(seg_ref.atlas_id == interface::ATLAS_UNDEFINED){ + // This is usually intentional for invisible voxels + log_t(MODULE, "Voxel %i face %i atlas undefined", voxel_id0, face_id); + continue; + } + const AtlasSegmentCache *aseg = atlas_reg->get_texture(seg_ref); + if(aseg == nullptr) + throw Exception("No atlas segment cache for voxel "+itos(voxel_id0)+ + " face "+itos(face_id)); + // Get or create the appropriate temporary geometry for this atlas + TemporaryGeometry &tg = temp_geoms[seg_ref.atlas_id]; + if(tg.vertex_data.Empty()){ + tg.atlas_id = seg_ref.atlas_id; + // It can't get larger than this and will only exist temporarily in + // memory, so let's do only one big memory allocation + tg.vertex_data.Reserve(pv_vertices.size() / 4 * 6); + } + // Go through indices of the face and mangle vertices according to them + // into the temporary vertex buffer + size_t pv_index_i0 = pv_face_i * 6; + for(size_t pv_index_i1 = 0; pv_index_i1 < 6; pv_index_i1++){ + size_t pv_index_i = pv_index_i0 + pv_index_i1; + size_t pv_vertex_i = pv_indices[pv_index_i]; + if(pv_index_i1 == 0 && pv_vertex_i0 != pv_vertex_i) + throw Exception("First index of face does not point to first " + "vertex of face"); + const auto &pv_vert = pv_vertices[pv_vertex_i]; + tg.vertex_data.Resize(tg.vertex_data.Size() + 1); + CustomGeometryVertex &tg_vert = tg.vertex_data.Back(); + tg_vert.position_.x_ = pv_vert.position.getX() * lod + - w/2.0f - 1.0f - lod/2.0f - 0.5f; + tg_vert.position_.y_ = pv_vert.position.getY() * lod + - h/2.0f - 1.0f - lod/2.0f - 0.5f; + tg_vert.position_.z_ = pv_vert.position.getZ() * lod + - d/2.0f - 1.0f - lod/2.0f - 0.5f; + // Set real normal temporarily for assign_txcoords(). + tg_vert.normal_.x_ = pv_vert.normal.getX(); + tg_vert.normal_.y_ = pv_vert.normal.getY(); + tg_vert.normal_.z_ = pv_vert.normal.getZ(); + // Figure out texture coordinates + size_t pv_vertex_i1 = pv_vertex_i - pv_vertex_i0; + assign_txcoords(pv_vertex_i1, aseg, tg_vert); + // Don't use the real normal. + // Constant bright shading is needed so that the look of multiple + // shaded faces can be emulated by using textures. This normal + // should give it to us. + // Note that this normal isn't actually exactly correct: It should + // be the normal that points to the main light source of the scene; + // however, it is close enough. + // We can't turn off lighting for this geometry, because then we + // don't get shadows, which we do want. + tg_vert.normal_.x_ = 0; + tg_vert.normal_.y_ = 1; + tg_vert.normal_.z_ = 0; + } + } + + // Generate CustomGeometry from TemporaryGeometry + + ResourceCache *cache = context->GetSubsystem(); + + cg->Clear(); + cg->SetNumGeometries(temp_geoms.size()); Vector> &cg_all_vertices = cg->GetVertices(); diff --git a/src/impl/voxel.cpp b/src/impl/voxel.cpp index e144807..2ba6b45 100644 --- a/src/impl/voxel.cpp +++ b/src/impl/voxel.cpp @@ -138,10 +138,31 @@ struct CVoxelRegistry: public VoxelRegistry if(seg_def.resource_name == ""){ AtlasSegmentReference seg_ref; // Use default values cache.textures[i] = seg_ref; + for(size_t j = 0; j < VOXELDEF_NUM_LOD; j++){ + cache.lod_textures[j][i] = seg_ref; + } } else { - AtlasSegmentReference seg_ref = - atlas_reg->find_or_add_segment(seg_def); - cache.textures[i] = seg_ref; + { + AtlasSegmentReference seg_ref = + atlas_reg->find_or_add_segment(seg_def); + cache.textures[i] = seg_ref; + } + for(size_t j = 0; j < VOXELDEF_NUM_LOD; j++){ + int lod = 2 + j; + AtlasSegmentDefinition lod_seg_def = seg_def; + lod_seg_def.lod_simulation = lod; + if(i == 0){ + lod_seg_def.lod_simulation |= + interface::ATLAS_LOD_TOP_FACE; + } + if(i == 2/*X+*/ || i == 5/*Z-*/){ + lod_seg_def.lod_simulation |= + interface::ATLAS_LOD_HALFBRIGHT_FACE; + } + AtlasSegmentReference lod_seg_ref = + atlas_reg->find_or_add_segment(lod_seg_def); + cache.lod_textures[j][i] = lod_seg_ref; + } } } // Caller sets cache.textures_valid = true diff --git a/src/interface/atlas.h b/src/interface/atlas.h index 51d286b..168ba2a 100644 --- a/src/interface/atlas.h +++ b/src/interface/atlas.h @@ -24,11 +24,16 @@ namespace interface uint segment_id = 0; }; + const uint8_t ATLAS_LOD_TOP_FACE = 0x10; + const uint8_t ATLAS_LOD_HALFBRIGHT_FACE = 0x20; + struct AtlasSegmentDefinition { ss_ resource_name; // If "", segment won't be added magic::IntVector2 total_segments; magic::IntVector2 select_segment; + // Mask 0x0f: LOD level, mask 0xf0: flags + uint8_t lod_simulation = 0; // TODO: Rotation bool operator==(const AtlasSegmentDefinition &other) const; diff --git a/src/interface/mesh.h b/src/interface/mesh.h index 773c504..277bf42 100644 --- a/src/interface/mesh.h +++ b/src/interface/mesh.h @@ -49,6 +49,10 @@ namespace interface void set_voxel_geometry(CustomGeometry *cg, Context *context, pv::RawVolume &volume, VoxelRegistry *voxel_reg, TextureAtlasRegistry *atlas_reg); + // lod=1 -> 1:1, lod=3 -> 1:3 + void set_voxel_lod_geometry(int lod, CustomGeometry *cg, Context *context, + pv::RawVolume &volume_orig, + VoxelRegistry *voxel_reg, TextureAtlasRegistry *atlas_reg); void set_voxel_physics_boxes(Node *node, Context *context, pv::RawVolume &volume, diff --git a/src/interface/voxel.h b/src/interface/voxel.h index 9ea36a1..c55b02b 100644 --- a/src/interface/voxel.h +++ b/src/interface/voxel.h @@ -57,6 +57,8 @@ namespace interface // and what thing type that is in this case }; + static constexpr size_t VOXELDEF_NUM_LOD = 2; + // This definition should be as small as practical so that large portions of // the definition array can fit in CPU cache // (the absolute maximum number of these is VOXELTYPEID_MAX+1) @@ -70,6 +72,7 @@ namespace interface bool textures_valid = false; AtlasSegmentReference textures[6]; + AtlasSegmentReference lod_textures[VOXELDEF_NUM_LOD][6]; }; struct VoxelInstance; diff --git a/src/lua_bindings/voxel.cpp b/src/lua_bindings/voxel.cpp index 0ecf505..25b1ea7 100644 --- a/src/lua_bindings/voxel.cpp +++ b/src/lua_bindings/voxel.cpp @@ -75,7 +75,7 @@ static int l_set_simple_voxel_model(lua_State *L) SharedPtr fromScratchModel( interface::create_simple_voxel_model(context, w, h, d, data)); - StaticModel *object = node->CreateComponent(); + StaticModel *object = node->GetOrCreateComponent(); object->SetModel(fromScratchModel); return 0; @@ -114,7 +114,7 @@ static int l_set_8bit_voxel_geometry(lua_State *L) auto *voxel_reg = buildat_app->get_voxel_registry(); auto *atlas_reg = buildat_app->get_atlas_registry(); - CustomGeometry *cg = node->CreateComponent(); + CustomGeometry *cg = node->GetOrCreateComponent(); interface::set_8bit_voxel_geometry(cg, context, w, h, d, data, voxel_reg, atlas_reg); @@ -152,7 +152,7 @@ static int l_set_voxel_geometry(lua_State *L) auto *voxel_reg = buildat_app->get_voxel_registry(); auto *atlas_reg = buildat_app->get_atlas_registry(); - CustomGeometry *cg = node->CreateComponent(); + CustomGeometry *cg = node->GetOrCreateComponent(); up_> volume = interface::deserialize_volume(data); @@ -167,6 +167,48 @@ static int l_set_voxel_geometry(lua_State *L) return 0; } +// set_voxel_lod_geometry(lod: number, node: Node, buffer: VectorBuffer) +static int l_set_voxel_lod_geometry(lua_State *L) +{ + tolua_Error tolua_err; + + int lod = lua_tointeger(L, 1); + GET_TOLUA_STUFF(node, 2, Node); + TRY_GET_TOLUA_STUFF(buf, 3, const VectorBuffer); + + log_d(MODULE, "set_voxel_lod_geometry(): lod=%i", lod); + log_d(MODULE, "set_voxel_lod_geometry(): node=%p", node); + log_d(MODULE, "set_voxel_lod_geometry(): buf=%p", buf); + + ss_ data; + if(buf == nullptr) + data = lua_tocppstring(L, 2); + else + data.assign((const char*)&buf->GetBuffer()[0], buf->GetBuffer().Size()); + + lua_getfield(L, LUA_REGISTRYINDEX, "__buildat_app"); + app::App *buildat_app = (app::App*)lua_touserdata(L, -1); + lua_pop(L, 1); + Context *context = buildat_app->get_scene()->GetContext(); + auto *voxel_reg = buildat_app->get_voxel_registry(); + auto *atlas_reg = buildat_app->get_atlas_registry(); + + CustomGeometry *cg = node->GetOrCreateComponent(); + + up_> volume = interface::deserialize_volume(data); + + interface::set_voxel_lod_geometry(lod, cg, context, *volume, + voxel_reg, atlas_reg); + + // Maybe appropriate + cg->SetOccluder(true); + + // TODO: Don't do this here; allow the caller to do this + cg->SetCastShadows(true); + + return 0; +} + // set_voxel_physics_boxes(node, buffer: VectorBuffer) static int l_set_voxel_physics_boxes(lua_State *L) { @@ -192,7 +234,7 @@ static int l_set_voxel_physics_boxes(lua_State *L) up_> volume = interface::deserialize_volume(data); - RigidBody *body = node->GetOrCreateComponent(LOCAL); + node->GetOrCreateComponent(LOCAL); interface::set_voxel_physics_boxes(node, context, *volume, voxel_reg); return 0; @@ -207,6 +249,7 @@ void init_voxel(lua_State *L) DEF_BUILDAT_FUNC(set_simple_voxel_model); DEF_BUILDAT_FUNC(set_8bit_voxel_geometry); DEF_BUILDAT_FUNC(set_voxel_geometry); + DEF_BUILDAT_FUNC(set_voxel_lod_geometry); DEF_BUILDAT_FUNC(set_voxel_physics_boxes); }