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`.
## Physics

View File

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

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(...)*
```lua
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
vecn.dot(a: vector, b: vector)
```
#### Mixing - *vecn.mix(...)*
```lua
-- returns vector a * (1.0 - t) + b * t
vecn.mix(a: vector, b: vector, t: number)
-- writes to dst vector a * (1.0 - t) + b * t
vecn.mix(a: vector, b: vector, t: number, dst: vector)
```
#### Convert to string - *vecn.tostring(...)*
> [!WARNING]
> Returns only if the content is a vector
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
-- returns the direction angle of the vector {x, y} in degrees [0, 360]
vec2.angle(x: number, y: number)
-- returns the vector rotated by an angle in degrees counterclockwise
vec2.rotate(v: vec2, angle: number) -> vec2
-- writes the vector rotated by an angle in degrees counterclockwise to dst
vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2
```
@ -188,6 +211,10 @@ print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80}
local result_mul_scal = vec3.mul(v1_3d, scal)
print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
-- calculating distance between vectors
local result_distance = vec3.distance(v1_3d, v2_3d)
print("distance: " .. result_distance) -- 43
-- vector normalization
local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
@ -211,3 +238,7 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4}
-- scalar product of vectors
local result_dot = vec3.dot(v1_3d, v2_3d)
print("dot: " ..result_dot) -- 250
-- mixing vectors
local result_mix = vec3.mix(v1_3d, v2_3d, 0.25)
print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5}

View File

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

View File

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

View File

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

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(...)*
```lua
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
vecn.dot(a: vector, b: vector)
```
#### Смешивание - *vecn.mix(...)*
```lua
-- возвращает вектор a * (1.0 - t) + b * t
vecn.mix(a: vector, b: vector, t: number)
-- записывает в dst вектор a * (1.0 - t) + b * t
vecn.mix(a: vector, b: vector, t: number, dst: vector)
```
#### Перевод в строку - *vecn.tostring(...)*
> [!WARNING]
> Возвращает только тогда, когда содержимым является вектор
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
-- возвращает угол направления вектора {x, y} в градусах [0, 360]
vec2.angle(x: number, y: number)
-- возвращает повернутый вектор на угол в градусах против часовой стрелки
vec2.rotate(v: vec2, angle: number) -> vec2
-- записывает повернутый вектор на угол в градусах против часовой стрелки в dst
vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2
```
@ -192,6 +215,10 @@ print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
-- дистанция между векторами
local result_distance = vec3.distance(v1_3d, v2_3d)
print("distance: " .. result_distance) -- 43
-- длина вектора
local result_len = vec3.length(v1_3d)
print("len: " .. result_len) -- 3
@ -211,4 +238,9 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4}
-- скалярное произведение векторов
local result_dot = vec3.dot(v1_3d, v2_3d)
print("dot: " .. result_dot) -- 250
-- смешивание векторов
local result_mix = vec3.mix(v1_3d, v2_3d, 0.25)
print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5}
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -110,6 +110,21 @@ function vec3.dot(a, b)
return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
end
function vec3.mix(a, b, t, dest)
if dest then
dest[1] = a[1] * (1.0 - t) + b[1] * t
dest[2] = a[2] * (1.0 - t) + b[2] * t
dest[3] = a[3] * (1.0 - t) + b[3] * t
return dest
else
return {
a[1] * (1.0 - t) + b[1] * t,
a[2] * (1.0 - t) + b[2] * t,
a[3] * (1.0 - t) + b[3] * t,
}
end
end
-- =================================================== --
-- ====================== vec2 ======================= --
-- =================================================== --
@ -210,3 +225,16 @@ end
function vec2.dot(a, b)
return a[1] * b[1] + a[2] * b[2]
end
function vec2.mix(a, b, t, dest)
if dest then
dest[1] = a[1] * (1.0 - t) + b[1] * t
dest[2] = a[2] * (1.0 - t) + b[2] * t
return dest
else
return {
a[1] * (1.0 - t) + b[1] * t,
a[2] * (1.0 - t) + b[2] * t,
}
end
end

View File

@ -25,6 +25,7 @@ local Rigidbody = {__index={
get_linear_damping=function(self) return __rigidbody.get_linear_damping(self.eid) end,
set_linear_damping=function(self, f) return __rigidbody.set_linear_damping(self.eid, f) end,
is_vdamping=function(self) return __rigidbody.is_vdamping(self.eid) end,
get_vdamping=function(self) return __rigidbody.get_vdamping(self.eid) end,
set_vdamping=function(self, b) return __rigidbody.set_vdamping(self.eid, b) end,
is_grounded=function(self) return __rigidbody.is_grounded(self.eid) end,
is_crouching=function(self) return __rigidbody.is_crouching(self.eid) end,
@ -63,6 +64,13 @@ local Entity = {__index={
get_skeleton=function(self) return entities.get_skeleton(self.eid) end,
set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end,
get_component=function(self, name) return self.components[name] end,
require_component=function(self, name)
local component = self.components[name]
if not component then
error(("entity has no required component '%s'"):format(name))
end
return component
end,
has_component=function(self, name) return self.components[name] ~= nil end,
get_uid=function(self) return self.eid end,
def_index=function(self) return entities.get_def(self.eid) end,
@ -125,6 +133,19 @@ return {
::continue::
end
end,
physics_update = function(delta)
for uid, entity in pairs(entities) do
for _, component in pairs(entity.components) do
local callback = component.on_physics_update
if not component.__disabled and callback then
local result, err = pcall(callback, delta)
if err then
debug.error(err)
end
end
end
end
end,
render = function(delta)
for _,entity in pairs(entities) do
for _, component in pairs(entity.components) do

View File

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

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
end
local function update_hand()
local skeleton = gfx.skeletons
local pid = hud.get_player()
local invid, slot = player.get_inventory(pid)
local itemid = inventory.get(invid, slot)
local cam = cameras.get("core:first-person")
local bone = skeleton.index("hand", "item")
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
local rotation = cam:get_rot()
local angle = player.get_rot(pid) - 90
local cos = math.cos(angle / (180 / math.pi))
local sin = math.sin(angle / (180 / math.pi))
local newX = offset[1] * cos - offset[3] * sin
local newZ = offset[1] * sin + offset[3] * cos
offset[1] = newX
offset[3] = newZ
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
mat4.mul(rotation, mat, mat)
mat4.rotate(mat, {0, 1, 0}, -90, mat)
mat4.translate(mat, offset, mat)
skeleton.set_matrix("hand", bone, mat)
skeleton.set_model("hand", bone, item.model_name(itemid))
end
function on_hud_open()
input.add_callback("player.pick", function ()
if hud.is_paused() or hud.is_inventory_open() then
@ -63,7 +96,7 @@ function on_hud_open()
player.set_noclip(pid, true)
end
end)
input.add_callback("player.flight", function ()
if hud.is_paused() or hud.is_inventory_open() then
return
@ -81,39 +114,8 @@ function on_hud_open()
end)
configure_SSAO()
end
local function update_hand()
local skeleton = gfx.skeletons
local pid = hud.get_player()
local invid, slot = player.get_inventory(pid)
local itemid = inventory.get(invid, slot)
local cam = cameras.get("core:first-person")
local bone = skeleton.index("hand", "item")
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
local rotation = cam:get_rot()
local angle = player.get_rot() - 90
local cos = math.cos(angle / (180 / math.pi))
local sin = math.sin(angle / (180 / math.pi))
local newX = offset[1] * cos - offset[3] * sin
local newZ = offset[1] * sin + offset[3] * cos
offset[1] = newX
offset[3] = newZ
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
mat4.mul(rotation, mat, mat)
mat4.rotate(mat, {0, 1, 0}, -90, mat)
mat4.translate(mat, offset, mat)
skeleton.set_matrix("hand", bone, mat)
skeleton.set_model("hand", bone, item.model_name(itemid))
hud.default_hand_controller = update_hand
end
function on_hud_render()

View File

@ -157,6 +157,16 @@ console.add_command(
end
)
console.add_command(
"entity.spawn name:str x:num~pos.x y:num~pos.y z:num~pos.z",
"Spawn entity with default parameters",
function(args, kwargs)
local eid = entities.spawn(args[1], {args[2], args[3], args[4]})
return string.format("spawned %s at %s, %s, %s", unpack(args))
end
)
console.add_command(
"entity.despawn entity:sel=$entity.selected",
"Despawn entity",

View File

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

View File

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

View File

@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) {
this->paths = paths;
}
void GLSLExtension::setTraceOutput(bool enabled) {
this->traceOutput = enabled;
}
void GLSLExtension::loadHeader(const std::string& name) {
if (paths == nullptr) {
return;
@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) {
io::path file = paths->find("shaders/lib/" + name + ".glsl");
std::string source = io::read_string(file);
addHeader(name, {});
addHeader(name, process(file, source, true));
addHeader(name, process(file, source, true, {}));
}
void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) {
@ -123,13 +127,22 @@ static Value default_value_for(Type type) {
class GLSLParser : public BasicParser<char> {
public:
GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header)
GLSLParser(
GLSLExtension& glsl,
std::string_view file,
std::string_view source,
bool header,
const std::vector<std::string>& defines
)
: BasicParser(file, source), glsl(glsl) {
if (!header) {
ss << "#version " << GLSLExtension::VERSION << '\n';
}
for (auto& entry : glsl.getDefines()) {
ss << "#define " << entry.first << " " << entry.second << '\n';
for (auto& entry : defines) {
ss << "#define " << entry << '\n';
}
for (auto& entry : defines) {
ss << "#define " << entry << '\n';
}
}
uint linenum = 1;
source_line(ss, linenum);
@ -289,10 +302,34 @@ private:
std::stringstream ss;
};
static void trace_output(
const io::path& file,
const std::string& source,
const GLSLExtension::ProcessingResult& result
) {
std::stringstream ss;
ss << "export:trace/" << file.name();
io::path outfile = ss.str();
try {
io::create_directories(outfile.parent());
io::write_string(outfile, result.code);
} catch (const std::runtime_error& err) {
logger.error() << "error on saving GLSLExtension::preprocess output ("
<< outfile.string() << "): " << err.what();
}
}
GLSLExtension::ProcessingResult GLSLExtension::process(
const io::path& file, const std::string& source, bool header
const io::path& file,
const std::string& source,
bool header,
const std::vector<std::string>& defines
) {
std::string filename = file.string();
GLSLParser parser(*this, filename, source, header);
return parser.process();
GLSLParser parser(*this, filename, source, header, defines);
auto result = parser.process();
if (traceOutput) {
trace_output(file, source, result);
}
return result;
}

View File

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

View File

@ -30,7 +30,18 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
if (auto found = root.at("components")) {
for (const auto& elem : *found) {
def.components.emplace_back(elem.asString());
std::string name;
dv::value params;
if (elem.isObject()) {
name = elem["name"].asString();
if (elem.has("args")) {
params = elem["args"];
}
} else {
name = elem.asString();
}
def.components.push_back(ComponentInstance {
std::move(name), std::move(params)});
}
}
if (auto found = root.at("hitbox")) {

View File

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

View File

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

View File

@ -12,12 +12,14 @@
#include "graphics/render/WorldRenderer.hpp"
#include "graphics/render/ParticlesRenderer.hpp"
#include "graphics/render/ChunksRenderer.hpp"
#include "graphics/render/DebugLinesRenderer.hpp"
#include "logic/scripting/scripting.hpp"
#include "network/Network.hpp"
#include "objects/Player.hpp"
#include "objects/Players.hpp"
#include "objects/Entities.hpp"
#include "objects/EntityDef.hpp"
#include "objects/Entity.hpp"
#include "physics/Hitbox.hpp"
#include "util/stringutil.hpp"
#include "voxels/Block.hpp"
@ -44,6 +46,7 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
// TODO: move to xml
// TODO: move to xml finally
// TODO: move to xml finally
// TODO: move to xml finally
std::shared_ptr<UINode> create_debug_panel(
Engine& engine,
Level& level,
@ -260,6 +263,18 @@ std::shared_ptr<UINode> create_debug_panel(
});
panel->add(checkbox);
}
{
auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Paths", glm::vec2(400, 24)
);
checkbox->setSupplier([=]() {
return DebugLinesRenderer::showPaths;
});
checkbox->setConsumer([=](bool checked) {
DebugLinesRenderer::showPaths = checked;
});
panel->add(checkbox);
}
{
auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Generator Minimap", glm::vec2(400, 24)

View File

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

View File

@ -1,5 +1,6 @@
#include "ImageData.hpp"
#include <glm/glm.hpp>
#include <assert.h>
#include <stdexcept>
#include <cstring>
@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color
}
}
template<uint channels>
static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) {
ubyte* data = image.getData();
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1);
int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1);
int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1);
int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1);
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
int index = (y * imageWidth + x) * channels;
for (int i = 0; i < channels; i++) {
data[index + i] = color[i];
}
}
}
}
void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) {
switch (format) {
case ImageFormat::rgb888:
draw_rect<3>(*this, x, y, width, height, color);
break;
case ImageFormat::rgba8888:
draw_rect<4>(*this, x, y, width, height, color);
break;
default:
break;
}
}
void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
ubyte* source = image.getData();
uint srcwidth = image.getWidth();

View File

@ -28,6 +28,7 @@ public:
void flipY();
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color);
void drawRect(int x, int y, int width, int height, const glm::ivec4& color);
void blit(const ImageData& image, int x, int y);
void extrude(int x, int y, int w, int h);
void fixAlphaColor();

View File

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

View File

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

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;
}
void ChunksRenderer::drawChunksShadowsPass(
void ChunksRenderer::drawShadowsPass(
const Camera& camera, Shader& shader, const Camera& playerCamera
) {
Frustum frustum;

View File

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

View File

@ -1,15 +1,51 @@
#include "GuidesRenderer.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include "DebugLinesRenderer.hpp"
#include "graphics/core/Shader.hpp"
#include "window/Camera.hpp"
#include "graphics/core/LineBatch.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/render/LinesRenderer.hpp"
#include "world/Level.hpp"
#include "voxels/Chunk.hpp"
#include "voxels/Pathfinding.hpp"
#include "maths/voxmaths.hpp"
#include "window/Camera.hpp"
#include "constants.hpp"
void GuidesRenderer::drawBorders(
bool DebugLinesRenderer::showPaths = false;
static void draw_route(
LinesRenderer& lines, const voxels::Agent& agent
) {
const auto& route = agent.route;
if (!route.found)
return;
for (int i = 1; i < route.nodes.size(); i++) {
const auto& a = route.nodes.at(i - 1);
const auto& b = route.nodes.at(i);
if (i == 1) {
lines.pushLine(
glm::vec3(a.pos) + glm::vec3(0.5f),
glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f),
glm::vec4(1, 1, 1, 1)
);
}
lines.pushLine(
glm::vec3(a.pos) + glm::vec3(0.5f),
glm::vec3(b.pos) + glm::vec3(0.5f),
glm::vec4(1, 0, 1, 1)
);
lines.pushLine(
glm::vec3(b.pos) + glm::vec3(0.5f),
glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f),
glm::vec4(1, 1, 1, 1)
);
}
}
void DebugLinesRenderer::drawBorders(
LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez
) {
int ww = ex - sx;
@ -37,7 +73,7 @@ void GuidesRenderer::drawBorders(
batch.flush();
}
void GuidesRenderer::drawCoordSystem(
void DebugLinesRenderer::drawCoordSystem(
LineBatch& batch, const DrawContext& pctx, float length
) {
auto ctx = pctx.sub();
@ -55,14 +91,22 @@ void GuidesRenderer::drawCoordSystem(
batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f);
}
void GuidesRenderer::renderDebugLines(
const DrawContext& pctx,
void DebugLinesRenderer::render(
DrawContext& pctx,
const Camera& camera,
LineBatch& batch,
LinesRenderer& renderer,
LineBatch& linesBatch,
Shader& linesShader,
bool showChunkBorders
) {
DrawContext ctx = pctx.sub(&batch);
// In-world lines
if (showPaths) {
for (const auto& [_, agent] : level.pathfinding->getAgents()) {
draw_route(renderer, agent);
}
}
DrawContext ctx = pctx.sub(&linesBatch);
const auto& viewport = ctx.getViewport();
ctx.setDepthTest(true);
@ -78,7 +122,7 @@ void GuidesRenderer::renderDebugLines(
int cz = floordiv(static_cast<int>(coord.z), CHUNK_D);
drawBorders(
batch,
linesBatch,
cx * CHUNK_W,
0,
cz * CHUNK_D,
@ -103,5 +147,5 @@ void GuidesRenderer::renderDebugLines(
) * model *
glm::inverse(camera.rotation)
);
drawCoordSystem(batch, ctx, length);
drawCoordSystem(linesBatch, ctx, length);
}

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/Players.hpp"
#include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "logic/LevelController.hpp"
#include "util/stringutil.hpp"
#include "engine/Engine.hpp"

View File

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

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/Chunk.hpp"
#include "voxels/Chunks.hpp"
#include "voxels/Pathfinding.hpp"
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "world/LevelEvents.hpp"
@ -42,7 +43,7 @@
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/ShadowMap.hpp"
#include "graphics/core/Shadows.hpp"
#include "graphics/core/GBuffer.hpp"
#include "BlockWrapsRenderer.hpp"
#include "ParticlesRenderer.hpp"
@ -51,7 +52,8 @@
#include "NamedSkeletons.hpp"
#include "TextsRenderer.hpp"
#include "ChunksRenderer.hpp"
#include "GuidesRenderer.hpp"
#include "LinesRenderer.hpp"
#include "DebugLinesRenderer.hpp"
#include "ModelBatch.hpp"
#include "Skybox.hpp"
#include "Emitter.hpp"
@ -61,8 +63,6 @@ using namespace advanced_pipeline;
inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0;
inline constexpr int MIN_SHADOW_MAP_RES = 512;
bool WorldRenderer::showChunkBorders = false;
bool WorldRenderer::showEntitiesDebug = false;
@ -80,8 +80,7 @@ WorldRenderer::WorldRenderer(
modelBatch(std::make_unique<ModelBatch>(
MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings()
)),
guides(std::make_unique<GuidesRenderer>()),
chunks(std::make_unique<ChunksRenderer>(
chunksRenderer(std::make_unique<ChunksRenderer>(
&level,
*player.chunks,
assets,
@ -102,7 +101,7 @@ WorldRenderer::WorldRenderer(
auto& settings = engine.getSettings();
level.events->listen(
LevelEventType::CHUNK_HIDDEN,
[this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); }
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
);
auto assets = engine.getAssets();
skybox = std::make_unique<Skybox>(
@ -118,10 +117,26 @@ WorldRenderer::WorldRenderer(
hands = std::make_unique<HandsRenderer>(
*assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig)
);
lines = std::make_unique<LinesRenderer>();
shadowMapping = std::make_unique<Shadows>(level);
debugLines = std::make_unique<DebugLinesRenderer>(level);
}
WorldRenderer::~WorldRenderer() = default;
static void setup_weather(Shader& shader, const Weather& weather) {
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
}
static void setup_camera(Shader& shader, const Camera& camera) {
shader.uniformMatrix("u_model", glm::mat4(1.0f));
shader.uniformMatrix("u_proj", camera.getProjection());
shader.uniformMatrix("u_view", camera.getView());
shader.uniform3f("u_cameraPos", camera.position);
}
void WorldRenderer::setupWorldShader(
Shader& shader,
const Camera& camera,
@ -129,45 +144,20 @@ void WorldRenderer::setupWorldShader(
float fogFactor
) {
shader.use();
shader.uniformMatrix("u_model", glm::mat4(1.0f));
shader.uniformMatrix("u_proj", camera.getProjection());
shader.uniformMatrix("u_view", camera.getView());
setup_camera(shader, camera);
setup_weather(shader, weather);
shadowMapping->setup(shader, weather);
shader.uniform1f("u_timer", timer);
shader.uniform1f("u_gamma", settings.graphics.gamma.get());
shader.uniform1f("u_fogFactor", fogFactor);
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
shader.uniform1i("u_debugLights", lightsDebug);
shader.uniform1i("u_debugNormals", false);
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
shader.uniform1f("u_weatherFogCurve", weather.fogCurve());
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform3f("u_cameraPos", camera.position);
shader.uniform1i("u_skybox", 1);
shader.uniform1i("u_enableShadows", shadows);
if (shadows) {
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
shader.uniform1i("u_screen", 0);
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
shader.uniform3f("u_sunDir", shadowCamera.front);
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
glActiveTexture(TEXTURE_MAIN);
}
shader.uniform1i("u_skybox", TARGET_SKYBOX);
auto indices = level.content.getIndices();
// Light emission when an emissive item is chosen
@ -186,7 +176,7 @@ void WorldRenderer::setupWorldShader(
}
}
void WorldRenderer::renderLevel(
void WorldRenderer::renderOpaque(
const DrawContext& ctx,
const Camera& camera,
const EngineSettings& settings,
@ -215,7 +205,9 @@ void WorldRenderer::renderLevel(
*modelBatch,
culling ? frustumCulling.get() : nullptr,
delta,
pause
pause,
player.currentCamera.get() == player.fpCamera.get() ? player.getEntity()
: 0
);
modelBatch->render();
particles->render(camera, delta * !pause);
@ -225,7 +217,7 @@ void WorldRenderer::renderLevel(
setupWorldShader(shader, camera, settings, fogFactor);
chunks->drawChunks(camera, shader);
chunksRenderer->drawChunks(camera, shader);
blockWraps->draw(ctx, player);
if (hudVisible) {
@ -284,79 +276,7 @@ void WorldRenderer::renderLines(
}
}
void WorldRenderer::generateShadowsMap(
const Camera& camera,
const DrawContext& pctx,
ShadowMap& shadowMap,
Camera& shadowCamera,
float scale
) {
auto& shadowsShader = assets.require<Shader>("shadows");
auto world = level.getWorld();
const auto& worldInfo = world->getInfo();
const auto& settings = engine.getSettings();
int resolution = shadowMap.getResolution();
int quality = settings.graphics.shadowsQuality.get();
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;
float shadowMapSize = resolution * shadowMapScale;
glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f;
glm::vec3 prevPos = shadowCamera.position;
shadowCamera = Camera(
glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos,
shadowMapSize
);
shadowCamera.near = 0.1f;
shadowCamera.far = 1000.0f;
shadowCamera.perspective = false;
shadowCamera.setAspectRatio(1.0f);
float t = worldInfo.daytime - 0.25f;
if (t < 0.0f) {
t += 1.0f;
}
t = fmod(t, 0.5f);
float sunCycleStep = 1.0f / 500.0f;
float sunAngle = glm::radians(
90.0f -
((static_cast<int>(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f
);
float sunAltitude = glm::pi<float>() * 0.25f;
shadowCamera.rotate(
-glm::cos(sunAngle + glm::pi<float>() * 0.5f) * sunAltitude,
sunAngle - glm::pi<float>() * 0.5f,
glm::radians(0.0f)
);
shadowCamera.position -= shadowCamera.front * 500.0f;
shadowCamera.position += shadowCamera.up * 0.0f;
shadowCamera.position += camera.front * 0.0f;
auto view = shadowCamera.getView();
auto currentPos = shadowCamera.position;
auto topRight = shadowCamera.right + shadowCamera.up;
auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f);
auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f);
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
{
auto sctx = pctx.sub();
sctx.setDepthTest(true);
sctx.setCullFace(true);
sctx.setViewport({resolution, resolution});
shadowMap.bind();
setupWorldShader(shadowsShader, shadowCamera, settings, 0.0f);
chunks->drawChunksShadowsPass(shadowCamera, shadowsShader, camera);
shadowMap.unbind();
}
}
void WorldRenderer::draw(
void WorldRenderer::renderFrame(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
@ -365,6 +285,9 @@ void WorldRenderer::draw(
PostProcessing& postProcessing
) {
// TODO: REFACTOR WHOLE RENDER ENGINE
auto projView = camera.getProjView();
float delta = uiDelta * !pause;
timer += delta;
weather.update(delta);
@ -380,22 +303,17 @@ void WorldRenderer::draw(
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
Shader* affectedShaders[] {
&mainShader, &entityShader, &translucentShader, &deferredShader
};
gbufferPipeline = settings.graphics.advancedRender.get();
int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline;
int resolution = MIN_SHADOW_MAP_RES << shadowsQuality;
if (shadowsQuality > 0 && !shadows) {
shadowMap = std::make_unique<ShadowMap>(resolution);
wideShadowMap = std::make_unique<ShadowMap>(resolution);
shadows = true;
} else if (shadowsQuality == 0 && shadows) {
shadowMap.reset();
wideShadowMap.reset();
shadows = false;
}
shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadows,
shadowsQuality != 0,
settings.graphics.ssao.get() && gbufferPipeline
};
if (
@ -403,19 +321,15 @@ void WorldRenderer::draw(
prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao
) {
Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows);
Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao);
Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender);
mainShader.recompile();
entityShader.recompile();
deferredShader.recompile();
translucentShader.recompile();
prevCTShaderSettings = currentSettings;
}
std::vector<std::string> defines;
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
if (shadows && shadowMap->getResolution() != resolution) {
shadowMap = std::make_unique<ShadowMap>(resolution);
wideShadowMap = std::make_unique<ShadowMap>(resolution);
for (auto shader : affectedShaders) {
shader->recompile(defines);
}
prevCTShaderSettings = currentSettings;
}
const auto& worldInfo = world->getInfo();
@ -426,38 +340,24 @@ void WorldRenderer::draw(
skybox->refresh(pctx, worldInfo.daytime, mie, 4);
chunks->update();
chunksRenderer->update();
static int frameid = 0;
if (shadows) {
if (frameid % 2 == 0) {
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f);
} else {
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f);
}
}
frameid++;
auto& linesShader = assets.require<Shader>("lines");
/* World render scope with diegetic HUD included */ {
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
{
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Actually world render with depth buffer on */ {
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible);
// Debug lines
if (hudVisible) {
if (debug) {
guides->renderDebugLines(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
}
}
renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible);
}
texts->render(pctx, camera, settings, hudVisible, true);
}
@ -478,19 +378,33 @@ void WorldRenderer::draw(
} else {
postProcessing.getFramebuffer()->bind();
}
// Drawing background sky plane
// Background sky plane
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
if (debug && hudVisible) {
debugLines->render(
ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders
);
}
linesShader.uniformMatrix("u_projview", projView);
lines->draw(*lineBatch);
lineBatch->flush();
// Translucent blocks
{
auto sctx = ctx.sub();
sctx.setCullFace(true);
skybox->bind();
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunks->drawSortedMeshes(camera, translucentShader);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
skybox->unbind();
}
// Weather effects
entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor);
@ -582,7 +496,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
}
void WorldRenderer::clear() {
chunks->clear();
chunksRenderer->clear();
}
void WorldRenderer::setDebug(bool flag) {

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@
#include "items/ItemStack.hpp"
#include "lighting/Lighting.hpp"
#include "objects/Entities.hpp"
#include "objects/Entity.hpp"
#include "objects/Player.hpp"
#include "objects/Players.hpp"
#include "physics/Hitbox.hpp"
@ -270,7 +271,6 @@ void PlayerController::update(float delta, const Input* inputEvents) {
} else {
resetKeyboard();
}
updatePlayer(delta);
}
void PlayerController::postUpdate(
@ -309,20 +309,7 @@ void PlayerController::updateKeyboard(const Input& inputEvents) {
}
void PlayerController::resetKeyboard() {
input.zoom = false;
input.moveForward = false;
input.moveBack = false;
input.moveLeft = false;
input.moveRight = false;
input.sprint = false;
input.shift = false;
input.cheat = false;
input.jump = false;
input.delta = {};
}
void PlayerController::updatePlayer(float delta) {
player.updateInput(input, delta);
input = {};
}
static int determine_rotation(
@ -338,7 +325,8 @@ static int determine_rotation(
if (norm.z > 0.0f) return BLOCK_DIR_NORTH;
if (norm.z < 0.0f) return BLOCK_DIR_SOUTH;
} else if (name == "pane" || name == "stairs") {
int verticalBit = (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0;
int verticalBit =
(name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0;
if (abs(camDir.x) > abs(camDir.z)) {
if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit;
if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit;

View File

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

View File

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

View File

@ -62,6 +62,15 @@ static int l_set_gravity_scale(lua::State* L) {
static int l_is_vdamping(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
return lua::pushboolean(
L, entity->getRigidbody().hitbox.verticalDamping > 0.0
);
}
return 0;
}
static int l_get_vdamping(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
return lua::pushnumber(
L, entity->getRigidbody().hitbox.verticalDamping
);
}
@ -70,7 +79,11 @@ static int l_is_vdamping(lua::State* L) {
static int l_set_vdamping(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
entity->getRigidbody().hitbox.verticalDamping = lua::toboolean(L, 2);
if (lua::isboolean(L, 2)) {
entity->getRigidbody().hitbox.verticalDamping = lua::toboolean(L, 2);
} else {
entity->getRigidbody().hitbox.verticalDamping = lua::tonumber(L, 2);
}
}
return 0;
}
@ -144,6 +157,7 @@ const luaL_Reg rigidbodylib[] = {
{"get_linear_damping", lua::wrap<l_get_linear_damping>},
{"set_linear_damping", lua::wrap<l_set_linear_damping>},
{"is_vdamping", lua::wrap<l_is_vdamping>},
{"get_vdamping", lua::wrap<l_get_vdamping>},
{"set_vdamping", lua::wrap<l_set_vdamping>},
{"is_grounded", lua::wrap<l_is_grounded>},
{"is_crouching", lua::wrap<l_is_crouching>},

View File

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

View File

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

View File

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

View File

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

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/Player.hpp"
#include "objects/Players.hpp"
#include "objects/Entity.hpp"
#include "physics/Hitbox.hpp"
#include "window/Camera.hpp"
#include "world/Level.hpp"

View File

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

View File

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

View File

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

View File

@ -19,8 +19,6 @@
#include "lua/lua_engine.hpp"
#include "lua/lua_custom_types.hpp"
#include "maths/Heightmap.hpp"
#include "objects/Entities.hpp"
#include "objects/EntityDef.hpp"
#include "objects/Player.hpp"
#include "util/stringutil.hpp"
#include "util/timeutil.hpp"
@ -34,8 +32,6 @@ using namespace scripting;
static debug::Logger logger("scripting");
static inline const std::string STDCOMP = "stdcomp";
std::ostream* scripting::output_stream = &std::cout;
std::ostream* scripting::error_stream = &std::cerr;
Engine* scripting::engine = nullptr;
@ -234,36 +230,6 @@ std::unique_ptr<Process> scripting::start_coroutine(const io::path& script) {
});
}
[[nodiscard]] static scriptenv create_component_environment(
const scriptenv& parent, int entityIdx, const std::string& name
) {
auto L = lua::get_main_state();
int id = lua::create_environment(L, *parent);
lua::pushvalue(L, entityIdx);
lua::pushenv(L, id);
lua::pushvalue(L, -1);
lua::setfield(L, "this");
lua::pushvalue(L, -2);
lua::setfield(L, "entity");
lua::pop(L);
if (lua::getfield(L, "components")) {
lua::pushenv(L, id);
lua::setfield(L, name);
lua::pop(L);
}
lua::pop(L);
return std::shared_ptr<int>(new int(id), [=](int* id) { //-V508
lua::remove_environment(L, *id);
delete id;
});
}
void scripting::process_post_runnables() {
auto L = lua::get_main_state();
if (lua::getglobal(L, "__process_post_runnables")) {
@ -599,244 +565,6 @@ bool scripting::on_item_break_block(
);
}
dv::value scripting::get_component_value(
const scriptenv& env, const std::string& name
) {
auto L = lua::get_main_state();
lua::pushenv(L, *env);
if (lua::getfield(L, name)) {
return lua::tovalue(L, -1);
}
return nullptr;
}
void scripting::on_entity_spawn(
const EntityDef&,
entityid_t eid,
const std::vector<std::unique_ptr<UserComponent>>& components,
const dv::value& args,
const dv::value& saved
) {
auto L = lua::get_main_state();
lua::stackguard guard(L);
lua::requireglobal(L, STDCOMP);
if (lua::getfield(L, "new_Entity")) {
lua::pushinteger(L, eid);
lua::call(L, 1);
}
if (components.size() > 1) {
for (size_t i = 0; i < components.size() - 1; i++) {
lua::pushvalue(L, -1);
}
}
for (auto& component : components) {
auto compenv = create_component_environment(
get_root_environment(), -1, component->name
);
lua::get_from(L, lua::CHUNKS_TABLE, component->name, true);
lua::pushenv(L, *compenv);
if (args != nullptr) {
std::string compfieldname = component->name;
util::replaceAll(compfieldname, ":", "__");
if (args.has(compfieldname)) {
lua::pushvalue(L, args[compfieldname]);
} else {
lua::createtable(L, 0, 0);
}
} else {
lua::createtable(L, 0, 0);
}
lua::setfield(L, "ARGS");
if (saved == nullptr) {
lua::createtable(L, 0, 0);
} else {
if (saved.has(component->name)) {
lua::pushvalue(L, saved[component->name]);
} else {
lua::createtable(L, 0, 0);
}
}
lua::setfield(L, "SAVED_DATA");
lua::setfenv(L);
lua::call_nothrow(L, 0, 0);
lua::pushenv(L, *compenv);
auto& funcsset = component->funcsset;
funcsset.on_grounded = lua::hasfield(L, "on_grounded");
funcsset.on_fall = lua::hasfield(L, "on_fall");
funcsset.on_despawn = lua::hasfield(L, "on_despawn");
funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter");
funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit");
funcsset.on_save = lua::hasfield(L, "on_save");
funcsset.on_aim_on = lua::hasfield(L, "on_aim_on");
funcsset.on_aim_off = lua::hasfield(L, "on_aim_off");
funcsset.on_attacked = lua::hasfield(L, "on_attacked");
funcsset.on_used = lua::hasfield(L, "on_used");
lua::pop(L, 2);
component->env = compenv;
}
}
static void process_entity_callback(
const scriptenv& env,
const std::string& name,
std::function<int(lua::State*)> args
) {
auto L = lua::get_main_state();
lua::pushenv(L, *env);
if (lua::hasfield(L, "__disabled")) {
lua::pop(L);
return;
}
if (lua::getfield(L, name)) {
if (args) {
lua::call_nothrow(L, args(L), 0);
} else {
lua::call_nothrow(L, 0, 0);
}
}
lua::pop(L);
}
static void process_entity_callback(
const Entity& entity,
const std::string& name,
bool EntityFuncsSet::*flag,
std::function<int(lua::State*)> args
) {
const auto& script = entity.getScripting();
for (auto& component : script.components) {
if (component->funcsset.*flag) {
process_entity_callback(component->env, name, args);
}
}
}
void scripting::on_entity_despawn(const Entity& entity) {
process_entity_callback(
entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr
);
auto L = lua::get_main_state();
lua::get_from(L, "stdcomp", "remove_Entity", true);
lua::pushinteger(L, entity.getUID());
lua::call(L, 1, 0);
}
void scripting::on_entity_grounded(const Entity& entity, float force) {
process_entity_callback(
entity,
"on_grounded",
&EntityFuncsSet::on_grounded,
[force](auto L) { return lua::pushnumber(L, force); }
);
}
void scripting::on_entity_fall(const Entity& entity) {
process_entity_callback(
entity, "on_fall", &EntityFuncsSet::on_fall, nullptr
);
}
void scripting::on_entity_save(const Entity& entity) {
process_entity_callback(
entity, "on_save", &EntityFuncsSet::on_save, nullptr
);
}
void scripting::on_sensor_enter(
const Entity& entity, size_t index, entityid_t oid
) {
process_entity_callback(
entity,
"on_sensor_enter",
&EntityFuncsSet::on_sensor_enter,
[index, oid](auto L) {
lua::pushinteger(L, index);
lua::pushinteger(L, oid);
return 2;
}
);
}
void scripting::on_sensor_exit(
const Entity& entity, size_t index, entityid_t oid
) {
process_entity_callback(
entity,
"on_sensor_exit",
&EntityFuncsSet::on_sensor_exit,
[index, oid](auto L) {
lua::pushinteger(L, index);
lua::pushinteger(L, oid);
return 2;
}
);
}
void scripting::on_aim_on(const Entity& entity, Player* player) {
process_entity_callback(
entity,
"on_aim_on",
&EntityFuncsSet::on_aim_on,
[player](auto L) { return lua::pushinteger(L, player->getId()); }
);
}
void scripting::on_aim_off(const Entity& entity, Player* player) {
process_entity_callback(
entity,
"on_aim_off",
&EntityFuncsSet::on_aim_off,
[player](auto L) { return lua::pushinteger(L, player->getId()); }
);
}
void scripting::on_attacked(
const Entity& entity, Player* player, entityid_t attacker
) {
process_entity_callback(
entity,
"on_attacked",
&EntityFuncsSet::on_attacked,
[player, attacker](auto L) {
lua::pushinteger(L, attacker);
lua::pushinteger(L, player->getId());
return 2;
}
);
}
void scripting::on_entity_used(const Entity& entity, Player* player) {
process_entity_callback(
entity,
"on_used",
&EntityFuncsSet::on_used,
[player](auto L) { return lua::pushinteger(L, player->getId()); }
);
}
void scripting::on_entities_update(int tps, int parts, int part) {
auto L = lua::get_main_state();
lua::get_from(L, STDCOMP, "update", true);
lua::pushinteger(L, tps);
lua::pushinteger(L, parts);
lua::pushinteger(L, part);
lua::call_nothrow(L, 3, 0);
lua::pop(L);
}
void scripting::on_entities_render(float delta) {
auto L = lua::get_main_state();
lua::get_from(L, STDCOMP, "render", true);
lua::pushnumber(L, delta);
lua::call_nothrow(L, 1, 0);
lua::pop(L);
}
void scripting::on_ui_open(
UiDocument* layout, std::vector<dv::value> args
) {

View File

@ -139,6 +139,7 @@ namespace scripting {
void on_entity_fall(const Entity& entity);
void on_entity_save(const Entity& entity);
void on_entities_update(int tps, int parts, int part);
void on_entities_physics_update(float delta);
void on_entities_render(float delta);
void on_sensor_enter(const Entity& entity, size_t index, entityid_t oid);
void on_sensor_exit(const Entity& entity, size_t index, entityid_t oid);

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

View File

@ -17,110 +17,25 @@
#include "maths/FrustumCulling.hpp"
#include "maths/rays.hpp"
#include "EntityDef.hpp"
#include "Entity.hpp"
#include "rigging.hpp"
#include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp"
#include "world/Level.hpp"
static debug::Logger logger("entities");
static inline std::string COMP_TRANSFORM = "transform";
static inline std::string COMP_RIGIDBODY = "rigidbody";
static inline std::string COMP_SKELETON = "skeleton";
static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA";
void Transform::refresh() {
combined = glm::mat4(1.0f);
combined = glm::translate(combined, pos);
combined = combined * glm::mat4(rot);
combined = glm::scale(combined, size);
displayPos = pos;
displaySize = size;
dirty = false;
}
void Entity::setInterpolatedPosition(const glm::vec3& position) {
getSkeleton().interpolation.refresh(position);
}
glm::vec3 Entity::getInterpolatedPosition() const {
const auto& skeleton = getSkeleton();
if (skeleton.interpolation.isEnabled()) {
return skeleton.interpolation.getCurrent();
}
return getTransform().pos;
}
void Entity::destroy() {
if (isValid()) {
entities.despawn(id);
}
}
rigging::Skeleton& Entity::getSkeleton() const {
return registry.get<rigging::Skeleton>(entity);
}
void Entity::setRig(const rigging::SkeletonConfig* rigConfig) {
auto& skeleton = registry.get<rigging::Skeleton>(entity);
skeleton.config = rigConfig;
skeleton.pose.matrices.resize(
rigConfig->getBones().size(), glm::mat4(1.0f)
);
skeleton.calculated.matrices.resize(
rigConfig->getBones().size(), glm::mat4(1.0f)
);
}
Entities::Entities(Level& level)
: level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) {
: level(level),
sensorsTickClock(20, 3),
updateTickClock(20, 3) {
}
template <void (*callback)(const Entity&, size_t, entityid_t)>
static sensorcallback create_sensor_callback(Entities* entities) {
return [=](auto entityid, auto index, auto otherid) {
if (auto entity = entities->get(entityid)) {
if (entity->isValid()) {
callback(*entity, index, otherid);
}
}
};
}
static void initialize_body(
const EntityDef& def, Rigidbody& body, entityid_t id, Entities* entities
) {
body.sensors.resize(def.radialSensors.size() + def.boxSensors.size());
for (auto& [i, box] : def.boxSensors) {
SensorParams params {};
params.aabb = box;
body.sensors[i] = Sensor {
true,
SensorType::AABB,
i,
id,
params,
params,
{},
{},
create_sensor_callback<scripting::on_sensor_enter>(entities),
create_sensor_callback<scripting::on_sensor_exit>(entities)};
}
for (auto& [i, radius] : def.radialSensors) {
SensorParams params {};
params.radial = glm::vec4(radius);
body.sensors[i] = Sensor {
true,
SensorType::RADIUS,
i,
id,
params,
params,
{},
{},
create_sensor_callback<scripting::on_sensor_enter>(entities),
create_sensor_callback<scripting::on_sensor_exit>(entities)};
std::optional<Entity> Entities::get(entityid_t id) {
const auto& found = entities.find(id);
if (found != entities.end() && registry.valid(found->second)) {
return Entity(*this, id, registry, found->second);
}
return std::nullopt;
}
entityid_t Entities::spawn(
@ -168,14 +83,14 @@ entityid_t Entities::spawn(
Hitbox {def.bodyType, position, def.hitbox * 0.5f},
std::vector<Sensor> {}
);
initialize_body(def, body, id, this);
body.initialize(def, id, *this);
auto& scripting = registry.emplace<ScriptComponents>(entity);
registry.emplace<rigging::Skeleton>(entity, skeleton->instance());
for (auto& componentName : def.components) {
for (auto& instance : def.components) {
auto component = std::make_unique<UserComponent>(
componentName, EntityFuncsSet {}, nullptr
instance.component, EntityFuncsSet {}, nullptr, instance.params
);
scripting.components.emplace_back(std::move(component));
}
@ -186,7 +101,8 @@ entityid_t Entities::spawn(
}
body.hitbox.position = tsf.pos;
scripting::on_entity_spawn(
def, id, scripting.components, args, componentsMap);
def, id, scripting.components, args, componentsMap
);
return id;
}
@ -213,41 +129,18 @@ void Entities::loadEntity(const dv::value& map, Entity entity) {
auto& skeleton = entity.getSkeleton();
if (map.has(COMP_RIGIDBODY)) {
auto& bodymap = map[COMP_RIGIDBODY];
dv::get_vec(bodymap, "vel", body.hitbox.velocity);
std::string bodyTypeName;
map.at("type").get(bodyTypeName);
BodyTypeMeta.getItem(bodyTypeName, body.hitbox.type);
bodymap["crouch"].asBoolean(body.hitbox.crouching);
bodymap["damping"].asNumber(body.hitbox.linearDamping);
body.deserialize(map[COMP_RIGIDBODY]);
}
if (map.has(COMP_TRANSFORM)) {
auto& tsfmap = map[COMP_TRANSFORM];
dv::get_vec(tsfmap, "pos", transform.pos);
dv::get_vec(tsfmap, "size", transform.size);
dv::get_mat(tsfmap, "rot", transform.rot);
transform.deserialize(map[COMP_TRANSFORM]);
}
std::string skeletonName = skeleton.config->getName();
map.at("skeleton").get(skeletonName);
if (skeletonName != skeleton.config->getName()) {
skeleton.config = level.content.getSkeleton(skeletonName);
}
if (auto found = map.at(COMP_SKELETON)) {
auto& skeletonmap = *found;
if (auto found = skeletonmap.at("textures")) {
auto& texturesmap = *found;
for (auto& [slot, _] : texturesmap.asObject()) {
texturesmap.at(slot).get(skeleton.textures[slot]);
}
}
if (auto found = skeletonmap.at("pose")) {
auto& posearr = *found;
for (size_t i = 0;
i < std::min(skeleton.pose.matrices.size(), posearr.size());
i++) {
dv::get_mat(posearr[i], skeleton.pose.matrices[i]);
}
}
if (auto foundSkeleton = map.at(COMP_SKELETON)) {
skeleton.deserialize(*foundSkeleton);
}
}
@ -284,7 +177,7 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
void Entities::loadEntities(dv::value root) {
clean();
auto& list = root["data"];
const auto& list = root["data"];
for (auto& map : list) {
try {
loadEntity(map);
@ -298,74 +191,6 @@ void Entities::onSave(const Entity& entity) {
scripting::on_entity_save(entity);
}
dv::value Entities::serialize(const Entity& entity) {
auto root = dv::object();
auto& eid = entity.getID();
auto& def = eid.def;
root["def"] = def.name;
root["uid"] = eid.uid;
{
auto& transform = entity.getTransform();
auto& tsfmap = root.object(COMP_TRANSFORM);
tsfmap["pos"] = dv::to_value(transform.pos);
if (transform.size != glm::vec3(1.0f)) {
tsfmap["size"] = dv::to_value(transform.size);
}
if (transform.rot != glm::mat3(1.0f)) {
tsfmap["rot"] = dv::to_value(transform.rot);
}
}
{
auto& rigidbody = entity.getRigidbody();
auto& hitbox = rigidbody.hitbox;
auto& bodymap = root.object(COMP_RIGIDBODY);
if (!rigidbody.enabled) {
bodymap["enabled"] = false;
}
if (def.save.body.velocity) {
bodymap["vel"] = dv::to_value(rigidbody.hitbox.velocity);
}
if (def.save.body.settings) {
bodymap["damping"] = rigidbody.hitbox.linearDamping;
if (hitbox.type != def.bodyType) {
bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type);
}
if (hitbox.crouching) {
bodymap["crouch"] = hitbox.crouching;
}
}
}
auto& skeleton = entity.getSkeleton();
if (skeleton.config->getName() != def.skeletonName) {
root["skeleton"] = skeleton.config->getName();
}
if (def.save.skeleton.pose || def.save.skeleton.textures) {
auto& skeletonmap = root.object(COMP_SKELETON);
if (def.save.skeleton.textures) {
auto& map = skeletonmap.object("textures");
for (auto& [slot, texture] : skeleton.textures) {
map[slot] = texture;
}
}
if (def.save.skeleton.pose) {
auto& list = skeletonmap.list("pose");
for (auto& mat : skeleton.pose.matrices) {
list.add(dv::to_value(mat));
}
}
}
auto& scripts = entity.getScripting();
if (!scripts.components.empty()) {
auto& compsMap = root.object("comps");
for (auto& comp : scripts.components) {
auto data =
scripting::get_component_value(comp->env, SAVED_DATA_VARNAME);
compsMap[comp->name] = data;
}
}
return root;
}
dv::value Entities::serialize(const std::vector<Entity>& entities) {
auto list = dv::list();
for (auto& entity : entities) {
@ -375,7 +200,7 @@ dv::value Entities::serialize(const std::vector<Entity>& entities) {
}
level.entities->onSave(entity);
if (!eid.destroyFlag) {
list.add(level.entities->serialize(entity));
list.add(entity.serialize());
}
}
return list;
@ -474,7 +299,9 @@ void Entities::updatePhysics(float delta) {
int substeps = static_cast<int>(delta * vel * 20);
substeps = std::min(100, std::max(2, substeps));
physics->step(*level.chunks, hitbox, delta, substeps, eid.uid);
hitbox.linearDamping = hitbox.grounded * 24;
hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f)
? 8.0f
: (!grounded ? 2.0f : 10.0f);
transform.setPos(hitbox.position);
if (hitbox.grounded && !grounded) {
scripting::on_entity_grounded(
@ -495,6 +322,8 @@ void Entities::update(float delta) {
updateTickClock.getPart()
);
}
updatePhysics(delta);
scripting::on_entities_physics_update(delta);
}
static void debug_render_skeleton(
@ -505,13 +334,10 @@ static void debug_render_skeleton(
size_t pindex = bone->getIndex();
for (auto& sub : bone->getSubnodes()) {
size_t sindex = sub->getIndex();
const auto& matrices = skeleton.calculated.matrices;
batch.line(
glm::vec3(
skeleton.calculated.matrices[pindex] * glm::vec4(0, 0, 0, 1)
),
glm::vec3(
skeleton.calculated.matrices[sindex] * glm::vec4(0, 0, 0, 1)
),
glm::vec3(matrices[pindex] * glm::vec4(0, 0, 0, 1)),
glm::vec3(matrices[sindex] * glm::vec4(0, 0, 0, 1)),
glm::vec4(0, 0.5f, 0, 1)
);
debug_render_skeleton(batch, sub.get(), skeleton);
@ -568,10 +394,14 @@ void Entities::render(
ModelBatch& batch,
const Frustum* frustum,
float delta,
bool pause
bool pause,
entityid_t fpsEntity
) {
auto view = registry.view<Transform, rigging::Skeleton>();
for (auto [entity, transform, skeleton] : view.each()) {
auto view = registry.view<EntityId, Transform, rigging::Skeleton>();
for (auto [entity, eid, transform, skeleton] : view.each()) {
if (eid.uid == fpsEntity) {
continue;
}
if (transform.dirty) {
transform.refresh();
}

View File

@ -5,101 +5,21 @@
#include <optional>
#include <vector>
#include "data/dv.hpp"
#include "physics/Hitbox.hpp"
#include "Transform.hpp"
#include "Rigidbody.hpp"
#include "ScriptComponents.hpp"
#include "typedefs.hpp"
#include "util/Clock.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <entt/entity/registry.hpp>
#include <glm/gtx/norm.hpp>
#include <unordered_map>
struct EntityFuncsSet {
bool init;
bool on_despawn;
bool on_grounded;
bool on_fall;
bool on_sensor_enter;
bool on_sensor_exit;
bool on_save;
bool on_aim_on;
bool on_aim_off;
bool on_attacked;
bool on_used;
};
#include <entt/entity/registry.hpp>
#include <unordered_map>
struct EntityDef;
struct EntityId {
entityid_t uid;
const EntityDef& def;
bool destroyFlag = false;
int64_t player = -1;
};
struct Transform {
static inline constexpr float EPSILON = 0.0000001f;
glm::vec3 pos;
glm::vec3 size;
glm::mat3 rot;
glm::mat4 combined;
bool dirty = true;
glm::vec3 displayPos;
glm::vec3 displaySize;
void refresh();
inline void setRot(glm::mat3 m) {
rot = m;
dirty = true;
}
inline void setSize(glm::vec3 v) {
if (glm::distance2(displaySize, v) >= EPSILON) {
dirty = true;
}
size = v;
}
inline void setPos(glm::vec3 v) {
if (glm::distance2(displayPos, v) >= EPSILON) {
dirty = true;
}
pos = v;
}
};
struct Rigidbody {
bool enabled = true;
Hitbox hitbox;
std::vector<Sensor> sensors;
};
struct UserComponent {
std::string name;
EntityFuncsSet funcsset;
scriptenv env;
UserComponent(
const std::string& name, EntityFuncsSet funcsset, scriptenv env
)
: name(name), funcsset(funcsset), env(env) {
}
};
struct ScriptComponents {
std::vector<std::unique_ptr<UserComponent>> components;
ScriptComponents() = default;
ScriptComponents(ScriptComponents&& other)
: components(std::move(other.components)) {
}
};
class Level;
class Assets;
class Entity;
class LineBatch;
class ModelBatch;
class Frustum;
@ -111,72 +31,6 @@ namespace rigging {
class SkeletonConfig;
}
class Entity {
Entities& entities;
entityid_t id;
entt::registry& registry;
const entt::entity entity;
public:
Entity(
Entities& entities,
entityid_t id,
entt::registry& registry,
const entt::entity entity
)
: entities(entities), id(id), registry(registry), entity(entity) {
}
EntityId& getID() const {
return registry.get<EntityId>(entity);
}
bool isValid() const {
return registry.valid(entity);
}
const EntityDef& getDef() const {
return registry.get<EntityId>(entity).def;
}
Transform& getTransform() const {
return registry.get<Transform>(entity);
}
Rigidbody& getRigidbody() const {
return registry.get<Rigidbody>(entity);
}
ScriptComponents& getScripting() const {
return registry.get<ScriptComponents>(entity);
}
rigging::Skeleton& getSkeleton() const;
void setRig(const rigging::SkeletonConfig* rigConfig);
entityid_t getUID() const {
return registry.get<EntityId>(entity).uid;
}
entt::entity getHandler() const {
return entity;
}
int64_t getPlayer() const {
return registry.get<EntityId>(entity).player;
}
void setPlayer(int64_t id) {
registry.get<EntityId>(entity).player = id;
}
void setInterpolatedPosition(const glm::vec3& position);
glm::vec3 getInterpolatedPosition() const;
void destroy();
};
class Entities {
entt::registry registry;
Level& level;
@ -211,7 +65,8 @@ public:
ModelBatch& batch,
const Frustum* frustum,
float delta,
bool pause
bool pause,
entityid_t fpsEntity
);
entityid_t spawn(
@ -222,13 +77,7 @@ public:
entityid_t uid = 0
);
std::optional<Entity> get(entityid_t id) {
const auto& found = entities.find(id);
if (found != entities.end() && registry.valid(found->second)) {
return Entity(*this, id, registry, found->second);
}
return std::nullopt;
}
std::optional<Entity> get(entityid_t id);
/// @brief Entities raycast. No blocks check included, use combined with
/// Chunks.rayCast
@ -253,7 +102,6 @@ public:
std::vector<Entity> getAllInRadius(glm::vec3 center, float radius);
void despawn(entityid_t id);
void despawn(std::vector<Entity> entities);
dv::value serialize(const Entity& entity);
dv::value serialize(const std::vector<Entity>& entities);
void setNextID(entityid_t id) {

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

View File

@ -9,6 +9,7 @@
#include "content/ContentReport.hpp"
#include "items/Inventory.hpp"
#include "Entities.hpp"
#include "Entity.hpp"
#include "rigging.hpp"
#include "physics/Hitbox.hpp"
#include "physics/PhysicsSolver.hpp"
@ -20,13 +21,6 @@
static debug::Logger logger("player");
constexpr float CROUCH_SPEED_MUL = 0.35f;
constexpr float RUN_SPEED_MUL = 1.5f;
constexpr float PLAYER_GROUND_DAMPING = 10.0f;
constexpr float PLAYER_AIR_DAMPING = 8.0f;
constexpr float FLIGHT_SPEED_MUL = 4.0f;
constexpr float CHEAT_SPEED_MUL = 5.0f;
constexpr float JUMP_FORCE = 8.0f;
constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64;
Player::Player(
@ -81,17 +75,6 @@ void Player::updateEntity() {
"will be respawned";
eid = ENTITY_AUTO;
}
auto hitbox = getHitbox();
if (hitbox == nullptr) {
return;
}
hitbox->linearDamping = PLAYER_GROUND_DAMPING;
hitbox->verticalDamping = flight;
hitbox->gravityScale = flight ? 0.0f : 1.0f;
if (flight || !hitbox->grounded) {
hitbox->linearDamping = PLAYER_AIR_DAMPING;
}
hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC;
}
Hitbox* Player::getHitbox() {
@ -101,57 +84,6 @@ Hitbox* Player::getHitbox() {
return nullptr;
}
void Player::updateInput(PlayerInput& input, float delta) {
auto hitbox = getHitbox();
if (hitbox == nullptr) {
return;
}
bool crouch = input.shift && hitbox->grounded && !input.sprint;
float speed = this->speed;
if (flight) {
speed *= FLIGHT_SPEED_MUL;
}
if (input.cheat) {
speed *= CHEAT_SPEED_MUL;
}
hitbox->crouching = crouch;
if (crouch) {
speed *= CROUCH_SPEED_MUL;
} else if (input.sprint) {
speed *= RUN_SPEED_MUL;
}
glm::vec3 dir(0, 0, 0);
if (input.moveForward) {
dir += fpCamera->dir;
}
if (input.moveBack) {
dir -= fpCamera->dir;
}
if (input.moveRight) {
dir += fpCamera->right;
}
if (input.moveLeft) {
dir -= fpCamera->right;
}
if (glm::length(dir) > 0.0f) {
dir = glm::normalize(dir);
hitbox->velocity += dir * speed * delta * 9.0f;
}
if (flight) {
if (input.jump) {
hitbox->velocity.y += speed * delta * 9;
}
if (input.shift) {
hitbox->velocity.y -= speed * delta * 9;
}
}
if (input.jump && hitbox->grounded) {
hitbox->velocity.y = JUMP_FORCE;
}
}
void Player::updateSelectedEntity() {
selectedEid = selection.entity;
}
@ -172,10 +104,6 @@ void Player::postUpdate() {
attemptToFindSpawnpoint();
}
}
// TODO: ERASE & FORGET
auto& skeleton = entity->getSkeleton();
skeleton.visible = currentCamera != fpCamera;
}
void Player::teleport(glm::vec3 position) {

View File

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

83
src/objects/Rigidbody.cpp Normal file
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 model,
std::vector<std::unique_ptr<Bone>> bones,
glm::vec3 offset
const glm::vec3& offset
)
: index(index),
name(std::move(name)),
@ -53,6 +53,39 @@ Skeleton::Skeleton(const SkeletonConfig* config)
}
}
dv::value Skeleton::serialize(bool saveTextures, bool savePose) const {
auto root = dv::object();
if (saveTextures) {
auto& map = root.object("textures");
for (auto& [slot, texture] : textures) {
map[slot] = texture;
}
}
if (savePose) {
auto& list = root.list("pose");
for (auto& mat : pose.matrices) {
list.add(dv::to_value(mat));
}
}
return root;
}
void Skeleton::deserialize(const dv::value& root) {
if (auto found = root.at("textures")) {
auto& texturesmap = *found;
for (auto& [slot, _] : texturesmap.asObject()) {
texturesmap.at(slot).get(textures[slot]);
}
}
if (auto found = root.at("pose")) {
auto& posearr = *found;
auto& matrices = pose.matrices;
for (size_t i = 0; i < std::min(matrices.size(), posearr.size()); i++) {
dv::get_mat(posearr[i], pose.matrices[i]);
}
}
}
static void get_all_nodes(std::vector<Bone*>& nodes, Bone* node) {
nodes[node->getIndex()] = node;
for (auto& subnode : node->getSubnodes()) {

View File

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

View File

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

View File

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

View File

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

View File

@ -85,11 +85,18 @@ struct GraphicsSettings {
IntegerSetting denseRenderDistance {56, 0, 10'000};
};
struct PathfindingSettings {
/// @brief Max visited blocks by an agent per async tick
IntegerSetting stepsPerAsyncAgent {128, 1, 2048};
};
struct DebugSettings {
/// @brief Turns off chunks saving/loading
FlagSetting generatorTestMode {false};
/// @brief Write lights cache
FlagSetting doWriteLights {true};
/// @brief Write preprocessed shaders code to user:export
FlagSetting doTraceShaders {false};
/// @brief Enable experimental optimizations and features
FlagSetting enableExperimental {false};
};
@ -111,4 +118,5 @@ struct EngineSettings {
DebugSettings debug;
UiSettings ui;
NetworkSettings network;
PathfindingSettings pathfinding;
};

View File

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

View File

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

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

View File

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

View File

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