Merge pull request #855 from MihailRis/animation

Animation
This commit is contained in:
MihailRis 2026-09-08 21:50:11 +03:00 • committed by GitHub
commit 349e61a3f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 1674 additions and 506 deletions

View file

@ -3,11 +3,13 @@
`preload.json` in content-pack folder is used for specifying additional assets should be loaded, like sounds.
The file contains following categories available:
- animation
- fonts
- shaders
- textures
- sounds
- models
- shaders
- skeletons
- sounds
- textures
> [!NOTE]
> Sound loading with all variations following template:

View file

@ -37,6 +37,16 @@ assets.parse_model(
skeleton_name: string
)
-- Parses and loads animation
assets.parse_animation(
-- Animation file format (vca)
format: string,
-- Animation file content
content: string,
-- Animation name after loading
name: string
)
-- Creates a Canvas from a loaded texture.
assets.to_canvas(
-- The name of the loaded texture.

View file

@ -17,7 +17,7 @@ entity:def_name() -> str
-- Returns the name of the entity skeleton
entity:get_skeleton() -> str
-- Replaces the entity skeleton
entity:set_skeleton(name: str)
entity:set_skeleton(name: str | nil)
-- Returns the unique entity identifier
entity:get_uid() -> int

View file

@ -100,7 +100,7 @@ xml.tostring(data: XML, multiline: bool = true) -> str
-- Parses XML
xml.parse(code: str) -> XML
-- Parses the VCD (VoxelCore Declaration) format used by the VCM format.
-- Parses the VCD (VoxelCore Declaration) format used by VCM and VCA formats.
-- root_tag - a root element with the specified tag will be implicitly created,
-- containing all root elements
xml.parse_vcd(code: str, root_tag: str = "root") -> XML

34
doc/ru/animation.md Normal file
View file

@ -0,0 +1,34 @@
# Анимация
Для того, чтобы загрузить дорожку анимации для скелета сущности ([см. риггинг](rigging.md)) или камеры ([см. камеры](scripting/builtins/libcameras.md)) её, как любой, незагружаемый по-умолчанию, следует указать в [preload.json пака](assets-preload.md) в секции animation.
На данный момент, движком поддерживается один формат описания анимации - [VCA](vca.md).
Общий API для работы с анимацией, как и другими продолжительными событиями, существует библиотека animation, доступная в виде глобальной переменной.
Самый простой способ анимировать что-то:
```lua
animation.play("имя_анимации", цель)
```
Где цель может быть камерой:
```lua
local camera = cameras.get(cameras.index("пак:имя_камеры"))
animation.play("имя_анимации", camera)
```
Или скелет сущности:
```lua
animation.play("имя_анимации", entity.skeleton)
```
Но для скелетов, подразумевающих переключение между состояниями (те же мобы), лучше использовать [компонент](scripting/ecs.md), реализующий простую машину состояний для анимации - [core:animator](#компонент-coreanimator)
Более подробно функциональность библиотеки animation описана [в соответствующем разделе](scripting/builtins/libanimation.md)
## Компонент core:animator
Компонент находится в работе. Зайдите позже.

View file

@ -3,11 +3,13 @@
Для загрузки ассетов, не загружаемых автоматически, такие как звуки, дополнительные текстуры, используется файл `preload.json`, создающийся в папке контент-пака.
Ассеты в файле разделяются на категории:
- animation - анимация
- fonts - шрифты
- shaders - шейдеры
- textures - текстуры
- sounds - звуки
- models - модели
- shaders - шейдеры
- skeletons - скелеты
- sounds - звуки
- textures - текстуры
> [!NOTE]
> При загрузке звука подгружаются все его вариации, по шаблону:

View file

@ -6,6 +6,7 @@
## Разделы
- [XML разметка интерфейса](xml-ui-layouts.md)
- [Анимация](animation.md)
- [Аудио](audio.md)
- [Движок генерации мира](world-generator.md)
- [Консоль](console.md)
@ -21,4 +22,5 @@
- [Скриптинг](scripting.md)
- [Стили текста](text-styles.md)
- [Формат моделей VCM](vcm.md)
- [Формат анимации VCA](vca.md)
- [Частицы](particles.md)

View file

@ -9,6 +9,7 @@
- [Свойства и методы UI элементов](scripting/ui.md)
- [Сущности и компоненты](scripting/ecs.md)
- [Библиотеки](#)
- [animation](scripting/builtins/libanimation.md)
- [app](scripting/builtins/libapp.md)
- [assets](scripting/builtins/libassets.md)
- [base64](scripting/builtins/libbase64.md)

View file

@ -0,0 +1,21 @@
# Библиотека *animation*
Библиотека для работы с анимацией и решения некоторых сопутствующих задач.
```lua
-- Воспроизводит анимацию на указанном объекте
animation.play(
-- Имя дорожки анимации
name: string,
-- Цель, что будет анимирована
target: Skeleton | Camera
)
```
```lua
-- Запускает корутину, обновляющуюся вне паузы и завершающуюся вместе с выходом из мира.
animation.action(
-- Целевая асинхронная функция
func: function()
)
```

View file

@ -38,6 +38,16 @@ assets.parse_model(
skeleton_name: string
)
-- Парсит и загружает анимацию
assets.parse_animation(
-- Формат файла анимации (vca)
format: string,
-- Содержимое файла анимации
content: string,
-- Имя анимации после загрузки
name: string
)
-- Создаёт холст (Canvas) из загруженной текстуры
assets.to_canvas(
-- Имя загруженной текстуры.

View file

@ -17,7 +17,7 @@ entity:def_name() -> str
-- Возращает имя скелета сущности
entity:get_skeleton() -> str
-- Заменяет скелет сущности
entity:set_skeleton(name: str)
entity:set_skeleton(name: str | nil)
-- Возращает уникальный идентификатор сущности
entity:get_uid() -> int

View file

@ -100,7 +100,7 @@ xml.tostring(data: XML, multiline: bool = true) -> str
-- Парсит XML
xml.parse(code: str) -> XML
-- Парсит формат VCD (VoxelCore Declaration), используемый форматом VCM.
-- Парсит формат VCD (VoxelCore Declaration), используемый форматами VCM и VCA.
-- root_tag - будет неявно создан корневой элемент с указанным тегом,
-- содержащий все корневые элементы
xml.parse_vcd(code: str, root_tag: str | nil = nil) -> XML

107
doc/ru/vca.md Normal file
View file

@ -0,0 +1,107 @@
# VCA
Текстовый формат анимации, использующий синтаксис [VCM](vcm.md).
VCA файл состоит из набора директив, порядок следования которых не влияет на порядок применение трансформаций.
Директивы применяются к переданной цели, которая может быть скелетом сущности и камерой.
При использовании скелета, в директиве указывается имя кости через атрибут `bone`:
```vcd
@move bone имя_кости ...
```
Директивы отвечают за отдельные скалярные значения, такие как x, y, z и т.д., указываемые через атрибут `by`:
```vcd
@move bone hand by y ...
```
На данный момент доступны два вида описания кривых, дающих значение `f(t) = x` где t - время:
- [кривые с ключевыми кадрами (curve)](#кривые-с-ключевыми-кадрами)
- [кривые через выражения (func)](#кривые-через-выражения)
## Директивы анимации
- `@move` - смещение объекта/кости
- `@rotate` - поворот объекта/кости
- `@scale` - масштабирование объекта/кости (множитель)
- `@zoom` - масштаб камеры (множитель)
## Мета-информация
Информация о анимации в файле настраивается через директиву `configure`:
- `fps` - частота кадров, являющаяся делителем при рассчёте длительности
- `frames` - число кадров анимации, являющееся делимым при рассчёте длительности
- `duration` - явно указанная длительность в секундах (требуется указание fps при наличии ключевых кадров)
- `rotation-order` - указывает порядок применения углов Эйлера при вращении (так как порядок директив не учитывается форматом). Примеры: XYZ, ZYX, YZX.
Мета-информация применяется глобально. Поведение не зависит от её положения в файле, но рекомендуется располагать её в начале.
## Кривые с ключевыми кадрами
Требуется через атрибут `curve` указать тип кривой:
- `const` - значения не интерполируются, сменяясь в момент достижения ключевого кадра
- `linear` - ломанная линия, используется линейная интерполяция
- `bezier` - ключевые кадры кривой Безье с явно указанными левой и правой касательными (lx, ly, rx, ry)
Ключевые кадры описываются в последующем `{...}` блоке в виде упорядоченных `@key` директив:
```vcd
@rotate by z curve linear {
@key ...
@key ...
}
```
Ключевой кадр должен содержать номер кадра `frame` и значенин `value`. Для `bezier` кадров также описываются касательные.
Пример для `curve bezier`:
```vcd
@key frame 138 value 53.00411 lx 127.067 ly 53.00411 rx 138.001 ry 53.00411
```
## Кривые через выражения
Через атрибут описывается функция `f(t) = x` где t - время в секундах.
Пример: `func (sin(t * 2.5) + pi)`.
Операции доступные в выражениях:
- `a + b` - сложение
- `a * b` - умножение
- `a / b` - деление
- `a % b` - деление по модулю
- `a ^ b` - возведение в степень
Константы доступные в выражениях:
- `pi` число π
- `e` число Эйлера
Функции доступные в выражениях:
- `sin(x)` - синус от x
- `cos(x)` - косинус от x
- `tan(x)` - тангенс от x
- `noise(x, octaves)` - быстрый одномерный шум от x с указанным числом октав
- `noise2d(x, y, octaves)` - двумерный шум от x,y с указанным числом октав
- `sign(x)` - целое число, указывающее знак x (-1/0/1)
- `rand(low, high)` - псевдослучайное дробное число в диапазоне от low до high
- `round(x, places)` - округляет значение x до указанного количества знаков после запятой places (опционально).
- `floor(x)` - округляет значение x до ближайшего меньшего целого
- `ceil(x)` - округляет значение x до ближайшего большего целого
- `exp(x)` - $e^{x}$
- `min(x, ...)` - выбирает наименьшее значение из переданных аргументов
- `max(x, ...)` - выбирает наибольшее значение из переданных аргументов
- `sqrt(x)` - квадратный корень от x
- `log(x)` - $ln({x})$
- `log10(x)` - $log_{10}({x})$
- `deg(x)` - конвертирует радианы в градусы
- `rad(x)` - конвертирует градусы в радианы
>[!WARNING]
> Использование незадокументированных функций может иметь иметь любые незадокументированные последствия.

View file

@ -1,4 +1,4 @@
# VC-DBG protocol v1
# VC-DBG protocol v1.1
## Notes
@ -116,6 +116,15 @@ Then `value` is:
}
```
### File hot-reload signal
```json
{
"type": "hot-reload",
"file": "string"
}
```
## Server-to-client
### Response signals

View file

@ -0,0 +1,7 @@
language = "VCA"
extensions = ["vca"]
line-comment = "#"
identifier-part-chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
keywords = [
"bone", "by", "func", "curve", "fps", "duration", "frames", "rotation-order"
]

30
res/models/human.vcm Normal file
View file

@ -0,0 +1,30 @@
@bone name 'body' {
@box from (-0.2,0,-0.125) to (0.2,0.5,0.15)
@bone name 'head' move (0,0.5,0) {
@box from (-0.2,0,-0.2) to (0.2,0.4,0.2)
}
@bone name 'leg_left' move (-0.125, 0, 0) rotate (-0,0,0) {
@box from (-0.075, -0.45, -0.125) to (0.125, 0.0, 0.15)
@bone name 'leg_left_btm' move (0, -0.45, 0) rotate (0,0,0) {
@box from (-0.075, -0.45, -0.125) to (0.125, 0.0, 0.15)
}
}
@bone name 'leg_right' move (0.125, 0.0, 0) rotate (0,0,0) {
@box from (-0.125, -0.45, -0.125) to (0.075, 0.0, 0.15)
@bone name 'leg_right_btm' move (0, -0.45, 0) rotate (0,0,0) {
@box from (-0.125, -0.45, -0.125) to (0.075, 0.0, 0.15)
}
}
@bone name 'hand_left' move (-0.325, 0.5, 0) rotate (-0,0,0) {
@box from (-0.075, -0.45, -0.125) to (0.125, 0.0, 0.15)
@bone name 'hand_left_btm' move (0, -0.45, 0) rotate (-0,0,0) {
@box from (-0.075, -0.45, -0.125) to (0.125, 0.0, 0.15)
}
}
@bone name 'hand_right' move (0.325, 0.5, 0) rotate (0,0,0) {
@box from (-0.125, -0.45, -0.125) to (0.075, 0.0, 0.15)
@bone name 'hand_right_btm' move (0, -0.45, 0) rotate (-0,0,0) {
@box from (-0.125, -0.45, -0.125) to (0.075, 0.0, 0.15)
}
}
}

378
res/modules/animation.lua Normal file
View file

@ -0,0 +1,378 @@
local internals = __vc_internals
local this = {
CH_TRANSLATE = 1,
CH_ROTATE = 2,
CH_SCALE = 3,
CH_ZOOM = 4,
INT_CONST = 1,
INT_LINEAR = 2,
INT_BEZIER = 3,
TRACE_CODEGEN = false,
}
local INT_CONST = this.INT_CONST
local INT_BEZIER = this.INT_BEZIER
local function bezier(a, b, c, d, u)
local s = 1 - u
return s * s * s * a +
3 * s * s * u * b +
3 * s * u * u * c +
u * u * u * d
end
local function bezier_derivative(a, b, c, d, u)
local s = 1 - u
return
3 * s * s * (b - a) +
6 * s * u * (c - b) +
3 * u * u * (d - c)
end
local function bezier_interpolation(k0, k1, t)
local frame = k0.frame + t * (k1.frame - k0.frame)
local u = t
for i=1,8 do
local x = bezier(k0.frame, k0.rx, k1.lx, k1.frame, u)
local dx = bezier_derivative(k0.frame, k0.rx, k1.lx, k1.frame, u)
if math.abs(dx) < 1e-8 then
break
end
u = u - (x - frame) / dx
if u < 0 then u = 0 end
if u > 1 then u = 1 end
end
return bezier(k0.value, k0.ry, k1.ly, k1.value, u)
end
local patterns = {
{name="sint", pattern="sin(t)"},
{name="sint2", pattern="sin(t * 2)"},
}
local exclude_patters = {
"end",
(string.pattern_safe("'")),
(string.pattern_safe('"')),
(string.pattern_safe("--")),
(string.pattern_safe("..")),
}
-- TODO: replace with actual expression -> lua translator
local function process_expression(src, memoised)
for i, pattern in ipairs(exclude_patters) do
if src:find(pattern) then
debug.print(exclude_patters)
error("invalid syntax "..string.escape(src))
end
end
for i, pattern in ipairs(patterns) do
local pattern_safe = string.pattern_safe(pattern.pattern)
if src:find(pattern_safe) then
memoised[pattern.name] = pattern.pattern
src = src:gsub(pattern_safe, pattern.name)
end
end
return src
end
local function key_neighbors(keys, frame)
local left = 1
local right = #keys
while left <= right do
local mid = math.floor((left + right) / 2)
if keys[mid].frame < frame then
left = mid + 1
elseif keys[mid].frame > frame then
right = mid - 1
else
return mid, mid
end
end
if left > #keys then
left = #keys
end
return right, left
end
local env = {
mat4 = mat4,
e = math.exp(1),
X = {1, 0, 0},
Y = {0, 1, 0},
Z = {0, 0, 1},
DST = mat4.idt(),
value_at = function(keys, frame, interp)
local left, right = key_neighbors(keys, frame)
if left == right then
return keys[left].value
end
left = keys[left]
if interp == INT_CONST then
return left.value
end
right = keys[right]
if left == nil then
return right.value
end
local t = (frame - left.frame) / (right.frame - left.frame)
if interp == INT_BEZIER then
return bezier_interpolation(left, right, t)
end
return left.value * (1.0 - t) + right.value * t
end,
set_matrix = function(target, matrix)
local info = mat4.decompose(matrix)
if info then
local pos = info.translation
local rot = info.rotation
local scale = info.scale
if target.set_pos then
target:set_pos(pos)
end
if target.set_rot then
target:set_rot(rot)
end
if target.set_scale then
target:set_scale(scale)
end
end
end,
dump = debug.print
}
local math_funcs = {
"sqrt", "min", "max", "deg", "rad", "log", "log10", "floor", "ceil", "sin",
"tan", "noise", "noise2", "sign", "round", "exp", "pi", "e"
}
for _, name in ipairs(math_funcs) do
env[name] = math[name]
end
local function codegen_track(raw_track, lineset, memoised, keysets, use_tsf)
local lines = lineset.lines
local code = ""
local has_tsf = false
local translation = {false, false, false}
local rotation = {false, false, false}
local scale = {false, false, false}
for i, line in ipairs(lines) do
if line.expression then
code = code .. "\n local l" .. i .. " = (" ..
process_expression(line.expression, memoised) .. ")"
elseif line.keys then
local target_keysets = keysets[lineset.target_name]
if not target_keysets then
target_keysets = {}
keysets[lineset.target_name] = target_keysets
end
target_keysets[i] = line.keys
code = code .. string.format(
"\n local l%d = value_at(keysets['%s'][%d], t * %s, %s)",
i, lineset.target_name, i, raw_track.fps, line.interp)
end
if line.channel == this.CH_TRANSLATE then
translation[line.axis] = i
has_tsf = true
elseif line.channel == this.CH_ROTATE then
rotation[line.axis] = i
has_tsf = true
elseif line.channel == this.CH_SCALE then
scale[line.axis] = i
has_tsf = true
elseif line.channel == this.CH_ZOOM then
code = code .. "\n zoom = l" .. i
end
end
if not has_tsf or not use_tsf then
return code
end
code = code .. "\n mat4.idt(dst)"
if translation[1] or translation[2] or translation[3] then
code = code .. "\n mat4.translate(dst, {" ..
(translation[1] and ("l" .. translation[1]) or '0').. ", " ..
(translation[2] and ("l" .. translation[2]) or '0').. ", " ..
(translation[3] and ("l" .. translation[3]) or '0').. "}, dst)"
end
local axis_names = {"X", "Y", "Z"}
local axis_indices = {X=1, Y=2, Z=3}
local rotation_order = raw_track.rotation_order
for i=1,3 do
local axis = axis_indices[rotation_order[i]]
local var = rotation[axis]
if var then
code = code .. "\n mat4.rotate(dst, " .. axis_names[axis] ..
", l" .. var .. ", dst)"
end
end
if scale[1] or scale[2] or scale[3] then
code = code .. "\n mat4.scale(dst, {" ..
(scale[1] and ("l" .. scale[1]) or '1').. ", " ..
(scale[2] and ("l" .. scale[2]) or '1').. ", " ..
(scale[3] and ("l" .. scale[3]) or '1').. "}, dst)"
end
return code
end
local function codegen_rig_target(raw_track, memoised, keysets)
local code = "\n if target.set_matrix and target.index then\n"
code = code .. " local dst = DST\n"
for bone, lineset in pairs(raw_track.linesets) do
if lineset.target_type ~= "bone" then
goto continue
end
local lineset_code = codegen_track(
raw_track, lineset, memoised, keysets, true)
code = code .. "\n do" .. lineset_code .. "\n end\n" ..
" target:set_matrix(target:index(" .. string.escape(bone) .. "), dst)\n"
::continue::
end
return code .. " end"
end
local function codegen_object_target(raw_track, memoised, keysets)
local code = "\n if target.set_pos then\n"
code = code .. " local dst = DST\n"
local lineset = raw_track.linesets[""]
if not lineset then
return ""
end
local lineset_code = codegen_track(
raw_track, lineset, memoised, keysets, true)
code = code .. "\n do" .. lineset_code .. "\n end\n"
.. " set_matrix(target, dst)\n"
return code .. " end"
end
local function codegen_camera_target(raw_track, memoised, keysets)
local code = "\n if target.set_zoom then\n"
code = code .. " local zoom = 1.0\n"
local lineset = raw_track.linesets[""]
if not lineset then
return ""
end
local lineset_code = codegen_track(
raw_track, lineset, memoised, keysets, false)
code = code .. "\n do" .. lineset_code .. "\n end\n"
.. " target:set_zoom(zoom)\n"
return code .. " end"
end
function this.compile_track(raw_track, track_name)
local code = ""
local memoised = {}
local keysets = {}
code = code .. codegen_rig_target(raw_track, memoised, keysets)
code = code .. codegen_object_target(raw_track, memoised, keysets)
code = code .. codegen_camera_target(raw_track, memoised, keysets)
local memoised_code = ""
for name, expression in pairs(memoised) do
memoised_code = memoised_code .. "\n local " .. name .. " = "
.. expression
end
if #memoised_code > 0 then
code = memoised_code .. "\n" .. code
end
local src = "return function(target, t, m)\n"
.. code .. "\nend"
if this.TRACE_CODEGEN then
debug.log("["..
string.escape(track_name or "nil").." codegen trace]:\n"..src)
end
local generator, err = load(
src, "<expr>", "bt", table.extend({keysets = keysets}, env))
if not generator then
error(err)
end
return {
duration = raw_track.duration,
func = generator(),
}
end
local loaded_tracks = {}
local backup_tracks = {}
function internals.store_animation(name, track)
loaded_tracks[name] = track
end
function this.get_track(identifier)
return loaded_tracks[identifier]
end
local running_actions = {}
local playing_tracks = {}
function this.action(func)
table.insert(running_actions, coroutine.create(func))
end
function this.play(name, target)
table.insert(playing_tracks, {name=name, target=target, timer=0.0})
end
function internals.on_animation_frame()
for i=1,#running_actions do
local co = running_actions[i]
local status, result = coroutine.resume(co)
if not status then
debug.error("error in animation action: "..result)
table.remove(running_actions, i)
elseif coroutine.status(co) == "dead" then
table.remove(running_actions, i)
end
end
local delta = time.delta()
for i=1,#playing_tracks do
local track_info = playing_tracks[i]
local track = loaded_tracks[track_info.name]
if not track then
debug.error("animation track not found: "..track_info.name)
table.remove(playing_tracks, i)
else
track_info.timer = track_info.timer + delta
if track_info.timer > track.duration then
table.remove(playing_tracks, i)
else
track.func(track_info.target, track_info.timer)
end
end
end
end
function internals.stop_animation_actions()
running_actions = {}
end
function internals.backup_and_clear_animation()
loaded_tracks, backup_tracks = backup_tracks, {}
end
function internals.restore_animation_backup()
loaded_tracks, backup_tracks = backup_tracks, {}
end
return this

View file

@ -0,0 +1,107 @@
local internals = __vc_internals
local DEFAULT_FPS = 60
local INT_BEZIER = animation.INT_BEZIER
local action_to_channel = {
move = animation.CH_TRANSLATE,
rotate = animation.CH_ROTATE,
scale = animation.CH_SCALE,
zoom = animation.CH_ZOOM,
}
local curve_to_interp = {
const = animation.INT_CONST,
linear = animation.INT_LINEAR,
bezier = animation.INT_BEZIER,
}
local function parse_configure(raw_track, node)
if node.fps then
raw_track.fps = node.fps
end
if node.frames then
raw_track.duration = node.frames / (node.fps or DEFAULT_FPS)
elseif node.duration then
raw_track.duration = node.duration
end
raw_track.rotation_order = string.upper(node["rotation-order"]) or "XYZ"
end
local function parse_curve(line, node)
line.interp = curve_to_interp[node.curve]
line.keys = {}
for j, key_node in ipairs(node) do
local keyframe = {
frame = tonumber(key_node.frame),
value = tonumber(key_node.value),
}
if line.interp == INT_BEZIER then
keyframe.lx = tonumber(key_node.lx)
keyframe.ly = tonumber(key_node.ly)
keyframe.rx = tonumber(key_node.rx)
keyframe.ry = tonumber(key_node.ry)
end
table.insert(line.keys, keyframe)
end
end
local function parse_track(root)
local raw_track = {
duration = math.huge,
fps = DEFAULT_FPS,
linesets = {},
}
local linesets = raw_track.linesets
for i, node in ipairs(root) do
if type(node) == "string" then
goto continue
end
local tag = node['#']
if tag == "configure" then
parse_configure(raw_track, node)
goto continue
end
local target_type = nil
if node.bone then
target_type = "bone"
end
local target_name = node.bone or ""
local lineset = linesets[target_name]
if not lineset then
lineset = {
lines = {},
target_type = target_type,
target_name = target_name
}
linesets[target_name] = lineset
end
local line = {
axis = node.by and ("xyz"):find(node.by) or "",
channel = action_to_channel[tag]
}
if node.func then
line.expression = node.func
elseif node.curve then
parse_curve(line, node)
else
error("not implemented")
end
table.insert(lineset.lines, line)
::continue::
end
return raw_track
end
local function load_vca(source, filepath)
local raw_track = parse_track(xml.parse_vcd(source, "track"))
return animation.compile_track(raw_track, filepath)
end
function internals.load_vca_animation(filepath, source, identifier)
local track = load_vca(source or file.read(filepath), filepath)
internals.store_animation(identifier, track)
end

View file

@ -1,3 +1,5 @@
local internals = __vc_internals
local updating_blocks = {}
local present_queues = {}
local REGISTER_BIT = 0x1
@ -5,7 +7,7 @@ local UPDATING_BIT = 0x2
local PRESENT_BIT = 0x4
local REMOVED_BIT = 0x8
block.__perform_ticks = function(delta)
function internals.tick_updating_blocks(delta)
for id, entry in pairs(updating_blocks) do
entry.timer = entry.timer + delta
local steps = math.floor(entry.timer / entry.delta * #entry / 3)
@ -63,7 +65,7 @@ end
local block_pull_register_events = block.__pull_register_events
block.__pull_register_events = nil
block.__process_register_events = function()
function internals.process_block_register_events()
local register_events = block_pull_register_events()
if not register_events then
return

View file

@ -1,5 +1,6 @@
local __app = __vc_app
local app = __vc_app
local __rules = rules
local internals = __vc_internals
local function configure_SSAO()
-- Temporary using slot to configure built-in SSAO effect
@ -32,10 +33,10 @@ local function configure_SSAO()
end
events.on("core:setting.graphics.ssao.set", update_ssao_quality)
update_ssao_quality(__app.get_setting("graphics.ssao"))
update_ssao_quality(app.get_setting("graphics.ssao"))
end
local function __vc_on_hud_open()
function internals.on_hud_open()
local _hud_is_content_access = hud._is_content_access
local _hud_set_content_access = hud._set_content_access
local _hud_set_debug_cheats = hud._set_debug_cheats
@ -130,7 +131,8 @@ local function ScheduleGroup()
end
local RULES_FILE = "world:rules.toml"
local function __vc_on_world_open()
function internals.on_world_open()
time.schedules.world = ScheduleGroup()
if not file.exists(RULES_FILE) then
@ -142,15 +144,15 @@ local function __vc_on_world_open()
end
end
local function __vc_on_world_tick(tps)
function internals.on_world_tick(tps)
time.schedules.world:tick(1.0 / tps)
end
local function __vc_process_before_quit()
block.__process_register_events()
function internals.process_before_quit()
internals.process_block_register_events()
end
local function __vc_on_world_save()
function internals.on_world_save()
local rule_values = {}
for name, rule in pairs(__rules.rules) do
rule_values[name] = rule.value
@ -165,7 +167,8 @@ file.__close_all_descriptors = nil
gui_util.__reset_local = nil
stdcomp.reset = nil
local function __vc_on_world_quit()
function internals.on_world_quit()
internals.stop_animation_actions()
__rules.clear()
__gui_util_reset_local()
__stdcomp_reset()
@ -176,8 +179,6 @@ local __post_runnables = {}
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
audio.__reset_fetch_buffer = nil
local __vc_update_coroutines = _G.__vc_update_coroutines
local function __process_post_runnables()
if #__post_runnables > 0 then
for _, func in ipairs(__post_runnables) do
@ -189,41 +190,38 @@ local function __process_post_runnables()
__post_runnables = {}
end
__vc_update_coroutines()
internals.update_coroutines()
fn_audio_reset_fetch_buffer()
debug.pull_events()
network.__process_events()
if not hud or not hud.is_paused() then
block.__process_register_events()
block.__perform_ticks(time.delta())
internals.process_block_register_events()
internals.tick_updating_blocks(time.delta())
end
end
local __vc__is_post_runnable = false
local is_post_runnable = false
function __vc__process_post_runnables()
__vc__is_post_runnable = true
function internals.process_post_runnables()
is_post_runnable = true
local success, err = pcall(__process_post_runnables)
if not success then
debug.error("an error ocurred while processing post-runnables: ".. err)
end
__vc__is_post_runnable = false
is_post_runnable = false
end
function internals.is_post_runnable_context()
return is_post_runnable
end
function time.post_runnable(runnable)
table.insert(__post_runnables, runnable)
end
return {
__vc_on_hud_open = __vc_on_hud_open,
__vc_on_world_open = __vc_on_world_open,
__vc_on_world_tick = __vc_on_world_tick,
__vc_process_before_quit = __vc_process_before_quit,
__vc_on_world_save = __vc_on_world_save,
__vc_on_world_quit = __vc_on_world_quit,
__vc__process_post_runnables = __vc__process_post_runnables,
__vc_is_post_runnable_context = function()
return __vc__is_post_runnable
end
}
function internals.on_render()
internals.on_animation_frame()
end
return {}

View file

@ -1,3 +1,8 @@
local sin = math.sin
local cos = math.cos
local sqrt = math.sqrt
local rad = math.rad
-- =================================================== --
-- ====================== vec3 ======================= --
-- =================================================== --
@ -238,3 +243,93 @@ function vec2.mix(a, b, t, dest)
}
end
end
function mat4.idt(dst)
if dst then
for i=1,16 do
dst[i] = (i % 5 == 1) and 1 or 0
end
return dst
end
return {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
}
end
function mat4.rotate(...)
local argc = select("#", ...)
local matrix, axis, angle, dst
if argc == 2 then
axis, angle = ...
matrix = mat4.idt()
elseif argc == 3 then
matrix, axis, angle = ...
elseif argc == 4 then
matrix, axis, angle, dst = ...
else
error("invalid arguments number (2, 3 or 4 expected)")
end
angle = rad(angle)
local x, y, z = axis[1], axis[2], axis[3]
local len = sqrt(x * x + y * y + z * z)
if len == 0 then
error("rotation axis has zero length")
end
x = x / len
y = y / len
z = z / len
local c = cos(angle)
local s = sin(angle)
local t = 1 - c
local r00 = t * x * x + c
local r01 = t * x * y - s * z
local r02 = t * x * z + s * y
local r10 = t * x * y + s * z
local r11 = t * y * y + c
local r12 = t * y * z - s * x
local r20 = t * x * z - s * y
local r21 = t * y * z + s * x
local r22 = t * z * z + c
local out = dst or {}
local m1, m2, m3, m4 = matrix[1], matrix[2], matrix[3], matrix[4]
local m5, m6, m7, m8 = matrix[5], matrix[6], matrix[7], matrix[8]
local m9, m10, m11, m12 = matrix[9], matrix[10], matrix[11], matrix[12]
local m13, m14, m15, m16 = matrix[13], matrix[14], matrix[15], matrix[16]
out[1] = m1 * r00 + m5 * r10 + m9 * r20
out[2] = m2 * r00 + m6 * r10 + m10 * r20
out[3] = m3 * r00 + m7 * r10 + m11 * r20
out[4] = m4 * r00 + m8 * r10 + m12 * r20
out[5] = m1 * r01 + m5 * r11 + m9 * r21
out[6] = m2 * r01 + m6 * r11 + m10 * r21
out[7] = m3 * r01 + m7 * r11 + m11 * r21
out[8] = m4 * r01 + m8 * r11 + m12 * r21
out[9] = m1 * r02 + m5 * r12 + m9 * r22
out[10] = m2 * r02 + m6 * r12 + m10 * r22
out[11] = m3 * r02 + m7 * r12 + m11 * r22
out[12] = m4 * r02 + m8 * r12 + m12 * r22
out[13] = m13
out[14] = m14
out[15] = m15
out[16] = m16
return out
end

View file

@ -50,8 +50,9 @@ end
local Skeleton = {__index={
get_model=function(self, i) return __skeleton.get_model(self.eid, i) end,
set_model=function(self, i, s) return __skeleton.set_model(self.eid, i, s) end,
get_matrix=function(self, i) return __skeleton.get_matrix(self.eid, i) end,
get_matrix=function(self, i, ...) return __skeleton.get_matrix(self.eid, i, ...) end,
set_matrix=function(self, i, m) return __skeleton.set_matrix(self.eid, i, m) end,
reset_pose=function(self) return __skeleton.reset_pose(self.eid) end,
get_texture=function(self, s) return __skeleton.get_texture(self.eid, s) end,
set_texture=function(self, s, s2) return __skeleton.set_texture(self.eid, s, s2) end,
index=function(self, s) return __skeleton.index(self.eid, s) end,
@ -112,6 +113,7 @@ return {
entity.rigidbody = new_Rigidbody(eid)
entity.skeleton = new_Skeleton(eid)
entity.components = {}
entity.ordered_components = {}
entities[eid] = entity;
return entity
end,
@ -122,6 +124,7 @@ return {
local entity = entities[eid]
if entity then
entity.components = nil
entity.ordered_components = nil
entities[eid] = nil;
end
end,
@ -130,10 +133,10 @@ return {
if uid % parts ~= part then
goto continue
end
for _, component in pairs(entity.components) do
for _, component in ipairs(entity.ordered_components) do
local callback = rawget(component, "on_update")
if not component.__disabled and callback then
local result, err = pcall(callback, tps)
local _, err = pcall(callback, tps)
if err then
debug.error(err)
end
@ -144,7 +147,7 @@ return {
end,
physics_update = function(delta)
for uid, entity in pairs(entities) do
for _, component in pairs(entity.components) do
for _, component in ipairs(entity.ordered_components) do
local callback = rawget(component, "on_physics_update")
if not component.__disabled and callback then
local result, err = pcall(callback, delta)
@ -157,10 +160,10 @@ return {
end,
render = function(delta)
for _,entity in pairs(entities) do
for _, component in pairs(entity.components) do
for _, component in ipairs(entity.ordered_components) do
local callback = rawget(component, "on_render")
if not component.__disabled and callback then
local result, err = pcall(callback, delta)
local _, err = pcall(callback, delta)
if err then
debug.error(err)
end

View file

@ -0,0 +1,66 @@
local ALMOST_HUGE = 1e9
local rig = entity.skeleton
local states = {}
function create_state(name, track_id, max_intencity)
states[name] = {
track = nil,
track_id = track_id,
timer = 0.0,
fade_time = 0.0,
fade_timer = 0.0,
fade_in = false,
max_intencity = max_intencity,
}
end
local current_state = nil
function set_state(name, fade_time)
local state = states[name]
if state == current_state then
return
end
if current_state then
current_state.fade_in = false
current_state.fade_time = fade_time
current_state.fade_timer = 0.0
end
current_state = state
if state then
state.timer = 0.0
state.fade_in = true
state.fade_time = fade_time
state.fade_timer = 0.0
end
end
local function update(delta)
for _, state in pairs(states) do
state.timer = state.timer + delta
local intensity = state.fade_in and 1.0 or 0.0
if state.fade_timer < state.fade_time then
state.fade_timer = state.fade_timer + delta
intensity = math.min(1.0, state.fade_timer / state.fade_time)
if not state.fade_in then
intensity = 1.0 - intensity
end
end
state.track = state.track or animation.get_track(state.track_id)
if state.track and intensity > 0.0 then
state.track.func(
rig,
state.timer % math.min(ALMOST_HUGE, state.track.duration),
intensity * state.max_intencity
)
end
end
end
function on_render()
update(time.delta())
end

View file

@ -40,7 +40,7 @@ local function angle_delta(a, b)
return normalize_angle(a - b)
end
local dir = mat4.mul(tsf:get_rot(), {0, 0, -1})
local mob_dir = mat4.mul(tsf:get_rot(), {0, 0, -1})
local flight = false
function jump(multiplier)
@ -87,12 +87,12 @@ function look_at(point, change_dir)
local pos = tsf:get_pos()
local viewdir = vec3.normalize(vec3.sub(point, pos))
local dot = vec3.dot(viewdir, dir)
local dot = vec3.dot(viewdir, mob_dir)
if dot < 0.0 and not change_dir then
viewdir = mat4.mul(tsf:get_rot(), {0, 0, -1})
else
dir[1] = dir[1] * 0.8 + viewdir[1] * 0.13
dir[3] = dir[3] * 0.8 + viewdir[3] * 0.13
mob_dir[1] = mob_dir[1] * 0.8 + viewdir[1] * 0.13
mob_dir[3] = mob_dir[3] * 0.8 + viewdir[3] * 0.13
end
if not headIndex then
@ -112,33 +112,30 @@ end
function follow_waypoints(pathfinding)
pathfinding = pathfinding or entity:require_component("core:pathfinding")
local pos = tsf:get_pos()
local pos = tsf:get_pos()
local waypoint = pathfinding.next_waypoint()
if not waypoint then
return
end
local speed = props.movement_speed
local vel = body:get_vel()
dir = vec3.sub(
mob_dir = vec3.sub(
vec3.add(waypoint, {0.5, 0, 0.5}),
{pos[1], math.floor(pos[2]), pos[3]}
)
local upper = dir[2] > 0
dir[2] = 0.0
vec3.normalize(dir, dir)
move_horizontal(speed, {dir[1], dir[3]}, vel)
local upper = mob_dir[2] > 0
mob_dir[2] = 0.0
vec3.normalize(mob_dir, mob_dir)
move_horizontal(speed, {mob_dir[1], mob_dir[3]}, vel)
if upper and body:is_grounded() then
jump(1.0)
end
end
function set_dir(new_dir)
dir = new_dir
end
function set_dir(new_dir) mob_dir = new_dir end
function get_dir()
return dir
end
function get_dir() return mob_dir end
function is_flight() return flight end
@ -147,7 +144,7 @@ function set_flight(flag)
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
end
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
local prev_angle = (vec2.angle({mob_dir[3], mob_dir[1]})) % 360
function on_physics_update(delta)
local grounded = body:is_grounded()
@ -156,7 +153,7 @@ function on_physics_update(delta)
(flight or not grounded) and props.air_damping or props.ground_damping
)
local new_angle = (vec2.angle({dir[3], dir[1]})) % 360
local new_angle = (vec2.angle({mob_dir[3], mob_dir[1]})) % 360
local angle = prev_angle
local adelta = angle_delta(

View file

@ -1,4 +1,5 @@
local __app = __vc_app
local internals = __vc_internals
local enable_experimental = __app.get_setting("debug.enable-experimental")
------------------------------------------------
@ -45,7 +46,7 @@ local function complete_app_lib(app)
app.tick = __app_tick
local function call_in_app_script_co(func, ...)
if __vc_is_post_runnable_context() then
if internals.is_post_runnable_context() then
func(...)
return
end
@ -208,7 +209,7 @@ function start_coroutine(chunk, name)
__vc_named_coroutines[name] = co
end
function __vc_update_coroutines()
function internals.update_coroutines()
local dead = {}
for name, co in pairs(__vc_named_coroutines) do
local success, err = coroutine.resume(co)
@ -332,6 +333,9 @@ end
world.raycast = entities.__world_raycast
entities.__world_raycast = nil
animation = require "core:animation"
require "core:internal/formats/vca"
__vc_scripts_registry = require "core:internal/scripts_registry"
file.open = require "core:internal/stream_providers/file"
@ -366,6 +370,7 @@ core.get_core_token = audio.input.__get_core_token
require "core:internal/console"
require "core:internal/deprecated"
require "core:internal/internal_events"
------------------------------------------
@ -395,5 +400,4 @@ __vc_app = nil
__vc_internals = nil
__vc_lock_internal_modules()
__vc_lock_internal_modules = nil
__vc_update_coroutines = nil
__VC_SCRIPT_NAME = ""

View file

@ -13,7 +13,7 @@ using namespace rigging;
void ModelReference::refresh(const Assets& assets) {
if (updateFlag) {
model = assets.get<model::Model>(name);
model = assets.getShared<model::Model>(name);
updateFlag = false;
}
}
@ -29,24 +29,27 @@ Bone::Bone(
name(std::move(name)),
bones(std::move(bones)),
offset(offset),
model({model, nullptr, true}) {
model({model, {}, true}) {
}
void Bone::setModel(const std::string& name) {
if (model.name == name) {
return;
}
model = {name, nullptr, true};
model = {name, {}, true};
}
Skeleton::Skeleton(std::shared_ptr<const SkeletonConfig> config)
: config(config),
pose(config->getBones().size()),
calculated(config->getBones().size()),
flags(config->getBones().size()),
pose(config ? config->getBones().size() : 0),
calculated(config ? config->getBones().size() : 0),
flags(config ? config->getBones().size() : 0),
textures(),
modelOverrides(config->getBones().size()),
modelOverrides(config ? config->getBones().size() : 0),
visible(true) {
if (config == nullptr) {
return;
}
const auto& bones = config->getBones();
for (size_t i = 0; i < bones.size(); i++) {
flags[i].visible = true;
@ -89,19 +92,15 @@ void Skeleton::deserialize(const dv::value& root) {
void Skeleton::setConfig(std::shared_ptr<const SkeletonConfig> rigConfig) {
config = std::move(rigConfig);
const auto& bones = config->getBones();
int bonesCount = config ? config->getBones().size() : 0;
pose.matrices.resize(
bones.size(), glm::mat4(1.0f)
);
calculated.matrices.resize(
bones.size(), glm::mat4(1.0f)
);
pose.matrices.resize(bonesCount, glm::mat4(1.0f));
calculated.matrices.resize(bonesCount, glm::mat4(1.0f));
modelOverrides.resize(bones.size());
flags.resize(bones.size());
modelOverrides.resize(bonesCount);
flags.resize(bonesCount);
for (size_t i = 0; i < bones.size(); i++) {
for (size_t i = 0; i < bonesCount; i++) {
flags[i].visible = true;
}
}
@ -194,12 +193,14 @@ void SkeletonConfig::render(
continue;
}
node->model.refresh(assets);
auto model = node->model.model;
auto model = node->model.model.lock().get(); // TODO: cache model pointer during frame
auto& modelOverride = skeleton.modelOverrides.at(i);
if (modelOverride.updateFlag) {
modelOverride.refresh(assets);
}
model = modelOverride.model ? modelOverride.model : model;
if (auto foundOverride = modelOverride.model.lock()) {
model = foundOverride.get();
}
if (model) {
batch.draw(
skeleton.calculated.matrices[i],
@ -207,6 +208,8 @@ void SkeletonConfig::render(
model,
&skeleton.textures
);
} else if (!node->model.name.empty()) {
node->model.updateFlag = true;
}
}
}

View file

@ -33,8 +33,8 @@ namespace rigging {
struct ModelReference {
std::string name;
model::Model* model;
bool updateFlag;
std::weak_ptr<model::Model> model;
bool updateFlag = false;
void refresh(const Assets& assets);
};

View file

@ -1,6 +1,8 @@
#include "Assets.hpp"
#include "AssetsLoader.hpp"
#include "io/path.hpp"
Assets::Assets(util::ObjectsKeeper* vault) : vault(vault) {
Assets::Assets(util::ObjectsKeeper* vault) : vault(vault), assetsLoadInfo(std::make_unique<AssetsLoadInfo>()) {
}
Assets::~Assets() {

View file

@ -1,5 +1,10 @@
#pragma once
#include "util/stringutil.hpp"
#include "util/ObjectsKeeper.hpp"
#include "graphics/core/TextureAnimation.hpp"
#include "io/fwd.hpp"
#include <functional>
#include <memory>
#include <optional>
@ -11,13 +16,12 @@
#include <unordered_map>
#include <vector>
#include "util/stringutil.hpp"
#include "util/ObjectsKeeper.hpp"
#include "graphics/core/TextureAnimation.hpp"
class Assets;
struct AssetsLoadInfo;
class AssetsLoader;
enum class AssetType {
ANIMATION,
TEXTURE,
SHADER,
FONT,
@ -31,12 +35,10 @@ enum class AssetType {
namespace assetload {
/// @brief final work to do in the main thread
using postfunc = std::function<void(Assets*)>;
using setupfunc = std::function<void(const Assets*)>;
using postfunc = std::function<void(Assets&)>;
template <class T>
void assets_setup(const Assets*);
void assets_setup(const Assets&);
class error : public std::runtime_error {
AssetType type;
@ -62,16 +64,13 @@ namespace assetload {
return reason;
}
};
}
class Assets {
util::ObjectsKeeper* vault;
std::vector<TextureAnimation> animations;
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
std::unordered_map<std::type_index, assets_map> assets;
std::vector<assetload::setupfunc> setupFuncs;
public:
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
Assets(util::ObjectsKeeper* vault);
Assets(const Assets&) = delete;
~Assets();
@ -143,20 +142,20 @@ public:
return &mapIter->second;
}
void setup() {
for (auto& setupFunc : setupFuncs) {
setupFunc(this);
}
AssetsLoadInfo& getLoadInfo() const {
return *assetsLoadInfo;
}
private:
util::ObjectsKeeper* vault;
std::vector<TextureAnimation> animations;
void addSetupFunc(assetload::setupfunc setupfunc) {
setupFuncs.push_back(setupfunc);
}
std::unordered_map<std::type_index, assets_map> assets;
std::unique_ptr<AssetsLoadInfo> assetsLoadInfo;
};
template <class T>
void assetload::assets_setup(const Assets* assets) {
if (auto mapPtr = assets->getMap<T>()) {
void assetload::assets_setup(const Assets& assets) {
if (auto mapPtr = assets.getMap<T>()) {
for (const auto& entry : **mapPtr) {
static_cast<T*>(entry.second.get())->setup();
}

View file

@ -1,5 +1,6 @@
#include "AssetsLoader.hpp"
#include "animation/rigging.hpp"
#include "assetload_funcs.hpp"
#include "Assets.hpp"
#include "coders/commons.hpp"
@ -15,7 +16,6 @@
#include "items/ItemDef.hpp"
#include "logic/scripting/scripting.hpp"
#include "objects/EntityDef.hpp"
#include "objects/rigging.hpp"
#include "util/ThreadPool.hpp"
#include "voxels/Block.hpp"
@ -26,17 +26,23 @@ namespace fs = std::filesystem;
static debug::Logger logger("assets-loader");
AssetsLoader::AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths)
: engine(engine), assets(assets), paths(paths) {
addLoader(AssetType::SHADER, assetload::shader);
addLoader(AssetType::TEXTURE, assetload::texture);
addLoader(AssetType::FONT, assetload::font);
AssetsLoader::AssetsLoader(
Engine& engine, Assets& assets, const ResPaths& paths
)
: engine(engine),
assets(assets),
paths(paths),
assetsLoadInfo(assets.getLoadInfo()) {
addLoader(AssetType::ANIMATION, assetload::animation);
addLoader(AssetType::ATLAS, assetload::atlas);
addLoader(AssetType::FONT, assetload::font);
addLoader(AssetType::LAYOUT, assetload::layout);
addLoader(AssetType::SOUND, assetload::sound);
addLoader(AssetType::MODEL, assetload::model);
addLoader(AssetType::POST_EFFECT, assetload::posteffect);
addLoader(AssetType::SHADER, assetload::shader);
addLoader(AssetType::SKELETON, assetload::skeleton);
addLoader(AssetType::SOUND, assetload::sound);
addLoader(AssetType::TEXTURE, assetload::texture);
}
void AssetsLoader::addLoader(AssetType tag, aloader_func func) {
@ -50,11 +56,11 @@ void AssetsLoader::add(
std::shared_ptr<AssetCfg> settings,
bool overwrite
) {
if (!overwrite && enqueued.find({tag, alias}) != enqueued.end()){
if (!overwrite && enqueued.find({alias, tag}) != enqueued.end()){
return;
}
entries.push(aloader_entry {tag, filename, alias, std::move(settings)});
enqueued.insert({tag, alias});
enqueued.insert({alias, tag});
}
bool AssetsLoader::hasNext() const {
@ -72,15 +78,15 @@ aloader_func AssetsLoader::getLoader(AssetType tag) {
}
void AssetsLoader::loadNext() {
const aloader_entry& entry = entries.front();
aloader_entry& entry = entries.front();
logger.info() << "loading " << entry.filename << " as " << entry.alias;
std::string error {};
try {
aloader_func loader = getLoader(entry.tag);
auto postfunc =
loader(this, paths, entry.filename, entry.alias, entry.config);
postfunc(&assets);
loader(*this, paths, entry.filename, entry.alias, entry.config);
postfunc(assets);
} catch (const parsing_error& err) {
error = err.errorLog();
} catch (const std::runtime_error& err) {
@ -93,6 +99,8 @@ void AssetsLoader::loadNext() {
entries.pop();
throw assetload::error(tag, std::move(filename), std::move(error));
}
assetsLoadInfo.processedEntries[{entry.alias, entry.tag}] =
std::move(entry);
entries.pop();
}
@ -117,34 +125,28 @@ static void add_layouts(
}
}
void AssetsLoader::tryAddSound(const std::string& name) {
if (name.empty()) {
return;
}
std::string file = SOUNDS_FOLDER + "/" + name;
add(AssetType::SOUND, file, name);
}
static std::string assets_def_folder(AssetType tag) {
switch (tag) {
case AssetType::FONT:
return FONTS_FOLDER;
case AssetType::SHADER:
return SHADERS_FOLDER;
case AssetType::TEXTURE:
return TEXTURES_FOLDER;
case AssetType::ANIMATION:
return ANIMATION_FOLDER;
case AssetType::ATLAS:
return TEXTURES_FOLDER;
case AssetType::FONT:
return FONTS_FOLDER;
case AssetType::LAYOUT:
return LAYOUTS_FOLDER;
case AssetType::SOUND:
return SOUNDS_FOLDER;
case AssetType::MODEL:
return MODELS_FOLDER;
case AssetType::POST_EFFECT:
return POST_EFFECTS_FOLDER;
case AssetType::SHADER:
return SHADERS_FOLDER;
case AssetType::SKELETON:
return SKELETONS_FOLDER;
case AssetType::SOUND:
return SOUNDS_FOLDER;
case AssetType::TEXTURE:
return TEXTURES_FOLDER;
}
return "<error>";
}
@ -160,7 +162,8 @@ void AssetsLoader::processPreload(
}
std::shared_ptr<AssetCfg> config = nullptr;
map.at("path").get(path);
logger.debug() << "processing preload " << util::quote(name) << " path: " << util::quote(path);
logger.debug() << "processing preload " << util::quote(name)
<< " path: " << util::quote(path);
switch (tag) {
case AssetType::SOUND: {
bool keepPCM = false;
@ -221,14 +224,15 @@ void AssetsLoader::processPreloadList(AssetType tag, const dv::value& list) {
void AssetsLoader::processPreloadConfig(const io::path& file) {
auto root = io::read_json(file);
processPreloadList(AssetType::ANIMATION, root["animation"]);
processPreloadList(AssetType::ATLAS, root["atlases"]);
processPreloadList(AssetType::FONT, root["fonts"]);
processPreloadList(AssetType::SHADER, root["shaders"]);
processPreloadList(AssetType::TEXTURE, root["textures"]);
processPreloadList(AssetType::SOUND, root["sounds"]);
processPreloadList(AssetType::MODEL, root["models"]);
processPreloadList(AssetType::POST_EFFECT, root["post-effects"]);
processPreloadList(AssetType::SHADER, root["shaders"]);
processPreloadList(AssetType::SKELETON, root["skeletons"]);
processPreloadList(AssetType::SOUND, root["sounds"]);
processPreloadList(AssetType::TEXTURE, root["textures"]);
// layouts are loaded automatically
}
@ -263,65 +267,73 @@ static void add_variant(AssetsLoader& loader, const Variant& variant) {
}
}
void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
loader.processPreloadConfigs(content);
if (content) {
for (auto& entry : content->getBlockMaterials()) {
auto& material = *entry.second;
loader.tryAddSound(material.stepsSound);
loader.tryAddSound(material.placeSound);
loader.tryAddSound(material.breakSound);
loader.tryAddSound(material.hitSound);
void AssetsLoader::addDefaults(const Content* content) {
processPreloadConfigs(content);
if (content == nullptr) {
return;
}
auto tryAddSound = [this](const std::string& name){
if (name.empty()) {
return;
}
std::string file = SOUNDS_FOLDER + "/" + name;
add(AssetType::SOUND, file, name);
};
for (auto& entry : content->getBlockMaterials()) {
auto& material = *entry.second;
tryAddSound(material.stepsSound);
tryAddSound(material.placeSound);
tryAddSound(material.breakSound);
tryAddSound(material.hitSound);
}
for (auto& entry : content->getPacks()) {
auto pack = entry.second.get();
auto& info = pack->getInfo();
io::path folder = info.folder / "layouts";
add_layouts(pack->getEnvironment(), info.id, folder, loader);
}
for (auto& entry : content->getPacks()) {
auto pack = entry.second.get();
auto& info = pack->getInfo();
io::path folder = info.folder / "layouts";
add_layouts(pack->getEnvironment(), info.id, folder, *this);
}
for (const auto& entry : content->getPacks()) {
io::path skeletonsDir = entry.first + ":skeletons";
if (!io::is_directory(skeletonsDir)) {
continue;
}
for (const auto& file : io::directory_iterator(skeletonsDir)) {
loader.add(
AssetType::SKELETON,
(file.parent() / file.stem()).string(),
entry.first + ":" + file.stem()
);
}
for (const auto& entry : content->getPacks()) {
io::path skeletonsDir = entry.first + ":skeletons";
if (!io::is_directory(skeletonsDir)) {
continue;
}
for (const auto& file : io::directory_iterator(skeletonsDir)) {
add(
AssetType::SKELETON,
(file.parent() / file.stem()).string(),
entry.first + ":" + file.stem()
);
}
}
for (const auto& [_, def] : content->blocks.getDefs()) {
if (def->variants) {
for (const auto& variant : def->variants->variants) {
add_variant(loader, variant);
}
} else {
add_variant(loader, def->defaults);
for (const auto& [_, def] : content->blocks.getDefs()) {
if (def->variants) {
for (const auto& variant : def->variants->variants) {
add_variant(*this, variant);
}
} else {
add_variant(*this, def->defaults);
}
for (const auto& [_, def] : content->items.getDefs()) {
if (def->modelName.find(':') == std::string::npos) {
loader.add(
AssetType::MODEL,
MODELS_FOLDER + "/" + def->modelName,
def->modelName
);
}
}
for (const auto& [_, def] : content->items.getDefs()) {
if (def->modelName.find(':') == std::string::npos) {
add(
AssetType::MODEL,
MODELS_FOLDER + "/" + def->modelName,
def->modelName
);
}
for (const auto& [_, def] : content->entities.getDefs()) {
if (def->skeletonName.find(':') == std::string::npos) {
// expecting a VCM with skeleton
loader.add(
AssetType::MODEL,
MODELS_FOLDER + "/" + def->skeletonName,
def->skeletonName
);
}
}
for (const auto& [_, def] : content->entities.getDefs()) {
if (def->skeletonName.find(':') == std::string::npos) {
// expecting a VCM with skeleton
add(
AssetType::MODEL,
MODELS_FOLDER + "/" + def->skeletonName,
def->skeletonName
);
}
}
}
@ -361,17 +373,17 @@ const ResPaths& AssetsLoader::getPaths() const {
}
class LoaderWorker : public util::Worker<aloader_entry, assetload::postfunc> {
AssetsLoader* loader;
AssetsLoader& loader;
public:
LoaderWorker(AssetsLoader* loader) : loader(loader) {
LoaderWorker(AssetsLoader& loader) : loader(loader) {
}
assetload::postfunc operator()(const aloader_entry& entry
) override {
aloader_func loadfunc = loader->getLoader(entry.tag);
aloader_func loadfunc = loader.getLoader(entry.tag);
return loadfunc(
loader,
loader->getPaths(),
loader.getPaths(),
entry.filename,
entry.alias,
entry.config
@ -383,8 +395,8 @@ std::shared_ptr<Task> AssetsLoader::startTask(runnable onDone, int maxWorkers) {
auto pool =
std::make_shared<util::ThreadPool<aloader_entry, assetload::postfunc>>(
"assets-loader-pool",
[=]() { return std::make_unique<LoaderWorker>(this); },
[this](const assetload::postfunc& func) { func(&assets); },
[=]() { return std::make_unique<LoaderWorker>(*this); },
[this](const assetload::postfunc& func) { func(assets); },
maxWorkers
);
pool->setOnComplete(std::move(onDone));
@ -404,3 +416,22 @@ std::shared_ptr<Task> AssetsLoader::startTask(runnable onDone, int maxWorkers) {
}
return pool;
}
void AssetsLoader::attachToFile(const io::path& file, AssetFullId assetId) {
assetsLoadInfo.referencedAssets.insert({file, std::move(assetId)});
}
int AssetsLoader::addReload(const io::path& path) {
int added = 0;
auto range = assetsLoadInfo.referencedAssets.equal_range(path);
for (auto it = range.first; it != range.second; ++it) {
const auto& recipe = assetsLoadInfo.processedEntries.find(it->second);
if (recipe != assetsLoadInfo.processedEntries.end()) {
entries.push(recipe->second);
added++;
}
}
return added;
}

View file

@ -1,5 +1,12 @@
#pragma once
#include "delegates.hpp"
#include "interfaces/Task.hpp"
#include "typedefs.hpp"
#include "Assets.hpp"
#include "data/dv.hpp"
#include "io/fwd.hpp"
#include <functional>
#include <map>
#include <set>
@ -8,13 +15,6 @@
#include <string>
#include <utility>
#include "delegates.hpp"
#include "interfaces/Task.hpp"
#include "typedefs.hpp"
#include "Assets.hpp"
#include "data/dv.hpp"
#include "io/fwd.hpp"
class ResPaths;
class AssetsLoader;
class Content;
@ -72,7 +72,12 @@ struct ModelCfg : AssetCfg {
using aloader_func = std::function<
assetload::
postfunc(AssetsLoader*, const ResPaths&, const std::string&, const std::string&, std::shared_ptr<AssetCfg>)>;
postfunc(
AssetsLoader&,
const ResPaths&,
const std::string&,
const std::string&,
std::shared_ptr<AssetCfg>)>;
struct aloader_entry {
AssetType tag;
@ -81,24 +86,18 @@ struct aloader_entry {
std::shared_ptr<AssetCfg> config;
};
using AssetFullId = std::pair<std::string, AssetType>;
struct AssetsLoadInfo {
std::map<AssetFullId, aloader_entry> processedEntries;
std::multimap<io::path, AssetFullId> referencedAssets;
};
class AssetsLoader {
Engine& engine;
Assets& assets;
std::map<AssetType, aloader_func> loaders;
std::queue<aloader_entry> entries;
std::set<std::pair<AssetType, std::string>> enqueued;
const ResPaths& paths;
void tryAddSound(const std::string& name);
void processPreload(
AssetType tag, const std::string& name, const dv::value& map
);
void processPreloadList(AssetType tag, const dv::value& list);
void processPreloadConfig(const io::path& file);
void processPreloadConfigs(const Content* content);
public:
AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths);
AssetsLoader(const AssetsLoader&) = delete;
void addLoader(AssetType tag, aloader_func func);
/// @brief Enqueue asset load
@ -125,9 +124,8 @@ public:
aloader_func getLoader(AssetType tag);
/// @brief Enqueue core and content assets
/// @param loader target loader
/// @param content engine content
static void addDefaults(AssetsLoader& loader, const Content* content);
void addDefaults(const Content* content);
static bool loadExternalTexture(
AssetsLoader& loader,
@ -135,6 +133,25 @@ public:
const std::vector<io::path>& alternatives
);
int addReload(const io::path& path);
void attachToFile(const io::path& file, AssetFullId assetId);
Assets& getAssets();
Engine& getEngine();
private:
Engine& engine;
Assets& assets;
std::map<AssetType, aloader_func> loaders;
std::queue<aloader_entry> entries;
std::set<AssetFullId> enqueued;
const ResPaths& paths;
AssetsLoadInfo& assetsLoadInfo;
void processPreload(
AssetType tag, const std::string& name, const dv::value& map
);
void processPreloadList(AssetType tag, const dv::value& list);
void processPreloadConfig(const io::path& file);
void processPreloadConfigs(const Content* content);
};

View file

@ -1,13 +1,11 @@
#include "assetload_funcs.hpp"
#include <array>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include "animation/rigging.hpp"
#include "Assets.hpp"
#include "AssetsLoader.hpp"
#include "audio/audio.hpp"
#include "coders/GLSLExtension.hpp"
#include "coders/commons.hpp"
#include "coders/GLSLExtension.hpp"
#include "coders/imageio.hpp"
#include "coders/json.hpp"
#include "coders/obj.hpp"
@ -17,26 +15,28 @@
#include "constants.hpp"
#include "debug/Logger.hpp"
#include "engine/EnginePaths.hpp"
#include "io/io.hpp"
#include "frontend/UiDocument.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/TextureAnimation.hpp"
#include "graphics/commons/Model.hpp"
#include "objects/rigging.hpp"
#include "logic/scripting/scripting.hpp"
#include "io/io.hpp"
#include "util/stringutil.hpp"
#include "Assets.hpp"
#include "AssetsLoader.hpp"
#include <array>
#include <filesystem>
#include <stdexcept>
static debug::Logger logger("assetload-funcs");
namespace fs = std::filesystem;
static bool load_animation(
Assets* assets,
static bool load_animated_texture(
Assets& assets,
const ResPaths& paths,
const std::string& atlasName,
const std::string& directory,
@ -44,22 +44,39 @@ static bool load_animation(
Atlas* dstAtlas
);
assetload::postfunc assetload::texture(
AssetsLoader*,
assetload::postfunc assetload::animation(
AssetsLoader& loader,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>&
) {
auto actualFile = paths.find(filename + ".png");
auto path = paths.find(filename + ".vca");
loader.attachToFile(path, {name, AssetType::ANIMATION});
if (io::is_regular_file(path)) {
scripting::load_vca_animation(path, nullptr, name);
}
return [](auto&) {};
}
assetload::postfunc assetload::texture(
AssetsLoader& loader,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>&
) {
auto file = paths.find(filename + ".png");
loader.attachToFile(file, {name, AssetType::TEXTURE});
try {
std::shared_ptr<ImageData> image(imageio::read(actualFile));
return [name, image, actualFile](auto assets) {
assets->store(Texture::from(image.get()), name);
std::shared_ptr<ImageData> image(imageio::read(file));
return [name, image, file](auto& assets) {
assets.store(Texture::from(image.get()), name);
};
} catch (const std::runtime_error& err) {
logger.error() << actualFile.string() << ": " << err.what();
return [](auto) {};
logger.error() << file.string() << ": " << err.what();
return [](auto&) {};
}
}
@ -86,7 +103,7 @@ static auto read_program(const ResPaths& paths, const std::string& filename) {
}
assetload::postfunc assetload::shader(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
@ -99,8 +116,8 @@ assetload::postfunc assetload::shader(
io::path vertexFile = paths.find(filename + ".glslv");
io::path fragmentFile = paths.find(filename + ".glslf");
return [=](auto assets) {
assets->store(
return [=](auto& assets) {
assets.store(
Shader::create(
{vertexFile.string(), vertex},
{fragmentFile.string(), fragment}
@ -111,13 +128,15 @@ assetload::postfunc assetload::shader(
}
assetload::postfunc assetload::posteffect(
AssetsLoader*,
AssetsLoader& loader,
const ResPaths& paths,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
) {
io::path effectFile = paths.find(file + ".glsl");
loader.attachToFile(effectFile, {name, AssetType::POST_EFFECT});
std::string effectSource = io::read_string(effectFile);
auto& preprocessor = *Shader::preprocessor;
@ -131,7 +150,7 @@ assetload::postfunc assetload::posteffect(
std::string vertexSource = std::move(vertex.code);
std::string fragmentSource = std::move(fragment.code);
return [=](auto assets) {
return [=](auto& assets) {
auto program = Shader::create(
{effectFile.string(), vertexSource},
{effectFile.string(), fragmentSource}
@ -140,7 +159,7 @@ assetload::postfunc assetload::posteffect(
if (settings) {
advanced = dynamic_cast<const PostEffectCfg*>(settings.get())->advanced;
}
assets->store(
assets.store(
std::make_shared<PostEffect>(advanced, std::move(program), params),
name
);
@ -160,7 +179,7 @@ static bool append_atlas(AtlasBuilder& atlas, const io::path& file) {
}
assetload::postfunc assetload::atlas(
AssetsLoader* loader,
AssetsLoader& loader,
const ResPaths& paths,
const std::string& directory,
const std::string& name,
@ -171,13 +190,13 @@ assetload::postfunc assetload::atlas(
for (const auto& file : paths.listdir(directory)) {
if (!imageio::is_read_supported(file.extension()))
continue;
loader->add(
loader.add(
AssetType::TEXTURE,
directory + "/" + file.stem(),
name + "/" + file.stem()
);
}
return [](auto){};
return [](auto&){};
}
AtlasBuilder builder;
for (const auto& file : paths.listdir(directory)) {
@ -186,17 +205,17 @@ assetload::postfunc assetload::atlas(
}
std::set<std::string> names = builder.getNames();
Atlas* atlas = builder.build(ATLAS_EXTRUSION, false).release();
return [=](auto assets) {
return [=](Assets& assets) {
atlas->prepare();
assets->store(std::unique_ptr<Atlas>(atlas), name);
assets.store(std::unique_ptr<Atlas>(atlas), name);
for (const auto& file : names) {
load_animation(assets, paths, name, directory, file, atlas);
load_animated_texture(assets, paths, name, directory, file, atlas);
}
};
}
assetload::postfunc assetload::font(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
@ -204,17 +223,17 @@ assetload::postfunc assetload::font(
) {
auto cfg = std::dynamic_pointer_cast<FontCfg>(config);
auto ext = fs::path(filename).extension().string();
if (ext == ".ttf" || ext == ".otf") {
if (!ext.empty()) {
logger.info() << "loading vector font " << util::quote(filename);
return [=](Assets* assets) {
return [=](Assets& assets) {
using FontFile = vector_fonts::FontFile;
FontFile* fontFile;
if ((fontFile = assets->get<FontFile>(filename)) == nullptr) {
if ((fontFile = assets.get<FontFile>(filename)) == nullptr) {
auto fontFilePtr = vector_fonts::load_font(paths.find(filename).string());
fontFile = fontFilePtr.get();
assets->store(fontFilePtr, filename);
assets.store(fontFilePtr, filename);
}
assets->store(fontFile->createInstance(cfg ? cfg->size : 16), name);
assets.store(fontFile->createInstance(cfg ? cfg->size : 16), name);
};
}
@ -230,24 +249,24 @@ assetload::postfunc assetload::font(
pages->push_back(nullptr);
}
}
return [=](auto assets) {
assets->store(Font::createBitmapFont(std::move(*pages)), name);
return [=](auto& assets) {
assets.store(Font::createBitmapFont(std::move(*pages)), name);
};
}
assetload::postfunc assetload::layout(
AssetsLoader*,
AssetsLoader&,
const ResPaths&,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& config
) {
return [=](auto assets) {
return [=](auto& assets) {
try {
auto cfg = std::dynamic_pointer_cast<LayoutCfg>(config);
size_t pos = name.find(':');
auto prefix = name.substr(0, pos);
assets->store(
assets.store(
UiDocument::read(
*cfg->gui,
cfg->env,
@ -265,7 +284,7 @@ assetload::postfunc assetload::layout(
};
}
assetload::postfunc assetload::sound(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& file,
const std::string& name,
@ -307,81 +326,162 @@ assetload::postfunc assetload::sound(
}
auto sound = baseSound.release();
return [=](auto assets) {
assets->store(std::unique_ptr<audio::Sound>(sound), name);
return [=](auto& assets) {
assets.store(std::unique_ptr<audio::Sound>(sound), name);
};
}
static void request_textures(AssetsLoader* loader, const model::Model& model) {
static void request_textures(AssetsLoader& loader, const model::Model& model) {
for (auto& mesh : model.meshes) {
if (mesh.texture.find('$') == std::string::npos &&
mesh.texture.find(':') == std::string::npos) {
auto filename = TEXTURES_FOLDER + "/" + mesh.texture;
loader->add(
loader.add(
AssetType::TEXTURE, filename, mesh.texture, nullptr
);
}
}
}
static assetload::postfunc load_vec3_model(
const io::path& file,
AssetsLoader& loader,
const std::string& name,
const std::shared_ptr<ModelCfg>& config
) {
loader.attachToFile(file, {name, AssetType::MODEL});
auto bytes = io::read_bytes_buffer(file);
auto modelVEC3 = std::make_shared<vec3::File>(vec3::load(file.string(), bytes));
return [&loader, name, file, config, modelVEC3 = std::move(modelVEC3)](
Assets& assets
) {
loader.attachToFile(file, {name, AssetType::MODEL});
if (config && config->squashed) {
model::Model fullModel;
for (auto& entry : modelVEC3->models) {
auto& vec3model = entry.second;
auto& model = vec3model.model;
model.translate(vec3model.origin);
fullModel.merge(std::move(model));
}
request_textures(loader, fullModel);
assets.store(
std::make_unique<model::Model>(fullModel),
name
);
logger.info() << "store model " << util::quote(name);
return;
}
for (auto& [modelName, model] : modelVEC3->models) {
request_textures(loader, model.model);
std::string fullName = name;
if (name != modelName) {
fullName += "." + modelName;
}
assets.store(
std::make_unique<model::Model>(model.model),
fullName
);
logger.info() << "store model " << util::quote(modelName)
<< " as " << util::quote(fullName);
}
};
}
static assetload::postfunc load_obj_model(
const io::path& file,
AssetsLoader& loader,
const std::string& name,
const std::shared_ptr<ModelCfg>&
) {
loader.attachToFile(file, {name, AssetType::MODEL});
auto text = io::read_string(file);
try {
auto model = obj::parse(file.string(), text).release();
return [=, &loader](Assets& assets) {
request_textures(loader, *model);
assets.store(std::unique_ptr<model::Model>(model), name);
};
} catch (const parsing_error& err) {
logger.error() << "error on loading model " << name << ": "
<< err.errorLog();
throw;
}
}
static assetload::postfunc load_vcm_model(
const io::path& file,
AssetsLoader& loader,
const std::string& name,
const std::shared_ptr<ModelCfg>& cfg
) {
loader.attachToFile(file, {name, AssetType::MODEL});
auto text = io::read_string(file);
try {
auto vcmModel = vcm::parse(file.string(), text, file.extension() == ".xml");
assert(vcmModel.parts.size() > 0);
if (vcmModel.parts.size() == 1 || (cfg && cfg->squashed)) {
auto modelPtr =
std::make_unique<model::Model>(std::move(vcmModel.squash()))
.release();
return [=, &loader](Assets& assets) {
auto model = std::unique_ptr<model::Model>(modelPtr);
request_textures(loader, *model);
assets.store(std::move(model), name);
logger.info() << "store model " << util::quote(name);
};
}
auto vcmModelPtr =
std::make_unique<vcm::VcmModel>(std::move(vcmModel)).release();
return [=, &loader](Assets& assets) {
auto vcmModel = std::unique_ptr<vcm::VcmModel>(vcmModelPtr);
for (auto& [partName, model] : vcmModel->parts) {
auto fullName = name + "." + partName;
logger.info()
<< "store model part " << util::quote(partName)
<< " as " << util::quote(fullName);
request_textures(loader, model);
assets.store(
std::make_unique<model::Model>(std::move(model)),
fullName
);
}
for (auto& bone : vcmModel->skeleton->getBones()) {
bone->setModel(name + "." + bone->model.name);
}
logger.info() << "store skeleton " << util::quote(name);
assets.store<rigging::SkeletonConfig>(
std::make_unique<rigging::SkeletonConfig>(
std::move(*vcmModel->skeleton)
),
name
);
};
} catch (const parsing_error& err) {
logger.error() << "error on loading model " << name << ": "
<< err.errorLog();
throw;
}
}
assetload::postfunc assetload::model(
AssetsLoader* loader,
AssetsLoader& loader,
const ResPaths& paths,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& config
) {
auto cfg = std::dynamic_pointer_cast<ModelCfg>(config);
auto modelConfig = std::dynamic_pointer_cast<ModelCfg>(config);
auto path = paths.find(file + ".vec3");
if (io::exists(path)) {
auto bytes = io::read_bytes_buffer(path);
auto modelVEC3 = std::make_shared<vec3::File>(vec3::load(path.string(), bytes));
return [loader, name, cfg, modelVEC3=std::move(modelVEC3)](Assets* assets) {
if (cfg && cfg->squashed) {
model::Model fullModel;
for (auto& entry : modelVEC3->models) {
auto& vec3model = entry.second;
auto& model = vec3model.model;
model.translate(vec3model.origin);
fullModel.merge(std::move(model));
}
request_textures(loader, fullModel);
assets->store(
std::make_unique<model::Model>(fullModel),
name
);
logger.info() << "store model " << util::quote(name);
return;
}
for (auto& [modelName, model] : modelVEC3->models) {
request_textures(loader, model.model);
std::string fullName = name;
if (name != modelName) {
fullName += "." + modelName;
}
assets->store(
std::make_unique<model::Model>(model.model),
fullName
);
logger.info() << "store model " << util::quote(modelName)
<< " as " << util::quote(fullName);
}
};
return load_vec3_model(path, loader, name, modelConfig);
}
path = paths.find(file + ".obj");
if (io::exists(path)) {
auto text = io::read_string(path);
try {
auto model = obj::parse(path.string(), text).release();
return [=](Assets* assets) {
request_textures(loader, *model);
assets->store(std::unique_ptr<model::Model>(model), name);
};
} catch (const parsing_error& err) {
std::cerr << err.errorLog() << std::endl;
throw;
}
return load_obj_model(path, loader, name, modelConfig);
}
std::array<std::string, 2> extensions {
@ -401,60 +501,17 @@ assetload::postfunc assetload::model(
throw std::runtime_error("could not to find model " + util::quote(file));
}
auto text = io::read_string(path);
try {
auto vcmModel = vcm::parse(path.string(), text, path.extension() == ".xml");
assert(vcmModel.parts.size() > 0);
if (vcmModel.parts.size() == 1 || (cfg && cfg->squashed)) {
auto modelPtr = std::make_unique<model::Model>(std::move(vcmModel.squash())).release();
return [=](Assets* assets) {
auto model = std::unique_ptr<model::Model>(modelPtr);
request_textures(loader, *model);
assets->store(std::move(model), name);
logger.info() << "store model " << util::quote(name);
};
} else {
auto vcmModelPtr = std::make_unique<vcm::VcmModel>(std::move(vcmModel)).release();
return [=](Assets* assets) {
auto vcmModel = std::unique_ptr<vcm::VcmModel>(vcmModelPtr);
for (auto& [partName, model] : vcmModel->parts) {
auto fullName = name + "." + partName;
logger.info()
<< "store model part " << util::quote(partName)
<< " as " << util::quote(fullName);
assets->store(
std::make_unique<model::Model>(std::move(model)),
fullName
);
}
for (auto& bone : vcmModel->skeleton->getBones()) {
bone->setModel(name + "." + bone->model.name);
}
logger.info() << "store skeleton " << util::quote(name);
assets->store<rigging::SkeletonConfig>(
std::make_unique<rigging::SkeletonConfig>(
std::move(*vcmModel->skeleton)
),
name
);
};
}
} catch (const parsing_error& err) {
std::cerr << err.errorLog() << std::endl;
throw;
}
return load_vcm_model(path, loader, name, modelConfig);
}
assetload::postfunc assetload::skeleton(
AssetsLoader* loader,
AssetsLoader& loader,
const ResPaths& paths,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
) {
return [=](auto assets) {
return [&loader, &paths, file, name](auto& assets) {
auto path = paths.find(file + ".json");
std::string text = io::read_string(path);
auto skeleton = rigging::SkeletonConfig::parse(text, file, name);
@ -465,12 +522,12 @@ assetload::postfunc assetload::skeleton(
model = model.substr(0, pos);
}
if (!model.empty()) {
loader->add(
loader.add(
AssetType::MODEL, MODELS_FOLDER + "/" + model, model
);
}
}
assets->store(std::move(skeleton), name);
assets.store(std::move(skeleton), name);
};
}
@ -552,8 +609,8 @@ inline bool contains(
return false;
}
static bool load_animation(
Assets* assets,
static bool load_animated_texture(
Assets& assets,
const ResPaths& paths,
const std::string& atlasName,
const std::string& directory,
@ -576,8 +633,7 @@ static bool load_animation(
read_anim_file(animFile, frameList);
}
for (const auto& file : paths.listdir(animsDir + "/" + name)) {
if (!frameList.empty() &&
!contains(frameList, file.stem())) {
if (!frameList.empty() && !contains(frameList, file.stem())) {
continue;
}
if (!append_atlas(builder, file)) continue;
@ -591,10 +647,10 @@ static bool load_animation(
auto animation = create_animation(
srcAtlas.get(), dstAtlas, name, builder.getNames(), frameList
);
assets->store(
assets.store(
std::move(srcAtlas), atlasName + "/" + name + "_animation"
);
assets->store(animation);
assets.store(animation);
return true;
}
return true;

View file

@ -1,10 +1,10 @@
#pragma once
#include "Assets.hpp"
#include <memory>
#include <string>
#include "Assets.hpp"
class ResPaths;
class Assets;
class AssetsLoader;
@ -13,57 +13,64 @@ struct AssetCfg;
/// @brief see AssetsLoader.h: aloader_func
namespace assetload {
postfunc animation(
AssetsLoader&,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc texture(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc shader(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc atlas(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& directory,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc font(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& filename,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc layout(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc sound(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc model(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& file,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc posteffect(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& file,
const std::string& name,
@ -71,7 +78,7 @@ namespace assetload {
);
postfunc skeleton(
AssetsLoader*,
AssetsLoader&,
const ResPaths& paths,
const std::string& file,
const std::string& name,

View file

@ -37,16 +37,22 @@ void Syntax::deserialize(const dv::value& src) {
std::string multilineCommentEnd;
std::string multilineStringStart;
std::string multilineStringEnd;
std::string identifierStartChars;
std::string identifierPartChars;
src.at("line-comment").get(lineComment);
src.at("multiline-comment-start").get(multilineCommentStart);
src.at("multiline-comment-end").get(multilineCommentEnd);
src.at("multiline-string-start").get(multilineStringStart);
src.at("multiline-string-end").get(multilineStringEnd);
src.at("identifier-start-chars").get(identifierStartChars);
src.at("identifier-part-chars").get(identifierPartChars);
this->lineComment = util::str2wstr_utf8(lineComment);
this->multilineCommentStart = util::str2wstr_utf8(multilineCommentStart);
this->multilineCommentEnd = util::str2wstr_utf8(multilineCommentEnd);
this->multilineStringStart = util::str2wstr_utf8(multilineStringStart);
this->multilineStringEnd = util::str2wstr_utf8(multilineStringEnd);
this->identifierStarts = util::str2wstr_utf8(identifierStartChars);
this->identifierParts = util::str2wstr_utf8(identifierPartChars);
if (src.has("extensions")) {
const auto& extsList = src["extensions"];
@ -77,6 +83,8 @@ inline bool is_lua_operator_start(int c) {
|| c == '.';
}
#include <iostream>
class Tokenizer : BasicParser<wchar_t> {
const Syntax& syntax;
std::vector<Token> tokens;
@ -87,13 +95,28 @@ public:
: BasicParser(file, source), syntax(syntax) {
}
std::wstring parseLuaName() {
char c = peek();
if (!is_identifier_start(c)) {
bool isIdentifierStart(wchar_t c) {
if (!syntax.identifierStarts.empty()) {
return syntax.identifierStarts.find(c) != std::wstring::npos;
}
return is_common_identifier_start(c);
}
bool isIdentifierPart(wchar_t c) {
if (!syntax.identifierParts.empty()) {
return syntax.identifierParts.find(c) != std::wstring::npos;
}
return is_common_identifier_part(c);
}
std::wstring parseIdentifier() {
wchar_t c = peek();
if (!isIdentifierStart(c)) {
throw error("identifier expected");
}
int start = pos;
while (hasNext() && is_minimal_identifier_part(source[pos])) {
while (hasNext() && isIdentifierPart(source[pos])) {
pos++;
}
return std::wstring(source.substr(start, pos - start));
@ -167,7 +190,7 @@ public:
wchar_t c = peek();
auto start = currentLocation();
if (is_common_identifier_start(c)) {
auto name = parseLuaName();
auto name = parseIdentifier();
TokenTag tag =
(syntax.keywords.find(name) == syntax.keywords.end()
? TokenTag::NAME

View file

@ -17,6 +17,8 @@ namespace devtools {
std::wstring multilineCommentEnd;
std::wstring multilineStringStart;
std::wstring multilineStringEnd;
std::wstring identifierStarts;
std::wstring identifierParts;
dv::value serialize() const override;
void deserialize(const dv::value& src) override;

View file

@ -1,13 +1,12 @@
#pragma once
#include <memory>
#include "animation/rigging.hpp"
#include "graphics/commons/Model.hpp"
#include <string>
#include <optional>
#include <unordered_map>
#include "graphics/commons/Model.hpp"
#include "objects/rigging.hpp"
namespace rigging {
class SkeletonConfig;
}

View file

@ -75,13 +75,14 @@ inline constexpr int DEFAULT_FONT_SIZE = 16;
inline constexpr int DEFAULT_PRE_RENDER_FONT_PAGES = 1; // must be at least 1
inline const std::string SHADERS_FOLDER = "shaders";
inline const std::string TEXTURES_FOLDER = "textures";
inline const std::string ANIMATION_FOLDER = "animation";
inline const std::string FONTS_FOLDER = "fonts";
inline const std::string LAYOUTS_FOLDER = "layouts";
inline const std::string SOUNDS_FOLDER = "sounds";
inline const std::string MODELS_FOLDER = "models";
inline const std::string SKELETONS_FOLDER = "skeletons";
inline const std::string POST_EFFECTS_FOLDER = "shaders/effects";
inline const std::string SHADERS_FOLDER = "shaders";
inline const std::string SKELETONS_FOLDER = "skeletons";
inline const std::string SOUNDS_FOLDER = "sounds";
inline const std::string TEXTURES_FOLDER = "textures";
inline const std::string FONT_DEFAULT = "normal";

View file

@ -1,16 +1,16 @@
#include "ContentControl.hpp"
#include "io/io.hpp"
#include "engine/EnginePaths.hpp"
#include "animation/rigging.hpp"
#include "Content.hpp"
#include "ContentPack.hpp"
#include "ContentBuilder.hpp"
#include "ContentLoader.hpp"
#include "PacksManager.hpp"
#include "objects/rigging.hpp"
#include "devtools/Project.hpp"
#include "logic/scripting/scripting.hpp"
#include "ContentPack.hpp"
#include "core_defs.hpp"
#include "devtools/Project.hpp"
#include "engine/EnginePaths.hpp"
#include "io/io.hpp"
#include "logic/scripting/scripting.hpp"
#include "PacksManager.hpp"
static void load_configs(Input* input, const io::path& root) {
auto configFolder = root / "config";

17
src/debug/debug.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <iostream>
#include <glm/glm.hpp>
template<int N, typename T>
std::ostream& operator<<(std::ostream& out, const glm::vec<N, T>& vec) {
out << "{";
for (int i = 0; i < N; i++) {
if (i > 0) {
out << ", ";
}
out << vec[i];
}
out << "}";
return out;
}

View file

@ -1,9 +1,13 @@
#include "DebuggingServer.hpp"
#include "engine/Engine.hpp"
#include "network/Network.hpp"
#include "debug/Logger.hpp"
#include "assets/AssetsLoader.hpp"
#include "coders/json.hpp"
#include "data/dv.hpp"
#include "debug/Logger.hpp"
#include "engine/AssetsManagement.hpp"
#include "engine/Engine.hpp"
#include "io/path.hpp"
#include "network/Network.hpp"
using namespace devtools;
@ -260,6 +264,16 @@ bool DebuggingServer::performCommand(
}
});
return true;
} else if (type == "hot-reload") {
if (!map.has("file")) {
return false;
}
logger.info() << "hot-reload: " << map["file"].asString();
int count = engine.getAssetsManagement().acquireBackgroundLoader().addReload(
map["file"].asString()
);
logger.info() << "enqueued to reload: " << count;
return false;
} else {
logger.error() << "unsupported command '" << type << "'";
}

View file

@ -9,6 +9,7 @@
#include "graphics/core/Shader.hpp"
#include "graphics/render/ModelsGenerator.hpp"
#include "graphics/ui/GUI.hpp"
#include "logic/scripting/scripting.hpp"
static debug::Logger logger("assets-management");
@ -49,35 +50,41 @@ AssetsLoader& AssetsManagement::acquireBackgroundLoader() {
void AssetsManagement::loadAssets(Content* content) {
finishBackgroundLoader();
scripting::on_assets_loading();
const auto& paths = engine.getPaths();
logger.info() << "loading assets";
Shader::preprocessor->setPaths(&paths.resPaths);
auto new_assets = std::make_unique<Assets>(
auto newAssets = std::make_unique<Assets>(
settings.system.preserveAssetsDuringFrame.get() ? &assetsVault : nullptr
);
AssetsLoader loader(engine, *new_assets, paths.resPaths);
AssetsLoader::addDefaults(loader, content);
AssetsLoader loader(engine, *newAssets, paths.resPaths);
loader.addDefaults(content);
// no need
// correct log messages order is more useful
// todo: before setting to true, check if GLSLExtension thread safe
bool threading = false; // look at three upper lines
if (threading) {
auto task = loader.startTask(
[=]() {}, 0
);
task->waitForEnd();
} else {
while (loader.hasNext()) {
loader.loadNext();
try {
// no need
// correct log messages order is more useful
// todo: before setting to true, check if GLSLExtension thread safe
bool threading = false; // look at three upper lines
if (threading) {
auto task = loader.startTask(
[=]() {}, 0
);
task->waitForEnd();
} else {
while (loader.hasNext()) {
loader.loadNext();
}
}
} catch (...) {
scripting::revert_assets_loading();
throw;
}
assets = std::move(new_assets);
assets = std::move(newAssets);
if (content) {
ModelsGenerator::prepare(*content, *assets);
}
assets->setup();
engine.getGUI().onAssetsLoad(assets.get());
}

View file

@ -179,7 +179,7 @@ void Engine::initialize(CoreParameters coreParameters) {
content = std::make_unique<ContentControl>(
*project, *paths, input.get(), [this]() { onContentLoad(); }
);
scripting::initialize(this);
scripting::initialize(*this);
if (!isHeadless()) {
gui->getMenu()->setPageLoader(scripting::create_page_loader());

View file

@ -190,5 +190,9 @@ public:
return *appScripts;
}
AssetsManagement& getAssetsManagement() const {
return *assets;
}
void detachDebugger();
};

View file

@ -1,14 +1,14 @@
#include "HandsRenderer.hpp"
#include "animation/rigging.hpp"
#include "content/Content.hpp"
#include "graphics/commons/Model.hpp"
#include "ModelBatch.hpp"
#include "window/Camera.hpp"
#include <glm/ext.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "ModelBatch.hpp"
#include "content/Content.hpp"
#include "graphics/commons/Model.hpp"
#include "objects/rigging.hpp"
#include "window/Camera.hpp"
using namespace rigging;
HandsRenderer::HandsRenderer(

View file

@ -1,6 +1,6 @@
#include "NamedSkeletons.hpp"
#include "objects/rigging.hpp"
#include "animation/rigging.hpp"
using namespace rigging;

View file

@ -1,5 +1,6 @@
#include "WorldRenderer.hpp"
#include "animation/rigging.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "CloudsRenderer.hpp"
@ -42,7 +43,6 @@
#include "maths/voxmaths.hpp"
#include "objects/Entities.hpp"
#include "objects/Player.hpp"
#include "objects/rigging.hpp"
#include "settings.hpp"
#include "voxels/Block.hpp"
#include "voxels/Chunk.hpp"

View file

@ -1,6 +1,6 @@
#include "objects/rigging.hpp"
#include "libentity.hpp"
#include "animation/rigging.hpp"
#include "engine/Engine.hpp"
#include "graphics/render/WorldRenderer.hpp"
#include "graphics/render/NamedSkeletons.hpp"
@ -42,7 +42,7 @@ static int l_get_model(lua::State* L) {
auto& rigConfig = *skeleton->config;
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
const auto& modelOverride = skeleton->modelOverrides[index];
if (modelOverride.model) {
if (modelOverride.model.expired()) {
return lua::pushstring(L, modelOverride.name);
}
return lua::pushstring(L, rigConfig.getBones()[index]->model.name);
@ -55,9 +55,9 @@ static int l_set_model(lua::State* L) {
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
auto& modelOverride = skeleton->modelOverrides[index];
if (lua::isnoneornil(L, 3)) {
modelOverride = {"", nullptr, true};
modelOverride = {"", {}, true};
} else {
modelOverride = {lua::require_string(L, 3), nullptr, true};
modelOverride = {lua::require_string(L, 3), {}, true};
}
}
return 0;
@ -66,19 +66,34 @@ static int l_set_model(lua::State* L) {
static int l_get_matrix(lua::State* L) {
if (auto skeleton = get_skeleton(L)) {
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
return lua::pushmat4(L, skeleton->pose.matrices[index]);
if (lua::istable(L, 3)) {
lua::setmat4(L, 3, skeleton->pose.matrices[index]);
} else {
return lua::pushmat4(L, skeleton->pose.matrices[index]);
}
}
return 0;
}
static int l_set_matrix(lua::State* L) {
if (auto skeleton = get_skeleton(L)) {
if (lua::isnoneornil(L, 2)) {
return 0;
}
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
skeleton->pose.matrices[index] = lua::tomat4(L, 3);
}
return 0;
}
static int l_reset_pose(lua::State* L) {
if (auto skeleton = get_skeleton(L)) {
auto& pose = skeleton->pose;
std::fill(pose.matrices.begin(), pose.matrices.end(), glm::mat4(1.0f));
}
return 0;
}
static int l_get_texture(lua::State* L) {
if (auto skeleton = get_skeleton(L)) {
const auto& found = skeleton->textures.find(lua::require_string(L, 2));
@ -169,6 +184,7 @@ const luaL_Reg skeletonlib[] = {
{"set_model", lua::wrap<l_set_model>},
{"get_matrix", lua::wrap<l_get_matrix>},
{"set_matrix", lua::wrap<l_set_matrix>},
{"reset_pose", lua::wrap<l_reset_pose>},
{"get_texture", lua::wrap<l_get_texture>},
{"set_texture", lua::wrap<l_set_texture>},
{"index", lua::wrap<l_index>},

View file

@ -11,6 +11,7 @@
#include "graphics/commons/Model.hpp"
#include "graphics/core/Atlas.hpp"
#include "graphics/core/Texture.hpp"
#include "io/io.hpp"
#include "util/Buffer.hpp"
using namespace scripting;
@ -66,6 +67,20 @@ static int l_load_texture(lua::State* L) {
return 0;
}
static int l_parse_animation(lua::State* L) {
auto format = lua::require_lstring(L, 1);
auto string = lua::require_lstring(L, 2);
std::string name = lua::require_string(L, 3);
if (format != "vca") {
throw std::runtime_error(
"unknown format " + util::quote(std::string(format))
);
}
scripting::load_vca_animation(name, &string, name);
return 0;
}
static int l_parse_model(lua::State* L) {
auto& assets = engine->requireAssets();
@ -161,6 +176,7 @@ static int l_to_canvas(lua::State* L) {
const luaL_Reg assetslib[] = {
{"request_texture", lua::wrap<l_request_texture>},
{"load_texture", lua::wrap<l_load_texture>},
{"parse_animation", lua::wrap<l_parse_animation>},
{"parse_model", lua::wrap<l_parse_model>},
{"to_canvas", lua::wrap<l_to_canvas>},
{nullptr, nullptr}

View file

@ -1,20 +1,20 @@
#include "libentity.hpp"
#include "animation/rigging.hpp"
#include "assets/Assets.hpp"
#include "content/Content.hpp"
#include "content/ContentPack.hpp"
#include "engine/Engine.hpp"
#include "engine/EnginePaths.hpp"
#include "objects/Entities.hpp"
#include "objects/EntityDef.hpp"
#include "objects/Entity.hpp"
#include "objects/Rigidbody.hpp"
#include "objects/EntityDef.hpp"
#include "objects/Player.hpp"
#include "objects/rigging.hpp"
#include "objects/Rigidbody.hpp"
#include "physics/Hitbox.hpp"
#include "voxels/Chunks.hpp"
#include "voxels/Block.hpp"
#include "voxels/blocks_agent.hpp"
#include "voxels/Chunks.hpp"
#include "window/Camera.hpp"
#include <set>
@ -116,6 +116,10 @@ static int l_set_skeleton(lua::State* L) {
return 0;
}
if (auto entity = get_entity(L, 1)) {
if (lua::isnil(L, 2)) {
entity->setRig(nullptr);
return 0;
}
std::string skeletonName = lua::require_string(L, 2);
auto rigConfig = assets->getShared<rigging::SkeletonConfig>(skeletonName);
if (rigConfig == nullptr) {

View file

@ -77,7 +77,7 @@ static void create_libs(State* L, StateType stateType) {
createtable(L, 0, 0);
pushvalue(L, -1);
setglobal(L, "__vc_internals");
setregistry(L, "internals");
setregistry(L, lua::INTERNALS_TABLE);
if (stateType == StateType::SCRIPT) {
getregistry(L, "app");

View file

@ -16,6 +16,7 @@ namespace lua {
inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks
inline std::string PACK_ENVS_TABLE = "$P";
inline std::string ENVS_TABLE = "$E";
inline std::string INTERNALS_TABLE = "$I";
extern std::unordered_map<std::type_index, std::string> usertypeNames;
int userdata_destructor(lua::State* L);
@ -635,6 +636,13 @@ namespace lua {
int call(lua::State*, int argc, int nresults = -1);
int call_nothrow(lua::State*, int argc, int nresults = 1);
inline void call_internal(lua::State* L, const std::string& name) {
if (lua::get_from_registry(L, lua::INTERNALS_TABLE, name, true)) {
lua::call_nothrow(L, 0, 0);
pop(L);
}
}
inline int eval(
lua::State* L,
int env,

View file

@ -65,14 +65,13 @@ int scripting::load_script(
return lua::execute(lua::get_main_state(), env, src, fileName);
}
void scripting::initialize(Engine* engine) {
scripting::engine = engine;
scripting::content_control = &engine->getContentControl();
lua::initialize(engine->getPaths(), engine->getCoreParameters());
void scripting::initialize(Engine& engine) {
scripting::engine = &engine;
scripting::content_control = &engine.getContentControl();
lua::initialize(engine.getPaths(), engine.getCoreParameters());
load_script(io::path("stdlib.lua"), true);
load_script(io::path("classes.lua"), true);
load_script(io::path("internal_events.lua"), true);
}
class LuaCoroutine : public Process {
@ -244,9 +243,7 @@ std::unique_ptr<Process> scripting::start_app_script(const io::path& script) {
void scripting::process_post_runnables() {
auto L = lua::get_main_state();
if (lua::getglobal(L, "__vc__process_post_runnables")) {
lua::call_nothrow(L, 0, 0);
}
lua::call_internal(L, "process_post_runnables");
}
template <class T, typename IdType>
@ -263,6 +260,14 @@ static int push_properties_tables(
return 1;
}
void scripting::on_assets_loading() {
lua::call_internal(lua::get_main_state(), "backup_and_clear_animation");
}
void scripting::revert_assets_loading() {
lua::call_internal(lua::get_main_state(), "restore_animation_backup");
}
void scripting::on_content_load(Content* content) {
scripting::content = content;
scripting::indices = content->getIndices();
@ -291,13 +296,15 @@ void scripting::on_content_load(Content* content) {
lua::getregistry(L, "app");
lua::setglobal(L, "__vc_app");
lua::getregistry(L, "internals");
lua::getregistry(L, lua::INTERNALS_TABLE);
lua::setglobal(L, "__vc_internals");
try {
load_script("post_content.lua", true);
} catch (const std::exception&) {
lua::pushnil(L);
lua::setglobal(L, "__vc_app");
lua::pushnil(L);
lua::setglobal(L, "__vc_internals");
throw;
}
lua::pushnil(L);
@ -319,9 +326,7 @@ void scripting::on_world_load(LevelController* controller) {
scripting::controller = controller;
auto L = lua::get_main_state();
if (lua::getglobal(L, "__vc_on_world_open")) {
lua::call_nothrow(L, 0, 0);
}
lua::call_internal(L, "on_world_open");
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(L, pack.id + ":.worldopen", [](auto L) {
@ -334,9 +339,10 @@ void scripting::on_world_load(LevelController* controller) {
void scripting::on_world_tick(int tps) {
auto L = lua::get_main_state();
if (lua::getglobal(L, "__vc_on_world_tick")) {
if (lua::get_from_registry(L, lua::INTERNALS_TABLE, "on_world_tick", true)) {
lua::pushinteger(L, tps);
lua::call_nothrow(L, 1, 0);
lua::pop(L);
}
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(L, pack.id + ":.worldtick");
@ -348,16 +354,12 @@ void scripting::on_world_save() {
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(L, pack.id + ":.worldsave");
}
if (lua::getglobal(L, "__vc_on_world_save")) {
lua::call_nothrow(L, 0, 0);
}
lua::call_internal(L, "on_world_save");
}
void scripting::process_before_quit() {
auto L = lua::get_main_state();
if (lua::getglobal(L, "__vc_process_before_quit")) {
lua::call_nothrow(L, 0, 0);
}
lua::call_internal(L, "process_before_quit");
}
void scripting::on_world_quit() {
@ -365,9 +367,7 @@ void scripting::on_world_quit() {
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(L, pack.id + ":.worldquit");
}
if (lua::getglobal(L, "__vc_on_world_quit")) {
lua::call_nothrow(L, 0, 0);
}
lua::call_internal(L, "on_world_quit");
scripting::level = nullptr;
scripting::content = nullptr;
scripting::indices = nullptr;
@ -842,6 +842,27 @@ void scripting::load_layout_script(
lua::pop(L);
}
void scripting::load_vca_animation(
const io::path& file,
const std::string_view* content,
const std::string& identifier
) {
auto L = lua::get_main_state();
if (lua::get_from_registry(L, lua::INTERNALS_TABLE, "load_vca_animation", true)) {
lua::pushlstring(L, file.string());
if (content) {
lua::pushlstring(L, *content);
} else {
lua::pushnil(L);
}
lua::pushlstring(L, identifier);
if (lua::call(L, 3, 0)) {
lua::pop(L);
}
lua::pop(L);
}
}
void scripting::close() {
lua::finalize();
content = nullptr;

View file

@ -48,7 +48,10 @@ namespace scripting {
extern std::ostream* output_stream;
extern std::ostream* error_stream;
void initialize(Engine* engine);
void initialize(Engine& engine);
void on_assets_loading();
void revert_assets_loading();
void on_content_load(Content* content);
void on_content_reset();
@ -252,6 +255,12 @@ namespace scripting {
UiDocScript& script
);
void load_vca_animation(
const io::path& file,
const std::string_view* content,
const std::string& identifier
);
/// @brief Finalize lua state. Using scripting after will lead to Lua panic
void close();
}

View file

@ -37,6 +37,11 @@ static inline const std::string STDCOMP = "stdcomp";
lua::setfield(L, name);
lua::pop(L);
}
if (lua::getfield(L, "ordered_components")) {
lua::pushenv(L, id);
lua::rawseti(L, lua::objlen(L, -2) + 1);
lua::pop(L);
}
lua::pop(L);
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508

View file

@ -47,9 +47,7 @@ void scripting::on_frontend_init(
load_script("hud_classes.lua");
if (lua::getglobal(L, "__vc_on_hud_open")) {
lua::call_nothrow(L, 0, 0);
}
lua::call_internal(L, "on_hud_open");
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(
@ -63,12 +61,13 @@ void scripting::on_frontend_init(
}
void scripting::on_frontend_render() {
auto L = lua::get_main_state();
lua::call_internal(L, "on_render");
for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(
lua::get_main_state(),
pack.id + ":.hudrender",
[](lua::State* L) { return 0; }
);
lua::emit_event(L, pack.id + ":.hudrender", [](lua::State* L) {
return 0;
});
}
}

View file

@ -1,6 +1,7 @@
#define VC_ENABLE_REFLECTION
#include "Entities.hpp"
#include "animation/rigging.hpp"
#include "assets/Assets.hpp"
#include "content/Content.hpp"
#include "data/dv_util.hpp"
@ -17,7 +18,6 @@
#include "maths/rays.hpp"
#include "maths/util.hpp"
#include "physics/PhysicsSolver.hpp"
#include "rigging.hpp"
#include "world/Level.hpp"
#include <entt/entity/registry.hpp>
@ -59,7 +59,11 @@ entityid_t Entities::spawn(
if (assets) {
skeleton = assets->get<rigging::SkeletonConfig>(def.skeletonName);
if (skeleton == nullptr) {
throw std::runtime_error("skeleton " + def.skeletonName + " not found");
if (def.skeletonName == def.name) {
logger.warning() << "skeleton " + def.skeletonName + " not found";
} else {
throw std::runtime_error("skeleton " + def.skeletonName + " not found");
}
}
}
entityid_t id;
@ -100,7 +104,9 @@ entityid_t Entities::spawn(
auto& scripting = registry->emplace<ScriptComponents>(entity);
if (assets) {
registry->emplace<rigging::Skeleton>(entity, skeleton->instance());
registry->emplace<rigging::Skeleton>(
entity, skeleton ? skeleton->instance() : rigging::Skeleton(nullptr)
);
}
for (auto& instance : def.components) {

View file

@ -1,12 +1,12 @@
#include "Entity.hpp"
#include "Transform.hpp"
#include "Rigidbody.hpp"
#include "ScriptComponents.hpp"
#include "animation/rigging.hpp"
#include "Entities.hpp"
#include "EntityDef.hpp"
#include "rigging.hpp"
#include "logic/scripting/scripting.hpp"
#include "Rigidbody.hpp"
#include "ScriptComponents.hpp"
#include "Transform.hpp"
#include <entt/entt.hpp>

View file

@ -1,27 +1,28 @@
#include "Player.hpp"
#include <algorithm>
#define GLM_ENABLE_EXPERIMENTAL
#include "animation/rigging.hpp"
#include "content/ContentReport.hpp"
#include "data/dv_util.hpp"
#include "debug/Logger.hpp"
#include "Entities.hpp"
#include "Entity.hpp"
#include "items/Inventory.hpp"
#include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp"
#include "util/stringutil.hpp"
#include "voxels/Chunks.hpp"
#include "window/Camera.hpp"
#include "world/generator/GeneratorDef.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
#include <algorithm>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <utility>
#include "content/ContentReport.hpp"
#include "items/Inventory.hpp"
#include "Entities.hpp"
#include "Entity.hpp"
#include "rigging.hpp"
#include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp"
#include "voxels/Chunks.hpp"
#include "window/Camera.hpp"
#include "world/Level.hpp"
#include "world/World.hpp"
#include "world/generator/GeneratorDef.hpp"
#include "data/dv_util.hpp"
#include "debug/Logger.hpp"
#include "util/stringutil.hpp"
static debug::Logger logger("player");
constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64;

View file

@ -103,6 +103,22 @@ namespace util {
return data_.ptr()[index];
}
T& front() {
return data_.ptr()[0];
}
T& back() {
return data_.ptr()[size_ - 1];
}
const T& front() const {
return data_.ptr()[0];
}
const T& back() const {
return data_.ptr()[size_ - 1];
}
int size() const {
return size_;
}