Very ugly LOD mechanism

This commit is contained in:
Perttu Ahola 2014-10-14 14:23:55 +03:00
parent 9ca7697f7e
commit a2890529bf
11 changed files with 632 additions and 76 deletions

View File

@ -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(),
})

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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; y<seg_size.y_ * 2; y++){
for(int x = 0; x<seg_size.x_ * 2; x++){
magic::IntVector2 src_p = src_off + magic::IntVector2(
(x + seg_size.x_ / 2) % seg_size.x_,
(y + seg_size.y_ / 2) % 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_);
atlas.image->SetPixel(dst_p.x_, dst_p.y_, c);
if(def.lod_simulation == 0){
for(int y = 0; y<seg_size.y_ * 2; y++){
for(int x = 0; x<seg_size.x_ * 2; x++){
magic::IntVector2 src_p = src_off + magic::IntVector2(
(x + seg_size.x_ / 2) % seg_size.x_,
(y + seg_size.y_ / 2) % 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_);
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; y<seg_size.y_ * 2; y++){
for(int x = 0; x<seg_size.x_ * 2; x++){
if(flags & ATLAS_LOD_TOP_FACE){
// Preserve original colors
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_ *= 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

View File

@ -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<ResourceCache>();
cg->Clear();
cg->SetNumGeometries(temp_geoms.size());
Vector<PODVector<CustomGeometryVertex>> &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<CustomGeometryVertex> &cg_vertices = cg_all_vertices[cg_i];
cg_vertices = tg.vertex_data;
Material *material = new Material(context);
material->SetTechnique(0,
cache->GetResource<Technique>("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<VoxelInstance> &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<VoxelInstance> 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<VoxelInstance> iqn(voxel_reg, atlas_reg);
pv::SurfaceMesh<pv::PositionMaterialNormal> pv_mesh;
pv::CubicSurfaceExtractorWithNormals<pv::RawVolume<VoxelInstance>,
IsQuadNeededByRegistry<VoxelInstance>>
surfaceExtractor(&volume, volume.getEnclosingRegion(), &pv_mesh, iqn);
surfaceExtractor.execute();
const sv_<uint32_t> &pv_indices = pv_mesh.getIndices();
const sv_<pv::PositionMaterialNormal> &pv_vertices = pv_mesh.getVertices();
sm_<uint, TemporaryGeometry> 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<ResourceCache>();
cg->Clear();
cg->SetNumGeometries(temp_geoms.size());
Vector<PODVector<CustomGeometryVertex>> &cg_all_vertices = cg->GetVertices();

View File

@ -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

View File

@ -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;

View File

@ -49,6 +49,10 @@ namespace interface
void set_voxel_geometry(CustomGeometry *cg, Context *context,
pv::RawVolume<VoxelInstance> &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<VoxelInstance> &volume_orig,
VoxelRegistry *voxel_reg, TextureAtlasRegistry *atlas_reg);
void set_voxel_physics_boxes(Node *node, Context *context,
pv::RawVolume<VoxelInstance> &volume,

View File

@ -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;

View File

@ -75,7 +75,7 @@ static int l_set_simple_voxel_model(lua_State *L)
SharedPtr<Model> fromScratchModel(
interface::create_simple_voxel_model(context, w, h, d, data));
StaticModel *object = node->CreateComponent<StaticModel>();
StaticModel *object = node->GetOrCreateComponent<StaticModel>();
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>();
CustomGeometry *cg = node->GetOrCreateComponent<CustomGeometry>();
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>();
CustomGeometry *cg = node->GetOrCreateComponent<CustomGeometry>();
up_<pv::RawVolume<VoxelInstance>> 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<CustomGeometry>();
up_<pv::RawVolume<VoxelInstance>> 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_<pv::RawVolume<VoxelInstance>> volume = interface::deserialize_volume(data);
RigidBody *body = node->GetOrCreateComponent<RigidBody>(LOCAL);
node->GetOrCreateComponent<RigidBody>(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);
}