2014-10-12 10:06:07 +03:00
|
|
|
-- Buildat: builtin/voxelworld/client_lua/module.lua
|
|
|
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
-- Copyright 2014 Perttu Ahola <celeron55@gmail.com>
|
|
|
|
local log = buildat.Logger("voxelworld")
|
2014-10-12 10:11:36 +03:00
|
|
|
local magic = require("buildat/extension/urho3d")
|
|
|
|
local replicate = require("buildat/extension/replicate")
|
|
|
|
local cereal = require("buildat/extension/cereal")
|
|
|
|
local dump = buildat.dump
|
2014-10-12 10:06:07 +03:00
|
|
|
local M = {}
|
|
|
|
|
2014-10-12 10:11:36 +03:00
|
|
|
local camera_node = nil
|
2014-10-12 11:26:33 +03:00
|
|
|
local update_counter = -1
|
2014-10-13 01:07:09 +03:00
|
|
|
local camera_last_dir = magic.Vector3(0, 0, 0)
|
2014-10-13 01:32:58 +03:00
|
|
|
local camera_last_p = magic.Vector3(0, 0, 0)
|
2014-10-14 12:23:51 +03:00
|
|
|
local end_of_update_processing_us = 0
|
2014-10-12 11:26:33 +03:00
|
|
|
|
|
|
|
M.chunk_size_voxels = nil
|
|
|
|
M.section_size_chunks = nil
|
|
|
|
M.section_size_voxels = nil
|
2014-10-12 10:11:36 +03:00
|
|
|
|
2014-10-13 22:54:45 +03:00
|
|
|
--[[
|
|
|
|
table.binsearch( table, value [, compval [, reversed] ] )
|
|
|
|
|
|
|
|
Searches the table through BinarySearch for the given value.
|
|
|
|
If the value is found:
|
|
|
|
it returns a table holding all the mathing indices (e.g. { startindice,endindice } )
|
|
|
|
endindice may be the same as startindice if only one matching indice was found
|
|
|
|
If compval is given:
|
|
|
|
then it must be a function that takes one value and returns a second value2,
|
|
|
|
to be compared with the input value, e.g.:
|
|
|
|
compvalue = function( value ) return value[1] end
|
|
|
|
If reversed is set to true:
|
|
|
|
then the search assumes that the table is sorted in reverse order (largest value at position 1)
|
|
|
|
note when reversed is given compval must be given as well, it can be nil/_ in this case
|
|
|
|
Return value:
|
|
|
|
on success: a table holding matching indices (e.g. { startindice,endindice } )
|
|
|
|
on failure: nil
|
|
|
|
]]--
|
|
|
|
do
|
|
|
|
-- Avoid heap allocs for performance
|
2014-10-14 12:23:51 +03:00
|
|
|
local default_fcompval = function(value) return value end
|
|
|
|
local fcompf = function(a,b) return a < b end
|
|
|
|
local fcompr = function(a,b) return a > b end
|
|
|
|
function table_binsearch(t,value,fcompval,reversed)
|
2014-10-13 22:54:45 +03:00
|
|
|
-- Initialise functions
|
|
|
|
local fcompval = fcompval or default_fcompval
|
|
|
|
local fcomp = reversed and fcompr or fcompf
|
|
|
|
-- Initialise numbers
|
|
|
|
local iStart,iEnd,iMid = 1,#t,0
|
|
|
|
-- Binary Search
|
|
|
|
while iStart <= iEnd do
|
|
|
|
-- calculate middle
|
2014-10-14 12:23:51 +03:00
|
|
|
iMid = math.floor((iStart+iEnd)/2)
|
2014-10-13 22:54:45 +03:00
|
|
|
-- get compare value
|
2014-10-14 12:23:51 +03:00
|
|
|
local value2 = fcompval(t[iMid])
|
2014-10-13 22:54:45 +03:00
|
|
|
-- get all values that match
|
|
|
|
if value == value2 then
|
2014-10-14 12:23:51 +03:00
|
|
|
local tfound,num = {iMid,iMid},iMid - 1
|
|
|
|
while value == fcompval(t[num]) do
|
2014-10-13 22:54:45 +03:00
|
|
|
tfound[1],num = num,num - 1
|
|
|
|
end
|
|
|
|
num = iMid + 1
|
2014-10-14 12:23:51 +03:00
|
|
|
while value == fcompval(t[num]) do
|
2014-10-13 22:54:45 +03:00
|
|
|
tfound[2],num = num,num + 1
|
|
|
|
end
|
|
|
|
return tfound
|
|
|
|
-- keep searching
|
2014-10-14 12:23:51 +03:00
|
|
|
elseif fcomp(value, value2) then
|
2014-10-13 22:54:45 +03:00
|
|
|
iEnd = iMid - 1
|
|
|
|
else
|
|
|
|
iStart = iMid + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
table.bininsert( table, value [, comp] )
|
|
|
|
|
|
|
|
Inserts a given value through BinaryInsert into the table sorted by [, comp].
|
|
|
|
|
|
|
|
If 'comp' is given, then it must be a function that receives
|
|
|
|
two table elements, and returns true when the first is less
|
|
|
|
than the second, e.g. comp = function(a, b) return a > b end,
|
|
|
|
will give a sorted table, with the biggest value on position 1.
|
|
|
|
[, comp] behaves as in table.sort(table, value [, comp])
|
|
|
|
returns the index where 'value' was inserted
|
|
|
|
]]--
|
|
|
|
do
|
|
|
|
-- Avoid heap allocs for performance
|
2014-10-14 12:23:51 +03:00
|
|
|
local fcomp_default = function(a,b) return a < b end
|
2014-10-13 22:54:45 +03:00
|
|
|
function table_bininsert(t, value, fcomp)
|
|
|
|
-- Initialise compare function
|
|
|
|
local fcomp = fcomp or fcomp_default
|
|
|
|
-- Initialise numbers
|
|
|
|
local iStart,iEnd,iMid,iState = 1,#t,1,0
|
|
|
|
-- Get insert position
|
|
|
|
while iStart <= iEnd do
|
|
|
|
-- calculate middle
|
2014-10-14 12:23:51 +03:00
|
|
|
iMid = math.floor((iStart+iEnd)/2)
|
2014-10-13 22:54:45 +03:00
|
|
|
-- compare
|
2014-10-14 12:23:51 +03:00
|
|
|
if fcomp(value, t[iMid]) then
|
2014-10-13 22:54:45 +03:00
|
|
|
iEnd,iState = iMid - 1,0
|
|
|
|
else
|
|
|
|
iStart,iState = iMid + 1,1
|
|
|
|
end
|
|
|
|
end
|
2014-10-14 12:23:51 +03:00
|
|
|
table.insert(t,(iMid+iState),value)
|
2014-10-13 22:54:45 +03:00
|
|
|
return (iMid+iState)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function SpatialUpdateQueue()
|
2014-10-14 12:23:51 +03:00
|
|
|
local function fcomp(a, b) return a.d > b.d end
|
2014-10-13 22:54:45 +03:00
|
|
|
local self = {
|
2014-10-14 12:23:51 +03:00
|
|
|
p = buildat.Vector3(0, 0, 0),
|
|
|
|
queue_oldest_p = buildat.Vector3(0, 0, 0),
|
2014-10-13 23:24:51 +03:00
|
|
|
queue = {},
|
|
|
|
old_queue = nil,
|
2014-10-13 22:54:45 +03:00
|
|
|
|
2014-10-14 12:23:51 +03:00
|
|
|
-- This has to be called once per frame or so
|
|
|
|
update = function(self, max_operations)
|
|
|
|
max_operations = max_operations or 100
|
2014-10-13 23:24:51 +03:00
|
|
|
if self.old_queue then
|
2014-10-14 12:23:51 +03:00
|
|
|
log:debug("SpatialUpdateQueue(): Items in old queue: "..
|
|
|
|
#self.old_queue)
|
2014-10-13 23:24:51 +03:00
|
|
|
-- Move stuff from old queue to new queue
|
2014-10-14 12:23:51 +03:00
|
|
|
for i = 1, max_operations do
|
2014-10-13 23:24:51 +03:00
|
|
|
local item = table.remove(self.old_queue)
|
|
|
|
if not item then
|
|
|
|
self.old_queue = nil
|
|
|
|
break
|
|
|
|
end
|
2014-10-14 12:23:51 +03:00
|
|
|
item.d = (item.p - self.p):length()
|
2014-10-13 23:24:51 +03:00
|
|
|
table_bininsert(self.queue, item, fcomp)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
2014-10-13 22:54:45 +03:00
|
|
|
set_p = function(self, p)
|
2014-10-14 12:23:51 +03:00
|
|
|
p = buildat.Vector3(p) -- Strip out the heavy Urho3D wrapper
|
2014-10-13 22:54:45 +03:00
|
|
|
self.p = p
|
2014-10-13 23:24:51 +03:00
|
|
|
if self.old_queue == nil and
|
2014-10-14 12:23:51 +03:00
|
|
|
(p - self.queue_oldest_p):length() > 20 then
|
2014-10-13 23:24:51 +03:00
|
|
|
-- Move queue to old_queue and reset queue
|
|
|
|
self.old_queue = self.queue
|
2014-10-13 22:54:45 +03:00
|
|
|
self.queue = {}
|
2014-10-13 23:24:51 +03:00
|
|
|
self.queue_oldest_p = self.p
|
2014-10-13 22:54:45 +03:00
|
|
|
end
|
|
|
|
end,
|
|
|
|
put = function(self, p, value)
|
2014-10-14 12:23:51 +03:00
|
|
|
p = buildat.Vector3(p) -- Strip out the heavy Urho3D wrapper
|
|
|
|
local d = (p - self.p):length()
|
2014-10-13 22:54:45 +03:00
|
|
|
table_bininsert(self.queue, {p=p, d=d, value=value}, fcomp)
|
|
|
|
end,
|
2014-10-14 12:23:51 +03:00
|
|
|
get = function(self)
|
2014-10-13 22:54:45 +03:00
|
|
|
local item = table.remove(self.queue)
|
|
|
|
if not item then return nil end
|
|
|
|
return item.value
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2014-10-12 10:06:07 +03:00
|
|
|
function M.init()
|
|
|
|
log:info("voxelworld.init()")
|
2014-10-12 10:11:36 +03:00
|
|
|
|
|
|
|
buildat.sub_packet("voxelworld:init", function(data)
|
|
|
|
local values = cereal.binary_input(data, {"object",
|
|
|
|
{"chunk_size_voxels", {"object",
|
|
|
|
{"x", "int32_t"},
|
|
|
|
{"y", "int32_t"},
|
|
|
|
{"z", "int32_t"},
|
|
|
|
}},
|
|
|
|
{"section_size_chunks", {"object",
|
|
|
|
{"x", "int32_t"},
|
|
|
|
{"y", "int32_t"},
|
|
|
|
{"z", "int32_t"},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
log:info(dump(values))
|
2014-10-14 12:23:51 +03:00
|
|
|
M.chunk_size_voxels = buildat.Vector3(values.chunk_size_voxels)
|
|
|
|
M.section_size_chunks = buildat.Vector3(values.section_size_chunks)
|
2014-10-12 11:26:33 +03:00
|
|
|
M.section_size_voxels =
|
|
|
|
M.chunk_size_voxels:mul_components(M.section_size_chunks)
|
2014-10-12 10:11:36 +03:00
|
|
|
end)
|
|
|
|
|
2014-10-13 18:56:28 +03:00
|
|
|
local function update_voxel_geometry(node)
|
2014-10-12 23:32:54 +03:00
|
|
|
local data = node:GetVar("buildat_voxel_data"):GetBuffer()
|
2014-10-13 18:56:28 +03:00
|
|
|
--local registry_name = node:GetVar("buildat_voxel_registry_name"):GetBuffer()
|
2014-10-12 23:32:54 +03:00
|
|
|
log:info(dump(node:GetName()).." voxel data size: "..data:GetSize())
|
|
|
|
buildat.set_voxel_geometry(node, data, registry_name)
|
2014-10-13 18:56:28 +03:00
|
|
|
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())
|
|
|
|
buildat.set_voxel_physics_boxes(node, data, registry_name)
|
2014-10-12 23:32:54 +03:00
|
|
|
end
|
|
|
|
|
2014-10-13 22:54:45 +03:00
|
|
|
local node_update_queue = SpatialUpdateQueue()
|
2014-10-12 23:48:49 +03:00
|
|
|
|
2014-10-12 10:11:36 +03:00
|
|
|
magic.SubscribeToEvent("Update", function(event_type, event_data)
|
2014-10-14 12:23:51 +03:00
|
|
|
--local t0 = buildat.get_time_us()
|
|
|
|
--local dt = event_data:GetFloat("TimeStep")
|
2014-10-13 18:56:28 +03:00
|
|
|
update_counter = update_counter + 1
|
|
|
|
|
2014-10-12 13:28:24 +03:00
|
|
|
if camera_node and M.section_size_voxels then
|
2014-10-13 16:49:40 +03:00
|
|
|
-- TODO: How should position information be sent to the server?
|
2014-10-12 23:48:49 +03:00
|
|
|
local p = camera_node:GetWorldPosition()
|
2014-10-12 10:11:36 +03:00
|
|
|
if update_counter % 60 == 0 then
|
2014-10-14 12:23:51 +03:00
|
|
|
local section_p = buildat.Vector3(p):div_components(
|
2014-10-12 11:26:33 +03:00
|
|
|
M.section_size_voxels):floor()
|
2014-10-12 13:28:24 +03:00
|
|
|
--log:info("p: "..p.x..", "..p.y..", "..p.z.." -> section_p: "..
|
|
|
|
-- section_p.x..", "..section_p.y..", "..section_p.z)
|
2014-10-14 12:23:51 +03:00
|
|
|
--[[send_get_section(section_p + buildat.Vector3( 0, 0, 0))
|
|
|
|
send_get_section(section_p + buildat.Vector3(-1, 0, 0))
|
|
|
|
send_get_section(section_p + buildat.Vector3( 1, 0, 0))
|
|
|
|
send_get_section(section_p + buildat.Vector3( 0, 1, 0))
|
|
|
|
send_get_section(section_p + buildat.Vector3( 0,-1, 0))
|
|
|
|
send_get_section(section_p + buildat.Vector3( 0, 0, 1))
|
|
|
|
send_get_section(section_p + buildat.Vector3( 0, 0,-1))]]
|
2014-10-12 10:11:36 +03:00
|
|
|
end
|
|
|
|
end
|
2014-10-13 18:56:28 +03:00
|
|
|
|
2014-10-13 22:54:45 +03:00
|
|
|
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
|
|
|
|
|
2014-10-14 12:23:51 +03:00
|
|
|
-- 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
|
2014-10-13 22:54:45 +03:00
|
|
|
node_update_queue:set_p(camera_p)
|
2014-10-14 12:23:51 +03:00
|
|
|
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)
|
|
|
|
end
|
2014-10-13 18:56:28 +03:00
|
|
|
end
|
2014-10-14 12:23:51 +03:00
|
|
|
-- 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")
|
|
|
|
break
|
2014-10-12 23:32:54 +03:00
|
|
|
end
|
2014-10-13 18:56:28 +03:00
|
|
|
end
|
2014-10-14 12:23:51 +03:00
|
|
|
end_of_update_processing_us = buildat.get_time_us()
|
2014-10-13 18:56:28 +03:00
|
|
|
|
|
|
|
if camera_node then
|
2014-10-13 01:32:58 +03:00
|
|
|
camera_last_dir = camera_dir
|
|
|
|
camera_last_p = camera_p
|
2014-10-12 23:32:54 +03:00
|
|
|
end
|
2014-10-14 12:23:51 +03:00
|
|
|
--log:info(buildat.get_time_us()-t0)
|
2014-10-12 10:11:36 +03:00
|
|
|
end)
|
2014-10-12 11:26:33 +03:00
|
|
|
|
|
|
|
replicate.sub_sync_node_added({}, function(node)
|
|
|
|
if not node:GetVar("buildat_voxel_data"):IsEmpty() then
|
2014-10-13 22:54:45 +03:00
|
|
|
node_update_queue:put(node:GetWorldPosition(), {
|
|
|
|
type = "geometry",
|
2014-10-14 12:23:51 +03:00
|
|
|
node_id = node:GetID(),
|
2014-10-13 22:54:45 +03:00
|
|
|
})
|
|
|
|
node_update_queue:put(node:GetWorldPosition(), {
|
|
|
|
type = "physics",
|
2014-10-14 12:23:51 +03:00
|
|
|
node_id = node:GetID(),
|
2014-10-13 22:54:45 +03:00
|
|
|
})
|
2014-10-12 11:26:33 +03:00
|
|
|
end
|
2014-10-14 12:23:51 +03:00
|
|
|
--local name = node:GetName()
|
2014-10-12 11:26:33 +03:00
|
|
|
end)
|
2014-10-12 10:11:36 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
function M.set_camera(new_camera_node)
|
|
|
|
camera_node = new_camera_node
|
2014-10-12 10:06:07 +03:00
|
|
|
end
|
|
|
|
|
2014-10-12 11:26:33 +03:00
|
|
|
function send_get_section(p)
|
|
|
|
local data = cereal.binary_output({
|
|
|
|
p = {
|
|
|
|
x = p.x,
|
|
|
|
y = p.y,
|
|
|
|
z = p.z,
|
|
|
|
},
|
|
|
|
}, {"object",
|
|
|
|
{"p", {"object",
|
|
|
|
{"x", "int32_t"},
|
|
|
|
{"y", "int32_t"},
|
|
|
|
{"z", "int32_t"},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
--log:info(dump(buildat.bytes(data)))
|
|
|
|
buildat.send_packet("voxelworld:get_section", data)
|
|
|
|
end
|
|
|
|
|
2014-10-12 10:06:07 +03:00
|
|
|
return M
|
|
|
|
-- vim: set noet ts=4 sw=4:
|