Very ugly LOD mechanism
This commit is contained in:
parent
9ca7697f7e
commit
a2890529bf
@ -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(),
|
||||
})
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user