aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authororwell96 <orwell@bleipb.de>2018-04-25 16:38:12 +0200
committerorwell96 <orwell@bleipb.de>2018-06-14 17:39:42 +0200
commit139a26fccce1e622d58f1673284e2addfb0d1ed2 (patch)
treea7df19f93e09a9b7699d93a4dbe07714164fe083
parent30e98520e7e6fa17beaf3f1c328fd58e2b19cebc (diff)
Bugfixes part 1
There's something wrong with the new paths, next time build a path validity checker to trace the issue
-rw-r--r--advtrains/helpers.lua10
-rw-r--r--advtrains/init.lua47
-rw-r--r--advtrains/nodedb.lua4
-rw-r--r--advtrains/occupation.lua27
-rw-r--r--advtrains/path.lua99
-rw-r--r--advtrains/textures/advtrains_wagon_placeholder.pngbin0 -> 723 bytes
-rw-r--r--advtrains/trainhud.lua2
-rw-r--r--advtrains/trainlogic.lua88
-rw-r--r--advtrains/wagons.lua134
9 files changed, 227 insertions, 184 deletions
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index 70afadd..9ad5a8e 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -103,8 +103,8 @@ function advtrains.yawToClosestConn(yaw, conns)
end
function advtrains.dir_to_angle(dir)
- local uvec = vector.normalize(advtrains.dirToCoord())
- local yaw1 = math.atan2(uvec.z, -uvec.x)
+ local uvec = vector.normalize(advtrains.dirToCoord(dir))
+ return math.atan2(uvec.z, -uvec.x)
end
@@ -304,8 +304,8 @@ function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_
end
if not conn_idx then
for coni, _ in ipairs(this_conns) do
- local adj_pos, adj_conn_idx, _, nry = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
- if adj_pos then return adj_pos,adj_conn_idx,coni,nry end
+ local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
+ if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end
end
return nil
end
@@ -330,7 +330,7 @@ function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_
end
local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns)
if adj_connid then
- return adj_pos, adj_connid, conn_idx, nextrail_y
+ return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns
end
return nil
end
diff --git a/advtrains/init.lua b/advtrains/init.lua
index 0043c22..69b482d 100644
--- a/advtrains/init.lua
+++ b/advtrains/init.lua
@@ -1,3 +1,7 @@
+
+local lot = os.clock()
+minetest.log("action", "[advtrains] Loading...")
+
-- Boilerplate to support localized strings if intllib mod is installed.
if minetest.get_modpath("intllib") then
attrans = intllib.Getter()
@@ -10,7 +14,7 @@ end
--Constant for maximum connection value/division of the circle
AT_CMAX = 16
-advtrains = {trains={}, wagon_save={}, player_to_train_mapping={}}
+advtrains = {trains={}, player_to_train_mapping={}}
--pcall
local no_action=false
@@ -34,24 +38,16 @@ end
function advtrains.pcall(fun)
if no_action then return end
- local succ, return1, return2, return3, return4=xpcall(fun, function(err)
- if advtrains.atprint_context_tid then
- local train=advtrains.trains[advtrains.atprint_context_tid_full]
- advtrains.dumppath(train.path)
- atwarn("Dumping last debug outputs: ", err)
- atprint("Train state: index",train.index,"end_index", train.end_index,"| max_iot", train.max_index_on_track, "min_iot", train.min_index_on_track, "<> pe_min", train.path_extent_min,"pe_max", train.path_extent_max)
- if minetest.settings:get_bool("advtrains_enable_debugging") then
- advtrains.drb_dump(advtrains.atprint_context_tid)
- end
- end
- atwarn("Lua Error occured: ", err)
- atwarn(debug.traceback())
- end)
- if not succ then
- reload_saves()
- else
- return return1, return2, return3, return4
- end
+ --local succ, return1, return2, return3, return4=xpcall(fun, function(err)
+ -- atwarn("Lua Error occured: ", err)
+ -- atwarn(debug.traceback())
+ -- end)
+ --if not succ then
+ -- reload_saves()
+ --else
+ -- return return1, return2, return3, return4
+ --end
+ return fun()
end
@@ -149,6 +145,7 @@ dofile(advtrains.modpath.."/trainlogic.lua")
dofile(advtrains.modpath.."/trainhud.lua")
dofile(advtrains.modpath.."/trackplacer.lua")
dofile(advtrains.modpath.."/tracks.lua")
+dofile(advtrains.modpath.."/occupation.lua")
dofile(advtrains.modpath.."/atc.lua")
dofile(advtrains.modpath.."/wagons.lua")
dofile(advtrains.modpath.."/protection.lua")
@@ -282,10 +279,6 @@ advtrains.avt_save = function(remove_players_from_wagons)
"atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open",
"text_outside", "text_inside", "couple_lck_front", "couple_lck_back", "line"
})
- --then invalidate
- if train.index then
- v.restore_add_index=train.index-math.floor(train.index+1)
- end
--then save it
tmp_trains[id]=v
end
@@ -294,7 +287,7 @@ advtrains.avt_save = function(remove_players_from_wagons)
-- 1 - Initial new save format.
local save_tbl={
trains = tmp_trains,
- wagon_save = advtrains.wagon_save,
+ wagon_save = advtrains.wagons,
ptmap = advtrains.player_to_train_mapping,
atc = advtrains.atc.save_data(),
ndb = advtrains.ndb.save_data(),
@@ -351,7 +344,7 @@ minetest.register_globalstep(function(dtime_mt)
if save_timer<=0 then
local t=os.clock()
--save
- advtrains.save()
+ --advtrains.save()
save_timer=save_interval
atprintbm("saving", t)
end
@@ -390,7 +383,7 @@ function advtrains.save(remove_players_from_wagons)
end
atprint("[save_all]Saved advtrains save files")
end
-minetest.register_on_shutdown(advtrains.save)
+--minetest.register_on_shutdown(advtrains.save)
-- This chat command provides a solution to the problem known on the LinuxWorks server
-- There are many players that joined a single time, got on a train and then left forever
@@ -422,3 +415,5 @@ minetest.register_chatcommand("at_reroute",
end,
})
+local tot=(os.clock()-lot)*1000
+minetest.log("action", "[advtrains] Loaded in "..tot.."ms")
diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua
index 592e730..6695eba 100644
--- a/advtrains/nodedb.lua
+++ b/advtrains/nodedb.lua
@@ -210,7 +210,7 @@ ndb.run_lbm = function(pos, node)
end
else
--if not in database, take it.
- atlog("Node Database:", pos, "was not found in the database, have you used worldedit?")
+ --atlog("Node Database:", pos, "was not found in the database, have you used worldedit?")
ndb.update(pos, node)
end
return false
@@ -256,7 +256,7 @@ ndb.restore_all = function()
end
end
end
- local text="Restore node database: Replaced"..cnt.."nodes, removed"..dcnt.."ghost nodes."
+ local text="Restore node database: Replaced "..cnt.." nodes, removed "..dcnt.." ghost nodes."
atlog(text)
return text
end
diff --git a/advtrains/occupation.lua b/advtrains/occupation.lua
index f6fa6fc..7af4a3d 100644
--- a/advtrains/occupation.lua
+++ b/advtrains/occupation.lua
@@ -64,7 +64,7 @@ Composition of a step:
4. we iterate our change lists and determine what to do
]]--
-local o
+local o = {}
o.restore_required = true
@@ -74,6 +74,8 @@ local seqnum = 0
local occ = {}
local occ_chg = {}
+local addchg, handle_chg
+
local function occget(p)
local t = occ[p.y]
@@ -81,10 +83,11 @@ local function occget(p)
occ[p.y] = {}
t = occ[p.y]
end
+ local s = t
t = t[p.x]
if not t then
- t[p.x] = {}
- t = t[p.x]
+ s[p.x] = {}
+ t = s[p.x]
end
return t[p.z]
end
@@ -94,15 +97,17 @@ local function occgetcreate(p)
occ[p.y] = {}
t = occ[p.y]
end
+ local s = t
t = t[p.x]
if not t then
- t[p.x] = {}
- t = t[p.x]
+ s[p.x] = {}
+ t = s[p.x]
end
+ s = t
t = t[p.z]
if not t then
- t[p.z] = {}
- t = t[p.z]
+ s[p.z] = {}
+ t = s[p.z]
end
return t
end
@@ -119,7 +124,7 @@ end
function o.init_occupation(train_id, pos, oid)
local t = occgetcreate(pos)
local i = 1
- while t[i]
+ while t[i] do
if t[i]==train_id then
break
end
@@ -132,7 +137,7 @@ end
function o.set_occupation(train_id, pos, oid)
local t = occgetcreate(pos)
local i = 1
- while t[i]
+ while t[i] do
if t[i]==train_id then
break
end
@@ -171,7 +176,7 @@ function o.clear_occupation(train_id, pos)
end
end
-local function addchg(pos, train_id, old, new)
+function addchg(pos, train_id, old, new)
occ_chg[#occ_chg + 1] = {
pos = pos,
train_id = train_id,
@@ -196,7 +201,7 @@ function o.end_step()
seqnum = seqnum + 1
end
-local function handle_chg(t, pos, train_id, old, new)
+function handle_chg(t, pos, train_id, old, new)
-- Handling the actual "change" is only necessary on_train_enter (change to 1) and on_train_leave (change from 1)
if new==1 then
o.call_enter_callback(pos, train_id)
diff --git a/advtrains/path.lua b/advtrains/path.lua
index a98a1b5..039085c 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -113,8 +113,8 @@ function advtrains.path_create(train, pos, connid, rel_index)
train.path_dist = {}
train.path_dir = {
- [ 0] = conns[connid],
- [-1] = conns[mconnid]
+ [1] = conns[connid].c,
+ [0] = advtrains.oppd(conns[mconnid].c)
}
train.path_ext_f=0
@@ -123,31 +123,49 @@ function advtrains.path_create(train, pos, connid, rel_index)
train.path_trk_b=0
train.path_req_f=0
train.path_req_b=0
+ atdebug("path_create",train)
end
+function advtrains.path_invalidate(train)
+ train.path = nil
+ train.path_dist = nil
+ train.path_cp = nil
+ train.path_cn = nil
+ train.path_dir = nil
+ train.path_ext_f=0
+ train.path_ext_b=0
+ train.path_trk_f=0
+ train.path_trk_b=0
+ train.path_req_f=0
+ train.path_req_b=0
+end
+
-- Function to get path entry at a position. This function will automatically calculate more of the path when required.
-- returns: pos, on_track
function advtrains.path_get(train, index)
+ if not train.path then
+ error("For train "..train.id..": path_get called but there's no path set yet!")
+ end
if index ~= atfloor(index) then
error("For train "..train.id..": Called path_get() but index="..index.." is not a round number")
end
while index > train.path_ext_f do
local pos = train.path[train.path_ext_f]
local connid = train.path_cn[train.path_ext_f]
- local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns
if train.path_ext_f == train.path_trk_f then
- node_ok, this_conns = advtrains.get_rail_info_at(this_pos)
+ node_ok, this_conns = advtrains.get_rail_info_at(pos)
if not node_ok then error("For train "..train.id..": Path item "..train.path_ext_f.." on-track but not a valid node!") end
- adj_pos, adj_connid, conn_idx, nextrail_y = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
end
train.path_ext_f = train.path_ext_f + 1
if adj_pos then
adj_pos.y = adj_pos.y + nextrail_y
train.path_cp[train.path_ext_f] = adj_connid
- local mconnid = advtrains.get_matching_conn(adj_connid)
+ local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
train.path_cn[train.path_ext_f] = mconnid
- train.path_dir[train.path_ext_f] = this_conns[mconnid]
+ train.path_dir[train.path_ext_f+1] = this_conns[mconnid].c
train.path_trk_f = train.path_ext_f
else
-- off-track fallback behavior
@@ -160,19 +178,21 @@ function advtrains.path_get(train, index)
while index < train.path_ext_b do
local pos = train.path[train.path_ext_b]
local connid = train.path_cp[train.path_ext_b]
- local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns
if train.path_ext_b == train.path_trk_b then
- node_ok, this_conns = advtrains.get_rail_info_at(this_pos)
+ node_ok, this_conns = advtrains.get_rail_info_at(pos)
if not node_ok then error("For train "..train.id..": Path item "..train.path_ext_f.." on-track but not a valid node!") end
- adj_pos, adj_connid, conn_idx, nextrail_y = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
end
train.path_ext_b = train.path_ext_b - 1
if adj_pos then
adj_pos.y = adj_pos.y + nextrail_y
- train.path_cp[train.path_ext_b] = adj_connid
- local mconnid = advtrains.get_matching_conn(adj_connid)
- train.path_cn[train.path_ext_b] = mconnid
- train.path_dir[train.path_ext_b] = advtrains.oppd(this_conns[mconnid]) --we need to rotate this here so that it points in positive path direction
+ train.path_cn[train.path_ext_b] = adj_connid
+ local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns)
+ train.path_cp[train.path_ext_b] = mconnid
+
+ train.path_dir[train.path_ext_b] = advtrains.oppd(this_conns[mconnid].c)
+
train.path_trk_b = train.path_ext_b
else
-- off-track fallback behavior
@@ -200,7 +220,7 @@ function advtrains.path_get_interpolated(train, index)
local i_floor = atfloor(index)
local i_ceil = i_floor + 1
local frac = index - i_floor
- local p_floor, = advtrains.path_get(train, i_floor)
+ local p_floor = advtrains.path_get(train, i_floor)
local p_ceil = advtrains.path_get(train, i_ceil)
-- Note: minimal code duplication to path_get_adjacent, for performance
@@ -211,7 +231,7 @@ function advtrains.path_get_interpolated(train, index)
local ang = advtrains.minAngleDiffRad(a_floor, a_ceil)
- return vector.add(p_floor, vector.multiply(vector.subtract(npos2, npos), frac), (a_floor + frac * ang)%(2*math.pi), p_floor, p_ceil -- TODO does this behave correctly?
+ return vector.add(p_floor, vector.multiply(vector.subtract(p_ceil, p_floor), frac)), (a_floor + frac * ang)%(2*math.pi), p_floor, p_ceil -- TODO does this behave correctly?
end
-- returns the 2 path positions directly adjacent to index and the fraction on how to interpolate between them
-- returns: pos_floor, pos_ceil, fraction
@@ -219,26 +239,43 @@ function advtrains.path_get_adjacent(train, index)
local i_floor = atfloor(index)
local i_ceil = i_floor + 1
local frac = index - i_floor
- local p_floor, = advtrains.path_get(train, i_floor)
+ local p_floor = advtrains.path_get(train, i_floor)
local p_ceil = advtrains.path_get(train, i_ceil)
return p_floor, p_ceil, frac
end
function advtrains.path_get_index_by_offset(train, index, offset)
- local pos_in_train_left=pit
- local index=train.index
- if pos_in_train_left>(index-math.floor(index))*(train.path_dist[math.floor(index)] or 1) then
- pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(train.path_dist[math.floor(index)] or 1)
- index=math.floor(index)
- while pos_in_train_left>(train.path_dist[index-1] or 1) do
- pos_in_train_left=pos_in_train_left - (train.path_dist[index-1] or 1)
- index=index-1
+ local off = offset
+ local idx = atfloor(index)
+ -- go down to floor. Calculate required path_dist
+ advtrains.path_get_adjacent(train, idx)
+ off = off + ((index-idx) * train.path_dist[idx])
+ --atdebug("pibo: 1 off=",off,"idx=",idx," index=",index)
+
+ -- then walk the path back until we overshoot (off becomes >=0)
+ while off<0 do
+ idx = idx - 1
+ advtrains.path_get_adjacent(train, idx)
+ off = off + train.path_dist[idx]
+ end
+ --atdebug("pibo: 2 off=",off,"idx=",idx)
+ -- then walk the path forward until we would overshoot
+ while off - train.path_dist[idx] >= 0 do
+ idx = idx - 1
+ advtrains.path_get_adjacent(train, idx)
+ if not train.path_dist[idx] then
+ atdebug("second while",idx)
+ for i=-5,5 do
+ atdebug(idx+i,train.path_dist[idx+i])
+ end
end
- index=index-(pos_in_train_left/(train.path_dist[index-1] or 1))
- else
- index=index-(pos_in_train_left/(train.path_dist[math.floor(index-1)] or 1))
+ off = off - train.path_dist[idx]
end
- return index
+ --atdebug("pibo: 3 off=",off,"idx=",idx," returns:",idx + (off / train.path_dist[idx]))
+ -- we should now be on the floor of the index we actually want.
+ -- give them the rest!
+
+ return idx + (off / train.path_dist[idx])
end
local PATH_CLEAR_KEEP = 2
@@ -246,14 +283,14 @@ local PATH_CLEAR_KEEP = 2
function advtrains.path_clear_unused(train)
for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do
train.path[i] = nil
- train.path_dist[i] = nil
+ train.path_dist[i-1] = nil
train.path_cp[i] = nil
train.path_cn[i] = nil
train.path_dir[i] = nil
end
for i = train.path_req_f + PATH_CLEAR_KEEP, train.path_ext_f do
train.path[i] = nil
- train.path_dist[i-1] = nil
+ train.path_dist[i] = nil
train.path_cp[i] = nil
train.path_cn[i] = nil
train.path_dir[i+1] = nil
diff --git a/advtrains/textures/advtrains_wagon_placeholder.png b/advtrains/textures/advtrains_wagon_placeholder.png
new file mode 100644
index 0000000..383c181
--- /dev/null
+++ b/advtrains/textures/advtrains_wagon_placeholder.png
Binary files differ
diff --git a/advtrains/trainhud.lua b/advtrains/trainhud.lua
index e48e3e4..26af2a1 100644
--- a/advtrains/trainhud.lua
+++ b/advtrains/trainhud.lua
@@ -60,7 +60,7 @@ function advtrains.on_control_change(pc, train, flip)
end
act=true
else
- train.movedir = -train.movedir
+ --train.movedir = -train.movedir
end
end
if pc.left then
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
index 110d3f6..12ec8b5 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -70,24 +70,26 @@ advtrains.mainloop_trainlogic=function(dtime)
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
- train_ensure_clean(k, v, dtime)
+ advtrains.train_ensure_clean(k, v, dtime, advtrains.occ.restore_required)
end
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
- train_step_b(k, v, dtime)
+ advtrains.train_step_b(k, v, dtime)
end
for k,v in pairs(advtrains.trains) do
advtrains.atprint_context_tid=sid(k)
advtrains.atprint_context_tid_full=k
- train_step_c(k, v, dtime)
+ advtrains.train_step_c(k, v, dtime)
end
advtrains.atprint_context_tid=nil
advtrains.atprint_context_tid_full=nil
+ advtrains.occ.end_step()
+
atprintbm("trainsteps", t)
endstep()
end
@@ -216,7 +218,7 @@ local function write_occupation(win, train_id, train, write_mode)
local c_index = math.ceil(win[1])
while win[n_window] do
local winix = win[n_window]
- local oid = WINDOW_ZONE_IDS[n_windows - 1]
+ local oid = WINDOW_ZONE_IDS[n_window - 1]
while winix > c_index do
local pos = advtrains.path_get(train, c_index)
if write_mode == 1 then
@@ -242,7 +244,7 @@ end
-- - the train's path got cleared
-- - the occupation table got cleared
-- Additionally, this gets called outside the step cycle to initialize and/or remove a train, then occ_write_mode is set.
-local function train_ensure_clean(id, train, dtime, report_occupations, occ_write_mode)
+function advtrains.train_ensure_clean(id, train, dtime, report_occupations, occ_write_mode)
train.dirty = true
if train.no_step then return end
@@ -258,29 +260,31 @@ local function train_ensure_clean(id, train, dtime, report_occupations, occ_writ
--restore path
if not train.path then
if not train.last_pos then
- atwarn("Train",id": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.")
+ atwarn("Train",id,": Restoring path failed, no last_pos set! Train will be disabled. You can try to fix the issue in the save file.")
train.no_step = true
return
end
if not train.last_connid then
- atwarn("Train",id": Restoring path failed, no last_connid set! Will assume 1")
+ atwarn("Train",id,": Restoring path: no last_connid set! Will assume 1")
end
- local result = advtrains.path_create(train, train.last_pos, train.last_connid, train.last_frac or 0)
+ local result = advtrains.path_create(train, train.last_pos, train.last_connid or 1, train.last_frac or 0)
if result==false then
- atwarn("Train",id": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to fix the issue in the save file.")
+ atwarn("Train",id,": Restoring path failed, node at",train.last_pos,"is gone! Train will be disabled. You can try to fix the issue in the save file.")
train.no_step = true
return
elseif result==nil then
if not train.wait_for_path then
- atwarn("Train",id": Can't initialize: Waiting for the (yet unloaded) node at",train.last_pos," to be loaded.")
+ atwarn("Train",id,": Can't initialize: Waiting for the (yet unloaded) node at",train.last_pos," to be loaded.")
end
train.wait_for_path = true
end
-- by now, we should have a working initial path
+ train.wait_for_path = false
train.occwindows = nil
advtrains.update_trainpart_properties(id)
+ atdebug("Train",id,": Successfully restored path at",train.last_pos," connid",train.last_connid," frac",train.last_frac)
-- TODO recoverposition?!
end
@@ -289,18 +293,18 @@ local function train_ensure_clean(id, train, dtime, report_occupations, occ_writ
train.occwindows = calc_occwindows(id, train)
end
if report_occupations then
- write_occupation(train.occwindows, train, occ_write_mode)
+ write_occupation(train.occwindows, id, train, occ_write_mode)
end
train.dirty = false -- TODO einbauen!
end
-local function train_step_b(id, train, dtime)
+function advtrains.train_step_b(id, train, dtime)
if train.no_step or train.wait_for_path then return end
-- in this code, we check variables such as path_trk_? and path_dist. We need to ensure that the path is known for the whole 'Train' zone
- advtrains.path_get(train, train.index + 1)
- advtrains.path_get(train, train.end_index - 1)
+ advtrains.path_get(train, atfloor(train.index + 2))
+ advtrains.path_get(train, atfloor(train.end_index - 1))
--- 3. handle velocity influences ---
local train_moves=(train.velocity~=0)
@@ -318,7 +322,7 @@ local function train_step_b(id, train, dtime)
end
--- 3a. this can be useful for debugs/warnings and is used for check_trainpartload ---
- local t_info, train_pos=sid(id), advtrains.path_get(atfloor(train.index))
+ local t_info, train_pos=sid(id), advtrains.path_get(train, atfloor(train.index))
if train_pos then
t_info=t_info.." @"..minetest.pos_to_string(train_pos)
--atprint("train_pos:",train_pos)
@@ -416,13 +420,13 @@ local function train_step_b(id, train, dtime)
end
-local function train_recalc_occupation()
+local function train_recalc_occupation(id, train)
local new_occwindows = calc_occwindows(id, train)
apply_occupation_changes(train.occwindows, new_occwindows, id)
train.occwindows = new_occwindows
end
-local function train_step_c(id, train, dtime)
+function advtrains.train_step_c(id, train, dtime)
if train.no_step or train.wait_for_path then return end
-- all location/extent-critical actions have been done.
@@ -433,7 +437,7 @@ if train.no_step or train.wait_for_path then return end
-- Set our path restoration position
local fli = atfloor(train.index)
- train.last_pos = advtrains.path_get(fli)
+ train.last_pos = advtrains.path_get(train, fli)
train.last_connid = train.path_cn[fli]
train.last_frac = train.index - fli
@@ -453,7 +457,8 @@ if train.no_step or train.wait_for_path then return end
local collpos
local coll_grace=1
- collpos=advtrains.path_get_index_by_offset(train, train.index-coll_grace)
+ local collindex = advtrains.path_get_index_by_offset(train, train.index, coll_grace)
+ collpos = advtrains.path_get(train, atround(collindex))
if collpos then
local rcollpos=advtrains.round_vector_floor_y(collpos)
for x=-train.extent_h,train.extent_h do
@@ -509,8 +514,8 @@ function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
local new_id=advtrains.random_id()
while advtrains.trains[new_id] do new_id=advtrains.random_id() end--ensure uniqueness
- t={}
- t.id = newtrain_id
+ local t={}
+ t.id = new_id
t.last_pos=pos
t.last_connid=connid
@@ -520,22 +525,24 @@ function advtrains.create_new_train_at(pos, connid, ioff, trainparts)
t.velocity=0
t.trainparts=trainparts
-
advtrains.trains[new_id] = t
+ atdebug("Created new train:",t)
advtrains.update_trainpart_properties(new_id)
- train_ensure_clean(new_id, advtrains.trains[new_id], 0, true, 1)
+ advtrains.train_ensure_clean(new_id, advtrains.trains[new_id], 0, true, 1)
- return newtrain_id
+ return new_id
end
function advtrains.remove_train(id)
local train = advtrains.trains[id]
+ advtrains.train_ensure_clean(id, train)
+
advtrains.update_trainpart_properties(id)
- train_ensure_clean(id, train, 0, true, 2)
+ advtrains.train_ensure_clean(id, train, 0, true, 2)
local tp = train.trainparts
@@ -549,7 +556,7 @@ end
function advtrains.add_wagon_to_train(wagon_id, train_id, index)
local train=advtrains.trains[train_id]
- train_ensure_clean(train_id, train)
+ advtrains.train_ensure_clean(train_id, train)
if index then
table.insert(train.trainparts, index, wagon_id)
@@ -578,10 +585,10 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate)
-- 1st: update wagon data (pos_in_train a.s.o)
if data then
- local wagon = minetest.registered_luaentites[data.type]
+ local wagon = advtrains.wagon_prototypes[data.type]
if not wagon then
atwarn("Wagon '",data.type,"' couldn't be found. Please check that all required modules are loaded!")
- wagon = minetest.registered_luaentites["advtrains:wagon_placeholder"]
+ wagon = advtrains.wagon_prototypes["advtrains:wagon_placeholder"]
end
rel_pos=rel_pos+wagon.wagon_span
@@ -608,6 +615,8 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate)
train.extent_h = math.max(train.extent_h, wagon.extent_h or 1);
end
end
+ train.trainlen = rel_pos
+ train.locomotives_in_train = count_l
end
-- This function checks whether entities need to be spawned for certain wagons, and spawns them.
@@ -624,13 +633,11 @@ function advtrains.spawn_wagons(train_id)
if minetest.get_node_or_nil(pos) then
local wt = advtrains.get_wagon_prototype(data)
- wagon=minetest.add_entity(pos, wt):get_luaentity()
+ local wagon = minetest.add_entity(pos, wt):get_luaentity()
wagon:set_id(w_id)
end
end
end
-
-
end
end
@@ -641,7 +648,7 @@ function advtrains.split_train_at_wagon(wagon_id)
local train=advtrains.trains[data.train_id]
local _, wagon = advtrains.get_wagon_prototype(data)
- train_ensure_clean(data.train_id, train)
+ advtrains.train_ensure_clean(data.train_id, train)
local index=advtrains.path_get_index_by_offset(train, train.index, -(data.pos_in_train + wagon.wagon_span))
@@ -901,21 +908,8 @@ end
function advtrains.invalidate_path(id)
local v=advtrains.trains[id]
if not v then return end
- --TODO duplicate code in init.lua avt_save()!
- if v.index then
- v.restore_add_index=v.index-math.floor(v.index+1)
- end
- v.path=nil
- v.path_dist=nil
- v.index=nil
- v.end_index=nil
- v.min_index_on_track=nil
- v.max_index_on_track=nil
- v.path_extent_min=nil
- v.path_extent_max=nil
-
- v.detector_old_index=nil
- v.detector_old_end_index=nil
+ advtrains.path_invalidate(v)
+ v.dirty = true
end
--not blocking trains group
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index 83f919e..c3217e2 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -2,24 +2,27 @@
-- Holds all logic related to wagons
-- From now on, wagons are, just like trains, just entries in a table
-- All data that is static is stored in the entity prototype (self).
--- A copy of the entity prototype is always available inside minetest.registered_luaentities
+-- A copy of the entity prototype is always available inside wagon_prototypes
-- All dynamic data is stored in the (new) wagons table
-- An entity is ONLY spawned by update_trainpart_properties when it finds it useful.
-- Only data that are only important to the entity itself are stored in the luaentity
advtrains.wagons = {}
+advtrains.wagon_prototypes = {}
+
--
-function advtrains.create_wagon(type, owner)
+function advtrains.create_wagon(wtype, owner)
local new_id=advtrains.random_id()
while advtrains.wagons[new_id] do new_id=advtrains.random_id() end
local wgn = {}
- wgn.type = type
+ wgn.type = wtype
wgn.seatp = {}
wgn.owner = owner
wgn.id = new_id
---wgn.train_id = train_id --- will get this via update_trainpart_properties
advtrains.wagons[new_id] = wgn
+ atdebug("Created new wagon:",wgn)
return new_id
end
@@ -59,6 +62,9 @@ function wagon:set_id(wid)
local data = advtrains.wagons[self.id]
+ data.object = self.object
+ atdebug("Created wagon entity:",self.name," w_id",wid," t_id",data.train_id)
+
if self.has_inventory then
--to be used later
local inv=minetest.create_detached_inventory("advtrains_wgn_"..self.id, {
@@ -89,20 +95,27 @@ function wagon:set_id(wid)
if self.custom_on_activate then
self:custom_on_activate(dtime_s)
end
+end
function wagon:get_staticdata()
return "STATIC"
end
function wagon:ensure_init()
- if self.initialized then
- if self.noninitticks then self.noninitticks=nil end
- return true
+ -- Note: A wagon entity won't exist when there's no train, because the train is
+ -- the thing that actually creates the entity
+ -- Train not being set just means that this will happen as soon as the train calls update_trainpart_properties.
+ if self.initialized and self.id then
+ local data = advtrains.wagons[self.id]
+ if data.train_id then
+ if self.noninitticks then self.noninitticks=nil end
+ return true
+ end
end
if not self.noninitticks then self.noninitticks=0 end
self.noninitticks=self.noninitticks+1
if self.noninitticks>20 then
- self.object:remove()
+ self:destroy()
else
self.object:setvelocity({x=0,y=0,z=0})
end
@@ -110,7 +123,8 @@ function wagon:ensure_init()
end
function wagon:train()
- return advtrains.trains[self.train_id]
+ local data = advtrains.wagons[self.id]
+ return advtrains.trains[data.train_id]
end
-- Remove the wagon
@@ -158,26 +172,28 @@ function wagon:destroy()
-- single left-click shows warning
-- shift leftclick destroys
-- not when a driver is inside
- local data = advtrains.wagons[self.id]
-
- if self.custom_on_destroy then
- self.custom_on_destroy(self)
- end
-
- for seat,_ in pairs(data.seatp) do
- self:get_off(seat)
+ if self.id then
+ local data = advtrains.wagons[self.id]
+
+ if self.custom_on_destroy then
+ self.custom_on_destroy(self)
+ end
+
+ for seat,_ in pairs(data.seatp) do
+ self:get_off(seat)
+ end
+
+ if data.train_id and self:train() then
+ table.remove(self:train().trainparts, data.pos_in_trainparts)
+ advtrains.update_trainpart_properties(self.train_id)
+ advtrains.wagons[self.id]=nil
+ if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects
+ return true
+ end
end
-
atprint("[wagon ", self.id, "]: destroying")
self.object:remove()
-
- if self.train_id and self:train() then
- table.remove(self:train().trainparts, data.pos_in_trainparts)
- advtrains.update_trainpart_properties(self.train_id)
- advtrains.wagons[self.id]=nil
- if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects
- return true
end
@@ -190,16 +206,10 @@ function wagon:on_step(dtime)
local data = advtrains.wagons[self.id]
if not pos then
- atprint("["..self.unique_id.."][fatal] missing position (object:getpos() returned nil)")
+ atdebug("["..self.id.."][fatal] missing position (object:getpos() returned nil)")
return
end
- --is my train still here
- if not self.train_id or not self:train() then
- atprint("[wagon "..self.unique_id.."] missing train_id, destroying")
- self:destroy()
- return
- end
if not data.seatp then
data.seatp={}
end
@@ -331,7 +341,7 @@ function wagon:on_step(dtime)
if data.pos_in_trainparts and data.pos_in_trainparts>1 then
if train.velocity==0 and not data.dcpl_lock then
if not self.discouple or not self.discouple.object:getyaw() then
- atprint(self.unique_id,"trying to spawn discouple")
+ atprint(self.id,"trying to spawn discouple")
local yaw = self.object:getyaw()
local flipsign=data.wagon_flipped and -1 or 1
local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span*flipsign, z=math.cos(yaw)*self.wagon_span*flipsign})
@@ -342,7 +352,7 @@ function wagon:on_step(dtime)
--box is hidden when attached, so unuseful.
--object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
self.discouple=le
- atprint(self.unique_id,"success")
+ atprint(self.id,"success")
else
atprint("Couldn't spawn DisCouple")
end
@@ -370,7 +380,7 @@ function wagon:on_step(dtime)
--automatic get_on
--needs to know index and path
- if self.door_entry and gp.door_open and gp.door_open~=0 and gp.velocity==0 then
+ if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
--using the mapping created by the trainlogic globalstep
for i, ino in ipairs(self.door_entry) do
--fct is the flipstate flag from door animation above
@@ -399,7 +409,7 @@ function wagon:on_step(dtime)
end
--checking for environment collisions(a 3x3 cube around the center)
- if not gp.recently_collided_with_env then
+ if not train.recently_collided_with_env then
local collides=false
local exh = self.extent_h or 1
local exv = self.extent_v or 2
@@ -407,9 +417,10 @@ function wagon:on_step(dtime)
for y=0,exv do
for z=-exh,exh do
local node=minetest.get_node_or_nil(vector.add(npos, {x=x, y=y, z=z}))
- if (advtrains.train_collides(node)) then
- collides=true
- end
+ -- TODO
+ --if (advtrains.train_collides(node)) then
+ -- collides=true
+ --end
end
end
end
@@ -432,8 +443,8 @@ function wagon:on_step(dtime)
end
--FIX: use index of the wagon, not of the train.
- local velocity = (gp.velocity*gp.movedir)
- local acceleration = (gp.last_accel or 0)
+ local velocity = train.velocity
+ local acceleration = (train.acceleration or 0)
local velocityvec = vector.multiply(vdir, velocity)
local accelerationvec = vector.multiply(vdir, acceleration)
@@ -482,13 +493,13 @@ function wagon:on_step(dtime)
self:update_animation(gp.velocity, self.old_velocity)
end
if self.custom_on_velocity_change then
- self:custom_on_velocity_change(gp.velocity, self.old_velocity or 0, dtime)
+ self:custom_on_velocity_change(train.velocity, self.old_velocity or 0, dtime)
end
end
self.old_velocity_vector=velocityvec
- self.old_velocity = gp.velocity
+ self.old_velocity = train.velocity
self.old_acceleration_vector=accelerationvec
self.old_yaw=yaw
atprintbm("wagon step", t)
@@ -542,7 +553,7 @@ function wagon:on_rightclick(clicker)
for pos,ent in ipairs(poss) do
form = form .. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"
end
- minetest.show_formspec(pname, "advtrains_seating_"..self.unique_id, form)
+ minetest.show_formspec(pname, "advtrains_seating_"..self.id, form)
end
else
self:get_off(no)
@@ -553,7 +564,7 @@ function wagon:on_rightclick(clicker)
if self.seat_groups then
if #self.seats==0 then
if self.has_inventory and self.get_inventory_formspec then
- minetest.show_formspec(pname, "advtrains_inv_"..self.unique_id, self:get_inventory_formspec(pname))
+ minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname))
end
return
end
@@ -687,7 +698,7 @@ function wagon:show_get_on_form(pname)
local data = advtrains.wagons[self.id]
if #self.seats==0 then
if self.has_inventory and self.get_inventory_formspec then
- minetest.show_formspec(pname, "advtrains_inv_"..self.unique_id, self:get_inventory_formspec(pname))
+ minetest.show_formspec(pname, "advtrains_inv_"..self.id, self:get_inventory_formspec(pname))
end
return
end
@@ -705,7 +716,7 @@ function wagon:show_get_on_form(pname)
if self.has_inventory and self.get_inventory_formspec then
form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]"
end
- minetest.show_formspec(pname, "advtrains_geton_"..self.unique_id, form)
+ minetest.show_formspec(pname, "advtrains_geton_"..self.id, form)
end
function wagon:show_wagon_properties(pname)
--[[
@@ -754,9 +765,9 @@ function wagon:show_bordcom(pname)
linhei=5
local pre_own, pre_wl, owns_any = nil, nil, minetest.check_player_privs(pname, "train_admin")
for i, tpid in ipairs(train.trainparts) do
- local ent = advtrains.wagon_save[tpid]
+ local ent = advtrains.wagons[tpid]
if ent then
- local ename = ent.entity_name
+ local ename = ent.type
form = form .. "item_image["..i..","..linhei..";1,1;"..ename.."]"
if i~=1 then
if not ent.dcpl_lock then
@@ -809,7 +820,7 @@ function wagon:show_bordcom(pname)
end
form = form .. "button[0.5,8;3,1;Save;save]"
- minetest.show_formspec(pname, "advtrains_bordcom_"..self.unique_id, form)
+ minetest.show_formspec(pname, "advtrains_bordcom_"..self.id, form)
end
function wagon:handle_bordcom_fields(pname, formname, fields)
local data = advtrains.wagons[self.id]
@@ -837,8 +848,8 @@ function wagon:handle_bordcom_fields(pname, formname, fields)
for i, tpid in ipairs(train.trainparts) do
if fields["dcpl_"..i] then
for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.unique_id==tpid then
- wagon:safe_decouple(pname) -- TODO: Move this into external function (don't search entity?)
+ if wagon.is_wagon and wagon.initialized and wagon.id==tpid then
+ wagon:safe_decouple(pname) -- TODO: Move this into external function (don't search entity?)
end
end
end
@@ -954,7 +965,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
uid=string.match(formname, "^advtrains_bordcom_(.+)$")
if uid then
for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then
+ if wagon.is_wagon and wagon.initialized and wagon.id==uid then
wagon:handle_bordcom_fields(player:get_player_name(), formname, fields)
end
end
@@ -962,7 +973,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end)
end)
function wagon:seating_from_key_helper(pname, fields, no)
- local data = advtrains.wagons[wagon.id]
+ local data = advtrains.wagons[self.id]
local sgr=self.seats[no].group
for _,access in ipairs(self.seat_groups[sgr].access_to) do
if fields["sgr_"..access] and self:check_seat_group_access(pname, access) then
@@ -1000,7 +1011,7 @@ function wagon:check_seat_group_access(pname, sgr)
return true
end
function wagon:reattach_all()
- local data = advtrains.wagons[wagon.id]
+ local data = advtrains.wagons[self.id]
if not data.seatp then data.seatp={} end
for seatno, pname in pairs(data.seatp) do
local p=minetest.get_player_by_name(pname)
@@ -1015,7 +1026,7 @@ function wagon:safe_decouple(pname)
minetest.chat_send_player(pname, "Missing train_operator privilege")
return false
end
- local data = advtrains.wagons[wagon.id]
+ local data = advtrains.wagons[self.id]
if data.dcpl_lock then
minetest.chat_send_player(pname, "Couple is locked (ask owner or admin to unlock it)")
return false
@@ -1028,11 +1039,11 @@ end
function advtrains.get_wagon_prototype(data)
local wt = data.type
- if not minetest.registered_luaentities[wt] then
+ if not advtrains.wagon_prototypes[wt] then
atprint("Unable to load wagon type",wt,", using placeholder")
wt="advtrains:wagon_placeholder"
end
- return wt, minetest.registered_luaentities[wt]
+ return wt, advtrains.wagon_prototypes[wt]
end
function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreative)
@@ -1042,6 +1053,7 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
end
setmetatable(prototype, {__index=wagon})
minetest.register_entity(":"..sysname,prototype)
+ advtrains.wagon_prototypes[sysname] = prototype
minetest.register_craftitem(":"..sysname, {
description = desc,
@@ -1049,7 +1061,7 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
wield_image = inv_img,
stack_max = 1,
- groups = { not_in_creative_inventory = nincreative and 0 or 1}
+ groups = { not_in_creative_inventory = nincreative and 1 or 0},
on_place = function(itemstack, placer, pointed_thing)
return advtrains.pcall(function()
@@ -1082,7 +1094,7 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
return
end
- local wid = advtrains.create_wagon(type, pname)
+ local wid = advtrains.create_wagon(sysname, pname)
local id=advtrains.create_new_train_at(pointed_thing.under, plconnid, 0, {wid})
@@ -1099,7 +1111,7 @@ end
-- Placeholder wagon. Will be spawned whenever a mod is missing
advtrains.register_wagon("advtrains:wagon_placeholder", {
visual="sprite",
- textures = {"advtrains_wheel.png"},
+ textures = {"advtrains_wagon_placeholder.png"},
collisionbox = {-0.3,-0.3,-0.3, 0.3,0.3,0.3},
visual_size = {x=0.7, y=0.7},
initial_sprite_basepos = {x=0, y=0},
@@ -1112,5 +1124,5 @@ advtrains.register_wagon("advtrains:wagon_placeholder", {
assign_to_seat_group = {},
wagon_span=1,
drops={},
-}, "Wagon placeholder", "advtrains_subway_wagon_inv.png", true)
+}, "Wagon placeholder", "advtrains_wagon_placeholder.png", true)
S3DVertex vertices[24] = { // top video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]), video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]), video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]), video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]), // bottom video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]), video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]), video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]), video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]), // right video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]), video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]), video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]), video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]), // left video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]), video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]), video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]), video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]), // back video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]), video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]), video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]), video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]), // front video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]), video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]), video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]), video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]), }; static const u8 light_indices[24] = { 3, 7, 6, 2, 0, 4, 5, 1, 6, 7, 5, 4, 3, 2, 0, 1, 7, 3, 1, 5, 2, 6, 4, 0 }; for (int face = 0; face < 6; face++) { int tileindex = MYMIN(face, tilecount - 1); const TileSpec &tile = tiles[tileindex]; for (int j = 0; j < 4; j++) { video::S3DVertex &vertex = vertices[face * 4 + j]; v2f &tcoords = vertex.TCoords; switch (tile.rotation) { case 0: break; case 1: // R90 tcoords.rotateBy(90, irr::core::vector2df(0, 0)); break; case 2: // R180 tcoords.rotateBy(180, irr::core::vector2df(0, 0)); break; case 3: // R270 tcoords.rotateBy(270, irr::core::vector2df(0, 0)); break; case 4: // FXR90 tcoords.X = 1.0 - tcoords.X; tcoords.rotateBy(90, irr::core::vector2df(0, 0)); break; case 5: // FXR270 tcoords.X = 1.0 - tcoords.X; tcoords.rotateBy(270, irr::core::vector2df(0, 0)); break; case 6: // FYR90 tcoords.Y = 1.0 - tcoords.Y; tcoords.rotateBy(90, irr::core::vector2df(0, 0)); break; case 7: // FYR270 tcoords.Y = 1.0 - tcoords.Y; tcoords.rotateBy(270, irr::core::vector2df(0, 0)); break; case 8: // FX tcoords.X = 1.0 - tcoords.X; break; case 9: // FY tcoords.Y = 1.0 - tcoords.Y; break; default: break; } } } if (data->m_smooth_lighting) { for (int j = 0; j < 24; ++j) { video::S3DVertex &vertex = vertices[j]; vertex.Color = encode_light( lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)), f->light_source); if (!f->light_source) applyFacesShading(vertex.Color, vertex.Normal); } } // Add to mesh collector for (int k = 0; k < 6; ++k) { int tileindex = MYMIN(k, tilecount - 1); collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6); } } // Gets the base lighting values for a node void MapblockMeshGenerator::getSmoothLightFrame() { for (int k = 0; k < 8; ++k) frame.sunlight[k] = false; for (int k = 0; k < 8; ++k) { LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data)); frame.lightsDay[k] = light.lightDay; frame.lightsNight[k] = light.lightNight; // If there is direct sunlight and no ambient occlusion at some corner, // mark the vertical edge (top and bottom corners) containing it. if (light.lightDay == 255) { frame.sunlight[k] = true; frame.sunlight[k ^ 2] = true; } } } // Calculates vertex light level // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos) { // Light levels at (logical) node corners are known. Here, // trilinear interpolation is used to calculate light level // at a given point in the node. f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE); f32 lightDay = 0.0; // daylight f32 lightNight = 0.0; f32 lightBoosted = 0.0; // daylight + direct sunlight, if any for (int k = 0; k < 8; ++k) { f32 dx = (k & 4) ? x : 1 - x; f32 dy = (k & 2) ? y : 1 - y; f32 dz = (k & 1) ? z : 1 - z; // Use direct sunlight (255), if any; use daylight otherwise. f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k]; lightDay += dx * dy * dz * frame.lightsDay[k]; lightNight += dx * dy * dz * frame.lightsNight[k]; lightBoosted += dx * dy * dz * light_boosted; } return LightInfo{lightDay, lightNight, lightBoosted}; } // Calculates vertex color to be used in mapblock mesh // vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so) // tile_color - node's tile color video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos) { LightInfo light = blendLight(vertex_pos); return encode_light(light.getPair(), f->light_source); } video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal) { LightInfo light = blendLight(vertex_pos); video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source); if (!f->light_source) applyFacesShading(color, vertex_normal); return color; } void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords) { f32 tx1 = (box.MinEdge.X / BS) + 0.5; f32 ty1 = (box.MinEdge.Y / BS) + 0.5; f32 tz1 = (box.MinEdge.Z / BS) + 0.5; f32 tx2 = (box.MaxEdge.X / BS) + 0.5; f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; f32 txc[24] = { tx1, 1 - tz2, tx2, 1 - tz1, // up tx1, tz1, tx2, tz2, // down tz1, 1 - ty2, tz2, 1 - ty1, // right 1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left 1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back tx1, 1 - ty2, tx2, 1 - ty1, // front }; for (int i = 0; i != 24; ++i) coords[i] = txc[i]; } void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, TileSpec *tiles, int tile_count) { bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f; f32 texture_coord_buf[24]; f32 dx1 = box.MinEdge.X; f32 dy1 = box.MinEdge.Y; f32 dz1 = box.MinEdge.Z; f32 dx2 = box.MaxEdge.X; f32 dy2 = box.MaxEdge.Y; f32 dz2 = box.MaxEdge.Z; if (scale) { if (!txc) { // generate texture coords before scaling generateCuboidTextureCoords(box, texture_coord_buf); txc = texture_coord_buf; } box.MinEdge *= f->visual_scale; box.MaxEdge *= f->visual_scale; } box.MinEdge += origin; box.MaxEdge += origin; if (!txc) { generateCuboidTextureCoords(box, texture_coord_buf); txc = texture_coord_buf; } if (!tiles) { tiles = &tile; tile_count = 1; } if (data->m_smooth_lighting) { LightInfo lights[8]; for (int j = 0; j < 8; ++j) { v3f d; d.X = (j & 4) ? dx2 : dx1; d.Y = (j & 2) ? dy2 : dy1; d.Z = (j & 1) ? dz2 : dz1; lights[j] = blendLight(d); } drawCuboid(box, tiles, tile_count, lights, txc); } else { drawCuboid(box, tiles, tile_count, nullptr, txc); } } void MapblockMeshGenerator::prepareLiquidNodeDrawing() { getSpecialTile(0, &tile_liquid_top); getSpecialTile(1, &tile_liquid); MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z)); MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z)); c_flowing = f->liquid_alternative_flowing_id; c_source = f->liquid_alternative_source_id; top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source); draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source); if (draw_liquid_bottom) { const ContentFeatures &f2 = nodedef->get(nbottom.getContent()); if (f2.solidness > 1) draw_liquid_bottom = false; } if (data->m_smooth_lighting) return; // don't need to pre-compute anything in this case if (f->light_source != 0) { // If this liquid emits light and doesn't contain light, draw // it at what it emits, for an increased effect u8 e = decode_light(f->light_source); light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight)); } else if (nodedef->get(ntop).param_type == CPT_LIGHT) { // Otherwise, use the light of the node on top if possible light = LightPair(getInteriorLight(ntop, 0, nodedef)); } color_liquid_top = encode_light(light, f->light_source); color = encode_light(light, f->light_source); } void MapblockMeshGenerator::getLiquidNeighborhood() { u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8); for (int w = -1; w <= 1; w++) for (int u = -1; u <= 1; u++) { NeighborData &neighbor = liquid_neighbors[w + 1][u + 1]; v3s16 p2 = p + v3s16(u, 0, w); MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); neighbor.content = n2.getContent(); neighbor.level = -0.5 * BS; neighbor.is_same_liquid = false; neighbor.top_is_same_liquid = false; if (neighbor.content == CONTENT_IGNORE) continue; if (neighbor.content == c_source) { neighbor.is_same_liquid = true; neighbor.level = 0.5 * BS; } else if (neighbor.content == c_flowing) { neighbor.is_same_liquid = true; u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK); if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range) liquid_level = 0; else liquid_level -= (LIQUID_LEVEL_MAX + 1 - range); neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS; } // Check node above neighbor. // NOTE: This doesn't get executed if neighbor // doesn't exist p2.Y++; n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); if (n2.getContent() == c_source || n2.getContent() == c_flowing) neighbor.top_is_same_liquid = true; } } void MapblockMeshGenerator::calculateCornerLevels() { for (int k = 0; k < 2; k++) for (int i = 0; i < 2; i++) corner_levels[k][i] = getCornerLevel(i, k); } f32 MapblockMeshGenerator::getCornerLevel(int i, int k) { float sum = 0; int count = 0; int air_count = 0; for (int dk = 0; dk < 2; dk++) for (int di = 0; di < 2; di++) { NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di]; content_t content = neighbor_data.content; // If top is liquid, draw starting from top of node if (neighbor_data.top_is_same_liquid) return 0.5 * BS; // Source always has the full height if (content == c_source) return 0.5 * BS; // Flowing liquid has level information if (content == c_flowing) { sum += neighbor_data.level; count++; } else if (content == CONTENT_AIR) { air_count++; if (air_count >= 2) return -0.5 * BS + 0.2; } } if (count > 0) return sum / count; return 0; } namespace { struct LiquidFaceDesc { v3s16 dir; // XZ v3s16 p[2]; // XZ only; 1 means +, 0 means - }; struct UV { int u, v; }; static const LiquidFaceDesc liquid_base_faces[4] = { {v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}}, {v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}}, {v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}}, {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}}, }; static const UV liquid_base_vertices[4] = { {0, 1}, {1, 1}, {1, 0}, {0, 0} }; } void MapblockMeshGenerator::drawLiquidSides() { for (const auto &face : liquid_base_faces) { const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1]; // No face between nodes of the same liquid, unless there is node // at the top to which it should be connected. Again, unless the face // there would be inside the liquid if (neighbor.is_same_liquid) { if (!top_is_same_liquid) continue; if (neighbor.top_is_same_liquid) continue; } const ContentFeatures &neighbor_features = nodedef->get(neighbor.content); // Don't draw face if neighbor is blocking the view if (neighbor_features.solidness == 2) continue; video::S3DVertex vertices[4]; for (int j = 0; j < 4; j++) { const UV &vertex = liquid_base_vertices[j]; const v3s16 &base = face.p[vertex.u]; float v = vertex.v; v3f pos; pos.X = (base.X - 0.5f) * BS; pos.Z = (base.Z - 0.5f) * BS; if (vertex.v) { pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5f * BS; } else if (top_is_same_liquid) { pos.Y = 0.5f * BS; } else { pos.Y = corner_levels[base.Z][base.X]; v += (0.5f * BS - corner_levels[base.Z][base.X]) / BS; } if (data->m_smooth_lighting) color = blendLightColor(pos); pos += origin; vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, v); }; collector->append(tile_liquid, vertices, 4, quad_indices, 6); } } void MapblockMeshGenerator::drawLiquidTop() { // To get backface culling right, the vertices need to go // clockwise around the front of the face. And we happened to // calculate corner levels in exact reverse order. static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; video::S3DVertex vertices[4] = { video::S3DVertex(-BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), video::S3DVertex( BS / 2, 0, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), }; for (int i = 0; i < 4; i++) { int u = corner_resolve[i][0]; int w = corner_resolve[i][1]; vertices[i].Pos.Y += corner_levels[w][u]; if (data->m_smooth_lighting) vertices[i].Color = blendLightColor(vertices[i].Pos); vertices[i].Pos += origin; } // Default downwards-flowing texture animation goes from // -Z towards +Z, thus the direction is +Z. // Rotate texture to make animation go in flow direction // Positive if liquid moves towards +Z f32 dz = (corner_levels[0][0] + corner_levels[0][1]) - (corner_levels[1][0] + corner_levels[1][1]); // Positive if liquid moves towards +X f32 dx = (corner_levels[0][0] + corner_levels[1][0]) - (corner_levels[0][1] + corner_levels[1][1]); f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG; v2f tcoord_center(0.5, 0.5); v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X); tcoord_translate.rotateBy(tcoord_angle); tcoord_translate.X -= floor(tcoord_translate.X); tcoord_translate.Y -= floor(tcoord_translate.Y); for (video::S3DVertex &vertex : vertices) { vertex.TCoords.rotateBy(tcoord_angle, tcoord_center); vertex.TCoords += tcoord_translate; } std::swap(vertices[0].TCoords, vertices[2].TCoords); collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); } void MapblockMeshGenerator::drawLiquidBottom() { video::S3DVertex vertices[4] = { video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0), video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0), video::S3DVertex( BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 1, 1), video::S3DVertex(-BS / 2, -BS / 2, BS / 2, 0, 0, 0, color_liquid_top, 0, 1), }; for (int i = 0; i < 4; i++) { if (data->m_smooth_lighting) vertices[i].Color = blendLightColor(vertices[i].Pos); vertices[i].Pos += origin; } collector->append(tile_liquid_top, vertices, 4, quad_indices, 6); } void MapblockMeshGenerator::drawLiquidNode() { prepareLiquidNodeDrawing(); getLiquidNeighborhood(); calculateCornerLevels(); drawLiquidSides(); if (!top_is_same_liquid) drawLiquidTop(); if (draw_liquid_bottom) drawLiquidBottom(); } void MapblockMeshGenerator::drawGlasslikeNode() { useTile(0, 0, 0); for (int face = 0; face < 6; face++) { // Check this neighbor v3s16 dir = g_6dirs[face]; v3s16 neighbor_pos = blockpos_nodes + p + dir; MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos); // Don't make face if neighbor is of same type if (neighbor.getContent() == n.getContent()) continue; // Face at Z- v3f vertices[4] = { v3f(-BS / 2, BS / 2, -BS / 2), v3f( BS / 2, BS / 2, -BS / 2), v3f( BS / 2, -BS / 2, -BS / 2), v3f(-BS / 2, -BS / 2, -BS / 2), }; for (v3f &vertex : vertices) { switch (face) { case D6D_ZP: vertex.rotateXZBy(180); break; case D6D_YP: vertex.rotateYZBy( 90); break; case D6D_XP: vertex.rotateXZBy( 90); break; case D6D_ZN: vertex.rotateXZBy( 0); break; case D6D_YN: vertex.rotateYZBy(-90); break; case D6D_XN: vertex.rotateXZBy(-90); break; } } drawQuad(vertices, dir); } } void MapblockMeshGenerator::drawGlasslikeFramedNode() { TileSpec tiles[6]; for (int face = 0; face < 6; face++) getTile(g_6dirs[face], &tiles[face]); if (!data->m_smooth_lighting) color = encode_light(light, f->light_source); TileSpec glass_tiles[6]; for (auto &glass_tile : glass_tiles) glass_tile = tiles[4]; u8 param2 = n.getParam2(); bool H_merge = !(param2 & 128); bool V_merge = !(param2 & 64); param2 &= 63; static const float a = BS / 2.0f; static const float g = a - 0.03f; static const float b = 0.876f * (BS / 2.0f); static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = { aabb3f( b, b, -a, a, a, a), // y+ aabb3f(-a, b, -a, -b, a, a), // y+ aabb3f( b, -a, -a, a, -b, a), // y- aabb3f(-a, -a, -a, -b, -b, a), // y- aabb3f( b, -a, b, a, a, a), // x+ aabb3f( b, -a, -a, a, a, -b), // x+ aabb3f(-a, -a, b, -b, a, a), // x- aabb3f(-a, -a, -a, -b, a, -b), // x- aabb3f(-a, b, b, a, a, a), // z+ aabb3f(-a, -a, b, a, -b, a), // z+ aabb3f(-a, -a, -a, a, -b, -b), // z- aabb3f(-a, b, -a, a, a, -b), // z- }; // tables of neighbour (connect if same type and merge allowed), // checked with g_26dirs // 1 = connect, 0 = face visible bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // 1 = check static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] = {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0}; static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] = {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0}; static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] = {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1}; const bool *check_nb = check_nb_all; // neighbours checks for frames visibility if (H_merge || V_merge) { if (!H_merge) check_nb = check_nb_vertical; // vertical-only merge if (!V_merge) check_nb = check_nb_horizontal; // horizontal-only merge content_t current = n.getContent(); for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) { if (!check_nb[i]) continue; v3s16 n2p = blockpos_nodes + p + g_26dirs[i]; MapNode n2 = data->m_vmanip.getNodeNoEx(n2p); content_t n2c = n2.getContent(); if (n2c == current) nb[i] = 1; } } // edge visibility static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = { {1, 2, 7}, {1, 5, 6}, {4, 2, 15}, {4, 5, 14}, {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12}, {0, 1, 8}, {0, 4, 16}, {3, 4, 17}, {3, 1, 9}, }; tile = tiles[1]; for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) { bool edge_invisible; if (nb[nb_triplet[edge][2]]) edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]]; else edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; if (edge_invisible) continue; drawAutoLightedCuboid(frame_edges[edge]); } for (int face = 0; face < 6; face++) { if (nb[face]) continue; tile = glass_tiles[face]; // Face at Z- v3f vertices[4] = { v3f(-a, a, -g), v3f( a, a, -g), v3f( a, -a, -g), v3f(-a, -a, -g), }; for (v3f &vertex : vertices) { switch (face) { case D6D_ZP: vertex.rotateXZBy(180); break; case D6D_YP: vertex.rotateYZBy( 90); break; case D6D_XP: vertex.rotateXZBy( 90); break; case D6D_ZN: vertex.rotateXZBy( 0); break; case D6D_YN: vertex.rotateYZBy(-90); break; case D6D_XN: vertex.rotateXZBy(-90); break; } } v3s16 dir = g_6dirs[face]; drawQuad(vertices, dir); } // Optionally render internal liquid level defined by param2 // Liquid is textured with 1 tile defined in nodedef 'special_tiles' if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && f->special_tiles[0].layers[0].texture) { // Internal liquid level has param2 range 0 .. 63, // convert it to -0.5 .. 0.5 float vlev = (param2 / 63.0f) * 2.0f - 1.0f; getSpecialTile(0, &tile); drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b), -(nb[4] ? g : b), -(nb[3] ? g : b), (nb[2] ? g : b), (nb[1] ? g : b) * vlev, (nb[0] ? g : b))); } } void MapblockMeshGenerator::drawAllfacesNode() { static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2); useTile(0, 0, 0); drawAutoLightedCuboid(box); } void MapblockMeshGenerator::drawTorchlikeNode() { u8 wall = n.getWallMounted(nodedef); u8 tileindex = 0; switch (wall) { case DWM_YP: tileindex = 1; break; // ceiling case DWM_YN: tileindex = 0; break; // floor default: tileindex = 2; // side (or invalid—should we care?) } useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); float size = BS / 2 * f->visual_scale; v3f vertices[4] = { v3f(-size, size, 0), v3f( size, size, 0), v3f( size, -size, 0), v3f(-size, -size, 0), }; for (v3f &vertex : vertices) { switch (wall) { case DWM_YP: vertex.Y += -size + BS/2; vertex.rotateXZBy(-45); break; case DWM_YN: vertex.Y += size - BS/2; vertex.rotateXZBy(45); break; case DWM_XP: vertex.X += -size + BS/2; break; case DWM_XN: vertex.X += -size + BS/2; vertex.rotateXZBy(180); break; case DWM_ZP: vertex.X += -size + BS/2; vertex.rotateXZBy(90); break; case DWM_ZN: vertex.X += -size + BS/2; vertex.rotateXZBy(-90); } } drawQuad(vertices); } void MapblockMeshGenerator::drawSignlikeNode() { u8 wall = n.getWallMounted(nodedef); useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); static const float offset = BS / 16; float size = BS / 2 * f->visual_scale; // Wall at X+ of node v3f vertices[4] = { v3f(BS / 2 - offset, size, size), v3f(BS / 2 - offset, size, -size), v3f(BS / 2 - offset, -size, -size), v3f(BS / 2 - offset, -size, size), }; for (v3f &vertex : vertices) { switch (wall) { case DWM_YP: vertex.rotateXYBy( 90); break; case DWM_YN: vertex.rotateXYBy(-90); break; case DWM_XP: vertex.rotateXZBy( 0); break; case DWM_XN: vertex.rotateXZBy(180); break; case DWM_ZP: vertex.rotateXZBy( 90); break; case DWM_ZN: vertex.rotateXZBy(-90); break; } } drawQuad(vertices); } void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset, bool offset_top_only) { v3f vertices[4] = { v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0), v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0), v3f( scale, -BS / 2, 0), v3f(-scale, -BS / 2, 0), }; if (random_offset_Y) { PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24); offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125); } int offset_count = offset_top_only ? 2 : 4; for (int i = 0; i < offset_count; i++) vertices[i].Z += quad_offset; for (v3f &vertex : vertices) { vertex.rotateXZBy(rotation + rotate_degree); vertex += offset; } drawQuad(vertices, v3s16(0, 0, 0), plant_height); } void MapblockMeshGenerator::drawPlantlike() { draw_style = PLANT_STYLE_CROSS; scale = BS / 2 * f->visual_scale; offset = v3f(0, 0, 0); rotate_degree = 0; random_offset_Y = false; face_num = 0; plant_height = 1.0; switch (f->param_type_2) { case CPT2_MESHOPTIONS: draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE); if (n.param2 & MO_BIT_SCALE_SQRT2) scale *= 1.41421; if (n.param2 & MO_BIT_RANDOM_OFFSET) { PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16); offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145); } if (n.param2 & MO_BIT_RANDOM_OFFSET_Y) random_offset_Y = true; break; case CPT2_DEGROTATE: rotate_degree = n.param2 * 2; break; case CPT2_LEVELED: plant_height = n.param2 / 16.0; break; default: break; } switch (draw_style) { case PLANT_STYLE_CROSS: drawPlantlikeQuad(46); drawPlantlikeQuad(-44); break; case PLANT_STYLE_CROSS2: drawPlantlikeQuad(91); drawPlantlikeQuad(1); break; case PLANT_STYLE_STAR: drawPlantlikeQuad(121); drawPlantlikeQuad(241); drawPlantlikeQuad(1); break; case PLANT_STYLE_HASH: drawPlantlikeQuad( 1, BS / 4); drawPlantlikeQuad( 91, BS / 4); drawPlantlikeQuad(181, BS / 4); drawPlantlikeQuad(271, BS / 4); break; case PLANT_STYLE_HASH2: drawPlantlikeQuad( 1, -BS / 2, true); drawPlantlikeQuad( 91, -BS / 2, true); drawPlantlikeQuad(181, -BS / 2, true); drawPlantlikeQuad(271, -BS / 2, true); break; } } void MapblockMeshGenerator::drawPlantlikeNode() { useTile(); drawPlantlike(); } void MapblockMeshGenerator::drawPlantlikeRootedNode() { useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true); origin += v3f(0.0, BS, 0.0); p.Y++; if (data->m_smooth_lighting) { getSmoothLightFrame(); } else { MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); light = LightPair(getInteriorLight(ntop, 1, nodedef)); } drawPlantlike(); p.Y--; } void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle, float offset_h, float offset_v) { v3f vertices[4] = { v3f(-scale, -BS / 2 + scale * 2, 0), v3f( scale, -BS / 2 + scale * 2, 0), v3f( scale, -BS / 2, 0), v3f(-scale, -BS / 2, 0), }; for (v3f &vertex : vertices) { vertex.rotateYZBy(opening_angle); vertex.Z += offset_h; vertex.rotateXZBy(rotation); vertex.Y += offset_v; } drawQuad(vertices); } void MapblockMeshGenerator::drawFirelikeNode() { useTile(); scale = BS / 2 * f->visual_scale; // Check for adjacent nodes bool neighbors = false; bool neighbor[6] = {0, 0, 0, 0, 0, 0};