commit
5de8ae5f61
@ -20,6 +20,22 @@ Example:
|
||||
]
|
||||
```
|
||||
|
||||
You can pass values in ARGS from the entity configuration.
|
||||
They will be passed both when creating a new entity and when loading a saved one.
|
||||
The `args` list is used for this:
|
||||
|
||||
```json
|
||||
"components": [
|
||||
{
|
||||
"name": "base:drop",
|
||||
"args": {
|
||||
"item": "base:stone.item",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The components code should be in `scripts/components`.
|
||||
|
||||
## Physics
|
||||
|
||||
@ -32,6 +32,7 @@ Subsections:
|
||||
- [mat4](scripting/builtins/libmat4.md)
|
||||
- [network](scripting/builtins/libnetwork.md)
|
||||
- [pack](scripting/builtins/libpack.md)
|
||||
- [pathfinding](scripting/builtins/libpathfinding.md)
|
||||
- [player](scripting/builtins/libplayer.md)
|
||||
- [quat](scripting/builtins/libquat.md)
|
||||
- [rules](scripting/builtins/librules.md)
|
||||
|
||||
66
doc/en/scripting/builtins/libpathfinding.md
Normal file
66
doc/en/scripting/builtins/libpathfinding.md
Normal file
@ -0,0 +1,66 @@
|
||||
# *pathfinding* library
|
||||
|
||||
The *pathfinding* library provides functions for working with the pathfinding system in the game world. It allows you to create and manage agents finding routes between points in the world.
|
||||
|
||||
When used in entity logic, the `core:pathfinding` component should be used.
|
||||
|
||||
## `core:pathfinding` component
|
||||
|
||||
```lua
|
||||
local pf = entity:get_component("core:pathfinding")
|
||||
|
||||
--- ...
|
||||
local x = ...
|
||||
local y = ...
|
||||
local z = ...
|
||||
|
||||
--- Set the target for the agent
|
||||
pf.set_target({x, y, z})
|
||||
|
||||
--- Get the current target of the agent
|
||||
local target = pf.get_target() --> vec3 or nil
|
||||
--- ...
|
||||
|
||||
--- Get the current route of the agent
|
||||
local route = pf.get_route() --> table<vec3> or nil
|
||||
--- ...
|
||||
```
|
||||
|
||||
## Library functions
|
||||
|
||||
```lua
|
||||
--- Create a new agent. Returns the ID of the created agent
|
||||
local agent = pathfinding.create_agent() --> int
|
||||
|
||||
--- Delete an agent by ID. Returns true if the agent existed, otherwise false
|
||||
pathfinding.remove_agent(agent: int) --> bool
|
||||
|
||||
--- Set the agent state (enabled/disabled)
|
||||
pathfinding.set_enabled(agent: int, enabled: bool)
|
||||
|
||||
--- Check the agent state. Returns true if the agent is enabled, otherwise false
|
||||
pathfinding.is_enabled(agent: int) --> bool
|
||||
|
||||
--- Create a route based on the given points. Returns an array of route points
|
||||
pathfinding.make_route(start: vec3, target: vec3) --> table<vec3>
|
||||
|
||||
--- Asynchronously create a route based on the given points.
|
||||
--- This function allows to perform pathfinding in the background without blocking the main thread of execution
|
||||
pathfinding.make_route_async(agent: int, start: vec3, target: vec3)
|
||||
|
||||
--- Get the route that the agent has already found. Used to get the route after an asynchronous search.
|
||||
--- If the search has not yet completed, returns nil. If the route is not found, returns an empty table.
|
||||
pathfinding.pull_route(agent: int) --> table<vec3> or nil
|
||||
|
||||
--- Set the maximum number of visited blocks for the agent. Used to limit the amount of work of the pathfinding algorithm.
|
||||
pathfinding.set_max_visited(agent: int, max_visited: int)
|
||||
|
||||
--- Adding an avoided blocks tag
|
||||
pathfinding.avoid_tag(
|
||||
agent: int,
|
||||
-- tag for avoided blocks
|
||||
tag: string, [optional],
|
||||
-- cost of crossing a block
|
||||
cost: int = 10
|
||||
)
|
||||
```
|
||||
@ -100,6 +100,13 @@ vecn.length(a: vector)
|
||||
|
||||
```
|
||||
|
||||
#### Distance - *vecn.distance(...)*
|
||||
|
||||
```lua
|
||||
-- returns the distance between two vectors
|
||||
vecn.distance(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Absolute value - *vecn.abs(...)*
|
||||
|
||||
```lua
|
||||
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
|
||||
vecn.dot(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Mixing - *vecn.mix(...)*
|
||||
|
||||
```lua
|
||||
-- returns vector a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number)
|
||||
|
||||
-- writes to dst vector a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number, dst: vector)
|
||||
```
|
||||
|
||||
#### Convert to string - *vecn.tostring(...)*
|
||||
> [!WARNING]
|
||||
> Returns only if the content is a vector
|
||||
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
|
||||
|
||||
-- returns the direction angle of the vector {x, y} in degrees [0, 360]
|
||||
vec2.angle(x: number, y: number)
|
||||
|
||||
-- returns the vector rotated by an angle in degrees counterclockwise
|
||||
vec2.rotate(v: vec2, angle: number) -> vec2
|
||||
|
||||
-- writes the vector rotated by an angle in degrees counterclockwise to dst
|
||||
vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2
|
||||
```
|
||||
|
||||
|
||||
@ -188,6 +211,10 @@ print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80}
|
||||
local result_mul_scal = vec3.mul(v1_3d, scal)
|
||||
print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
|
||||
|
||||
-- calculating distance between vectors
|
||||
local result_distance = vec3.distance(v1_3d, v2_3d)
|
||||
print("distance: " .. result_distance) -- 43
|
||||
|
||||
-- vector normalization
|
||||
local result_norm = vec3.normalize(v1_3d)
|
||||
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
|
||||
@ -211,3 +238,7 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4}
|
||||
-- scalar product of vectors
|
||||
local result_dot = vec3.dot(v1_3d, v2_3d)
|
||||
print("dot: " ..result_dot) -- 250
|
||||
|
||||
-- mixing vectors
|
||||
local result_mix = vec3.mix(v1_3d, v2_3d, 0.25)
|
||||
print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5}
|
||||
|
||||
@ -26,6 +26,8 @@ entity:get_uid() -> int
|
||||
entity:get_component(name: str) -> component or nil
|
||||
-- Checks for the presence of a component by name
|
||||
entity:has_component(name: str) -> bool
|
||||
-- Retrieves a component by name. Throws an exception if it does not exist
|
||||
entity:require_component(name: str) -> component
|
||||
|
||||
-- Enables/disables the component
|
||||
entity:set_enabled(name: str, enable: bool)
|
||||
@ -93,10 +95,12 @@ body:get_linear_damping() -> number
|
||||
-- Sets the linear velocity attenuation multiplier
|
||||
body:set_linear_damping(value: number)
|
||||
|
||||
-- Checks if vertical velocity attenuation is enabled
|
||||
-- Checks if vertical damping is enabled
|
||||
body:is_vdamping() -> bool
|
||||
-- Enables/disables vertical velocity attenuation
|
||||
body:set_vdamping(enabled: bool)
|
||||
-- Returns the vertical damping multiplier
|
||||
body:get_vdamping() -> number
|
||||
-- Enables/disables vertical damping / sets vertical damping multiplier
|
||||
body:set_vdamping(enabled: bool | number)
|
||||
|
||||
-- Checks if the entity is on the ground
|
||||
body:is_grounded() -> bool
|
||||
@ -188,6 +192,12 @@ function on_update(tps: int)
|
||||
|
||||
Called every entities tick (currently 20 times per second).
|
||||
|
||||
```lua
|
||||
function on_physics_update(delta: number)
|
||||
```
|
||||
|
||||
Called after each physics step
|
||||
|
||||
```lua
|
||||
function on_render(delta: number)
|
||||
```
|
||||
|
||||
@ -20,6 +20,22 @@
|
||||
]
|
||||
```
|
||||
|
||||
Из конфигурации сущности можно передавать значения в ARGS.
|
||||
Они будут передаваться как при создании новой сущности, так и при загрузке сохранённой.
|
||||
Для этого используется список `args`:
|
||||
|
||||
```json
|
||||
"components": [
|
||||
{
|
||||
"name": "base:drop",
|
||||
"args": {
|
||||
"item": "base:stone.item",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Код компонентов должен находиться в `scripts/components`.
|
||||
|
||||
## Физика
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
- [mat4](scripting/builtins/libmat4.md)
|
||||
- [network](scripting/builtins/libnetwork.md)
|
||||
- [pack](scripting/builtins/libpack.md)
|
||||
- [pathfinding](scripting/builtins/libpathfinding.md)
|
||||
- [player](scripting/builtins/libplayer.md)
|
||||
- [quat](scripting/builtins/libquat.md)
|
||||
- [rules](scripting/builtins/librules.md)
|
||||
|
||||
66
doc/ru/scripting/builtins/libpathfinding.md
Normal file
66
doc/ru/scripting/builtins/libpathfinding.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Библиотека *pathfinding*
|
||||
|
||||
Библиотека *pathfinding* предоставляет функции для работы с системой поиска пути в игровом мире. Она позволяет создавать и управлять агентами, которые могут находить маршруты между точками в мире.
|
||||
|
||||
При использовании в логике сущностей следует использовать компонент `core:pathfinding`.
|
||||
|
||||
## Компонент `core:pathfinding`
|
||||
|
||||
```lua
|
||||
local pf = entity:get_component("core:pathfinding")
|
||||
|
||||
--- ...
|
||||
local x = ...
|
||||
local y = ...
|
||||
local z = ...
|
||||
|
||||
--- Установка цели для агента
|
||||
pf.set_target({x, y, z})
|
||||
|
||||
--- Получение текущей цели агента
|
||||
local target = pf.get_target() --> vec3 или nil
|
||||
--- ...
|
||||
|
||||
--- Получение текущего маршрута агента
|
||||
local route = pf.get_route() --> table<vec3> или nil
|
||||
--- ...
|
||||
```
|
||||
|
||||
## Функции библиотеки
|
||||
|
||||
```lua
|
||||
--- Создание нового агента. Возвращает идентификатор созданного агента
|
||||
local agent = pathfinding.create_agent() --> int
|
||||
|
||||
--- Удаление агента по идентификатору. Возвращает true, если агент существовал, иначе false
|
||||
pathfinding.remove_agent(agent: int) --> bool
|
||||
|
||||
--- Установка состояния агента (включен/выключен)
|
||||
pathfinding.set_enabled(agent: int, enabled: bool)
|
||||
|
||||
--- Проверка состояния агента. Возвращает true, если агент включен, иначе false
|
||||
pathfinding.is_enabled(agent: int) --> bool
|
||||
|
||||
--- Создание маршрута на основе заданных точек. Возвращает массив точек маршрута
|
||||
pathfinding.make_route(start: vec3, target: vec3) --> table<vec3>
|
||||
|
||||
--- Асинхронное создание маршрута на основе заданных точек.
|
||||
--- Функция позволяет выполнять поиск пути в фоновом режиме, не блокируя основной поток выполнения
|
||||
pathfinding.make_route_async(agent: int, start: vec3, target: vec3)
|
||||
|
||||
--- Получение маршрута, который агент уже нашел. Используется для получения маршрута после асинхронного поиска.
|
||||
--- Если поиск ещё не завершён, возвращает nil. Если маршрут не найден, возвращает пустую таблицу.
|
||||
pathfinding.pull_route(agent: int) --> table<vec3> или nil
|
||||
|
||||
--- Установка максимального количества посещенных блоков для агента. Используется для ограничения объема работы алгоритма поиска пути.
|
||||
pathfinding.set_max_visited(agent: int, max_visited: int)
|
||||
|
||||
--- Добавление тега избегаемых блоков
|
||||
pathfinding.avoid_tag(
|
||||
agent: int,
|
||||
-- тег избегаемых блоков
|
||||
tag: string, [опционально],
|
||||
-- стоимость пересечения блока
|
||||
cost: int = 10
|
||||
)
|
||||
```
|
||||
@ -100,6 +100,13 @@ vecn.length(a: vector)
|
||||
|
||||
```
|
||||
|
||||
#### Дистанция - *vecn.distance(...)*
|
||||
|
||||
```lua
|
||||
-- возвращает расстояние между двумя векторами
|
||||
vecn.distance(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Абсолютное значение - *vecn.abs(...)*
|
||||
|
||||
```lua
|
||||
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
|
||||
vecn.dot(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Смешивание - *vecn.mix(...)*
|
||||
|
||||
```lua
|
||||
-- возвращает вектор a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number)
|
||||
|
||||
-- записывает в dst вектор a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number, dst: vector)
|
||||
```
|
||||
|
||||
#### Перевод в строку - *vecn.tostring(...)*
|
||||
> [!WARNING]
|
||||
> Возвращает только тогда, когда содержимым является вектор
|
||||
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
|
||||
|
||||
-- возвращает угол направления вектора {x, y} в градусах [0, 360]
|
||||
vec2.angle(x: number, y: number)
|
||||
|
||||
-- возвращает повернутый вектор на угол в градусах против часовой стрелки
|
||||
vec2.rotate(v: vec2, angle: number) -> vec2
|
||||
|
||||
-- записывает повернутый вектор на угол в градусах против часовой стрелки в dst
|
||||
vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2
|
||||
```
|
||||
|
||||
|
||||
@ -192,6 +215,10 @@ print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
|
||||
local result_norm = vec3.normalize(v1_3d)
|
||||
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
|
||||
|
||||
-- дистанция между векторами
|
||||
local result_distance = vec3.distance(v1_3d, v2_3d)
|
||||
print("distance: " .. result_distance) -- 43
|
||||
|
||||
-- длина вектора
|
||||
local result_len = vec3.length(v1_3d)
|
||||
print("len: " .. result_len) -- 3
|
||||
@ -211,4 +238,9 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4}
|
||||
-- скалярное произведение векторов
|
||||
local result_dot = vec3.dot(v1_3d, v2_3d)
|
||||
print("dot: " .. result_dot) -- 250
|
||||
|
||||
-- смешивание векторов
|
||||
local result_mix = vec3.mix(v1_3d, v2_3d, 0.25)
|
||||
print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5}
|
||||
|
||||
```
|
||||
|
||||
@ -26,6 +26,8 @@ entity:get_uid() -> int
|
||||
entity:get_component(name: str) -> компонент или nil
|
||||
-- Проверяет наличие компонента по имени
|
||||
entity:has_component(name: str) -> bool
|
||||
-- Запрашивает компонент по имени. Бросает исключение при отсутствии
|
||||
entity:require_component(name: str) -> компонент
|
||||
|
||||
-- Включает/выключает компонент по имени
|
||||
entity:set_enabled(name: str, enable: bool)
|
||||
@ -95,8 +97,10 @@ body:set_linear_damping(value: number)
|
||||
|
||||
-- Проверяет, включено ли вертикальное затухание скорости
|
||||
body:is_vdamping() -> bool
|
||||
-- Включает/выключает вертикальное затухание скорости
|
||||
body:set_vdamping(enabled: bool)
|
||||
-- Возвращает множитель вертикального затухания скорости
|
||||
body:get_vdamping() -> number
|
||||
-- Включает/выключает вертикальное затухание скорости / устанавливает значение множителя
|
||||
body:set_vdamping(enabled: bool | number)
|
||||
|
||||
-- Проверяет, находится ли сущность на земле (приземлена)
|
||||
body:is_grounded() -> bool
|
||||
@ -188,6 +192,12 @@ function on_update(tps: int)
|
||||
|
||||
Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду).
|
||||
|
||||
```lua
|
||||
function on_physics_update(delta: number)
|
||||
```
|
||||
|
||||
Вызывается после каждого шага физики
|
||||
|
||||
```lua
|
||||
function on_render(delta: number)
|
||||
```
|
||||
|
||||
@ -8,5 +8,5 @@
|
||||
"selectable": false,
|
||||
"replaceable": true,
|
||||
"translucent": true,
|
||||
"tags": ["base:liquid"]
|
||||
"tags": ["core:liquid"]
|
||||
}
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
{
|
||||
"components": [
|
||||
"base:drop"
|
||||
{
|
||||
"name": "base:drop",
|
||||
"args": {
|
||||
"item": "base:stone.item",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
|
||||
],
|
||||
"hitbox": [0.4, 0.25, 0.4],
|
||||
"sensors": [
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"name": "core:mob",
|
||||
"args": {
|
||||
"jump_force": 8.0
|
||||
}
|
||||
},
|
||||
"core:player",
|
||||
"base:player_animator"
|
||||
],
|
||||
"hitbox": [0.6, 1.8, 0.6]
|
||||
|
||||
@ -8,6 +8,9 @@ timer = 0.3
|
||||
|
||||
local def_index = entity:def_index()
|
||||
dropitem = ARGS
|
||||
if dropitem.item then
|
||||
dropitem.id = item.index(dropitem.item)
|
||||
end
|
||||
if dropitem then
|
||||
timer = dropitem.pickup_delay or timer
|
||||
end
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
local tsf = entity.transform
|
||||
local body = entity.rigidbody
|
||||
local rig = entity.skeleton
|
||||
local mob = entity:require_component("core:mob")
|
||||
|
||||
local itemid = 0
|
||||
local headIndex = rig:index("head")
|
||||
local itemIndex = rig:index("item")
|
||||
local bodyIndex = rig:index("body")
|
||||
|
||||
local function refresh_model(id)
|
||||
itemid = id
|
||||
@ -18,10 +17,11 @@ function on_render()
|
||||
if pid == -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local rx, ry, rz = player.get_rot(pid, pid ~= hud.get_player())
|
||||
rig:set_matrix(headIndex, mat4.rotate({1, 0, 0}, ry))
|
||||
rig:set_matrix(bodyIndex, mat4.rotate({0, 1, 0}, rx))
|
||||
|
||||
local rx, _, _ = player.get_rot(pid, pid ~= hud.get_player())
|
||||
|
||||
local dir = vec2.rotate({0, -1}, -rx)
|
||||
mob.set_dir({dir[1], 0, dir[2]})
|
||||
|
||||
local invid, slotid = player.get_inventory(pid)
|
||||
local id, _ = inventory.get(invid, slotid)
|
||||
|
||||
@ -110,6 +110,21 @@ function vec3.dot(a, b)
|
||||
return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
|
||||
end
|
||||
|
||||
function vec3.mix(a, b, t, dest)
|
||||
if dest then
|
||||
dest[1] = a[1] * (1.0 - t) + b[1] * t
|
||||
dest[2] = a[2] * (1.0 - t) + b[2] * t
|
||||
dest[3] = a[3] * (1.0 - t) + b[3] * t
|
||||
return dest
|
||||
else
|
||||
return {
|
||||
a[1] * (1.0 - t) + b[1] * t,
|
||||
a[2] * (1.0 - t) + b[2] * t,
|
||||
a[3] * (1.0 - t) + b[3] * t,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- =================================================== --
|
||||
-- ====================== vec2 ======================= --
|
||||
-- =================================================== --
|
||||
@ -210,3 +225,16 @@ end
|
||||
function vec2.dot(a, b)
|
||||
return a[1] * b[1] + a[2] * b[2]
|
||||
end
|
||||
|
||||
function vec2.mix(a, b, t, dest)
|
||||
if dest then
|
||||
dest[1] = a[1] * (1.0 - t) + b[1] * t
|
||||
dest[2] = a[2] * (1.0 - t) + b[2] * t
|
||||
return dest
|
||||
else
|
||||
return {
|
||||
a[1] * (1.0 - t) + b[1] * t,
|
||||
a[2] * (1.0 - t) + b[2] * t,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@ -25,6 +25,7 @@ local Rigidbody = {__index={
|
||||
get_linear_damping=function(self) return __rigidbody.get_linear_damping(self.eid) end,
|
||||
set_linear_damping=function(self, f) return __rigidbody.set_linear_damping(self.eid, f) end,
|
||||
is_vdamping=function(self) return __rigidbody.is_vdamping(self.eid) end,
|
||||
get_vdamping=function(self) return __rigidbody.get_vdamping(self.eid) end,
|
||||
set_vdamping=function(self, b) return __rigidbody.set_vdamping(self.eid, b) end,
|
||||
is_grounded=function(self) return __rigidbody.is_grounded(self.eid) end,
|
||||
is_crouching=function(self) return __rigidbody.is_crouching(self.eid) end,
|
||||
@ -63,6 +64,13 @@ local Entity = {__index={
|
||||
get_skeleton=function(self) return entities.get_skeleton(self.eid) end,
|
||||
set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end,
|
||||
get_component=function(self, name) return self.components[name] end,
|
||||
require_component=function(self, name)
|
||||
local component = self.components[name]
|
||||
if not component then
|
||||
error(("entity has no required component '%s'"):format(name))
|
||||
end
|
||||
return component
|
||||
end,
|
||||
has_component=function(self, name) return self.components[name] ~= nil end,
|
||||
get_uid=function(self) return self.eid end,
|
||||
def_index=function(self) return entities.get_def(self.eid) end,
|
||||
@ -125,6 +133,19 @@ return {
|
||||
::continue::
|
||||
end
|
||||
end,
|
||||
physics_update = function(delta)
|
||||
for uid, entity in pairs(entities) do
|
||||
for _, component in pairs(entity.components) do
|
||||
local callback = component.on_physics_update
|
||||
if not component.__disabled and callback then
|
||||
local result, err = pcall(callback, delta)
|
||||
if err then
|
||||
debug.error(err)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
render = function(delta)
|
||||
for _,entity in pairs(entities) do
|
||||
for _, component in pairs(entity.components) do
|
||||
|
||||
@ -11,6 +11,9 @@ local Schedule = {
|
||||
self._next_interval = id + 1
|
||||
return id
|
||||
end,
|
||||
set_timeout = function(self, ms, callback)
|
||||
self:set_interval(ms, callback, 1)
|
||||
end,
|
||||
tick = function(self, dt)
|
||||
local timer = self._timer + dt
|
||||
for id, interval in pairs(self._intervals) do
|
||||
|
||||
177
res/scripts/components/mob.lua
Normal file
177
res/scripts/components/mob.lua
Normal file
@ -0,0 +1,177 @@
|
||||
local body = entity.rigidbody
|
||||
local tsf = entity.transform
|
||||
local rig = entity.skeleton
|
||||
|
||||
local props = {}
|
||||
|
||||
local function def_prop(name, def_value)
|
||||
props[name] = SAVED_DATA[name] or ARGS[name] or def_value
|
||||
this["get_"..name] = function() return props[name] end
|
||||
this["set_"..name] = function(value)
|
||||
props[name] = value
|
||||
if math.abs(value - def_value) < 1e-7 then
|
||||
SAVED_DATA[name] = nil
|
||||
else
|
||||
SAVED_DATA[name] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def_prop("jump_force", 0.0)
|
||||
def_prop("air_damping", 1.0)
|
||||
def_prop("ground_damping", 1.0)
|
||||
def_prop("movement_speed", 3.0)
|
||||
def_prop("run_speed_mul", 1.5)
|
||||
def_prop("crouch_speed_mul", 0.35)
|
||||
def_prop("flight_speed_mul", 4.0)
|
||||
def_prop("gravity_scale", 1.0)
|
||||
|
||||
local function normalize_angle(angle)
|
||||
while angle > 180 do
|
||||
angle = angle - 360
|
||||
end
|
||||
while angle <= -180 do
|
||||
angle = angle + 360
|
||||
end
|
||||
return angle
|
||||
end
|
||||
|
||||
local function angle_delta(a, b)
|
||||
return normalize_angle(a - b)
|
||||
end
|
||||
|
||||
local dir = mat4.mul(tsf:get_rot(), {0, 0, -1})
|
||||
local flight = false
|
||||
|
||||
function jump(multiplier)
|
||||
local vel = body:get_vel()
|
||||
body:set_vel(
|
||||
vec3.add(vel, {0, props.jump_force * (multiplier or 1.0), 0}, vel))
|
||||
end
|
||||
|
||||
function move_vertical(speed, vel)
|
||||
vel = vel or body:get_vel()
|
||||
vel[2] = vel[2] * 0.2 + props.movement_speed * speed * 0.8
|
||||
body:set_vel(vel)
|
||||
end
|
||||
|
||||
local function move_horizontal(speed, dir, vel)
|
||||
vel = vel or body:get_vel()
|
||||
if vec2.length(dir) > 0.0 then
|
||||
vec2.normalize(dir, dir)
|
||||
|
||||
local magnitude = vec2.length({vel[1], vel[3]})
|
||||
|
||||
if magnitude <= 1e-4 or (magnitude < speed or vec2.dot(
|
||||
{vel[1] / magnitude, vel[3] / magnitude}, dir) < 0.9)
|
||||
then
|
||||
vel[1] = vel[1] * 0.2 + dir[1] * speed * 0.8
|
||||
vel[3] = vel[3] * 0.2 + dir[2] * speed * 0.8
|
||||
end
|
||||
magnitude = vec3.length({vel[1], 0, vel[3]})
|
||||
if vec2.dot({vel[1] / magnitude, vel[3] / magnitude}, dir) > 0.5 then
|
||||
vel[1] = vel[1] / magnitude * speed
|
||||
vel[3] = vel[3] / magnitude * speed
|
||||
end
|
||||
end
|
||||
body:set_vel(vel)
|
||||
end
|
||||
|
||||
function go(dir, speed_multiplier, sprint, crouch, vel)
|
||||
local speed = props.movement_speed * speed_multiplier
|
||||
if flight then
|
||||
speed = speed * props.flight_speed_mul
|
||||
end
|
||||
if sprint then
|
||||
speed = speed * props.run_speed_mul
|
||||
elseif crouch then
|
||||
speed = speed * props.crouch_speed_mul
|
||||
end
|
||||
move_horizontal(speed, dir, vel)
|
||||
end
|
||||
|
||||
local headIndex = rig:index("head")
|
||||
|
||||
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)
|
||||
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.2
|
||||
dir[3] = dir[3] * 0.8 + viewdir[3] * 0.2
|
||||
end
|
||||
|
||||
if not headIndex then
|
||||
return
|
||||
end
|
||||
|
||||
local headrot = mat4.idt()
|
||||
local curdir = mat4.mul(mat4.mul(tsf:get_rot(),
|
||||
rig:get_matrix(headIndex)), {0, 0, -1})
|
||||
|
||||
vec3.mix(curdir, viewdir, 0.2, viewdir)
|
||||
|
||||
headrot = mat4.inverse(mat4.look_at({0,0,0}, viewdir, {0, 1, 0}))
|
||||
headrot = mat4.mul(mat4.inverse(tsf:get_rot()), headrot)
|
||||
rig:set_matrix(headIndex, headrot)
|
||||
end
|
||||
|
||||
function follow_waypoints(pathfinding)
|
||||
pathfinding = pathfinding or entity:require_component("core:pathfinding")
|
||||
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(
|
||||
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)
|
||||
if upper and body:is_grounded() then
|
||||
jump(1.0)
|
||||
end
|
||||
end
|
||||
|
||||
function set_dir(new_dir)
|
||||
dir = new_dir
|
||||
end
|
||||
|
||||
function is_flight() return flight end
|
||||
|
||||
function set_flight(flag) flight = flag end
|
||||
|
||||
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
|
||||
|
||||
function on_physics_update(delta)
|
||||
local grounded = body:is_grounded()
|
||||
body:set_vdamping(flight)
|
||||
body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0})
|
||||
body:set_linear_damping(
|
||||
(flight or not grounded) and props.air_damping or props.ground_damping
|
||||
)
|
||||
|
||||
local new_angle = (vec2.angle({dir[3], dir[1]})) % 360
|
||||
local angle = prev_angle
|
||||
|
||||
local adelta = angle_delta(
|
||||
normalize_angle(new_angle),
|
||||
normalize_angle(prev_angle)
|
||||
)
|
||||
local rotate_speed = entity:get_player() == -1 and 200 or 400
|
||||
|
||||
if math.abs(adelta) > 5 then
|
||||
angle = angle + delta * rotate_speed * (adelta > 0 and 1 or -1)
|
||||
end
|
||||
|
||||
tsf:set_rot(mat4.rotate({0, 1, 0}, angle + 180))
|
||||
prev_angle = angle
|
||||
end
|
||||
71
res/scripts/components/pathfinding.lua
Normal file
71
res/scripts/components/pathfinding.lua
Normal file
@ -0,0 +1,71 @@
|
||||
local target
|
||||
local route
|
||||
local started
|
||||
|
||||
local tsf = entity.transform
|
||||
local body = entity.rigidbody
|
||||
|
||||
agent = pathfinding.create_agent()
|
||||
pathfinding.set_max_visited(agent, 1e3)
|
||||
pathfinding.avoid_tag(agent, "core:liquid", 8)
|
||||
|
||||
function set_target(new_target)
|
||||
target = new_target
|
||||
end
|
||||
|
||||
function set_jump_height(height)
|
||||
pathfinding.set_jump_height(agent, height)
|
||||
end
|
||||
|
||||
function get_target()
|
||||
return target
|
||||
end
|
||||
|
||||
function get_route()
|
||||
return route
|
||||
end
|
||||
|
||||
function next_waypoint()
|
||||
if not route or #route == 0 then
|
||||
return
|
||||
end
|
||||
local waypoint = route[#route]
|
||||
local pos = tsf:get_pos()
|
||||
local dst = vec2.length({
|
||||
math.floor(waypoint[1] - math.floor(pos[1])),
|
||||
math.floor(waypoint[3] - math.floor(pos[3]))
|
||||
})
|
||||
if dst < 1.0 then
|
||||
table.remove(route, #route)
|
||||
end
|
||||
return route[#route]
|
||||
end
|
||||
|
||||
local refresh_internal = 100
|
||||
local frameid = math.random(0, refresh_internal)
|
||||
|
||||
function set_refresh_interval(interval)
|
||||
refresh_internal = interval
|
||||
end
|
||||
|
||||
function on_update()
|
||||
if not started then
|
||||
frameid = frameid + 1
|
||||
if body:is_grounded() then
|
||||
if target and (frameid % refresh_internal == 1 or not route) then
|
||||
pathfinding.make_route_async(agent, tsf:get_pos(), target)
|
||||
started = true
|
||||
end
|
||||
end
|
||||
else
|
||||
local new_route = pathfinding.pull_route(agent)
|
||||
if new_route then
|
||||
route = new_route
|
||||
started = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function on_despawn()
|
||||
pathfinding.remove_agent(agent)
|
||||
end
|
||||
62
res/scripts/components/player.lua
Normal file
62
res/scripts/components/player.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local tsf = entity.transform
|
||||
local body = entity.rigidbody
|
||||
local mob = entity:require_component("core:mob")
|
||||
|
||||
local cheat_speed_mul = 5.0
|
||||
|
||||
local function process_player_inputs(pid, delta)
|
||||
if not hud or hud.is_inventory_open() or menu.page ~= "" then
|
||||
return
|
||||
end
|
||||
local cam = cameras.get("core:first-person")
|
||||
local front = cam:get_front()
|
||||
local right = cam:get_right()
|
||||
front[2] = 0.0
|
||||
vec3.normalize(front, front)
|
||||
|
||||
local isjump = input.is_active('movement.jump')
|
||||
local issprint = input.is_active('movement.sprint')
|
||||
local iscrouch = input.is_active('movement.crouch')
|
||||
local isforward = input.is_active('movement.forward')
|
||||
local ischeat = input.is_active('movement.cheat')
|
||||
local isback = input.is_active('movement.back')
|
||||
local isleft = input.is_active('movement.left')
|
||||
local isright = input.is_active('movement.right')
|
||||
mob.set_flight(player.is_flight(pid))
|
||||
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
|
||||
body:set_crouching(iscrouch)
|
||||
|
||||
local vel = body:get_vel()
|
||||
local speed = ischeat and cheat_speed_mul or 1.0
|
||||
|
||||
local dir = {0, 0, 0}
|
||||
|
||||
if isforward then vec3.add(dir, front, dir) end
|
||||
if isback then vec3.sub(dir, front, dir) end
|
||||
if isright then vec3.add(dir, right, dir) end
|
||||
if isleft then vec3.sub(dir, right, dir) end
|
||||
|
||||
if vec3.length(dir) > 0.0 then
|
||||
mob.go({dir[1], dir[3]}, speed, issprint, iscrouch, vel)
|
||||
end
|
||||
|
||||
if mob.is_flight() then
|
||||
if isjump then
|
||||
mob.move_vertical(speed * 4)
|
||||
elseif iscrouch then
|
||||
mob.move_vertical(-speed * 4)
|
||||
end
|
||||
elseif body:is_grounded() and isjump then
|
||||
mob.jump()
|
||||
end
|
||||
end
|
||||
|
||||
function on_physics_update(delta)
|
||||
local pid = entity:get_player()
|
||||
if pid ~= -1 then
|
||||
local pos = tsf:get_pos()
|
||||
local cam = cameras.get("core:first-person")
|
||||
process_player_inputs(pid, delta)
|
||||
mob.look_at(vec3.add(pos, cam:get_front()))
|
||||
end
|
||||
end
|
||||
@ -22,6 +22,39 @@ local function configure_SSAO()
|
||||
-- for test purposes
|
||||
end
|
||||
|
||||
local function update_hand()
|
||||
local skeleton = gfx.skeletons
|
||||
local pid = hud.get_player()
|
||||
local invid, slot = player.get_inventory(pid)
|
||||
local itemid = inventory.get(invid, slot)
|
||||
|
||||
local cam = cameras.get("core:first-person")
|
||||
local bone = skeleton.index("hand", "item")
|
||||
|
||||
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
|
||||
|
||||
local rotation = cam:get_rot()
|
||||
|
||||
local angle = player.get_rot(pid) - 90
|
||||
local cos = math.cos(angle / (180 / math.pi))
|
||||
local sin = math.sin(angle / (180 / math.pi))
|
||||
|
||||
local newX = offset[1] * cos - offset[3] * sin
|
||||
local newZ = offset[1] * sin + offset[3] * cos
|
||||
|
||||
offset[1] = newX
|
||||
offset[3] = newZ
|
||||
|
||||
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
|
||||
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
|
||||
mat4.mul(rotation, mat, mat)
|
||||
mat4.rotate(mat, {0, 1, 0}, -90, mat)
|
||||
mat4.translate(mat, offset, mat)
|
||||
|
||||
skeleton.set_matrix("hand", bone, mat)
|
||||
skeleton.set_model("hand", bone, item.model_name(itemid))
|
||||
end
|
||||
|
||||
function on_hud_open()
|
||||
input.add_callback("player.pick", function ()
|
||||
if hud.is_paused() or hud.is_inventory_open() then
|
||||
@ -63,7 +96,7 @@ function on_hud_open()
|
||||
player.set_noclip(pid, true)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
input.add_callback("player.flight", function ()
|
||||
if hud.is_paused() or hud.is_inventory_open() then
|
||||
return
|
||||
@ -81,39 +114,8 @@ function on_hud_open()
|
||||
end)
|
||||
|
||||
configure_SSAO()
|
||||
end
|
||||
|
||||
local function update_hand()
|
||||
local skeleton = gfx.skeletons
|
||||
local pid = hud.get_player()
|
||||
local invid, slot = player.get_inventory(pid)
|
||||
local itemid = inventory.get(invid, slot)
|
||||
|
||||
local cam = cameras.get("core:first-person")
|
||||
local bone = skeleton.index("hand", "item")
|
||||
|
||||
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
|
||||
|
||||
local rotation = cam:get_rot()
|
||||
|
||||
local angle = player.get_rot() - 90
|
||||
local cos = math.cos(angle / (180 / math.pi))
|
||||
local sin = math.sin(angle / (180 / math.pi))
|
||||
|
||||
local newX = offset[1] * cos - offset[3] * sin
|
||||
local newZ = offset[1] * sin + offset[3] * cos
|
||||
|
||||
offset[1] = newX
|
||||
offset[3] = newZ
|
||||
|
||||
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
|
||||
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
|
||||
mat4.mul(rotation, mat, mat)
|
||||
mat4.rotate(mat, {0, 1, 0}, -90, mat)
|
||||
mat4.translate(mat, offset, mat)
|
||||
|
||||
skeleton.set_matrix("hand", bone, mat)
|
||||
skeleton.set_model("hand", bone, item.model_name(itemid))
|
||||
hud.default_hand_controller = update_hand
|
||||
end
|
||||
|
||||
function on_hud_render()
|
||||
|
||||
@ -157,6 +157,16 @@ console.add_command(
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
console.add_command(
|
||||
"entity.spawn name:str x:num~pos.x y:num~pos.y z:num~pos.z",
|
||||
"Spawn entity with default parameters",
|
||||
function(args, kwargs)
|
||||
local eid = entities.spawn(args[1], {args[2], args[3], args[4]})
|
||||
return string.format("spawned %s at %s, %s, %s", unpack(args))
|
||||
end
|
||||
)
|
||||
|
||||
console.add_command(
|
||||
"entity.despawn entity:sel=$entity.selected",
|
||||
"Despawn entity",
|
||||
|
||||
@ -429,6 +429,8 @@ function __vc_on_hud_open()
|
||||
hud.open_permanent("core:ingame_chat")
|
||||
end
|
||||
|
||||
local Schedule = require "core:schedule"
|
||||
|
||||
local ScheduleGroup_mt = {
|
||||
__index = {
|
||||
publish = function(self, schedule)
|
||||
@ -440,10 +442,11 @@ local ScheduleGroup_mt = {
|
||||
for id, schedule in pairs(self._schedules) do
|
||||
schedule:tick(dt)
|
||||
end
|
||||
self.common:tick(dt)
|
||||
end,
|
||||
remove = function(self, id)
|
||||
self._schedules[id] = nil
|
||||
end
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,6 +454,7 @@ local function ScheduleGroup()
|
||||
return setmetatable({
|
||||
_next_schedule = 1,
|
||||
_schedules = {},
|
||||
common = Schedule()
|
||||
}, ScheduleGroup_mt)
|
||||
end
|
||||
|
||||
|
||||
@ -71,8 +71,8 @@ static auto process_program(const ResPaths& paths, const std::string& filename)
|
||||
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
|
||||
auto vertex = preprocessor.process(vertexFile, vertexSource);
|
||||
auto fragment = preprocessor.process(fragmentFile, fragmentSource);
|
||||
auto vertex = preprocessor.process(vertexFile, vertexSource, false, {});
|
||||
auto fragment = preprocessor.process(fragmentFile, fragmentSource, false, {});
|
||||
return std::make_pair(vertex, fragment);
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ assetload::postfunc assetload::posteffect(
|
||||
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
preprocessor.addHeader(
|
||||
"__effect__", preprocessor.process(effectFile, effectSource, true)
|
||||
"__effect__", preprocessor.process(effectFile, effectSource, true, {})
|
||||
);
|
||||
|
||||
auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect");
|
||||
|
||||
@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) {
|
||||
this->paths = paths;
|
||||
}
|
||||
|
||||
void GLSLExtension::setTraceOutput(bool enabled) {
|
||||
this->traceOutput = enabled;
|
||||
}
|
||||
|
||||
void GLSLExtension::loadHeader(const std::string& name) {
|
||||
if (paths == nullptr) {
|
||||
return;
|
||||
@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) {
|
||||
io::path file = paths->find("shaders/lib/" + name + ".glsl");
|
||||
std::string source = io::read_string(file);
|
||||
addHeader(name, {});
|
||||
addHeader(name, process(file, source, true));
|
||||
addHeader(name, process(file, source, true, {}));
|
||||
}
|
||||
|
||||
void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) {
|
||||
@ -123,13 +127,22 @@ static Value default_value_for(Type type) {
|
||||
|
||||
class GLSLParser : public BasicParser<char> {
|
||||
public:
|
||||
GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header)
|
||||
GLSLParser(
|
||||
GLSLExtension& glsl,
|
||||
std::string_view file,
|
||||
std::string_view source,
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
)
|
||||
: BasicParser(file, source), glsl(glsl) {
|
||||
if (!header) {
|
||||
ss << "#version " << GLSLExtension::VERSION << '\n';
|
||||
}
|
||||
for (auto& entry : glsl.getDefines()) {
|
||||
ss << "#define " << entry.first << " " << entry.second << '\n';
|
||||
for (auto& entry : defines) {
|
||||
ss << "#define " << entry << '\n';
|
||||
}
|
||||
for (auto& entry : defines) {
|
||||
ss << "#define " << entry << '\n';
|
||||
}
|
||||
}
|
||||
uint linenum = 1;
|
||||
source_line(ss, linenum);
|
||||
@ -289,10 +302,34 @@ private:
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
static void trace_output(
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
const GLSLExtension::ProcessingResult& result
|
||||
) {
|
||||
std::stringstream ss;
|
||||
ss << "export:trace/" << file.name();
|
||||
io::path outfile = ss.str();
|
||||
try {
|
||||
io::create_directories(outfile.parent());
|
||||
io::write_string(outfile, result.code);
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << "error on saving GLSLExtension::preprocess output ("
|
||||
<< outfile.string() << "): " << err.what();
|
||||
}
|
||||
}
|
||||
|
||||
GLSLExtension::ProcessingResult GLSLExtension::process(
|
||||
const io::path& file, const std::string& source, bool header
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
) {
|
||||
std::string filename = file.string();
|
||||
GLSLParser parser(*this, filename, source, header);
|
||||
return parser.process();
|
||||
GLSLParser parser(*this, filename, source, header, defines);
|
||||
auto result = parser.process();
|
||||
if (traceOutput) {
|
||||
trace_output(file, source, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "data/setting.hpp"
|
||||
#include "graphics/core/PostEffect.hpp"
|
||||
|
||||
class ResPaths;
|
||||
@ -19,6 +20,7 @@ public:
|
||||
};
|
||||
|
||||
void setPaths(const ResPaths* paths);
|
||||
void setTraceOutput(bool enabled);
|
||||
|
||||
void define(const std::string& name, std::string value);
|
||||
void undefine(const std::string& name);
|
||||
@ -37,7 +39,8 @@ public:
|
||||
ProcessingResult process(
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
bool header = false
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
);
|
||||
|
||||
static inline std::string VERSION = "330 core";
|
||||
@ -46,4 +49,5 @@ private:
|
||||
std::unordered_map<std::string, std::string> defines;
|
||||
|
||||
const ResPaths* paths = nullptr;
|
||||
bool traceOutput = false;
|
||||
};
|
||||
|
||||
@ -30,7 +30,18 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
||||
|
||||
if (auto found = root.at("components")) {
|
||||
for (const auto& elem : *found) {
|
||||
def.components.emplace_back(elem.asString());
|
||||
std::string name;
|
||||
dv::value params;
|
||||
if (elem.isObject()) {
|
||||
name = elem["name"].asString();
|
||||
if (elem.has("args")) {
|
||||
params = elem["args"];
|
||||
}
|
||||
} else {
|
||||
name = elem.asString();
|
||||
}
|
||||
def.components.push_back(ComponentInstance {
|
||||
std::move(name), std::move(params)});
|
||||
}
|
||||
}
|
||||
if (auto found = root.at("hitbox")) {
|
||||
|
||||
@ -46,12 +46,12 @@ namespace dv {
|
||||
if (!map.has(key)) {
|
||||
return;
|
||||
}
|
||||
auto& list = map[key];
|
||||
const auto& srcList = map[key];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if constexpr (std::is_floating_point<T>()) {
|
||||
vec[i] = list[i].asNumber();
|
||||
vec[i] = srcList[i].asNumber();
|
||||
} else {
|
||||
vec[i] = list[i].asInteger();
|
||||
vec[i] = srcList[i].asInteger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,6 +143,12 @@ void Engine::initializeClient() {
|
||||
},
|
||||
true
|
||||
));
|
||||
keepAlive(settings.debug.doTraceShaders.observe(
|
||||
[](bool value) {
|
||||
Shader::preprocessor->setTraceOutput(value);
|
||||
},
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
void Engine::initialize(CoreParameters coreParameters) {
|
||||
|
||||
@ -12,12 +12,14 @@
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
#include "graphics/render/DebugLinesRenderer.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
@ -44,6 +46,7 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
|
||||
// TODO: move to xml
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
std::shared_ptr<UINode> create_debug_panel(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
@ -260,6 +263,18 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
{
|
||||
auto checkbox = std::make_shared<FullCheckBox>(
|
||||
gui, L"Show Paths", glm::vec2(400, 24)
|
||||
);
|
||||
checkbox->setSupplier([=]() {
|
||||
return DebugLinesRenderer::showPaths;
|
||||
});
|
||||
checkbox->setConsumer([=](bool checked) {
|
||||
DebugLinesRenderer::showPaths = checked;
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
{
|
||||
auto checkbox = std::make_shared<FullCheckBox>(
|
||||
gui, L"Show Generator Minimap", glm::vec2(400, 24)
|
||||
|
||||
@ -176,7 +176,7 @@ void LevelScreen::saveWorldPreview() {
|
||||
static_cast<uint>(previewSize)}
|
||||
);
|
||||
|
||||
renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing);
|
||||
renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing);
|
||||
auto image = postProcessing->toImage();
|
||||
image->flipY();
|
||||
imageio::write("world:preview.png", image.get());
|
||||
@ -263,7 +263,7 @@ void LevelScreen::draw(float delta) {
|
||||
if (!hud->isPause()) {
|
||||
scripting::on_entities_render(engine.getTime().getDelta());
|
||||
}
|
||||
renderer->draw(
|
||||
renderer->renderFrame(
|
||||
ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
|
||||
);
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "ImageData.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color
|
||||
}
|
||||
}
|
||||
|
||||
template<uint channels>
|
||||
static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) {
|
||||
ubyte* data = image.getData();
|
||||
int imageWidth = image.getWidth();
|
||||
int imageHeight = image.getHeight();
|
||||
|
||||
int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1);
|
||||
int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1);
|
||||
|
||||
int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1);
|
||||
int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1);
|
||||
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
int index = (y * imageWidth + x) * channels;
|
||||
for (int i = 0; i < channels; i++) {
|
||||
data[index + i] = color[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) {
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888:
|
||||
draw_rect<3>(*this, x, y, width, height, color);
|
||||
break;
|
||||
case ImageFormat::rgba8888:
|
||||
draw_rect<4>(*this, x, y, width, height, color);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
|
||||
ubyte* source = image.getData();
|
||||
uint srcwidth = image.getWidth();
|
||||
|
||||
@ -28,6 +28,7 @@ public:
|
||||
void flipY();
|
||||
|
||||
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color);
|
||||
void drawRect(int x, int y, int width, int height, const glm::ivec4& color);
|
||||
void blit(const ImageData& image, int x, int y);
|
||||
void extrude(int x, int y, int w, int h);
|
||||
void fixAlphaColor();
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
|
||||
@ -138,15 +137,21 @@ glshader compile_shader(GLenum type, const GLchar* source, const std::string& fi
|
||||
}
|
||||
|
||||
static GLuint compile_program(
|
||||
const Shader::Source& vertexSource, const Shader::Source& fragmentSource
|
||||
const Shader::Source& vertexSource,
|
||||
const Shader::Source& fragmentSource,
|
||||
const std::vector<std::string>& defines
|
||||
) {
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
|
||||
auto vertexCode = std::move(
|
||||
preprocessor.process(vertexSource.file, vertexSource.code).code
|
||||
preprocessor
|
||||
.process(vertexSource.file, vertexSource.code, false, defines)
|
||||
.code
|
||||
);
|
||||
auto fragmentCode = std::move(
|
||||
preprocessor.process(fragmentSource.file, fragmentSource.code).code
|
||||
preprocessor
|
||||
.process(fragmentSource.file, fragmentSource.code, false, defines)
|
||||
.code
|
||||
);
|
||||
|
||||
const GLchar* vCode = vertexCode.c_str();
|
||||
@ -176,8 +181,8 @@ static GLuint compile_program(
|
||||
return program;
|
||||
}
|
||||
|
||||
void Shader::recompile() {
|
||||
GLuint newProgram = compile_program(vertexSource, fragmentSource);
|
||||
void Shader::recompile(const std::vector<std::string>& defines) {
|
||||
GLuint newProgram = compile_program(vertexSource, fragmentSource, defines);
|
||||
glDeleteProgram(id);
|
||||
id = newProgram;
|
||||
uniformLocations.clear();
|
||||
@ -188,7 +193,7 @@ std::unique_ptr<Shader> Shader::create(
|
||||
Source&& vertexSource, Source&& fragmentSource
|
||||
) {
|
||||
return std::make_unique<Shader>(
|
||||
compile_program(vertexSource, fragmentSource),
|
||||
compile_program(vertexSource, fragmentSource, {}),
|
||||
std::move(vertexSource),
|
||||
std::move(fragmentSource)
|
||||
);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
@ -50,7 +51,7 @@ public:
|
||||
void uniform4v(const std::string& name, int length, const float* v);
|
||||
|
||||
/// @brief Re-preprocess source code and re-compile shader program
|
||||
void recompile();
|
||||
void recompile(const std::vector<std::string>& defines);
|
||||
|
||||
/// @brief Create shader program using vertex and fragment shaders source.
|
||||
/// @return linked shader program containing vertex and fragment shaders
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
#include "ShadowMap.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
ShadowMap::ShadowMap(int resolution) : resolution(resolution) {
|
||||
glGenTextures(1, &depthMap);
|
||||
glBindTexture(GL_TEXTURE_2D, depthMap);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
|
||||
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||
float border[4] {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, border);
|
||||
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
|
||||
glDrawBuffer(GL_NONE);
|
||||
glReadBuffer(GL_NONE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
ShadowMap::~ShadowMap() {
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &depthMap);
|
||||
}
|
||||
|
||||
void ShadowMap::bind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void ShadowMap::unbind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
uint ShadowMap::getDepthMap() const {
|
||||
return depthMap;
|
||||
}
|
||||
|
||||
int ShadowMap::getResolution() const {
|
||||
return resolution;
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "typedefs.hpp"
|
||||
|
||||
class ShadowMap {
|
||||
public:
|
||||
ShadowMap(int resolution);
|
||||
~ShadowMap();
|
||||
|
||||
void bind();
|
||||
void unbind();
|
||||
uint getDepthMap() const;
|
||||
int getResolution() const;
|
||||
private:
|
||||
uint fbo;
|
||||
uint depthMap;
|
||||
int resolution;
|
||||
};
|
||||
199
src/graphics/core/Shadows.cpp
Normal file
199
src/graphics/core/Shadows.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
#include "Shadows.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/commons.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/Weather.hpp"
|
||||
#include "world/World.hpp"
|
||||
|
||||
using namespace advanced_pipeline;
|
||||
|
||||
inline constexpr int MIN_SHADOW_MAP_RES = 512;
|
||||
inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0;
|
||||
|
||||
class ShadowMap {
|
||||
public:
|
||||
ShadowMap(int resolution) : resolution(resolution) {
|
||||
glGenTextures(1, &depthMap);
|
||||
glBindTexture(GL_TEXTURE_2D, depthMap);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
|
||||
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(
|
||||
GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE
|
||||
);
|
||||
float border[4] {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border);
|
||||
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glFramebufferTexture2D(
|
||||
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0
|
||||
);
|
||||
glDrawBuffer(GL_NONE);
|
||||
glReadBuffer(GL_NONE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
~ShadowMap() {
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &depthMap);
|
||||
}
|
||||
|
||||
void bind(){
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
uint getDepthMap() const {
|
||||
return depthMap;
|
||||
}
|
||||
int getResolution() const {
|
||||
return resolution;
|
||||
}
|
||||
private:
|
||||
uint fbo;
|
||||
uint depthMap;
|
||||
int resolution;
|
||||
};
|
||||
|
||||
Shadows::Shadows(const Level& level) : level(level) {}
|
||||
|
||||
Shadows::~Shadows() = default;
|
||||
|
||||
void Shadows::setQuality(int quality) {
|
||||
int resolution = MIN_SHADOW_MAP_RES << quality;
|
||||
if (quality > 0 && !shadows) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
shadows = true;
|
||||
} else if (quality == 0 && shadows) {
|
||||
shadowMap.reset();
|
||||
wideShadowMap.reset();
|
||||
shadows = false;
|
||||
}
|
||||
if (shadows && shadowMap->getResolution() != resolution) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
}
|
||||
this->quality = quality;
|
||||
}
|
||||
|
||||
void Shadows::setup(Shader& shader, const Weather& weather) {
|
||||
if (shadows) {
|
||||
const auto& worldInfo = level.getWorld()->getInfo();
|
||||
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
|
||||
shader.uniform1i("u_screen", 0);
|
||||
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
|
||||
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
|
||||
shader.uniform3f("u_sunDir", shadowCamera.front);
|
||||
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
|
||||
shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable
|
||||
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
|
||||
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
|
||||
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
|
||||
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
|
||||
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(TEXTURE_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
void Shadows::refresh(const Camera& camera, const DrawContext& pctx, std::function<void(Camera&)> renderShadowPass) {
|
||||
static int frameid = 0;
|
||||
if (shadows) {
|
||||
if (frameid % 2 == 0) {
|
||||
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass);
|
||||
} else {
|
||||
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f, renderShadowPass);
|
||||
}
|
||||
}
|
||||
frameid++;
|
||||
}
|
||||
|
||||
void Shadows::generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale,
|
||||
std::function<void(Camera&)> renderShadowPass
|
||||
) {
|
||||
auto world = level.getWorld();
|
||||
const auto& worldInfo = world->getInfo();
|
||||
|
||||
int resolution = shadowMap.getResolution();
|
||||
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;
|
||||
float shadowMapSize = resolution * shadowMapScale;
|
||||
|
||||
glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f;
|
||||
glm::vec3 prevPos = shadowCamera.position;
|
||||
shadowCamera = Camera(
|
||||
glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos,
|
||||
shadowMapSize
|
||||
);
|
||||
shadowCamera.near = 0.1f;
|
||||
shadowCamera.far = 1000.0f;
|
||||
shadowCamera.perspective = false;
|
||||
shadowCamera.setAspectRatio(1.0f);
|
||||
|
||||
float t = worldInfo.daytime - 0.25f;
|
||||
if (t < 0.0f) {
|
||||
t += 1.0f;
|
||||
}
|
||||
t = fmod(t, 0.5f);
|
||||
|
||||
float sunCycleStep = 1.0f / 500.0f;
|
||||
float sunAngle = glm::radians(
|
||||
90.0f -
|
||||
((static_cast<int>(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f
|
||||
);
|
||||
float sunAltitude = glm::pi<float>() * 0.25f;
|
||||
shadowCamera.rotate(
|
||||
-glm::cos(sunAngle + glm::pi<float>() * 0.5f) * sunAltitude,
|
||||
sunAngle - glm::pi<float>() * 0.5f,
|
||||
glm::radians(0.0f)
|
||||
);
|
||||
|
||||
shadowCamera.position -= shadowCamera.front * 500.0f;
|
||||
shadowCamera.position += shadowCamera.up * 0.0f;
|
||||
shadowCamera.position += camera.front * 0.0f;
|
||||
|
||||
auto view = shadowCamera.getView();
|
||||
|
||||
auto currentPos = shadowCamera.position;
|
||||
auto topRight = shadowCamera.right + shadowCamera.up;
|
||||
auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
|
||||
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
|
||||
|
||||
{
|
||||
auto sctx = pctx.sub();
|
||||
sctx.setDepthTest(true);
|
||||
sctx.setCullFace(true);
|
||||
sctx.setViewport({resolution, resolution});
|
||||
shadowMap.bind();
|
||||
if (renderShadowPass) {
|
||||
renderShadowPass(shadowCamera);
|
||||
}
|
||||
shadowMap.unbind();
|
||||
}
|
||||
}
|
||||
46
src/graphics/core/Shadows.hpp
Normal file
46
src/graphics/core/Shadows.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
|
||||
class Shader;
|
||||
class Level;
|
||||
class Assets;
|
||||
struct Weather;
|
||||
class DrawContext;
|
||||
struct EngineSettings;
|
||||
class ShadowMap;
|
||||
|
||||
class Shadows {
|
||||
public:
|
||||
Shadows(const Level& level);
|
||||
~Shadows();
|
||||
|
||||
void setup(Shader& shader, const Weather& weather);
|
||||
void setQuality(int quality);
|
||||
void refresh(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
std::function<void(Camera&)> renderShadowPass
|
||||
);
|
||||
private:
|
||||
const Level& level;
|
||||
bool shadows = false;
|
||||
Camera shadowCamera;
|
||||
Camera wideShadowCamera;
|
||||
std::unique_ptr<ShadowMap> shadowMap;
|
||||
std::unique_ptr<ShadowMap> wideShadowMap;
|
||||
int quality = 0;
|
||||
|
||||
void generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale,
|
||||
std::function<void(Camera&)> renderShadowPass
|
||||
);
|
||||
};
|
||||
@ -187,7 +187,7 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void ChunksRenderer::drawChunksShadowsPass(
|
||||
void ChunksRenderer::drawShadowsPass(
|
||||
const Camera& camera, Shader& shader, const Camera& playerCamera
|
||||
) {
|
||||
Frustum frustum;
|
||||
|
||||
@ -73,7 +73,7 @@ public:
|
||||
const std::shared_ptr<Chunk>& chunk, bool important
|
||||
);
|
||||
|
||||
void drawChunksShadowsPass(
|
||||
void drawShadowsPass(
|
||||
const Camera& camera, Shader& shader, const Camera& playerCamera
|
||||
);
|
||||
|
||||
|
||||
@ -1,15 +1,51 @@
|
||||
#include "GuidesRenderer.hpp"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include "DebugLinesRenderer.hpp"
|
||||
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "graphics/core/LineBatch.hpp"
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/render/LinesRenderer.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "maths/voxmaths.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "constants.hpp"
|
||||
|
||||
void GuidesRenderer::drawBorders(
|
||||
bool DebugLinesRenderer::showPaths = false;
|
||||
|
||||
static void draw_route(
|
||||
LinesRenderer& lines, const voxels::Agent& agent
|
||||
) {
|
||||
const auto& route = agent.route;
|
||||
if (!route.found)
|
||||
return;
|
||||
|
||||
for (int i = 1; i < route.nodes.size(); i++) {
|
||||
const auto& a = route.nodes.at(i - 1);
|
||||
const auto& b = route.nodes.at(i);
|
||||
|
||||
if (i == 1) {
|
||||
lines.pushLine(
|
||||
glm::vec3(a.pos) + glm::vec3(0.5f),
|
||||
glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f),
|
||||
glm::vec4(1, 1, 1, 1)
|
||||
);
|
||||
}
|
||||
|
||||
lines.pushLine(
|
||||
glm::vec3(a.pos) + glm::vec3(0.5f),
|
||||
glm::vec3(b.pos) + glm::vec3(0.5f),
|
||||
glm::vec4(1, 0, 1, 1)
|
||||
);
|
||||
|
||||
lines.pushLine(
|
||||
glm::vec3(b.pos) + glm::vec3(0.5f),
|
||||
glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f),
|
||||
glm::vec4(1, 1, 1, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugLinesRenderer::drawBorders(
|
||||
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
|
||||
) {
|
||||
int ww = ex - sx;
|
||||
@ -37,7 +73,7 @@ void GuidesRenderer::drawBorders(
|
||||
batch.flush();
|
||||
}
|
||||
|
||||
void GuidesRenderer::drawCoordSystem(
|
||||
void DebugLinesRenderer::drawCoordSystem(
|
||||
LineBatch& batch, const DrawContext& pctx, float length
|
||||
) {
|
||||
auto ctx = pctx.sub();
|
||||
@ -55,14 +91,22 @@ void GuidesRenderer::drawCoordSystem(
|
||||
batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
void GuidesRenderer::renderDebugLines(
|
||||
const DrawContext& pctx,
|
||||
|
||||
void DebugLinesRenderer::render(
|
||||
DrawContext& pctx,
|
||||
const Camera& camera,
|
||||
LineBatch& batch,
|
||||
LinesRenderer& renderer,
|
||||
LineBatch& linesBatch,
|
||||
Shader& linesShader,
|
||||
bool showChunkBorders
|
||||
) {
|
||||
DrawContext ctx = pctx.sub(&batch);
|
||||
// In-world lines
|
||||
if (showPaths) {
|
||||
for (const auto& [_, agent] : level.pathfinding->getAgents()) {
|
||||
draw_route(renderer, agent);
|
||||
}
|
||||
}
|
||||
DrawContext ctx = pctx.sub(&linesBatch);
|
||||
const auto& viewport = ctx.getViewport();
|
||||
|
||||
ctx.setDepthTest(true);
|
||||
@ -78,7 +122,7 @@ void GuidesRenderer::renderDebugLines(
|
||||
int cz = floordiv(static_cast<int>(coord.z), CHUNK_D);
|
||||
|
||||
drawBorders(
|
||||
batch,
|
||||
linesBatch,
|
||||
cx * CHUNK_W,
|
||||
0,
|
||||
cz * CHUNK_D,
|
||||
@ -103,5 +147,5 @@ void GuidesRenderer::renderDebugLines(
|
||||
) * model *
|
||||
glm::inverse(camera.rotation)
|
||||
);
|
||||
drawCoordSystem(batch, ctx, length);
|
||||
drawCoordSystem(linesBatch, ctx, length);
|
||||
}
|
||||
41
src/graphics/render/DebugLinesRenderer.hpp
Normal file
41
src/graphics/render/DebugLinesRenderer.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
class DrawContext;
|
||||
class Camera;
|
||||
class LineBatch;
|
||||
class LinesRenderer;
|
||||
class Shader;
|
||||
class Level;
|
||||
|
||||
class DebugLinesRenderer {
|
||||
public:
|
||||
static bool showPaths;
|
||||
|
||||
DebugLinesRenderer(const Level& level)
|
||||
: level(level) {};
|
||||
|
||||
/// @brief Render debug lines in the world
|
||||
/// @param ctx Draw context
|
||||
/// @param camera Camera used for rendering
|
||||
/// @param renderer Lines renderer used for rendering lines
|
||||
/// @param linesShader Shader used for rendering lines
|
||||
/// @param showChunkBorders Whether to show chunk borders
|
||||
void render(
|
||||
DrawContext& ctx,
|
||||
const Camera& camera,
|
||||
LinesRenderer& renderer,
|
||||
LineBatch& linesBatch,
|
||||
Shader& linesShader,
|
||||
bool showChunkBorders
|
||||
);
|
||||
private:
|
||||
const Level& level;
|
||||
|
||||
void drawBorders(
|
||||
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
|
||||
);
|
||||
void drawCoordSystem(
|
||||
LineBatch& batch, const DrawContext& pctx, float length
|
||||
);
|
||||
|
||||
};
|
||||
@ -15,6 +15,7 @@
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "window/Camera.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
Emitter::Emitter(
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class LineBatch;
|
||||
class DrawContext;
|
||||
class Camera;
|
||||
class Shader;
|
||||
|
||||
class GuidesRenderer {
|
||||
public:
|
||||
void drawBorders(
|
||||
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
|
||||
);
|
||||
void drawCoordSystem(
|
||||
LineBatch& batch, const DrawContext& pctx, float length
|
||||
);
|
||||
|
||||
/// @brief Render all debug lines (chunks borders, coord system guides)
|
||||
/// @param context graphics context
|
||||
/// @param camera active camera
|
||||
/// @param linesShader shader used
|
||||
void renderDebugLines(
|
||||
const DrawContext& context,
|
||||
const Camera& camera,
|
||||
LineBatch& batch,
|
||||
Shader& linesShader,
|
||||
bool showChunkBorders
|
||||
);
|
||||
};
|
||||
14
src/graphics/render/LinesRenderer.cpp
Normal file
14
src/graphics/render/LinesRenderer.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include "LinesRenderer.hpp"
|
||||
|
||||
#include "graphics/core/LineBatch.hpp"
|
||||
|
||||
void LinesRenderer::draw(LineBatch& batch) {
|
||||
for (const auto& line : queue) {
|
||||
batch.line(line.a, line.b, line.color);
|
||||
}
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
void LinesRenderer::pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color) {
|
||||
queue.push_back({a, b, color});
|
||||
}
|
||||
22
src/graphics/render/LinesRenderer.hpp
Normal file
22
src/graphics/render/LinesRenderer.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/vec4.hpp>
|
||||
#include <vector>
|
||||
|
||||
class LineBatch;
|
||||
|
||||
class LinesRenderer {
|
||||
public:
|
||||
struct Line {
|
||||
glm::vec3 a;
|
||||
glm::vec3 b;
|
||||
glm::vec4 color;
|
||||
};
|
||||
|
||||
void draw(LineBatch& batch);
|
||||
|
||||
void pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color);
|
||||
private:
|
||||
std::vector<Line> queue;
|
||||
};
|
||||
@ -27,6 +27,7 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/LevelEvents.hpp"
|
||||
@ -42,7 +43,7 @@
|
||||
#include "graphics/core/Shader.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
#include "graphics/core/ShadowMap.hpp"
|
||||
#include "graphics/core/Shadows.hpp"
|
||||
#include "graphics/core/GBuffer.hpp"
|
||||
#include "BlockWrapsRenderer.hpp"
|
||||
#include "ParticlesRenderer.hpp"
|
||||
@ -51,7 +52,8 @@
|
||||
#include "NamedSkeletons.hpp"
|
||||
#include "TextsRenderer.hpp"
|
||||
#include "ChunksRenderer.hpp"
|
||||
#include "GuidesRenderer.hpp"
|
||||
#include "LinesRenderer.hpp"
|
||||
#include "DebugLinesRenderer.hpp"
|
||||
#include "ModelBatch.hpp"
|
||||
#include "Skybox.hpp"
|
||||
#include "Emitter.hpp"
|
||||
@ -61,8 +63,6 @@ using namespace advanced_pipeline;
|
||||
|
||||
inline constexpr size_t BATCH3D_CAPACITY = 4096;
|
||||
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
|
||||
inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0;
|
||||
inline constexpr int MIN_SHADOW_MAP_RES = 512;
|
||||
|
||||
bool WorldRenderer::showChunkBorders = false;
|
||||
bool WorldRenderer::showEntitiesDebug = false;
|
||||
@ -80,8 +80,7 @@ WorldRenderer::WorldRenderer(
|
||||
modelBatch(std::make_unique<ModelBatch>(
|
||||
MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings()
|
||||
)),
|
||||
guides(std::make_unique<GuidesRenderer>()),
|
||||
chunks(std::make_unique<ChunksRenderer>(
|
||||
chunksRenderer(std::make_unique<ChunksRenderer>(
|
||||
&level,
|
||||
*player.chunks,
|
||||
assets,
|
||||
@ -102,7 +101,7 @@ WorldRenderer::WorldRenderer(
|
||||
auto& settings = engine.getSettings();
|
||||
level.events->listen(
|
||||
LevelEventType::CHUNK_HIDDEN,
|
||||
[this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); }
|
||||
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
|
||||
);
|
||||
auto assets = engine.getAssets();
|
||||
skybox = std::make_unique<Skybox>(
|
||||
@ -118,10 +117,26 @@ WorldRenderer::WorldRenderer(
|
||||
hands = std::make_unique<HandsRenderer>(
|
||||
*assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig)
|
||||
);
|
||||
lines = std::make_unique<LinesRenderer>();
|
||||
shadowMapping = std::make_unique<Shadows>(level);
|
||||
debugLines = std::make_unique<DebugLinesRenderer>(level);
|
||||
}
|
||||
|
||||
WorldRenderer::~WorldRenderer() = default;
|
||||
|
||||
static void setup_weather(Shader& shader, const Weather& weather) {
|
||||
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
|
||||
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
|
||||
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
|
||||
}
|
||||
|
||||
static void setup_camera(Shader& shader, const Camera& camera) {
|
||||
shader.uniformMatrix("u_model", glm::mat4(1.0f));
|
||||
shader.uniformMatrix("u_proj", camera.getProjection());
|
||||
shader.uniformMatrix("u_view", camera.getView());
|
||||
shader.uniform3f("u_cameraPos", camera.position);
|
||||
}
|
||||
|
||||
void WorldRenderer::setupWorldShader(
|
||||
Shader& shader,
|
||||
const Camera& camera,
|
||||
@ -129,45 +144,20 @@ void WorldRenderer::setupWorldShader(
|
||||
float fogFactor
|
||||
) {
|
||||
shader.use();
|
||||
shader.uniformMatrix("u_model", glm::mat4(1.0f));
|
||||
shader.uniformMatrix("u_proj", camera.getProjection());
|
||||
shader.uniformMatrix("u_view", camera.getView());
|
||||
|
||||
setup_camera(shader, camera);
|
||||
setup_weather(shader, weather);
|
||||
shadowMapping->setup(shader, weather);
|
||||
|
||||
shader.uniform1f("u_timer", timer);
|
||||
shader.uniform1f("u_gamma", settings.graphics.gamma.get());
|
||||
shader.uniform1f("u_fogFactor", fogFactor);
|
||||
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
|
||||
shader.uniform1i("u_debugLights", lightsDebug);
|
||||
shader.uniform1i("u_debugNormals", false);
|
||||
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
|
||||
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
|
||||
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
|
||||
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
|
||||
shader.uniform2f("u_lightDir", skybox->getLightDir());
|
||||
shader.uniform3f("u_cameraPos", camera.position);
|
||||
shader.uniform1i("u_skybox", 1);
|
||||
shader.uniform1i("u_enableShadows", shadows);
|
||||
|
||||
if (shadows) {
|
||||
const auto& worldInfo = level.getWorld()->getInfo();
|
||||
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
|
||||
shader.uniform1i("u_screen", 0);
|
||||
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
|
||||
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
|
||||
shader.uniform3f("u_sunDir", shadowCamera.front);
|
||||
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
|
||||
shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable
|
||||
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
|
||||
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
|
||||
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
|
||||
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
|
||||
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
|
||||
|
||||
glActiveTexture(TEXTURE_MAIN);
|
||||
}
|
||||
shader.uniform1i("u_skybox", TARGET_SKYBOX);
|
||||
|
||||
auto indices = level.content.getIndices();
|
||||
// Light emission when an emissive item is chosen
|
||||
@ -186,7 +176,7 @@ void WorldRenderer::setupWorldShader(
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::renderLevel(
|
||||
void WorldRenderer::renderOpaque(
|
||||
const DrawContext& ctx,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
@ -215,7 +205,9 @@ void WorldRenderer::renderLevel(
|
||||
*modelBatch,
|
||||
culling ? frustumCulling.get() : nullptr,
|
||||
delta,
|
||||
pause
|
||||
pause,
|
||||
player.currentCamera.get() == player.fpCamera.get() ? player.getEntity()
|
||||
: 0
|
||||
);
|
||||
modelBatch->render();
|
||||
particles->render(camera, delta * !pause);
|
||||
@ -225,7 +217,7 @@ void WorldRenderer::renderLevel(
|
||||
|
||||
setupWorldShader(shader, camera, settings, fogFactor);
|
||||
|
||||
chunks->drawChunks(camera, shader);
|
||||
chunksRenderer->drawChunks(camera, shader);
|
||||
blockWraps->draw(ctx, player);
|
||||
|
||||
if (hudVisible) {
|
||||
@ -284,79 +276,7 @@ void WorldRenderer::renderLines(
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale
|
||||
) {
|
||||
auto& shadowsShader = assets.require<Shader>("shadows");
|
||||
|
||||
auto world = level.getWorld();
|
||||
const auto& worldInfo = world->getInfo();
|
||||
|
||||
const auto& settings = engine.getSettings();
|
||||
int resolution = shadowMap.getResolution();
|
||||
int quality = settings.graphics.shadowsQuality.get();
|
||||
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;
|
||||
float shadowMapSize = resolution * shadowMapScale;
|
||||
|
||||
glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f;
|
||||
glm::vec3 prevPos = shadowCamera.position;
|
||||
shadowCamera = Camera(
|
||||
glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos,
|
||||
shadowMapSize
|
||||
);
|
||||
shadowCamera.near = 0.1f;
|
||||
shadowCamera.far = 1000.0f;
|
||||
shadowCamera.perspective = false;
|
||||
shadowCamera.setAspectRatio(1.0f);
|
||||
|
||||
float t = worldInfo.daytime - 0.25f;
|
||||
if (t < 0.0f) {
|
||||
t += 1.0f;
|
||||
}
|
||||
t = fmod(t, 0.5f);
|
||||
|
||||
float sunCycleStep = 1.0f / 500.0f;
|
||||
float sunAngle = glm::radians(
|
||||
90.0f -
|
||||
((static_cast<int>(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f
|
||||
);
|
||||
float sunAltitude = glm::pi<float>() * 0.25f;
|
||||
shadowCamera.rotate(
|
||||
-glm::cos(sunAngle + glm::pi<float>() * 0.5f) * sunAltitude,
|
||||
sunAngle - glm::pi<float>() * 0.5f,
|
||||
glm::radians(0.0f)
|
||||
);
|
||||
|
||||
shadowCamera.position -= shadowCamera.front * 500.0f;
|
||||
shadowCamera.position += shadowCamera.up * 0.0f;
|
||||
shadowCamera.position += camera.front * 0.0f;
|
||||
|
||||
auto view = shadowCamera.getView();
|
||||
|
||||
auto currentPos = shadowCamera.position;
|
||||
auto topRight = shadowCamera.right + shadowCamera.up;
|
||||
auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f);
|
||||
|
||||
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
|
||||
|
||||
{
|
||||
auto sctx = pctx.sub();
|
||||
sctx.setDepthTest(true);
|
||||
sctx.setCullFace(true);
|
||||
sctx.setViewport({resolution, resolution});
|
||||
shadowMap.bind();
|
||||
setupWorldShader(shadowsShader, shadowCamera, settings, 0.0f);
|
||||
chunks->drawChunksShadowsPass(shadowCamera, shadowsShader, camera);
|
||||
shadowMap.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldRenderer::draw(
|
||||
void WorldRenderer::renderFrame(
|
||||
const DrawContext& pctx,
|
||||
Camera& camera,
|
||||
bool hudVisible,
|
||||
@ -365,6 +285,9 @@ void WorldRenderer::draw(
|
||||
PostProcessing& postProcessing
|
||||
) {
|
||||
// TODO: REFACTOR WHOLE RENDER ENGINE
|
||||
|
||||
auto projView = camera.getProjView();
|
||||
|
||||
float delta = uiDelta * !pause;
|
||||
timer += delta;
|
||||
weather.update(delta);
|
||||
@ -380,22 +303,17 @@ void WorldRenderer::draw(
|
||||
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
|
||||
const auto& settings = engine.getSettings();
|
||||
|
||||
Shader* affectedShaders[] {
|
||||
&mainShader, &entityShader, &translucentShader, &deferredShader
|
||||
};
|
||||
|
||||
gbufferPipeline = settings.graphics.advancedRender.get();
|
||||
int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline;
|
||||
int resolution = MIN_SHADOW_MAP_RES << shadowsQuality;
|
||||
if (shadowsQuality > 0 && !shadows) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
shadows = true;
|
||||
} else if (shadowsQuality == 0 && shadows) {
|
||||
shadowMap.reset();
|
||||
wideShadowMap.reset();
|
||||
shadows = false;
|
||||
}
|
||||
shadowMapping->setQuality(shadowsQuality);
|
||||
|
||||
CompileTimeShaderSettings currentSettings {
|
||||
gbufferPipeline,
|
||||
shadows,
|
||||
shadowsQuality != 0,
|
||||
settings.graphics.ssao.get() && gbufferPipeline
|
||||
};
|
||||
if (
|
||||
@ -403,19 +321,15 @@ void WorldRenderer::draw(
|
||||
prevCTShaderSettings.shadows != currentSettings.shadows ||
|
||||
prevCTShaderSettings.ssao != currentSettings.ssao
|
||||
) {
|
||||
Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows);
|
||||
Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao);
|
||||
Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender);
|
||||
mainShader.recompile();
|
||||
entityShader.recompile();
|
||||
deferredShader.recompile();
|
||||
translucentShader.recompile();
|
||||
prevCTShaderSettings = currentSettings;
|
||||
}
|
||||
std::vector<std::string> defines;
|
||||
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
|
||||
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
|
||||
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
|
||||
|
||||
if (shadows && shadowMap->getResolution() != resolution) {
|
||||
shadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
||||
for (auto shader : affectedShaders) {
|
||||
shader->recompile(defines);
|
||||
}
|
||||
prevCTShaderSettings = currentSettings;
|
||||
}
|
||||
|
||||
const auto& worldInfo = world->getInfo();
|
||||
@ -426,38 +340,24 @@ void WorldRenderer::draw(
|
||||
|
||||
skybox->refresh(pctx, worldInfo.daytime, mie, 4);
|
||||
|
||||
chunks->update();
|
||||
chunksRenderer->update();
|
||||
|
||||
static int frameid = 0;
|
||||
if (shadows) {
|
||||
if (frameid % 2 == 0) {
|
||||
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f);
|
||||
} else {
|
||||
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f);
|
||||
}
|
||||
}
|
||||
frameid++;
|
||||
|
||||
auto& linesShader = assets.require<Shader>("lines");
|
||||
/* World render scope with diegetic HUD included */ {
|
||||
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
|
||||
auto& shader = assets.require<Shader>("shadows");
|
||||
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
|
||||
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
|
||||
});
|
||||
{
|
||||
DrawContext wctx = pctx.sub();
|
||||
postProcessing.use(wctx, gbufferPipeline);
|
||||
|
||||
display::clearDepth();
|
||||
|
||||
/* Actually world render with depth buffer on */ {
|
||||
/* Main opaque pass (GBuffer pass) */ {
|
||||
DrawContext ctx = wctx.sub();
|
||||
ctx.setDepthTest(true);
|
||||
ctx.setCullFace(true);
|
||||
renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible);
|
||||
// Debug lines
|
||||
if (hudVisible) {
|
||||
if (debug) {
|
||||
guides->renderDebugLines(
|
||||
ctx, camera, *lineBatch, linesShader, showChunkBorders
|
||||
);
|
||||
}
|
||||
}
|
||||
renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible);
|
||||
}
|
||||
texts->render(pctx, camera, settings, hudVisible, true);
|
||||
}
|
||||
@ -478,19 +378,33 @@ void WorldRenderer::draw(
|
||||
} else {
|
||||
postProcessing.getFramebuffer()->bind();
|
||||
}
|
||||
// Drawing background sky plane
|
||||
|
||||
// Background sky plane
|
||||
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
|
||||
|
||||
auto& linesShader = assets.require<Shader>("lines");
|
||||
linesShader.use();
|
||||
if (debug && hudVisible) {
|
||||
debugLines->render(
|
||||
ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders
|
||||
);
|
||||
}
|
||||
linesShader.uniformMatrix("u_projview", projView);
|
||||
lines->draw(*lineBatch);
|
||||
lineBatch->flush();
|
||||
|
||||
// Translucent blocks
|
||||
{
|
||||
auto sctx = ctx.sub();
|
||||
sctx.setCullFace(true);
|
||||
skybox->bind();
|
||||
translucentShader.use();
|
||||
setupWorldShader(translucentShader, camera, settings, fogFactor);
|
||||
chunks->drawSortedMeshes(camera, translucentShader);
|
||||
chunksRenderer->drawSortedMeshes(camera, translucentShader);
|
||||
skybox->unbind();
|
||||
}
|
||||
|
||||
// Weather effects
|
||||
entityShader.use();
|
||||
setupWorldShader(entityShader, camera, settings, fogFactor);
|
||||
|
||||
@ -582,7 +496,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
|
||||
}
|
||||
|
||||
void WorldRenderer::clear() {
|
||||
chunks->clear();
|
||||
chunksRenderer->clear();
|
||||
}
|
||||
|
||||
void WorldRenderer::setDebug(bool flag) {
|
||||
|
||||
@ -22,7 +22,7 @@ class BlockWrapsRenderer;
|
||||
class PrecipitationRenderer;
|
||||
class HandsRenderer;
|
||||
class NamedSkeletons;
|
||||
class GuidesRenderer;
|
||||
class LinesRenderer;
|
||||
class TextsRenderer;
|
||||
class Shader;
|
||||
class Frustum;
|
||||
@ -33,8 +33,9 @@ class PostProcessing;
|
||||
class DrawContext;
|
||||
class ModelBatch;
|
||||
class Assets;
|
||||
class ShadowMap;
|
||||
class Shadows;
|
||||
class GBuffer;
|
||||
class DebugLinesRenderer;
|
||||
struct EngineSettings;
|
||||
|
||||
struct CompileTimeShaderSettings {
|
||||
@ -52,21 +53,17 @@ class WorldRenderer {
|
||||
std::unique_ptr<LineBatch> lineBatch;
|
||||
std::unique_ptr<Batch3D> batch3d;
|
||||
std::unique_ptr<ModelBatch> modelBatch;
|
||||
std::unique_ptr<GuidesRenderer> guides;
|
||||
std::unique_ptr<ChunksRenderer> chunks;
|
||||
std::unique_ptr<ChunksRenderer> chunksRenderer;
|
||||
std::unique_ptr<HandsRenderer> hands;
|
||||
std::unique_ptr<Skybox> skybox;
|
||||
std::unique_ptr<ShadowMap> shadowMap;
|
||||
std::unique_ptr<ShadowMap> wideShadowMap;
|
||||
std::unique_ptr<Shadows> shadowMapping;
|
||||
std::unique_ptr<DebugLinesRenderer> debugLines;
|
||||
Weather weather {};
|
||||
Camera shadowCamera;
|
||||
Camera wideShadowCamera;
|
||||
|
||||
float timer = 0.0f;
|
||||
bool debug = false;
|
||||
bool lightsDebug = false;
|
||||
bool gbufferPipeline = false;
|
||||
bool shadows = false;
|
||||
|
||||
CompileTimeShaderSettings prevCTShaderSettings {};
|
||||
|
||||
@ -89,12 +86,17 @@ class WorldRenderer {
|
||||
float fogFactor
|
||||
);
|
||||
|
||||
void generateShadowsMap(
|
||||
const Camera& camera,
|
||||
const DrawContext& pctx,
|
||||
ShadowMap& shadowMap,
|
||||
Camera& shadowCamera,
|
||||
float scale
|
||||
/// @brief Render opaque pass
|
||||
/// @param context graphics context
|
||||
/// @param camera active camera
|
||||
/// @param settings engine settings
|
||||
void renderOpaque(
|
||||
const DrawContext& context,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float delta,
|
||||
bool pause,
|
||||
bool hudVisible
|
||||
);
|
||||
public:
|
||||
std::unique_ptr<ParticlesRenderer> particles;
|
||||
@ -102,6 +104,7 @@ public:
|
||||
std::unique_ptr<BlockWrapsRenderer> blockWraps;
|
||||
std::unique_ptr<PrecipitationRenderer> precipitation;
|
||||
std::unique_ptr<NamedSkeletons> skeletons;
|
||||
std::unique_ptr<LinesRenderer> lines;
|
||||
|
||||
static bool showChunkBorders;
|
||||
static bool showEntitiesDebug;
|
||||
@ -109,7 +112,7 @@ public:
|
||||
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
|
||||
~WorldRenderer();
|
||||
|
||||
void draw(
|
||||
void renderFrame(
|
||||
const DrawContext& context,
|
||||
Camera& camera,
|
||||
bool hudVisible,
|
||||
@ -118,19 +121,6 @@ public:
|
||||
PostProcessing& postProcessing
|
||||
);
|
||||
|
||||
/// @brief Render level without diegetic interface
|
||||
/// @param context graphics context
|
||||
/// @param camera active camera
|
||||
/// @param settings engine settings
|
||||
void renderLevel(
|
||||
const DrawContext& context,
|
||||
const Camera& camera,
|
||||
const EngineSettings& settings,
|
||||
float delta,
|
||||
bool pause,
|
||||
bool hudVisible
|
||||
);
|
||||
|
||||
void clear();
|
||||
|
||||
void setDebug(bool flag);
|
||||
|
||||
@ -83,9 +83,13 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
||||
builder.add("language", &settings.ui.language);
|
||||
builder.add("world-preview-size", &settings.ui.worldPreviewSize);
|
||||
|
||||
builder.section("pathfinding");
|
||||
builder.add("steps-per-async-agent", &settings.pathfinding.stepsPerAsyncAgent);
|
||||
|
||||
builder.section("debug");
|
||||
builder.add("generator-test-mode", &settings.debug.generatorTestMode);
|
||||
builder.add("do-write-lights", &settings.debug.doWriteLights);
|
||||
builder.add("do-trace-shaders", &settings.debug.doTraceShaders);
|
||||
builder.add("enable-experimental", &settings.debug.enableExperimental);
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include "objects/Player.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "scripting/scripting.hpp"
|
||||
#include "lighting/Lighting.hpp"
|
||||
#include "settings.hpp"
|
||||
@ -69,6 +70,9 @@ LevelController::LevelController(
|
||||
}
|
||||
|
||||
void LevelController::update(float delta, bool pause) {
|
||||
level->pathfinding->performAllAsync(
|
||||
settings.pathfinding.stepsPerAsyncAgent.get()
|
||||
);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
continue;
|
||||
@ -91,7 +95,6 @@ void LevelController::update(float delta, bool pause) {
|
||||
if (!pause) {
|
||||
// update all objects that needed
|
||||
blocks->update(delta, settings.chunks.padding.get());
|
||||
level->entities->updatePhysics(delta);
|
||||
level->entities->update(delta);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include "items/ItemStack.hpp"
|
||||
#include "lighting/Lighting.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
@ -270,7 +271,6 @@ void PlayerController::update(float delta, const Input* inputEvents) {
|
||||
} else {
|
||||
resetKeyboard();
|
||||
}
|
||||
updatePlayer(delta);
|
||||
}
|
||||
|
||||
void PlayerController::postUpdate(
|
||||
@ -309,20 +309,7 @@ void PlayerController::updateKeyboard(const Input& inputEvents) {
|
||||
}
|
||||
|
||||
void PlayerController::resetKeyboard() {
|
||||
input.zoom = false;
|
||||
input.moveForward = false;
|
||||
input.moveBack = false;
|
||||
input.moveLeft = false;
|
||||
input.moveRight = false;
|
||||
input.sprint = false;
|
||||
input.shift = false;
|
||||
input.cheat = false;
|
||||
input.jump = false;
|
||||
input.delta = {};
|
||||
}
|
||||
|
||||
void PlayerController::updatePlayer(float delta) {
|
||||
player.updateInput(input, delta);
|
||||
input = {};
|
||||
}
|
||||
|
||||
static int determine_rotation(
|
||||
@ -338,7 +325,8 @@ static int determine_rotation(
|
||||
if (norm.z > 0.0f) return BLOCK_DIR_NORTH;
|
||||
if (norm.z < 0.0f) return BLOCK_DIR_SOUTH;
|
||||
} else if (name == "pane" || name == "stairs") {
|
||||
int verticalBit = (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0;
|
||||
int verticalBit =
|
||||
(name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0;
|
||||
if (abs(camDir.x) > abs(camDir.z)) {
|
||||
if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit;
|
||||
if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit;
|
||||
|
||||
@ -60,7 +60,6 @@ class PlayerController {
|
||||
|
||||
void updateKeyboard(const Input& inputEvents);
|
||||
void resetKeyboard();
|
||||
void updatePlayer(float delta);
|
||||
void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick);
|
||||
void updateInteraction(const Input& inputEvents, float delta);
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@ extern const luaL_Reg mat4lib[];
|
||||
extern const luaL_Reg networklib[];
|
||||
extern const luaL_Reg packlib[];
|
||||
extern const luaL_Reg particleslib[]; // gfx.particles
|
||||
extern const luaL_Reg pathfindinglib[];
|
||||
extern const luaL_Reg playerlib[];
|
||||
extern const luaL_Reg posteffectslib[]; // gfx.posteffects
|
||||
extern const luaL_Reg quatlib[];
|
||||
|
||||
@ -62,6 +62,15 @@ static int l_set_gravity_scale(lua::State* L) {
|
||||
static int l_is_vdamping(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
return lua::pushboolean(
|
||||
L, entity->getRigidbody().hitbox.verticalDamping > 0.0
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_vdamping(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
return lua::pushnumber(
|
||||
L, entity->getRigidbody().hitbox.verticalDamping
|
||||
);
|
||||
}
|
||||
@ -70,7 +79,11 @@ static int l_is_vdamping(lua::State* L) {
|
||||
|
||||
static int l_set_vdamping(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
entity->getRigidbody().hitbox.verticalDamping = lua::toboolean(L, 2);
|
||||
if (lua::isboolean(L, 2)) {
|
||||
entity->getRigidbody().hitbox.verticalDamping = lua::toboolean(L, 2);
|
||||
} else {
|
||||
entity->getRigidbody().hitbox.verticalDamping = lua::tonumber(L, 2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -144,6 +157,7 @@ const luaL_Reg rigidbodylib[] = {
|
||||
{"get_linear_damping", lua::wrap<l_get_linear_damping>},
|
||||
{"set_linear_damping", lua::wrap<l_set_linear_damping>},
|
||||
{"is_vdamping", lua::wrap<l_is_vdamping>},
|
||||
{"get_vdamping", lua::wrap<l_get_vdamping>},
|
||||
{"set_vdamping", lua::wrap<l_set_vdamping>},
|
||||
{"is_grounded", lua::wrap<l_is_grounded>},
|
||||
{"is_crouching", lua::wrap<l_is_crouching>},
|
||||
|
||||
@ -91,7 +91,7 @@ static int l_set_texture(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_index(lua::State* L) {
|
||||
if (auto skeleton= get_skeleton(L)) {
|
||||
if (auto skeleton = get_skeleton(L)) {
|
||||
if (auto bone = skeleton->config->find(lua::require_string(L, 2))) {
|
||||
return lua::pushinteger(L, bone->getIndex());
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
#include "engine/Engine.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Rigidbody.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/rigging.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include "frontend/hud.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "api_lua.hpp"
|
||||
|
||||
@ -31,6 +31,8 @@ static int l_mousecode(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_add_callback(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
std::string bindname = lua::require_string(L, 1);
|
||||
size_t pos = bindname.find(':');
|
||||
|
||||
@ -75,10 +77,14 @@ static int l_add_callback(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_mouse_pos(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
return lua::pushvec2(L, engine->getInput().getCursor().pos);
|
||||
}
|
||||
|
||||
static int l_get_bindings(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
const auto& bindings = engine->getInput().getBindings().getAll();
|
||||
lua::createtable(L, bindings.size(), 0);
|
||||
|
||||
@ -92,18 +98,24 @@ static int l_get_bindings(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_get_binding_text(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
auto bindname = lua::require_string(L, 1);
|
||||
const auto& bind = engine->getInput().getBindings().require(bindname);
|
||||
return lua::pushstring(L, bind.text());
|
||||
}
|
||||
|
||||
static int l_is_active(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
auto bindname = lua::require_string(L, 1);
|
||||
auto& bind = engine->getInput().getBindings().require(bindname);
|
||||
return lua::pushboolean(L, bind.active());
|
||||
}
|
||||
|
||||
static int l_is_pressed(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
std::string code = lua::require_string(L, 1);
|
||||
size_t sep = code.find(':');
|
||||
if (sep == std::string::npos) {
|
||||
@ -136,6 +148,8 @@ static void reset_pack_bindings(const io::path& packFolder) {
|
||||
}
|
||||
|
||||
static int l_reset_bindings(lua::State*) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
reset_pack_bindings("res:");
|
||||
for (const auto& pack : content_control->getContentPacks()) {
|
||||
reset_pack_bindings(pack.folder);
|
||||
@ -144,6 +158,8 @@ static int l_reset_bindings(lua::State*) {
|
||||
}
|
||||
|
||||
static int l_set_enabled(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
std::string bindname = lua::require_string(L, 1);
|
||||
bool enabled = lua::toboolean(L, 2);
|
||||
engine->getInput().getBindings().require(bindname).enabled = enabled;
|
||||
@ -161,4 +177,5 @@ const luaL_Reg inputlib[] = {
|
||||
{"is_pressed", lua::wrap<l_is_pressed>},
|
||||
{"reset_bindings", lua::wrap<l_reset_bindings>},
|
||||
{"set_enabled", lua::wrap<l_set_enabled>},
|
||||
{NULL, NULL}};
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
130
src/logic/scripting/lua/libs/libpathfinding.cpp
Normal file
130
src/logic/scripting/lua/libs/libpathfinding.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
#include "api_lua.hpp"
|
||||
|
||||
#include "content/Content.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static voxels::Agent* get_agent(lua::State* L) {
|
||||
return level->pathfinding->getAgent(lua::tointeger(L, 1));
|
||||
}
|
||||
|
||||
static int l_create_agent(lua::State* L) {
|
||||
return lua::pushinteger(L, level->pathfinding->createAgent());
|
||||
}
|
||||
|
||||
static int l_remove_agent(lua::State* L) {
|
||||
int id = lua::tointeger(L, 1);
|
||||
return lua::pushboolean(L, level->pathfinding->removeAgent(id));
|
||||
}
|
||||
|
||||
static int l_set_enabled(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
agent->enabled = lua::toboolean(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_enabled(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
return lua::pushboolean(L, agent->enabled);
|
||||
}
|
||||
return lua::pushboolean(L, false);
|
||||
}
|
||||
|
||||
static int push_route(lua::State* L, const voxels::Route& route) {
|
||||
lua::createtable(L, route.nodes.size(), 1);
|
||||
for (int i = 0; i < route.nodes.size(); i++) {
|
||||
lua::pushvec3(L, route.nodes[i].pos);
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
lua::pushinteger(L, route.totalVisited);
|
||||
lua::setfield(L, "total_visited");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_make_route(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
auto start = lua::tovec3(L, 2);
|
||||
auto target = lua::tovec3(L, 3);
|
||||
agent->state = {};
|
||||
agent->start = glm::floor(start);
|
||||
agent->target = target;
|
||||
auto route = level->pathfinding->perform(*agent);
|
||||
if (!route.found) {
|
||||
return 0;
|
||||
}
|
||||
return push_route(L, route);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_make_route_async(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
auto start = lua::tovec3(L, 2);
|
||||
auto target = lua::tovec3(L, 3);
|
||||
agent->state = {};
|
||||
agent->start = glm::floor(start);
|
||||
agent->target = target;
|
||||
level->pathfinding->perform(*agent, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_pull_route(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
auto& route = agent->route;
|
||||
if (!agent->state.finished) {
|
||||
return 0;
|
||||
}
|
||||
if (!route.found && !agent->mayBeIncomplete) {
|
||||
return lua::createtable(L, 0, 0);
|
||||
}
|
||||
return push_route(L, route);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_max_visited_blocks(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
agent->maxVisitedBlocks = lua::tointeger(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_jump_height(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
agent->jumpHeight = lua::tointeger(L, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_avoid_tag(lua::State* L) {
|
||||
if (auto agent = get_agent(L)) {
|
||||
int index =
|
||||
content->getTagIndex(std::string(lua::require_lstring(L, 2)));
|
||||
if (index != -1) {
|
||||
int cost = lua::tonumber(L, 3);
|
||||
if (cost == 0) {
|
||||
cost = 10;
|
||||
}
|
||||
agent->avoidTags.insert({index, cost});
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg pathfindinglib[] = {
|
||||
{"create_agent", lua::wrap<l_create_agent>},
|
||||
{"remove_agent", lua::wrap<l_remove_agent>},
|
||||
{"set_enabled", lua::wrap<l_set_enabled>},
|
||||
{"is_enabled", lua::wrap<l_is_enabled>},
|
||||
{"make_route", lua::wrap<l_make_route>},
|
||||
{"make_route_async", lua::wrap<l_make_route_async>},
|
||||
{"pull_route", lua::wrap<l_pull_route>},
|
||||
{"set_max_visited", lua::wrap<l_set_max_visited_blocks>},
|
||||
{"set_jump_height", lua::wrap<l_set_jump_height>},
|
||||
{"avoid_tag", lua::wrap<l_avoid_tag>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
@ -5,6 +5,7 @@
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
@ -15,10 +15,24 @@ inline T angle(glm::vec<2, T> vec) {
|
||||
return val;
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_mix(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 3, 4);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
auto t = lua::tonumber(L, 3);
|
||||
|
||||
if (argc == 3) {
|
||||
return lua::pushvec(L, a * (1.0 - t) + b * t);
|
||||
} else {
|
||||
return lua::setvec(L, 4, a * (1.0 - t) + b * t);
|
||||
}
|
||||
}
|
||||
|
||||
template <int n, template <class> class Op>
|
||||
static int l_binop(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 2, 3);
|
||||
auto a = lua::tovec<n>(L, 1);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
|
||||
if (lua::isnumber(L, 2)) { // scalar second operand overload
|
||||
auto b = lua::tonumber(L, 2);
|
||||
@ -31,10 +45,10 @@ static int l_binop(lua::State* L) {
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
return lua::setvec(L, 3, op(a, glm::vec<n, float>(b)));
|
||||
return lua::setvec(L, 3, op(a, glm::vec<n, number_t>(b)));
|
||||
}
|
||||
} else {
|
||||
auto b = lua::tovec<n>(L, 2);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
Op op;
|
||||
if (argc == 2) {
|
||||
lua::createtable(L, n, 0);
|
||||
@ -49,10 +63,10 @@ static int l_binop(lua::State* L) {
|
||||
}
|
||||
}
|
||||
|
||||
template <int n, glm::vec<n, float> (*func)(const glm::vec<n, float>&)>
|
||||
template <int n, glm::vec<n, number_t> (*func)(const glm::vec<n, number_t>&)>
|
||||
static int l_unaryop(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 1, 2);
|
||||
auto vec = func(lua::tovec<n>(L, 1));
|
||||
auto vec = func(lua::tovec<n, number_t>(L, 1));
|
||||
switch (argc) {
|
||||
case 1:
|
||||
lua::createtable(L, n, 0);
|
||||
@ -67,17 +81,25 @@ static int l_unaryop(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <int n, float (*func)(const glm::vec<n, float>&)>
|
||||
template <int n, number_t (*func)(const glm::vec<n, number_t>&)>
|
||||
static int l_scalar_op(lua::State* L) {
|
||||
lua::check_argc(L, 1);
|
||||
auto vec = lua::tovec<n>(L, 1);
|
||||
auto vec = lua::tovec<n, number_t>(L, 1);
|
||||
return lua::pushnumber(L, func(vec));
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_distance(lua::State* L) {
|
||||
lua::check_argc(L, 2);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
return lua::pushnumber(L,glm::distance(a, b));
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_pow(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 2, 3);
|
||||
auto a = lua::tovec<n>(L, 1);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
|
||||
if (lua::isnumber(L, 2)) {
|
||||
auto b = lua::tonumber(L, 2);
|
||||
@ -89,10 +111,10 @@ static int l_pow(lua::State* L) {
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
return lua::setvec(L, 3, pow(a, glm::vec<n, float>(b)));
|
||||
return lua::setvec(L, 3, pow(a, glm::vec<n, number_t>(b)));
|
||||
}
|
||||
} else {
|
||||
auto b = lua::tovec<n>(L, 2);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
if (argc == 2) {
|
||||
lua::createtable(L, n, 0);
|
||||
for (uint i = 0; i < n; i++) {
|
||||
@ -109,15 +131,15 @@ static int l_pow(lua::State* L) {
|
||||
template <int n>
|
||||
static int l_dot(lua::State* L) {
|
||||
lua::check_argc(L, 2);
|
||||
const auto& a = lua::tovec<n>(L, 1);
|
||||
const auto& b = lua::tovec<n>(L, 2);
|
||||
auto a = lua::tovec<n, number_t>(L, 1);
|
||||
auto b = lua::tovec<n, number_t>(L, 2);
|
||||
return lua::pushnumber(L, glm::dot(a, b));
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_inverse(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 1, 2);
|
||||
auto vec = lua::tovec<n>(L, 1);
|
||||
auto vec = lua::tovec<n, number_t>(L, 1);
|
||||
switch (argc) {
|
||||
case 1:
|
||||
lua::createtable(L, n, 0);
|
||||
@ -141,7 +163,7 @@ static int l_spherical_rand(lua::State* L) {
|
||||
return lua::setvec(
|
||||
L,
|
||||
2,
|
||||
glm::sphericalRand(static_cast<float>(lua::tonumber(L, 1)))
|
||||
glm::sphericalRand(lua::tonumber(L, 1))
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
@ -161,10 +183,22 @@ static int l_vec2_angle(lua::State* L) {
|
||||
}
|
||||
}
|
||||
|
||||
static int l_vec2_rotate(lua::State* L) {
|
||||
uint argc = lua::check_argc(L, 2, 3);
|
||||
auto vec = lua::tovec<2, number_t>(L, 1);
|
||||
auto angle = glm::radians(lua::tonumber(L, 2));
|
||||
|
||||
if (argc == 2) {
|
||||
return lua::pushvec(L, glm::rotate(vec, angle));
|
||||
} else {
|
||||
return lua::setvec(L, 3, glm::rotate(vec, angle));
|
||||
}
|
||||
}
|
||||
|
||||
template <int n>
|
||||
static int l_tostring(lua::State* L) {
|
||||
lua::check_argc(L, 1);
|
||||
auto vec = lua::tovec<n>(L, 1);
|
||||
auto vec = lua::tovec<n, number_t>(L, 1);
|
||||
std::stringstream ss;
|
||||
ss << "vec" << std::to_string(n) << "{";
|
||||
for (int i = 0; i < n; i++) {
|
||||
@ -182,6 +216,7 @@ const luaL_Reg vec2lib[] = {
|
||||
{"sub", lua::wrap<l_binop<2, std::minus>>},
|
||||
{"mul", lua::wrap<l_binop<2, std::multiplies>>},
|
||||
{"div", lua::wrap<l_binop<2, std::divides>>},
|
||||
{"distance", lua::wrap<l_distance<2>>},
|
||||
{"normalize", lua::wrap<l_unaryop<2, glm::normalize>>},
|
||||
{"length", lua::wrap<l_scalar_op<2, glm::length>>},
|
||||
{"tostring", lua::wrap<l_tostring<2>>},
|
||||
@ -191,6 +226,8 @@ const luaL_Reg vec2lib[] = {
|
||||
{"pow", lua::wrap<l_pow<2>>},
|
||||
{"dot", lua::wrap<l_dot<2>>},
|
||||
{"angle", lua::wrap<l_vec2_angle>},
|
||||
{"mix", lua::wrap<l_mix<2>>},
|
||||
{"rotate", lua::wrap<l_vec2_rotate>},
|
||||
{NULL, NULL}};
|
||||
|
||||
const luaL_Reg vec3lib[] = {
|
||||
@ -198,6 +235,7 @@ const luaL_Reg vec3lib[] = {
|
||||
{"sub", lua::wrap<l_binop<3, std::minus>>},
|
||||
{"mul", lua::wrap<l_binop<3, std::multiplies>>},
|
||||
{"div", lua::wrap<l_binop<3, std::divides>>},
|
||||
{"distance", lua::wrap<l_distance<3>>},
|
||||
{"normalize", lua::wrap<l_unaryop<3, glm::normalize>>},
|
||||
{"length", lua::wrap<l_scalar_op<3, glm::length>>},
|
||||
{"tostring", lua::wrap<l_tostring<3>>},
|
||||
@ -207,6 +245,7 @@ const luaL_Reg vec3lib[] = {
|
||||
{"pow", lua::wrap<l_pow<3>>},
|
||||
{"dot", lua::wrap<l_dot<3>>},
|
||||
{"spherical_rand", lua::wrap<l_spherical_rand>},
|
||||
{"mix", lua::wrap<l_mix<3>>},
|
||||
{NULL, NULL}};
|
||||
|
||||
const luaL_Reg vec4lib[] = {
|
||||
@ -214,6 +253,7 @@ const luaL_Reg vec4lib[] = {
|
||||
{"sub", lua::wrap<l_binop<4, std::minus>>},
|
||||
{"mul", lua::wrap<l_binop<4, std::multiplies>>},
|
||||
{"div", lua::wrap<l_binop<4, std::divides>>},
|
||||
{"distance", lua::wrap<l_distance<4>>},
|
||||
{"normalize", lua::wrap<l_unaryop<4, glm::normalize>>},
|
||||
{"length", lua::wrap<l_scalar_op<4, glm::length>>},
|
||||
{"tostring", lua::wrap<l_tostring<4>>},
|
||||
@ -222,4 +262,5 @@ const luaL_Reg vec4lib[] = {
|
||||
{"inverse", lua::wrap<l_inverse<4>>},
|
||||
{"pow", lua::wrap<l_pow<4>>},
|
||||
{"dot", lua::wrap<l_dot<4>>},
|
||||
{"mix", lua::wrap<l_mix<4>>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@ -72,6 +72,7 @@ static void create_libs(State* L, StateType stateType) {
|
||||
openlib(L, "input", inputlib);
|
||||
openlib(L, "inventory", inventorylib);
|
||||
openlib(L, "network", networklib);
|
||||
openlib(L, "pathfinding", pathfindinglib);
|
||||
openlib(L, "player", playerlib);
|
||||
openlib(L, "time", timelib);
|
||||
openlib(L, "world", worldlib);
|
||||
|
||||
@ -48,8 +48,8 @@ namespace lua {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <int n>
|
||||
inline int pushvec(lua::State* L, const glm::vec<n, float>& vec) {
|
||||
template <int n, typename T = float>
|
||||
inline int pushvec(lua::State* L, const glm::vec<n, T>& vec) {
|
||||
createtable(L, n, 0);
|
||||
for (int i = 0; i < n; i++) {
|
||||
pushnumber(L, vec[i]);
|
||||
@ -161,8 +161,8 @@ namespace lua {
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
template <int n>
|
||||
inline int setvec(lua::State* L, int idx, glm::vec<n, float> vec) {
|
||||
template <int n, typename T = float>
|
||||
inline int setvec(lua::State* L, int idx, glm::vec<n, T> vec) {
|
||||
pushvalue(L, idx);
|
||||
for (int i = 0; i < n; i++) {
|
||||
pushnumber(L, vec[i]);
|
||||
@ -305,15 +305,15 @@ namespace lua {
|
||||
setglobal(L, name);
|
||||
}
|
||||
|
||||
template <int n>
|
||||
inline glm::vec<n, float> tovec(lua::State* L, int idx) {
|
||||
template <int n, typename T = float>
|
||||
inline glm::vec<n, T> tovec(lua::State* L, int idx) {
|
||||
pushvalue(L, idx);
|
||||
if (!istable(L, idx) || objlen(L, idx) < n) {
|
||||
throw std::runtime_error(
|
||||
"value must be an array of " + std::to_string(n) + " numbers"
|
||||
);
|
||||
}
|
||||
glm::vec<n, float> vec;
|
||||
glm::vec<n, T> vec;
|
||||
for (int i = 0; i < n; i++) {
|
||||
rawgeti(L, i + 1);
|
||||
vec[i] = tonumber(L, -1);
|
||||
|
||||
@ -19,8 +19,6 @@
|
||||
#include "lua/lua_engine.hpp"
|
||||
#include "lua/lua_custom_types.hpp"
|
||||
#include "maths/Heightmap.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "util/timeutil.hpp"
|
||||
@ -34,8 +32,6 @@ using namespace scripting;
|
||||
|
||||
static debug::Logger logger("scripting");
|
||||
|
||||
static inline const std::string STDCOMP = "stdcomp";
|
||||
|
||||
std::ostream* scripting::output_stream = &std::cout;
|
||||
std::ostream* scripting::error_stream = &std::cerr;
|
||||
Engine* scripting::engine = nullptr;
|
||||
@ -234,36 +230,6 @@ std::unique_ptr<Process> scripting::start_coroutine(const io::path& script) {
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] static scriptenv create_component_environment(
|
||||
const scriptenv& parent, int entityIdx, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
int id = lua::create_environment(L, *parent);
|
||||
|
||||
lua::pushvalue(L, entityIdx);
|
||||
|
||||
lua::pushenv(L, id);
|
||||
|
||||
lua::pushvalue(L, -1);
|
||||
lua::setfield(L, "this");
|
||||
|
||||
lua::pushvalue(L, -2);
|
||||
lua::setfield(L, "entity");
|
||||
|
||||
lua::pop(L);
|
||||
if (lua::getfield(L, "components")) {
|
||||
lua::pushenv(L, id);
|
||||
lua::setfield(L, name);
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508
|
||||
lua::remove_environment(L, *id);
|
||||
delete id;
|
||||
});
|
||||
}
|
||||
|
||||
void scripting::process_post_runnables() {
|
||||
auto L = lua::get_main_state();
|
||||
if (lua::getglobal(L, "__process_post_runnables")) {
|
||||
@ -599,244 +565,6 @@ bool scripting::on_item_break_block(
|
||||
);
|
||||
}
|
||||
|
||||
dv::value scripting::get_component_value(
|
||||
const scriptenv& env, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::getfield(L, name)) {
|
||||
return lua::tovalue(L, -1);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void scripting::on_entity_spawn(
|
||||
const EntityDef&,
|
||||
entityid_t eid,
|
||||
const std::vector<std::unique_ptr<UserComponent>>& components,
|
||||
const dv::value& args,
|
||||
const dv::value& saved
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::stackguard guard(L);
|
||||
lua::requireglobal(L, STDCOMP);
|
||||
if (lua::getfield(L, "new_Entity")) {
|
||||
lua::pushinteger(L, eid);
|
||||
lua::call(L, 1);
|
||||
}
|
||||
if (components.size() > 1) {
|
||||
for (size_t i = 0; i < components.size() - 1; i++) {
|
||||
lua::pushvalue(L, -1);
|
||||
}
|
||||
}
|
||||
for (auto& component : components) {
|
||||
auto compenv = create_component_environment(
|
||||
get_root_environment(), -1, component->name
|
||||
);
|
||||
lua::get_from(L, lua::CHUNKS_TABLE, component->name, true);
|
||||
lua::pushenv(L, *compenv);
|
||||
|
||||
if (args != nullptr) {
|
||||
std::string compfieldname = component->name;
|
||||
util::replaceAll(compfieldname, ":", "__");
|
||||
if (args.has(compfieldname)) {
|
||||
lua::pushvalue(L, args[compfieldname]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
lua::setfield(L, "ARGS");
|
||||
|
||||
if (saved == nullptr) {
|
||||
lua::createtable(L, 0, 0);
|
||||
} else {
|
||||
if (saved.has(component->name)) {
|
||||
lua::pushvalue(L, saved[component->name]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::setfield(L, "SAVED_DATA");
|
||||
|
||||
lua::setfenv(L);
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
|
||||
lua::pushenv(L, *compenv);
|
||||
auto& funcsset = component->funcsset;
|
||||
funcsset.on_grounded = lua::hasfield(L, "on_grounded");
|
||||
funcsset.on_fall = lua::hasfield(L, "on_fall");
|
||||
funcsset.on_despawn = lua::hasfield(L, "on_despawn");
|
||||
funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter");
|
||||
funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit");
|
||||
funcsset.on_save = lua::hasfield(L, "on_save");
|
||||
funcsset.on_aim_on = lua::hasfield(L, "on_aim_on");
|
||||
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
|
||||
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
|
||||
funcsset.on_used = lua::hasfield(L, "on_used");
|
||||
lua::pop(L, 2);
|
||||
|
||||
component->env = compenv;
|
||||
}
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const scriptenv& env,
|
||||
const std::string& name,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::hasfield(L, "__disabled")) {
|
||||
lua::pop(L);
|
||||
return;
|
||||
}
|
||||
if (lua::getfield(L, name)) {
|
||||
if (args) {
|
||||
lua::call_nothrow(L, args(L), 0);
|
||||
} else {
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const Entity& entity,
|
||||
const std::string& name,
|
||||
bool EntityFuncsSet::*flag,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
const auto& script = entity.getScripting();
|
||||
for (auto& component : script.components) {
|
||||
if (component->funcsset.*flag) {
|
||||
process_entity_callback(component->env, name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_entity_despawn(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr
|
||||
);
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, "stdcomp", "remove_Entity", true);
|
||||
lua::pushinteger(L, entity.getUID());
|
||||
lua::call(L, 1, 0);
|
||||
}
|
||||
|
||||
void scripting::on_entity_grounded(const Entity& entity, float force) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_grounded",
|
||||
&EntityFuncsSet::on_grounded,
|
||||
[force](auto L) { return lua::pushnumber(L, force); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_fall(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_fall", &EntityFuncsSet::on_fall, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_save(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_save", &EntityFuncsSet::on_save, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_enter(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_enter",
|
||||
&EntityFuncsSet::on_sensor_enter,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_exit(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_exit",
|
||||
&EntityFuncsSet::on_sensor_exit,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_on(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_on",
|
||||
&EntityFuncsSet::on_aim_on,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_off(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_off",
|
||||
&EntityFuncsSet::on_aim_off,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_attacked(
|
||||
const Entity& entity, Player* player, entityid_t attacker
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_attacked",
|
||||
&EntityFuncsSet::on_attacked,
|
||||
[player, attacker](auto L) {
|
||||
lua::pushinteger(L, attacker);
|
||||
lua::pushinteger(L, player->getId());
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_used(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_used",
|
||||
&EntityFuncsSet::on_used,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entities_update(int tps, int parts, int part) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "update", true);
|
||||
lua::pushinteger(L, tps);
|
||||
lua::pushinteger(L, parts);
|
||||
lua::pushinteger(L, part);
|
||||
lua::call_nothrow(L, 3, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_entities_render(float delta) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "render", true);
|
||||
lua::pushnumber(L, delta);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_ui_open(
|
||||
UiDocument* layout, std::vector<dv::value> args
|
||||
) {
|
||||
|
||||
@ -139,6 +139,7 @@ namespace scripting {
|
||||
void on_entity_fall(const Entity& entity);
|
||||
void on_entity_save(const Entity& entity);
|
||||
void on_entities_update(int tps, int parts, int part);
|
||||
void on_entities_physics_update(float delta);
|
||||
void on_entities_render(float delta);
|
||||
void on_sensor_enter(const Entity& entity, size_t index, entityid_t oid);
|
||||
void on_sensor_exit(const Entity& entity, size_t index, entityid_t oid);
|
||||
|
||||
296
src/logic/scripting/scripting_entities.cpp
Normal file
296
src/logic/scripting/scripting_entities.cpp
Normal file
@ -0,0 +1,296 @@
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include "lua/lua_engine.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
static inline const std::string STDCOMP = "stdcomp";
|
||||
|
||||
[[nodiscard]] static scriptenv create_component_environment(
|
||||
const scriptenv& parent, int entityIdx, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
int id = lua::create_environment(L, *parent);
|
||||
|
||||
lua::pushvalue(L, entityIdx);
|
||||
|
||||
lua::pushenv(L, id);
|
||||
|
||||
lua::pushvalue(L, -1);
|
||||
lua::setfield(L, "this");
|
||||
|
||||
lua::pushvalue(L, -2);
|
||||
lua::setfield(L, "entity");
|
||||
|
||||
lua::pop(L);
|
||||
if (lua::getfield(L, "components")) {
|
||||
lua::pushenv(L, id);
|
||||
lua::setfield(L, name);
|
||||
lua::pop(L);
|
||||
}
|
||||
lua::pop(L);
|
||||
|
||||
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508
|
||||
lua::remove_environment(L, *id);
|
||||
delete id;
|
||||
});
|
||||
}
|
||||
|
||||
dv::value scripting::get_component_value(
|
||||
const scriptenv& env, const std::string& name
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::getfield(L, name)) {
|
||||
return lua::tovalue(L, -1);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void create_component(
|
||||
lua::State* L,
|
||||
int entityIdx,
|
||||
UserComponent& component,
|
||||
const dv::value& args,
|
||||
const dv::value& saved
|
||||
) {
|
||||
lua::pushvalue(L, entityIdx);
|
||||
auto compenv = create_component_environment(
|
||||
get_root_environment(), -1, component.name
|
||||
);
|
||||
lua::get_from(L, lua::CHUNKS_TABLE, component.name, true);
|
||||
lua::pushenv(L, *compenv);
|
||||
|
||||
if (args != nullptr) {
|
||||
std::string compfieldname = component.name;
|
||||
util::replaceAll(compfieldname, ":", "__");
|
||||
if (args.has(compfieldname)) {
|
||||
lua::pushvalue(L, args[compfieldname]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
} else if (component.params != nullptr) {
|
||||
lua::pushvalue(L, component.params);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
lua::setfield(L, "ARGS");
|
||||
|
||||
if (saved == nullptr) {
|
||||
lua::createtable(L, 0, 0);
|
||||
} else {
|
||||
if (saved.has(component.name)) {
|
||||
lua::pushvalue(L, saved[component.name]);
|
||||
} else {
|
||||
lua::createtable(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::setfield(L, "SAVED_DATA");
|
||||
|
||||
lua::setfenv(L);
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
|
||||
lua::pushenv(L, *compenv);
|
||||
auto& funcsset = component.funcsset;
|
||||
funcsset.on_grounded = lua::hasfield(L, "on_grounded");
|
||||
funcsset.on_fall = lua::hasfield(L, "on_fall");
|
||||
funcsset.on_despawn = lua::hasfield(L, "on_despawn");
|
||||
funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter");
|
||||
funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit");
|
||||
funcsset.on_save = lua::hasfield(L, "on_save");
|
||||
funcsset.on_aim_on = lua::hasfield(L, "on_aim_on");
|
||||
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
|
||||
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
|
||||
funcsset.on_used = lua::hasfield(L, "on_used");
|
||||
lua::pop(L, 2);
|
||||
|
||||
component.env = compenv;
|
||||
}
|
||||
|
||||
void scripting::on_entity_spawn(
|
||||
const EntityDef&,
|
||||
entityid_t eid,
|
||||
const std::vector<std::unique_ptr<UserComponent>>& components,
|
||||
const dv::value& args,
|
||||
const dv::value& saved
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::stackguard guard(L);
|
||||
lua::requireglobal(L, STDCOMP);
|
||||
if (lua::getfield(L, "new_Entity")) {
|
||||
lua::pushinteger(L, eid);
|
||||
lua::call(L, 1);
|
||||
}
|
||||
for (auto& component : components) {
|
||||
create_component(L, -1, *component, args, saved);
|
||||
}
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const scriptenv& env,
|
||||
const std::string& name,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::pushenv(L, *env);
|
||||
if (lua::hasfield(L, "__disabled")) {
|
||||
lua::pop(L);
|
||||
return;
|
||||
}
|
||||
if (lua::getfield(L, name)) {
|
||||
if (args) {
|
||||
lua::call_nothrow(L, args(L), 0);
|
||||
} else {
|
||||
lua::call_nothrow(L, 0, 0);
|
||||
}
|
||||
}
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
static void process_entity_callback(
|
||||
const Entity& entity,
|
||||
const std::string& name,
|
||||
bool EntityFuncsSet::*flag,
|
||||
std::function<int(lua::State*)> args
|
||||
) {
|
||||
const auto& script = entity.getScripting();
|
||||
for (auto& component : script.components) {
|
||||
if (component->funcsset.*flag) {
|
||||
process_entity_callback(component->env, name, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scripting::on_entity_despawn(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr
|
||||
);
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, "stdcomp", "remove_Entity", true);
|
||||
lua::pushinteger(L, entity.getUID());
|
||||
lua::call(L, 1, 0);
|
||||
}
|
||||
|
||||
void scripting::on_entity_grounded(const Entity& entity, float force) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_grounded",
|
||||
&EntityFuncsSet::on_grounded,
|
||||
[force](auto L) { return lua::pushnumber(L, force); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_fall(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_fall", &EntityFuncsSet::on_fall, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_save(const Entity& entity) {
|
||||
process_entity_callback(
|
||||
entity, "on_save", &EntityFuncsSet::on_save, nullptr
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_enter(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_enter",
|
||||
&EntityFuncsSet::on_sensor_enter,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_sensor_exit(
|
||||
const Entity& entity, size_t index, entityid_t oid
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_sensor_exit",
|
||||
&EntityFuncsSet::on_sensor_exit,
|
||||
[index, oid](auto L) {
|
||||
lua::pushinteger(L, index);
|
||||
lua::pushinteger(L, oid);
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_on(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_on",
|
||||
&EntityFuncsSet::on_aim_on,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_aim_off(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_aim_off",
|
||||
&EntityFuncsSet::on_aim_off,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_attacked(
|
||||
const Entity& entity, Player* player, entityid_t attacker
|
||||
) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_attacked",
|
||||
&EntityFuncsSet::on_attacked,
|
||||
[player, attacker](auto L) {
|
||||
lua::pushinteger(L, attacker);
|
||||
lua::pushinteger(L, player->getId());
|
||||
return 2;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entity_used(const Entity& entity, Player* player) {
|
||||
process_entity_callback(
|
||||
entity,
|
||||
"on_used",
|
||||
&EntityFuncsSet::on_used,
|
||||
[player](auto L) { return lua::pushinteger(L, player->getId()); }
|
||||
);
|
||||
}
|
||||
|
||||
void scripting::on_entities_update(int tps, int parts, int part) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "update", true);
|
||||
lua::pushinteger(L, tps);
|
||||
lua::pushinteger(L, parts);
|
||||
lua::pushinteger(L, part);
|
||||
lua::call_nothrow(L, 3, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_entities_physics_update(float delta) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "physics_update", true);
|
||||
lua::pushnumber(L, delta);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
|
||||
void scripting::on_entities_render(float delta) {
|
||||
auto L = lua::get_main_state();
|
||||
lua::get_from(L, STDCOMP, "render", true);
|
||||
lua::pushnumber(L, delta);
|
||||
lua::call_nothrow(L, 1, 0);
|
||||
lua::pop(L);
|
||||
}
|
||||
@ -743,7 +743,6 @@ public:
|
||||
|
||||
class SocketUdpServer : public UdpServer {
|
||||
u64id_t id;
|
||||
Network* network;
|
||||
SOCKET descriptor;
|
||||
bool open = true;
|
||||
std::unique_ptr<std::thread> thread = nullptr;
|
||||
@ -752,13 +751,13 @@ class SocketUdpServer : public UdpServer {
|
||||
|
||||
public:
|
||||
SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port)
|
||||
: id(id), network(network), descriptor(descriptor), port(port) {}
|
||||
: id(id), descriptor(descriptor), port(port) {}
|
||||
|
||||
~SocketUdpServer() override {
|
||||
SocketUdpServer::close();
|
||||
}
|
||||
|
||||
void startListen(ServerDatagramCallback handler) {
|
||||
void startListen(ServerDatagramCallback handler) override {
|
||||
callback = std::move(handler);
|
||||
|
||||
thread = std::make_unique<std::thread>([this]() {
|
||||
|
||||
@ -17,110 +17,25 @@
|
||||
#include "maths/FrustumCulling.hpp"
|
||||
#include "maths/rays.hpp"
|
||||
#include "EntityDef.hpp"
|
||||
#include "Entity.hpp"
|
||||
#include "rigging.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "physics/PhysicsSolver.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
static debug::Logger logger("entities");
|
||||
|
||||
static inline std::string COMP_TRANSFORM = "transform";
|
||||
static inline std::string COMP_RIGIDBODY = "rigidbody";
|
||||
static inline std::string COMP_SKELETON = "skeleton";
|
||||
static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA";
|
||||
|
||||
void Transform::refresh() {
|
||||
combined = glm::mat4(1.0f);
|
||||
combined = glm::translate(combined, pos);
|
||||
combined = combined * glm::mat4(rot);
|
||||
combined = glm::scale(combined, size);
|
||||
displayPos = pos;
|
||||
displaySize = size;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Entity::setInterpolatedPosition(const glm::vec3& position) {
|
||||
getSkeleton().interpolation.refresh(position);
|
||||
}
|
||||
|
||||
glm::vec3 Entity::getInterpolatedPosition() const {
|
||||
const auto& skeleton = getSkeleton();
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
return skeleton.interpolation.getCurrent();
|
||||
}
|
||||
return getTransform().pos;
|
||||
}
|
||||
|
||||
void Entity::destroy() {
|
||||
if (isValid()) {
|
||||
entities.despawn(id);
|
||||
}
|
||||
}
|
||||
|
||||
rigging::Skeleton& Entity::getSkeleton() const {
|
||||
return registry.get<rigging::Skeleton>(entity);
|
||||
}
|
||||
|
||||
void Entity::setRig(const rigging::SkeletonConfig* rigConfig) {
|
||||
auto& skeleton = registry.get<rigging::Skeleton>(entity);
|
||||
skeleton.config = rigConfig;
|
||||
skeleton.pose.matrices.resize(
|
||||
rigConfig->getBones().size(), glm::mat4(1.0f)
|
||||
);
|
||||
skeleton.calculated.matrices.resize(
|
||||
rigConfig->getBones().size(), glm::mat4(1.0f)
|
||||
);
|
||||
}
|
||||
|
||||
Entities::Entities(Level& level)
|
||||
: level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) {
|
||||
: level(level),
|
||||
sensorsTickClock(20, 3),
|
||||
updateTickClock(20, 3) {
|
||||
}
|
||||
|
||||
template <void (*callback)(const Entity&, size_t, entityid_t)>
|
||||
static sensorcallback create_sensor_callback(Entities* entities) {
|
||||
return [=](auto entityid, auto index, auto otherid) {
|
||||
if (auto entity = entities->get(entityid)) {
|
||||
if (entity->isValid()) {
|
||||
callback(*entity, index, otherid);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void initialize_body(
|
||||
const EntityDef& def, Rigidbody& body, entityid_t id, Entities* entities
|
||||
) {
|
||||
body.sensors.resize(def.radialSensors.size() + def.boxSensors.size());
|
||||
for (auto& [i, box] : def.boxSensors) {
|
||||
SensorParams params {};
|
||||
params.aabb = box;
|
||||
body.sensors[i] = Sensor {
|
||||
true,
|
||||
SensorType::AABB,
|
||||
i,
|
||||
id,
|
||||
params,
|
||||
params,
|
||||
{},
|
||||
{},
|
||||
create_sensor_callback<scripting::on_sensor_enter>(entities),
|
||||
create_sensor_callback<scripting::on_sensor_exit>(entities)};
|
||||
}
|
||||
for (auto& [i, radius] : def.radialSensors) {
|
||||
SensorParams params {};
|
||||
params.radial = glm::vec4(radius);
|
||||
body.sensors[i] = Sensor {
|
||||
true,
|
||||
SensorType::RADIUS,
|
||||
i,
|
||||
id,
|
||||
params,
|
||||
params,
|
||||
{},
|
||||
{},
|
||||
create_sensor_callback<scripting::on_sensor_enter>(entities),
|
||||
create_sensor_callback<scripting::on_sensor_exit>(entities)};
|
||||
std::optional<Entity> Entities::get(entityid_t id) {
|
||||
const auto& found = entities.find(id);
|
||||
if (found != entities.end() && registry.valid(found->second)) {
|
||||
return Entity(*this, id, registry, found->second);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
entityid_t Entities::spawn(
|
||||
@ -168,14 +83,14 @@ entityid_t Entities::spawn(
|
||||
Hitbox {def.bodyType, position, def.hitbox * 0.5f},
|
||||
std::vector<Sensor> {}
|
||||
);
|
||||
initialize_body(def, body, id, this);
|
||||
body.initialize(def, id, *this);
|
||||
|
||||
auto& scripting = registry.emplace<ScriptComponents>(entity);
|
||||
registry.emplace<rigging::Skeleton>(entity, skeleton->instance());
|
||||
|
||||
for (auto& componentName : def.components) {
|
||||
for (auto& instance : def.components) {
|
||||
auto component = std::make_unique<UserComponent>(
|
||||
componentName, EntityFuncsSet {}, nullptr
|
||||
instance.component, EntityFuncsSet {}, nullptr, instance.params
|
||||
);
|
||||
scripting.components.emplace_back(std::move(component));
|
||||
}
|
||||
@ -186,7 +101,8 @@ entityid_t Entities::spawn(
|
||||
}
|
||||
body.hitbox.position = tsf.pos;
|
||||
scripting::on_entity_spawn(
|
||||
def, id, scripting.components, args, componentsMap);
|
||||
def, id, scripting.components, args, componentsMap
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
@ -213,41 +129,18 @@ void Entities::loadEntity(const dv::value& map, Entity entity) {
|
||||
auto& skeleton = entity.getSkeleton();
|
||||
|
||||
if (map.has(COMP_RIGIDBODY)) {
|
||||
auto& bodymap = map[COMP_RIGIDBODY];
|
||||
dv::get_vec(bodymap, "vel", body.hitbox.velocity);
|
||||
std::string bodyTypeName;
|
||||
map.at("type").get(bodyTypeName);
|
||||
BodyTypeMeta.getItem(bodyTypeName, body.hitbox.type);
|
||||
bodymap["crouch"].asBoolean(body.hitbox.crouching);
|
||||
bodymap["damping"].asNumber(body.hitbox.linearDamping);
|
||||
body.deserialize(map[COMP_RIGIDBODY]);
|
||||
}
|
||||
if (map.has(COMP_TRANSFORM)) {
|
||||
auto& tsfmap = map[COMP_TRANSFORM];
|
||||
dv::get_vec(tsfmap, "pos", transform.pos);
|
||||
dv::get_vec(tsfmap, "size", transform.size);
|
||||
dv::get_mat(tsfmap, "rot", transform.rot);
|
||||
transform.deserialize(map[COMP_TRANSFORM]);
|
||||
}
|
||||
std::string skeletonName = skeleton.config->getName();
|
||||
map.at("skeleton").get(skeletonName);
|
||||
if (skeletonName != skeleton.config->getName()) {
|
||||
skeleton.config = level.content.getSkeleton(skeletonName);
|
||||
}
|
||||
if (auto found = map.at(COMP_SKELETON)) {
|
||||
auto& skeletonmap = *found;
|
||||
if (auto found = skeletonmap.at("textures")) {
|
||||
auto& texturesmap = *found;
|
||||
for (auto& [slot, _] : texturesmap.asObject()) {
|
||||
texturesmap.at(slot).get(skeleton.textures[slot]);
|
||||
}
|
||||
}
|
||||
if (auto found = skeletonmap.at("pose")) {
|
||||
auto& posearr = *found;
|
||||
for (size_t i = 0;
|
||||
i < std::min(skeleton.pose.matrices.size(), posearr.size());
|
||||
i++) {
|
||||
dv::get_mat(posearr[i], skeleton.pose.matrices[i]);
|
||||
}
|
||||
}
|
||||
if (auto foundSkeleton = map.at(COMP_SKELETON)) {
|
||||
skeleton.deserialize(*foundSkeleton);
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,7 +177,7 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
|
||||
|
||||
void Entities::loadEntities(dv::value root) {
|
||||
clean();
|
||||
auto& list = root["data"];
|
||||
const auto& list = root["data"];
|
||||
for (auto& map : list) {
|
||||
try {
|
||||
loadEntity(map);
|
||||
@ -298,74 +191,6 @@ void Entities::onSave(const Entity& entity) {
|
||||
scripting::on_entity_save(entity);
|
||||
}
|
||||
|
||||
dv::value Entities::serialize(const Entity& entity) {
|
||||
auto root = dv::object();
|
||||
auto& eid = entity.getID();
|
||||
auto& def = eid.def;
|
||||
root["def"] = def.name;
|
||||
root["uid"] = eid.uid;
|
||||
{
|
||||
auto& transform = entity.getTransform();
|
||||
auto& tsfmap = root.object(COMP_TRANSFORM);
|
||||
tsfmap["pos"] = dv::to_value(transform.pos);
|
||||
if (transform.size != glm::vec3(1.0f)) {
|
||||
tsfmap["size"] = dv::to_value(transform.size);
|
||||
}
|
||||
if (transform.rot != glm::mat3(1.0f)) {
|
||||
tsfmap["rot"] = dv::to_value(transform.rot);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto& rigidbody = entity.getRigidbody();
|
||||
auto& hitbox = rigidbody.hitbox;
|
||||
auto& bodymap = root.object(COMP_RIGIDBODY);
|
||||
if (!rigidbody.enabled) {
|
||||
bodymap["enabled"] = false;
|
||||
}
|
||||
if (def.save.body.velocity) {
|
||||
bodymap["vel"] = dv::to_value(rigidbody.hitbox.velocity);
|
||||
}
|
||||
if (def.save.body.settings) {
|
||||
bodymap["damping"] = rigidbody.hitbox.linearDamping;
|
||||
if (hitbox.type != def.bodyType) {
|
||||
bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type);
|
||||
}
|
||||
if (hitbox.crouching) {
|
||||
bodymap["crouch"] = hitbox.crouching;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& skeleton = entity.getSkeleton();
|
||||
if (skeleton.config->getName() != def.skeletonName) {
|
||||
root["skeleton"] = skeleton.config->getName();
|
||||
}
|
||||
if (def.save.skeleton.pose || def.save.skeleton.textures) {
|
||||
auto& skeletonmap = root.object(COMP_SKELETON);
|
||||
if (def.save.skeleton.textures) {
|
||||
auto& map = skeletonmap.object("textures");
|
||||
for (auto& [slot, texture] : skeleton.textures) {
|
||||
map[slot] = texture;
|
||||
}
|
||||
}
|
||||
if (def.save.skeleton.pose) {
|
||||
auto& list = skeletonmap.list("pose");
|
||||
for (auto& mat : skeleton.pose.matrices) {
|
||||
list.add(dv::to_value(mat));
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& scripts = entity.getScripting();
|
||||
if (!scripts.components.empty()) {
|
||||
auto& compsMap = root.object("comps");
|
||||
for (auto& comp : scripts.components) {
|
||||
auto data =
|
||||
scripting::get_component_value(comp->env, SAVED_DATA_VARNAME);
|
||||
compsMap[comp->name] = data;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
dv::value Entities::serialize(const std::vector<Entity>& entities) {
|
||||
auto list = dv::list();
|
||||
for (auto& entity : entities) {
|
||||
@ -375,7 +200,7 @@ dv::value Entities::serialize(const std::vector<Entity>& entities) {
|
||||
}
|
||||
level.entities->onSave(entity);
|
||||
if (!eid.destroyFlag) {
|
||||
list.add(level.entities->serialize(entity));
|
||||
list.add(entity.serialize());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@ -474,7 +299,9 @@ void Entities::updatePhysics(float delta) {
|
||||
int substeps = static_cast<int>(delta * vel * 20);
|
||||
substeps = std::min(100, std::max(2, substeps));
|
||||
physics->step(*level.chunks, hitbox, delta, substeps, eid.uid);
|
||||
hitbox.linearDamping = hitbox.grounded * 24;
|
||||
hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f)
|
||||
? 8.0f
|
||||
: (!grounded ? 2.0f : 10.0f);
|
||||
transform.setPos(hitbox.position);
|
||||
if (hitbox.grounded && !grounded) {
|
||||
scripting::on_entity_grounded(
|
||||
@ -495,6 +322,8 @@ void Entities::update(float delta) {
|
||||
updateTickClock.getPart()
|
||||
);
|
||||
}
|
||||
updatePhysics(delta);
|
||||
scripting::on_entities_physics_update(delta);
|
||||
}
|
||||
|
||||
static void debug_render_skeleton(
|
||||
@ -505,13 +334,10 @@ static void debug_render_skeleton(
|
||||
size_t pindex = bone->getIndex();
|
||||
for (auto& sub : bone->getSubnodes()) {
|
||||
size_t sindex = sub->getIndex();
|
||||
const auto& matrices = skeleton.calculated.matrices;
|
||||
batch.line(
|
||||
glm::vec3(
|
||||
skeleton.calculated.matrices[pindex] * glm::vec4(0, 0, 0, 1)
|
||||
),
|
||||
glm::vec3(
|
||||
skeleton.calculated.matrices[sindex] * glm::vec4(0, 0, 0, 1)
|
||||
),
|
||||
glm::vec3(matrices[pindex] * glm::vec4(0, 0, 0, 1)),
|
||||
glm::vec3(matrices[sindex] * glm::vec4(0, 0, 0, 1)),
|
||||
glm::vec4(0, 0.5f, 0, 1)
|
||||
);
|
||||
debug_render_skeleton(batch, sub.get(), skeleton);
|
||||
@ -568,10 +394,14 @@ void Entities::render(
|
||||
ModelBatch& batch,
|
||||
const Frustum* frustum,
|
||||
float delta,
|
||||
bool pause
|
||||
bool pause,
|
||||
entityid_t fpsEntity
|
||||
) {
|
||||
auto view = registry.view<Transform, rigging::Skeleton>();
|
||||
for (auto [entity, transform, skeleton] : view.each()) {
|
||||
auto view = registry.view<EntityId, Transform, rigging::Skeleton>();
|
||||
for (auto [entity, eid, transform, skeleton] : view.each()) {
|
||||
if (eid.uid == fpsEntity) {
|
||||
continue;
|
||||
}
|
||||
if (transform.dirty) {
|
||||
transform.refresh();
|
||||
}
|
||||
|
||||
@ -5,101 +5,21 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "data/dv.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "Transform.hpp"
|
||||
#include "Rigidbody.hpp"
|
||||
#include "ScriptComponents.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/Clock.hpp"
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
struct EntityFuncsSet {
|
||||
bool init;
|
||||
bool on_despawn;
|
||||
bool on_grounded;
|
||||
bool on_fall;
|
||||
bool on_sensor_enter;
|
||||
bool on_sensor_exit;
|
||||
bool on_save;
|
||||
bool on_aim_on;
|
||||
bool on_aim_off;
|
||||
bool on_attacked;
|
||||
bool on_used;
|
||||
};
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
struct EntityDef;
|
||||
|
||||
struct EntityId {
|
||||
entityid_t uid;
|
||||
const EntityDef& def;
|
||||
bool destroyFlag = false;
|
||||
int64_t player = -1;
|
||||
};
|
||||
|
||||
struct Transform {
|
||||
static inline constexpr float EPSILON = 0.0000001f;
|
||||
glm::vec3 pos;
|
||||
glm::vec3 size;
|
||||
glm::mat3 rot;
|
||||
glm::mat4 combined;
|
||||
bool dirty = true;
|
||||
|
||||
glm::vec3 displayPos;
|
||||
glm::vec3 displaySize;
|
||||
|
||||
void refresh();
|
||||
|
||||
inline void setRot(glm::mat3 m) {
|
||||
rot = m;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
inline void setSize(glm::vec3 v) {
|
||||
if (glm::distance2(displaySize, v) >= EPSILON) {
|
||||
dirty = true;
|
||||
}
|
||||
size = v;
|
||||
}
|
||||
|
||||
inline void setPos(glm::vec3 v) {
|
||||
if (glm::distance2(displayPos, v) >= EPSILON) {
|
||||
dirty = true;
|
||||
}
|
||||
pos = v;
|
||||
}
|
||||
};
|
||||
|
||||
struct Rigidbody {
|
||||
bool enabled = true;
|
||||
Hitbox hitbox;
|
||||
std::vector<Sensor> sensors;
|
||||
};
|
||||
|
||||
struct UserComponent {
|
||||
std::string name;
|
||||
EntityFuncsSet funcsset;
|
||||
scriptenv env;
|
||||
|
||||
UserComponent(
|
||||
const std::string& name, EntityFuncsSet funcsset, scriptenv env
|
||||
)
|
||||
: name(name), funcsset(funcsset), env(env) {
|
||||
}
|
||||
};
|
||||
|
||||
struct ScriptComponents {
|
||||
std::vector<std::unique_ptr<UserComponent>> components;
|
||||
|
||||
ScriptComponents() = default;
|
||||
|
||||
ScriptComponents(ScriptComponents&& other)
|
||||
: components(std::move(other.components)) {
|
||||
}
|
||||
};
|
||||
|
||||
class Level;
|
||||
class Assets;
|
||||
class Entity;
|
||||
class LineBatch;
|
||||
class ModelBatch;
|
||||
class Frustum;
|
||||
@ -111,72 +31,6 @@ namespace rigging {
|
||||
class SkeletonConfig;
|
||||
}
|
||||
|
||||
class Entity {
|
||||
Entities& entities;
|
||||
entityid_t id;
|
||||
entt::registry& registry;
|
||||
const entt::entity entity;
|
||||
public:
|
||||
Entity(
|
||||
Entities& entities,
|
||||
entityid_t id,
|
||||
entt::registry& registry,
|
||||
const entt::entity entity
|
||||
)
|
||||
: entities(entities), id(id), registry(registry), entity(entity) {
|
||||
}
|
||||
|
||||
EntityId& getID() const {
|
||||
return registry.get<EntityId>(entity);
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return registry.valid(entity);
|
||||
}
|
||||
|
||||
const EntityDef& getDef() const {
|
||||
return registry.get<EntityId>(entity).def;
|
||||
}
|
||||
|
||||
Transform& getTransform() const {
|
||||
return registry.get<Transform>(entity);
|
||||
}
|
||||
|
||||
Rigidbody& getRigidbody() const {
|
||||
return registry.get<Rigidbody>(entity);
|
||||
}
|
||||
|
||||
ScriptComponents& getScripting() const {
|
||||
return registry.get<ScriptComponents>(entity);
|
||||
}
|
||||
|
||||
rigging::Skeleton& getSkeleton() const;
|
||||
|
||||
void setRig(const rigging::SkeletonConfig* rigConfig);
|
||||
|
||||
entityid_t getUID() const {
|
||||
return registry.get<EntityId>(entity).uid;
|
||||
}
|
||||
|
||||
entt::entity getHandler() const {
|
||||
return entity;
|
||||
}
|
||||
|
||||
int64_t getPlayer() const {
|
||||
return registry.get<EntityId>(entity).player;
|
||||
}
|
||||
|
||||
void setPlayer(int64_t id) {
|
||||
registry.get<EntityId>(entity).player = id;
|
||||
}
|
||||
|
||||
void setInterpolatedPosition(const glm::vec3& position);
|
||||
|
||||
glm::vec3 getInterpolatedPosition() const;
|
||||
|
||||
void destroy();
|
||||
};
|
||||
|
||||
class Entities {
|
||||
entt::registry registry;
|
||||
Level& level;
|
||||
@ -211,7 +65,8 @@ public:
|
||||
ModelBatch& batch,
|
||||
const Frustum* frustum,
|
||||
float delta,
|
||||
bool pause
|
||||
bool pause,
|
||||
entityid_t fpsEntity
|
||||
);
|
||||
|
||||
entityid_t spawn(
|
||||
@ -222,13 +77,7 @@ public:
|
||||
entityid_t uid = 0
|
||||
);
|
||||
|
||||
std::optional<Entity> get(entityid_t id) {
|
||||
const auto& found = entities.find(id);
|
||||
if (found != entities.end() && registry.valid(found->second)) {
|
||||
return Entity(*this, id, registry, found->second);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<Entity> get(entityid_t id);
|
||||
|
||||
/// @brief Entities raycast. No blocks check included, use combined with
|
||||
/// Chunks.rayCast
|
||||
@ -253,7 +102,6 @@ public:
|
||||
std::vector<Entity> getAllInRadius(glm::vec3 center, float radius);
|
||||
void despawn(entityid_t id);
|
||||
void despawn(std::vector<Entity> entities);
|
||||
dv::value serialize(const Entity& entity);
|
||||
dv::value serialize(const std::vector<Entity>& entities);
|
||||
|
||||
void setNextID(entityid_t id) {
|
||||
|
||||
119
src/objects/Entity.cpp
Normal file
119
src/objects/Entity.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
#include "Entity.hpp"
|
||||
|
||||
#include "Transform.hpp"
|
||||
#include "Rigidbody.hpp"
|
||||
#include "ScriptComponents.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "EntityDef.hpp"
|
||||
#include "rigging.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA";
|
||||
|
||||
void Entity::setInterpolatedPosition(const glm::vec3& position) {
|
||||
getSkeleton().interpolation.refresh(position);
|
||||
}
|
||||
|
||||
glm::vec3 Entity::getInterpolatedPosition() const {
|
||||
const auto& skeleton = getSkeleton();
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
return skeleton.interpolation.getCurrent();
|
||||
}
|
||||
return getTransform().pos;
|
||||
}
|
||||
|
||||
void Entity::destroy() {
|
||||
if (isValid()) {
|
||||
entities.despawn(id);
|
||||
}
|
||||
}
|
||||
|
||||
rigging::Skeleton& Entity::getSkeleton() const {
|
||||
return registry.get<rigging::Skeleton>(entity);
|
||||
}
|
||||
|
||||
void Entity::setRig(const rigging::SkeletonConfig* rigConfig) {
|
||||
auto& skeleton = registry.get<rigging::Skeleton>(entity);
|
||||
skeleton.config = rigConfig;
|
||||
skeleton.pose.matrices.resize(
|
||||
rigConfig->getBones().size(), glm::mat4(1.0f)
|
||||
);
|
||||
skeleton.calculated.matrices.resize(
|
||||
rigConfig->getBones().size(), glm::mat4(1.0f)
|
||||
);
|
||||
}
|
||||
|
||||
dv::value Entity::serialize() const {
|
||||
const auto& eid = getID();
|
||||
const auto& def = eid.def;
|
||||
const auto& transform = getTransform();
|
||||
const auto& rigidbody = getRigidbody();
|
||||
const auto& skeleton = getSkeleton();
|
||||
const auto& scripts = getScripting();
|
||||
|
||||
auto root = dv::object();
|
||||
root["def"] = def.name;
|
||||
root["uid"] = eid.uid;
|
||||
|
||||
root[COMP_TRANSFORM] = transform.serialize();
|
||||
root[COMP_RIGIDBODY] =
|
||||
rigidbody.serialize(def.save.body.velocity, def.save.body.settings);
|
||||
|
||||
if (skeleton.config->getName() != def.skeletonName) {
|
||||
root["skeleton"] = skeleton.config->getName();
|
||||
}
|
||||
if (def.save.skeleton.pose || def.save.skeleton.textures) {
|
||||
root[COMP_SKELETON] = skeleton.serialize(
|
||||
def.save.skeleton.pose, def.save.skeleton.textures
|
||||
);
|
||||
}
|
||||
if (!scripts.components.empty()) {
|
||||
auto& compsMap = root.object("comps");
|
||||
for (auto& comp : scripts.components) {
|
||||
auto data =
|
||||
scripting::get_component_value(comp->env, SAVED_DATA_VARNAME);
|
||||
compsMap[comp->name] = data;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
EntityId& Entity::getID() const {
|
||||
return registry.get<EntityId>(entity);
|
||||
}
|
||||
|
||||
bool Entity::isValid() const {
|
||||
return registry.valid(entity);
|
||||
}
|
||||
|
||||
Transform& Entity::getTransform() const {
|
||||
return registry.get<Transform>(entity);
|
||||
}
|
||||
|
||||
|
||||
ScriptComponents& Entity::getScripting() const {
|
||||
return registry.get<ScriptComponents>(entity);
|
||||
}
|
||||
|
||||
const EntityDef& Entity::getDef() const {
|
||||
return registry.get<EntityId>(entity).def;
|
||||
}
|
||||
|
||||
Rigidbody& Entity::getRigidbody() const {
|
||||
return registry.get<Rigidbody>(entity);
|
||||
}
|
||||
|
||||
entityid_t Entity::getUID() const {
|
||||
return registry.get<EntityId>(entity).uid;
|
||||
}
|
||||
|
||||
int64_t Entity::getPlayer() const {
|
||||
return registry.get<EntityId>(entity).player;
|
||||
}
|
||||
|
||||
void Entity::setPlayer(int64_t id) {
|
||||
registry.get<EntityId>(entity).player = id;
|
||||
}
|
||||
|
||||
80
src/objects/Entity.hpp
Normal file
80
src/objects/Entity.hpp
Normal file
@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "data/dv_fwd.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <entt/fwd.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
class Entities;
|
||||
struct EntityDef;
|
||||
struct Transform;
|
||||
struct Rigidbody;
|
||||
struct ScriptComponents;
|
||||
|
||||
inline std::string COMP_TRANSFORM = "transform";
|
||||
inline std::string COMP_RIGIDBODY = "rigidbody";
|
||||
inline std::string COMP_SKELETON = "skeleton";
|
||||
|
||||
namespace rigging {
|
||||
struct Skeleton;
|
||||
class SkeletonConfig;
|
||||
}
|
||||
|
||||
struct EntityId {
|
||||
entityid_t uid;
|
||||
const EntityDef& def;
|
||||
bool destroyFlag = false;
|
||||
int64_t player = -1;
|
||||
};
|
||||
|
||||
class Entity {
|
||||
Entities& entities;
|
||||
entityid_t id;
|
||||
entt::registry& registry;
|
||||
const entt::entity entity;
|
||||
public:
|
||||
Entity(
|
||||
Entities& entities,
|
||||
entityid_t id,
|
||||
entt::registry& registry,
|
||||
const entt::entity entity
|
||||
)
|
||||
: entities(entities), id(id), registry(registry), entity(entity) {
|
||||
}
|
||||
|
||||
dv::value serialize() const;
|
||||
|
||||
EntityId& getID() const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
const EntityDef& getDef() const;
|
||||
|
||||
Transform& getTransform() const;
|
||||
|
||||
Rigidbody& getRigidbody() const;
|
||||
|
||||
ScriptComponents& getScripting() const;
|
||||
|
||||
rigging::Skeleton& getSkeleton() const;
|
||||
|
||||
void setRig(const rigging::SkeletonConfig* rigConfig);
|
||||
|
||||
entityid_t getUID() const;
|
||||
|
||||
int64_t getPlayer() const;
|
||||
|
||||
void setPlayer(int64_t id);
|
||||
|
||||
void setInterpolatedPosition(const glm::vec3& position);
|
||||
|
||||
glm::vec3 getInterpolatedPosition() const;
|
||||
|
||||
entt::entity getHandler() const {
|
||||
return entity;
|
||||
}
|
||||
|
||||
void destroy();
|
||||
};
|
||||
@ -5,6 +5,7 @@
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "maths/aabb.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
|
||||
@ -12,12 +13,17 @@ namespace rigging {
|
||||
class SkeletonConfig;
|
||||
}
|
||||
|
||||
struct ComponentInstance {
|
||||
std::string component;
|
||||
dv::value params;
|
||||
};
|
||||
|
||||
struct EntityDef {
|
||||
/// @brief Entity string id (with prefix included)
|
||||
std::string const name;
|
||||
|
||||
/// @brief Component IDs
|
||||
std::vector<std::string> components;
|
||||
/// @brief Component instances
|
||||
std::vector<ComponentInstance> components;
|
||||
|
||||
/// @brief Physic body type
|
||||
BodyType bodyType = BodyType::DYNAMIC;
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#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"
|
||||
@ -20,13 +21,6 @@
|
||||
|
||||
static debug::Logger logger("player");
|
||||
|
||||
constexpr float CROUCH_SPEED_MUL = 0.35f;
|
||||
constexpr float RUN_SPEED_MUL = 1.5f;
|
||||
constexpr float PLAYER_GROUND_DAMPING = 10.0f;
|
||||
constexpr float PLAYER_AIR_DAMPING = 8.0f;
|
||||
constexpr float FLIGHT_SPEED_MUL = 4.0f;
|
||||
constexpr float CHEAT_SPEED_MUL = 5.0f;
|
||||
constexpr float JUMP_FORCE = 8.0f;
|
||||
constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64;
|
||||
|
||||
Player::Player(
|
||||
@ -81,17 +75,6 @@ void Player::updateEntity() {
|
||||
"will be respawned";
|
||||
eid = ENTITY_AUTO;
|
||||
}
|
||||
auto hitbox = getHitbox();
|
||||
if (hitbox == nullptr) {
|
||||
return;
|
||||
}
|
||||
hitbox->linearDamping = PLAYER_GROUND_DAMPING;
|
||||
hitbox->verticalDamping = flight;
|
||||
hitbox->gravityScale = flight ? 0.0f : 1.0f;
|
||||
if (flight || !hitbox->grounded) {
|
||||
hitbox->linearDamping = PLAYER_AIR_DAMPING;
|
||||
}
|
||||
hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC;
|
||||
}
|
||||
|
||||
Hitbox* Player::getHitbox() {
|
||||
@ -101,57 +84,6 @@ Hitbox* Player::getHitbox() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Player::updateInput(PlayerInput& input, float delta) {
|
||||
auto hitbox = getHitbox();
|
||||
if (hitbox == nullptr) {
|
||||
return;
|
||||
}
|
||||
bool crouch = input.shift && hitbox->grounded && !input.sprint;
|
||||
float speed = this->speed;
|
||||
if (flight) {
|
||||
speed *= FLIGHT_SPEED_MUL;
|
||||
}
|
||||
if (input.cheat) {
|
||||
speed *= CHEAT_SPEED_MUL;
|
||||
}
|
||||
|
||||
hitbox->crouching = crouch;
|
||||
if (crouch) {
|
||||
speed *= CROUCH_SPEED_MUL;
|
||||
} else if (input.sprint) {
|
||||
speed *= RUN_SPEED_MUL;
|
||||
}
|
||||
|
||||
glm::vec3 dir(0, 0, 0);
|
||||
if (input.moveForward) {
|
||||
dir += fpCamera->dir;
|
||||
}
|
||||
if (input.moveBack) {
|
||||
dir -= fpCamera->dir;
|
||||
}
|
||||
if (input.moveRight) {
|
||||
dir += fpCamera->right;
|
||||
}
|
||||
if (input.moveLeft) {
|
||||
dir -= fpCamera->right;
|
||||
}
|
||||
if (glm::length(dir) > 0.0f) {
|
||||
dir = glm::normalize(dir);
|
||||
hitbox->velocity += dir * speed * delta * 9.0f;
|
||||
}
|
||||
if (flight) {
|
||||
if (input.jump) {
|
||||
hitbox->velocity.y += speed * delta * 9;
|
||||
}
|
||||
if (input.shift) {
|
||||
hitbox->velocity.y -= speed * delta * 9;
|
||||
}
|
||||
}
|
||||
if (input.jump && hitbox->grounded) {
|
||||
hitbox->velocity.y = JUMP_FORCE;
|
||||
}
|
||||
}
|
||||
|
||||
void Player::updateSelectedEntity() {
|
||||
selectedEid = selection.entity;
|
||||
}
|
||||
@ -172,10 +104,6 @@ void Player::postUpdate() {
|
||||
attemptToFindSpawnpoint();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ERASE & FORGET
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
skeleton.visible = currentCamera != fpCamera;
|
||||
}
|
||||
|
||||
void Player::teleport(glm::vec3 position) {
|
||||
|
||||
@ -82,7 +82,6 @@ public:
|
||||
|
||||
void teleport(glm::vec3 position);
|
||||
void updateEntity();
|
||||
void updateInput(PlayerInput& input, float delta);
|
||||
void updateSelectedEntity();
|
||||
void postUpdate();
|
||||
|
||||
|
||||
83
src/objects/Rigidbody.cpp
Normal file
83
src/objects/Rigidbody.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#define VC_ENABLE_REFLECTION
|
||||
|
||||
#include "Rigidbody.hpp"
|
||||
|
||||
#include "EntityDef.hpp"
|
||||
#include "Entities.hpp"
|
||||
#include "Entity.hpp"
|
||||
#include "data/dv_util.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const {
|
||||
auto bodymap = dv::object();
|
||||
if (!enabled) {
|
||||
bodymap["enabled"] = false;
|
||||
}
|
||||
if (saveVelocity) {
|
||||
bodymap["vel"] = dv::to_value(hitbox.velocity);
|
||||
}
|
||||
if (saveBodySettings) {
|
||||
bodymap["damping"] = hitbox.linearDamping;
|
||||
bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type);
|
||||
if (hitbox.crouching) {
|
||||
bodymap["crouch"] = hitbox.crouching;
|
||||
}
|
||||
}
|
||||
return bodymap;
|
||||
}
|
||||
|
||||
void Rigidbody::deserialize(const dv::value& root) {
|
||||
dv::get_vec(root, "vel", hitbox.velocity);
|
||||
std::string bodyTypeName;
|
||||
root.at("type").get(bodyTypeName);
|
||||
BodyTypeMeta.getItem(bodyTypeName, hitbox.type);
|
||||
root["crouch"].asBoolean(hitbox.crouching);
|
||||
root["damping"].asNumber(hitbox.linearDamping);
|
||||
}
|
||||
|
||||
template <void (*callback)(const Entity&, size_t, entityid_t)>
|
||||
static sensorcallback create_sensor_callback(Entities& entities) {
|
||||
return [&entities](auto entityid, auto index, auto otherid) {
|
||||
if (auto entity = entities.get(entityid)) {
|
||||
if (entity->isValid()) {
|
||||
callback(*entity, index, otherid);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void Rigidbody::initialize(
|
||||
const EntityDef& def, entityid_t id, Entities& entities
|
||||
) {
|
||||
sensors.resize(def.radialSensors.size() + def.boxSensors.size());
|
||||
for (auto& [i, box] : def.boxSensors) {
|
||||
SensorParams params {};
|
||||
params.aabb = box;
|
||||
sensors[i] = Sensor {
|
||||
true,
|
||||
SensorType::AABB,
|
||||
i,
|
||||
id,
|
||||
params,
|
||||
params,
|
||||
{},
|
||||
{},
|
||||
create_sensor_callback<scripting::on_sensor_enter>(entities),
|
||||
create_sensor_callback<scripting::on_sensor_exit>(entities)};
|
||||
}
|
||||
for (auto& [i, radius] : def.radialSensors) {
|
||||
SensorParams params {};
|
||||
params.radial = glm::vec4(radius);
|
||||
sensors[i] = Sensor {
|
||||
true,
|
||||
SensorType::RADIUS,
|
||||
i,
|
||||
id,
|
||||
params,
|
||||
params,
|
||||
{},
|
||||
{},
|
||||
create_sensor_callback<scripting::on_sensor_enter>(entities),
|
||||
create_sensor_callback<scripting::on_sensor_exit>(entities)};
|
||||
}
|
||||
}
|
||||
23
src/objects/Rigidbody.hpp
Normal file
23
src/objects/Rigidbody.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "data/dv_fwd.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <entt/fwd.hpp>
|
||||
|
||||
class Entities;
|
||||
struct EntityDef;
|
||||
|
||||
struct Rigidbody {
|
||||
bool enabled = true;
|
||||
Hitbox hitbox;
|
||||
std::vector<Sensor> sensors;
|
||||
|
||||
dv::value serialize(bool saveVelocity, bool saveBodySettings) const;
|
||||
void deserialize(const dv::value& root);
|
||||
|
||||
void initialize(
|
||||
const EntityDef& def, entityid_t id, Entities& entities
|
||||
);
|
||||
};
|
||||
49
src/objects/ScriptComponents.hpp
Normal file
49
src/objects/ScriptComponents.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "data/dv.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct EntityFuncsSet {
|
||||
bool init;
|
||||
bool on_despawn;
|
||||
bool on_grounded;
|
||||
bool on_fall;
|
||||
bool on_sensor_enter;
|
||||
bool on_sensor_exit;
|
||||
bool on_save;
|
||||
bool on_aim_on;
|
||||
bool on_aim_off;
|
||||
bool on_attacked;
|
||||
bool on_used;
|
||||
};
|
||||
|
||||
struct UserComponent {
|
||||
std::string name;
|
||||
EntityFuncsSet funcsset;
|
||||
scriptenv env;
|
||||
dv::value params;
|
||||
|
||||
UserComponent(
|
||||
const std::string& name,
|
||||
EntityFuncsSet funcsset,
|
||||
scriptenv env,
|
||||
dv::value params
|
||||
)
|
||||
: name(name),
|
||||
funcsset(funcsset),
|
||||
env(std::move(env)),
|
||||
params(std::move(params)) {
|
||||
}
|
||||
};
|
||||
|
||||
struct ScriptComponents {
|
||||
std::vector<std::unique_ptr<UserComponent>> components;
|
||||
|
||||
ScriptComponents() = default;
|
||||
|
||||
ScriptComponents(ScriptComponents&& other)
|
||||
: components(std::move(other.components)) {
|
||||
}
|
||||
};
|
||||
31
src/objects/Transform.cpp
Normal file
31
src/objects/Transform.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "Transform.hpp"
|
||||
|
||||
#include "data/dv_util.hpp"
|
||||
|
||||
void Transform::refresh() {
|
||||
combined = glm::mat4(1.0f);
|
||||
combined = glm::translate(combined, pos);
|
||||
combined = combined * glm::mat4(rot);
|
||||
combined = glm::scale(combined, size);
|
||||
displayPos = pos;
|
||||
displaySize = size;
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
dv::value Transform::serialize() const {
|
||||
auto tsfmap = dv::object();
|
||||
tsfmap["pos"] = dv::to_value(pos);
|
||||
if (size != glm::vec3(1.0f)) {
|
||||
tsfmap["size"] = dv::to_value(size);
|
||||
}
|
||||
if (rot != glm::mat3(1.0f)) {
|
||||
tsfmap["rot"] = dv::to_value(rot);
|
||||
}
|
||||
return tsfmap;
|
||||
}
|
||||
|
||||
void Transform::deserialize(const dv::value& root) {
|
||||
dv::get_vec(root, "pos", pos);
|
||||
dv::get_vec(root, "size", size);
|
||||
dv::get_mat(root, "rot", rot);
|
||||
}
|
||||
44
src/objects/Transform.hpp
Normal file
44
src/objects/Transform.hpp
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <data/dv_fwd.hpp>
|
||||
|
||||
struct Transform {
|
||||
static inline constexpr float EPSILON = 1e-7f;
|
||||
glm::vec3 pos;
|
||||
glm::vec3 size;
|
||||
glm::mat3 rot;
|
||||
glm::mat4 combined;
|
||||
bool dirty = true;
|
||||
|
||||
glm::vec3 displayPos;
|
||||
glm::vec3 displaySize;
|
||||
|
||||
dv::value serialize() const;
|
||||
void deserialize(const dv::value& root);
|
||||
|
||||
void refresh();
|
||||
|
||||
inline void setRot(glm::mat3 m) {
|
||||
rot = m;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
inline void setSize(glm::vec3 v) {
|
||||
if (glm::distance2(displaySize, v) >= EPSILON) {
|
||||
dirty = true;
|
||||
}
|
||||
size = v;
|
||||
}
|
||||
|
||||
inline void setPos(glm::vec3 v) {
|
||||
if (glm::distance2(displayPos, v) >= EPSILON) {
|
||||
dirty = true;
|
||||
}
|
||||
pos = v;
|
||||
}
|
||||
};
|
||||
@ -23,7 +23,7 @@ Bone::Bone(
|
||||
std::string name,
|
||||
std::string model,
|
||||
std::vector<std::unique_ptr<Bone>> bones,
|
||||
glm::vec3 offset
|
||||
const glm::vec3& offset
|
||||
)
|
||||
: index(index),
|
||||
name(std::move(name)),
|
||||
@ -53,6 +53,39 @@ Skeleton::Skeleton(const SkeletonConfig* config)
|
||||
}
|
||||
}
|
||||
|
||||
dv::value Skeleton::serialize(bool saveTextures, bool savePose) const {
|
||||
auto root = dv::object();
|
||||
if (saveTextures) {
|
||||
auto& map = root.object("textures");
|
||||
for (auto& [slot, texture] : textures) {
|
||||
map[slot] = texture;
|
||||
}
|
||||
}
|
||||
if (savePose) {
|
||||
auto& list = root.list("pose");
|
||||
for (auto& mat : pose.matrices) {
|
||||
list.add(dv::to_value(mat));
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
void Skeleton::deserialize(const dv::value& root) {
|
||||
if (auto found = root.at("textures")) {
|
||||
auto& texturesmap = *found;
|
||||
for (auto& [slot, _] : texturesmap.asObject()) {
|
||||
texturesmap.at(slot).get(textures[slot]);
|
||||
}
|
||||
}
|
||||
if (auto found = root.at("pose")) {
|
||||
auto& posearr = *found;
|
||||
auto& matrices = pose.matrices;
|
||||
for (size_t i = 0; i < std::min(matrices.size(), posearr.size()); i++) {
|
||||
dv::get_mat(posearr[i], pose.matrices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_all_nodes(std::vector<Bone*>& nodes, Bone* node) {
|
||||
nodes[node->getIndex()] = node;
|
||||
for (auto& subnode : node->getSubnodes()) {
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "data/dv_fwd.hpp"
|
||||
#include "util/Interpolation.hpp"
|
||||
|
||||
class Assets;
|
||||
@ -50,7 +51,7 @@ namespace rigging {
|
||||
std::string name,
|
||||
std::string model,
|
||||
std::vector<std::unique_ptr<Bone>> bones,
|
||||
glm::vec3 offset
|
||||
const glm::vec3& offset
|
||||
);
|
||||
|
||||
void setModel(const std::string& name);
|
||||
@ -89,6 +90,9 @@ namespace rigging {
|
||||
util::VecInterpolation<3, float> interpolation {false};
|
||||
|
||||
Skeleton(const SkeletonConfig* config);
|
||||
|
||||
dv::value serialize(bool saveTextures, bool savePose) const;
|
||||
void deserialize(const dv::value& root);
|
||||
};
|
||||
|
||||
class SkeletonConfig {
|
||||
|
||||
@ -6,6 +6,5 @@ Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize)
|
||||
: type(type),
|
||||
position(position),
|
||||
halfsize(halfsize),
|
||||
velocity(0.0f,0.0f,0.0f),
|
||||
linearDamping(0.1f)
|
||||
velocity(0.0f,0.0f,0.0f)
|
||||
{}
|
||||
|
||||
@ -52,8 +52,9 @@ struct Hitbox {
|
||||
glm::vec3 position;
|
||||
glm::vec3 halfsize;
|
||||
glm::vec3 velocity;
|
||||
float linearDamping;
|
||||
bool verticalDamping = false;
|
||||
float linearDamping = 0.5;
|
||||
float friction = 1.0f;
|
||||
float verticalDamping = 1.0f;
|
||||
bool grounded = false;
|
||||
float gravityScale = 1.0f;
|
||||
bool crouching = false;
|
||||
|
||||
@ -25,7 +25,7 @@ void PhysicsSolver::step(
|
||||
entityid_t entity
|
||||
) {
|
||||
float dt = delta / static_cast<float>(substeps);
|
||||
float linearDamping = hitbox.linearDamping;
|
||||
float linearDamping = hitbox.linearDamping * hitbox.friction;
|
||||
float s = 2.0f/BLOCK_AABB_GRID;
|
||||
|
||||
const glm::vec3& half = hitbox.halfsize;
|
||||
@ -45,11 +45,6 @@ void PhysicsSolver::step(
|
||||
colisionCalc(chunks, hitbox, vel, pos, half,
|
||||
(prevGrounded && gravityScale > 0.0f) ? 0.5f : 0.0f);
|
||||
}
|
||||
vel.x *= glm::max(0.0f, 1.0f - dt * linearDamping);
|
||||
if (hitbox.verticalDamping) {
|
||||
vel.y *= glm::max(0.0f, 1.0f - dt * linearDamping);
|
||||
}
|
||||
vel.z *= glm::max(0.0f, 1.0f - dt * linearDamping);
|
||||
|
||||
pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f;
|
||||
if (hitbox.grounded && pos.y < py) {
|
||||
@ -89,6 +84,12 @@ void PhysicsSolver::step(
|
||||
hitbox.grounded = true;
|
||||
}
|
||||
}
|
||||
vel.x /= 1.0f + delta * linearDamping;
|
||||
vel.z /= 1.0f + delta * linearDamping;
|
||||
if (hitbox.verticalDamping > 0.0f) {
|
||||
vel.y /= 1.0f + delta * linearDamping * hitbox.verticalDamping;
|
||||
}
|
||||
|
||||
AABB aabb;
|
||||
aabb.a = hitbox.position - hitbox.halfsize;
|
||||
aabb.b = hitbox.position + hitbox.halfsize;
|
||||
|
||||
@ -85,11 +85,18 @@ struct GraphicsSettings {
|
||||
IntegerSetting denseRenderDistance {56, 0, 10'000};
|
||||
};
|
||||
|
||||
struct PathfindingSettings {
|
||||
/// @brief Max visited blocks by an agent per async tick
|
||||
IntegerSetting stepsPerAsyncAgent {128, 1, 2048};
|
||||
};
|
||||
|
||||
struct DebugSettings {
|
||||
/// @brief Turns off chunks saving/loading
|
||||
FlagSetting generatorTestMode {false};
|
||||
/// @brief Write lights cache
|
||||
FlagSetting doWriteLights {true};
|
||||
/// @brief Write preprocessed shaders code to user:export
|
||||
FlagSetting doTraceShaders {false};
|
||||
/// @brief Enable experimental optimizations and features
|
||||
FlagSetting enableExperimental {false};
|
||||
};
|
||||
@ -111,4 +118,5 @@ struct EngineSettings {
|
||||
DebugSettings debug;
|
||||
UiSettings ui;
|
||||
NetworkSettings network;
|
||||
PathfindingSettings pathfinding;
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ Clock::Clock(int tickRate, int tickParts)
|
||||
|
||||
bool Clock::update(float delta) {
|
||||
tickTimer += delta;
|
||||
float delay = 1.0f / float(tickRate);
|
||||
float delay = 1.0f / static_cast<float>(tickRate);
|
||||
if (tickTimer > delay || tickPartsUndone) {
|
||||
if (tickPartsUndone) {
|
||||
tickPartsUndone--;
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "lighting/Lightmap.hpp"
|
||||
#include "maths/voxmaths.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "voxels/blocks_agent.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "world/LevelEvents.hpp"
|
||||
|
||||
269
src/voxels/Pathfinding.cpp
Normal file
269
src/voxels/Pathfinding.cpp
Normal file
@ -0,0 +1,269 @@
|
||||
#include "Pathfinding.hpp"
|
||||
|
||||
#include "content/Content.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/GlobalChunks.hpp"
|
||||
#include "voxels/blocks_agent.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
inline constexpr float SQRT2 = 1.4142135623730951f; // sqrt(2)
|
||||
|
||||
using namespace voxels;
|
||||
|
||||
static float heuristic(const glm::ivec3& a, const glm::ivec3& b) {
|
||||
return glm::distance(glm::vec3(a), glm::vec3(b));
|
||||
}
|
||||
|
||||
Pathfinding::Pathfinding(const Level& level)
|
||||
: level(level),
|
||||
chunks(*level.chunks),
|
||||
blockDefs(level.content.getIndices()->blocks) {
|
||||
}
|
||||
|
||||
static bool check_passability(
|
||||
const Agent& agent,
|
||||
const GlobalChunks& chunks,
|
||||
const Node& node,
|
||||
const glm::ivec2& offset,
|
||||
bool diagonal
|
||||
) {
|
||||
if (!diagonal) {
|
||||
return true;
|
||||
}
|
||||
auto a = node.pos + glm::ivec3(offset.x, 0, 0);
|
||||
auto b = node.pos + glm::ivec3(0, 0, offset.y);
|
||||
|
||||
for (int i = 0; i < agent.height; i++) {
|
||||
if (blocks_agent::is_obstacle_at(chunks, a.x, a.y + i, a.z))
|
||||
return false;
|
||||
if (blocks_agent::is_obstacle_at(chunks, b.x, b.y + i, b.z))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void restore_route(
|
||||
Route& route,
|
||||
const glm::ivec3& lastPos,
|
||||
const std::unordered_map<glm::ivec3, Node>& parents
|
||||
) {
|
||||
auto pos = lastPos;
|
||||
while (true) {
|
||||
const auto& found = parents.find(pos);
|
||||
if (found == parents.end()) {
|
||||
route.nodes.push_back({pos});
|
||||
break;
|
||||
}
|
||||
route.nodes.push_back({pos});
|
||||
pos = found->second.pos;
|
||||
}
|
||||
}
|
||||
|
||||
int Pathfinding::createAgent() {
|
||||
int id = nextAgent++;
|
||||
agents[id] = Agent();
|
||||
return id;
|
||||
}
|
||||
|
||||
bool Pathfinding::removeAgent(int id) {
|
||||
auto found = agents.find(id);
|
||||
if (found != agents.end()) {
|
||||
agents.erase(found);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Pathfinding::performAllAsync(int stepsPerAgent) {
|
||||
for (auto& [_, agent] : agents) {
|
||||
if (agent.state.finished) {
|
||||
continue;
|
||||
}
|
||||
perform(agent, stepsPerAgent);
|
||||
}
|
||||
}
|
||||
|
||||
static Route finish_route(Agent& agent, State&& state) {
|
||||
Route route {};
|
||||
restore_route(route, state.nearest, state.parents);
|
||||
route.totalVisited = state.blocked.size();
|
||||
route.nodes.push_back({agent.start});
|
||||
route.found = true;
|
||||
state.finished = true;
|
||||
agent.state = std::move(state);
|
||||
agent.route = route;
|
||||
return route;
|
||||
}
|
||||
|
||||
enum Passability {
|
||||
NON_PASSABLE = -1,
|
||||
OBSTACLE = 0,
|
||||
PASSABLE = 1,
|
||||
};
|
||||
|
||||
Route Pathfinding::perform(Agent& agent, int maxVisited) {
|
||||
using namespace blocks_agent;
|
||||
|
||||
State state = std::move(agent.state);
|
||||
if (state.queue.empty()) {
|
||||
state.queue.push(
|
||||
{agent.start, {}, 0, heuristic(agent.start, agent.target)}
|
||||
);
|
||||
}
|
||||
|
||||
const auto& chunks = *level.chunks;
|
||||
int height = std::max(agent.height, 1);
|
||||
|
||||
if (state.nearest == glm::ivec3(0)) {
|
||||
state.nearest = agent.start;
|
||||
state.minHScore = heuristic(agent.start, agent.target);
|
||||
}
|
||||
int visited = -1;
|
||||
|
||||
while (!state.queue.empty()) {
|
||||
if (state.blocked.size() == agent.maxVisitedBlocks) {
|
||||
if (agent.mayBeIncomplete) {
|
||||
return finish_route(agent, std::move(state));
|
||||
}
|
||||
break;
|
||||
}
|
||||
visited++;
|
||||
if (visited == maxVisited) {
|
||||
state.finished = false;
|
||||
agent.state = std::move(state);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto node = state.queue.top();
|
||||
state.queue.pop();
|
||||
|
||||
if (node.pos.x == agent.target.x &&
|
||||
glm::abs((node.pos.y - agent.target.y) / height) == 0 &&
|
||||
node.pos.z == agent.target.z) {
|
||||
return finish_route(agent, std::move(state));
|
||||
}
|
||||
|
||||
state.blocked.emplace(node.pos);
|
||||
glm::ivec2 neighbors[8] {
|
||||
{0, 1},
|
||||
{1, 0},
|
||||
{0, -1},
|
||||
{-1, 0},
|
||||
{-1, -1},
|
||||
{1, -1},
|
||||
{1, 1},
|
||||
{-1, 1},
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(neighbors) / sizeof(glm::ivec2); i++) {
|
||||
auto offset = neighbors[i];
|
||||
auto pos = node.pos;
|
||||
|
||||
float cost = glm::abs(node.pos.y - pos.y) * 10;
|
||||
int surface = getSurfaceAt(
|
||||
agent, pos + glm::ivec3(offset.x, 0, offset.y), 1, cost
|
||||
);
|
||||
|
||||
if (surface == NON_PASSABLE) {
|
||||
continue;
|
||||
}
|
||||
pos.y = surface;
|
||||
auto point = pos + glm::ivec3(offset.x, 0, offset.y);
|
||||
if (state.blocked.find(point) != state.blocked.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_obstacle_at(
|
||||
chunks, pos.x, pos.y + agent.jumpHeight, pos.z
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
if (!check_passability(agent, chunks, node, offset, i >= 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float sum = glm::abs(offset.x) + glm::abs(offset.y);
|
||||
float gScore = node.gScore + sum + cost;
|
||||
const auto& found = state.parents.find(point);
|
||||
if (found == state.parents.end()) {
|
||||
float hScore = heuristic(point, agent.target);
|
||||
if (hScore < state.minHScore) {
|
||||
state.minHScore = hScore;
|
||||
state.nearest = point;
|
||||
}
|
||||
float fScore = gScore * 0.75f + hScore;
|
||||
Node nNode {point, node.pos, gScore, fScore};
|
||||
state.parents[point] = node;
|
||||
state.queue.push(nNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.finished = true;
|
||||
agent.state = std::move(state);
|
||||
return finish_route(agent, std::move(agent.state));
|
||||
}
|
||||
|
||||
Agent* Pathfinding::getAgent(int id) {
|
||||
const auto& found = agents.find(id);
|
||||
if (found != agents.end()) {
|
||||
return &found->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::unordered_map<int, Agent>& Pathfinding::getAgents() const {
|
||||
return agents;
|
||||
}
|
||||
|
||||
int Pathfinding::checkPoint(const Agent& agent, int x, int y, int z, int& cost) {
|
||||
auto vox = blocks_agent::get(chunks, x, y, z);
|
||||
if (vox == nullptr) {
|
||||
return OBSTACLE;
|
||||
}
|
||||
const auto& def = blockDefs.require(vox->id);
|
||||
if (def.obstacle) {
|
||||
return OBSTACLE;
|
||||
}
|
||||
for (const auto& pair : agent.avoidTags) {
|
||||
if (def.rt.tags.find(pair.first) != def.rt.tags.end()) {
|
||||
cost = pair.second;
|
||||
return NON_PASSABLE;
|
||||
}
|
||||
}
|
||||
return PASSABLE;
|
||||
}
|
||||
|
||||
int Pathfinding::getSurfaceAt(
|
||||
const Agent& agent, const glm::ivec3& pos, int maxDelta, float& cost
|
||||
) {
|
||||
using namespace blocks_agent;
|
||||
|
||||
int status;
|
||||
int surface = pos.y;
|
||||
int ncost = 0;
|
||||
if ((status = checkPoint(agent, pos.x, surface, pos.z, ncost)) == OBSTACLE) {
|
||||
if ((status = checkPoint(agent, pos.x, surface + 1, pos.z, ncost)) == OBSTACLE) {
|
||||
return NON_PASSABLE;
|
||||
} else if (status == NON_PASSABLE) {
|
||||
cost += 5;
|
||||
}
|
||||
cost += ncost;
|
||||
return surface + 1;
|
||||
} else {
|
||||
if (status == NON_PASSABLE) {
|
||||
cost += 5;
|
||||
}
|
||||
if ((status = checkPoint(agent, pos.x, surface - 1, pos.z, ncost)) == OBSTACLE) {
|
||||
cost += ncost;
|
||||
return surface;
|
||||
} else if (status == NON_PASSABLE) {
|
||||
cost += 5;
|
||||
}
|
||||
if ((status = checkPoint(agent, pos.x, surface - 2, pos.z, ncost)) == OBSTACLE) {
|
||||
cost += ncost;
|
||||
return surface - 1;
|
||||
}
|
||||
return NON_PASSABLE;
|
||||
}
|
||||
return NON_PASSABLE;
|
||||
}
|
||||
95
src/voxels/Pathfinding.hpp
Normal file
95
src/voxels/Pathfinding.hpp
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/hash.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class Block;
|
||||
class Level;
|
||||
class GlobalChunks;
|
||||
|
||||
template <typename T>
|
||||
class ContentUnitIndices;
|
||||
|
||||
namespace voxels {
|
||||
struct RouteNode {
|
||||
glm::ivec3 pos;
|
||||
};
|
||||
|
||||
struct Route {
|
||||
bool found;
|
||||
std::vector<RouteNode> nodes;
|
||||
int totalVisited;
|
||||
};
|
||||
|
||||
struct Node {
|
||||
glm::ivec3 pos;
|
||||
glm::ivec3 parent;
|
||||
float gScore;
|
||||
float fScore;
|
||||
};
|
||||
|
||||
struct NodeLess {
|
||||
bool operator()(const Node& l, const Node& r) const {
|
||||
return l.fScore > r.fScore;
|
||||
}
|
||||
};
|
||||
|
||||
struct State {
|
||||
std::priority_queue<Node, std::vector<Node>, NodeLess> queue;
|
||||
std::unordered_set<glm::ivec3> blocked;
|
||||
std::unordered_map<glm::ivec3, Node> parents;
|
||||
glm::ivec3 nearest;
|
||||
float minHScore;
|
||||
bool finished = true;
|
||||
};
|
||||
|
||||
struct Agent {
|
||||
bool enabled = false;
|
||||
bool mayBeIncomplete = true;
|
||||
int height = 2;
|
||||
int jumpHeight = 1;
|
||||
int maxVisitedBlocks = 1e3;
|
||||
glm::ivec3 start;
|
||||
glm::ivec3 target;
|
||||
Route route;
|
||||
State state {};
|
||||
std::set<std::pair<int, int>> avoidTags;
|
||||
};
|
||||
|
||||
class Pathfinding {
|
||||
public:
|
||||
Pathfinding(const Level& level);
|
||||
|
||||
int createAgent();
|
||||
|
||||
bool removeAgent(int id);
|
||||
|
||||
void performAllAsync(int stepsPerAgent);
|
||||
|
||||
Route perform(Agent& agent, int maxVisited = -1);
|
||||
|
||||
Agent* getAgent(int id);
|
||||
|
||||
const std::unordered_map<int, Agent>& getAgents() const;
|
||||
private:
|
||||
const Level& level;
|
||||
const GlobalChunks& chunks;
|
||||
const ContentUnitIndices<Block>& blockDefs;
|
||||
std::unordered_map<int, Agent> agents;
|
||||
int nextAgent = 1;
|
||||
|
||||
int getSurfaceAt(
|
||||
const Agent& agent, const glm::ivec3& pos, int maxDelta, float& cost
|
||||
);
|
||||
|
||||
int checkPoint(const Agent& agent, int x, int y, int z, int& cost);
|
||||
};
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
#include "items/Inventories.hpp"
|
||||
#include "items/Inventory.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
@ -12,6 +13,7 @@
|
||||
#include "settings.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/GlobalChunks.hpp"
|
||||
#include "voxels/Pathfinding.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "LevelEvents.hpp"
|
||||
#include "World.hpp"
|
||||
@ -27,7 +29,8 @@ Level::Level(
|
||||
physics(std::make_unique<PhysicsSolver>(glm::vec3(0, -22.6f, 0))),
|
||||
events(std::make_unique<LevelEvents>()),
|
||||
entities(std::make_unique<Entities>(*this)),
|
||||
players(std::make_unique<Players>(*this)) {
|
||||
players(std::make_unique<Players>(*this)),
|
||||
pathfinding(std::make_unique<voxels::Pathfinding>(*this)) {
|
||||
const auto& worldInfo = world->getInfo();
|
||||
auto& cameraIndices = content.getIndices(ResourceType::CAMERA);
|
||||
for (size_t i = 0; i < cameraIndices.size(); i++) {
|
||||
|
||||
@ -18,6 +18,10 @@ class Camera;
|
||||
class Players;
|
||||
struct EngineSettings;
|
||||
|
||||
namespace voxels {
|
||||
class Pathfinding;
|
||||
}
|
||||
|
||||
/// @brief A level, contains chunks and objects
|
||||
class Level {
|
||||
std::unique_ptr<World> world;
|
||||
@ -30,6 +34,7 @@ public:
|
||||
std::unique_ptr<LevelEvents> events;
|
||||
std::unique_ptr<Entities> entities;
|
||||
std::unique_ptr<Players> players;
|
||||
std::unique_ptr<voxels::Pathfinding> pathfinding;
|
||||
std::vector<std::shared_ptr<Camera>> cameras; // move somewhere?
|
||||
|
||||
Level(
|
||||
|
||||
@ -11,7 +11,7 @@ TEST(GLSLExtension, processing) {
|
||||
"float sum(float a, float b) {\n"
|
||||
" return a + b;\n"
|
||||
"}\n",
|
||||
true
|
||||
true, {}
|
||||
)
|
||||
);
|
||||
try {
|
||||
@ -27,7 +27,7 @@ TEST(GLSLExtension, processing) {
|
||||
" vec4 color = texture(u_screen, v_uv);\n"
|
||||
" return mix(color, 1.0 - color, p_intensity);\n"
|
||||
"}\n",
|
||||
false);
|
||||
false, {});
|
||||
std::cout << processed.code << std::endl;
|
||||
} catch (const parsing_error& err) {
|
||||
std::cerr << err.errorLog() << std::endl;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user