This commit is contained in:
Xertis 2026-03-16 00:56:17 +03:00
commit d4e5bc99a0
61 changed files with 1209 additions and 534 deletions

View file

@ -95,6 +95,23 @@ Example:
0. A rectangular area with a width, height and length of 0.4 m, centered at 0.0. 0. A rectangular area with a width, height and length of 0.4 m, centered at 0.0.
1. Radial area with a radius of 1.6 m. 1. Radial area with a radius of 1.6 m.
### *solid*
Makes the entity's hitbox an obstacle. When enabling this property for `dynamic` body, a mass must be specified.
### *mass*
Defines the entity's mass. Kilograms are used as the default.
For `kinematic` bodies, an infinite mass is used instead.
### *elasticity*
Defines the entity's elasticity (affects the bounce on collision).
### *step-height*
Defines the maximum height of an obstacle an entity can step onto without jumping.
## View ## View
### *skeleton-name* ### *skeleton-name*

View file

@ -4,14 +4,17 @@ A library for managing audio/visual weather effects.
Weather settings: Weather settings:
| Property | Description | Default | | Property | Description | Default |
| ------------ | ------------------------------ | ------- | | ------------- | -------------------------------- | ----------------- |
| fall | Precipitation (see table 2) | {} | | fall | Precipitation (see table 2) | {} |
| clouds | Cloudiness [0.0, 1.0] | 0.0 | | clouds | Cloudiness [0.0, 1.0] | 0.0 |
| fog_opacity | Maximum fog density [0.0, 1.0] | 0.0 | | fog_opacity | Maximum fog density [0.0, 1.0] | 0.0 |
| fog_dencity | Fog density | 1.0 | | fog_dencity | Fog density | 1.0 |
| fog_curve | Fog curve | 1.0 | | fog_curve | Fog curve | 1.0 |
| thunder_rate | Thunder rate [0.0, 1.0] | 0.0 | | thunder_rate | Thunder rate [0.0, 1.0] | 0.0 |
| sky_tint | Sky color multiplier | {1, 1, 1} |
| clouds_tint | Cloud color multiplier | {1, 1, 1} |
| min_sky_light | Minimum sky-emitted light (moon) | {0.2, 0.25, 0.33} |
Precipitation: Precipitation:

View file

@ -78,14 +78,14 @@ Returns the id of all base packages (non-removeable)
```lua ```lua
pack.get_info(packid: str) -> { pack.get_info(packid: str) -> {
id: str, id: str,
title: str, title: str,
creator: str, creator: str,
description: str, description: str,
version: str, version: str,
path: str, path: str,
icon: str, -- not available in headless mode icon: str, -- not available in headless mode
dependencies: optional strings array dependencies: optional strings array
} }
``` ```
@ -95,6 +95,7 @@ Returns information about the pack (not necessarily installed).
- `!` - required - `!` - required
- `?` - optional - `?` - optional
- `~` - weak - `~` - weak
for example `!teal` for example `!teal`
To obtain information about multiple packs, use table of ids to avoid re-scanning:one To obtain information about multiple packs, use table of ids to avoid re-scanning:one

View file

@ -114,6 +114,21 @@ body:set_crouching(enabled: bool)
body:get_body_type() -> str body:get_body_type() -> str
-- Sets the physical body type -- Sets the physical body type
body:set_body_type(type: str) body:set_body_type(type: str)
-- Returns the body material (same as for blocks)
body:get_material() -> str
-- Sets the body material
body:set_material(material: str)
-- Returns the body's mass
body:get_mass() -> number
-- Sets the body's mass
body:set_mass(mass: number)
-- Returns the body's elasticity
body:get_elasticity() -> number
-- Sets the body's elasticity
body:set_elasticity(elasticity: number)
``` ```
### Skeleton ### Skeleton

View file

@ -98,6 +98,7 @@ A panel that controls the size and position of two elements. Allows the user to
Inner text is a button text. Inner text is a button text.
- `font` - font name
- `text-align` - inner text alignment (*left/center/right*). Type: string. - `text-align` - inner text alignment (*left/center/right*). Type: string.
## *checkbox* ## *checkbox*
@ -108,6 +109,7 @@ Inner text is a button text.
## *label* ## *label*
- `font` - font name
- `text-align` - text alignment (*left/center/right*). Type: string. - `text-align` - text alignment (*left/center/right*). Type: string.
- `valign` - vertical text alignment: top/center/bottom. - `valign` - vertical text alignment: top/center/bottom.
- `supplier` - text supplier (called every frame). - `supplier` - text supplier (called every frame).
@ -131,6 +133,7 @@ Inner text is a button text.
Inner text - initially entered text Inner text - initially entered text
- `font` - font name
- `placeholder` - placeholder text (used if the text field is empty) - `placeholder` - placeholder text (used if the text field is empty)
- `hint` - text displayed if the text field is empty (not sent to consumer, sub-consumer and validator). - `hint` - text displayed if the text field is empty (not sent to consumer, sub-consumer and validator).
- `supplier` - text supplier (called every frame) - `supplier` - text supplier (called every frame)
@ -190,6 +193,14 @@ Example of list description:
- `selected` - initially selected value. Default: "". - `selected` - initially selected value. Default: "".
- `onselect` - function to which the user-selected value is passed - `onselect` - function to which the user-selected value is passed
## *bindbox*
An element for displaying and editing key or mouse button bindings.
- `font` - font name. Type: string.
- `binding` - binding name. Type: string.
- `padding` - padding between text and element borders. Type: number. Default: 6
# Inventory elements # Inventory elements
## *inventory* ## *inventory*
@ -213,9 +224,6 @@ Element must be in direct sub-element of *inventory*.
## *slots-grid* ## *slots-grid*
- `start-index` - inventory slot index of the first slot. Type: integer - `start-index` - inventory slot index of the first slot. Type: integer
- `rows` - number of grid rows (unnecessary if *cols* and *count* specified). Type: integer
- `cols` - number of grid columns (unnecessary if *rows* and *count* specified). Type: integer
- `count` - total number of slots in grid (unnecessary if *rows* and *cols* specified). Type: integer
- `interval` - visual slots interval. Type: number - `interval` - visual slots interval. Type: number
- `padding` - grid padding (not slots interval). Type: number. (*deprecated*) - `padding` - grid padding (not slots interval). Type: number. (*deprecated*)
- `sharefunc` - Lua event called on <btn>LMB</btn> + <btn>Shift</btn>. Inventory id and slot index passed as arguments. - `sharefunc` - Lua event called on <btn>LMB</btn> + <btn>Shift</btn>. Inventory id and slot index passed as arguments.
@ -223,3 +231,8 @@ Element must be in direct sub-element of *inventory*.
- `onrightclick` - Lua event called on <btn>RMB</btn> click. Inventory id and slot index passed as arguments. - `onrightclick` - Lua event called on <btn>RMB</btn> click. Inventory id and slot index passed as arguments.
- `taking` - the ability to take an item from a slot. - `taking` - the ability to take an item from a slot.
- `placing` - the ability to put an item in a slot. - `placing` - the ability to put an item in a slot.
Slots configuration (just specify two attributes):
- `rows` - ​​number of rows. Type: integer
- `cols` - number of columns. Type: integer
- `count` - total number of slots. Type: integer

View file

@ -95,6 +95,23 @@
0. Прямоугольная область шириной, высотой и длиной в 0.4 м. с центром в 0.0. 0. Прямоугольная область шириной, высотой и длиной в 0.4 м. с центром в 0.0.
1. Радиальная область с радиусом 1.6 м. 1. Радиальная область с радиусом 1.6 м.
### Осязаемость - *solid*
Делает хитбокс сущности осязаемым препятствием. Вместе с включением данного свойства у `dynamic` тел следует указать массу (mass).
### Масса - *mass*
Определяет массу сущности. За стандарт приняты килограммы.
В случае `kinematic` тела вместо указанной используется бесконечная масса.
### Эластичность - *elasticity*
Определяет упругость сущности.
### Высота шага - *step-height*
Определяет максимальную высоту препятствия, на которое сущность может шагнуть без прыжка.
## Вид ## Вид
### Имя скелета - *skeleton-name* ### Имя скелета - *skeleton-name*

View file

@ -4,14 +4,17 @@
Настройки погоды: Настройки погоды:
| Свойство | Описание | По-умолчанию | | Свойство | Описание | По-умолчанию |
| ------------ | ---------------------------------------- | ------------ | | ------------- | ------------------------------------------ | ----------------- |
| fall | Осадки (см. таблица 2) | {} | | fall | Осадки (см. таблица 2) | {} |
| clouds | Облачность [0.0, 1.0] | 0.0 | | clouds | Облачность [0.0, 1.0] | 0.0 |
| fog_opacity | Максимальная плотность тумана [0.0, 1.0] | 0.0 | | fog_opacity | Максимальная плотность тумана [0.0, 1.0] | 0.0 |
| fog_dencity | Плотность тумана | 1.0 | | fog_dencity | Плотность тумана | 1.0 |
| fog_curve | Кривая тумана | 1.0 | | fog_curve | Кривая тумана | 1.0 |
| thunder_rate | Частота грома [0.0, 1.0] | 0.0 | | thunder_rate | Частота грома [0.0, 1.0] | 0.0 |
| sky_tint | Множитель цвета неба | {1, 1, 1} |
| clouds_tint | Множитель цвета облаков | {1, 1, 1} |
| min_sky_light | Минимальный свет излучаемый небом (лунный) | {0.2, 0.25, 0.33} |
Осадки: Осадки:

View file

@ -114,15 +114,29 @@ mat4.decompose(m: matrix)
} или nil } или nil
``` ```
## Отслеживание точки *mat4.look_at(...)* ## Отслеживание точки - *mat4.look_at(...)*
```lua ```lua
-- cоздает матрицу вида с точки 'eye' на точку 'center', где вектор 'up' определяет верх. -- cоздаёт матрицу вида с точки 'eye' на точку 'center', где вектор 'up' определяет верх.
mat4.look_at(eye: vec3, center: vec3, up: vec3) mat4.look_at(eye: vec3, center: vec3, up: vec3)
-- записывает матрицу вида в dst -- записывает матрицу вида в dst
mat4.look_at(eye: vec3, center: vec3, up: vec3, dst: matrix) mat4.look_at(eye: vec3, center: vec3, up: vec3, dst: matrix)
``` ```
## Расчёт матрицы перспективы - *mat4.perspective*
```lua
-- возвращает расчитанную матрицу перспективы.
-- параметр fov принимает значение вертикального поля зрения в градусах.
-- параметр ratio задаёт отношение сторон (сторона X к стороне Y, пример: для монитора 800:600 отношение сторон равно 4 к 3, или 4 / 3).
-- параметр near задаёт расстояние от камеры до плоскости Near.
-- параметр far задаёт расстояние от камеры до плоскости Far.
mat4.perspective(fov: number, ratio: number, near: number, far: number) -> mat4
-- расчитывает матрицу перспективы на основе переданных значений и записывает её в переменную dst.
mat4.perspective(fov: number, ratio: number, near: number, far: number, dst: mat4) -> nil
```
## Перевод в строку - *mat4.tostring(...)* ## Перевод в строку - *mat4.tostring(...)*
```lua ```lua

View file

@ -82,6 +82,7 @@ pack.get_info(packid: string) -> {
- `!` - required - `!` - required
- `?` - optional - `?` - optional
- `~` - weak - `~` - weak
например `!teal` например `!teal`
Для получения информации о нескольких паках используйте таблицу id, чтобы не Для получения информации о нескольких паках используйте таблицу id, чтобы не

View file

@ -2,22 +2,34 @@
Библиотека для работы с кватернионами. Библиотека для работы с кватернионами.
## Кватернион из матрицы - *mat4.from_quat(...)* ## Кватернион из матрицы - *quat.from_mat4(...)*
```lua ```lua
-- создает кватернион на основе матрицы вращения -- создаёт кватернион на основе матрицы вращения
quat.from_mat4(m: matrix) -> quat quat.from_mat4(m: matrix) -> quat
-- записывает кватернион по матрице вращения в dst -- записывает кватернион по матрице вращения в dst
quat.from_mat4(m: matrix, dst: quat) quat.from_mat4(m: matrix, dst: quat)
``` ```
## Кватернион из углов Эйлера - *quat.from_euler(...)*
```lua
-- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll)
-- (значения углов строго в градусах)
quat.from_euler(euler: vec3) -> quat
-- записывает кватернион в dst на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll)
-- (значения углов строго в градусах)
quat.from_euler(euler: vec3, dst: quat)
```
## Сферическая линейная интерполяция - *quat.slerp(...)* ## Сферическая линейная интерполяция - *quat.slerp(...)*
Интерполяция всегда выполняется по короткому пути, а вращение выполняется с постоянной скоростью. Интерполяция всегда выполняется по короткому пути, а вращение выполняется с постоянной скоростью.
```lua ```lua
-- создает кватернион как интерполяцию между a и b, -- создаёт кватернион как интерполяцию между a и b,
-- где t - фактор интерполяции -- где t - фактор интерполяции
quat.slerp(a: quat, b: quat, t: number) -> quat quat.slerp(a: quat, b: quat, t: number) -> quat
@ -29,6 +41,6 @@ quat.slerp(a: quat, b: quat, t: number, dst: quat)
## Перевод в строку - *quat.tostring(...)* ## Перевод в строку - *quat.tostring(...)*
```lua ```lua
-- возвращает строку представляющую содержимое кватерниона -- возвращает строку, представляющую содержимое кватерниона
quat.tostring(q: quat) -> string quat.tostring(q: quat) -> string
``` ```

View file

@ -114,6 +114,21 @@ body:set_crouching(enabled: bool)
body:get_body_type() -> str body:get_body_type() -> str
-- Устанавливает тип физического тела -- Устанавливает тип физического тела
body:set_body_type(type: str) body:set_body_type(type: str)
-- Возвращает материал тела (то же, что и у блоков)
body:get_material() -> str
-- Устанавливает материал тела
body:set_material(material: str)
-- Возвращает массу тела
body:get_mass() -> number
-- Устанавливает массу тела
body:set_mass(mass: number)
-- Возвращает упругость тела
body:get_elasticity() -> number
-- Устанавливает упругость тела
body:set_elasticity(elasticity: number)
``` ```
### Skeleton ### Skeleton

View file

@ -137,6 +137,18 @@ io_stream:write_line(string)
io_stream:read_fully( io_stream:read_fully(
[опционально] useTable: boolean [опционально] useTable: boolean
) -> Bytearray | table<number> | table<string> | string ) -> Bytearray | table<number> | table<string> | string
--[[
Устанавливает позицию в потоке
Режимы:
b - Задаёт позицию относительно начало файла
c - Задаёт позицию относительно текущей позиции
e - Задаёт позицию относительно конца файла
--]]
io_stream:seek(
mode: string
offset: number
)
``` ```
## Методы Buffered-режима ## Методы Buffered-режима

View file

@ -100,6 +100,7 @@
Внутренний текст - текст кнопки. Внутренний текст - текст кнопки.
- `font` - имя шрифта
- `text-align` - выравнивание текста ("left", "center" или "right"). Тип: строка. - `text-align` - выравнивание текста ("left", "center" или "right"). Тип: строка.
## Флажок - *checkbox* ## Флажок - *checkbox*
@ -110,6 +111,7 @@
## Метка - *label* ## Метка - *label*
- `font` - имя шрифта
- `text-align` - выравнивание текста ("left", "center" или "right"). Тип: строка. - `text-align` - выравнивание текста ("left", "center" или "right"). Тип: строка.
- `valign` - вертикальное выравнивание текста: top/center/bottom - `valign` - вертикальное выравнивание текста: top/center/bottom
- `supplier` - поставщик текста (вызывается каждый кадр) - `supplier` - поставщик текста (вызывается каждый кадр)
@ -132,6 +134,7 @@
Внутренний текст - изначально введенный текст Внутренний текст - изначально введенный текст
- `font` - имя шрифта
- `placeholder` - текст подстановки (используется если текстовое поле пусто) - `placeholder` - текст подстановки (используется если текстовое поле пусто)
- `hint` - текст, отображаемый, если текстовое поле пусто (не отправляется в consumer, sub-consumer и validator). - `hint` - текст, отображаемый, если текстовое поле пусто (не отправляется в consumer, sub-consumer и validator).
- `supplier` - поставщик текста (вызывается каждый кадр) - `supplier` - поставщик текста (вызывается каждый кадр)
@ -191,6 +194,14 @@
- `selected` - изначально выбранное значение. По-умолчанию: "". - `selected` - изначально выбранное значение. По-умолчанию: "".
- `onselect` - функция, в которую передаётся выбранное пользователем значение - `onselect` - функция, в которую передаётся выбранное пользователем значение
## Привязка ввода - *bindbox*
Элемент для отображения и изменения привязки клавиш или кнопок мыши.
- `font` - имя шрифта. Тип: строка.
- `binding` - имя привязки. Тип: строка.
- `padding` - отступ между текстом и границами элемента. Тип: число. По-умолчанию: 6
# Элементы инвентаря # Элементы инвентаря
## Инвентарь - *inventory* ## Инвентарь - *inventory*
@ -214,10 +225,8 @@
## Сетка слотов - *slots-grid* ## Сетка слотов - *slots-grid*
Элемент должен находиться внутри `inventory` элемента, без посредников. Элемент должен находиться внутри `inventory` элемента, без посредников.
- `start-index` - индекс первого слота - `start-index` - индекс первого слота
- `rows` - число рядов (не указывается, если указано cols).
- `cols` - число столбцов (не указывается, если указано rows).
- `count` - общее число слотов (не указывается, если указаны rows и cols).
- `interval` - интервал между слотами. Тип: число. - `interval` - интервал между слотами. Тип: число.
- `padding` - отступ вокруг решетки слотов. Тип: число. (*атрибут будет удален*) - `padding` - отступ вокруг решетки слотов. Тип: число. (*атрибут будет удален*)
- `sharefunc` - lua событие вызываемое при использовании ЛКМ + Shift. Передается id инвентаря и индекс слота - `sharefunc` - lua событие вызываемое при использовании ЛКМ + Shift. Передается id инвентаря и индекс слота
@ -225,3 +234,8 @@
- `onrightclick` - lua событие вызываемое при использовании ПКМ. Передается id инвентаря и индекс слота - `onrightclick` - lua событие вызываемое при использовании ПКМ. Передается id инвентаря и индекс слота
- `taking` - возможность взять предмет из слота. - `taking` - возможность взять предмет из слота.
- `placing` - возможность положить предмет в слот. - `placing` - возможность положить предмет в слот.
Конфигурация слотов (достаточно указать два атрибута):
- `rows` - число рядов. Тип: число.
- `cols` - число столбцов. Тип: число.
- `count` - общее число слотов. Тип: число.

View file

@ -3,5 +3,6 @@
"base:falling_block" "base:falling_block"
], ],
"skeleton-name": "base:block", "skeleton-name": "base:block",
"hitbox": [0.98, 0.98, 0.98] "hitbox": [0.98, 0.98, 0.98],
"solid": true
} }

View file

@ -29,6 +29,7 @@ do -- setup visuals
0, 0, 0, 1 0, 0, 0, 1
} }
rig:set_matrix(0, matrix) rig:set_matrix(0, matrix)
body:set_material(block.material(id))
end end
function on_grounded() function on_grounded()

View file

@ -32,6 +32,8 @@ local Rigidbody = {__index={
set_crouching=function(self, b) return __rigidbody.set_crouching(self.eid, b) end, set_crouching=function(self, b) return __rigidbody.set_crouching(self.eid, b) end,
get_body_type=function(self) return __rigidbody.get_body_type(self.eid) end, get_body_type=function(self) return __rigidbody.get_body_type(self.eid) end,
set_body_type=function(self, s) return __rigidbody.set_body_type(self.eid, s) end, set_body_type=function(self, s) return __rigidbody.set_body_type(self.eid, s) end,
get_material=function(self) return __rigidbody.get_material(self.eid) end,
set_material=function(self, s) return __rigidbody.set_material(self.eid, s) end,
}} }}
local function new_Rigidbody(eid) local function new_Rigidbody(eid)

View file

@ -3,6 +3,7 @@ local io_stream = require "core:io_stream"
local lib = { local lib = {
read = file.__read_descriptor, read = file.__read_descriptor,
write = file.__write_descriptor, write = file.__write_descriptor,
seek = file.__seek_descriptor,
flush = file.__flush_descriptor, flush = file.__flush_descriptor,
is_alive = file.__has_descriptor, is_alive = file.__has_descriptor,
close = file.__close_descriptor close = file.__close_descriptor

View file

@ -58,6 +58,10 @@ function lib.write(fd, bytearray)
end end
end end
function lib.seek(fd, mode, offset)
error("cannot seek the named pipe")
end
function lib.flush(fd) function lib.flush(fd)
-- no flush on unix -- no flush on unix
end end

View file

@ -62,7 +62,7 @@ function lib.read(handle, len)
if not has_data then if not has_data then
return out return out
elseif hasData == -1 then elseif has_data == -1 then
error("failed to read from named pipe: "..tostring(C.GetLastError())) error("failed to read from named pipe: "..tostring(C.GetLastError()))
end end
@ -84,8 +84,8 @@ end
function lib.write(handle, bytearray) function lib.write(handle, bytearray)
local len = #bytearray local len = #bytearray
local buffer = FFI.new("uint8_t[?]", len) local buffer = FFI.new("uint8_t[?]", len)
for i = 1, len do for i = 1, len do
buffer[i-1] = bytearray[i] buffer[i-1] = bytearray[i]
end end
@ -97,6 +97,10 @@ function lib.write(handle, bytearray)
end end
end end
function lib.seek(handle, mode, offset)
error("cannot seek the named pipe")
end
function lib.flush(handle) function lib.flush(handle)
C.FlushFileBuffers(handle) C.FlushFileBuffers(handle)
end end

View file

@ -369,6 +369,10 @@ function io_stream:write(arg, ...)
end end
end end
function io_stream:seek(mode, offset)
self.ioLib.seek(self.descriptor, mode, offset)
end
function io_stream:is_alive() function io_stream:is_alive()
return self.ioLib.is_alive(self.descriptor) return self.ioLib.is_alive(self.descriptor)
end end

View file

@ -60,6 +60,7 @@ end
local prepared = false local prepared = false
body:set_enabled(false) body:set_enabled(false)
mob:set_flight(true)
function on_physics_update(delta) function on_physics_update(delta)
if not prepared then if not prepared then

View file

@ -344,9 +344,10 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context& ctx) { static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context& ctx) {
std::string name = root.attr("name", "").getText(); std::string name = root.attr("name", "").getText();
glm::vec3 movement {};
glm::mat4 tsf(1.0f); glm::mat4 tsf(1.0f);
if (root.has("move")) { if (root.has("move")) {
tsf = glm::translate(tsf, root.attr("move").asVec3()); movement = root.attr("move").asVec3();
} }
if (root.has("rotate")) { if (root.has("rotate")) {
auto text = root.attr("rotate").getText(); auto text = root.attr("rotate").getText();
@ -371,7 +372,7 @@ static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context&
} }
builder.pop(); builder.pop();
} else { } else {
glm::vec3 origin = builder.getTransform() * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); glm::vec3 origin = builder.getTransform() * glm::vec4(movement, 1.0f);
size_t boneIndex = ctx.boneIndex++; size_t boneIndex = ctx.boneIndex++;
std::vector<std::unique_ptr<Bone>> bones; std::vector<std::unique_ptr<Bone>> bones;

View file

@ -88,5 +88,8 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
root.at("skeleton-name").get(def.skeletonName); root.at("skeleton-name").get(def.skeletonName);
root.at("blocking").get(def.blocking); root.at("blocking").get(def.blocking);
root.at("solid").get(def.solid);
root.at("mass").get(def.mass);
root.at("elasticity").get(def.elasticity);
root.at("step-height").get(def.stepHeight); root.at("step-height").get(def.stepHeight);
} }

View file

@ -18,6 +18,8 @@ AssetsManagement::AssetsManagement(Engine& engine)
AssetsManagement::~AssetsManagement() { AssetsManagement::~AssetsManagement() {
finishBackgroundLoader(); finishBackgroundLoader();
assets.reset();
assetsVault.clearKeepedObjects();
} }
const Assets* AssetsManagement::getStorage() const { const Assets* AssetsManagement::getStorage() const {

View file

@ -10,17 +10,19 @@
#include "logic/LevelController.hpp" #include "logic/LevelController.hpp"
#include "logic/PlayerController.hpp" #include "logic/PlayerController.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "physics/Hitbox.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/Chunks.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
LevelFrontend::LevelFrontend( LevelFrontend::LevelFrontend(
Engine& engine, Engine& engine,
Player* currentPlayer, PlayerController& playerController,
LevelController* controller, LevelController& controller,
const EngineSettings& settings const EngineSettings& settings
) )
: level(*controller->getLevel()), : level(*controller.getLevel()),
controller(controller), controller(controller),
assets(*engine.getAssets()), assets(*engine.getAssets()),
contentCache(std::make_unique<ContentGfxCache>( contentCache(std::make_unique<ContentGfxCache>(
@ -36,45 +38,79 @@ LevelFrontend::LevelFrontend(
"block-previews" "block-previews"
); );
auto& rassets = assets; auto& currentPlayer = playerController.getPlayer();
controller->getBlocksController()->listenBlockInteraction( auto& assets = this->assets;
[currentPlayer, controller, &rassets](auto player, const auto& pos, const auto& def, BlockInteraction type) { auto& level = this->level;
const auto& level = *controller->getLevel();
playerController.setFootstepCallback(
[&level, &currentPlayer, &assets](const Hitbox& hitbox) {
const BlockMaterial* material = nullptr;
if (hitbox.groundMaterial.empty()) {
const auto& pos = hitbox.position;
const auto& half = hitbox.getHalfSize();
auto& blockIndices = level.content.getIndices()->blocks;
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
for (int offsetX = -1; offsetX <= 1; offsetX++) {
int x = std::floor(pos.x + half.x * offsetX);
int y = std::floor(pos.y - half.y * 1.1f);
int z = std::floor(pos.z + half.z * offsetZ);
auto vox = currentPlayer.chunks->get(x, y, z);
if (vox) {
auto& def = blockIndices.require(vox->id);
if (!def.obstacle) {
continue;
}
material = level.content.findBlockMaterial(def.material);
break;
}
}
}
} else {
material = level.content.findBlockMaterial(hitbox.groundMaterial);
}
if (material == nullptr) {
return;
}
auto sound = assets.get<audio::Sound>(material->stepsSound);
glm::vec3 pos {};
auto soundsCamera = currentPlayer.currentCamera.get();
if (currentPlayer.isCurrentCameraBuiltin()) {
soundsCamera = currentPlayer.fpCamera.get();
}
bool relative = soundsCamera == currentPlayer.fpCamera.get();
if (!relative) {
pos = currentPlayer.getPosition();
}
audio::play(
sound,
pos,
relative,
0.333f,
1.0f + (rand() % 6 - 3) * 0.05f,
false,
audio::PRIORITY_LOW,
audio::get_channel_index("regular")
);
});
controller.getBlocksController()->listenBlockInteraction(
[&level, &assets]
(auto player, const auto& pos, const auto& def, BlockInteraction type) {
auto material = level.content.findBlockMaterial(def.material); auto material = level.content.findBlockMaterial(def.material);
if (material == nullptr) { if (material == nullptr) {
return; return;
} }
if (type == BlockInteraction::step) { if (type != BlockInteraction::step) {
auto sound = rassets.get<audio::Sound>(material->stepsSound);
glm::vec3 pos {};
auto soundsCamera = currentPlayer->currentCamera.get();
if (currentPlayer->isCurrentCameraBuiltin()) {
soundsCamera = currentPlayer->fpCamera.get();
}
bool relative = player == currentPlayer &&
soundsCamera == currentPlayer->fpCamera.get();
if (!relative) {
pos = player->getPosition();
}
audio::play(
sound,
pos,
relative,
0.333f,
1.0f + (rand() % 6 - 3) * 0.05f,
false,
audio::PRIORITY_LOW,
audio::get_channel_index("regular")
);
} else {
audio::Sound* sound = nullptr; audio::Sound* sound = nullptr;
switch (type) { switch (type) {
case BlockInteraction::placing: case BlockInteraction::placing:
sound = rassets.get<audio::Sound>(material->placeSound); sound = assets.get<audio::Sound>(material->placeSound);
break; break;
case BlockInteraction::destruction: case BlockInteraction::destruction:
sound = rassets.get<audio::Sound>(material->breakSound); sound = assets.get<audio::Sound>(material->breakSound);
break; break;
default: default:
break; break;
@ -108,6 +144,6 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const {
return *contentCache; return *contentCache;
} }
LevelController* LevelFrontend::getController() const { LevelController& LevelFrontend::getController() const {
return controller; return controller;
} }

View file

@ -2,24 +2,24 @@
#include <memory> #include <memory>
class Level;
class Assets; class Assets;
class Player;
class Engine;
class ContentGfxCache; class ContentGfxCache;
class Engine;
class Level;
class LevelController; class LevelController;
class PlayerController;
struct EngineSettings; struct EngineSettings;
class LevelFrontend { class LevelFrontend {
Level& level; Level& level;
LevelController* controller; LevelController& controller;
Assets& assets; Assets& assets;
std::unique_ptr<ContentGfxCache> contentCache; std::unique_ptr<ContentGfxCache> contentCache;
public: public:
LevelFrontend( LevelFrontend(
Engine& engine, Engine& engine,
Player* currentPlayer, PlayerController& currentPlayer,
LevelController* controller, LevelController& controller,
const EngineSettings& settings const EngineSettings& settings
); );
~LevelFrontend(); ~LevelFrontend();
@ -27,5 +27,5 @@ public:
Level& getLevel(); Level& getLevel();
const ContentGfxCache& getContentGfxCache() const; const ContentGfxCache& getContentGfxCache() const;
ContentGfxCache& getContentGfxCache(); ContentGfxCache& getContentGfxCache();
LevelController* getController() const; LevelController& getController() const;
}; };

View file

@ -272,7 +272,7 @@ void Hud::updateWorldGenDebug() {
const auto& chunks = *player.chunks; const auto& chunks = *player.chunks;
uint padding = engine.getSettings().chunks.padding.get(); uint padding = engine.getSettings().chunks.padding.get();
auto generator = auto generator =
frontend.getController()->getChunksController()->getGenerator(); frontend.getController().getChunksController()->getGenerator();
auto debugInfo = generator->createDebugInfo(); auto debugInfo = generator->createDebugInfo();
int width = debugImgWorldGen->getWidth(); int width = debugImgWorldGen->getWidth();
@ -294,7 +294,7 @@ void Hud::updateWorldGenDebug() {
bool isInLoadingZone = bool isInLoadingZone =
frontend.getController() frontend.getController()
->getChunksController() .getChunksController()
->isInLoadingZone(player, padding, ax + ox, az + oz); ->isInLoadingZone(player, padding, ax + ox, az + oz);
data[(flippedZ * width + x) * 4 + 1] = data[(flippedZ * width + x) * 4 + 1] =

View file

@ -68,7 +68,7 @@ LevelScreen::LevelScreen(
); );
frontend = std::make_unique<LevelFrontend>( frontend = std::make_unique<LevelFrontend>(
engine, player, controller.get(), settings engine, *playerController, *controller, settings
); );
renderer = std::make_unique<WorldRenderer>( renderer = std::make_unique<WorldRenderer>(
engine, *frontend, *player engine, *frontend, *player

View file

@ -3,6 +3,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <cstdint>
class Engine; class Engine;
class World; class World;

View file

@ -44,7 +44,9 @@ namespace {
const float CROUCH_SHIFT_Y = -0.2f; const float CROUCH_SHIFT_Y = -0.2f;
} }
CameraControl::CameraControl(Player& player, const CameraSettings& settings) CameraControl::CameraControl(
Player& player, const CameraSettings& settings
)
: player(player), : player(player),
camera(player.fpCamera), camera(player.fpCamera),
settings(settings), settings(settings),
@ -195,18 +197,30 @@ void CameraControl::update(
refreshPosition(); refreshPosition();
auto castRay = [](Player& player, Camera& camera, const glm::vec3& front)
-> glm::vec3 {
auto blockEnd = player.chunks->rayCastToObstacle(camera.position, front, 3.0f);
auto entityRay = player.getLevel().entities->rayCast(
camera.position, front, 3.0f, player.getEntity(), true
);
if (entityRay.has_value() &&
entityRay->distance < glm::distance(camera.position, blockEnd)) {
return (camera.position + front * entityRay->distance);
} else {
return blockEnd;
}
};
camera->updateVectors(); camera->updateVectors();
if (player.currentCamera == spCamera) { if (player.currentCamera == spCamera) {
spCamera->position = spCamera->position =
chunks.rayCastToObstacle(camera->position, camera->front, 3.0f) - castRay(player, *camera, camera->front) - 0.4f * camera->front;
0.4f * camera->front;
spCamera->dir = -camera->dir; spCamera->dir = -camera->dir;
spCamera->front = -camera->front; spCamera->front = -camera->front;
spCamera->right = -camera->right; spCamera->right = -camera->right;
} else if (player.currentCamera == tpCamera) { } else if (player.currentCamera == tpCamera) {
tpCamera->position = tpCamera->position = castRay(player, *camera, camera->front * -1.0f) +
chunks.rayCastToObstacle(camera->position, -camera->front, 3.0f) + 0.4f * camera->front;
0.4f * camera->front;
tpCamera->dir = camera->dir; tpCamera->dir = camera->dir;
tpCamera->front = camera->front; tpCamera->front = camera->front;
tpCamera->right = camera->right; tpCamera->right = camera->right;
@ -229,26 +243,8 @@ PlayerController::PlayerController(
} }
void PlayerController::onFootstep(const Hitbox& hitbox) { void PlayerController::onFootstep(const Hitbox& hitbox) {
auto pos = hitbox.position; if (footstepCallback) {
auto half = hitbox.halfsize; footstepCallback(hitbox);
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
for (int offsetX = -1; offsetX <= 1; offsetX++) {
int x = std::floor(pos.x + half.x * offsetX);
int y = std::floor(pos.y - half.y * 1.1f);
int z = std::floor(pos.z + half.z * offsetZ);
auto vox = player.chunks->get(x, y, z);
if (vox) {
auto& def = level.content.getIndices()->blocks.require(vox->id);
if (!def.obstacle) {
continue;
}
blocksController.onBlockInteraction(
&player, glm::ivec3(x, y, z), def, BlockInteraction::step
);
return;
}
}
} }
} }
@ -557,3 +553,7 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta)
Player& PlayerController::getPlayer() { Player& PlayerController::getPlayer() {
return player; return player;
} }
void PlayerController::setFootstepCallback(FootstepCallback&& callback) {
footstepCallback = std::move(callback);
}

View file

@ -18,6 +18,8 @@ struct Hitbox;
struct CameraSettings; struct CameraSettings;
struct EngineSettings; struct EngineSettings;
using FootstepCallback = std::function<void(const Hitbox&)>;
class CameraControl { class CameraControl {
Player& player; Player& player;
std::shared_ptr<Camera> camera; std::shared_ptr<Camera> camera;
@ -57,6 +59,7 @@ class PlayerController {
CameraControl camControl; CameraControl camControl;
BlocksController& blocksController; BlocksController& blocksController;
float interactionTimer = 0.0f; float interactionTimer = 0.0f;
FootstepCallback footstepCallback;
void updateKeyboard(const Input& inputEvents); void updateKeyboard(const Input& inputEvents);
void resetKeyboard(); void resetKeyboard();
@ -89,5 +92,8 @@ public:
void postUpdate( void postUpdate(
float delta, int windowHeight, const Input* inputEvents, bool pause float delta, int windowHeight, const Input* inputEvents, bool pause
); );
Player& getPlayer(); Player& getPlayer();
void setFootstepCallback(FootstepCallback&& callback);
}; };

View file

@ -150,6 +150,48 @@ static int l_set_linear_damping(lua::State* L) {
return 0; return 0;
} }
static int l_set_material(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
entity->getRigidbody().hitbox.material = lua::require_string(L, 2);
}
return 0;
}
static int l_get_material(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
return lua::pushstring(L, entity->getRigidbody().hitbox.material);
}
return 0;
}
static int l_get_mass(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
return lua::pushnumber(L, entity->getRigidbody().mass);
}
return 0;
}
static int l_set_mass(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
entity->getRigidbody().mass = lua::tonumber(L, 2);
}
return 0;
}
static int l_get_elasticity(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
return lua::pushnumber(L, entity->getRigidbody().elasticity);
}
return 0;
}
static int l_set_elasticity(lua::State* L) {
if (auto entity = get_entity(L, 1)) {
entity->getRigidbody().elasticity = lua::tonumber(L, 2);
}
return 0;
}
const luaL_Reg rigidbodylib[] = { const luaL_Reg rigidbodylib[] = {
{"is_enabled", lua::wrap<l_is_enabled>}, {"is_enabled", lua::wrap<l_is_enabled>},
{"set_enabled", lua::wrap<l_set_enabled>}, {"set_enabled", lua::wrap<l_set_enabled>},
@ -169,5 +211,11 @@ const luaL_Reg rigidbodylib[] = {
{"set_crouching", lua::wrap<l_set_crouching>}, {"set_crouching", lua::wrap<l_set_crouching>},
{"get_body_type", lua::wrap<l_get_body_type>}, {"get_body_type", lua::wrap<l_get_body_type>},
{"set_body_type", lua::wrap<l_set_body_type>}, {"set_body_type", lua::wrap<l_set_body_type>},
{"set_material", lua::wrap<l_set_material>},
{"get_material", lua::wrap<l_get_material>},
{"get_mass", lua::wrap<l_get_mass>},
{"set_mass", lua::wrap<l_set_mass>},
{"get_elasticity", lua::wrap<l_get_elasticity>},
{"set_elasticity", lua::wrap<l_set_elasticity>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -31,7 +31,7 @@ static int l_set_pos(lua::State* L) {
auto vec = lua::tovec3(L, 2); auto vec = lua::tovec3(L, 2);
check_valid(vec); check_valid(vec);
entity->getTransform().setPos(vec); entity->getTransform().setPos(vec);
entity->getRigidbody().hitbox.position = vec; entity->getRigidbody().hitbox.setPos(vec);
} }
return 0; return 0;
} }

View file

@ -20,8 +20,22 @@
using namespace scripting; using namespace scripting;
static inline const Block* get_block_def(lua::State* L) { static const Content& require_content() {
auto indices = content->getIndices(); if (content == nullptr) {
throw std::runtime_error("content is not initialized");
}
return *content;
}
static Level& require_level() {
if (level == nullptr) {
throw std::runtime_error("level is not initialized");
}
return *level;
}
static const Block* get_block_def(lua::State* L) {
auto indices = require_content().getIndices();
auto id = lua::tointeger(L, 1); auto id = lua::tointeger(L, 1);
return indices->blocks.get(id); return indices->blocks.get(id);
} }
@ -45,7 +59,7 @@ static int l_is_solid_at(lua::State* L) {
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
return lua::pushboolean( return lua::pushboolean(
L, blocks_agent::is_solid_at(*level->chunks, x, y, z) L, blocks_agent::is_solid_at(*require_level().chunks, x, y, z)
); );
} }
@ -55,7 +69,7 @@ static int l_count(lua::State* L) {
static int l_index(lua::State* L) { static int l_index(lua::State* L) {
auto name = lua::require_string(L, 1); auto name = lua::require_string(L, 1);
return lua::pushinteger(L, content->blocks.require(name).rt.id); return lua::pushinteger(L, require_content().blocks.require(name).rt.id);
} }
static int l_is_extended(lua::State* L) { static int l_is_extended(lua::State* L) {
@ -76,7 +90,7 @@ static int l_is_segment(lua::State* L) {
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
const auto& vox = blocks_agent::require(*level->chunks, x, y, z); const auto& vox = blocks_agent::require(*require_level().chunks, x, y, z);
return lua::pushboolean(L, vox.state.segment); return lua::pushboolean(L, vox.state.segment);
} }
@ -84,10 +98,11 @@ static int l_seek_origin(lua::State* L) {
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
const auto& vox = blocks_agent::require(*level->chunks, x, y, z); const auto& level = require_level();
const auto& vox = blocks_agent::require(*level.chunks, x, y, z);
auto& def = indices->blocks.require(vox.id); auto& def = indices->blocks.require(vox.id);
return lua::pushivec_stack( return lua::pushivec_stack(
L, blocks_agent::seek_origin(*level->chunks, {x, y, z}, def, vox.state) L, blocks_agent::seek_origin(*level.chunks, {x, y, z}, def, vox.state)
); );
} }
@ -98,10 +113,12 @@ static int l_set(lua::State* L) {
auto id = lua::tointeger(L, 4); auto id = lua::tointeger(L, 4);
auto state = lua::tointeger(L, 5); auto state = lua::tointeger(L, 5);
bool noupdate = lua::toboolean(L, 6); bool noupdate = lua::toboolean(L, 6);
if (static_cast<size_t>(id) >= indices->blocks.count()) { auto& level = require_level();
auto& indices = require_content().getIndices()->blocks;
if (static_cast<size_t>(id) >= indices.count()) {
return 0; return 0;
} }
if (!blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state))) { if (!blocks_agent::set(*level.chunks, x, y, z, id, int2blockstate(state))) {
return 0; return 0;
} }
@ -123,7 +140,7 @@ static int l_get(lua::State* L) {
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto vox = blocks_agent::get(*require_level().chunks, x, y, z);
int id = vox == nullptr ? -1 : vox->id; int id = vox == nullptr ? -1 : vox->id;
return lua::pushinteger(L, id); return lua::pushinteger(L, id);
} }
@ -138,8 +155,9 @@ template<int n>
static int get_axis(lua::State* L) { static int get_axis(lua::State* L) {
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto& level = require_level();
if (lua::gettop(L) == 2) { if (lua::gettop(L) == 2) {
const auto& def = level->content.getIndices()->blocks.require(x); const auto& def = level.content.getIndices()->blocks.require(x);
return get_axis<n>(L, def, y); return get_axis<n>(L, def, y);
} }
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
@ -147,11 +165,11 @@ static int get_axis(lua::State* L) {
glm::ivec3 defAxis {}; glm::ivec3 defAxis {};
defAxis[n] = 1; defAxis[n] = 1;
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto vox = blocks_agent::get(*level.chunks, x, y, z);
if (vox == nullptr) { if (vox == nullptr) {
return lua::pushivec_stack(L, defAxis); return lua::pushivec_stack(L, defAxis);
} }
const auto& def = level->content.getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (!def.rotatable) { if (!def.rotatable) {
return lua::pushivec_stack(L, defAxis); return lua::pushivec_stack(L, defAxis);
} else { } else {
@ -175,7 +193,7 @@ static int l_get_rotation(lua::State* L) {
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto vox = blocks_agent::get(*require_level().chunks, x, y, z);
int rotation = vox == nullptr ? 0 : vox->state.rotation; int rotation = vox == nullptr ? 0 : vox->state.rotation;
return lua::pushinteger(L, rotation); return lua::pushinteger(L, rotation);
} }
@ -185,7 +203,7 @@ static int l_set_rotation(lua::State* L) {
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto value = lua::tointeger(L, 4); auto value = lua::tointeger(L, 4);
blocks_agent::set_rotation(*level->chunks, x, y, z, value); blocks_agent::set_rotation(*require_level().chunks, x, y, z, value);
return 0; return 0;
} }
@ -193,7 +211,7 @@ static int l_get_states(lua::State* L) {
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto vox = blocks_agent::get(*require_level().chunks, x, y, z);
int states = vox == nullptr ? 0 : blockstate2int(vox->state); int states = vox == nullptr ? 0 : blockstate2int(vox->state);
return lua::pushinteger(L, states); return lua::pushinteger(L, states);
} }
@ -208,7 +226,7 @@ static int l_set_states(lua::State* L) {
} }
int cx = floordiv<CHUNK_W>(x); int cx = floordiv<CHUNK_W>(x);
int cz = floordiv<CHUNK_D>(z); int cz = floordiv<CHUNK_D>(z);
auto chunk = blocks_agent::get_chunk(*level->chunks, cx, cz); auto chunk = blocks_agent::get_chunk(*require_level().chunks, cx, cz);
if (chunk == nullptr) { if (chunk == nullptr) {
return 0; return 0;
} }
@ -227,16 +245,17 @@ static int l_get_user_bits(lua::State* L) {
auto offset = lua::tointeger(L, 4) + VOXEL_USER_BITS_OFFSET; auto offset = lua::tointeger(L, 4) + VOXEL_USER_BITS_OFFSET;
auto bits = lua::tointeger(L, 5); auto bits = lua::tointeger(L, 5);
auto vox = blocks_agent::get(*level->chunks, x, y, z); auto& level = require_level();
auto vox = blocks_agent::get(*level.chunks, x, y, z);
if (vox == nullptr) { if (vox == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
const auto& def = content->getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.rt.extended) { if (def.rt.extended) {
auto origin = blocks_agent::seek_origin( auto origin = blocks_agent::seek_origin(
*level->chunks, {x, y, z}, def, vox->state *level.chunks, {x, y, z}, def, vox->state
); );
vox = blocks_agent::get(*level->chunks, origin.x, origin.y, origin.z); vox = blocks_agent::get(*level.chunks, origin.x, origin.y, origin.z);
if (vox == nullptr) { if (vox == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
@ -246,7 +265,9 @@ static int l_get_user_bits(lua::State* L) {
} }
static int l_get_variant(lua::State* L) { static int l_get_variant(lua::State* L) {
auto& chunks = *level->chunks; auto& level = require_level();
auto& chunks = *level.chunks;
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
@ -255,7 +276,7 @@ static int l_get_variant(lua::State* L) {
if (vox == nullptr) { if (vox == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
const auto& def = content->getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.variants == nullptr) { if (def.variants == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
@ -272,7 +293,9 @@ static int l_get_variant(lua::State* L) {
} }
static int l_set_user_bits(lua::State* L) { static int l_set_user_bits(lua::State* L) {
auto& chunks = *level->chunks; auto& level = require_level();
auto& chunks = *level.chunks;
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
@ -291,7 +314,7 @@ static int l_set_user_bits(lua::State* L) {
int lx = x - cx * CHUNK_W; int lx = x - cx * CHUNK_W;
int lz = z - cz * CHUNK_D; int lz = z - cz * CHUNK_D;
auto vox = &chunk->voxels[vox_index(lx, y, lz)]; auto vox = &chunk->voxels[vox_index(lx, y, lz)];
const auto& def = content->getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.rt.extended) { if (def.rt.extended) {
auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state); auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
vox = blocks_agent::get(chunks, origin.x, origin.y, origin.z); vox = blocks_agent::get(chunks, origin.x, origin.y, origin.z);
@ -313,7 +336,9 @@ static int l_set_user_bits(lua::State* L) {
} }
static int l_set_variant(lua::State* L) { static int l_set_variant(lua::State* L) {
auto& chunks = *level->chunks; auto& level = require_level();
auto& chunks = *level.chunks;
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
@ -327,7 +352,7 @@ static int l_set_variant(lua::State* L) {
int lx = x - cx * CHUNK_W; int lx = x - cx * CHUNK_W;
int lz = z - cz * CHUNK_D; int lz = z - cz * CHUNK_D;
auto vox = &chunk->voxels[vox_index(lx, y, lz)]; auto vox = &chunk->voxels[vox_index(lx, y, lz)];
const auto& def = content->getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.variants == nullptr) { if (def.variants == nullptr) {
return 0; return 0;
@ -358,11 +383,12 @@ static int l_set_variant(lua::State* L) {
} }
static int l_is_replaceable_at(lua::State* L) { static int l_is_replaceable_at(lua::State* L) {
auto& level = require_level();
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
return lua::pushboolean( return lua::pushboolean(
L, blocks_agent::is_replaceable_at(*level->chunks, x, y, z) L, blocks_agent::is_replaceable_at(*level.chunks, x, y, z)
); );
} }
@ -373,20 +399,28 @@ static int l_caption(lua::State* L) {
return 0; return 0;
} }
static lua::Integer get_variant_index(lua::State* L, const Block* const block, int argumentIndex) { static lua::Integer get_variant_index(
const auto variantIndex = lua::gettop(L) >= argumentIndex ? lua::tointeger(L, argumentIndex) : 0; lua::State* L, const Block& block, int argumentIndex
const size_t variantsSize = block->variants->variants.size(); ) {
const auto variantIndex =
lua::isnumber(L, argumentIndex) ? lua::tointeger(L, argumentIndex) : 0;
const size_t variantsSize = block.variants->variants.size();
if (variantIndex < 0 || variantIndex >= variantsSize) { if (variantIndex < 0 || variantIndex >= variantsSize) {
throw std::out_of_range( throw std::out_of_range(
"variant index out of bounds [0, " + std::to_string(variantsSize - 1) + "]"); "variant index out of bounds [0, " +
std::to_string(variantsSize - 1) + "]"
);
} }
return variantIndex; return variantIndex;
} }
static int l_get_textures(lua::State* L) { static int l_get_textures(lua::State* L) {
if (auto def = get_block_def(L)) { if (auto def = get_block_def(L)) {
const auto& textureFaces = (def->variants ? def->variants->variants[get_variant_index(L, def, 2)].textureFaces : const auto& textureFaces =
def->defaults.textureFaces); (def->variants
? def->variants->variants[get_variant_index(L, *def, 2)]
.textureFaces
: def->defaults.textureFaces);
lua::createtable(L, 6, 0); lua::createtable(L, 6, 0);
for (size_t i = 0; i < 6; i++) { for (size_t i = 0; i < 6; i++) {
lua::pushstring(L, textureFaces[i]); lua::pushstring(L, textureFaces[i]);
@ -397,11 +431,13 @@ static int l_get_textures(lua::State* L) {
return 0; return 0;
} }
static int l_model_name(lua::State* L) { static int l_model_name(lua::State* L) {
if (auto def = get_block_def(L)) { if (auto def = get_block_def(L)) {
const auto& modelName = (def->variants ? def->variants->variants[get_variant_index(L, def, 2)].model.name : const auto& modelName =
def->defaults.model.name); (def->variants
? def->variants->variants[get_variant_index(L, *def, 2)]
.model.name
: def->defaults.model.name);
if (modelName.empty()) { if (modelName.empty()) {
return lua::pushlstring(L, def->name + ".model"); return lua::pushlstring(L, def->name + ".model");
} }
@ -412,8 +448,11 @@ static int l_model_name(lua::State* L) {
static int l_get_model(lua::State* L) { static int l_get_model(lua::State* L) {
if (auto def = get_block_def(L)) { if (auto def = get_block_def(L)) {
const BlockModelType modelType = (def->variants ? def->variants->variants[get_variant_index(L, def, 2)].model.type : const BlockModelType modelType =
def->defaults.model.type); (def->variants
? def->variants->variants[get_variant_index(L, *def, 2)]
.model.type
: def->defaults.model.type);
return lua::pushlstring(L, BlockModelTypeMeta.getName(modelType)); return lua::pushlstring(L, BlockModelTypeMeta.getName(modelType));
} }
return 0; return 0;
@ -422,7 +461,8 @@ static int l_get_model(lua::State* L) {
static int l_get_hitbox(lua::State* L) { static int l_get_hitbox(lua::State* L) {
if (auto def = get_block_def(L)) { if (auto def = get_block_def(L)) {
size_t rotation = lua::tointeger(L, 2); size_t rotation = lua::tointeger(L, 2);
const size_t hitboxIndex = static_cast<size_t>(lua::gettop(L) >= 3 ? lua::tointeger(L, 3) : 0); const size_t hitboxIndex =
static_cast<size_t>(lua::isnumber(L, 3) ? lua::tointeger(L, 3) : 0);
if (def->rotatable) { if (def->rotatable) {
rotation %= def->rotations.MAX_COUNT; rotation %= def->rotations.MAX_COUNT;
} else { } else {
@ -456,25 +496,28 @@ static int l_get_picking_item(lua::State* L) {
} }
static int l_place(lua::State* L) { static int l_place(lua::State* L) {
auto& level = require_level();
auto& indices = *level.content.getIndices();
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto id = lua::tointeger(L, 4); auto id = lua::tointeger(L, 4);
auto state = lua::tointeger(L, 5); auto state = lua::tointeger(L, 5);
auto playerid = lua::gettop(L) >= 6 ? lua::tointeger(L, 6) : -1; auto playerid = lua::gettop(L) >= 6 ? lua::tointeger(L, 6) : -1;
if (static_cast<size_t>(id) >= indices->blocks.count()) { if (static_cast<size_t>(id) >= indices.blocks.count()) {
return 0; return 0;
} }
if (!blocks_agent::get(*level->chunks, x, y, z)) { if (!blocks_agent::get(*level.chunks, x, y, z)) {
return 0; return 0;
} }
const auto def = level->content.getIndices()->blocks.get(id); const auto def = indices.blocks.get(id);
if (def == nullptr) { if (def == nullptr) {
throw std::runtime_error( throw std::runtime_error(
"there is no block with index " + std::to_string(id) "there is no block with index " + std::to_string(id)
); );
} }
auto player = level->players->get(playerid); auto player = level.players->get(playerid);
controller->getBlocksController()->placeBlock( controller->getBlocksController()->placeBlock(
player, *def, int2blockstate(state), x, y, z player, *def, int2blockstate(state), x, y, z
); );
@ -482,21 +525,26 @@ static int l_place(lua::State* L) {
} }
static int l_destruct(lua::State* L) { static int l_destruct(lua::State* L) {
auto& level = require_level();
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
auto playerid = lua::gettop(L) >= 4 ? lua::tointeger(L, 4) : -1; auto playerid = lua::isnumber(L, 4) ? lua::tointeger(L, 4) : -1;
auto vox = blocks_agent::get(*level->chunks, x, y, z);
auto vox = blocks_agent::get(*level.chunks, x, y, z);
if (vox == nullptr) { if (vox == nullptr) {
return 0; return 0;
} }
auto& def = level->content.getIndices()->blocks.require(vox->id); auto& def = level.content.getIndices()->blocks.require(vox->id);
auto player = level->players->get(playerid); auto player = level.players->get(playerid);
controller->getBlocksController()->breakBlock(player, def, x, y, z); controller->getBlocksController()->breakBlock(player, def, x, y, z);
return 0; return 0;
} }
static int l_raycast(lua::State* L) { static int l_raycast(lua::State* L) {
auto& level = require_level();
auto start = lua::tovec<3>(L, 1); auto start = lua::tovec<3>(L, 1);
auto dir = lua::tovec<3>(L, 2); auto dir = lua::tovec<3>(L, 2);
auto maxDistance = lua::tonumber(L, 3); auto maxDistance = lua::tonumber(L, 3);
@ -526,7 +574,7 @@ static int l_raycast(lua::State* L) {
glm::ivec3 normal; glm::ivec3 normal;
glm::ivec3 iend; glm::ivec3 iend;
if (auto voxel = blocks_agent::raycast( if (auto voxel = blocks_agent::raycast(
*level->chunks, *level.chunks,
start, start,
dir, dir,
maxDistance, maxDistance,
@ -620,6 +668,8 @@ static int get_field(
} }
static int l_get_field(lua::State* L) { static int l_get_field(lua::State* L) {
auto& level = require_level();
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
@ -628,9 +678,10 @@ static int l_get_field(lua::State* L) {
if (lua::gettop(L) >= 5) { if (lua::gettop(L) >= 5) {
index = lua::tointeger(L, 5); index = lua::tointeger(L, 5);
} }
auto cx = floordiv(x, CHUNK_W); auto cx = floordiv(x, CHUNK_W);
auto cz = floordiv(z, CHUNK_D); auto cz = floordiv(z, CHUNK_D);
auto chunk = blocks_agent::get_chunk(*level->chunks, cx, cz); auto chunk = blocks_agent::get_chunk(*level.chunks, cx, cz);
if (chunk == nullptr || y < 0 || y >= CHUNK_H) { if (chunk == nullptr || y < 0 || y >= CHUNK_H) {
return 0; return 0;
} }
@ -689,6 +740,8 @@ static int set_field(
} }
static int l_set_field(lua::State* L) { static int l_set_field(lua::State* L) {
auto& level = require_level();
auto x = lua::tointeger(L, 1); auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2); auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3); auto z = lua::tointeger(L, 3);
@ -698,11 +751,12 @@ static int l_set_field(lua::State* L) {
if (lua::gettop(L) >= 6) { if (lua::gettop(L) >= 6) {
index = lua::tointeger(L, 6); index = lua::tointeger(L, 6);
} }
auto cx = floordiv(x, CHUNK_W); auto cx = floordiv(x, CHUNK_W);
auto cz = floordiv(z, CHUNK_D); auto cz = floordiv(z, CHUNK_D);
auto lx = x - cx * CHUNK_W; auto lx = x - cx * CHUNK_W;
auto lz = z - cz * CHUNK_W; auto lz = z - cz * CHUNK_W;
auto chunk = blocks_agent::get_chunk(*level->chunks, cx, cz); auto chunk = blocks_agent::get_chunk(*level.chunks, cx, cz);
if (chunk == nullptr || y < 0 || y >= CHUNK_H) { if (chunk == nullptr || y < 0 || y >= CHUNK_H) {
return 0; return 0;
} }

View file

@ -7,28 +7,42 @@
using namespace scripting; using namespace scripting;
static Level& require_level() {
if (level == nullptr) {
throw std::runtime_error("level is not initialized");
}
return *level;
}
static const Content& require_content() {
if (content == nullptr) {
throw std::runtime_error("content is not initialized");
}
return *content;
}
template <int (*getterfunc)(lua::State*, const Camera&)> template <int (*getterfunc)(lua::State*, const Camera&)>
static int l_camera_getter(lua::State* L) { static int l_camera_getter(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1)); size_t index = static_cast<size_t>(lua::tointeger(L, 1));
return getterfunc(L, *level->cameras.at(index)); return getterfunc(L, *require_level().cameras.at(index));
} }
template <void (*setterfunc)(lua::State*, Camera&, int)> template <void (*setterfunc)(lua::State*, Camera&, int)>
static int l_camera_setter(lua::State* L) { static int l_camera_setter(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1)); size_t index = static_cast<size_t>(lua::tointeger(L, 1));
setterfunc(L, *level->cameras.at(index), 2); setterfunc(L, *require_level().cameras.at(index), 2);
return 0; return 0;
} }
static int l_index(lua::State* L) { static int l_index(lua::State* L) {
auto name = lua::require_string(L, 1); auto name = lua::require_string(L, 1);
auto& indices = content->getIndices(ResourceType::CAMERA); auto& indices = require_content().getIndices(ResourceType::CAMERA);
return lua::pushinteger(L, indices.indexOf(name)); return lua::pushinteger(L, indices.indexOf(name));
} }
static int l_name(lua::State* L) { static int l_name(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1)); size_t index = static_cast<size_t>(lua::tointeger(L, 1));
auto& indices = content->getIndices(ResourceType::CAMERA); auto& indices = require_content().getIndices(ResourceType::CAMERA);
return lua::pushstring(L, indices.getName(index)); return lua::pushstring(L, indices.getName(index));
} }
@ -89,7 +103,7 @@ static int getter_up(lua::State* L, const Camera& camera) {
static int l_look_at(lua::State* L) { static int l_look_at(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1)); size_t index = static_cast<size_t>(lua::tointeger(L, 1));
auto& camera = *level->cameras.at(index); auto& camera = *require_level().cameras.at(index);
auto center = lua::tovec<3>(L, 2); auto center = lua::tovec<3>(L, 2);
auto matrix = glm::inverse( auto matrix = glm::inverse(
glm::lookAt(glm::vec3(), center - camera.position, glm::vec3(0, 1, 0)) glm::lookAt(glm::vec3(), center - camera.position, glm::vec3(0, 1, 0))

View file

@ -347,6 +347,41 @@ static int l_write_descriptor(lua::State* L) {
return 0; return 0;
} }
static int l_seek_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
std::string mode = lua::require_string(L, 2);
std::ios_base::seekdir dir;
switch (mode[0]) {
case 'b':
dir = std::ios_base::beg;
break;
case 'c':
dir = std::ios_base::cur;
break;
case 'e':
dir = std::ios_base::end;
break;
default:
throw std::runtime_error("invalid seek mode");
}
auto* stream = scripting::descriptors_manager::get_output(descriptor);
stream->seekp(lua::tointeger(L, 3), dir);
if (!stream->good()) {
throw std::runtime_error("failed to seek stream");
}
return 0;
}
static int l_flush_descriptor(lua::State* L) { static int l_flush_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1); int descriptor = lua::tointeger(L, 1);
@ -406,9 +441,9 @@ const luaL_Reg filelib[] = {
{"__has_descriptor", lua::wrap<l_has_descriptor>}, {"__has_descriptor", lua::wrap<l_has_descriptor>},
{"__read_descriptor", lua::wrap<l_read_descriptor>}, {"__read_descriptor", lua::wrap<l_read_descriptor>},
{"__write_descriptor", lua::wrap<l_write_descriptor>}, {"__write_descriptor", lua::wrap<l_write_descriptor>},
{"__seek_descriptor", lua::wrap<l_seek_descriptor>},
{"__flush_descriptor", lua::wrap<l_flush_descriptor>}, {"__flush_descriptor", lua::wrap<l_flush_descriptor>},
{"__close_descriptor", lua::wrap<l_close_descriptor>}, {"__close_descriptor", lua::wrap<l_close_descriptor>},
{"__close_all_descriptors", lua::wrap<l_close_all_descriptors>}, {"__close_all_descriptors", lua::wrap<l_close_all_descriptors>},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -8,14 +8,28 @@
using namespace scripting; using namespace scripting;
namespace { namespace {
Level& require_level() {
if (level == nullptr) {
throw std::runtime_error("world is not open");
}
return *level;
}
const Content& require_content() {
if (content == nullptr) {
throw std::runtime_error("content is not initialized");
}
return *content;
}
void validate_itemid(itemid_t id) { void validate_itemid(itemid_t id) {
if (id >= indices->items.count()) { if (id >= require_content().getIndices()->items.count()) {
throw std::runtime_error("invalid item id"); throw std::runtime_error("invalid item id");
} }
} }
Inventory& get_inventory(int64_t id) { Inventory& get_inventory(int64_t id) {
auto inv = level->inventories->get(id); auto inv = require_level().inventories->get(id);
if (inv == nullptr) { if (inv == nullptr) {
throw std::runtime_error("inventory not found: " + std::to_string(id)); throw std::runtime_error("inventory not found: " + std::to_string(id));
} }
@ -23,7 +37,7 @@ namespace {
} }
Inventory& get_inventory(int64_t id, int arg) { Inventory& get_inventory(int64_t id, int arg) {
auto inv = level->inventories->get(id); auto inv = require_level().inventories->get(id);
if (inv == nullptr) { if (inv == nullptr) {
throw std::runtime_error( throw std::runtime_error(
"inventory not found: " + std::to_string(id) + " argument " + "inventory not found: " + std::to_string(id) + " argument " +
@ -137,7 +151,7 @@ static int l_unbind_block(lua::State* L) {
static int l_create(lua::State* L) { static int l_create(lua::State* L) {
auto invsize = lua::tointeger(L, 1); auto invsize = lua::tointeger(L, 1);
auto inv = level->inventories->create(invsize); auto inv = require_level().inventories->create(invsize);
if (inv == nullptr) { if (inv == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }
@ -146,13 +160,13 @@ static int l_create(lua::State* L) {
static int l_remove(lua::State* L) { static int l_remove(lua::State* L) {
auto invid = lua::tointeger(L, 1); auto invid = lua::tointeger(L, 1);
level->inventories->remove(invid); require_level().inventories->remove(invid);
return 0; return 0;
} }
static int l_clone(lua::State* L) { static int l_clone(lua::State* L) {
auto id = lua::tointeger(L, 1); auto id = lua::tointeger(L, 1);
auto clone = level->inventories->clone(id); auto clone = require_level().inventories->clone(id);
if (clone == nullptr) { if (clone == nullptr) {
return lua::pushinteger(L, 0); return lua::pushinteger(L, 0);
} }

View file

@ -8,6 +8,9 @@
using namespace scripting; using namespace scripting;
static const ItemDef* get_item_def(lua::State* L, int idx) { static const ItemDef* get_item_def(lua::State* L, int idx) {
if (content == nullptr) {
throw std::runtime_error("content is not initialized");
}
auto indices = content->getIndices(); auto indices = content->getIndices();
auto id = lua::tointeger(L, idx); auto id = lua::tointeger(L, idx);
return indices->items.get(id); return indices->items.get(id);

View file

@ -229,6 +229,20 @@ static int l_look_at(lua::State* L) {
} }
} }
static int l_perspective(lua::State *L) {
uint argc = lua::check_argc(L, 4, 5);
double fov = glm::radians(lua::tonumber(L, 1));
double ratio = lua::tonumber(L, 2);
double near = lua::tonumber(L, 3);
double far = lua::tonumber(L, 4);
glm::mat4 mat = glm::perspective(fov, ratio, near, far);
if (argc == 5) return lua::setmat4(L, 5, mat);
else return lua::pushmat4(L, mat);
}
static int l_from_quat(lua::State* L) { static int l_from_quat(lua::State* L) {
uint argc = lua::check_argc(L, 1, 2); uint argc = lua::check_argc(L, 1, 2);
auto quat = lua::toquat(L, 1); auto quat = lua::toquat(L, 1);
@ -280,6 +294,7 @@ const luaL_Reg mat4lib[] = {
{"determinant", lua::wrap<l_determinant>}, {"determinant", lua::wrap<l_determinant>},
{"decompose", lua::wrap<l_decompose>}, {"decompose", lua::wrap<l_decompose>},
{"look_at", lua::wrap<l_look_at>}, {"look_at", lua::wrap<l_look_at>},
{"perspective", lua::wrap<l_perspective>},
{"from_quat", lua::wrap<l_from_quat>}, {"from_quat", lua::wrap<l_from_quat>},
{"tostring", lua::wrap<l_tostring>}, {"tostring", lua::wrap<l_tostring>},
{nullptr, nullptr} {nullptr, nullptr}

View file

@ -14,6 +14,13 @@
using namespace scripting; using namespace scripting;
static Level& require_level() {
if (level == nullptr) {
throw std::runtime_error("world is not loaded");
}
return *level;
}
inline Player* get_player(lua::State* L, int idx) { inline Player* get_player(lua::State* L, int idx) {
if (!lua::isnumber(L, idx)) { if (!lua::isnumber(L, idx)) {
if (engine->isHeadless()) { if (engine->isHeadless()) {
@ -22,7 +29,7 @@ inline Player* get_player(lua::State* L, int idx) {
); );
} }
} }
return level->players->get(lua::tointeger(L, idx)); return require_level().players->get(lua::tointeger(L, idx));
} }
static int l_get_pos(lua::State* L) { static int l_get_pos(lua::State* L) {
@ -304,15 +311,18 @@ static int l_create(lua::State* L) {
if (lua::gettop(L) >= 2) { if (lua::gettop(L) >= 2) {
playerId = lua::tointeger(L, 2); playerId = lua::tointeger(L, 2);
} }
auto player = level->players->create(playerId); auto& level = require_level();
auto player = level.players->create(playerId);
player->setName(lua::require_string(L, 1)); player->setName(lua::require_string(L, 1));
return lua::pushinteger(L, player->getId()); return lua::pushinteger(L, player->getId());
} }
static int l_delete(lua::State* L) { static int l_delete(lua::State* L) {
auto id = lua::tointeger(L, 1); auto id = lua::tointeger(L, 1);
level->players->suspend(id);
level->players->remove(id); auto& level = require_level();
level.players->suspend(id);
level.players->remove(id);
return 0; return 0;
} }
@ -324,19 +334,21 @@ static int l_is_suspended(lua::State* L) {
} }
static int l_set_suspended(lua::State* L) { static int l_set_suspended(lua::State* L) {
auto& level = require_level();
if (lua::toboolean(L, 2)) { if (lua::toboolean(L, 2)) {
level->players->suspend(lua::tointeger(L, 1)); level.players->suspend(lua::tointeger(L, 1));
} else { } else {
level->players->resume(lua::tointeger(L, 1)); level.players->resume(lua::tointeger(L, 1));
} }
return 0; return 0;
} }
static int l_get_all_in_radius(lua::State* L) { static int l_get_all_in_radius(lua::State* L) {
auto& level = require_level();
auto center = lua::tovec3(L, 1); auto center = lua::tovec3(L, 1);
auto radius = static_cast<float>(lua::tonumber(L, 2)); auto radius = static_cast<float>(lua::tonumber(L, 2));
auto players = level->players->getAllInRadius(center, radius); auto players = level.players->getAllInRadius(center, radius);
lua::createtable(L, players.size(), 0); lua::createtable(L, players.size(), 0);
for (size_t i = 0; i < players.size(); i++) { for (size_t i = 0; i < players.size(); i++) {
lua::pushinteger(L, players[i]->getId()); lua::pushinteger(L, players[i]->getId());
@ -346,7 +358,7 @@ static int l_get_all_in_radius(lua::State* L) {
} }
static int l_get_all(lua::State* L) { static int l_get_all(lua::State* L) {
auto players = level->players->getAll(); auto players = require_level().players->getAll();
lua::createtable(L, players.size(), 0); lua::createtable(L, players.size(), 0);
for (size_t i = 0; i < players.size(); i++) { for (size_t i = 0; i < players.size(); i++) {
lua::pushinteger(L, players[i]->getId()); lua::pushinteger(L, players[i]->getId());
@ -356,8 +368,9 @@ static int l_get_all(lua::State* L) {
} }
static int l_get_nearest(lua::State* L) { static int l_get_nearest(lua::State* L) {
auto& level = require_level();
auto position = lua::tovec3(L, 1); auto position = lua::tovec3(L, 1);
if (auto player = level->players->getNearest(position)) { if (auto player = level.players->getNearest(position)) {
lua::pushinteger(L, player->getId()); lua::pushinteger(L, player->getId());
return 1; return 1;
} }

View file

@ -44,8 +44,17 @@ static int l_tostring(lua::State* L) {
return lua::pushstring(L, ss.str()); return lua::pushstring(L, ss.str());
} }
static int l_from_euler(lua::State *L) {
uint argc = lua::check_argc(L, 1, 2);
glm::vec3 euler = glm::radians(lua::tovec3(L, 1));
if (argc == 2) return lua::setquat(L, 2, glm::quat(euler));
return lua::pushquat(L, glm::quat(euler));
}
const luaL_Reg quatlib[] = { const luaL_Reg quatlib[] = {
{"from_mat4", lua::wrap<l_from_mat4>}, {"from_mat4", lua::wrap<l_from_mat4>},
{"from_euler", lua::wrap<l_from_euler>},
{"slerp", lua::wrap<l_slerp>}, {"slerp", lua::wrap<l_slerp>},
{"tostring", lua::wrap<l_tostring>}, {"tostring", lua::wrap<l_tostring>},
{nullptr, nullptr} {nullptr, nullptr}

View file

@ -25,11 +25,15 @@
using namespace scripting; using namespace scripting;
namespace fs = std::filesystem; namespace fs = std::filesystem;
static WorldInfo& require_world_info() { static Level& require_level() {
if (level == nullptr) { if (level == nullptr) {
throw std::runtime_error("no world open"); throw std::runtime_error("world is not open");
} }
return level->getWorld()->getInfo(); return *level;
}
static WorldInfo& require_world_info() {
return require_level().getWorld()->getInfo();
} }
static int l_is_open(lua::State* L) { static int l_is_open(lua::State* L) {

View file

@ -38,15 +38,23 @@ struct AABB {
return AABB(a + pos, b + pos); return AABB(a + pos, b + pos);
} }
AABB operator+(const glm::vec3& offset) const {
return translated(offset);
}
AABB operator-(const glm::vec3& offset) const {
return translated(-offset);
}
/// @brief Multiply AABB size from center /// @brief Multiply AABB size from center
inline void scale(const glm::vec3 mul) { inline void scale(const glm::vec3& mul) {
glm::vec3 center = (a + b) * 0.5f; glm::vec3 center = (a + b) * 0.5f;
a = (a - center) * mul + center; a = (a - center) * mul + center;
b = (b - center) * mul + center; b = (b - center) * mul + center;
} }
/// @brief Multiply AABB size from given origin /// @brief Multiply AABB size from given origin
inline void scale(const glm::vec3 mul, const glm::vec3 orig) { inline void scale(const glm::vec3& mul, const glm::vec3& orig) {
glm::vec3 beg = min(); glm::vec3 beg = min();
glm::vec3 end = max(); glm::vec3 end = max();
glm::vec3 center = glm::mix(beg, end, orig); glm::vec3 center = glm::mix(beg, end, orig);
@ -91,14 +99,15 @@ struct AABB {
addPoint(matrix * glm::vec4(pb.x, pa.y, pb.z, 1.0f)); addPoint(matrix * glm::vec4(pb.x, pa.y, pb.z, 1.0f));
} }
inline bool intersect(const AABB& aabb) { inline bool intersects(const AABB& aabb) const {
return ( return (
a.x <= aabb.b.x && b.x >= aabb.a.x && a.y <= aabb.b.y && a.x <= aabb.b.x && b.x >= aabb.a.x &&
b.y >= aabb.a.y && a.z <= aabb.b.z && b.z >= aabb.a.z a.y <= aabb.b.y && b.y >= aabb.a.y &&
a.z <= aabb.b.z && b.z >= aabb.a.z
); );
} }
inline bool intersect(const AABB& aabb, float margin) { inline bool intersects(const AABB& aabb, float margin) const {
return ( return (
a.x <= aabb.b.x + margin && b.x >= aabb.a.x - margin && a.x <= aabb.b.x + margin && b.x >= aabb.a.x - margin &&
a.y <= aabb.b.y + margin && b.y >= aabb.a.y - margin && a.y <= aabb.b.y + margin && b.y >= aabb.a.y - margin &&

View file

@ -1,28 +1,30 @@
#define VC_ENABLE_REFLECTION #define VC_ENABLE_REFLECTION
#include "Entities.hpp" #include "Entities.hpp"
#include <entt/entity/registry.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <sstream>
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
#include "data/dv_util.hpp" #include "data/dv_util.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "Entity.hpp"
#include "EntityDef.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/core/DrawContext.hpp" #include "graphics/core/DrawContext.hpp"
#include "graphics/core/LineBatch.hpp" #include "graphics/core/LineBatch.hpp"
#include "graphics/commons/Model.hpp"
#include "graphics/render/ModelBatch.hpp" #include "graphics/render/ModelBatch.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "maths/FrustumCulling.hpp" #include "maths/FrustumCulling.hpp"
#include "maths/rays.hpp" #include "maths/rays.hpp"
#include "EntityDef.hpp" #include "maths/util.hpp"
#include "Entity.hpp"
#include "rigging.hpp"
#include "physics/PhysicsSolver.hpp" #include "physics/PhysicsSolver.hpp"
#include "rigging.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include <entt/entity/registry.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <limits>
#include <sstream>
static debug::Logger logger("entities"); static debug::Logger logger("entities");
Entities::Entities(Level& level) Entities::Entities(Level& level)
@ -79,7 +81,7 @@ entityid_t Entities::spawn(
entities[id] = entity; entities[id] = entity;
uids[entity] = id; uids[entity] = id;
registry->emplace<EntityId>(entity, static_cast<entityid_t>(id), def); registry->emplace<EntityId>(entity, id, def);
const auto& tsf = registry->emplace<Transform>( const auto& tsf = registry->emplace<Transform>(
entity, entity,
position, position,
@ -91,7 +93,7 @@ entityid_t Entities::spawn(
auto& body = registry->emplace<Rigidbody>( auto& body = registry->emplace<Rigidbody>(
entity, entity,
true, true,
Hitbox {def.bodyType, position, def.hitbox * 0.5f}, Hitbox {id, def.bodyType, position, def.hitbox * 0.5f},
std::vector<Sensor> {} std::vector<Sensor> {}
); );
body.initialize(def, id, *this); body.initialize(def, id, *this);
@ -161,7 +163,11 @@ void Entities::loadEntity(const dv::value& map, Entity entity) {
} }
std::optional<Entities::RaycastResult> Entities::rayCast( std::optional<Entities::RaycastResult> Entities::rayCast(
glm::vec3 start, glm::vec3 dir, float maxDistance, entityid_t ignore glm::vec3 start,
glm::vec3 dir,
float maxDistance,
entityid_t ignore,
bool solidOnly
) { ) {
Ray ray(start, dir); Ray ray(start, dir);
auto view = registry->view<EntityId, Transform, Rigidbody>(); auto view = registry->view<EntityId, Transform, Rigidbody>();
@ -170,7 +176,7 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
glm::ivec3 foundNormal; glm::ivec3 foundNormal;
for (auto [entity, eid, transform, body] : view.each()) { for (auto [entity, eid, transform, body] : view.each()) {
if (eid.uid == ignore || !body.enabled) { if (eid.uid == ignore || !body.enabled || (solidOnly && !eid.def.solid)) {
continue; continue;
} }
auto& hitbox = body.hitbox; auto& hitbox = body.hitbox;
@ -278,13 +284,18 @@ void Entities::updateSensors(
} }
void Entities::preparePhysics(float delta) { void Entities::preparePhysics(float delta) {
auto& physics = *level.physics;
auto& hitboxes = physics.getHitboxesWriteable();
auto& solidHitboxes = physics.getSolidHitboxesWriteable();
if (sensorsTickClock.update(delta)) { if (sensorsTickClock.update(delta)) {
auto part = sensorsTickClock.getPart(); auto part = sensorsTickClock.getPart();
auto parts = sensorsTickClock.getParts(); auto parts = sensorsTickClock.getParts();
auto& sensors = physics.getSensorsWriteable();
sensors.clear();
auto view = registry->view<EntityId, Transform, Rigidbody>(); auto view = registry->view<EntityId, Transform, Rigidbody>();
auto physics = level.physics.get();
std::vector<Sensor*> sensors;
for (auto [entity, eid, transform, rigidbody] : view.each()) { for (auto [entity, eid, transform, rigidbody] : view.each()) {
if (!rigidbody.enabled) { if (!rigidbody.enabled) {
continue; continue;
@ -294,7 +305,25 @@ void Entities::preparePhysics(float delta) {
} }
updateSensors(rigidbody, transform, sensors); updateSensors(rigidbody, transform, sensors);
} }
physics->setSensors(std::move(sensors)); }
hitboxes.clear();
solidHitboxes.clear();
auto view = registry->view<EntityId, Rigidbody>();
for (auto [entity, eid, rigidbody] : view.each()) {
if (eid.destroyFlag || !rigidbody.enabled) {
continue;
}
rigidbody.hitbox.mass = eid.def.bodyType == BodyType::DYNAMIC
? rigidbody.mass
: std::numeric_limits<float>::infinity();
rigidbody.hitbox.elasticity = rigidbody.elasticity;
hitboxes.emplace_back(&rigidbody.hitbox);
if (!eid.def.solid) {
continue;
}
solidHitboxes.emplace_back(&rigidbody.hitbox);
} }
} }
@ -303,29 +332,31 @@ void Entities::updatePhysics(float delta) {
auto view = registry->view<EntityId, Transform, Rigidbody>(); auto view = registry->view<EntityId, Transform, Rigidbody>();
auto physics = level.physics.get(); auto physics = level.physics.get();
int substeps = std::max<int>(std::min<int>(delta * 1000, 200), 8);
physics->step(*level.chunks, delta, substeps);
for (auto [entity, eid, transform, rigidbody] : view.each()) { for (auto [entity, eid, transform, rigidbody] : view.each()) {
if (!rigidbody.enabled || rigidbody.hitbox.type == BodyType::STATIC) { if (!rigidbody.enabled ||
rigidbody.hitbox.type == BodyType::STATIC) {
continue; continue;
} }
auto& hitbox = rigidbody.hitbox; auto& hitbox = rigidbody.hitbox;
auto prevVel = hitbox.velocity;
bool grounded = hitbox.grounded;
float vel = glm::length(prevVel);
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.friction = glm::abs(hitbox.gravityScale <= 1e-7f)
? 8.0f
: (!grounded ? 2.0f : 10.0f);
hitbox.scale = transform.size; hitbox.scale = transform.size;
transform.setPos(hitbox.position); if (util::is_nan_or_inf(hitbox.position)) {
if (hitbox.grounded && !grounded) { logger.error()
<< "physics simulation produced nan or inf (entity "
<< eid.def.name << "#" << eid.uid << ")";
hitbox.position = transform.pos;
} else {
transform.setPos(hitbox.position);
}
if (hitbox.grounded && !hitbox.prevGrounded) {
scripting::on_entity_grounded( scripting::on_entity_grounded(
*get(eid.uid), glm::length(prevVel - hitbox.velocity) *get(eid.uid), glm::length(hitbox.prevVelocity - hitbox.velocity)
); );
} }
if (!hitbox.grounded && grounded) { if (!hitbox.grounded && hitbox.prevGrounded) {
scripting::on_entity_fall(*get(eid.uid)); scripting::on_entity_fall(*get(eid.uid));
} }
} }
@ -450,7 +481,7 @@ void Entities::render(
bool Entities::hasBlockingInside(AABB aabb) { bool Entities::hasBlockingInside(AABB aabb) {
auto view = registry->view<EntityId, Rigidbody>(); auto view = registry->view<EntityId, Rigidbody>();
for (auto [entity, eid, body] : view.each()) { for (auto [entity, eid, body] : view.each()) {
if (eid.def.blocking && aabb.intersect(body.hitbox.getAABB(), -0.05f)) { if (eid.def.blocking && aabb.intersects(body.hitbox.getAABB(), -0.05f)) {
return true; return true;
} }
} }

View file

@ -88,12 +88,14 @@ public:
/// @param dir Ray direction normalized vector /// @param dir Ray direction normalized vector
/// @param maxDistance Max ray length /// @param maxDistance Max ray length
/// @param ignore Ignored entity ID /// @param ignore Ignored entity ID
/// @param solidOnly If true, only entities with solid hitboxes will be checked
/// @return An optional structure containing entity, normal and distance /// @return An optional structure containing entity, normal and distance
std::optional<RaycastResult> rayCast( std::optional<RaycastResult> rayCast(
glm::vec3 start, glm::vec3 start,
glm::vec3 dir, glm::vec3 dir,
float maxDistance, float maxDistance,
entityid_t ignore = -1 entityid_t ignore = -1,
bool solidOnly = false
); );
void loadEntities(dv::value map); void loadEntities(dv::value map);

View file

@ -9,5 +9,9 @@ void EntityDef::cloneTo(EntityDef& dst) {
dst.radialSensors = radialSensors; dst.radialSensors = radialSensors;
dst.skeletonName = skeletonName; dst.skeletonName = skeletonName;
dst.blocking = blocking; dst.blocking = blocking;
dst.solid = solid;
dst.mass = mass;
dst.elasticity = elasticity;
dst.stepHeight = stepHeight;
dst.save = save; dst.save = save;
} }

View file

@ -42,6 +42,15 @@ struct EntityDef {
/// @brief Does entity prevent blocks setup /// @brief Does entity prevent blocks setup
bool blocking = true; bool blocking = true;
/// @brief Is the entity solid (blocks other entities)
bool solid = false;
/// @brief Mass for physics calculations
float mass = 1.0f;
/// @brief Elasticity for physics calculations
float elasticity = 0.0f;
/// @brief Max obstacle height that does not require jumping /// @brief Max obstacle height that does not require jumping
float stepHeight = 0.5f; float stepHeight = 0.5f;

View file

@ -114,7 +114,7 @@ void Player::teleport(glm::vec3 position) {
this->position = position; this->position = position;
if (auto entity = level.entities->get(eid)) { if (auto entity = level.entities->get(eid)) {
entity->getRigidbody().hitbox.position = position; entity->getRigidbody().hitbox.setPos(position);
entity->getTransform().setPos(position); entity->getTransform().setPos(position);
entity->setInterpolatedPosition(position); entity->setInterpolatedPosition(position);
} }

View file

@ -22,6 +22,8 @@ dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const {
if (hitbox.crouching) { if (hitbox.crouching) {
bodymap["crouch"] = hitbox.crouching; bodymap["crouch"] = hitbox.crouching;
} }
bodymap["mass"] = mass;
bodymap["elasticity"] = elasticity;
} }
return bodymap; return bodymap;
} }
@ -33,6 +35,8 @@ void Rigidbody::deserialize(const dv::value& root) {
BodyTypeMeta.getItem(bodyTypeName, hitbox.type); BodyTypeMeta.getItem(bodyTypeName, hitbox.type);
root["crouch"].asBoolean(hitbox.crouching); root["crouch"].asBoolean(hitbox.crouching);
root["damping"].asNumber(hitbox.linearDamping); root["damping"].asNumber(hitbox.linearDamping);
root["mass"].asNumber(mass);
root["elasticity"].asNumber(elasticity);
} }
template <void (*callback)(const Entity&, size_t, entityid_t)> template <void (*callback)(const Entity&, size_t, entityid_t)>
@ -49,6 +53,8 @@ static sensorcallback create_sensor_callback(Entities& entities) {
void Rigidbody::initialize( void Rigidbody::initialize(
const EntityDef& def, entityid_t id, Entities& entities const EntityDef& def, entityid_t id, Entities& entities
) { ) {
mass = def.mass;
elasticity = def.elasticity;
sensors.resize(def.radialSensors.size() + def.boxSensors.size()); sensors.resize(def.radialSensors.size() + def.boxSensors.size());
for (auto& [i, box] : def.boxSensors) { for (auto& [i, box] : def.boxSensors) {
SensorParams params {}; SensorParams params {};

View file

@ -13,6 +13,8 @@ struct Rigidbody {
bool enabled = true; bool enabled = true;
Hitbox hitbox; Hitbox hitbox;
std::vector<Sensor> sensors; std::vector<Sensor> sensors;
float mass;
float elasticity;
dv::value serialize(bool saveVelocity, bool saveBodySettings) const; dv::value serialize(bool saveVelocity, bool saveBodySettings) const;
void deserialize(const dv::value& root); void deserialize(const dv::value& root);

View file

@ -1,10 +1,12 @@
#include "Hitbox.hpp" #include "Hitbox.hpp"
#include <stdexcept> Hitbox::Hitbox(
entityid_t entity, BodyType type, glm::vec3 position, glm::vec3 halfsize
Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize) )
: type(type), : entity(entity),
position(position), type(type),
halfsize(halfsize), position(position),
velocity(0.0f,0.0f,0.0f) halfsize(halfsize),
{} velocity(0.0f, 0.0f, 0.0f),
prevPosition(position) {
}

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#define GLM_ENABLE_EXPERIMENTAL
#include "maths/aabb.hpp" #include "maths/aabb.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/EnumMetadata.hpp" #include "util/EnumMetadata.hpp"
@ -8,6 +10,7 @@
#include <string> #include <string>
#include <functional> #include <functional>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
enum class SensorType { enum class SensorType {
AABB, AABB,
@ -48,6 +51,7 @@ VC_ENUM_METADATA(BodyType)
VC_ENUM_END VC_ENUM_END
struct Hitbox { struct Hitbox {
entityid_t entity;
BodyType type; BodyType type;
glm::vec3 position; glm::vec3 position;
glm::vec3 halfsize; glm::vec3 halfsize;
@ -60,14 +64,34 @@ struct Hitbox {
float gravityScale = 1.0f; float gravityScale = 1.0f;
bool crouching = false; bool crouching = false;
float stepHeight = 0.5f; float stepHeight = 0.5f;
float mass = 1.0f;
float elasticity = 0.0f;
std::string material;
std::string groundMaterial;
glm::vec3 groundVelocity {};
Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize); glm::vec3 prevPosition {};
glm::vec3 prevVelocity {};
bool prevGrounded = false;
static inline constexpr float TELEPORT_THRESOLD_SQR = 0.5f;
Hitbox(
entityid_t entity, BodyType type, glm::vec3 position, glm::vec3 halfsize
);
AABB getAABB() const { AABB getAABB() const {
return AABB(position-halfsize, position+halfsize); return AABB(position - halfsize, position + halfsize);
} }
glm::vec3 getHalfSize() const { glm::vec3 getHalfSize() const {
return halfsize * scale; return halfsize * scale;
} }
void setPos(const glm::vec3& vec) {
position = vec;
if (glm::distance2(position, prevPosition) >= TELEPORT_THRESOLD_SQR) {
prevPosition = vec;
}
}
}; };

View file

@ -5,197 +5,133 @@
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "voxels/GlobalChunks.hpp" #include "voxels/GlobalChunks.hpp"
#include "voxels/voxel.hpp" #include "voxels/voxel.hpp"
#include "objects/Entities.hpp"
#include "debug/Logger.hpp"
#include <iostream>
#include <algorithm> #include <algorithm>
#define GLM_ENABLE_EXPERIMENTAL #define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp> #include <glm/gtx/norm.hpp>
inline const float E = 0.03f; inline constexpr float E = 0.03f;
inline const float MAX_FIX = 0.1f; inline constexpr float MAX_FIX = 0.1f;
PhysicsSolver::PhysicsSolver(glm::vec3 gravity) : gravity(gravity) {} static debug::Logger logger("physics-solver");
void PhysicsSolver::step( PhysicsSolver::PhysicsSolver(const GlobalChunks& chunks, glm::vec3 gravity)
const GlobalChunks& chunks, : chunks(chunks), gravity(std::move(gravity)) {
Hitbox& hitbox, }
float delta,
uint substeps, static glm::vec3 calc_collsion_velocity_result(
entityid_t entity const Hitbox& a, const Hitbox& b
) { ) {
float dt = delta / static_cast<float>(substeps); const auto& vA = a.velocity;
float linearDamping = hitbox.linearDamping * hitbox.friction; const auto& vB = b.velocity;
float s = 2.0f/BLOCK_AABB_GRID; if (glm::isinf(a.mass)) {
return vA;
auto half = hitbox.getHalfSize();
glm::vec3& pos = hitbox.position;
glm::vec3& vel = hitbox.velocity;
float gravityScale = hitbox.gravityScale;
bool prevGrounded = hitbox.grounded;
hitbox.grounded = false;
for (uint i = 0; i < substeps; i++) {
float px = pos.x;
float py = pos.y;
float pz = pos.z;
vel += gravity * dt * gravityScale;
if (hitbox.type == BodyType::DYNAMIC) {
colisionCalc(chunks, hitbox, vel, pos, half,
(prevGrounded && gravityScale > 0.0f) ? hitbox.stepHeight : 0.0f);
}
pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f;
if (hitbox.grounded && pos.y < py) {
pos.y = py;
}
if (hitbox.crouching && hitbox.grounded){
float y = (pos.y-half.y-E);
hitbox.grounded = false;
for (int ix = 0; ix <= (half.x-E)*2/s; ix++) {
float x = (px-half.x+E) + ix * s;
for (int iz = 0; iz <= (half.z-E)*2/s; iz++){
float z = (pos.z-half.z+E) + iz * s;
if (chunks.isObstacleAt(x,y,z)){
hitbox.grounded = true;
break;
}
}
}
if (!hitbox.grounded) {
pos.z = pz;
vel.z = 0.0f;
}
hitbox.grounded = false;
for (int ix = 0; ix <= (half.x-E)*2/s; ix++) {
float x = (pos.x-half.x+E) + ix * s;
for (int iz = 0; iz <= (half.z-E)*2/s; iz++){
float z = (pz-half.z+E) + iz * s;
if (chunks.isObstacleAt(x,y,z)){
hitbox.grounded = true;
break;
}
}
}
if (!hitbox.grounded) {
pos.x = px;
vel.x = 0.0f;
}
hitbox.grounded = true;
}
} }
vel.x /= 1.0f + delta * linearDamping; if (glm::isinf(b.mass)) {
vel.z /= 1.0f + delta * linearDamping; return vB - a.elasticity * (vA - vB);
if (hitbox.verticalDamping > 0.0f) {
vel.y /= 1.0f + delta * linearDamping * hitbox.verticalDamping;
}
AABB aabb;
aabb.a = hitbox.position - hitbox.getHalfSize();
aabb.b = hitbox.position + hitbox.getHalfSize();
for (size_t i = 0; i < sensors.size(); i++) {
auto& sensor = *sensors[i];
if (sensor.entity == entity) {
continue;
}
bool triggered = false;
switch (sensor.type) {
case SensorType::AABB:
triggered = aabb.intersect(sensor.calculated.aabb);
break;
case SensorType::RADIUS:
triggered = glm::distance2(
hitbox.position, glm::vec3(sensor.calculated.radial))
< sensor.calculated.radial.w;
break;
}
if (triggered) {
if (sensor.prevEntered.find(entity) == sensor.prevEntered.end()) {
sensor.enterCallback(sensor.entity, sensor.index, entity);
}
sensor.nextEntered.insert(entity);
}
} }
const auto& mA = a.mass;
const auto& mB = b.mass;
return (mA * vA + mB * vB + a.elasticity * mB * (vB - vA)) / (mA + mB);
} }
static float calc_step_height( static float calc_step_height(
const GlobalChunks& chunks, const GlobalChunks& chunks,
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& half, const glm::vec3& half,
float stepHeight, float stepHeight
float s
) { ) {
if (stepHeight > 0.0f) { AABB aabb(-half, +half);
for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { aabb.scale(glm::vec3(1.0f - E * 2, 1.0f, 1.0f - E * 2));
float x = (pos.x-half.x+E) + ix * s; aabb = aabb + pos + glm::vec3(0.0f, stepHeight, 0.0f);
for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { if (stepHeight <= 0.0f) {
float z = (pos.z-half.z+E) + iz * s; return stepHeight;
if (chunks.isObstacleAt(x, pos.y+half.y+stepHeight, z)) { }
return 0.0f; for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) {
} float x = (pos.x - half.x) + ix;
for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) {
float z = (pos.z - half.z) + iz;
if (chunks.isObstacleAt(x, pos.y + half.y + stepHeight, z, aabb)) {
return 0.0f;
} }
} }
} }
return stepHeight; return stepHeight;
} }
template <int nx, int ny, int nz> // todo: reduce code duplication
static bool calc_collision_neg(
template <int nx, int ny, int nz, int sign>
static void calc_collision(
Hitbox& hitbox,
const GlobalChunks& chunks, const GlobalChunks& chunks,
glm::vec3& pos, const std::vector<Hitbox*>& solidHitboxes,
glm::vec3& vel,
const glm::vec3& half, const glm::vec3& half,
float stepHeight, float stepHeight
float s
) { ) {
if (vel[nx] >= 0.0f) { auto& pos = hitbox.position;
return false; auto& vel = hitbox.velocity;
}
glm::vec3 offset(0.0f, stepHeight, 0.0f); glm::vec3 offset(0.0f, stepHeight + E, 0.0f);
for (int iy = 0; iy <= std::max<int>(1, ((half-offset*0.5f)[ny]-E)*2/s); iy++) { for (auto box : solidHitboxes) {
glm::vec3 coord; if (glm::distance2(box->position, pos) < E) {
coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; continue;
for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++){ }
coord[nz] = (pos[nz]-half[nz]+E) + iz * s; auto boxhalf = box->getHalfSize();
coord[nx] = (pos[nx]-half[nx]-E); auto aabb = AABB(pos - half, pos + half);
if (const auto aabb = chunks.isObstacleAt(coord.x, coord.y, coord.z)) { glm::vec3 scale(1.0f);
vel[nx] = 0.0f; scale[nz] = 1.0f - E * 8.0f;
float newx = std::floor(coord[nx]) + half[nx] + aabb->max()[nx] + E; scale[ny] = 1.0f - E * 2.0f;
if (newx - pos[nx] <= E) { aabb.scale(scale);
pos[nx] = newx; aabb = aabb + offset;
} aabb.b.y -= stepHeight + E * 2;
return true;
if ((box->position[nx] - pos[nx]) * sign <= 0.0f || !box->getAABB().intersects(aabb)) {
continue;
}
float newnegx = box->position[nx] - boxhalf[nx] - half[nx];
float newposx = box->position[nx] + boxhalf[nx] + half[nx];
float newx;
if ((glm::abs(newnegx - pos[nx]) - glm::abs(newposx - pos[nx]))*sign < 0.0f) {
newx = sign > 0 ? newnegx : newposx;
} else {
continue;
}
if ((pos[nx] - newx) * sign > 0.0f && glm::abs(pos[nx] - newx) < MAX_FIX) {
auto velA = calc_collsion_velocity_result(hitbox, *box);
auto velB = calc_collsion_velocity_result(*box, hitbox);
if ((vel[nx] - box->velocity[nx]) * sign > 0.0f) {
vel[nx] = velA[nx];
box->velocity[nx] = velB[nx];
} }
pos[nx] = newx;
} }
} }
return false; if (vel[nx] * sign <= 0.0f && hitbox.groundVelocity[nx] * sign <= 0.0f) {
}
template <int nx, int ny, int nz>
static void calc_collision_pos(
const GlobalChunks& chunks,
glm::vec3& pos,
glm::vec3& vel,
const glm::vec3& half,
float stepHeight,
float s
) {
if (vel[nx] <= 0.0f) {
return; return;
} }
glm::vec3 offset(0.0f, stepHeight, 0.0f); for (int iy = 0; iy <= glm::ceil(((half - offset * 0.5f)[ny] - E) * 2); iy++) {
for (int iy = 0; iy <= std::max<int>(1, ((half-offset*0.5f)[ny]-E)*2/s); iy++) {
glm::vec3 coord; glm::vec3 coord;
coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; coord[ny] = ((pos + offset)[ny] - half[ny] + E) + iy;
for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++) { for (int iz = 0; iz <= glm::ceil((half[nz] - E) * 2); iz++) {
coord[nz] = (pos[nz]-half[nz]+E) + iz * s; coord[nz] = (pos[nz] - half[nz] + E) + iz;
coord[nx] = (pos[nx]+half[nx]+E); coord[nx] = pos[nx] + (half[nx] + E * 2) * sign;
if (const auto aabb = chunks.isObstacleAt(coord.x, coord.y, coord.z)) {
vel[nx] = 0.0f; auto aabb = AABB(pos - half, pos + half);
float newx = std::floor(coord[nx]) - half[nx] + aabb->min()[nx] - E; glm::vec3 scale(1.0f);
if (newx - pos[nx] <= E) { scale[nz] = 1.0f - E * 8.0f;
aabb.scale(scale);
aabb = aabb + offset;
aabb.b.y -= stepHeight + E * 2;
if (const auto obstacle = chunks.isObstacleAt(coord.x, coord.y, coord.z, aabb)) {
float newx = std::floor(coord[nx]) - half[nx] * sign +
(sign > 0 ? obstacle->min() : obstacle->max())[nx];
if ((pos[nx] - newx) * sign > 0.0f && glm::abs(pos[nx] - newx) < MAX_FIX) {
vel[nx] = -hitbox.elasticity * vel[nx];
pos[nx] = newx; pos[nx] = newx;
} }
return; return;
@ -204,93 +140,314 @@ static void calc_collision_pos(
} }
} }
void PhysicsSolver::colisionCalc( bool PhysicsSolver::calcCollisionNegY(
const GlobalChunks& chunks, Hitbox& hitbox, const glm::vec3& half, float dt
Hitbox& hitbox,
glm::vec3& vel,
glm::vec3& pos,
const glm::vec3 half,
float stepHeight
) { ) {
// step size (smaller - more accurate, but slower) // TODO: GET RID OF THIS auto& pos = hitbox.position;
float s = 2.0f/BLOCK_AABB_GRID; auto& vel = hitbox.velocity;
stepHeight = calc_step_height(chunks, pos, half, stepHeight, s); for (auto box : solidHitboxes) {
if (glm::distance2(box->position, pos) < E) {
continue;
}
auto aabb = AABB(pos - half, pos + half);
glm::vec3 scale(1.0f);
scale.x = 1.0f - E * 4.0f;
scale.z = 1.0f - E * 4.0f;
aabb.scale(scale);
const AABB* aabb; auto boxhalf = box->getHalfSize();
if (box->position.y < pos.y && box->getAABB().intersects(aabb)) {
float newy = box->position.y + boxhalf.y + half.y;
if (pos.y < newy && glm::abs(pos.y - newy) < boxhalf.y) {
pos.y = newy;
}
calc_collision_neg<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); auto velA = calc_collsion_velocity_result(hitbox, *box);
calc_collision_pos<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); auto velB = calc_collsion_velocity_result(*box, hitbox);
calc_collision_neg<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); hitbox.groundVelocity = box->position - box->prevPosition;
calc_collision_pos<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); if (vel.y < hitbox.groundVelocity.y / dt) {
vel.y = velA.y;
if (calc_collision_neg<1, 0, 2>(chunks, pos, vel, half, 0.0f, s)) { box->velocity.y = velB.y;
hitbox.grounded = true; if (hitbox.groundMaterial.empty() && !box->material.empty()) {
} hitbox.groundMaterial = box->material;
if (stepHeight > 0.0 && vel.y <= 0.0f){
for (int ix = 0; ix <= (half.x-E)*2/s; ix++) {
float x = (pos.x-half.x+E) + ix * s;
for (int iz = 0; iz <= (half.z-E)*2/s; iz++) {
float z = (pos.z-half.z+E) + iz * s;
float y = (pos.y-half.y+E);
if ((aabb = chunks.isObstacleAt(x,y,z))){
vel.y = 0.0f;
float newy = std::floor(y) + aabb->max().y + half.y;
if (std::abs(newy-pos.y) <= MAX_FIX+stepHeight) {
pos.y = newy;
}
break;
} }
return true;
} }
} }
} }
if (vel.y > 0.0f){ if (vel.y >= 0.0f) {
for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { return false;
float x = (pos.x-half.x+E) + ix * s; }
for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { hitbox.groundVelocity = {};
float z = (pos.z-half.z+E) + iz * s; for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) {
float y = (pos.y+half.y+E); glm::vec3 coord;
if ((aabb = chunks.isObstacleAt(x,y,z))){ coord.x = (pos.x - half.x + E) + ix;
vel.y = 0.0f; for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) {
float newy = std::floor(y) - half.y + aabb->min().y - E; coord.z = (pos.z - half.z + E) + iz;
if (std::abs(newy-pos.y) <= MAX_FIX) { coord.y = pos.y - half.y - E * 2;
pos.y = newy;
} auto aabb = AABB(pos - half, pos + half);
break; glm::vec3 scale(1.0f);
scale.x = 1.0f - E * 4.0f;
scale.z = 1.0f - E * 4.0f;
aabb.scale(scale);
if (const auto obstacle =
chunks.isObstacleAt(coord.x, coord.y, coord.z, aabb)) {
float newy = std::floor(coord.y) + half.y + obstacle->max().y;
if (newy >= pos.y) {
vel.y = -hitbox.elasticity * vel.y;
pos.y = newy;
} }
return true;
} }
} }
} }
}
bool PhysicsSolver::isBlockInside(int x, int y, int z, Hitbox* hitbox) {
const glm::vec3& pos = hitbox->position;
auto half = hitbox->getHalfSize();
return x >= floor(pos.x-half.x) && x <= floor(pos.x+half.x) &&
z >= floor(pos.z-half.z) && z <= floor(pos.z+half.z) &&
y >= floor(pos.y-half.y) && y <= floor(pos.y+half.y);
}
bool PhysicsSolver::isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox) {
const float e = 0.001f; // inaccuracy
const glm::vec3& pos = hitbox->position;
auto half = hitbox->getHalfSize();
const auto& boxes = def->rotatable
? def->rt.hitboxes[state.rotation]
: def->hitboxes;
for (const auto& block_hitbox : boxes) {
glm::vec3 min = block_hitbox.min();
glm::vec3 max = block_hitbox.max();
if (min.x < pos.x+half.x-x-e && max.x > pos.x-half.x-x+e &&
min.z < pos.z+half.z-z-e && max.z > pos.z-half.z-z+e &&
min.y < pos.y+half.y-y-e && max.y > pos.y-half.y-y+e)
return true;
}
return false; return false;
} }
void PhysicsSolver::removeSensor(Sensor* sensor) { void PhysicsSolver::calcCollisions(
sensors.erase(std::remove(sensors.begin(), sensors.end(), sensor), sensors.end()); Hitbox& hitbox,
glm::vec3& vel,
glm::vec3& pos,
const glm::vec3& half,
float stepHeight,
float dt
) {
stepHeight = calc_step_height(chunks, pos, half, stepHeight);
auto prevPos = pos;
calc_collision<0, 1, 2, -1>(hitbox, chunks, solidHitboxes, half, stepHeight);
calc_collision<0, 1, 2, 1>(hitbox, chunks, solidHitboxes, half, stepHeight);
float xpos = pos.x;
pos.x = prevPos.x;
calc_collision<2, 1, 0, -1>(hitbox, chunks, solidHitboxes, half, stepHeight);
calc_collision<2, 1, 0, 1>(hitbox, chunks, solidHitboxes, half, stepHeight);
pos.x = xpos;
if (calcCollisionNegY(hitbox, half, dt)) {
hitbox.grounded = true;
}
if (vel.y > 0.0f){
AABB boxAABB = AABB(-half, +half);
boxAABB.scale(glm::vec3(1.0f - E * 4, 1.0f + E * 2, 1.0f - E * 4));
boxAABB = boxAABB.translated(pos);
for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) {
float x = (pos.x - half.x + E) + ix;
for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) {
float z = (pos.z - half.z + E) + iz;
float y = (pos.y + half.y + E) + 0.5f;
if (auto aabb = chunks.isObstacleAt(x, y, z, boxAABB)) {
float newy = std::floor(y) - half.y + aabb->min().y - E;
if (pos.y >= newy) {
vel.y = -hitbox.elasticity * vel.y;
pos.y = newy;
}
break;
}
}
}
for (auto box : solidHitboxes) {
if (glm::distance2(box->position, pos) < E) {
continue;
}
auto boxhalf = box->getHalfSize();
if (box->position.y > pos.y && box->getAABB().intersects(boxAABB)) {
float newy = box->position.y - boxhalf.y - half.y;
if (pos.y > newy && glm::abs(pos.y - newy) < 0.5f) {
pos.y = newy;
}
auto velA = calc_collsion_velocity_result(hitbox, *box);
auto velB = calc_collsion_velocity_result(*box, hitbox);
if (vel.y > hitbox.groundVelocity.y / dt) {
vel.y = velA.y;
box->velocity.y = velB.y;
break;
}
}
}
}
// step on
if (stepHeight > 0.0 && vel.y <= 0.0f) {
AABB boxAABB = AABB(pos - half, pos + half);
boxAABB.scale(glm::vec3(1.0f - E * 2, 1.0f - E * 2, 1.0f - E * 2));
for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) {
float x = (pos.x - half.x) + ix;
for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) {
float z = (pos.z - half.z) + iz;
float y = (pos.y - half.y + E) + E;
if (auto aabb = chunks.isObstacleAt(x, y, z, boxAABB)) {
if (vel.y < 0.0f) {
vel.y = 0.0f;
}
float newy = std::floor(y) + aabb->max().y + half.y;
if (std::abs(newy - pos.y) <= stepHeight) {
pos.y = newy;
}
break;
}
}
}
for (auto box : solidHitboxes) {
if (glm::distance2(box->position, pos) < E) {
continue;
}
auto boxhalf = box->getHalfSize();
if (box->getAABB().intersects(boxAABB)) {
vel.y = 0.0f;
float newy = box->position.y + boxhalf.y + half.y;
if (std::abs(newy - pos.y) <= stepHeight + E * 4) {
pos.y = newy;
}
}
}
}
}
void PhysicsSolver::calcSubstep(
Hitbox& hitbox, glm::vec3& vel, glm::vec3& pos, float dt
) {
auto initpos = pos;
auto half = hitbox.getHalfSize();
float gravityScale = hitbox.gravityScale;
if (hitbox.type == BodyType::DYNAMIC) {
calcCollisions(
hitbox,
vel,
pos,
half,
(hitbox.prevGrounded && gravityScale > 0.0f) ? hitbox.stepHeight
: 0.0f,
dt
);
}
vel += gravity * dt * gravityScale;
pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f;
// crouching
if (!hitbox.crouching || !hitbox.grounded) {
return;
}
float y = (pos.y - half.y - E);
for (int axis = 0; axis <= 2; axis += 2) {
hitbox.grounded = false;
auto checkPos = pos;
checkPos.x = axis != 0 ? initpos.x : pos.x;
checkPos.z = axis == 0 ? initpos.z : pos.z;
AABB boxAABB(checkPos - half, checkPos + half);
boxAABB.scale(glm::vec3(1.0f - E * 4, 1.5f, 1.0f - E * 4));
for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) {
float x = (pos.x - half.x + E) + ix;
for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++){
float z = (pos.z - half.z + E) + iz;
if (chunks.isObstacleAt(x, y, z, boxAABB)){
hitbox.grounded = true;
break;
}
}
}
for (auto box : solidHitboxes) {
if (glm::distance2(box->position, pos) < E) {
continue;
}
if (box->position.y < pos.y && box->getAABB().intersects(boxAABB)) {
hitbox.grounded = true;
break;
}
}
if (!hitbox.grounded) {
pos[axis] = initpos[axis];
vel[axis] = 0.0f;
}
}
hitbox.grounded = true;
}
void PhysicsSolver::step(
const GlobalChunks& chunks, float delta, uint substeps
) {
for (auto hitbox : hitboxes) {
hitbox->groundMaterial.clear();
hitbox->prevGrounded = hitbox->grounded;
hitbox->grounded = false;
hitbox->prevVelocity = hitbox->velocity;
}
float dt = delta / static_cast<float>(substeps);
for (uint i = 0; i < substeps; i++) {
for (auto hitbox : hitboxes) {
glm::vec3& pos = hitbox->position;
hitbox->prevPosition = hitbox->position;
calcSubstep(*hitbox, hitbox->velocity, pos, dt);
}
}
for (auto hitbox : hitboxes) {
float linearDamping = hitbox->linearDamping * hitbox->friction;
glm::vec3& vel = hitbox->velocity;
auto diff = hitbox->groundVelocity / dt - vel;
vel.x += diff.x * delta * linearDamping;
vel.z += diff.z * delta * linearDamping;
if (hitbox->verticalDamping > 0.0f) {
vel.y /= 1.0f + delta * linearDamping * hitbox->verticalDamping;
}
if (!hitbox->grounded) {
hitbox->groundVelocity = {};
}
updateSensors(*hitbox);
hitbox->friction = glm::abs(hitbox->gravityScale <= 1e-7f)
? 8.0f
: (!hitbox->prevGrounded ? 2.0f : 10.0f);
}
}
void PhysicsSolver::updateSensors(Hitbox& hitbox) {
auto aabb = hitbox.getAABB();
for (size_t i = 0; i < sensors.size(); i++) {
auto& sensor = *sensors[i];
if (sensor.entity == hitbox.entity) {
continue;
}
bool triggered = false;
switch (sensor.type) {
case SensorType::AABB:
triggered = aabb.intersects(sensor.calculated.aabb);
break;
case SensorType::RADIUS:
triggered = glm::distance2(
hitbox.position, glm::vec3(sensor.calculated.radial))
< sensor.calculated.radial.w;
break;
}
if (!triggered) {
continue;
}
if (sensor.prevEntered.find(hitbox.entity) == sensor.prevEntered.end()) {
sensor.enterCallback(sensor.entity, sensor.index, hitbox.entity);
}
sensor.nextEntered.insert(hitbox.entity);
}
}
void PhysicsSolver::removeSensor(Sensor* sensor) {
sensors.erase(
std::remove(sensors.begin(), sensors.end(), sensor), sensors.end()
);
} }

View file

@ -9,35 +9,48 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
class Block; class Block;
class Entities;
class GlobalChunks; class GlobalChunks;
struct Sensor; struct Sensor;
class PhysicsSolver { class PhysicsSolver {
glm::vec3 gravity;
std::vector<Sensor*> sensors;
public: public:
PhysicsSolver(glm::vec3 gravity); PhysicsSolver(const GlobalChunks& chunks, glm::vec3 gravity);
void step(
const GlobalChunks& chunks,
Hitbox& hitbox,
float delta,
uint substeps,
entityid_t entity
);
void colisionCalc(
const GlobalChunks& chunks,
Hitbox& hitbox,
glm::vec3& vel,
glm::vec3& pos,
const glm::vec3 half,
float stepHeight
);
bool isBlockInside(int x, int y, int z, Hitbox* hitbox);
bool isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox);
void setSensors(std::vector<Sensor*> sensors) { void step(const GlobalChunks& chunks, float delta, uint substeps);
this->sensors = std::move(sensors);
auto& getSensorsWriteable() {
return sensors;
}
auto& getSolidHitboxesWriteable() {
return solidHitboxes;
}
auto& getHitboxesWriteable() {
return hitboxes;
} }
void removeSensor(Sensor* sensor); void removeSensor(Sensor* sensor);
private:
const GlobalChunks& chunks;
glm::vec3 gravity;
std::vector<Sensor*> sensors;
std::vector<Hitbox*> solidHitboxes;
std::vector<Hitbox*> hitboxes;
void calcCollisions(
Hitbox& hitbox,
glm::vec3& vel,
glm::vec3& pos,
const glm::vec3& half,
float stepHeight,
float dt
);
void calcSubstep(Hitbox& hitbox, glm::vec3& vel, glm::vec3& pos, float dt);
bool calcCollisionNegY(Hitbox& hitbox, const glm::vec3& half, float dt);
void updateSensors(Hitbox& hitbox);
}; };

View file

@ -1,11 +1,5 @@
#include "Chunks.hpp" #include "Chunks.hpp"
#include <math.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "data/StructLayout.hpp" #include "data/StructLayout.hpp"
#include "coders/byte_utils.hpp" #include "coders/byte_utils.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
@ -20,6 +14,11 @@
#include "VoxelsVolume.hpp" #include "VoxelsVolume.hpp"
#include "blocks_agent.hpp" #include "blocks_agent.hpp"
#include <math.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
Chunks::Chunks( Chunks::Chunks(
int32_t w, int32_t w,
int32_t d, int32_t d,
@ -67,20 +66,21 @@ const AABB* Chunks::isObstacleAt(float x, float y, float z) const {
} }
} }
const auto& def = indices.blocks.require(v->id); const auto& def = indices.blocks.require(v->id);
if (def.obstacle) { if (!def.obstacle) {
glm::ivec3 offset {}; return nullptr;
if (v->state.segment) { }
glm::ivec3 point(ix, iy, iz); glm::ivec3 offset {};
offset = seekOrigin(point, def, v->state) - point; if (v->state.segment) {
} glm::ivec3 point(ix, iy, iz);
const auto& boxes = offset = seekOrigin(point, def, v->state) - point;
def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; }
for (const auto& hitbox : boxes) { const auto& boxes =
if (hitbox.contains( def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes;
{x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} for (const auto& hitbox : boxes) {
)) { if (hitbox.contains(
return &hitbox; {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z}
} )) {
return &hitbox;
} }
} }
return nullptr; return nullptr;

View file

@ -1,17 +1,16 @@
#pragma once #pragma once
#include <stdlib.h>
#include <glm/glm.hpp>
#include <memory>
#include <set>
#include <vector>
#include "typedefs.hpp" #include "typedefs.hpp"
#include "voxel.hpp" #include "voxel.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "util/AreaMap2D.hpp" #include "util/AreaMap2D.hpp"
#include <stdlib.h>
#include <glm/glm.hpp>
#include <memory>
#include <set>
#include <vector>
class VoxelRenderer; class VoxelRenderer;
struct AABB; struct AABB;

View file

@ -214,6 +214,6 @@ void GlobalChunks::putChunk(std::shared_ptr<Chunk> chunk) {
chunksMap[keyfrom(chunk->x, chunk->z)] = std::move(chunk); chunksMap[keyfrom(chunk->x, chunk->z)] = std::move(chunk);
} }
const AABB* GlobalChunks::isObstacleAt(float x, float y, float z) const { std::optional<AABB> GlobalChunks::isObstacleAt(float x, float y, float z, const AABB& aabb) const {
return blocks_agent::is_obstacle_at(*this, x, y, z); return blocks_agent::is_obstacle_at(*this, x, y, z, aabb);
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <optional>
#include <unordered_map> #include <unordered_map>
#define GLM_ENABLE_EXPERIMENTAL #define GLM_ENABLE_EXPERIMENTAL
@ -57,7 +58,7 @@ public:
void putChunk(std::shared_ptr<Chunk> chunk); void putChunk(std::shared_ptr<Chunk> chunk);
const AABB* isObstacleAt(float x, float y, float z) const; std::optional<AABB> isObstacleAt(float x, float y, float z, const AABB& aabb) const;
inline Chunk* getChunk(int cx, int cz) const { inline Chunk* getChunk(int cx, int cz) const {
const auto& found = chunksMap.find(keyfrom(cx, cz)); const auto& found = chunksMap.find(keyfrom(cx, cz));

View file

@ -13,7 +13,6 @@
#include "voxel.hpp" #include "voxel.hpp"
#include "VoxelsVolume.hpp" #include "VoxelsVolume.hpp"
#include <algorithm>
#include <algorithm> #include <algorithm>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <set> #include <set>
@ -468,8 +467,8 @@ void get_voxels(
); );
template <class Storage> template <class Storage>
inline const AABB* is_obstacle_at( inline std::optional<AABB> is_obstacle_at(
const Storage& chunks, float x, float y, float z const Storage& chunks, float x, float y, float z, const AABB& aabb
) { ) {
int ix = std::floor(x); int ix = std::floor(x);
int iy = std::floor(y); int iy = std::floor(y);
@ -477,30 +476,34 @@ inline const AABB* is_obstacle_at(
voxel* v = get(chunks, ix, iy, iz); voxel* v = get(chunks, ix, iy, iz);
if (v == nullptr) { if (v == nullptr) {
if (iy >= CHUNK_H) { if (iy >= CHUNK_H) {
return nullptr; return std::nullopt;
} else { } else {
static const AABB empty; return AABB();
return &empty;
} }
} }
const auto& def = chunks.getContentIndices().blocks.require(v->id); const auto& def = chunks.getContentIndices().blocks.require(v->id);
if (def.obstacle) { if (!def.obstacle) {
glm::ivec3 offset {}; return std::nullopt;
if (v->state.segment) { }
glm::ivec3 point(ix, iy, iz); glm::ivec3 offset {};
offset = seek_origin(chunks, point, def, v->state) - point; if (v->state.segment) {
} glm::ivec3 point(ix, iy, iz);
const auto& boxes = offset = seek_origin(chunks, point, def, v->state) - point;
def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; }
for (const auto& hitbox : boxes) { const auto& boxes =
if (hitbox.contains( def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes;
{x - ix - offset.x, y - iy - offset.y, z - iz - offset.z}
)) { for (const auto& hitbox : boxes) {
return &hitbox; if (hitbox.intersects(aabb - glm::ivec3(ix, iy, iz))) {
} return hitbox + offset;
} }
} }
return nullptr; return std::nullopt;
}
template <class Storage>
inline std::optional<AABB> is_obstacle_at(const Storage& chunks, float x, float y, float z) {
return is_obstacle_at(chunks, x, y, z, AABB({x, y, z}, {x + 1, y + 1, z + 1}));
} }
/// @brief Check block grounding /// @brief Check block grounding

View file

@ -26,7 +26,7 @@ Level::Level(
: world(std::move(worldPtr)), : world(std::move(worldPtr)),
content(content), content(content),
chunks(std::make_unique<GlobalChunks>(*this)), chunks(std::make_unique<GlobalChunks>(*this)),
physics(std::make_unique<PhysicsSolver>(glm::vec3(0, -22.6f, 0))), physics(std::make_unique<PhysicsSolver>(*chunks, glm::vec3(0, -22.6f, 0))),
events(std::make_unique<LevelEvents>()), events(std::make_unique<LevelEvents>()),
entities(std::make_unique<Entities>(*this)), entities(std::make_unique<Entities>(*this)),
players(std::make_unique<Players>(*this)), players(std::make_unique<Players>(*this)),

View file

@ -173,7 +173,7 @@ void WorldGenerator::placeStructure(
} }
auto& otherPrototype = *found->second; auto& otherPrototype = *found->second;
auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz); auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz);
if (chunkAABB.intersect(aabb)) { if (chunkAABB.intersects(aabb)) {
otherPrototype.placements.emplace_back( otherPrototype.placements.emplace_back(
priority, priority,
StructurePlacement { StructurePlacement {