Merge pull request #574 from MihailRis/pathfinding

Pathfinding + Mobs
This commit is contained in:
MihailRis 2025-09-13 23:56:15 +03:00 committed by GitHub
commit 5de8ae5f61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 2773 additions and 1149 deletions

View File

@ -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`. The components code should be in `scripts/components`.
## Physics ## Physics

View File

@ -32,6 +32,7 @@ Subsections:
- [mat4](scripting/builtins/libmat4.md) - [mat4](scripting/builtins/libmat4.md)
- [network](scripting/builtins/libnetwork.md) - [network](scripting/builtins/libnetwork.md)
- [pack](scripting/builtins/libpack.md) - [pack](scripting/builtins/libpack.md)
- [pathfinding](scripting/builtins/libpathfinding.md)
- [player](scripting/builtins/libplayer.md) - [player](scripting/builtins/libplayer.md)
- [quat](scripting/builtins/libquat.md) - [quat](scripting/builtins/libquat.md)
- [rules](scripting/builtins/librules.md) - [rules](scripting/builtins/librules.md)

View 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
)
```

View File

@ -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(...)* #### Absolute value - *vecn.abs(...)*
```lua ```lua
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
vecn.dot(a: vector, b: 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(...)* #### Convert to string - *vecn.tostring(...)*
> [!WARNING] > [!WARNING]
> Returns only if the content is a vector > 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] -- returns the direction angle of the vector {x, y} in degrees [0, 360]
vec2.angle(x: number, y: number) 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) local result_mul_scal = vec3.mul(v1_3d, scal)
print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} 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 -- vector normalization
local result_norm = vec3.normalize(v1_3d) local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} 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 -- scalar product of vectors
local result_dot = vec3.dot(v1_3d, v2_3d) local result_dot = vec3.dot(v1_3d, v2_3d)
print("dot: " ..result_dot) -- 250 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}

View File

@ -26,6 +26,8 @@ entity:get_uid() -> int
entity:get_component(name: str) -> component or nil entity:get_component(name: str) -> component or nil
-- Checks for the presence of a component by name -- Checks for the presence of a component by name
entity:has_component(name: str) -> bool 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 -- Enables/disables the component
entity:set_enabled(name: str, enable: bool) entity:set_enabled(name: str, enable: bool)
@ -93,10 +95,12 @@ body:get_linear_damping() -> number
-- Sets the linear velocity attenuation multiplier -- Sets the linear velocity attenuation multiplier
body:set_linear_damping(value: number) body:set_linear_damping(value: number)
-- Checks if vertical velocity attenuation is enabled -- Checks if vertical damping is enabled
body:is_vdamping() -> bool body:is_vdamping() -> bool
-- Enables/disables vertical velocity attenuation -- Returns the vertical damping multiplier
body:set_vdamping(enabled: bool) 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 -- Checks if the entity is on the ground
body:is_grounded() -> bool body:is_grounded() -> bool
@ -188,6 +192,12 @@ function on_update(tps: int)
Called every entities tick (currently 20 times per second). Called every entities tick (currently 20 times per second).
```lua
function on_physics_update(delta: number)
```
Called after each physics step
```lua ```lua
function on_render(delta: number) function on_render(delta: number)
``` ```

View File

@ -20,6 +20,22 @@
] ]
``` ```
Из конфигурации сущности можно передавать значения в ARGS.
Они будут передаваться как при создании новой сущности, так и при загрузке сохранённой.
Для этого используется список `args`:
```json
"components": [
{
"name": "base:drop",
"args": {
"item": "base:stone.item",
"count": 1
}
}
]
```
Код компонентов должен находиться в `scripts/components`. Код компонентов должен находиться в `scripts/components`.
## Физика ## Физика

View File

@ -32,6 +32,7 @@
- [mat4](scripting/builtins/libmat4.md) - [mat4](scripting/builtins/libmat4.md)
- [network](scripting/builtins/libnetwork.md) - [network](scripting/builtins/libnetwork.md)
- [pack](scripting/builtins/libpack.md) - [pack](scripting/builtins/libpack.md)
- [pathfinding](scripting/builtins/libpathfinding.md)
- [player](scripting/builtins/libplayer.md) - [player](scripting/builtins/libplayer.md)
- [quat](scripting/builtins/libquat.md) - [quat](scripting/builtins/libquat.md)
- [rules](scripting/builtins/librules.md) - [rules](scripting/builtins/librules.md)

View 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
)
```

View File

@ -100,6 +100,13 @@ vecn.length(a: vector)
``` ```
#### Дистанция - *vecn.distance(...)*
```lua
-- возвращает расстояние между двумя векторами
vecn.distance(a: vector, b: vector)
```
#### Абсолютное значение - *vecn.abs(...)* #### Абсолютное значение - *vecn.abs(...)*
```lua ```lua
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
vecn.dot(a: vector, b: 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(...)* #### Перевод в строку - *vecn.tostring(...)*
> [!WARNING] > [!WARNING]
> Возвращает только тогда, когда содержимым является вектор > Возвращает только тогда, когда содержимым является вектор
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
-- возвращает угол направления вектора {x, y} в градусах [0, 360] -- возвращает угол направления вектора {x, y} в градусах [0, 360]
vec2.angle(x: number, y: number) 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) local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} 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) local result_len = vec3.length(v1_3d)
print("len: " .. result_len) -- 3 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) local result_dot = vec3.dot(v1_3d, v2_3d)
print("dot: " .. result_dot) -- 250 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}
``` ```

View File

@ -26,6 +26,8 @@ entity:get_uid() -> int
entity:get_component(name: str) -> компонент или nil entity:get_component(name: str) -> компонент или nil
-- Проверяет наличие компонента по имени -- Проверяет наличие компонента по имени
entity:has_component(name: str) -> bool entity:has_component(name: str) -> bool
-- Запрашивает компонент по имени. Бросает исключение при отсутствии
entity:require_component(name: str) -> компонент
-- Включает/выключает компонент по имени -- Включает/выключает компонент по имени
entity:set_enabled(name: str, enable: bool) entity:set_enabled(name: str, enable: bool)
@ -95,8 +97,10 @@ body:set_linear_damping(value: number)
-- Проверяет, включено ли вертикальное затухание скорости -- Проверяет, включено ли вертикальное затухание скорости
body:is_vdamping() -> bool body:is_vdamping() -> bool
-- Включает/выключает вертикальное затухание скорости -- Возвращает множитель вертикального затухания скорости
body:set_vdamping(enabled: bool) body:get_vdamping() -> number
-- Включает/выключает вертикальное затухание скорости / устанавливает значение множителя
body:set_vdamping(enabled: bool | number)
-- Проверяет, находится ли сущность на земле (приземлена) -- Проверяет, находится ли сущность на земле (приземлена)
body:is_grounded() -> bool body:is_grounded() -> bool
@ -188,6 +192,12 @@ function on_update(tps: int)
Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду). Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду).
```lua
function on_physics_update(delta: number)
```
Вызывается после каждого шага физики
```lua ```lua
function on_render(delta: number) function on_render(delta: number)
``` ```

View File

@ -8,5 +8,5 @@
"selectable": false, "selectable": false,
"replaceable": true, "replaceable": true,
"translucent": true, "translucent": true,
"tags": ["base:liquid"] "tags": ["core:liquid"]
} }

View File

@ -1,6 +1,13 @@
{ {
"components": [ "components": [
"base:drop" {
"name": "base:drop",
"args": {
"item": "base:stone.item",
"count": 1
}
}
], ],
"hitbox": [0.4, 0.25, 0.4], "hitbox": [0.4, 0.25, 0.4],
"sensors": [ "sensors": [

View File

@ -1,5 +1,12 @@
{ {
"components": [ "components": [
{
"name": "core:mob",
"args": {
"jump_force": 8.0
}
},
"core:player",
"base:player_animator" "base:player_animator"
], ],
"hitbox": [0.6, 1.8, 0.6] "hitbox": [0.6, 1.8, 0.6]

View File

@ -8,6 +8,9 @@ timer = 0.3
local def_index = entity:def_index() local def_index = entity:def_index()
dropitem = ARGS dropitem = ARGS
if dropitem.item then
dropitem.id = item.index(dropitem.item)
end
if dropitem then if dropitem then
timer = dropitem.pickup_delay or timer timer = dropitem.pickup_delay or timer
end end

View File

@ -1,11 +1,10 @@
local tsf = entity.transform local tsf = entity.transform
local body = entity.rigidbody local body = entity.rigidbody
local rig = entity.skeleton local rig = entity.skeleton
local mob = entity:require_component("core:mob")
local itemid = 0 local itemid = 0
local headIndex = rig:index("head")
local itemIndex = rig:index("item") local itemIndex = rig:index("item")
local bodyIndex = rig:index("body")
local function refresh_model(id) local function refresh_model(id)
itemid = id itemid = id
@ -19,9 +18,10 @@ function on_render()
return return
end end
local rx, ry, rz = player.get_rot(pid, pid ~= hud.get_player()) local rx, _, _ = 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 dir = vec2.rotate({0, -1}, -rx)
mob.set_dir({dir[1], 0, dir[2]})
local invid, slotid = player.get_inventory(pid) local invid, slotid = player.get_inventory(pid)
local id, _ = inventory.get(invid, slotid) local id, _ = inventory.get(invid, slotid)

View File

@ -110,6 +110,21 @@ function vec3.dot(a, b)
return a[1] * b[1] + a[2] * b[2] + a[3] * b[3] return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
end 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 ======================= -- -- ====================== vec2 ======================= --
-- =================================================== -- -- =================================================== --
@ -210,3 +225,16 @@ end
function vec2.dot(a, b) function vec2.dot(a, b)
return a[1] * b[1] + a[2] * b[2] return a[1] * b[1] + a[2] * b[2]
end 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

View File

@ -25,6 +25,7 @@ local Rigidbody = {__index={
get_linear_damping=function(self) return __rigidbody.get_linear_damping(self.eid) end, 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, 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, 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, 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_grounded=function(self) return __rigidbody.is_grounded(self.eid) end,
is_crouching=function(self) return __rigidbody.is_crouching(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, get_skeleton=function(self) return entities.get_skeleton(self.eid) end,
set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end, set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end,
get_component=function(self, name) return self.components[name] 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, has_component=function(self, name) return self.components[name] ~= nil end,
get_uid=function(self) return self.eid end, get_uid=function(self) return self.eid end,
def_index=function(self) return entities.get_def(self.eid) end, def_index=function(self) return entities.get_def(self.eid) end,
@ -125,6 +133,19 @@ return {
::continue:: ::continue::
end end
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) render = function(delta)
for _,entity in pairs(entities) do for _,entity in pairs(entities) do
for _, component in pairs(entity.components) do for _, component in pairs(entity.components) do

View File

@ -11,6 +11,9 @@ local Schedule = {
self._next_interval = id + 1 self._next_interval = id + 1
return id return id
end, end,
set_timeout = function(self, ms, callback)
self:set_interval(ms, callback, 1)
end,
tick = function(self, dt) tick = function(self, dt)
local timer = self._timer + dt local timer = self._timer + dt
for id, interval in pairs(self._intervals) do for id, interval in pairs(self._intervals) do

View 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

View 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

View 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

View File

@ -22,6 +22,39 @@ local function configure_SSAO()
-- for test purposes -- for test purposes
end 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() function on_hud_open()
input.add_callback("player.pick", function () input.add_callback("player.pick", function ()
if hud.is_paused() or hud.is_inventory_open() then if hud.is_paused() or hud.is_inventory_open() then
@ -81,39 +114,8 @@ function on_hud_open()
end) end)
configure_SSAO() configure_SSAO()
end
local function update_hand() hud.default_hand_controller = 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))
end end
function on_hud_render() function on_hud_render()

View File

@ -157,6 +157,16 @@ console.add_command(
end 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( console.add_command(
"entity.despawn entity:sel=$entity.selected", "entity.despawn entity:sel=$entity.selected",
"Despawn entity", "Despawn entity",

View File

@ -429,6 +429,8 @@ function __vc_on_hud_open()
hud.open_permanent("core:ingame_chat") hud.open_permanent("core:ingame_chat")
end end
local Schedule = require "core:schedule"
local ScheduleGroup_mt = { local ScheduleGroup_mt = {
__index = { __index = {
publish = function(self, schedule) publish = function(self, schedule)
@ -440,10 +442,11 @@ local ScheduleGroup_mt = {
for id, schedule in pairs(self._schedules) do for id, schedule in pairs(self._schedules) do
schedule:tick(dt) schedule:tick(dt)
end end
self.common:tick(dt)
end, end,
remove = function(self, id) remove = function(self, id)
self._schedules[id] = nil self._schedules[id] = nil
end end,
} }
} }
@ -451,6 +454,7 @@ local function ScheduleGroup()
return setmetatable({ return setmetatable({
_next_schedule = 1, _next_schedule = 1,
_schedules = {}, _schedules = {},
common = Schedule()
}, ScheduleGroup_mt) }, ScheduleGroup_mt)
end end

View File

@ -71,8 +71,8 @@ static auto process_program(const ResPaths& paths, const std::string& filename)
auto& preprocessor = *Shader::preprocessor; auto& preprocessor = *Shader::preprocessor;
auto vertex = preprocessor.process(vertexFile, vertexSource); auto vertex = preprocessor.process(vertexFile, vertexSource, false, {});
auto fragment = preprocessor.process(fragmentFile, fragmentSource); auto fragment = preprocessor.process(fragmentFile, fragmentSource, false, {});
return std::make_pair(vertex, fragment); return std::make_pair(vertex, fragment);
} }
@ -121,7 +121,7 @@ assetload::postfunc assetload::posteffect(
auto& preprocessor = *Shader::preprocessor; auto& preprocessor = *Shader::preprocessor;
preprocessor.addHeader( preprocessor.addHeader(
"__effect__", preprocessor.process(effectFile, effectSource, true) "__effect__", preprocessor.process(effectFile, effectSource, true, {})
); );
auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect"); auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect");

View File

@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) {
this->paths = paths; this->paths = paths;
} }
void GLSLExtension::setTraceOutput(bool enabled) {
this->traceOutput = enabled;
}
void GLSLExtension::loadHeader(const std::string& name) { void GLSLExtension::loadHeader(const std::string& name) {
if (paths == nullptr) { if (paths == nullptr) {
return; return;
@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) {
io::path file = paths->find("shaders/lib/" + name + ".glsl"); io::path file = paths->find("shaders/lib/" + name + ".glsl");
std::string source = io::read_string(file); std::string source = io::read_string(file);
addHeader(name, {}); addHeader(name, {});
addHeader(name, process(file, source, true)); addHeader(name, process(file, source, true, {}));
} }
void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) { 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> { class GLSLParser : public BasicParser<char> {
public: 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) { : BasicParser(file, source), glsl(glsl) {
if (!header) { if (!header) {
ss << "#version " << GLSLExtension::VERSION << '\n'; ss << "#version " << GLSLExtension::VERSION << '\n';
} for (auto& entry : defines) {
for (auto& entry : glsl.getDefines()) { ss << "#define " << entry << '\n';
ss << "#define " << entry.first << " " << entry.second << '\n'; }
for (auto& entry : defines) {
ss << "#define " << entry << '\n';
}
} }
uint linenum = 1; uint linenum = 1;
source_line(ss, linenum); source_line(ss, linenum);
@ -289,10 +302,34 @@ private:
std::stringstream ss; 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( 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(); std::string filename = file.string();
GLSLParser parser(*this, filename, source, header); GLSLParser parser(*this, filename, source, header, defines);
return parser.process(); auto result = parser.process();
if (traceOutput) {
trace_output(file, source, result);
}
return result;
} }

View File

@ -5,6 +5,7 @@
#include <vector> #include <vector>
#include "io/io.hpp" #include "io/io.hpp"
#include "data/setting.hpp"
#include "graphics/core/PostEffect.hpp" #include "graphics/core/PostEffect.hpp"
class ResPaths; class ResPaths;
@ -19,6 +20,7 @@ public:
}; };
void setPaths(const ResPaths* paths); void setPaths(const ResPaths* paths);
void setTraceOutput(bool enabled);
void define(const std::string& name, std::string value); void define(const std::string& name, std::string value);
void undefine(const std::string& name); void undefine(const std::string& name);
@ -37,7 +39,8 @@ public:
ProcessingResult process( ProcessingResult process(
const io::path& file, const io::path& file,
const std::string& source, const std::string& source,
bool header = false bool header,
const std::vector<std::string>& defines
); );
static inline std::string VERSION = "330 core"; static inline std::string VERSION = "330 core";
@ -46,4 +49,5 @@ private:
std::unordered_map<std::string, std::string> defines; std::unordered_map<std::string, std::string> defines;
const ResPaths* paths = nullptr; const ResPaths* paths = nullptr;
bool traceOutput = false;
}; };

View File

@ -30,7 +30,18 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
if (auto found = root.at("components")) { if (auto found = root.at("components")) {
for (const auto& elem : *found) { 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")) { if (auto found = root.at("hitbox")) {

View File

@ -46,12 +46,12 @@ namespace dv {
if (!map.has(key)) { if (!map.has(key)) {
return; return;
} }
auto& list = map[key]; const auto& srcList = map[key];
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
if constexpr (std::is_floating_point<T>()) { if constexpr (std::is_floating_point<T>()) {
vec[i] = list[i].asNumber(); vec[i] = srcList[i].asNumber();
} else { } else {
vec[i] = list[i].asInteger(); vec[i] = srcList[i].asInteger();
} }
} }
} }

View File

@ -143,6 +143,12 @@ void Engine::initializeClient() {
}, },
true true
)); ));
keepAlive(settings.debug.doTraceShaders.observe(
[](bool value) {
Shader::preprocessor->setTraceOutput(value);
},
true
));
} }
void Engine::initialize(CoreParameters coreParameters) { void Engine::initialize(CoreParameters coreParameters) {

View File

@ -12,12 +12,14 @@
#include "graphics/render/WorldRenderer.hpp" #include "graphics/render/WorldRenderer.hpp"
#include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp"
#include "graphics/render/ChunksRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp"
#include "graphics/render/DebugLinesRenderer.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "network/Network.hpp" #include "network/Network.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/EntityDef.hpp" #include "objects/EntityDef.hpp"
#include "objects/Entity.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "voxels/Block.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
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally
std::shared_ptr<UINode> create_debug_panel( std::shared_ptr<UINode> create_debug_panel(
Engine& engine, Engine& engine,
Level& level, Level& level,
@ -260,6 +263,18 @@ std::shared_ptr<UINode> create_debug_panel(
}); });
panel->add(checkbox); 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>( auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Generator Minimap", glm::vec2(400, 24) gui, L"Show Generator Minimap", glm::vec2(400, 24)

View File

@ -176,7 +176,7 @@ void LevelScreen::saveWorldPreview() {
static_cast<uint>(previewSize)} 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(); auto image = postProcessing->toImage();
image->flipY(); image->flipY();
imageio::write("world:preview.png", image.get()); imageio::write("world:preview.png", image.get());
@ -263,7 +263,7 @@ void LevelScreen::draw(float delta) {
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta()); scripting::on_entities_render(engine.getTime().getDelta());
} }
renderer->draw( renderer->renderFrame(
ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
); );

View File

@ -1,5 +1,6 @@
#include "ImageData.hpp" #include "ImageData.hpp"
#include <glm/glm.hpp>
#include <assert.h> #include <assert.h>
#include <stdexcept> #include <stdexcept>
#include <cstring> #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) { void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
ubyte* source = image.getData(); ubyte* source = image.getData();
uint srcwidth = image.getWidth(); uint srcwidth = image.getWidth();

View File

@ -28,6 +28,7 @@ public:
void flipY(); void flipY();
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); 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 blit(const ImageData& image, int x, int y);
void extrude(int x, int y, int w, int h); void extrude(int x, int y, int w, int h);
void fixAlphaColor(); void fixAlphaColor();

View File

@ -2,7 +2,6 @@
#include <exception> #include <exception>
#include <fstream> #include <fstream>
#include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
@ -138,15 +137,21 @@ glshader compile_shader(GLenum type, const GLchar* source, const std::string& fi
} }
static GLuint compile_program( 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& preprocessor = *Shader::preprocessor;
auto vertexCode = std::move( auto vertexCode = std::move(
preprocessor.process(vertexSource.file, vertexSource.code).code preprocessor
.process(vertexSource.file, vertexSource.code, false, defines)
.code
); );
auto fragmentCode = std::move( 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(); const GLchar* vCode = vertexCode.c_str();
@ -176,8 +181,8 @@ static GLuint compile_program(
return program; return program;
} }
void Shader::recompile() { void Shader::recompile(const std::vector<std::string>& defines) {
GLuint newProgram = compile_program(vertexSource, fragmentSource); GLuint newProgram = compile_program(vertexSource, fragmentSource, defines);
glDeleteProgram(id); glDeleteProgram(id);
id = newProgram; id = newProgram;
uniformLocations.clear(); uniformLocations.clear();
@ -188,7 +193,7 @@ std::unique_ptr<Shader> Shader::create(
Source&& vertexSource, Source&& fragmentSource Source&& vertexSource, Source&& fragmentSource
) { ) {
return std::make_unique<Shader>( return std::make_unique<Shader>(
compile_program(vertexSource, fragmentSource), compile_program(vertexSource, fragmentSource, {}),
std::move(vertexSource), std::move(vertexSource),
std::move(fragmentSource) std::move(fragmentSource)
); );

View File

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector>
#include <unordered_map> #include <unordered_map>
#include <glm/glm.hpp> #include <glm/glm.hpp>
@ -50,7 +51,7 @@ public:
void uniform4v(const std::string& name, int length, const float* v); void uniform4v(const std::string& name, int length, const float* v);
/// @brief Re-preprocess source code and re-compile shader program /// @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. /// @brief Create shader program using vertex and fragment shaders source.
/// @return linked shader program containing vertex and fragment shaders /// @return linked shader program containing vertex and fragment shaders

View File

@ -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;
}

View File

@ -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;
};

View 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();
}
}

View 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
);
};

View File

@ -187,7 +187,7 @@ const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
return mesh; return mesh;
} }
void ChunksRenderer::drawChunksShadowsPass( void ChunksRenderer::drawShadowsPass(
const Camera& camera, Shader& shader, const Camera& playerCamera const Camera& camera, Shader& shader, const Camera& playerCamera
) { ) {
Frustum frustum; Frustum frustum;

View File

@ -73,7 +73,7 @@ public:
const std::shared_ptr<Chunk>& chunk, bool important const std::shared_ptr<Chunk>& chunk, bool important
); );
void drawChunksShadowsPass( void drawShadowsPass(
const Camera& camera, Shader& shader, const Camera& playerCamera const Camera& camera, Shader& shader, const Camera& playerCamera
); );

View File

@ -1,15 +1,51 @@
#include "GuidesRenderer.hpp" #include "DebugLinesRenderer.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include "graphics/core/Shader.hpp" #include "graphics/core/Shader.hpp"
#include "window/Camera.hpp"
#include "graphics/core/LineBatch.hpp" #include "graphics/core/LineBatch.hpp"
#include "graphics/core/DrawContext.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 "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 LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
) { ) {
int ww = ex - sx; int ww = ex - sx;
@ -37,7 +73,7 @@ void GuidesRenderer::drawBorders(
batch.flush(); batch.flush();
} }
void GuidesRenderer::drawCoordSystem( void DebugLinesRenderer::drawCoordSystem(
LineBatch& batch, const DrawContext& pctx, float length LineBatch& batch, const DrawContext& pctx, float length
) { ) {
auto ctx = pctx.sub(); 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); 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, const Camera& camera,
LineBatch& batch, LinesRenderer& renderer,
LineBatch& linesBatch,
Shader& linesShader, Shader& linesShader,
bool showChunkBorders 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(); const auto& viewport = ctx.getViewport();
ctx.setDepthTest(true); ctx.setDepthTest(true);
@ -78,7 +122,7 @@ void GuidesRenderer::renderDebugLines(
int cz = floordiv(static_cast<int>(coord.z), CHUNK_D); int cz = floordiv(static_cast<int>(coord.z), CHUNK_D);
drawBorders( drawBorders(
batch, linesBatch,
cx * CHUNK_W, cx * CHUNK_W,
0, 0,
cz * CHUNK_D, cz * CHUNK_D,
@ -103,5 +147,5 @@ void GuidesRenderer::renderDebugLines(
) * model * ) * model *
glm::inverse(camera.rotation) glm::inverse(camera.rotation)
); );
drawCoordSystem(batch, ctx, length); drawCoordSystem(linesBatch, ctx, length);
} }

View 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
);
};

View File

@ -15,6 +15,7 @@
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "logic/LevelController.hpp" #include "logic/LevelController.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"

View File

@ -7,6 +7,7 @@
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "graphics/core/Texture.hpp" #include "graphics/core/Texture.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
Emitter::Emitter( Emitter::Emitter(

View File

@ -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
);
};

View 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});
}

View 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;
};

View File

@ -27,6 +27,7 @@
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/Pathfinding.hpp"
#include "window/Window.hpp" #include "window/Window.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "world/LevelEvents.hpp" #include "world/LevelEvents.hpp"
@ -42,7 +43,7 @@
#include "graphics/core/Shader.hpp" #include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp" #include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp" #include "graphics/core/Font.hpp"
#include "graphics/core/ShadowMap.hpp" #include "graphics/core/Shadows.hpp"
#include "graphics/core/GBuffer.hpp" #include "graphics/core/GBuffer.hpp"
#include "BlockWrapsRenderer.hpp" #include "BlockWrapsRenderer.hpp"
#include "ParticlesRenderer.hpp" #include "ParticlesRenderer.hpp"
@ -51,7 +52,8 @@
#include "NamedSkeletons.hpp" #include "NamedSkeletons.hpp"
#include "TextsRenderer.hpp" #include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp" #include "ChunksRenderer.hpp"
#include "GuidesRenderer.hpp" #include "LinesRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "ModelBatch.hpp" #include "ModelBatch.hpp"
#include "Skybox.hpp" #include "Skybox.hpp"
#include "Emitter.hpp" #include "Emitter.hpp"
@ -61,8 +63,6 @@ using namespace advanced_pipeline;
inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; 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::showChunkBorders = false;
bool WorldRenderer::showEntitiesDebug = false; bool WorldRenderer::showEntitiesDebug = false;
@ -80,8 +80,7 @@ WorldRenderer::WorldRenderer(
modelBatch(std::make_unique<ModelBatch>( modelBatch(std::make_unique<ModelBatch>(
MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings() MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings()
)), )),
guides(std::make_unique<GuidesRenderer>()), chunksRenderer(std::make_unique<ChunksRenderer>(
chunks(std::make_unique<ChunksRenderer>(
&level, &level,
*player.chunks, *player.chunks,
assets, assets,
@ -102,7 +101,7 @@ WorldRenderer::WorldRenderer(
auto& settings = engine.getSettings(); auto& settings = engine.getSettings();
level.events->listen( level.events->listen(
LevelEventType::CHUNK_HIDDEN, LevelEventType::CHUNK_HIDDEN,
[this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); } [this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
); );
auto assets = engine.getAssets(); auto assets = engine.getAssets();
skybox = std::make_unique<Skybox>( skybox = std::make_unique<Skybox>(
@ -118,10 +117,26 @@ WorldRenderer::WorldRenderer(
hands = std::make_unique<HandsRenderer>( hands = std::make_unique<HandsRenderer>(
*assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig) *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; 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( void WorldRenderer::setupWorldShader(
Shader& shader, Shader& shader,
const Camera& camera, const Camera& camera,
@ -129,45 +144,20 @@ void WorldRenderer::setupWorldShader(
float fogFactor float fogFactor
) { ) {
shader.use(); shader.use();
shader.uniformMatrix("u_model", glm::mat4(1.0f));
shader.uniformMatrix("u_proj", camera.getProjection()); setup_camera(shader, camera);
shader.uniformMatrix("u_view", camera.getView()); setup_weather(shader, weather);
shadowMapping->setup(shader, weather);
shader.uniform1f("u_timer", timer); shader.uniform1f("u_timer", timer);
shader.uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_gamma", settings.graphics.gamma.get());
shader.uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogFactor", fogFactor);
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
shader.uniform1i("u_debugLights", lightsDebug); shader.uniform1i("u_debugLights", lightsDebug);
shader.uniform1i("u_debugNormals", false); 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.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform3f("u_cameraPos", camera.position); shader.uniform1i("u_skybox", TARGET_SKYBOX);
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);
}
auto indices = level.content.getIndices(); auto indices = level.content.getIndices();
// Light emission when an emissive item is chosen // 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 DrawContext& ctx,
const Camera& camera, const Camera& camera,
const EngineSettings& settings, const EngineSettings& settings,
@ -215,7 +205,9 @@ void WorldRenderer::renderLevel(
*modelBatch, *modelBatch,
culling ? frustumCulling.get() : nullptr, culling ? frustumCulling.get() : nullptr,
delta, delta,
pause pause,
player.currentCamera.get() == player.fpCamera.get() ? player.getEntity()
: 0
); );
modelBatch->render(); modelBatch->render();
particles->render(camera, delta * !pause); particles->render(camera, delta * !pause);
@ -225,7 +217,7 @@ void WorldRenderer::renderLevel(
setupWorldShader(shader, camera, settings, fogFactor); setupWorldShader(shader, camera, settings, fogFactor);
chunks->drawChunks(camera, shader); chunksRenderer->drawChunks(camera, shader);
blockWraps->draw(ctx, player); blockWraps->draw(ctx, player);
if (hudVisible) { if (hudVisible) {
@ -284,79 +276,7 @@ void WorldRenderer::renderLines(
} }
} }
void WorldRenderer::generateShadowsMap( void WorldRenderer::renderFrame(
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(
const DrawContext& pctx, const DrawContext& pctx,
Camera& camera, Camera& camera,
bool hudVisible, bool hudVisible,
@ -365,6 +285,9 @@ void WorldRenderer::draw(
PostProcessing& postProcessing PostProcessing& postProcessing
) { ) {
// TODO: REFACTOR WHOLE RENDER ENGINE // TODO: REFACTOR WHOLE RENDER ENGINE
auto projView = camera.getProjView();
float delta = uiDelta * !pause; float delta = uiDelta * !pause;
timer += delta; timer += delta;
weather.update(delta); weather.update(delta);
@ -380,22 +303,17 @@ void WorldRenderer::draw(
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader(); auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
Shader* affectedShaders[] {
&mainShader, &entityShader, &translucentShader, &deferredShader
};
gbufferPipeline = settings.graphics.advancedRender.get(); gbufferPipeline = settings.graphics.advancedRender.get();
int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline; int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline;
int resolution = MIN_SHADOW_MAP_RES << shadowsQuality; shadowMapping->setQuality(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;
}
CompileTimeShaderSettings currentSettings { CompileTimeShaderSettings currentSettings {
gbufferPipeline, gbufferPipeline,
shadows, shadowsQuality != 0,
settings.graphics.ssao.get() && gbufferPipeline settings.graphics.ssao.get() && gbufferPipeline
}; };
if ( if (
@ -403,19 +321,15 @@ void WorldRenderer::draw(
prevCTShaderSettings.shadows != currentSettings.shadows || prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao prevCTShaderSettings.ssao != currentSettings.ssao
) { ) {
Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows); std::vector<std::string> defines;
Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao); if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender); if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
mainShader.recompile(); if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
entityShader.recompile();
deferredShader.recompile();
translucentShader.recompile();
prevCTShaderSettings = currentSettings;
}
if (shadows && shadowMap->getResolution() != resolution) { for (auto shader : affectedShaders) {
shadowMap = std::make_unique<ShadowMap>(resolution); shader->recompile(defines);
wideShadowMap = std::make_unique<ShadowMap>(resolution); }
prevCTShaderSettings = currentSettings;
} }
const auto& worldInfo = world->getInfo(); const auto& worldInfo = world->getInfo();
@ -426,38 +340,24 @@ void WorldRenderer::draw(
skybox->refresh(pctx, worldInfo.daytime, mie, 4); skybox->refresh(pctx, worldInfo.daytime, mie, 4);
chunks->update(); chunksRenderer->update();
static int frameid = 0; shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
if (shadows) { auto& shader = assets.require<Shader>("shadows");
if (frameid % 2 == 0) { setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f); chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
} else { });
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f); {
}
}
frameid++;
auto& linesShader = assets.require<Shader>("lines");
/* World render scope with diegetic HUD included */ {
DrawContext wctx = pctx.sub(); DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline); postProcessing.use(wctx, gbufferPipeline);
display::clearDepth(); display::clearDepth();
/* Actually world render with depth buffer on */ { /* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub(); DrawContext ctx = wctx.sub();
ctx.setDepthTest(true); ctx.setDepthTest(true);
ctx.setCullFace(true); ctx.setCullFace(true);
renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible); renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible);
// Debug lines
if (hudVisible) {
if (debug) {
guides->renderDebugLines(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
}
}
} }
texts->render(pctx, camera, settings, hudVisible, true); texts->render(pctx, camera, settings, hudVisible, true);
} }
@ -478,19 +378,33 @@ void WorldRenderer::draw(
} else { } else {
postProcessing.getFramebuffer()->bind(); postProcessing.getFramebuffer()->bind();
} }
// Drawing background sky plane
// Background sky plane
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); 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(); auto sctx = ctx.sub();
sctx.setCullFace(true); sctx.setCullFace(true);
skybox->bind(); skybox->bind();
translucentShader.use(); translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor); setupWorldShader(translucentShader, camera, settings, fogFactor);
chunks->drawSortedMeshes(camera, translucentShader); chunksRenderer->drawSortedMeshes(camera, translucentShader);
skybox->unbind(); skybox->unbind();
} }
// Weather effects
entityShader.use(); entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor); setupWorldShader(entityShader, camera, settings, fogFactor);
@ -582,7 +496,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
} }
void WorldRenderer::clear() { void WorldRenderer::clear() {
chunks->clear(); chunksRenderer->clear();
} }
void WorldRenderer::setDebug(bool flag) { void WorldRenderer::setDebug(bool flag) {

View File

@ -22,7 +22,7 @@ class BlockWrapsRenderer;
class PrecipitationRenderer; class PrecipitationRenderer;
class HandsRenderer; class HandsRenderer;
class NamedSkeletons; class NamedSkeletons;
class GuidesRenderer; class LinesRenderer;
class TextsRenderer; class TextsRenderer;
class Shader; class Shader;
class Frustum; class Frustum;
@ -33,8 +33,9 @@ class PostProcessing;
class DrawContext; class DrawContext;
class ModelBatch; class ModelBatch;
class Assets; class Assets;
class ShadowMap; class Shadows;
class GBuffer; class GBuffer;
class DebugLinesRenderer;
struct EngineSettings; struct EngineSettings;
struct CompileTimeShaderSettings { struct CompileTimeShaderSettings {
@ -52,21 +53,17 @@ class WorldRenderer {
std::unique_ptr<LineBatch> lineBatch; std::unique_ptr<LineBatch> lineBatch;
std::unique_ptr<Batch3D> batch3d; std::unique_ptr<Batch3D> batch3d;
std::unique_ptr<ModelBatch> modelBatch; std::unique_ptr<ModelBatch> modelBatch;
std::unique_ptr<GuidesRenderer> guides; std::unique_ptr<ChunksRenderer> chunksRenderer;
std::unique_ptr<ChunksRenderer> chunks;
std::unique_ptr<HandsRenderer> hands; std::unique_ptr<HandsRenderer> hands;
std::unique_ptr<Skybox> skybox; std::unique_ptr<Skybox> skybox;
std::unique_ptr<ShadowMap> shadowMap; std::unique_ptr<Shadows> shadowMapping;
std::unique_ptr<ShadowMap> wideShadowMap; std::unique_ptr<DebugLinesRenderer> debugLines;
Weather weather {}; Weather weather {};
Camera shadowCamera;
Camera wideShadowCamera;
float timer = 0.0f; float timer = 0.0f;
bool debug = false; bool debug = false;
bool lightsDebug = false; bool lightsDebug = false;
bool gbufferPipeline = false; bool gbufferPipeline = false;
bool shadows = false;
CompileTimeShaderSettings prevCTShaderSettings {}; CompileTimeShaderSettings prevCTShaderSettings {};
@ -89,12 +86,17 @@ class WorldRenderer {
float fogFactor float fogFactor
); );
void generateShadowsMap( /// @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 Camera& camera,
const DrawContext& pctx, const EngineSettings& settings,
ShadowMap& shadowMap, float delta,
Camera& shadowCamera, bool pause,
float scale bool hudVisible
); );
public: public:
std::unique_ptr<ParticlesRenderer> particles; std::unique_ptr<ParticlesRenderer> particles;
@ -102,6 +104,7 @@ public:
std::unique_ptr<BlockWrapsRenderer> blockWraps; std::unique_ptr<BlockWrapsRenderer> blockWraps;
std::unique_ptr<PrecipitationRenderer> precipitation; std::unique_ptr<PrecipitationRenderer> precipitation;
std::unique_ptr<NamedSkeletons> skeletons; std::unique_ptr<NamedSkeletons> skeletons;
std::unique_ptr<LinesRenderer> lines;
static bool showChunkBorders; static bool showChunkBorders;
static bool showEntitiesDebug; static bool showEntitiesDebug;
@ -109,7 +112,7 @@ public:
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player); WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
~WorldRenderer(); ~WorldRenderer();
void draw( void renderFrame(
const DrawContext& context, const DrawContext& context,
Camera& camera, Camera& camera,
bool hudVisible, bool hudVisible,
@ -118,19 +121,6 @@ public:
PostProcessing& postProcessing 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 clear();
void setDebug(bool flag); void setDebug(bool flag);

View File

@ -83,9 +83,13 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.add("language", &settings.ui.language); builder.add("language", &settings.ui.language);
builder.add("world-preview-size", &settings.ui.worldPreviewSize); builder.add("world-preview-size", &settings.ui.worldPreviewSize);
builder.section("pathfinding");
builder.add("steps-per-async-agent", &settings.pathfinding.stepsPerAsyncAgent);
builder.section("debug"); builder.section("debug");
builder.add("generator-test-mode", &settings.debug.generatorTestMode); builder.add("generator-test-mode", &settings.debug.generatorTestMode);
builder.add("do-write-lights", &settings.debug.doWriteLights); builder.add("do-write-lights", &settings.debug.doWriteLights);
builder.add("do-trace-shaders", &settings.debug.doTraceShaders);
builder.add("enable-experimental", &settings.debug.enableExperimental); builder.add("enable-experimental", &settings.debug.enableExperimental);
} }

View File

@ -11,6 +11,7 @@
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "voxels/Pathfinding.hpp"
#include "scripting/scripting.hpp" #include "scripting/scripting.hpp"
#include "lighting/Lighting.hpp" #include "lighting/Lighting.hpp"
#include "settings.hpp" #include "settings.hpp"
@ -69,6 +70,9 @@ LevelController::LevelController(
} }
void LevelController::update(float delta, bool pause) { void LevelController::update(float delta, bool pause) {
level->pathfinding->performAllAsync(
settings.pathfinding.stepsPerAsyncAgent.get()
);
for (const auto& [_, player] : *level->players) { for (const auto& [_, player] : *level->players) {
if (player->isSuspended()) { if (player->isSuspended()) {
continue; continue;
@ -91,7 +95,6 @@ void LevelController::update(float delta, bool pause) {
if (!pause) { if (!pause) {
// update all objects that needed // update all objects that needed
blocks->update(delta, settings.chunks.padding.get()); blocks->update(delta, settings.chunks.padding.get());
level->entities->updatePhysics(delta);
level->entities->update(delta); level->entities->update(delta);
for (const auto& [_, player] : *level->players) { for (const auto& [_, player] : *level->players) {
if (player->isSuspended()) { if (player->isSuspended()) {

View File

@ -13,6 +13,7 @@
#include "items/ItemStack.hpp" #include "items/ItemStack.hpp"
#include "lighting/Lighting.hpp" #include "lighting/Lighting.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
@ -270,7 +271,6 @@ void PlayerController::update(float delta, const Input* inputEvents) {
} else { } else {
resetKeyboard(); resetKeyboard();
} }
updatePlayer(delta);
} }
void PlayerController::postUpdate( void PlayerController::postUpdate(
@ -309,20 +309,7 @@ void PlayerController::updateKeyboard(const Input& inputEvents) {
} }
void PlayerController::resetKeyboard() { void PlayerController::resetKeyboard() {
input.zoom = false; input = {};
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);
} }
static int determine_rotation( 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_NORTH;
if (norm.z < 0.0f) return BLOCK_DIR_SOUTH; if (norm.z < 0.0f) return BLOCK_DIR_SOUTH;
} else if (name == "pane" || name == "stairs") { } 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 (abs(camDir.x) > abs(camDir.z)) {
if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit; if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit;
if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit; if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit;

View File

@ -60,7 +60,6 @@ class PlayerController {
void updateKeyboard(const Input& inputEvents); void updateKeyboard(const Input& inputEvents);
void resetKeyboard(); void resetKeyboard();
void updatePlayer(float delta);
void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick); void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick);
void updateInteraction(const Input& inputEvents, float delta); void updateInteraction(const Input& inputEvents, float delta);

View File

@ -38,6 +38,7 @@ extern const luaL_Reg mat4lib[];
extern const luaL_Reg networklib[]; extern const luaL_Reg networklib[];
extern const luaL_Reg packlib[]; extern const luaL_Reg packlib[];
extern const luaL_Reg particleslib[]; // gfx.particles extern const luaL_Reg particleslib[]; // gfx.particles
extern const luaL_Reg pathfindinglib[];
extern const luaL_Reg playerlib[]; extern const luaL_Reg playerlib[];
extern const luaL_Reg posteffectslib[]; // gfx.posteffects extern const luaL_Reg posteffectslib[]; // gfx.posteffects
extern const luaL_Reg quatlib[]; extern const luaL_Reg quatlib[];

View File

@ -62,6 +62,15 @@ static int l_set_gravity_scale(lua::State* L) {
static int l_is_vdamping(lua::State* L) { static int l_is_vdamping(lua::State* L) {
if (auto entity = get_entity(L, 1)) { if (auto entity = get_entity(L, 1)) {
return lua::pushboolean( 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 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) { static int l_set_vdamping(lua::State* L) {
if (auto entity = get_entity(L, 1)) { 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; return 0;
} }
@ -144,6 +157,7 @@ const luaL_Reg rigidbodylib[] = {
{"get_linear_damping", lua::wrap<l_get_linear_damping>}, {"get_linear_damping", lua::wrap<l_get_linear_damping>},
{"set_linear_damping", lua::wrap<l_set_linear_damping>}, {"set_linear_damping", lua::wrap<l_set_linear_damping>},
{"is_vdamping", lua::wrap<l_is_vdamping>}, {"is_vdamping", lua::wrap<l_is_vdamping>},
{"get_vdamping", lua::wrap<l_get_vdamping>},
{"set_vdamping", lua::wrap<l_set_vdamping>}, {"set_vdamping", lua::wrap<l_set_vdamping>},
{"is_grounded", lua::wrap<l_is_grounded>}, {"is_grounded", lua::wrap<l_is_grounded>},
{"is_crouching", lua::wrap<l_is_crouching>}, {"is_crouching", lua::wrap<l_is_crouching>},

View File

@ -91,7 +91,7 @@ static int l_set_texture(lua::State* L) {
} }
static int l_index(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))) { if (auto bone = skeleton->config->find(lua::require_string(L, 2))) {
return lua::pushinteger(L, bone->getIndex()); return lua::pushinteger(L, bone->getIndex());
} }

View File

@ -4,6 +4,8 @@
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/EntityDef.hpp" #include "objects/EntityDef.hpp"
#include "objects/Entity.hpp"
#include "objects/Rigidbody.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/rigging.hpp" #include "objects/rigging.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"

View File

@ -4,6 +4,7 @@
#include "frontend/hud.hpp" #include "frontend/hud.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "logic/LevelController.hpp" #include "logic/LevelController.hpp"
#include "api_lua.hpp" #include "api_lua.hpp"

View File

@ -31,6 +31,8 @@ static int l_mousecode(lua::State* L) {
} }
static int l_add_callback(lua::State* L) { static int l_add_callback(lua::State* L) {
if (engine->isHeadless())
return 0;
std::string bindname = lua::require_string(L, 1); std::string bindname = lua::require_string(L, 1);
size_t pos = bindname.find(':'); 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) { static int l_get_mouse_pos(lua::State* L) {
if (engine->isHeadless())
return 0;
return lua::pushvec2(L, engine->getInput().getCursor().pos); return lua::pushvec2(L, engine->getInput().getCursor().pos);
} }
static int l_get_bindings(lua::State* L) { static int l_get_bindings(lua::State* L) {
if (engine->isHeadless())
return 0;
const auto& bindings = engine->getInput().getBindings().getAll(); const auto& bindings = engine->getInput().getBindings().getAll();
lua::createtable(L, bindings.size(), 0); 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) { static int l_get_binding_text(lua::State* L) {
if (engine->isHeadless())
return 0;
auto bindname = lua::require_string(L, 1); auto bindname = lua::require_string(L, 1);
const auto& bind = engine->getInput().getBindings().require(bindname); const auto& bind = engine->getInput().getBindings().require(bindname);
return lua::pushstring(L, bind.text()); return lua::pushstring(L, bind.text());
} }
static int l_is_active(lua::State* L) { static int l_is_active(lua::State* L) {
if (engine->isHeadless())
return 0;
auto bindname = lua::require_string(L, 1); auto bindname = lua::require_string(L, 1);
auto& bind = engine->getInput().getBindings().require(bindname); auto& bind = engine->getInput().getBindings().require(bindname);
return lua::pushboolean(L, bind.active()); return lua::pushboolean(L, bind.active());
} }
static int l_is_pressed(lua::State* L) { static int l_is_pressed(lua::State* L) {
if (engine->isHeadless())
return 0;
std::string code = lua::require_string(L, 1); std::string code = lua::require_string(L, 1);
size_t sep = code.find(':'); size_t sep = code.find(':');
if (sep == std::string::npos) { 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*) { static int l_reset_bindings(lua::State*) {
if (engine->isHeadless())
return 0;
reset_pack_bindings("res:"); reset_pack_bindings("res:");
for (const auto& pack : content_control->getContentPacks()) { for (const auto& pack : content_control->getContentPacks()) {
reset_pack_bindings(pack.folder); reset_pack_bindings(pack.folder);
@ -144,6 +158,8 @@ static int l_reset_bindings(lua::State*) {
} }
static int l_set_enabled(lua::State* L) { static int l_set_enabled(lua::State* L) {
if (engine->isHeadless())
return 0;
std::string bindname = lua::require_string(L, 1); std::string bindname = lua::require_string(L, 1);
bool enabled = lua::toboolean(L, 2); bool enabled = lua::toboolean(L, 2);
engine->getInput().getBindings().require(bindname).enabled = enabled; engine->getInput().getBindings().require(bindname).enabled = enabled;
@ -161,4 +177,5 @@ const luaL_Reg inputlib[] = {
{"is_pressed", lua::wrap<l_is_pressed>}, {"is_pressed", lua::wrap<l_is_pressed>},
{"reset_bindings", lua::wrap<l_reset_bindings>}, {"reset_bindings", lua::wrap<l_reset_bindings>},
{"set_enabled", lua::wrap<l_set_enabled>}, {"set_enabled", lua::wrap<l_set_enabled>},
{NULL, NULL}}; {NULL, NULL}
};

View 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}
};

View File

@ -5,6 +5,7 @@
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "objects/Entity.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"

View File

@ -15,10 +15,24 @@ inline T angle(glm::vec<2, T> vec) {
return val; 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> template <int n, template <class> class Op>
static int l_binop(lua::State* L) { static int l_binop(lua::State* L) {
uint argc = lua::check_argc(L, 2, 3); 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 if (lua::isnumber(L, 2)) { // scalar second operand overload
auto b = lua::tonumber(L, 2); auto b = lua::tonumber(L, 2);
@ -31,10 +45,10 @@ static int l_binop(lua::State* L) {
} }
return 1; return 1;
} else { } 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 { } else {
auto b = lua::tovec<n>(L, 2); auto b = lua::tovec<n, number_t>(L, 2);
Op op; Op op;
if (argc == 2) { if (argc == 2) {
lua::createtable(L, n, 0); 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) { static int l_unaryop(lua::State* L) {
uint argc = lua::check_argc(L, 1, 2); 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) { switch (argc) {
case 1: case 1:
lua::createtable(L, n, 0); lua::createtable(L, n, 0);
@ -67,17 +81,25 @@ static int l_unaryop(lua::State* L) {
return 0; 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) { static int l_scalar_op(lua::State* L) {
lua::check_argc(L, 1); 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)); 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> template <int n>
static int l_pow(lua::State* L) { static int l_pow(lua::State* L) {
uint argc = lua::check_argc(L, 2, 3); 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)) { if (lua::isnumber(L, 2)) {
auto b = lua::tonumber(L, 2); auto b = lua::tonumber(L, 2);
@ -89,10 +111,10 @@ static int l_pow(lua::State* L) {
} }
return 1; return 1;
} else { } 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 { } else {
auto b = lua::tovec<n>(L, 2); auto b = lua::tovec<n, number_t>(L, 2);
if (argc == 2) { if (argc == 2) {
lua::createtable(L, n, 0); lua::createtable(L, n, 0);
for (uint i = 0; i < n; i++) { for (uint i = 0; i < n; i++) {
@ -109,15 +131,15 @@ static int l_pow(lua::State* L) {
template <int n> template <int n>
static int l_dot(lua::State* L) { static int l_dot(lua::State* L) {
lua::check_argc(L, 2); lua::check_argc(L, 2);
const auto& a = lua::tovec<n>(L, 1); auto a = lua::tovec<n, number_t>(L, 1);
const auto& b = lua::tovec<n>(L, 2); auto b = lua::tovec<n, number_t>(L, 2);
return lua::pushnumber(L, glm::dot(a, b)); return lua::pushnumber(L, glm::dot(a, b));
} }
template <int n> template <int n>
static int l_inverse(lua::State* L) { static int l_inverse(lua::State* L) {
uint argc = lua::check_argc(L, 1, 2); 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) { switch (argc) {
case 1: case 1:
lua::createtable(L, n, 0); lua::createtable(L, n, 0);
@ -141,7 +163,7 @@ static int l_spherical_rand(lua::State* L) {
return lua::setvec( return lua::setvec(
L, L,
2, 2,
glm::sphericalRand(static_cast<float>(lua::tonumber(L, 1))) glm::sphericalRand(lua::tonumber(L, 1))
); );
} }
return 0; 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> template <int n>
static int l_tostring(lua::State* L) { static int l_tostring(lua::State* L) {
lua::check_argc(L, 1); lua::check_argc(L, 1);
auto vec = lua::tovec<n>(L, 1); auto vec = lua::tovec<n, number_t>(L, 1);
std::stringstream ss; std::stringstream ss;
ss << "vec" << std::to_string(n) << "{"; ss << "vec" << std::to_string(n) << "{";
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
@ -182,6 +216,7 @@ const luaL_Reg vec2lib[] = {
{"sub", lua::wrap<l_binop<2, std::minus>>}, {"sub", lua::wrap<l_binop<2, std::minus>>},
{"mul", lua::wrap<l_binop<2, std::multiplies>>}, {"mul", lua::wrap<l_binop<2, std::multiplies>>},
{"div", lua::wrap<l_binop<2, std::divides>>}, {"div", lua::wrap<l_binop<2, std::divides>>},
{"distance", lua::wrap<l_distance<2>>},
{"normalize", lua::wrap<l_unaryop<2, glm::normalize>>}, {"normalize", lua::wrap<l_unaryop<2, glm::normalize>>},
{"length", lua::wrap<l_scalar_op<2, glm::length>>}, {"length", lua::wrap<l_scalar_op<2, glm::length>>},
{"tostring", lua::wrap<l_tostring<2>>}, {"tostring", lua::wrap<l_tostring<2>>},
@ -191,6 +226,8 @@ const luaL_Reg vec2lib[] = {
{"pow", lua::wrap<l_pow<2>>}, {"pow", lua::wrap<l_pow<2>>},
{"dot", lua::wrap<l_dot<2>>}, {"dot", lua::wrap<l_dot<2>>},
{"angle", lua::wrap<l_vec2_angle>}, {"angle", lua::wrap<l_vec2_angle>},
{"mix", lua::wrap<l_mix<2>>},
{"rotate", lua::wrap<l_vec2_rotate>},
{NULL, NULL}}; {NULL, NULL}};
const luaL_Reg vec3lib[] = { const luaL_Reg vec3lib[] = {
@ -198,6 +235,7 @@ const luaL_Reg vec3lib[] = {
{"sub", lua::wrap<l_binop<3, std::minus>>}, {"sub", lua::wrap<l_binop<3, std::minus>>},
{"mul", lua::wrap<l_binop<3, std::multiplies>>}, {"mul", lua::wrap<l_binop<3, std::multiplies>>},
{"div", lua::wrap<l_binop<3, std::divides>>}, {"div", lua::wrap<l_binop<3, std::divides>>},
{"distance", lua::wrap<l_distance<3>>},
{"normalize", lua::wrap<l_unaryop<3, glm::normalize>>}, {"normalize", lua::wrap<l_unaryop<3, glm::normalize>>},
{"length", lua::wrap<l_scalar_op<3, glm::length>>}, {"length", lua::wrap<l_scalar_op<3, glm::length>>},
{"tostring", lua::wrap<l_tostring<3>>}, {"tostring", lua::wrap<l_tostring<3>>},
@ -207,6 +245,7 @@ const luaL_Reg vec3lib[] = {
{"pow", lua::wrap<l_pow<3>>}, {"pow", lua::wrap<l_pow<3>>},
{"dot", lua::wrap<l_dot<3>>}, {"dot", lua::wrap<l_dot<3>>},
{"spherical_rand", lua::wrap<l_spherical_rand>}, {"spherical_rand", lua::wrap<l_spherical_rand>},
{"mix", lua::wrap<l_mix<3>>},
{NULL, NULL}}; {NULL, NULL}};
const luaL_Reg vec4lib[] = { const luaL_Reg vec4lib[] = {
@ -214,6 +253,7 @@ const luaL_Reg vec4lib[] = {
{"sub", lua::wrap<l_binop<4, std::minus>>}, {"sub", lua::wrap<l_binop<4, std::minus>>},
{"mul", lua::wrap<l_binop<4, std::multiplies>>}, {"mul", lua::wrap<l_binop<4, std::multiplies>>},
{"div", lua::wrap<l_binop<4, std::divides>>}, {"div", lua::wrap<l_binop<4, std::divides>>},
{"distance", lua::wrap<l_distance<4>>},
{"normalize", lua::wrap<l_unaryop<4, glm::normalize>>}, {"normalize", lua::wrap<l_unaryop<4, glm::normalize>>},
{"length", lua::wrap<l_scalar_op<4, glm::length>>}, {"length", lua::wrap<l_scalar_op<4, glm::length>>},
{"tostring", lua::wrap<l_tostring<4>>}, {"tostring", lua::wrap<l_tostring<4>>},
@ -222,4 +262,5 @@ const luaL_Reg vec4lib[] = {
{"inverse", lua::wrap<l_inverse<4>>}, {"inverse", lua::wrap<l_inverse<4>>},
{"pow", lua::wrap<l_pow<4>>}, {"pow", lua::wrap<l_pow<4>>},
{"dot", lua::wrap<l_dot<4>>}, {"dot", lua::wrap<l_dot<4>>},
{"mix", lua::wrap<l_mix<4>>},
{NULL, NULL}}; {NULL, NULL}};

View File

@ -72,6 +72,7 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "input", inputlib); openlib(L, "input", inputlib);
openlib(L, "inventory", inventorylib); openlib(L, "inventory", inventorylib);
openlib(L, "network", networklib); openlib(L, "network", networklib);
openlib(L, "pathfinding", pathfindinglib);
openlib(L, "player", playerlib); openlib(L, "player", playerlib);
openlib(L, "time", timelib); openlib(L, "time", timelib);
openlib(L, "world", worldlib); openlib(L, "world", worldlib);

View File

@ -48,8 +48,8 @@ namespace lua {
return true; return true;
} }
template <int n> template <int n, typename T = float>
inline int pushvec(lua::State* L, const glm::vec<n, float>& vec) { inline int pushvec(lua::State* L, const glm::vec<n, T>& vec) {
createtable(L, n, 0); createtable(L, n, 0);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
pushnumber(L, vec[i]); pushnumber(L, vec[i]);
@ -161,8 +161,8 @@ namespace lua {
} }
return 1; return 1;
} }
template <int n> template <int n, typename T = float>
inline int setvec(lua::State* L, int idx, glm::vec<n, float> vec) { inline int setvec(lua::State* L, int idx, glm::vec<n, T> vec) {
pushvalue(L, idx); pushvalue(L, idx);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
pushnumber(L, vec[i]); pushnumber(L, vec[i]);
@ -305,15 +305,15 @@ namespace lua {
setglobal(L, name); setglobal(L, name);
} }
template <int n> template <int n, typename T = float>
inline glm::vec<n, float> tovec(lua::State* L, int idx) { inline glm::vec<n, T> tovec(lua::State* L, int idx) {
pushvalue(L, idx); pushvalue(L, idx);
if (!istable(L, idx) || objlen(L, idx) < n) { if (!istable(L, idx) || objlen(L, idx) < n) {
throw std::runtime_error( throw std::runtime_error(
"value must be an array of " + std::to_string(n) + " numbers" "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++) { for (int i = 0; i < n; i++) {
rawgeti(L, i + 1); rawgeti(L, i + 1);
vec[i] = tonumber(L, -1); vec[i] = tonumber(L, -1);

View File

@ -19,8 +19,6 @@
#include "lua/lua_engine.hpp" #include "lua/lua_engine.hpp"
#include "lua/lua_custom_types.hpp" #include "lua/lua_custom_types.hpp"
#include "maths/Heightmap.hpp" #include "maths/Heightmap.hpp"
#include "objects/Entities.hpp"
#include "objects/EntityDef.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "util/timeutil.hpp" #include "util/timeutil.hpp"
@ -34,8 +32,6 @@ using namespace scripting;
static debug::Logger logger("scripting"); static debug::Logger logger("scripting");
static inline const std::string STDCOMP = "stdcomp";
std::ostream* scripting::output_stream = &std::cout; std::ostream* scripting::output_stream = &std::cout;
std::ostream* scripting::error_stream = &std::cerr; std::ostream* scripting::error_stream = &std::cerr;
Engine* scripting::engine = nullptr; 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() { void scripting::process_post_runnables() {
auto L = lua::get_main_state(); auto L = lua::get_main_state();
if (lua::getglobal(L, "__process_post_runnables")) { 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( void scripting::on_ui_open(
UiDocument* layout, std::vector<dv::value> args UiDocument* layout, std::vector<dv::value> args
) { ) {

View File

@ -139,6 +139,7 @@ namespace scripting {
void on_entity_fall(const Entity& entity); void on_entity_fall(const Entity& entity);
void on_entity_save(const Entity& entity); void on_entity_save(const Entity& entity);
void on_entities_update(int tps, int parts, int part); 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_entities_render(float delta);
void on_sensor_enter(const Entity& entity, size_t index, entityid_t oid); 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); void on_sensor_exit(const Entity& entity, size_t index, entityid_t oid);

View 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);
}

View File

@ -743,7 +743,6 @@ public:
class SocketUdpServer : public UdpServer { class SocketUdpServer : public UdpServer {
u64id_t id; u64id_t id;
Network* network;
SOCKET descriptor; SOCKET descriptor;
bool open = true; bool open = true;
std::unique_ptr<std::thread> thread = nullptr; std::unique_ptr<std::thread> thread = nullptr;
@ -752,13 +751,13 @@ class SocketUdpServer : public UdpServer {
public: public:
SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port) 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() override {
SocketUdpServer::close(); SocketUdpServer::close();
} }
void startListen(ServerDatagramCallback handler) { void startListen(ServerDatagramCallback handler) override {
callback = std::move(handler); callback = std::move(handler);
thread = std::make_unique<std::thread>([this]() { thread = std::make_unique<std::thread>([this]() {

View File

@ -17,110 +17,25 @@
#include "maths/FrustumCulling.hpp" #include "maths/FrustumCulling.hpp"
#include "maths/rays.hpp" #include "maths/rays.hpp"
#include "EntityDef.hpp" #include "EntityDef.hpp"
#include "Entity.hpp"
#include "rigging.hpp" #include "rigging.hpp"
#include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp" #include "physics/PhysicsSolver.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
static debug::Logger logger("entities"); 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) 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)> std::optional<Entity> Entities::get(entityid_t id) {
static sensorcallback create_sensor_callback(Entities* entities) { const auto& found = entities.find(id);
return [=](auto entityid, auto index, auto otherid) { if (found != entities.end() && registry.valid(found->second)) {
if (auto entity = entities->get(entityid)) { return Entity(*this, id, registry, found->second);
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)};
} }
return std::nullopt;
} }
entityid_t Entities::spawn( entityid_t Entities::spawn(
@ -168,14 +83,14 @@ entityid_t Entities::spawn(
Hitbox {def.bodyType, position, def.hitbox * 0.5f}, Hitbox {def.bodyType, position, def.hitbox * 0.5f},
std::vector<Sensor> {} std::vector<Sensor> {}
); );
initialize_body(def, body, id, this); body.initialize(def, id, *this);
auto& scripting = registry.emplace<ScriptComponents>(entity); auto& scripting = registry.emplace<ScriptComponents>(entity);
registry.emplace<rigging::Skeleton>(entity, skeleton->instance()); registry.emplace<rigging::Skeleton>(entity, skeleton->instance());
for (auto& componentName : def.components) { for (auto& instance : def.components) {
auto component = std::make_unique<UserComponent>( auto component = std::make_unique<UserComponent>(
componentName, EntityFuncsSet {}, nullptr instance.component, EntityFuncsSet {}, nullptr, instance.params
); );
scripting.components.emplace_back(std::move(component)); scripting.components.emplace_back(std::move(component));
} }
@ -186,7 +101,8 @@ entityid_t Entities::spawn(
} }
body.hitbox.position = tsf.pos; body.hitbox.position = tsf.pos;
scripting::on_entity_spawn( scripting::on_entity_spawn(
def, id, scripting.components, args, componentsMap); def, id, scripting.components, args, componentsMap
);
return id; return id;
} }
@ -213,41 +129,18 @@ void Entities::loadEntity(const dv::value& map, Entity entity) {
auto& skeleton = entity.getSkeleton(); auto& skeleton = entity.getSkeleton();
if (map.has(COMP_RIGIDBODY)) { if (map.has(COMP_RIGIDBODY)) {
auto& bodymap = map[COMP_RIGIDBODY]; body.deserialize(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);
} }
if (map.has(COMP_TRANSFORM)) { if (map.has(COMP_TRANSFORM)) {
auto& tsfmap = map[COMP_TRANSFORM]; transform.deserialize(map[COMP_TRANSFORM]);
dv::get_vec(tsfmap, "pos", transform.pos);
dv::get_vec(tsfmap, "size", transform.size);
dv::get_mat(tsfmap, "rot", transform.rot);
} }
std::string skeletonName = skeleton.config->getName(); std::string skeletonName = skeleton.config->getName();
map.at("skeleton").get(skeletonName); map.at("skeleton").get(skeletonName);
if (skeletonName != skeleton.config->getName()) { if (skeletonName != skeleton.config->getName()) {
skeleton.config = level.content.getSkeleton(skeletonName); skeleton.config = level.content.getSkeleton(skeletonName);
} }
if (auto found = map.at(COMP_SKELETON)) { if (auto foundSkeleton = map.at(COMP_SKELETON)) {
auto& skeletonmap = *found; skeleton.deserialize(*foundSkeleton);
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]);
}
}
} }
} }
@ -284,7 +177,7 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
void Entities::loadEntities(dv::value root) { void Entities::loadEntities(dv::value root) {
clean(); clean();
auto& list = root["data"]; const auto& list = root["data"];
for (auto& map : list) { for (auto& map : list) {
try { try {
loadEntity(map); loadEntity(map);
@ -298,74 +191,6 @@ void Entities::onSave(const Entity& entity) {
scripting::on_entity_save(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) { dv::value Entities::serialize(const std::vector<Entity>& entities) {
auto list = dv::list(); auto list = dv::list();
for (auto& entity : entities) { for (auto& entity : entities) {
@ -375,7 +200,7 @@ dv::value Entities::serialize(const std::vector<Entity>& entities) {
} }
level.entities->onSave(entity); level.entities->onSave(entity);
if (!eid.destroyFlag) { if (!eid.destroyFlag) {
list.add(level.entities->serialize(entity)); list.add(entity.serialize());
} }
} }
return list; return list;
@ -474,7 +299,9 @@ void Entities::updatePhysics(float delta) {
int substeps = static_cast<int>(delta * vel * 20); int substeps = static_cast<int>(delta * vel * 20);
substeps = std::min(100, std::max(2, substeps)); substeps = std::min(100, std::max(2, substeps));
physics->step(*level.chunks, hitbox, delta, substeps, eid.uid); 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); transform.setPos(hitbox.position);
if (hitbox.grounded && !grounded) { if (hitbox.grounded && !grounded) {
scripting::on_entity_grounded( scripting::on_entity_grounded(
@ -495,6 +322,8 @@ void Entities::update(float delta) {
updateTickClock.getPart() updateTickClock.getPart()
); );
} }
updatePhysics(delta);
scripting::on_entities_physics_update(delta);
} }
static void debug_render_skeleton( static void debug_render_skeleton(
@ -505,13 +334,10 @@ static void debug_render_skeleton(
size_t pindex = bone->getIndex(); size_t pindex = bone->getIndex();
for (auto& sub : bone->getSubnodes()) { for (auto& sub : bone->getSubnodes()) {
size_t sindex = sub->getIndex(); size_t sindex = sub->getIndex();
const auto& matrices = skeleton.calculated.matrices;
batch.line( batch.line(
glm::vec3( glm::vec3(matrices[pindex] * glm::vec4(0, 0, 0, 1)),
skeleton.calculated.matrices[pindex] * glm::vec4(0, 0, 0, 1) glm::vec3(matrices[sindex] * glm::vec4(0, 0, 0, 1)),
),
glm::vec3(
skeleton.calculated.matrices[sindex] * glm::vec4(0, 0, 0, 1)
),
glm::vec4(0, 0.5f, 0, 1) glm::vec4(0, 0.5f, 0, 1)
); );
debug_render_skeleton(batch, sub.get(), skeleton); debug_render_skeleton(batch, sub.get(), skeleton);
@ -568,10 +394,14 @@ void Entities::render(
ModelBatch& batch, ModelBatch& batch,
const Frustum* frustum, const Frustum* frustum,
float delta, float delta,
bool pause bool pause,
entityid_t fpsEntity
) { ) {
auto view = registry.view<Transform, rigging::Skeleton>(); auto view = registry.view<EntityId, Transform, rigging::Skeleton>();
for (auto [entity, transform, skeleton] : view.each()) { for (auto [entity, eid, transform, skeleton] : view.each()) {
if (eid.uid == fpsEntity) {
continue;
}
if (transform.dirty) { if (transform.dirty) {
transform.refresh(); transform.refresh();
} }

View File

@ -5,101 +5,21 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
#include "data/dv.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "Transform.hpp"
#include "Rigidbody.hpp"
#include "ScriptComponents.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/Clock.hpp" #include "util/Clock.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <entt/entity/registry.hpp>
#include <glm/gtx/norm.hpp>
#include <unordered_map>
struct EntityFuncsSet { #include <entt/entity/registry.hpp>
bool init; #include <unordered_map>
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 EntityDef; 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 Level;
class Assets; class Assets;
class Entity;
class LineBatch; class LineBatch;
class ModelBatch; class ModelBatch;
class Frustum; class Frustum;
@ -111,72 +31,6 @@ namespace rigging {
class SkeletonConfig; 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 { class Entities {
entt::registry registry; entt::registry registry;
Level& level; Level& level;
@ -211,7 +65,8 @@ public:
ModelBatch& batch, ModelBatch& batch,
const Frustum* frustum, const Frustum* frustum,
float delta, float delta,
bool pause bool pause,
entityid_t fpsEntity
); );
entityid_t spawn( entityid_t spawn(
@ -222,13 +77,7 @@ public:
entityid_t uid = 0 entityid_t uid = 0
); );
std::optional<Entity> get(entityid_t id) { 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;
}
/// @brief Entities raycast. No blocks check included, use combined with /// @brief Entities raycast. No blocks check included, use combined with
/// Chunks.rayCast /// Chunks.rayCast
@ -253,7 +102,6 @@ public:
std::vector<Entity> getAllInRadius(glm::vec3 center, float radius); std::vector<Entity> getAllInRadius(glm::vec3 center, float radius);
void despawn(entityid_t id); void despawn(entityid_t id);
void despawn(std::vector<Entity> entities); void despawn(std::vector<Entity> entities);
dv::value serialize(const Entity& entity);
dv::value serialize(const std::vector<Entity>& entities); dv::value serialize(const std::vector<Entity>& entities);
void setNextID(entityid_t id) { void setNextID(entityid_t id) {

119
src/objects/Entity.cpp Normal file
View 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
View 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();
};

View File

@ -5,6 +5,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include "typedefs.hpp" #include "typedefs.hpp"
#include "data/dv.hpp"
#include "maths/aabb.hpp" #include "maths/aabb.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
@ -12,12 +13,17 @@ namespace rigging {
class SkeletonConfig; class SkeletonConfig;
} }
struct ComponentInstance {
std::string component;
dv::value params;
};
struct EntityDef { struct EntityDef {
/// @brief Entity string id (with prefix included) /// @brief Entity string id (with prefix included)
std::string const name; std::string const name;
/// @brief Component IDs /// @brief Component instances
std::vector<std::string> components; std::vector<ComponentInstance> components;
/// @brief Physic body type /// @brief Physic body type
BodyType bodyType = BodyType::DYNAMIC; BodyType bodyType = BodyType::DYNAMIC;

View File

@ -9,6 +9,7 @@
#include "content/ContentReport.hpp" #include "content/ContentReport.hpp"
#include "items/Inventory.hpp" #include "items/Inventory.hpp"
#include "Entities.hpp" #include "Entities.hpp"
#include "Entity.hpp"
#include "rigging.hpp" #include "rigging.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp" #include "physics/PhysicsSolver.hpp"
@ -20,13 +21,6 @@
static debug::Logger logger("player"); 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; constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64;
Player::Player( Player::Player(
@ -81,17 +75,6 @@ void Player::updateEntity() {
"will be respawned"; "will be respawned";
eid = ENTITY_AUTO; 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() { Hitbox* Player::getHitbox() {
@ -101,57 +84,6 @@ Hitbox* Player::getHitbox() {
return nullptr; 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() { void Player::updateSelectedEntity() {
selectedEid = selection.entity; selectedEid = selection.entity;
} }
@ -172,10 +104,6 @@ void Player::postUpdate() {
attemptToFindSpawnpoint(); attemptToFindSpawnpoint();
} }
} }
// TODO: ERASE & FORGET
auto& skeleton = entity->getSkeleton();
skeleton.visible = currentCamera != fpCamera;
} }
void Player::teleport(glm::vec3 position) { void Player::teleport(glm::vec3 position) {

View File

@ -82,7 +82,6 @@ public:
void teleport(glm::vec3 position); void teleport(glm::vec3 position);
void updateEntity(); void updateEntity();
void updateInput(PlayerInput& input, float delta);
void updateSelectedEntity(); void updateSelectedEntity();
void postUpdate(); void postUpdate();

83
src/objects/Rigidbody.cpp Normal file
View 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
View 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
);
};

View 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
View 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
View 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;
}
};

View File

@ -23,7 +23,7 @@ Bone::Bone(
std::string name, std::string name,
std::string model, std::string model,
std::vector<std::unique_ptr<Bone>> bones, std::vector<std::unique_ptr<Bone>> bones,
glm::vec3 offset const glm::vec3& offset
) )
: index(index), : index(index),
name(std::move(name)), 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) { static void get_all_nodes(std::vector<Bone*>& nodes, Bone* node) {
nodes[node->getIndex()] = node; nodes[node->getIndex()] = node;
for (auto& subnode : node->getSubnodes()) { for (auto& subnode : node->getSubnodes()) {

View File

@ -9,6 +9,7 @@
#include <vector> #include <vector>
#include "typedefs.hpp" #include "typedefs.hpp"
#include "data/dv_fwd.hpp"
#include "util/Interpolation.hpp" #include "util/Interpolation.hpp"
class Assets; class Assets;
@ -50,7 +51,7 @@ namespace rigging {
std::string name, std::string name,
std::string model, std::string model,
std::vector<std::unique_ptr<Bone>> bones, std::vector<std::unique_ptr<Bone>> bones,
glm::vec3 offset const glm::vec3& offset
); );
void setModel(const std::string& name); void setModel(const std::string& name);
@ -89,6 +90,9 @@ namespace rigging {
util::VecInterpolation<3, float> interpolation {false}; util::VecInterpolation<3, float> interpolation {false};
Skeleton(const SkeletonConfig* config); Skeleton(const SkeletonConfig* config);
dv::value serialize(bool saveTextures, bool savePose) const;
void deserialize(const dv::value& root);
}; };
class SkeletonConfig { class SkeletonConfig {

View File

@ -6,6 +6,5 @@ Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize)
: type(type), : type(type),
position(position), position(position),
halfsize(halfsize), halfsize(halfsize),
velocity(0.0f,0.0f,0.0f), velocity(0.0f,0.0f,0.0f)
linearDamping(0.1f)
{} {}

View File

@ -52,8 +52,9 @@ struct Hitbox {
glm::vec3 position; glm::vec3 position;
glm::vec3 halfsize; glm::vec3 halfsize;
glm::vec3 velocity; glm::vec3 velocity;
float linearDamping; float linearDamping = 0.5;
bool verticalDamping = false; float friction = 1.0f;
float verticalDamping = 1.0f;
bool grounded = false; bool grounded = false;
float gravityScale = 1.0f; float gravityScale = 1.0f;
bool crouching = false; bool crouching = false;

View File

@ -25,7 +25,7 @@ void PhysicsSolver::step(
entityid_t entity entityid_t entity
) { ) {
float dt = delta / static_cast<float>(substeps); float dt = delta / static_cast<float>(substeps);
float linearDamping = hitbox.linearDamping; float linearDamping = hitbox.linearDamping * hitbox.friction;
float s = 2.0f/BLOCK_AABB_GRID; float s = 2.0f/BLOCK_AABB_GRID;
const glm::vec3& half = hitbox.halfsize; const glm::vec3& half = hitbox.halfsize;
@ -45,11 +45,6 @@ void PhysicsSolver::step(
colisionCalc(chunks, hitbox, vel, pos, half, colisionCalc(chunks, hitbox, vel, pos, half,
(prevGrounded && gravityScale > 0.0f) ? 0.5f : 0.0f); (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; pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f;
if (hitbox.grounded && pos.y < py) { if (hitbox.grounded && pos.y < py) {
@ -89,6 +84,12 @@ void PhysicsSolver::step(
hitbox.grounded = true; 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 aabb;
aabb.a = hitbox.position - hitbox.halfsize; aabb.a = hitbox.position - hitbox.halfsize;
aabb.b = hitbox.position + hitbox.halfsize; aabb.b = hitbox.position + hitbox.halfsize;

View File

@ -85,11 +85,18 @@ struct GraphicsSettings {
IntegerSetting denseRenderDistance {56, 0, 10'000}; 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 { struct DebugSettings {
/// @brief Turns off chunks saving/loading /// @brief Turns off chunks saving/loading
FlagSetting generatorTestMode {false}; FlagSetting generatorTestMode {false};
/// @brief Write lights cache /// @brief Write lights cache
FlagSetting doWriteLights {true}; FlagSetting doWriteLights {true};
/// @brief Write preprocessed shaders code to user:export
FlagSetting doTraceShaders {false};
/// @brief Enable experimental optimizations and features /// @brief Enable experimental optimizations and features
FlagSetting enableExperimental {false}; FlagSetting enableExperimental {false};
}; };
@ -111,4 +118,5 @@ struct EngineSettings {
DebugSettings debug; DebugSettings debug;
UiSettings ui; UiSettings ui;
NetworkSettings network; NetworkSettings network;
PathfindingSettings pathfinding;
}; };

View File

@ -10,7 +10,7 @@ Clock::Clock(int tickRate, int tickParts)
bool Clock::update(float delta) { bool Clock::update(float delta) {
tickTimer += delta; tickTimer += delta;
float delay = 1.0f / float(tickRate); float delay = 1.0f / static_cast<float>(tickRate);
if (tickTimer > delay || tickPartsUndone) { if (tickTimer > delay || tickPartsUndone) {
if (tickPartsUndone) { if (tickPartsUndone) {
tickPartsUndone--; tickPartsUndone--;

View File

@ -10,6 +10,7 @@
#include "lighting/Lightmap.hpp" #include "lighting/Lightmap.hpp"
#include "maths/voxmaths.hpp" #include "maths/voxmaths.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "voxels/blocks_agent.hpp" #include "voxels/blocks_agent.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "world/LevelEvents.hpp" #include "world/LevelEvents.hpp"

269
src/voxels/Pathfinding.cpp Normal file
View 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;
}

View 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);
};
}

View File

@ -5,6 +5,7 @@
#include "items/Inventories.hpp" #include "items/Inventories.hpp"
#include "items/Inventory.hpp" #include "items/Inventory.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
@ -12,6 +13,7 @@
#include "settings.hpp" #include "settings.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "voxels/GlobalChunks.hpp" #include "voxels/GlobalChunks.hpp"
#include "voxels/Pathfinding.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "LevelEvents.hpp" #include "LevelEvents.hpp"
#include "World.hpp" #include "World.hpp"
@ -27,7 +29,8 @@ Level::Level(
physics(std::make_unique<PhysicsSolver>(glm::vec3(0, -22.6f, 0))), physics(std::make_unique<PhysicsSolver>(glm::vec3(0, -22.6f, 0))),
events(std::make_unique<LevelEvents>()), events(std::make_unique<LevelEvents>()),
entities(std::make_unique<Entities>(*this)), 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(); const auto& worldInfo = world->getInfo();
auto& cameraIndices = content.getIndices(ResourceType::CAMERA); auto& cameraIndices = content.getIndices(ResourceType::CAMERA);
for (size_t i = 0; i < cameraIndices.size(); i++) { for (size_t i = 0; i < cameraIndices.size(); i++) {

View File

@ -18,6 +18,10 @@ class Camera;
class Players; class Players;
struct EngineSettings; struct EngineSettings;
namespace voxels {
class Pathfinding;
}
/// @brief A level, contains chunks and objects /// @brief A level, contains chunks and objects
class Level { class Level {
std::unique_ptr<World> world; std::unique_ptr<World> world;
@ -30,6 +34,7 @@ public:
std::unique_ptr<LevelEvents> events; std::unique_ptr<LevelEvents> events;
std::unique_ptr<Entities> entities; std::unique_ptr<Entities> entities;
std::unique_ptr<Players> players; std::unique_ptr<Players> players;
std::unique_ptr<voxels::Pathfinding> pathfinding;
std::vector<std::shared_ptr<Camera>> cameras; // move somewhere? std::vector<std::shared_ptr<Camera>> cameras; // move somewhere?
Level( Level(

View File

@ -11,7 +11,7 @@ TEST(GLSLExtension, processing) {
"float sum(float a, float b) {\n" "float sum(float a, float b) {\n"
" return a + b;\n" " return a + b;\n"
"}\n", "}\n",
true true, {}
) )
); );
try { try {
@ -27,7 +27,7 @@ TEST(GLSLExtension, processing) {
" vec4 color = texture(u_screen, v_uv);\n" " vec4 color = texture(u_screen, v_uv);\n"
" return mix(color, 1.0 - color, p_intensity);\n" " return mix(color, 1.0 - color, p_intensity);\n"
"}\n", "}\n",
false); false, {});
std::cout << processed.code << std::endl; std::cout << processed.code << std::endl;
} catch (const parsing_error& err) { } catch (const parsing_error& err) {
std::cerr << err.errorLog() << std::endl; std::cerr << err.errorLog() << std::endl;