Merge branch 'main' into precipitation
This commit is contained in:
commit
bf5a5b243f
@ -7,7 +7,7 @@ AppDir:
|
||||
icon: VoxelCore
|
||||
version: latest
|
||||
exec: usr/bin/VoxelEngine
|
||||
exec_args: --dir $HOME/.voxeng --res $APPDIR/usr/share/VoxelCore/res $@
|
||||
exec_args: --dir $HOME/.config/voxelcore --res $APPDIR/usr/share/VoxelCore/res $@
|
||||
apt:
|
||||
arch: amd64
|
||||
sources:
|
||||
|
||||
@ -166,13 +166,21 @@ Properties:
|
||||
|
||||
Methods:
|
||||
|
||||
Here, *color* can be specified in the following ways:
|
||||
- rgba: int
|
||||
- r: int, g: int, b: int
|
||||
- r: int, g: int, b: int, a: int
|
||||
|
||||
| Method | Description |
|
||||
|----------------------------------------------------------|---------------------------------------------------------|
|
||||
| data:at(x: int, y: int) | returns an RGBA pixel at the given coordinates |
|
||||
| data:set(x: int, y: int, rgba: int) | updates an RGBA pixel at the given coordinates |
|
||||
| data:set(x: int, y: int, r: int, g: int, b: int) | updates an RGBA pixel at the given coordinates |
|
||||
| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | updates an RGBA pixel at the given coordinates |
|
||||
| data:set(x: int, y: int, *color*) | updates an RGBA pixel at the given coordinates |
|
||||
| data:line(x1: int, y1: int, x2: int, y2: int, *color*) | draws a line with the specified RGBA color |
|
||||
| data:blit(src: Canvas, dst_x: int, dst_y: int) | draws the src canvas at the specified coordinates |
|
||||
| data:clear() | clears the canvas |
|
||||
| data:clear(*color*) | fills the canvas with the specified RGBA color |
|
||||
| data:update() | applies changes to the canvas and uploads it to the GPU |
|
||||
| data:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) |
|
||||
|
||||
|
||||
## Inventory
|
||||
|
||||
@ -167,13 +167,21 @@ document["worlds-panel"]:clear()
|
||||
|
||||
Методы:
|
||||
|
||||
| Метод | Описание |
|
||||
|----------------------------------------------------------|-----------------------------------------------------|
|
||||
| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам |
|
||||
| data:set(x: int, y: int, rgba: int) | изменяет RGBA пиксель по указанным координатам |
|
||||
| data:set(x: int, y: int, r: int, g: int, b: int) | изменяет RGBA пиксель по указанным координатам |
|
||||
| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | изменяет RGBA пиксель по указанным координатам |
|
||||
| data:update() | применяет изменения и загружает холст в видеопамять |
|
||||
Здесь *цвет* может быть указан следующими способами:
|
||||
- rgba: int
|
||||
- r: int, g: int, b: int
|
||||
- r: int, g: int, b: int, a: int
|
||||
|
||||
| Метод | Описание |
|
||||
|----------------------------------------------------------|------------------------------------------------------|
|
||||
| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам |
|
||||
| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам |
|
||||
| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом |
|
||||
| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах |
|
||||
| data:clear() | очищает холст |
|
||||
| data:clear(*цвет*) | заполняет холст указанным RGBA цветом |
|
||||
| data:update() | применяет изменения и загружает холст в видеопамять |
|
||||
| data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
|
||||
|
||||
## Inventory (inventory)
|
||||
|
||||
|
||||
@ -1,13 +1,41 @@
|
||||
<container size='887,454' color='#0F1E2DB2' padding='8' interval='5' context='menu'>
|
||||
<panel id='packs_cur' pos='2' size='440,406' color='0' max-length='406'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
<panel id='packs_add' pos='445,2' size='440,406' color='0' max-length='406'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
<button id='apply_btn' pos='2,410' size='440,40' onclick='apply()'>@Apply</button>
|
||||
<button pos='445,410' size='398,40' onclick='menu:back()'>@Cancel</button>
|
||||
<image onclick='refresh()' interactive='true' src='gui/refresh'
|
||||
size='32' margin='7' gravity='bottom-right'
|
||||
color='#FFFFFF80' hover-color='#FFFFFF10'/>
|
||||
</container>
|
||||
|
||||
<container size='940,600' color='#0F1E2DB2' interval='5' context='menu'>
|
||||
<button pos='15,545' id='apply_btn' size='440,40' onclick='apply()'>@Apply</button>
|
||||
<button pos='485,545' size='440,40' onclick='menu:back()'>@Cancel</button>
|
||||
|
||||
<image id="move_left" src='gui/check_mark'
|
||||
size='32' margin='218,2,0,0' gravity='top-left'
|
||||
color='#FFFFFF50'/>
|
||||
|
||||
<image id="move_left" src='gui/cross'
|
||||
size='32' margin='0,2,219,0' gravity='top-right'
|
||||
color='#FFFFFF50'/>
|
||||
|
||||
<panel id='search_panel' size='440,36' pos='15,504' interval='1' color='#0000004C'>
|
||||
<textbox id='search_textbox' multiline='false' size='440,25' sub-consumer='function(x) refresh_search() end'></textbox>
|
||||
</panel>
|
||||
|
||||
<image onclick='core.open_folder("user:content")' interactive='true' src='gui/folder_icon'
|
||||
size='32' margin='0,0,18,66' gravity='bottom-right'
|
||||
color='#FFFFFF50' hover-color='#FFFFFF10'/>
|
||||
|
||||
<image onclick='refresh()' interactive='true' src='gui/refresh'
|
||||
size='32' margin='0,0,65,66' gravity='bottom-right'
|
||||
color='#FFFFFF80' hover-color='#FFFFFF10'/>
|
||||
|
||||
<image id="move_right" onclick='move_right()' interactive='true' src='gui/right_arrow'
|
||||
size='32' margin='0,0,380,64' gravity='bottom-right'
|
||||
color='#FFFFFF50' hover-color='#FFFFFF10'/>
|
||||
|
||||
<image id="move_left" onclick='move_left()' interactive='true' src='gui/left_arrow'
|
||||
size='32' margin='0,0,425,64' gravity='bottom-right'
|
||||
color='#FFFFFF50' hover-color='#FFFFFF10'/>
|
||||
|
||||
<panel id='packs_add' pos='485,34' size='440,507' color='0' max-length='455' scrollable='true'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
|
||||
<panel id='packs_cur' pos='15,34' size='440,507' color='0' max-length='455' scrollable='true'>
|
||||
<!-- content is generated in script -->
|
||||
</panel>
|
||||
</container>
|
||||
@ -5,9 +5,28 @@ function on_open(params)
|
||||
refresh()
|
||||
end
|
||||
|
||||
-- add - packs to be added to the world (after apply)
|
||||
-- rem - packs that should be removed from the world (after apply)
|
||||
add_packs = {}
|
||||
rem_packs = {}
|
||||
|
||||
-- included - connected packs to the world
|
||||
-- excluded - packs that are not connected to the world
|
||||
packs_included = {}
|
||||
packs_excluded = {}
|
||||
|
||||
packs_info = {}
|
||||
|
||||
local function include(id, is_include)
|
||||
if is_include then
|
||||
table.insert(packs_included, id)
|
||||
table.remove_value(packs_excluded, id)
|
||||
else
|
||||
table.insert(packs_excluded, id)
|
||||
table.remove_value(packs_included, id)
|
||||
end
|
||||
end
|
||||
|
||||
function apply()
|
||||
core.reconfig_packs(add_packs, rem_packs)
|
||||
if mode ~= "world" then
|
||||
@ -15,8 +34,70 @@ function apply()
|
||||
end
|
||||
end
|
||||
|
||||
function reposition_func(_pack)
|
||||
local INTERVAL = 2
|
||||
local STEP = 1
|
||||
local SIZE = 80
|
||||
|
||||
local tbl = nil
|
||||
if table.has(packs_included, _pack) then
|
||||
tbl = packs_included
|
||||
elseif table.has(packs_excluded, _pack) then
|
||||
tbl = packs_excluded
|
||||
else
|
||||
tbl = packs_excluded
|
||||
local packinfo = pack.get_info(_pack)
|
||||
packinfo[packinfo.id] = {packinfo.id, packinfo.title}
|
||||
table.insert(packs_excluded, packinfo.id)
|
||||
end
|
||||
|
||||
local indx = table.index(tbl, _pack) - 1
|
||||
local pos = {0, (SIZE + INTERVAL) * indx + STEP}
|
||||
|
||||
return pos[1], pos[2]
|
||||
end
|
||||
|
||||
|
||||
function refresh_search()
|
||||
local search_text = document.search_textbox.text:lower()
|
||||
|
||||
local new_included = table.copy(packs_included)
|
||||
local new_excluded = table.copy(packs_excluded)
|
||||
|
||||
local function score(pack_name)
|
||||
if pack_name:lower():find(search_text) then
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function sorting(a, b)
|
||||
local score_a = score(packs_info[a][2])
|
||||
local score_b = score(packs_info[b][2])
|
||||
|
||||
if score_a ~= score_b then
|
||||
return score_a > score_b
|
||||
else
|
||||
return packs_info[a][2] < packs_info[b][2]
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(new_included, sorting)
|
||||
table.sort(new_excluded, sorting)
|
||||
|
||||
packs_included = new_included
|
||||
packs_excluded = new_excluded
|
||||
|
||||
for _, id in ipairs(table.merge(table.copy(packs_included), packs_excluded)) do
|
||||
local content = document["pack_" .. id]
|
||||
content:reposition()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function refresh_changes()
|
||||
document.apply_btn.enabled = (#add_packs>0) or (#rem_packs>0)
|
||||
refresh_search()
|
||||
end
|
||||
|
||||
function move_pack(id)
|
||||
@ -24,23 +105,61 @@ function move_pack(id)
|
||||
if table.has(add_packs, id) then
|
||||
document["pack_"..id]:moveInto(document.packs_add)
|
||||
table.remove_value(add_packs, id)
|
||||
include(id, false)
|
||||
-- cancel pack removal
|
||||
elseif table.has(rem_packs, id) then
|
||||
document["pack_"..id]:moveInto(document.packs_cur)
|
||||
table.remove_value(rem_packs, id)
|
||||
include(id, true)
|
||||
-- add pack
|
||||
elseif table.has(packs_installed, id) then
|
||||
document["pack_"..id]:moveInto(document.packs_add)
|
||||
table.insert(rem_packs, id)
|
||||
include(id, false)
|
||||
-- remove pack
|
||||
else
|
||||
document["pack_"..id]:moveInto(document.packs_cur)
|
||||
table.insert(add_packs, id)
|
||||
include(id, true)
|
||||
end
|
||||
refresh_changes()
|
||||
end
|
||||
|
||||
function place_pack(panel, packinfo, callback)
|
||||
function move_left()
|
||||
for _, id in pairs(table.copy(packs_excluded)) do
|
||||
if not document["pack_"..id].enabled then goto continue end
|
||||
|
||||
include(id, true)
|
||||
table.insert(add_packs, id)
|
||||
table.remove_value(rem_packs, id)
|
||||
document["pack_"..id]:moveInto(document.packs_cur)
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
refresh_changes()
|
||||
end
|
||||
|
||||
function move_right()
|
||||
for _, id in pairs(table.copy(packs_included)) do
|
||||
if not document["pack_"..id].enabled then goto continue end
|
||||
|
||||
include(id, false)
|
||||
|
||||
if table.has(packs_installed, id) then
|
||||
table.insert(rem_packs, id)
|
||||
end
|
||||
|
||||
table.remove_value(add_packs, id)
|
||||
document["pack_"..id]:moveInto(document.packs_add)
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
refresh_changes()
|
||||
end
|
||||
|
||||
function place_pack(panel, packinfo, callback, position_func)
|
||||
if packinfo.error then
|
||||
callback = nil
|
||||
end
|
||||
@ -50,6 +169,7 @@ function place_pack(panel, packinfo, callback)
|
||||
packinfo.id_verbose = packinfo.id
|
||||
end
|
||||
packinfo.callback = callback
|
||||
packinfo.position_func = position_func or function () end
|
||||
panel:add(gui.template("pack", packinfo))
|
||||
if not callback then
|
||||
document["pack_"..packinfo.id].enabled = false
|
||||
@ -76,15 +196,30 @@ function check_dependencies(packinfo)
|
||||
return
|
||||
end
|
||||
|
||||
function check_deleted()
|
||||
for i = 1, math.max(#packs_included, #packs_excluded) do
|
||||
local pack = packs_included[i]
|
||||
if pack and not table.has(packs_all, pack) then
|
||||
table.remove(packs_included, i)
|
||||
table.insert(rem_packs, pack)
|
||||
end
|
||||
|
||||
pack = packs_excluded[i]
|
||||
if pack and not table.has(packs_all, pack) then
|
||||
table.remove(packs_excluded, i)
|
||||
table.insert(rem_packs, pack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function refresh()
|
||||
packs_installed = pack.get_installed()
|
||||
packs_available = pack.get_available()
|
||||
base_packs = pack.get_base_packs()
|
||||
packs_all = {unpack(packs_installed)}
|
||||
required = {}
|
||||
for i,k in ipairs(packs_available) do
|
||||
table.insert(packs_all, k)
|
||||
end
|
||||
|
||||
table.merge(packs_all, packs_available)
|
||||
|
||||
local packs_cur = document.packs_cur
|
||||
local packs_add = document.packs_add
|
||||
@ -105,20 +240,14 @@ function refresh()
|
||||
end
|
||||
local packinfos = pack.get_info(packids)
|
||||
|
||||
for i,id in ipairs(packs_installed) do
|
||||
local packinfo = packinfos[id]
|
||||
packinfo.index = i
|
||||
callback = not table.has(base_packs, id) and string.format('move_pack("%s")', id) or nil
|
||||
packinfo.error = check_dependencies(packinfo)
|
||||
place_pack(packs_cur, packinfo, callback)
|
||||
for _,id in ipairs(base_packs) do
|
||||
local packinfo = pack.get_info(id)
|
||||
packs_info[id] = {packinfo.id, packinfo.title}
|
||||
end
|
||||
|
||||
for i,id in ipairs(packs_available) do
|
||||
local packinfo = packinfos[id]
|
||||
packinfo.index = i
|
||||
callback = string.format('move_pack("%s")', id)
|
||||
packinfo.error = check_dependencies(packinfo)
|
||||
place_pack(packs_add, packinfo, callback)
|
||||
for _,id in ipairs(packs_all) do
|
||||
local packinfo = pack.get_info(id)
|
||||
packs_info[id] = {packinfo.id, packinfo.title}
|
||||
end
|
||||
|
||||
for i,id in ipairs(packs_installed) do
|
||||
@ -127,6 +256,26 @@ function refresh()
|
||||
end
|
||||
end
|
||||
|
||||
if #packs_excluded == 0 then packs_excluded = table.copy(packs_available) end
|
||||
if #packs_included == 0 then packs_included = table.copy(packs_installed) end
|
||||
|
||||
for i,id in ipairs(packs_installed) do
|
||||
local packinfo = packinfos[id]
|
||||
packinfo.index = i
|
||||
callback = not table.has(base_packs, id) and string.format('move_pack("%s")', id) or nil
|
||||
packinfo.error = check_dependencies(packinfo)
|
||||
place_pack(packs_cur, packinfo, callback, string.format('reposition_func("%s")', packinfo.id))
|
||||
end
|
||||
|
||||
for i,id in ipairs(packs_available) do
|
||||
local packinfo = packinfos[id]
|
||||
packinfo.index = i
|
||||
callback = string.format('move_pack("%s")', id)
|
||||
packinfo.error = check_dependencies(packinfo)
|
||||
place_pack(packs_add, packinfo, callback, string.format('reposition_func("%s")', packinfo.id))
|
||||
end
|
||||
|
||||
check_deleted()
|
||||
apply_movements(packs_cur, packs_add)
|
||||
refresh_changes()
|
||||
end
|
||||
|
||||
@ -28,7 +28,8 @@ function on_open()
|
||||
document.content_btn.text = string.format(
|
||||
"%s [%s]", gui.str("Content", "menu"), #pack.get_installed()
|
||||
)
|
||||
if settings.generator == nil then
|
||||
|
||||
if settings.generator == nil or generation.get_generators()[settings.generator] == nil then
|
||||
settings.generator = generation.get_default_generator()
|
||||
end
|
||||
document.generator_btn.text = string.format(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' z-index="%{index}">
|
||||
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}">
|
||||
<label color='#FFFFFF80' size='300,25' align='right' gravity='top-right'>
|
||||
[%{id_verbose}]
|
||||
</label>
|
||||
|
||||
@ -21,7 +21,10 @@
|
||||
"gui/folder_icon",
|
||||
"gui/settings_icon",
|
||||
"misc/rain",
|
||||
"misc/snow"
|
||||
"misc/snow",
|
||||
"gui/check_mark",
|
||||
"gui/left_arrow",
|
||||
"gui/right_arrow"
|
||||
],
|
||||
"fonts": [
|
||||
{
|
||||
|
||||
@ -1,3 +1,31 @@
|
||||
local _ffi = ffi
|
||||
ffi = nil
|
||||
|
||||
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
||||
-- may be reused one global ffi buffer per lua_State
|
||||
local canvas_ffi_buffer
|
||||
local canvas_ffi_buffer_size = 0
|
||||
|
||||
function __vc_Canvas_set_data(self, data)
|
||||
if type(data) == "cdata" then
|
||||
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
|
||||
end
|
||||
local width = self.width
|
||||
local height = self.height
|
||||
|
||||
local size = width * height * 4
|
||||
if size > canvas_ffi_buffer_size then
|
||||
canvas_ffi_buffer = _ffi.new(
|
||||
string.format("unsigned char[%s]", size)
|
||||
)
|
||||
canvas_ffi_buffer_size = size
|
||||
end
|
||||
for i=0, size - 1 do
|
||||
canvas_ffi_buffer[i] = data[i + 1]
|
||||
end
|
||||
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
|
||||
end
|
||||
|
||||
-- Check if given table is an array
|
||||
function is_array(x)
|
||||
if #x > 0 then
|
||||
@ -151,9 +179,27 @@ function table.map(t, func)
|
||||
end
|
||||
|
||||
function table.filter(t, func)
|
||||
|
||||
for i = #t, 1, -1 do
|
||||
if not func(i, t[i]) then
|
||||
table.remove(t, i)
|
||||
end
|
||||
end
|
||||
|
||||
local size = #t
|
||||
|
||||
for i, v in pairs(t) do
|
||||
if not func(i, v) then
|
||||
t[i] = nil
|
||||
local i_type = type(i)
|
||||
if i_type == "number" then
|
||||
if i < 1 or i > size then
|
||||
if not func(i, v) then
|
||||
t[i] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
if not func(i, v) then
|
||||
t[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
BIN
res/textures/gui/check_mark.png
Normal file
BIN
res/textures/gui/check_mark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 560 B |
BIN
res/textures/gui/left_arrow.png
Normal file
BIN
res/textures/gui/left_arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 B |
BIN
res/textures/gui/loupe.png
Normal file
BIN
res/textures/gui/loupe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 B |
BIN
res/textures/gui/right_arrow.png
Normal file
BIN
res/textures/gui/right_arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 B |
@ -2,7 +2,6 @@
|
||||
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "util/listutil.hpp"
|
||||
|
||||
@ -125,9 +124,7 @@ std::vector<std::string> PacksManager::assemble(
|
||||
std::queue<const ContentPack*> queue;
|
||||
std::queue<const ContentPack*> queue2;
|
||||
|
||||
std::sort(allNames.begin(), allNames.end());
|
||||
|
||||
for (auto& name : allNames) {
|
||||
for (auto& name : names) {
|
||||
auto found = packs.find(name);
|
||||
if (found == packs.end()) {
|
||||
throw contentpack_error(name, io::path(), "pack not found");
|
||||
|
||||
@ -97,7 +97,7 @@ std::unique_ptr<Atlas> AtlasBuilder::build(uint extrusion, bool prepare, uint ma
|
||||
uint y = rect.y;
|
||||
uint w = rect.width;
|
||||
uint h = rect.height;
|
||||
canvas->blit(entry.image.get(), rect.x, rect.y);
|
||||
canvas->blit(*entry.image, rect.x, rect.y);
|
||||
for (uint j = 0; j < extrusion; j++) {
|
||||
canvas->extrude(x - j, y - j, w + j*2, h + j*2);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
ImageData::ImageData(ImageFormat format, uint width, uint height)
|
||||
@ -80,23 +81,116 @@ void ImageData::flipY() {
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::blit(const ImageData* image, int x, int y) {
|
||||
if (format == image->format) {
|
||||
void ImageData::blit(const ImageData& image, int x, int y) {
|
||||
if (format == image.format) {
|
||||
blitMatchingFormat(image, x, y);
|
||||
return;
|
||||
}
|
||||
if (format == ImageFormat::rgba8888 &&
|
||||
image->format == ImageFormat::rgb888) {
|
||||
image.format == ImageFormat::rgb888) {
|
||||
blitRGB_on_RGBA(image, x, y);
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("mismatching format");
|
||||
}
|
||||
|
||||
void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) {
|
||||
ubyte* source = image->getData();
|
||||
uint srcwidth = image->getWidth();
|
||||
uint srcheight = image->getHeight();
|
||||
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) {
|
||||
const int left = 0;
|
||||
const int right = width;
|
||||
const int bottom = 0;
|
||||
const int top = height;
|
||||
|
||||
int dx = x2 - x1;
|
||||
int dy = y2 - y1;
|
||||
|
||||
float t0 = 0.0f;
|
||||
float t1 = 1.0f;
|
||||
|
||||
auto clip = [](int p, int q, float& t0, float& t1) {
|
||||
if (p == 0) {
|
||||
return q >= 0;
|
||||
}
|
||||
float t = static_cast<float>(q) / p;
|
||||
if (p < 0) {
|
||||
if (t > t1) return false;
|
||||
if (t > t0) t0 = t;
|
||||
} else {
|
||||
if (t < t0) return false;
|
||||
if (t < t1) t1 = t;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!clip(-dx, x1 - left, t0, t1)) return false;
|
||||
if (!clip( dx, right - x1, t0, t1)) return false;
|
||||
if (!clip(-dy, y1 - bottom, t0, t1)) return false;
|
||||
if (!clip( dy, top - y1, t0, t1)) return false;
|
||||
|
||||
if (t1 < 1.0f) {
|
||||
x2 = x1 + static_cast<int>(std::round(t1 * dx));
|
||||
y2 = y1 + static_cast<int>(std::round(t1 * dy));
|
||||
}
|
||||
if (t0 > 0.0f) {
|
||||
x1 = x1 + static_cast<int>(std::round(t0 * dx));
|
||||
y1 = y1 + static_cast<int>(std::round(t0 * dy));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<uint channels>
|
||||
static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const glm::ivec4& color) {
|
||||
ubyte* data = image.getData();
|
||||
uint width = image.getWidth();
|
||||
uint height = image.getHeight();
|
||||
|
||||
if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width ||
|
||||
y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) &&
|
||||
!clip_line(x1, y1, x2, y2, width, height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int dx = std::abs(x2 - x1);
|
||||
int dy = -std::abs(y2 - y1);
|
||||
int sx = x1 < x2 ? 1 : -1;
|
||||
int sy = y1 < y2 ? 1 : -1;
|
||||
int err = dx + dy;
|
||||
|
||||
while (true) {
|
||||
size_t pos = (y1 * width + x1) * channels;
|
||||
for (int i = 0; i < channels; i++) {
|
||||
data[pos + i] = color[i];
|
||||
}
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
|
||||
int e2 = 2 * err;
|
||||
if (e2 >= dy) {
|
||||
err += dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 <= dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color) {
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888:
|
||||
draw_line<3>(*this, x1, y1, x2, y2, color);
|
||||
break;
|
||||
case ImageFormat::rgba8888:
|
||||
draw_line<4>(*this, x1, y1, x2, y2, color);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
|
||||
ubyte* source = image.getData();
|
||||
uint srcwidth = image.getWidth();
|
||||
uint srcheight = image.getHeight();
|
||||
|
||||
for (uint srcy = std::max(0, -y);
|
||||
srcy < std::min(srcheight, height - y);
|
||||
@ -116,7 +210,7 @@ void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) {
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::blitMatchingFormat(const ImageData* image, int x, int y) {
|
||||
void ImageData::blitMatchingFormat(const ImageData& image, int x, int y) {
|
||||
uint comps;
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888: comps = 3; break;
|
||||
@ -124,20 +218,24 @@ void ImageData::blitMatchingFormat(const ImageData* image, int x, int y) {
|
||||
default:
|
||||
throw std::runtime_error("only unsigned byte formats supported");
|
||||
}
|
||||
ubyte* source = image->getData();
|
||||
uint srcwidth = image->getWidth();
|
||||
uint srcheight = image->getHeight();
|
||||
ubyte* source = image.getData();
|
||||
|
||||
const uint width = this->width;
|
||||
const uint height = this->height;
|
||||
const uint src_width = image.getWidth();
|
||||
const uint src_height = image.getHeight();
|
||||
ubyte* data = this->data.get();
|
||||
|
||||
for (uint srcy = std::max(0, -y);
|
||||
srcy < std::min(srcheight, height - y);
|
||||
srcy < std::min(src_height, height - y);
|
||||
srcy++) {
|
||||
for (uint srcx = std::max(0, -x);
|
||||
srcx < std::min(srcwidth, width - x);
|
||||
srcx < std::min(src_width, width - x);
|
||||
srcx++) {
|
||||
uint dstx = srcx + x;
|
||||
uint dsty = srcy + y;
|
||||
uint dstidx = (dsty * width + dstx) * comps;
|
||||
uint srcidx = (srcy * srcwidth + srcx) * comps;
|
||||
uint srcidx = (srcy * src_width + srcx) * comps;
|
||||
for (uint c = 0; c < comps; c++) {
|
||||
data[dstidx + c] = source[srcidx + c];
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
#include <glm/vec4.hpp>
|
||||
#include <memory>
|
||||
|
||||
enum class ImageFormat {
|
||||
@ -14,6 +15,9 @@ class ImageData {
|
||||
uint width;
|
||||
uint height;
|
||||
std::unique_ptr<ubyte[]> data;
|
||||
|
||||
void blitRGB_on_RGBA(const ImageData& image, int x, int y);
|
||||
void blitMatchingFormat(const ImageData& image, int x, int y);
|
||||
public:
|
||||
ImageData(ImageFormat format, uint width, uint height);
|
||||
ImageData(ImageFormat format, uint width, uint height, std::unique_ptr<ubyte[]> data);
|
||||
@ -23,9 +27,8 @@ public:
|
||||
void flipX();
|
||||
void flipY();
|
||||
|
||||
void blitRGB_on_RGBA(const ImageData* image, int x, int y);
|
||||
void blitMatchingFormat(const ImageData* image, int x, int y);
|
||||
void blit(const ImageData* image, int x, int y);
|
||||
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color);
|
||||
void blit(const ImageData& image, int x, int y);
|
||||
void extrude(int x, int y, int w, int h);
|
||||
void fixAlphaColor();
|
||||
|
||||
@ -44,6 +47,11 @@ public:
|
||||
uint getHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
size_t getDataSize() const {
|
||||
size_t channels = 3 + (format == ImageFormat::rgba8888);
|
||||
return width * height * channels;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ImageData> add_atlas_margins(ImageData* image, int grid_size);
|
||||
|
||||
@ -16,5 +16,5 @@ void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
|
||||
auto batch = pctx.getBatch2D();
|
||||
batch->texture(mTexture.get());
|
||||
batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, true, col);
|
||||
batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, false, col);
|
||||
}
|
||||
|
||||
@ -26,4 +26,4 @@ namespace gui {
|
||||
std::shared_ptr<::Texture> mTexture;
|
||||
std::shared_ptr<ImageData> mData;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,10 +114,14 @@ namespace lua {
|
||||
return *mData;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasTexture() const {
|
||||
return mTexture != nullptr;
|
||||
}
|
||||
|
||||
static int createMetatable(lua::State*);
|
||||
inline static std::string TYPENAME = "Canvas";
|
||||
private:
|
||||
std::shared_ptr<Texture> mTexture;
|
||||
std::shared_ptr<Texture> mTexture; // nullable
|
||||
std::shared_ptr<ImageData> mData;
|
||||
};
|
||||
static_assert(!std::is_abstract<LuaCanvas>());
|
||||
|
||||
@ -88,14 +88,17 @@ static void create_libs(State* L, StateType stateType) {
|
||||
|
||||
void lua::init_state(State* L, StateType stateType) {
|
||||
// Allowed standard libraries
|
||||
pop(L, luaopen_base(L));
|
||||
pop(L, luaopen_math(L));
|
||||
pop(L, luaopen_string(L));
|
||||
pop(L, luaopen_table(L));
|
||||
pop(L, luaopen_debug(L));
|
||||
pop(L, luaopen_jit(L));
|
||||
pop(L, luaopen_bit(L));
|
||||
pop(L, luaopen_os(L));
|
||||
luaL_openlibs(L);
|
||||
|
||||
if (getglobal(L, "require")) {
|
||||
pushstring(L, "ffi");
|
||||
if (call_nothrow(L, 1, 1)) {
|
||||
setglobal(L, "ffi");
|
||||
}
|
||||
}
|
||||
pushnil(L);
|
||||
setglobal(L, "io");
|
||||
|
||||
const char* removed_os[] {
|
||||
"execute", "exit", "remove", "rename", "setlocale", "tmpname", nullptr};
|
||||
remove_lib_funcs(L, "os", removed_os);
|
||||
|
||||
@ -163,20 +163,23 @@ int lua::call(State* L, int argc, int nresults) {
|
||||
int handler_pos = gettop(L) - argc;
|
||||
pushcfunction(L, l_error_handler);
|
||||
insert(L, handler_pos);
|
||||
int top = gettop(L);
|
||||
if (lua_pcall(L, argc, nresults, handler_pos)) {
|
||||
std::string log = tostring(L, -1);
|
||||
pop(L);
|
||||
remove(L, handler_pos);
|
||||
throw luaerror(log);
|
||||
}
|
||||
int added = gettop(L) - (top - argc - 1);
|
||||
remove(L, handler_pos);
|
||||
return nresults == -1 ? 1 : nresults;
|
||||
return added;
|
||||
}
|
||||
|
||||
int lua::call_nothrow(State* L, int argc, int nresults) {
|
||||
int handler_pos = gettop(L) - argc;
|
||||
pushcfunction(L, l_error_handler);
|
||||
insert(L, handler_pos);
|
||||
int top = gettop(L);
|
||||
if (lua_pcall(L, argc, -1, handler_pos)) {
|
||||
auto errorstr = tostring(L, -1);
|
||||
if (errorstr) {
|
||||
@ -188,8 +191,9 @@ int lua::call_nothrow(State* L, int argc, int nresults) {
|
||||
remove(L, handler_pos);
|
||||
return 0;
|
||||
}
|
||||
int added = gettop(L) - (top - argc - 1);
|
||||
remove(L, handler_pos);
|
||||
return 1;
|
||||
return added;
|
||||
}
|
||||
|
||||
void lua::dump_stack(State* L) {
|
||||
|
||||
@ -25,7 +25,8 @@ namespace lua {
|
||||
if (n < 0) {
|
||||
abort();
|
||||
}
|
||||
if (lua_gettop(L) < n) {
|
||||
int top = lua_gettop(L);
|
||||
if (top < n) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
@ -63,6 +64,9 @@ namespace lua {
|
||||
inline void rawseti(lua::State* L, int n, int idx = -2) {
|
||||
lua_rawseti(L, idx, n);
|
||||
}
|
||||
inline void rawset(lua::State* L, int idx = -3) {
|
||||
lua_rawset(L, idx);
|
||||
}
|
||||
|
||||
inline int createtable(lua::State* L, int narr, int nrec) {
|
||||
lua_createtable(L, narr, nrec);
|
||||
|
||||
@ -17,6 +17,7 @@ union RGBA {
|
||||
struct {
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
uint8_t arr[4];
|
||||
uint32_t rgba;
|
||||
};
|
||||
|
||||
@ -49,38 +50,118 @@ static int l_at(State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static RGBA get_rgba(State* L, int first) {
|
||||
RGBA rgba {};
|
||||
rgba.a = 255;
|
||||
switch (gettop(L) - first) {
|
||||
case 0:
|
||||
rgba.rgba = static_cast<uint>(tointeger(L, first));
|
||||
break;
|
||||
case 3:
|
||||
rgba.a = static_cast<ubyte>(tointeger(L, first + 3));
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
rgba.r = static_cast<ubyte>(tointeger(L, first));
|
||||
rgba.g = static_cast<ubyte>(tointeger(L, first + 1));
|
||||
rgba.b = static_cast<ubyte>(tointeger(L, first + 2));
|
||||
break;
|
||||
}
|
||||
return rgba;
|
||||
}
|
||||
|
||||
static int l_set(State* L) {
|
||||
auto x = static_cast<uint>(tointeger(L, 2));
|
||||
auto y = static_cast<uint>(tointeger(L, 3));
|
||||
|
||||
if (auto pixel = get_at(L, x, y)) {
|
||||
switch (gettop(L)) {
|
||||
case 4:
|
||||
pixel->rgba = static_cast<uint>(tointeger(L, 4));
|
||||
return 1;
|
||||
case 6:
|
||||
pixel->r = static_cast<ubyte>(tointeger(L, 4));
|
||||
pixel->g = static_cast<ubyte>(tointeger(L, 5));
|
||||
pixel->b = static_cast<ubyte>(tointeger(L, 6));
|
||||
pixel->a = 255;
|
||||
return 1;
|
||||
case 7:
|
||||
pixel->r = static_cast<ubyte>(tointeger(L, 4));
|
||||
pixel->g = static_cast<ubyte>(tointeger(L, 5));
|
||||
pixel->b = static_cast<ubyte>(tointeger(L, 6));
|
||||
pixel->a = static_cast<ubyte>(tointeger(L, 7));
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
*pixel = get_rgba(L, 4);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LuaCanvas& require_canvas(State* L, int idx) {
|
||||
if (const auto canvas = touserdata<LuaCanvas>(L, idx)) {
|
||||
return *canvas;
|
||||
}
|
||||
throw std::runtime_error(
|
||||
"canvas expected as argument #" + std::to_string(idx)
|
||||
);
|
||||
}
|
||||
|
||||
static int l_clear(State* L) {
|
||||
auto& canvas = require_canvas(L, 1);
|
||||
auto& image = canvas.data();
|
||||
ubyte* data = image.getData();
|
||||
RGBA rgba {};
|
||||
if (gettop(L) == 1) {
|
||||
std::fill(data, data + image.getDataSize(), 0);
|
||||
return 0;
|
||||
}
|
||||
rgba = get_rgba(L, 2);
|
||||
size_t pixels = image.getWidth() * image.getHeight();
|
||||
const size_t channels = 4;
|
||||
for (size_t i = 0; i < pixels * channels; i++) {
|
||||
data[i] = rgba.arr[i % channels];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_line(State* L) {
|
||||
int x1 = tointeger(L, 2);
|
||||
int y1 = tointeger(L, 3);
|
||||
|
||||
int x2 = tointeger(L, 4);
|
||||
int y2 = tointeger(L, 5);
|
||||
|
||||
RGBA rgba = get_rgba(L, 6);
|
||||
if (auto canvas = touserdata<LuaCanvas>(L, 1)) {
|
||||
auto& image = canvas->data();
|
||||
image.drawLine(
|
||||
x1, y1, x2, y2, glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a}
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_blit(State* L) {
|
||||
auto& dst = require_canvas(L, 1);
|
||||
auto& src = require_canvas(L, 2);
|
||||
int dst_x = tointeger(L, 3);
|
||||
int dst_y = tointeger(L, 4);
|
||||
dst.data().blit(src.data(), dst_x, dst_y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_data(State* L) {
|
||||
auto& canvas = require_canvas(L, 1);
|
||||
auto& image = canvas.data();
|
||||
auto data = image.getData();
|
||||
|
||||
if (lua::isstring(L, 2)) {
|
||||
auto ptr = reinterpret_cast<ubyte*>(std::stoull(lua::tostring(L, 2)));
|
||||
std::memcpy(data, ptr, image.getDataSize());
|
||||
return 0;
|
||||
}
|
||||
int len = objlen(L, 2);
|
||||
if (len < image.getDataSize()) {
|
||||
throw std::runtime_error(
|
||||
"data size mismatch expected " +
|
||||
std::to_string(image.getDataSize()) + ", got " + std::to_string(len)
|
||||
);
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
rawgeti(L, i + 1, 2);
|
||||
data[i] = tointeger(L, -1);
|
||||
pop(L);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_update(State* L) {
|
||||
if (auto canvas = touserdata<LuaCanvas>(L, 1)) {
|
||||
canvas->texture().reload(canvas->data());
|
||||
if (canvas->hasTexture()) {
|
||||
canvas->texture().reload(canvas->data());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -88,7 +169,11 @@ static int l_update(State* L) {
|
||||
static std::unordered_map<std::string, lua_CFunction> methods {
|
||||
{"at", lua::wrap<l_at>},
|
||||
{"set", lua::wrap<l_set>},
|
||||
{"update", lua::wrap<l_update>}
|
||||
{"line", lua::wrap<l_line>},
|
||||
{"blit", lua::wrap<l_blit>},
|
||||
{"clear", lua::wrap<l_clear>},
|
||||
{"update", lua::wrap<l_update>},
|
||||
{"_set_data", lua::wrap<l_set_data>},
|
||||
};
|
||||
|
||||
static int l_meta_index(State* L) {
|
||||
@ -110,6 +195,9 @@ static int l_meta_index(State* L) {
|
||||
if (!strcmp(name, "height")) {
|
||||
return pushinteger(L, data.getHeight());
|
||||
}
|
||||
if (!strcmp(name, "set_data")) {
|
||||
return getglobal(L, "__vc_Canvas_set_data");
|
||||
}
|
||||
if (auto func = methods.find(tostring(L, 2)); func != methods.end()) {
|
||||
return pushcfunction(L, func->second);
|
||||
}
|
||||
@ -126,18 +214,33 @@ static int l_meta_newindex(State* L) {
|
||||
if (isnumber(L, 2) && isnumber(L, 3)) {
|
||||
if (auto pixel = get_at(data, static_cast<uint>(tointeger(L, 2)))) {
|
||||
pixel->rgba = static_cast<uint>(tointeger(L, 3));
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_meta_meta_call(lua::State* L) {
|
||||
auto size = glm::ivec2(tovec2(L, 2));
|
||||
if (size.x <= 0 || size.y <= 0) {
|
||||
throw std::runtime_error("size must be positive");
|
||||
}
|
||||
return newuserdata<LuaCanvas>(
|
||||
L,
|
||||
nullptr,
|
||||
std::make_shared<ImageData>(ImageFormat::rgba8888, size.x, size.y)
|
||||
);
|
||||
}
|
||||
|
||||
int LuaCanvas::createMetatable(State* L) {
|
||||
createtable(L, 0, 3);
|
||||
pushcfunction(L, lua::wrap<l_meta_index>);
|
||||
setfield(L, "__index");
|
||||
pushcfunction(L, lua::wrap<l_meta_newindex>);
|
||||
setfield(L, "__newindex");
|
||||
|
||||
createtable(L, 0, 1);
|
||||
pushcfunction(L, lua::wrap<l_meta_meta_call>);
|
||||
setfield(L, "__call");
|
||||
setmetatable(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ std::unique_ptr<Process> scripting::start_coroutine(
|
||||
lua::loadbuffer(L, 0, source, script.name());
|
||||
if (lua::call(L, 1)) {
|
||||
int id = lua::tointeger(L, -1);
|
||||
lua::pop(L, 2);
|
||||
lua::pop(L, 1);
|
||||
return std::make_unique<LuaCoroutine>(L, id);
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
@ -370,11 +370,14 @@ dv::value Entities::serialize(const Entity& entity) {
|
||||
dv::value Entities::serialize(const std::vector<Entity>& entities) {
|
||||
auto list = dv::list();
|
||||
for (auto& entity : entities) {
|
||||
if (!entity.getDef().save.enabled) {
|
||||
const EntityId& eid = entity.getID();
|
||||
if (!entity.getDef().save.enabled || eid.destroyFlag) {
|
||||
continue;
|
||||
}
|
||||
level.entities->onSave(entity);
|
||||
list.add(level.entities->serialize(entity));
|
||||
if (!eid.destroyFlag) {
|
||||
list.add(level.entities->serialize(entity));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@ -597,9 +600,9 @@ bool Entities::hasBlockingInside(AABB aabb) {
|
||||
|
||||
std::vector<Entity> Entities::getAllInside(AABB aabb) {
|
||||
std::vector<Entity> collected;
|
||||
auto view = registry.view<Transform>();
|
||||
for (auto [entity, transform] : view.each()) {
|
||||
if (aabb.contains(transform.pos)) {
|
||||
auto view = registry.view<EntityId, Transform>();
|
||||
for (auto [entity, eid, transform] : view.each()) {
|
||||
if (!eid.destroyFlag && aabb.contains(transform.pos)) {
|
||||
const auto& found = uids.find(entity);
|
||||
if (found == uids.end()) {
|
||||
continue;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user