Merge branch 'MihailRis:main' into main

This commit is contained in:
Xertis 2026-03-16 00:54:38 +03:00 • committed by GitHub
commit e049f4ae0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 1209 additions and 534 deletions

View file

@ -95,6 +95,23 @@ Example:
0. A rectangular area with a width, height and length of 0.4 m, centered at 0.0.
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*

View file

@ -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:

View file

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

View file

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

View file

@ -98,6 +98,7 @@ A panel that controls the size and position of two elements. Allows the user to
Inner text is a button text.
- `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 <btn>LMB</btn> + <btn>Shift</btn>. Inventory id and slot index passed as arguments.
@ -223,3 +231,8 @@ Element must be in direct sub-element of *inventory*.
- `onrightclick` - Lua event called on <btn>RMB</btn> click. Inventory id and slot index passed as arguments.
- `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

View file

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

View file

@ -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
```
```

View file

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

View file

@ -82,6 +82,7 @@ pack.get_info(packid: string) -> {
- `!` - required
- `?` - optional
- `~` - weak
например `!teal`
Для получения информации о нескольких паках используйте таблицу id, чтобы не
@ -101,4 +102,4 @@ pack.assemble(packids: table<string>) -> table<string>
pack.request_writeable(packid: string, callback: function(string))
```
Запрашивает у пользователя право на модификацию пака. При подтвержении новая точка входа будет передана в callback.
Запрашивает у пользователя право на модификацию пака. При подтвержении новая точка входа будет передана в callback.

View file

@ -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
```

View file

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

View file

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

View file

@ -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` - общее число слотов. Тип: число.

View file

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

View file

@ -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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -344,9 +344,10 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context& ctx) {
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<std::unique_ptr<Bone>> bones;

View file

@ -88,5 +88,8 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
root.at("skeleton-name").get(def.skeletonName);
root.at("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);
}

View file

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

View file

@ -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<ContentGfxCache>(
@ -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, &currentPlayer, &assets](const Hitbox& hitbox) {
const BlockMaterial* material = nullptr;
if (hitbox.groundMaterial.empty()) {
const auto& pos = hitbox.position;
const auto& half = hitbox.getHalfSize();
auto& blockIndices = level.content.getIndices()->blocks;
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
for (int offsetX = -1; offsetX <= 1; offsetX++) {
int x = std::floor(pos.x + half.x * offsetX);
int y = std::floor(pos.y - half.y * 1.1f);
int z = std::floor(pos.z + half.z * offsetZ);
auto vox = currentPlayer.chunks->get(x, y, z);
if (vox) {
auto& def = blockIndices.require(vox->id);
if (!def.obstacle) {
continue;
}
material = level.content.findBlockMaterial(def.material);
break;
}
}
}
} else {
material = level.content.findBlockMaterial(hitbox.groundMaterial);
}
if (material == nullptr) {
return;
}
auto sound = assets.get<audio::Sound>(material->stepsSound);
glm::vec3 pos {};
auto soundsCamera = currentPlayer.currentCamera.get();
if (currentPlayer.isCurrentCameraBuiltin()) {
soundsCamera = currentPlayer.fpCamera.get();
}
bool relative = soundsCamera == currentPlayer.fpCamera.get();
if (!relative) {
pos = currentPlayer.getPosition();
}
audio::play(
sound,
pos,
relative,
0.333f,
1.0f + (rand() % 6 - 3) * 0.05f,
false,
audio::PRIORITY_LOW,
audio::get_channel_index("regular")
);
});
controller.getBlocksController()->listenBlockInteraction(
[&level, &assets]
(auto player, const auto& pos, const auto& def, BlockInteraction type) {
auto material = level.content.findBlockMaterial(def.material);
if (material == nullptr) {
return;
}
if (type == BlockInteraction::step) {
auto sound = rassets.get<audio::Sound>(material->stepsSound);
glm::vec3 pos {};
auto soundsCamera = currentPlayer->currentCamera.get();
if (currentPlayer->isCurrentCameraBuiltin()) {
soundsCamera = currentPlayer->fpCamera.get();
}
bool relative = player == currentPlayer &&
soundsCamera == currentPlayer->fpCamera.get();
if (!relative) {
pos = player->getPosition();
}
audio::play(
sound,
pos,
relative,
0.333f,
1.0f + (rand() % 6 - 3) * 0.05f,
false,
audio::PRIORITY_LOW,
audio::get_channel_index("regular")
);
} else {
if (type != BlockInteraction::step) {
audio::Sound* sound = nullptr;
switch (type) {
case BlockInteraction::placing:
sound = rassets.get<audio::Sound>(material->placeSound);
sound = assets.get<audio::Sound>(material->placeSound);
break;
case BlockInteraction::destruction:
sound = rassets.get<audio::Sound>(material->breakSound);
sound = assets.get<audio::Sound>(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;
}

View file

@ -2,24 +2,24 @@
#include <memory>
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<ContentGfxCache> 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;
};

View file

@ -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] =

View file

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

View file

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

View file

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

View file

@ -18,6 +18,8 @@ struct Hitbox;
struct CameraSettings;
struct EngineSettings;
using FootstepCallback = std::function<void(const Hitbox&)>;
class CameraControl {
Player& player;
std::shared_ptr<Camera> 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);
};

View file

@ -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<l_is_enabled>},
{"set_enabled", lua::wrap<l_set_enabled>},
@ -169,5 +211,11 @@ const luaL_Reg rigidbodylib[] = {
{"set_crouching", lua::wrap<l_set_crouching>},
{"get_body_type", lua::wrap<l_get_body_type>},
{"set_body_type", lua::wrap<l_set_body_type>},
{"set_material", lua::wrap<l_set_material>},
{"get_material", lua::wrap<l_get_material>},
{"get_mass", lua::wrap<l_get_mass>},
{"set_mass", lua::wrap<l_set_mass>},
{"get_elasticity", lua::wrap<l_get_elasticity>},
{"set_elasticity", lua::wrap<l_set_elasticity>},
{nullptr, nullptr}
};

View file

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

View file

@ -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<size_t>(id) >= indices->blocks.count()) {
auto& level = require_level();
auto& indices = require_content().getIndices()->blocks;
if (static_cast<size_t>(id) >= indices.count()) {
return 0;
}
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<int n>
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<n>(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<CHUNK_W>(x);
int cz = floordiv<CHUNK_D>(z);
auto chunk = blocks_agent::get_chunk(*level->chunks, cx, cz);
auto chunk = blocks_agent::get_chunk(*require_level().chunks, cx, cz);
if (chunk == nullptr) {
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<size_t>(lua::gettop(L) >= 3 ? lua::tointeger(L, 3) : 0);
const size_t hitboxIndex =
static_cast<size_t>(lua::isnumber(L, 3) ? lua::tointeger(L, 3) : 0);
if (def->rotatable) {
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<size_t>(id) >= indices->blocks.count()) {
if (static_cast<size_t>(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;
}

View file

@ -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 <int (*getterfunc)(lua::State*, const Camera&)>
static int l_camera_getter(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1));
return getterfunc(L, *level->cameras.at(index));
return getterfunc(L, *require_level().cameras.at(index));
}
template <void (*setterfunc)(lua::State*, Camera&, int)>
static int l_camera_setter(lua::State* L) {
size_t index = static_cast<size_t>(lua::tointeger(L, 1));
setterfunc(L, *level->cameras.at(index), 2);
setterfunc(L, *require_level().cameras.at(index), 2);
return 0;
}
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<size_t>(lua::tointeger(L, 1));
auto& indices = content->getIndices(ResourceType::CAMERA);
auto& indices = require_content().getIndices(ResourceType::CAMERA);
return lua::pushstring(L, indices.getName(index));
}
@ -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<size_t>(lua::tointeger(L, 1));
auto& camera = *level->cameras.at(index);
auto& camera = *require_level().cameras.at(index);
auto center = lua::tovec<3>(L, 2);
auto matrix = glm::inverse(
glm::lookAt(glm::vec3(), center - camera.position, glm::vec3(0, 1, 0))

View file

@ -347,6 +347,41 @@ static int l_write_descriptor(lua::State* L) {
return 0;
}
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<l_has_descriptor>},
{"__read_descriptor", lua::wrap<l_read_descriptor>},
{"__write_descriptor", lua::wrap<l_write_descriptor>},
{"__seek_descriptor", lua::wrap<l_seek_descriptor>},
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
{"__close_descriptor", lua::wrap<l_close_descriptor>},
{"__close_all_descriptors", lua::wrap<l_close_all_descriptors>},
{nullptr, nullptr}
};

View file

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

View file

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

View file

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

View file

@ -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<float>(lua::tonumber(L, 2));
auto players = level->players->getAllInRadius(center, radius);
auto players = level.players->getAllInRadius(center, radius);
lua::createtable(L, players.size(), 0);
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;
}

View file

@ -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<l_from_mat4>},
{"from_euler", lua::wrap<l_from_euler>},
{"slerp", lua::wrap<l_slerp>},
{"tostring", lua::wrap<l_tostring>},
{nullptr, nullptr}

View file

@ -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) {

View file

@ -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 &&

View file

@ -1,28 +1,30 @@
#define VC_ENABLE_REFLECTION
#include "Entities.hpp"
#include <entt/entity/registry.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <sstream>
#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 <entt/entity/registry.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <limits>
#include <sstream>
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<EntityId>(entity, static_cast<entityid_t>(id), def);
registry->emplace<EntityId>(entity, id, def);
const auto& tsf = registry->emplace<Transform>(
entity,
position,
@ -91,7 +93,7 @@ entityid_t Entities::spawn(
auto& body = registry->emplace<Rigidbody>(
entity,
true,
Hitbox {def.bodyType, position, def.hitbox * 0.5f},
Hitbox {id, def.bodyType, position, def.hitbox * 0.5f},
std::vector<Sensor> {}
);
body.initialize(def, id, *this);
@ -161,7 +163,11 @@ void Entities::loadEntity(const dv::value& map, Entity entity) {
}
std::optional<Entities::RaycastResult> Entities::rayCast(
glm::vec3 start, glm::vec3 dir, float maxDistance, entityid_t ignore
glm::vec3 start,
glm::vec3 dir,
float maxDistance,
entityid_t ignore,
bool solidOnly
) {
Ray ray(start, dir);
auto view = registry->view<EntityId, Transform, Rigidbody>();
@ -170,7 +176,7 @@ std::optional<Entities::RaycastResult> 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<EntityId, Transform, Rigidbody>();
auto physics = level.physics.get();
std::vector<Sensor*> 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<EntityId, Rigidbody>();
for (auto [entity, eid, rigidbody] : view.each()) {
if (eid.destroyFlag || !rigidbody.enabled) {
continue;
}
rigidbody.hitbox.mass = eid.def.bodyType == BodyType::DYNAMIC
? rigidbody.mass
: std::numeric_limits<float>::infinity();
rigidbody.hitbox.elasticity = rigidbody.elasticity;
hitboxes.emplace_back(&rigidbody.hitbox);
if (!eid.def.solid) {
continue;
}
solidHitboxes.emplace_back(&rigidbody.hitbox);
}
}
@ -303,29 +332,31 @@ void Entities::updatePhysics(float delta) {
auto view = registry->view<EntityId, Transform, Rigidbody>();
auto physics = level.physics.get();
int substeps = std::max<int>(std::min<int>(delta * 1000, 200), 8);
physics->step(*level.chunks, delta, substeps);
for (auto [entity, eid, transform, rigidbody] : view.each()) {
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<int>(delta * vel * 20);
substeps = std::min(100, std::max(2, substeps));
physics->step(*level.chunks, hitbox, delta, substeps, eid.uid);
hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f)
? 8.0f
: (!grounded ? 2.0f : 10.0f);
hitbox.scale = transform.size;
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<EntityId, Rigidbody>();
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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <void (*callback)(const Entity&, size_t, entityid_t)>
@ -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 {};

View file

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

View file

@ -1,10 +1,12 @@
#include "Hitbox.hpp"
#include <stdexcept>
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) {
}

View file

@ -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 <string>
#include <functional>
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
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;
}
}
};

View file

@ -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 <iostream>
#include <algorithm>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
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<float>(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 <int nx, int ny, int nz>
static bool calc_collision_neg(
// todo: reduce code duplication
template <int nx, int ny, int nz, int sign>
static void calc_collision(
Hitbox& hitbox,
const GlobalChunks& chunks,
glm::vec3& pos,
glm::vec3& vel,
const std::vector<Hitbox*>& 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<int>(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 <int nx, int ny, int nz>
static void calc_collision_pos(
const GlobalChunks& chunks,
glm::vec3& pos,
glm::vec3& vel,
const glm::vec3& half,
float stepHeight,
float s
) {
if (vel[nx] <= 0.0f) {
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<int>(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<float>(substeps);
for (uint i = 0; i < substeps; i++) {
for (auto hitbox : hitboxes) {
glm::vec3& pos = hitbox->position;
hitbox->prevPosition = hitbox->position;
calcSubstep(*hitbox, hitbox->velocity, pos, dt);
}
}
for (auto hitbox : hitboxes) {
float linearDamping = hitbox->linearDamping * hitbox->friction;
glm::vec3& vel = hitbox->velocity;
auto diff = hitbox->groundVelocity / dt - vel;
vel.x += diff.x * delta * linearDamping;
vel.z += diff.z * delta * linearDamping;
if (hitbox->verticalDamping > 0.0f) {
vel.y /= 1.0f + delta * linearDamping * hitbox->verticalDamping;
}
if (!hitbox->grounded) {
hitbox->groundVelocity = {};
}
updateSensors(*hitbox);
hitbox->friction = glm::abs(hitbox->gravityScale <= 1e-7f)
? 8.0f
: (!hitbox->prevGrounded ? 2.0f : 10.0f);
}
}
void PhysicsSolver::updateSensors(Hitbox& hitbox) {
auto aabb = hitbox.getAABB();
for (size_t i = 0; i < sensors.size(); i++) {
auto& sensor = *sensors[i];
if (sensor.entity == hitbox.entity) {
continue;
}
bool triggered = false;
switch (sensor.type) {
case SensorType::AABB:
triggered = aabb.intersects(sensor.calculated.aabb);
break;
case SensorType::RADIUS:
triggered = glm::distance2(
hitbox.position, glm::vec3(sensor.calculated.radial))
< sensor.calculated.radial.w;
break;
}
if (!triggered) {
continue;
}
if (sensor.prevEntered.find(hitbox.entity) == sensor.prevEntered.end()) {
sensor.enterCallback(sensor.entity, sensor.index, hitbox.entity);
}
sensor.nextEntered.insert(hitbox.entity);
}
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()
);
}

View file

@ -9,35 +9,48 @@
#include <glm/glm.hpp>
class Block;
class Entities;
class GlobalChunks;
struct Sensor;
class PhysicsSolver {
glm::vec3 gravity;
std::vector<Sensor*> 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<Sensor*> 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<Sensor*> sensors;
std::vector<Hitbox*> solidHitboxes;
std::vector<Hitbox*> hitboxes;
void calcCollisions(
Hitbox& hitbox,
glm::vec3& vel,
glm::vec3& pos,
const glm::vec3& half,
float stepHeight,
float dt
);
void calcSubstep(Hitbox& hitbox, glm::vec3& vel, glm::vec3& pos, float dt);
bool calcCollisionNegY(Hitbox& hitbox, const glm::vec3& half, float dt);
void updateSensors(Hitbox& hitbox);
};

View file

@ -1,11 +1,5 @@
#include "Chunks.hpp"
#include <math.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
#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 <math.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
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;

View file

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

View file

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

View file

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

View file

@ -13,7 +13,6 @@
#include "voxel.hpp"
#include "VoxelsVolume.hpp"
#include <algorithm>
#include <algorithm>
#include <glm/glm.hpp>
#include <set>
@ -468,8 +467,8 @@ void get_voxels(
);
template <class Storage>
inline const AABB* is_obstacle_at(
const Storage& chunks, float x, float y, float z
inline std::optional<AABB> 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 &empty;
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 <class Storage>
inline std::optional<AABB> is_obstacle_at(const Storage& chunks, float x, float y, float z) {
return is_obstacle_at(chunks, x, y, z, AABB({x, y, z}, {x + 1, y + 1, z + 1}));
}
/// @brief Check block grounding

View file

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

View file

@ -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 {