diff --git a/doc/en/entity-properties.md b/doc/en/entity-properties.md index a6530191f..bb12dcd86 100644 --- a/doc/en/entity-properties.md +++ b/doc/en/entity-properties.md @@ -95,6 +95,23 @@ Example: 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. +### *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 ### *skeleton-name* diff --git a/doc/en/scripting/builtins/libgfx-weather.md b/doc/en/scripting/builtins/libgfx-weather.md index 62d8ec33b..669ebc5ea 100644 --- a/doc/en/scripting/builtins/libgfx-weather.md +++ b/doc/en/scripting/builtins/libgfx-weather.md @@ -4,14 +4,17 @@ A library for managing audio/visual weather effects. Weather settings: -| Property | Description | Default | -| ------------ | ------------------------------ | ------- | -| fall | Precipitation (see table 2) | {} | -| clouds | Cloudiness [0.0, 1.0] | 0.0 | -| fog_opacity | Maximum fog density [0.0, 1.0] | 0.0 | -| fog_dencity | Fog density | 1.0 | -| fog_curve | Fog curve | 1.0 | -| thunder_rate | Thunder rate [0.0, 1.0] | 0.0 | +| Property | Description | Default | +| ------------- | -------------------------------- | ----------------- | +| fall | Precipitation (see table 2) | {} | +| clouds | Cloudiness [0.0, 1.0] | 0.0 | +| fog_opacity | Maximum fog density [0.0, 1.0] | 0.0 | +| fog_dencity | Fog density | 1.0 | +| fog_curve | Fog curve | 1.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: diff --git a/doc/en/scripting/builtins/libpack.md b/doc/en/scripting/builtins/libpack.md index 2daab1269..651a030d2 100644 --- a/doc/en/scripting/builtins/libpack.md +++ b/doc/en/scripting/builtins/libpack.md @@ -78,14 +78,14 @@ Returns the id of all base packages (non-removeable) ```lua pack.get_info(packid: str) -> { - id: str, - title: str, - creator: str, - description: str, - version: str, - path: str, - icon: str, -- not available in headless mode - dependencies: optional strings array + id: str, + title: str, + creator: str, + description: str, + version: str, + path: str, + icon: str, -- not available in headless mode + dependencies: optional strings array } ``` @@ -95,6 +95,7 @@ Returns information about the pack (not necessarily installed). - `!` - required - `?` - optional - `~` - weak + for example `!teal` To obtain information about multiple packs, use table of ids to avoid re-scanning:one diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md index 7da294c22..e0c6b69c6 100644 --- a/doc/en/scripting/ecs.md +++ b/doc/en/scripting/ecs.md @@ -114,6 +114,21 @@ body:set_crouching(enabled: bool) body:get_body_type() -> str -- Sets the physical body type 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 diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 97b36d1f6..0bd1b5353 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -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. +- `font` - font name - `text-align` - inner text alignment (*left/center/right*). Type: string. ## *checkbox* @@ -108,6 +109,7 @@ Inner text is a button text. ## *label* +- `font` - font name - `text-align` - text alignment (*left/center/right*). Type: string. - `valign` - vertical text alignment: top/center/bottom. - `supplier` - text supplier (called every frame). @@ -131,6 +133,7 @@ Inner text is a button text. Inner text - initially entered text +- `font` - font name - `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). - `supplier` - text supplier (called every frame) @@ -190,6 +193,14 @@ Example of list description: - `selected` - initially selected value. Default: "". - `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* @@ -213,9 +224,6 @@ Element must be in direct sub-element of *inventory*. ## *slots-grid* - `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 - `padding` - grid padding (not slots interval). Type: number. (*deprecated*) - `sharefunc` - Lua event called on LMB + Shift. 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 RMB click. Inventory id and slot index passed as arguments. - `taking` - the ability to take an item from 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 diff --git a/doc/ru/entity-properties.md b/doc/ru/entity-properties.md index 3489e3aae..50b3b1499 100644 --- a/doc/ru/entity-properties.md +++ b/doc/ru/entity-properties.md @@ -95,6 +95,23 @@ 0. Прямоугольная область шириной, высотой и длиной в 0.4 м. с центром в 0.0. 1. Радиальная область с радиусом 1.6 м. +### Осязаемость - *solid* + +Делает хитбокс сущности осязаемым препятствием. Вместе с включением данного свойства у `dynamic` тел следует указать массу (mass). + +### Масса - *mass* + +Определяет массу сущности. За стандарт приняты килограммы. +В случае `kinematic` тела вместо указанной используется бесконечная масса. + +### Эластичность - *elasticity* + +Определяет упругость сущности. + +### Высота шага - *step-height* + +Определяет максимальную высоту препятствия, на которое сущность может шагнуть без прыжка. + ## Вид ### Имя скелета - *skeleton-name* diff --git a/doc/ru/scripting/builtins/libgfx-weather.md b/doc/ru/scripting/builtins/libgfx-weather.md index add94550b..b51bd58e1 100644 --- a/doc/ru/scripting/builtins/libgfx-weather.md +++ b/doc/ru/scripting/builtins/libgfx-weather.md @@ -4,14 +4,17 @@ Настройки погоды: -| Свойство | Описание | По-умолчанию | -| ------------ | ---------------------------------------- | ------------ | -| fall | Осадки (см. таблица 2) | {} | -| clouds | Облачность [0.0, 1.0] | 0.0 | -| fog_opacity | Максимальная плотность тумана [0.0, 1.0] | 0.0 | -| fog_dencity | Плотность тумана | 1.0 | -| fog_curve | Кривая тумана | 1.0 | -| thunder_rate | Частота грома [0.0, 1.0] | 0.0 | +| Свойство | Описание | По-умолчанию | +| ------------- | ------------------------------------------ | ----------------- | +| fall | Осадки (см. таблица 2) | {} | +| clouds | Облачность [0.0, 1.0] | 0.0 | +| fog_opacity | Максимальная плотность тумана [0.0, 1.0] | 0.0 | +| fog_dencity | Плотность тумана | 1.0 | +| fog_curve | Кривая тумана | 1.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} | Осадки: @@ -50,4 +53,4 @@ gfx.weather.get_fall_intensity() -> number -- Проверяет, происходит ли в данный момент переключение погоды gfx.weather.is_transition() -> boolean -``` \ No newline at end of file +``` diff --git a/doc/ru/scripting/builtins/libmat4.md b/doc/ru/scripting/builtins/libmat4.md index 1531798a4..1b44df1ee 100644 --- a/doc/ru/scripting/builtins/libmat4.md +++ b/doc/ru/scripting/builtins/libmat4.md @@ -114,15 +114,29 @@ mat4.decompose(m: matrix) } или nil ``` -## Отслеживание точки *mat4.look_at(...)* +## Отслеживание точки - *mat4.look_at(...)* ```lua --- cоздает матрицу вида с точки 'eye' на точку 'center', где вектор 'up' определяет верх. +-- cоздаёт матрицу вида с точки 'eye' на точку 'center', где вектор 'up' определяет верх. mat4.look_at(eye: vec3, center: vec3, up: vec3) -- записывает матрицу вида в dst 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(...)* ```lua diff --git a/doc/ru/scripting/builtins/libpack.md b/doc/ru/scripting/builtins/libpack.md index 0c4aac0c1..925cad7f8 100644 --- a/doc/ru/scripting/builtins/libpack.md +++ b/doc/ru/scripting/builtins/libpack.md @@ -82,6 +82,7 @@ pack.get_info(packid: string) -> { - `!` - required - `?` - optional - `~` - weak + например `!teal` Для получения информации о нескольких паках используйте таблицу id, чтобы не @@ -101,4 +102,4 @@ pack.assemble(packids: table) -> table pack.request_writeable(packid: string, callback: function(string)) ``` -Запрашивает у пользователя право на модификацию пака. При подтвержении новая точка входа будет передана в callback. \ No newline at end of file +Запрашивает у пользователя право на модификацию пака. При подтвержении новая точка входа будет передана в callback. diff --git a/doc/ru/scripting/builtins/libquat.md b/doc/ru/scripting/builtins/libquat.md index 5a6495658..e991de252 100644 --- a/doc/ru/scripting/builtins/libquat.md +++ b/doc/ru/scripting/builtins/libquat.md @@ -2,22 +2,34 @@ Библиотека для работы с кватернионами. -## Кватернион из матрицы - *mat4.from_quat(...)* +## Кватернион из матрицы - *quat.from_mat4(...)* ```lua --- создает кватернион на основе матрицы вращения +-- создаёт кватернион на основе матрицы вращения quat.from_mat4(m: matrix) -> quat -- записывает кватернион по матрице вращения в dst 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(...)* Интерполяция всегда выполняется по короткому пути, а вращение выполняется с постоянной скоростью. ```lua --- создает кватернион как интерполяцию между a и b, +-- создаёт кватернион как интерполяцию между a и b, -- где t - фактор интерполяции 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(...)* ```lua --- возвращает строку представляющую содержимое кватерниона +-- возвращает строку, представляющую содержимое кватерниона quat.tostring(q: quat) -> string ``` diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md index 6c1b041d7..50735913a 100644 --- a/doc/ru/scripting/ecs.md +++ b/doc/ru/scripting/ecs.md @@ -114,6 +114,21 @@ body:set_crouching(enabled: bool) body:get_body_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 diff --git a/doc/ru/scripting/io_stream.md b/doc/ru/scripting/io_stream.md index 283e0c48b..cc3403eda 100644 --- a/doc/ru/scripting/io_stream.md +++ b/doc/ru/scripting/io_stream.md @@ -137,6 +137,18 @@ io_stream:write_line(string) io_stream:read_fully( [опционально] useTable: boolean ) -> Bytearray | table | table | string + +--[[ +Устанавливает позицию в потоке +Режимы: + b - Задаёт позицию относительно начало файла + c - Задаёт позицию относительно текущей позиции + e - Задаёт позицию относительно конца файла +--]] +io_stream:seek( + mode: string + offset: number +) ``` ## Методы Buffered-режима diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index aeb669b3f..2ca6da1ce 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -100,6 +100,7 @@ Внутренний текст - текст кнопки. +- `font` - имя шрифта - `text-align` - выравнивание текста ("left", "center" или "right"). Тип: строка. ## Флажок - *checkbox* @@ -110,6 +111,7 @@ ## Метка - *label* +- `font` - имя шрифта - `text-align` - выравнивание текста ("left", "center" или "right"). Тип: строка. - `valign` - вертикальное выравнивание текста: top/center/bottom - `supplier` - поставщик текста (вызывается каждый кадр) @@ -132,6 +134,7 @@ Внутренний текст - изначально введенный текст +- `font` - имя шрифта - `placeholder` - текст подстановки (используется если текстовое поле пусто) - `hint` - текст, отображаемый, если текстовое поле пусто (не отправляется в consumer, sub-consumer и validator). - `supplier` - поставщик текста (вызывается каждый кадр) @@ -191,6 +194,14 @@ - `selected` - изначально выбранное значение. По-умолчанию: "". - `onselect` - функция, в которую передаётся выбранное пользователем значение +## Привязка ввода - *bindbox* + +Элемент для отображения и изменения привязки клавиш или кнопок мыши. + +- `font` - имя шрифта. Тип: строка. +- `binding` - имя привязки. Тип: строка. +- `padding` - отступ между текстом и границами элемента. Тип: число. По-умолчанию: 6 + # Элементы инвентаря ## Инвентарь - *inventory* @@ -214,10 +225,8 @@ ## Сетка слотов - *slots-grid* Элемент должен находиться внутри `inventory` элемента, без посредников. + - `start-index` - индекс первого слота -- `rows` - число рядов (не указывается, если указано cols). -- `cols` - число столбцов (не указывается, если указано rows). -- `count` - общее число слотов (не указывается, если указаны rows и cols). - `interval` - интервал между слотами. Тип: число. - `padding` - отступ вокруг решетки слотов. Тип: число. (*атрибут будет удален*) - `sharefunc` - lua событие вызываемое при использовании ЛКМ + Shift. Передается id инвентаря и индекс слота @@ -225,3 +234,8 @@ - `onrightclick` - lua событие вызываемое при использовании ПКМ. Передается id инвентаря и индекс слота - `taking` - возможность взять предмет из слота. - `placing` - возможность положить предмет в слот. + +Конфигурация слотов (достаточно указать два атрибута): +- `rows` - число рядов. Тип: число. +- `cols` - число столбцов. Тип: число. +- `count` - общее число слотов. Тип: число. diff --git a/res/content/base/entities/falling_block.json b/res/content/base/entities/falling_block.json index cf5146fd9..b466c2a11 100644 --- a/res/content/base/entities/falling_block.json +++ b/res/content/base/entities/falling_block.json @@ -3,5 +3,6 @@ "base:falling_block" ], "skeleton-name": "base:block", - "hitbox": [0.98, 0.98, 0.98] + "hitbox": [0.98, 0.98, 0.98], + "solid": true } diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua index 483e86aee..b6bbe4656 100644 --- a/res/content/base/scripts/components/falling_block.lua +++ b/res/content/base/scripts/components/falling_block.lua @@ -29,6 +29,7 @@ do -- setup visuals 0, 0, 0, 1 } rig:set_matrix(0, matrix) + body:set_material(block.material(id)) end function on_grounded() diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index 7cedd1887..d85c11963 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -32,6 +32,8 @@ local Rigidbody = {__index={ 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, 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) diff --git a/res/modules/internal/stream_providers/file.lua b/res/modules/internal/stream_providers/file.lua index 80bc8c5bb..ec81a42c0 100644 --- a/res/modules/internal/stream_providers/file.lua +++ b/res/modules/internal/stream_providers/file.lua @@ -3,6 +3,7 @@ local io_stream = require "core:io_stream" local lib = { read = file.__read_descriptor, write = file.__write_descriptor, + seek = file.__seek_descriptor, flush = file.__flush_descriptor, is_alive = file.__has_descriptor, close = file.__close_descriptor diff --git a/res/modules/internal/stream_providers/named_pipe_unix.lua b/res/modules/internal/stream_providers/named_pipe_unix.lua index 1843dabaa..8b924f47a 100644 --- a/res/modules/internal/stream_providers/named_pipe_unix.lua +++ b/res/modules/internal/stream_providers/named_pipe_unix.lua @@ -58,6 +58,10 @@ function lib.write(fd, bytearray) end end +function lib.seek(fd, mode, offset) + error("cannot seek the named pipe") +end + function lib.flush(fd) -- no flush on unix end diff --git a/res/modules/internal/stream_providers/named_pipe_windows.lua b/res/modules/internal/stream_providers/named_pipe_windows.lua index 83691aa5a..3d4b31af1 100644 --- a/res/modules/internal/stream_providers/named_pipe_windows.lua +++ b/res/modules/internal/stream_providers/named_pipe_windows.lua @@ -62,7 +62,7 @@ function lib.read(handle, len) if not has_data then return out - elseif hasData == -1 then + elseif has_data == -1 then error("failed to read from named pipe: "..tostring(C.GetLastError())) end @@ -84,8 +84,8 @@ end function lib.write(handle, bytearray) local len = #bytearray - local buffer = FFI.new("uint8_t[?]", len) + for i = 1, len do buffer[i-1] = bytearray[i] end @@ -97,6 +97,10 @@ function lib.write(handle, bytearray) end end +function lib.seek(handle, mode, offset) + error("cannot seek the named pipe") +end + function lib.flush(handle) C.FlushFileBuffers(handle) end diff --git a/res/modules/io_stream.lua b/res/modules/io_stream.lua index 15ba85d12..b608a22a9 100644 --- a/res/modules/io_stream.lua +++ b/res/modules/io_stream.lua @@ -369,6 +369,10 @@ function io_stream:write(arg, ...) end end +function io_stream:seek(mode, offset) + self.ioLib.seek(self.descriptor, mode, offset) +end + function io_stream:is_alive() return self.ioLib.is_alive(self.descriptor) end diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index 66fdda825..840bce743 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -60,6 +60,7 @@ end local prepared = false body:set_enabled(false) +mob:set_flight(true) function on_physics_update(delta) if not prepared then diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 0f408131d..738d49c09 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -344,9 +344,10 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) { static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context& ctx) { std::string name = root.attr("name", "").getText(); + glm::vec3 movement {}; glm::mat4 tsf(1.0f); if (root.has("move")) { - tsf = glm::translate(tsf, root.attr("move").asVec3()); + movement = root.attr("move").asVec3(); } if (root.has("rotate")) { auto text = root.attr("rotate").getText(); @@ -371,7 +372,7 @@ static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context& } builder.pop(); } 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++; std::vector> bones; diff --git a/src/content/loading/EntityLoader.cpp b/src/content/loading/EntityLoader.cpp index 12e5313a7..91967ea89 100644 --- a/src/content/loading/EntityLoader.cpp +++ b/src/content/loading/EntityLoader.cpp @@ -88,5 +88,8 @@ template<> void ContentUnitLoader::loadUnit( root.at("skeleton-name").get(def.skeletonName); 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); } diff --git a/src/engine/AssetsManagement.cpp b/src/engine/AssetsManagement.cpp index 1ca1935c9..13df11220 100644 --- a/src/engine/AssetsManagement.cpp +++ b/src/engine/AssetsManagement.cpp @@ -18,6 +18,8 @@ AssetsManagement::AssetsManagement(Engine& engine) AssetsManagement::~AssetsManagement() { finishBackgroundLoader(); + assets.reset(); + assetsVault.clearKeepedObjects(); } const Assets* AssetsManagement::getStorage() const { diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 9a8553da6..d70f09ba2 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -10,17 +10,19 @@ #include "logic/LevelController.hpp" #include "logic/PlayerController.hpp" #include "objects/Player.hpp" +#include "physics/Hitbox.hpp" #include "voxels/Block.hpp" +#include "voxels/Chunks.hpp" #include "world/Level.hpp" #include "engine/Engine.hpp" LevelFrontend::LevelFrontend( Engine& engine, - Player* currentPlayer, - LevelController* controller, + PlayerController& playerController, + LevelController& controller, const EngineSettings& settings ) - : level(*controller->getLevel()), + : level(*controller.getLevel()), controller(controller), assets(*engine.getAssets()), contentCache(std::make_unique( @@ -36,45 +38,79 @@ LevelFrontend::LevelFrontend( "block-previews" ); - auto& rassets = assets; - controller->getBlocksController()->listenBlockInteraction( - [currentPlayer, controller, &rassets](auto player, const auto& pos, const auto& def, BlockInteraction type) { - const auto& level = *controller->getLevel(); + auto& currentPlayer = playerController.getPlayer(); + auto& assets = this->assets; + auto& level = this->level; + + playerController.setFootstepCallback( + [&level, ¤tPlayer, &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(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); if (material == nullptr) { return; } - if (type == BlockInteraction::step) { - auto sound = rassets.get(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 { + if (type != BlockInteraction::step) { audio::Sound* sound = nullptr; switch (type) { case BlockInteraction::placing: - sound = rassets.get(material->placeSound); + sound = assets.get(material->placeSound); break; case BlockInteraction::destruction: - sound = rassets.get(material->breakSound); + sound = assets.get(material->breakSound); break; default: break; @@ -108,6 +144,6 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const { return *contentCache; } -LevelController* LevelFrontend::getController() const { +LevelController& LevelFrontend::getController() const { return controller; } diff --git a/src/frontend/LevelFrontend.hpp b/src/frontend/LevelFrontend.hpp index cc4f996b2..451f88675 100644 --- a/src/frontend/LevelFrontend.hpp +++ b/src/frontend/LevelFrontend.hpp @@ -2,24 +2,24 @@ #include -class Level; class Assets; -class Player; -class Engine; class ContentGfxCache; +class Engine; +class Level; class LevelController; +class PlayerController; struct EngineSettings; class LevelFrontend { Level& level; - LevelController* controller; + LevelController& controller; Assets& assets; std::unique_ptr contentCache; public: LevelFrontend( Engine& engine, - Player* currentPlayer, - LevelController* controller, + PlayerController& currentPlayer, + LevelController& controller, const EngineSettings& settings ); ~LevelFrontend(); @@ -27,5 +27,5 @@ public: Level& getLevel(); const ContentGfxCache& getContentGfxCache() const; ContentGfxCache& getContentGfxCache(); - LevelController* getController() const; + LevelController& getController() const; }; diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index fee9ff192..4366d8c6e 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -272,7 +272,7 @@ void Hud::updateWorldGenDebug() { const auto& chunks = *player.chunks; uint padding = engine.getSettings().chunks.padding.get(); auto generator = - frontend.getController()->getChunksController()->getGenerator(); + frontend.getController().getChunksController()->getGenerator(); auto debugInfo = generator->createDebugInfo(); int width = debugImgWorldGen->getWidth(); @@ -294,7 +294,7 @@ void Hud::updateWorldGenDebug() { bool isInLoadingZone = frontend.getController() - ->getChunksController() + .getChunksController() ->isInLoadingZone(player, padding, ax + ox, az + oz); data[(flippedZ * width + x) * 4 + 1] = diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 13618d33a..737742afd 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -68,7 +68,7 @@ LevelScreen::LevelScreen( ); frontend = std::make_unique( - engine, player, controller.get(), settings + engine, *playerController, *controller, settings ); renderer = std::make_unique( engine, *frontend, *player diff --git a/src/logic/EngineController.hpp b/src/logic/EngineController.hpp index a46973cf3..baba7ba9e 100644 --- a/src/logic/EngineController.hpp +++ b/src/logic/EngineController.hpp @@ -3,6 +3,7 @@ #include #include #include +#include class Engine; class World; diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index e707d1637..663ff1fa1 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -44,7 +44,9 @@ namespace { const float CROUCH_SHIFT_Y = -0.2f; } -CameraControl::CameraControl(Player& player, const CameraSettings& settings) +CameraControl::CameraControl( + Player& player, const CameraSettings& settings +) : player(player), camera(player.fpCamera), settings(settings), @@ -195,18 +197,30 @@ void CameraControl::update( 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(); if (player.currentCamera == spCamera) { spCamera->position = - chunks.rayCastToObstacle(camera->position, camera->front, 3.0f) - - 0.4f * camera->front; + castRay(player, *camera, camera->front) - 0.4f * camera->front; spCamera->dir = -camera->dir; spCamera->front = -camera->front; spCamera->right = -camera->right; } else if (player.currentCamera == tpCamera) { - tpCamera->position = - chunks.rayCastToObstacle(camera->position, -camera->front, 3.0f) + - 0.4f * camera->front; + tpCamera->position = castRay(player, *camera, camera->front * -1.0f) + + 0.4f * camera->front; tpCamera->dir = camera->dir; tpCamera->front = camera->front; tpCamera->right = camera->right; @@ -229,26 +243,8 @@ PlayerController::PlayerController( } void PlayerController::onFootstep(const Hitbox& hitbox) { - auto pos = hitbox.position; - auto half = hitbox.halfsize; - - 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; - } - } + if (footstepCallback) { + footstepCallback(hitbox); } } @@ -557,3 +553,7 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta) Player& PlayerController::getPlayer() { return player; } + +void PlayerController::setFootstepCallback(FootstepCallback&& callback) { + footstepCallback = std::move(callback); +} diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index e730ff37c..5ec8e4b50 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -18,6 +18,8 @@ struct Hitbox; struct CameraSettings; struct EngineSettings; +using FootstepCallback = std::function; + class CameraControl { Player& player; std::shared_ptr camera; @@ -57,6 +59,7 @@ class PlayerController { CameraControl camControl; BlocksController& blocksController; float interactionTimer = 0.0f; + FootstepCallback footstepCallback; void updateKeyboard(const Input& inputEvents); void resetKeyboard(); @@ -89,5 +92,8 @@ public: void postUpdate( float delta, int windowHeight, const Input* inputEvents, bool pause ); + Player& getPlayer(); + + void setFootstepCallback(FootstepCallback&& callback); }; diff --git a/src/logic/scripting/lua/libs/lib__rigidbody.cpp b/src/logic/scripting/lua/libs/lib__rigidbody.cpp index f32e9d986..52234f91e 100644 --- a/src/logic/scripting/lua/libs/lib__rigidbody.cpp +++ b/src/logic/scripting/lua/libs/lib__rigidbody.cpp @@ -150,6 +150,48 @@ static int l_set_linear_damping(lua::State* L) { 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[] = { {"is_enabled", lua::wrap}, {"set_enabled", lua::wrap}, @@ -169,5 +211,11 @@ const luaL_Reg rigidbodylib[] = { {"set_crouching", lua::wrap}, {"get_body_type", lua::wrap}, {"set_body_type", lua::wrap}, + {"set_material", lua::wrap}, + {"get_material", lua::wrap}, + {"get_mass", lua::wrap}, + {"set_mass", lua::wrap}, + {"get_elasticity", lua::wrap}, + {"set_elasticity", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/lib__transform.cpp b/src/logic/scripting/lua/libs/lib__transform.cpp index 7cf7bed62..16662daaa 100644 --- a/src/logic/scripting/lua/libs/lib__transform.cpp +++ b/src/logic/scripting/lua/libs/lib__transform.cpp @@ -31,7 +31,7 @@ static int l_set_pos(lua::State* L) { auto vec = lua::tovec3(L, 2); check_valid(vec); entity->getTransform().setPos(vec); - entity->getRigidbody().hitbox.position = vec; + entity->getRigidbody().hitbox.setPos(vec); } return 0; } diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 2fa2b1281..f2a615dc0 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -20,8 +20,22 @@ using namespace scripting; -static inline const Block* get_block_def(lua::State* L) { - auto indices = content->getIndices(); +static const Content& require_content() { + 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); 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 z = lua::tointeger(L, 3); 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) { 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) { @@ -76,7 +90,7 @@ static int l_is_segment(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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); } @@ -84,10 +98,11 @@ static int l_seek_origin(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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); 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 state = lua::tointeger(L, 5); bool noupdate = lua::toboolean(L, 6); - if (static_cast(id) >= indices->blocks.count()) { + auto& level = require_level(); + auto& indices = require_content().getIndices()->blocks; + if (static_cast(id) >= indices.count()) { 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; } @@ -123,7 +140,7 @@ static int l_get(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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; return lua::pushinteger(L, id); } @@ -138,8 +155,9 @@ template static int get_axis(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); + auto& level = require_level(); 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(L, def, y); } auto z = lua::tointeger(L, 3); @@ -147,11 +165,11 @@ static int get_axis(lua::State* L) { glm::ivec3 defAxis {}; 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) { 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) { return lua::pushivec_stack(L, defAxis); } else { @@ -175,7 +193,7 @@ static int l_get_rotation(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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; return lua::pushinteger(L, rotation); } @@ -185,7 +203,7 @@ static int l_set_rotation(lua::State* L) { auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); 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; } @@ -193,7 +211,7 @@ static int l_get_states(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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); return lua::pushinteger(L, states); } @@ -208,7 +226,7 @@ static int l_set_states(lua::State* L) { } int cx = floordiv(x); int cz = floordiv(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) { 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 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) { 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) { 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) { 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) { - auto& chunks = *level->chunks; + auto& level = require_level(); + auto& chunks = *level.chunks; + auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); @@ -255,7 +276,7 @@ static int l_get_variant(lua::State* L) { if (vox == nullptr) { 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) { 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) { - auto& chunks = *level->chunks; + auto& level = require_level(); + auto& chunks = *level.chunks; + auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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 lz = z - cz * CHUNK_D; 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) { auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state); 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) { - auto& chunks = *level->chunks; + auto& level = require_level(); + auto& chunks = *level.chunks; + auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); 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 lz = z - cz * CHUNK_D; 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) { return 0; @@ -358,11 +383,12 @@ static int l_set_variant(lua::State* L) { } static int l_is_replaceable_at(lua::State* L) { + auto& level = require_level(); auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); 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; } -static lua::Integer get_variant_index(lua::State* L, const Block* const block, int argumentIndex) { - const auto variantIndex = lua::gettop(L) >= argumentIndex ? lua::tointeger(L, argumentIndex) : 0; - const size_t variantsSize = block->variants->variants.size(); +static lua::Integer get_variant_index( + lua::State* L, const Block& block, int argumentIndex +) { + 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) { 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; } static int l_get_textures(lua::State* L) { if (auto def = get_block_def(L)) { - const auto& textureFaces = (def->variants ? def->variants->variants[get_variant_index(L, def, 2)].textureFaces : - def->defaults.textureFaces); + const auto& textureFaces = + (def->variants + ? def->variants->variants[get_variant_index(L, *def, 2)] + .textureFaces + : def->defaults.textureFaces); lua::createtable(L, 6, 0); for (size_t i = 0; i < 6; i++) { lua::pushstring(L, textureFaces[i]); @@ -397,11 +431,13 @@ static int l_get_textures(lua::State* L) { return 0; } - static int l_model_name(lua::State* L) { if (auto def = get_block_def(L)) { - const auto& modelName = (def->variants ? def->variants->variants[get_variant_index(L, def, 2)].model.name : - def->defaults.model.name); + const auto& modelName = + (def->variants + ? def->variants->variants[get_variant_index(L, *def, 2)] + .model.name + : def->defaults.model.name); if (modelName.empty()) { 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) { if (auto def = get_block_def(L)) { - const BlockModelType modelType = (def->variants ? def->variants->variants[get_variant_index(L, def, 2)].model.type : - def->defaults.model.type); + const BlockModelType modelType = + (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 0; @@ -422,7 +461,8 @@ static int l_get_model(lua::State* L) { static int l_get_hitbox(lua::State* L) { if (auto def = get_block_def(L)) { size_t rotation = lua::tointeger(L, 2); - const size_t hitboxIndex = static_cast(lua::gettop(L) >= 3 ? lua::tointeger(L, 3) : 0); + const size_t hitboxIndex = + static_cast(lua::isnumber(L, 3) ? lua::tointeger(L, 3) : 0); if (def->rotatable) { rotation %= def->rotations.MAX_COUNT; } else { @@ -456,25 +496,28 @@ static int l_get_picking_item(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 y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); auto id = lua::tointeger(L, 4); auto state = lua::tointeger(L, 5); auto playerid = lua::gettop(L) >= 6 ? lua::tointeger(L, 6) : -1; - if (static_cast(id) >= indices->blocks.count()) { + if (static_cast(id) >= indices.blocks.count()) { return 0; } - if (!blocks_agent::get(*level->chunks, x, y, z)) { + if (!blocks_agent::get(*level.chunks, x, y, z)) { return 0; } - const auto def = level->content.getIndices()->blocks.get(id); + const auto def = indices.blocks.get(id); if (def == nullptr) { throw std::runtime_error( "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( 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) { + auto& level = require_level(); + auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); - auto playerid = lua::gettop(L) >= 4 ? lua::tointeger(L, 4) : -1; - auto vox = blocks_agent::get(*level->chunks, x, y, z); + auto playerid = lua::isnumber(L, 4) ? lua::tointeger(L, 4) : -1; + + auto vox = blocks_agent::get(*level.chunks, x, y, z); if (vox == nullptr) { return 0; } - auto& def = level->content.getIndices()->blocks.require(vox->id); - auto player = level->players->get(playerid); + auto& def = level.content.getIndices()->blocks.require(vox->id); + auto player = level.players->get(playerid); controller->getBlocksController()->breakBlock(player, def, x, y, z); return 0; } static int l_raycast(lua::State* L) { + auto& level = require_level(); + auto start = lua::tovec<3>(L, 1); auto dir = lua::tovec<3>(L, 2); auto maxDistance = lua::tonumber(L, 3); @@ -526,7 +574,7 @@ static int l_raycast(lua::State* L) { glm::ivec3 normal; glm::ivec3 iend; if (auto voxel = blocks_agent::raycast( - *level->chunks, + *level.chunks, start, dir, maxDistance, @@ -620,6 +668,8 @@ static int get_field( } static int l_get_field(lua::State* L) { + auto& level = require_level(); + auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); @@ -628,9 +678,10 @@ static int l_get_field(lua::State* L) { if (lua::gettop(L) >= 5) { index = lua::tointeger(L, 5); } + auto cx = floordiv(x, CHUNK_W); 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) { return 0; } @@ -689,6 +740,8 @@ static int set_field( } static int l_set_field(lua::State* L) { + auto& level = require_level(); + auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); @@ -698,11 +751,12 @@ static int l_set_field(lua::State* L) { if (lua::gettop(L) >= 6) { index = lua::tointeger(L, 6); } + auto cx = floordiv(x, CHUNK_W); auto cz = floordiv(z, CHUNK_D); auto lx = x - cx * 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) { return 0; } diff --git a/src/logic/scripting/lua/libs/libcamera.cpp b/src/logic/scripting/lua/libs/libcamera.cpp index 6ec6ef96c..c45d2740a 100644 --- a/src/logic/scripting/lua/libs/libcamera.cpp +++ b/src/logic/scripting/lua/libs/libcamera.cpp @@ -7,28 +7,42 @@ 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 static int l_camera_getter(lua::State* L) { size_t index = static_cast(lua::tointeger(L, 1)); - return getterfunc(L, *level->cameras.at(index)); + return getterfunc(L, *require_level().cameras.at(index)); } template static int l_camera_setter(lua::State* L) { size_t index = static_cast(lua::tointeger(L, 1)); - setterfunc(L, *level->cameras.at(index), 2); + setterfunc(L, *require_level().cameras.at(index), 2); return 0; } static int l_index(lua::State* L) { 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)); } static int l_name(lua::State* L) { size_t index = static_cast(lua::tointeger(L, 1)); - auto& indices = content->getIndices(ResourceType::CAMERA); + auto& indices = require_content().getIndices(ResourceType::CAMERA); 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) { size_t index = static_cast(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 matrix = glm::inverse( glm::lookAt(glm::vec3(), center - camera.position, glm::vec3(0, 1, 0)) diff --git a/src/logic/scripting/lua/libs/libfile.cpp b/src/logic/scripting/lua/libs/libfile.cpp index 687008152..26248379c 100644 --- a/src/logic/scripting/lua/libs/libfile.cpp +++ b/src/logic/scripting/lua/libs/libfile.cpp @@ -347,6 +347,41 @@ static int l_write_descriptor(lua::State* L) { 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) { int descriptor = lua::tointeger(L, 1); @@ -406,9 +441,9 @@ const luaL_Reg filelib[] = { {"__has_descriptor", lua::wrap}, {"__read_descriptor", lua::wrap}, {"__write_descriptor", lua::wrap}, + {"__seek_descriptor", lua::wrap}, {"__flush_descriptor", lua::wrap}, {"__close_descriptor", lua::wrap}, {"__close_all_descriptors", lua::wrap}, {nullptr, nullptr} }; - diff --git a/src/logic/scripting/lua/libs/libinventory.cpp b/src/logic/scripting/lua/libs/libinventory.cpp index 7f43ae88b..3ec9d2ca3 100644 --- a/src/logic/scripting/lua/libs/libinventory.cpp +++ b/src/logic/scripting/lua/libs/libinventory.cpp @@ -8,14 +8,28 @@ using namespace scripting; 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) { - if (id >= indices->items.count()) { + if (id >= require_content().getIndices()->items.count()) { throw std::runtime_error("invalid item id"); } } Inventory& get_inventory(int64_t id) { - auto inv = level->inventories->get(id); + auto inv = require_level().inventories->get(id); if (inv == nullptr) { throw std::runtime_error("inventory not found: " + std::to_string(id)); } @@ -23,7 +37,7 @@ namespace { } Inventory& get_inventory(int64_t id, int arg) { - auto inv = level->inventories->get(id); + auto inv = require_level().inventories->get(id); if (inv == nullptr) { throw std::runtime_error( "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) { auto invsize = lua::tointeger(L, 1); - auto inv = level->inventories->create(invsize); + auto inv = require_level().inventories->create(invsize); if (inv == nullptr) { return lua::pushinteger(L, 0); } @@ -146,13 +160,13 @@ static int l_create(lua::State* L) { static int l_remove(lua::State* L) { auto invid = lua::tointeger(L, 1); - level->inventories->remove(invid); + require_level().inventories->remove(invid); return 0; } static int l_clone(lua::State* L) { auto id = lua::tointeger(L, 1); - auto clone = level->inventories->clone(id); + auto clone = require_level().inventories->clone(id); if (clone == nullptr) { return lua::pushinteger(L, 0); } diff --git a/src/logic/scripting/lua/libs/libitem.cpp b/src/logic/scripting/lua/libs/libitem.cpp index 3a0f77ec7..4c7ba3983 100644 --- a/src/logic/scripting/lua/libs/libitem.cpp +++ b/src/logic/scripting/lua/libs/libitem.cpp @@ -8,6 +8,9 @@ using namespace scripting; 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 id = lua::tointeger(L, idx); return indices->items.get(id); diff --git a/src/logic/scripting/lua/libs/libmat4.cpp b/src/logic/scripting/lua/libs/libmat4.cpp index 081d99b5e..59f15e661 100644 --- a/src/logic/scripting/lua/libs/libmat4.cpp +++ b/src/logic/scripting/lua/libs/libmat4.cpp @@ -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) { uint argc = lua::check_argc(L, 1, 2); auto quat = lua::toquat(L, 1); @@ -280,6 +294,7 @@ const luaL_Reg mat4lib[] = { {"determinant", lua::wrap}, {"decompose", lua::wrap}, {"look_at", lua::wrap}, + {"perspective", lua::wrap}, {"from_quat", lua::wrap}, {"tostring", lua::wrap}, {nullptr, nullptr} diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index 9f634bddb..a978e5c22 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -14,6 +14,13 @@ 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) { if (!lua::isnumber(L, idx)) { 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) { @@ -304,15 +311,18 @@ static int l_create(lua::State* L) { if (lua::gettop(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)); return lua::pushinteger(L, player->getId()); } static int l_delete(lua::State* L) { 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; } @@ -324,19 +334,21 @@ static int l_is_suspended(lua::State* L) { } static int l_set_suspended(lua::State* L) { + auto& level = require_level(); if (lua::toboolean(L, 2)) { - level->players->suspend(lua::tointeger(L, 1)); + level.players->suspend(lua::tointeger(L, 1)); } else { - level->players->resume(lua::tointeger(L, 1)); + level.players->resume(lua::tointeger(L, 1)); } return 0; } static int l_get_all_in_radius(lua::State* L) { + auto& level = require_level(); auto center = lua::tovec3(L, 1); auto radius = static_cast(lua::tonumber(L, 2)); - auto players = level->players->getAllInRadius(center, radius); + auto players = level.players->getAllInRadius(center, radius); lua::createtable(L, players.size(), 0); for (size_t i = 0; i < players.size(); i++) { 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) { - auto players = level->players->getAll(); + auto players = require_level().players->getAll(); lua::createtable(L, players.size(), 0); for (size_t i = 0; i < players.size(); i++) { 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) { + auto& level = require_level(); 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()); return 1; } diff --git a/src/logic/scripting/lua/libs/libquat.cpp b/src/logic/scripting/lua/libs/libquat.cpp index 2316360a1..53757cb36 100644 --- a/src/logic/scripting/lua/libs/libquat.cpp +++ b/src/logic/scripting/lua/libs/libquat.cpp @@ -44,8 +44,17 @@ static int l_tostring(lua::State* L) { 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[] = { {"from_mat4", lua::wrap}, + {"from_euler", lua::wrap}, {"slerp", lua::wrap}, {"tostring", lua::wrap}, {nullptr, nullptr} diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index 30a619788..c2d6be43c 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -25,11 +25,15 @@ using namespace scripting; namespace fs = std::filesystem; -static WorldInfo& require_world_info() { +static Level& require_level() { 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) { diff --git a/src/maths/aabb.hpp b/src/maths/aabb.hpp index 471441951..ca19a81fe 100644 --- a/src/maths/aabb.hpp +++ b/src/maths/aabb.hpp @@ -38,15 +38,23 @@ struct AABB { 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 - inline void scale(const glm::vec3 mul) { + inline void scale(const glm::vec3& mul) { glm::vec3 center = (a + b) * 0.5f; a = (a - center) * mul + center; b = (b - center) * mul + center; } /// @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 end = max(); 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)); } - inline bool intersect(const AABB& aabb) { + inline bool intersects(const AABB& aabb) const { return ( - a.x <= aabb.b.x && b.x >= aabb.a.x && a.y <= aabb.b.y && - b.y >= aabb.a.y && a.z <= aabb.b.z && b.z >= aabb.a.z + a.x <= aabb.b.x && b.x >= aabb.a.x && + 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 ( a.x <= aabb.b.x + margin && b.x >= aabb.a.x - margin && a.y <= aabb.b.y + margin && b.y >= aabb.a.y - margin && diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index cbb507c89..82e293522 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -1,28 +1,30 @@ #define VC_ENABLE_REFLECTION #include "Entities.hpp" -#include -#include -#include - #include "assets/Assets.hpp" #include "content/Content.hpp" #include "data/dv_util.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" +#include "Entity.hpp" +#include "EntityDef.hpp" +#include "graphics/commons/Model.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/LineBatch.hpp" -#include "graphics/commons/Model.hpp" #include "graphics/render/ModelBatch.hpp" #include "logic/scripting/scripting.hpp" #include "maths/FrustumCulling.hpp" #include "maths/rays.hpp" -#include "EntityDef.hpp" -#include "Entity.hpp" -#include "rigging.hpp" +#include "maths/util.hpp" #include "physics/PhysicsSolver.hpp" +#include "rigging.hpp" #include "world/Level.hpp" +#include +#include +#include +#include + static debug::Logger logger("entities"); Entities::Entities(Level& level) @@ -79,7 +81,7 @@ entityid_t Entities::spawn( entities[id] = entity; uids[entity] = id; - registry->emplace(entity, static_cast(id), def); + registry->emplace(entity, id, def); const auto& tsf = registry->emplace( entity, position, @@ -91,7 +93,7 @@ entityid_t Entities::spawn( auto& body = registry->emplace( entity, true, - Hitbox {def.bodyType, position, def.hitbox * 0.5f}, + Hitbox {id, def.bodyType, position, def.hitbox * 0.5f}, std::vector {} ); body.initialize(def, id, *this); @@ -161,7 +163,11 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { } std::optional 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); auto view = registry->view(); @@ -170,7 +176,7 @@ std::optional Entities::rayCast( glm::ivec3 foundNormal; 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; } auto& hitbox = body.hitbox; @@ -278,13 +284,18 @@ void Entities::updateSensors( } void Entities::preparePhysics(float delta) { + auto& physics = *level.physics; + auto& hitboxes = physics.getHitboxesWriteable(); + auto& solidHitboxes = physics.getSolidHitboxesWriteable(); + if (sensorsTickClock.update(delta)) { auto part = sensorsTickClock.getPart(); auto parts = sensorsTickClock.getParts(); + auto& sensors = physics.getSensorsWriteable(); + sensors.clear(); + auto view = registry->view(); - auto physics = level.physics.get(); - std::vector sensors; for (auto [entity, eid, transform, rigidbody] : view.each()) { if (!rigidbody.enabled) { continue; @@ -294,7 +305,25 @@ void Entities::preparePhysics(float delta) { } updateSensors(rigidbody, transform, sensors); } - physics->setSensors(std::move(sensors)); + } + + hitboxes.clear(); + solidHitboxes.clear(); + + auto view = registry->view(); + 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::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(); auto physics = level.physics.get(); + + int substeps = std::max(std::min(delta * 1000, 200), 8); + physics->step(*level.chunks, delta, substeps); + 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; } auto& hitbox = rigidbody.hitbox; - auto prevVel = hitbox.velocity; - bool grounded = hitbox.grounded; - - float vel = glm::length(prevVel); - int substeps = static_cast(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; - transform.setPos(hitbox.position); - if (hitbox.grounded && !grounded) { + if (util::is_nan_or_inf(hitbox.position)) { + 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( - *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)); } } @@ -450,7 +481,7 @@ void Entities::render( bool Entities::hasBlockingInside(AABB aabb) { auto view = registry->view(); 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; } } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 804ce17f4..33786118a 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -88,12 +88,14 @@ public: /// @param dir Ray direction normalized vector /// @param maxDistance Max ray length /// @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 std::optional rayCast( glm::vec3 start, glm::vec3 dir, float maxDistance, - entityid_t ignore = -1 + entityid_t ignore = -1, + bool solidOnly = false ); void loadEntities(dv::value map); diff --git a/src/objects/EntityDef.cpp b/src/objects/EntityDef.cpp index 4cc858710..ae1f87c68 100644 --- a/src/objects/EntityDef.cpp +++ b/src/objects/EntityDef.cpp @@ -9,5 +9,9 @@ void EntityDef::cloneTo(EntityDef& dst) { dst.radialSensors = radialSensors; dst.skeletonName = skeletonName; dst.blocking = blocking; + dst.solid = solid; + dst.mass = mass; + dst.elasticity = elasticity; + dst.stepHeight = stepHeight; dst.save = save; } diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index 3752f3063..195d07a17 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -42,6 +42,15 @@ struct EntityDef { /// @brief Does entity prevent blocks setup 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 float stepHeight = 0.5f; diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index a63341d49..b77a07fdd 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -114,7 +114,7 @@ void Player::teleport(glm::vec3 position) { this->position = position; if (auto entity = level.entities->get(eid)) { - entity->getRigidbody().hitbox.position = position; + entity->getRigidbody().hitbox.setPos(position); entity->getTransform().setPos(position); entity->setInterpolatedPosition(position); } diff --git a/src/objects/Rigidbody.cpp b/src/objects/Rigidbody.cpp index 9e2edba4a..2c885f8e0 100644 --- a/src/objects/Rigidbody.cpp +++ b/src/objects/Rigidbody.cpp @@ -22,6 +22,8 @@ dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const { if (hitbox.crouching) { bodymap["crouch"] = hitbox.crouching; } + bodymap["mass"] = mass; + bodymap["elasticity"] = elasticity; } return bodymap; } @@ -33,6 +35,8 @@ void Rigidbody::deserialize(const dv::value& root) { BodyTypeMeta.getItem(bodyTypeName, hitbox.type); root["crouch"].asBoolean(hitbox.crouching); root["damping"].asNumber(hitbox.linearDamping); + root["mass"].asNumber(mass); + root["elasticity"].asNumber(elasticity); } template @@ -49,6 +53,8 @@ static sensorcallback create_sensor_callback(Entities& entities) { void Rigidbody::initialize( const EntityDef& def, entityid_t id, Entities& entities ) { + mass = def.mass; + elasticity = def.elasticity; sensors.resize(def.radialSensors.size() + def.boxSensors.size()); for (auto& [i, box] : def.boxSensors) { SensorParams params {}; diff --git a/src/objects/Rigidbody.hpp b/src/objects/Rigidbody.hpp index 91790d8a2..bc2a85f90 100644 --- a/src/objects/Rigidbody.hpp +++ b/src/objects/Rigidbody.hpp @@ -13,6 +13,8 @@ struct Rigidbody { bool enabled = true; Hitbox hitbox; std::vector sensors; + float mass; + float elasticity; dv::value serialize(bool saveVelocity, bool saveBodySettings) const; void deserialize(const dv::value& root); diff --git a/src/physics/Hitbox.cpp b/src/physics/Hitbox.cpp index adf078418..f49e0c2b8 100644 --- a/src/physics/Hitbox.cpp +++ b/src/physics/Hitbox.cpp @@ -1,10 +1,12 @@ #include "Hitbox.hpp" -#include - -Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize) - : type(type), - position(position), - halfsize(halfsize), - velocity(0.0f,0.0f,0.0f) -{} +Hitbox::Hitbox( + entityid_t entity, BodyType type, glm::vec3 position, glm::vec3 halfsize +) + : entity(entity), + type(type), + position(position), + halfsize(halfsize), + velocity(0.0f, 0.0f, 0.0f), + prevPosition(position) { +} diff --git a/src/physics/Hitbox.hpp b/src/physics/Hitbox.hpp index 909a48451..ecf41faa3 100644 --- a/src/physics/Hitbox.hpp +++ b/src/physics/Hitbox.hpp @@ -1,5 +1,7 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL + #include "maths/aabb.hpp" #include "typedefs.hpp" #include "util/EnumMetadata.hpp" @@ -8,6 +10,7 @@ #include #include #include +#include enum class SensorType { AABB, @@ -48,6 +51,7 @@ VC_ENUM_METADATA(BodyType) VC_ENUM_END struct Hitbox { + entityid_t entity; BodyType type; glm::vec3 position; glm::vec3 halfsize; @@ -60,14 +64,34 @@ struct Hitbox { float gravityScale = 1.0f; bool crouching = false; float stepHeight = 0.5f; + float mass = 1.0f; + float elasticity = 0.0f; + std::string material; + std::string groundMaterial; + glm::vec3 groundVelocity {}; + + glm::vec3 prevPosition {}; + glm::vec3 prevVelocity {}; + bool prevGrounded = false; - Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize); + static inline constexpr float TELEPORT_THRESOLD_SQR = 0.5f; + + Hitbox( + entityid_t entity, BodyType type, glm::vec3 position, glm::vec3 halfsize + ); AABB getAABB() const { - return AABB(position-halfsize, position+halfsize); + return AABB(position - halfsize, position + halfsize); } glm::vec3 getHalfSize() const { return halfsize * scale; } + + void setPos(const glm::vec3& vec) { + position = vec; + if (glm::distance2(position, prevPosition) >= TELEPORT_THRESOLD_SQR) { + prevPosition = vec; + } + } }; diff --git a/src/physics/PhysicsSolver.cpp b/src/physics/PhysicsSolver.cpp index e20bafacd..92780fe5e 100644 --- a/src/physics/PhysicsSolver.cpp +++ b/src/physics/PhysicsSolver.cpp @@ -5,197 +5,133 @@ #include "voxels/Block.hpp" #include "voxels/GlobalChunks.hpp" #include "voxels/voxel.hpp" +#include "objects/Entities.hpp" +#include "debug/Logger.hpp" -#include #include #define GLM_ENABLE_EXPERIMENTAL #include -inline const float E = 0.03f; -inline const float MAX_FIX = 0.1f; +inline constexpr float E = 0.03f; +inline constexpr float MAX_FIX = 0.1f; -PhysicsSolver::PhysicsSolver(glm::vec3 gravity) : gravity(gravity) {} +static debug::Logger logger("physics-solver"); -void PhysicsSolver::step( - const GlobalChunks& chunks, - Hitbox& hitbox, - float delta, - uint substeps, - entityid_t entity +PhysicsSolver::PhysicsSolver(const GlobalChunks& chunks, glm::vec3 gravity) + : chunks(chunks), gravity(std::move(gravity)) { +} + +static glm::vec3 calc_collsion_velocity_result( + const Hitbox& a, const Hitbox& b ) { - float dt = delta / static_cast(substeps); - float linearDamping = hitbox.linearDamping * hitbox.friction; - float s = 2.0f/BLOCK_AABB_GRID; - - 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; - } + const auto& vA = a.velocity; + const auto& vB = b.velocity; + if (glm::isinf(a.mass)) { + return vA; } - vel.x /= 1.0f + delta * linearDamping; - vel.z /= 1.0f + delta * linearDamping; - if (hitbox.verticalDamping > 0.0f) { - vel.y /= 1.0f + delta * linearDamping * hitbox.verticalDamping; - } - - AABB aabb; - aabb.a = hitbox.position - hitbox.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); - } + if (glm::isinf(b.mass)) { + return vB - a.elasticity * (vA - vB); } + 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( const GlobalChunks& chunks, const glm::vec3& pos, const glm::vec3& half, - float stepHeight, - float s + float stepHeight ) { - if (stepHeight > 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; - if (chunks.isObstacleAt(x, pos.y+half.y+stepHeight, z)) { - return 0.0f; - } + AABB aabb(-half, +half); + aabb.scale(glm::vec3(1.0f - E * 2, 1.0f, 1.0f - E * 2)); + aabb = aabb + pos + glm::vec3(0.0f, stepHeight, 0.0f); + if (stepHeight <= 0.0f) { + return stepHeight; + } + 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; } -template -static bool calc_collision_neg( +// todo: reduce code duplication + +template +static void calc_collision( + Hitbox& hitbox, const GlobalChunks& chunks, - glm::vec3& pos, - glm::vec3& vel, + const std::vector& solidHitboxes, const glm::vec3& half, - float stepHeight, - float s + float stepHeight ) { - if (vel[nx] >= 0.0f) { - return false; - } - glm::vec3 offset(0.0f, stepHeight, 0.0f); - for (int iy = 0; iy <= std::max(1, ((half-offset*0.5f)[ny]-E)*2/s); iy++) { - glm::vec3 coord; - coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; - for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++){ - coord[nz] = (pos[nz]-half[nz]+E) + iz * s; - coord[nx] = (pos[nx]-half[nx]-E); - if (const auto aabb = chunks.isObstacleAt(coord.x, coord.y, coord.z)) { - vel[nx] = 0.0f; - float newx = std::floor(coord[nx]) + half[nx] + aabb->max()[nx] + E; - if (newx - pos[nx] <= E) { - pos[nx] = newx; - } - return true; + auto& pos = hitbox.position; + auto& vel = hitbox.velocity; + + glm::vec3 offset(0.0f, stepHeight + E, 0.0f); + for (auto box : solidHitboxes) { + if (glm::distance2(box->position, pos) < E) { + continue; + } + auto boxhalf = box->getHalfSize(); + auto aabb = AABB(pos - half, pos + half); + glm::vec3 scale(1.0f); + scale[nz] = 1.0f - E * 8.0f; + scale[ny] = 1.0f - E * 2.0f; + aabb.scale(scale); + aabb = aabb + offset; + aabb.b.y -= stepHeight + E * 2; + + 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; -} - -template -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) { + if (vel[nx] * sign <= 0.0f && hitbox.groundVelocity[nx] * sign <= 0.0f) { return; } - glm::vec3 offset(0.0f, stepHeight, 0.0f); - for (int iy = 0; iy <= std::max(1, ((half-offset*0.5f)[ny]-E)*2/s); iy++) { + for (int iy = 0; iy <= glm::ceil(((half - offset * 0.5f)[ny] - E) * 2); iy++) { glm::vec3 coord; - coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; - for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++) { - coord[nz] = (pos[nz]-half[nz]+E) + iz * s; - coord[nx] = (pos[nx]+half[nx]+E); - if (const auto aabb = chunks.isObstacleAt(coord.x, coord.y, coord.z)) { - vel[nx] = 0.0f; - float newx = std::floor(coord[nx]) - half[nx] + aabb->min()[nx] - E; - if (newx - pos[nx] <= E) { + coord[ny] = ((pos + offset)[ny] - half[ny] + E) + iy; + for (int iz = 0; iz <= glm::ceil((half[nz] - E) * 2); iz++) { + coord[nz] = (pos[nz] - half[nz] + E) + iz; + coord[nx] = pos[nx] + (half[nx] + E * 2) * sign; + + auto aabb = AABB(pos - half, pos + half); + glm::vec3 scale(1.0f); + 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; } return; @@ -204,93 +140,314 @@ static void calc_collision_pos( } } -void PhysicsSolver::colisionCalc( - const GlobalChunks& chunks, - Hitbox& hitbox, - glm::vec3& vel, - glm::vec3& pos, - const glm::vec3 half, - float stepHeight +bool PhysicsSolver::calcCollisionNegY( + Hitbox& hitbox, const glm::vec3& half, float dt ) { - // step size (smaller - more accurate, but slower) // TODO: GET RID OF THIS - float s = 2.0f/BLOCK_AABB_GRID; + auto& pos = hitbox.position; + 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; - - calc_collision_neg<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); - calc_collision_pos<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); + 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<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); - calc_collision_pos<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); + auto velA = calc_collsion_velocity_result(hitbox, *box); + auto velB = calc_collsion_velocity_result(*box, hitbox); - if (calc_collision_neg<1, 0, 2>(chunks, pos, vel, half, 0.0f, s)) { - hitbox.grounded = true; - } - - 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; + hitbox.groundVelocity = box->position - box->prevPosition; + if (vel.y < hitbox.groundVelocity.y / dt) { + vel.y = velA.y; + box->velocity.y = velB.y; + if (hitbox.groundMaterial.empty() && !box->material.empty()) { + hitbox.groundMaterial = box->material; } + return true; } } } + if (vel.y >= 0.0f) { + return false; + } + hitbox.groundVelocity = {}; + for (int ix = 0; ix <= glm::ceil((half.x - E) * 2); ix++) { + glm::vec3 coord; + coord.x = (pos.x - half.x + E) + ix; + for (int iz = 0; iz <= glm::ceil((half.z - E) * 2); iz++) { + coord.z = (pos.z - half.z + E) + iz; + coord.y = pos.y - half.y - E * 2; + + 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); + + 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; + } + } + } + return false; +} + +void PhysicsSolver::calcCollisions( + 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){ - 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; + 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 (std::abs(newy-pos.y) <= MAX_FIX) { + 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; + } + } + } } } -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); +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; } -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; +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(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); } - return false; } void PhysicsSolver::removeSensor(Sensor* sensor) { - sensors.erase(std::remove(sensors.begin(), sensors.end(), sensor), sensors.end()); + sensors.erase( + std::remove(sensors.begin(), sensors.end(), sensor), sensors.end() + ); } diff --git a/src/physics/PhysicsSolver.hpp b/src/physics/PhysicsSolver.hpp index b7810cefd..e616696ca 100644 --- a/src/physics/PhysicsSolver.hpp +++ b/src/physics/PhysicsSolver.hpp @@ -9,35 +9,48 @@ #include class Block; +class Entities; class GlobalChunks; struct Sensor; class PhysicsSolver { - glm::vec3 gravity; - std::vector sensors; public: - PhysicsSolver(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); + PhysicsSolver(const GlobalChunks& chunks, glm::vec3 gravity); - void setSensors(std::vector sensors) { - this->sensors = std::move(sensors); + void step(const GlobalChunks& chunks, float delta, uint substeps); + + auto& getSensorsWriteable() { + return sensors; + } + + auto& getSolidHitboxesWriteable() { + return solidHitboxes; + } + + auto& getHitboxesWriteable() { + return hitboxes; } void removeSensor(Sensor* sensor); +private: + const GlobalChunks& chunks; + glm::vec3 gravity; + std::vector sensors; + std::vector solidHitboxes; + std::vector 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); }; diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index dc3f6153f..2a85b38bd 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -1,11 +1,5 @@ #include "Chunks.hpp" -#include - -#include -#include -#include - #include "data/StructLayout.hpp" #include "coders/byte_utils.hpp" #include "content/Content.hpp" @@ -20,6 +14,11 @@ #include "VoxelsVolume.hpp" #include "blocks_agent.hpp" +#include +#include +#include +#include + Chunks::Chunks( int32_t w, 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); - if (def.obstacle) { - glm::ivec3 offset {}; - if (v->state.segment) { - glm::ivec3 point(ix, iy, iz); - offset = seekOrigin(point, def, v->state) - point; - } - const auto& boxes = - def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; - for (const auto& hitbox : boxes) { - if (hitbox.contains( - {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} - )) { - return &hitbox; - } + if (!def.obstacle) { + return nullptr; + } + glm::ivec3 offset {}; + if (v->state.segment) { + glm::ivec3 point(ix, iy, iz); + offset = seekOrigin(point, def, v->state) - point; + } + const auto& boxes = + def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; + for (const auto& hitbox : boxes) { + if (hitbox.contains( + {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} + )) { + return &hitbox; } } return nullptr; diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index 4fe24f289..9fdebff1c 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -1,17 +1,16 @@ #pragma once -#include - -#include -#include -#include -#include - #include "typedefs.hpp" #include "voxel.hpp" #include "constants.hpp" #include "util/AreaMap2D.hpp" +#include +#include +#include +#include +#include + class VoxelRenderer; struct AABB; diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 032ae17e2..d24d8b26d 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -214,6 +214,6 @@ void GlobalChunks::putChunk(std::shared_ptr chunk) { chunksMap[keyfrom(chunk->x, chunk->z)] = std::move(chunk); } -const AABB* GlobalChunks::isObstacleAt(float x, float y, float z) const { - return blocks_agent::is_obstacle_at(*this, x, y, z); +std::optional GlobalChunks::isObstacleAt(float x, float y, float z, const AABB& aabb) const { + return blocks_agent::is_obstacle_at(*this, x, y, z, aabb); } diff --git a/src/voxels/GlobalChunks.hpp b/src/voxels/GlobalChunks.hpp index 7120658c2..de62cbc58 100644 --- a/src/voxels/GlobalChunks.hpp +++ b/src/voxels/GlobalChunks.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #define GLM_ENABLE_EXPERIMENTAL @@ -57,7 +58,7 @@ public: void putChunk(std::shared_ptr chunk); - const AABB* isObstacleAt(float x, float y, float z) const; + std::optional isObstacleAt(float x, float y, float z, const AABB& aabb) const; inline Chunk* getChunk(int cx, int cz) const { const auto& found = chunksMap.find(keyfrom(cx, cz)); diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index 0742a7293..a86b2e25b 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -13,7 +13,6 @@ #include "voxel.hpp" #include "VoxelsVolume.hpp" -#include #include #include #include @@ -468,8 +467,8 @@ void get_voxels( ); template -inline const AABB* is_obstacle_at( - const Storage& chunks, float x, float y, float z +inline std::optional is_obstacle_at( + const Storage& chunks, float x, float y, float z, const AABB& aabb ) { int ix = std::floor(x); int iy = std::floor(y); @@ -477,30 +476,34 @@ inline const AABB* is_obstacle_at( voxel* v = get(chunks, ix, iy, iz); if (v == nullptr) { if (iy >= CHUNK_H) { - return nullptr; + return std::nullopt; } else { - static const AABB empty; - return ∅ + return AABB(); } } const auto& def = chunks.getContentIndices().blocks.require(v->id); - if (def.obstacle) { - glm::ivec3 offset {}; - if (v->state.segment) { - glm::ivec3 point(ix, iy, iz); - offset = seek_origin(chunks, point, def, v->state) - point; - } - const auto& boxes = - def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; - for (const auto& hitbox : boxes) { - if (hitbox.contains( - {x - ix - offset.x, y - iy - offset.y, z - iz - offset.z} - )) { - return &hitbox; - } + if (!def.obstacle) { + return std::nullopt; + } + glm::ivec3 offset {}; + if (v->state.segment) { + glm::ivec3 point(ix, iy, iz); + offset = seek_origin(chunks, point, def, v->state) - point; + } + const auto& boxes = + def.rotatable ? def.rt.hitboxes[v->state.rotation] : def.hitboxes; + + for (const auto& hitbox : boxes) { + if (hitbox.intersects(aabb - glm::ivec3(ix, iy, iz))) { + return hitbox + offset; } } - return nullptr; + return std::nullopt; +} + +template +inline std::optional 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 diff --git a/src/world/Level.cpp b/src/world/Level.cpp index 2a1583bd7..e6d9000e1 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -26,7 +26,7 @@ Level::Level( : world(std::move(worldPtr)), content(content), chunks(std::make_unique(*this)), - physics(std::make_unique(glm::vec3(0, -22.6f, 0))), + physics(std::make_unique(*chunks, glm::vec3(0, -22.6f, 0))), events(std::make_unique()), entities(std::make_unique(*this)), players(std::make_unique(*this)), diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index 1f9a2241d..260a28e3c 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -173,7 +173,7 @@ void WorldGenerator::placeStructure( } auto& otherPrototype = *found->second; auto chunkAABB = gen_chunk_aabb(chunkX + lcx, chunkZ + lcz); - if (chunkAABB.intersect(aabb)) { + if (chunkAABB.intersects(aabb)) { otherPrototype.placements.emplace_back( priority, StructurePlacement {