commit
74a714fe2c
@ -34,6 +34,7 @@ Block model type from list:
|
||||
- "none" - invisible block (air)
|
||||
- "X" - grass model (two crossed sprites)
|
||||
- "aabb" - model based of block hitbox (complex hitbox will be combined into one). Examples: pipes, bulbs, panels.
|
||||
- "custom" - used when specifying custom models via *model-name*
|
||||
|
||||
### *model-name*
|
||||
|
||||
@ -64,6 +65,39 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
|
||||
- "pane" - panels, doors, signs
|
||||
- "stairs" - "pane" + flipped variants
|
||||
|
||||
## Variants
|
||||
|
||||
Some properties can vary dynamically, depending on the variant number stored in the user bits of the block.
|
||||
|
||||
Variability parameters are specified in the `state-based` block:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"state-based": {
|
||||
"offset": 0,
|
||||
"bits": 4,
|
||||
"variants": [
|
||||
{...},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `offset` specifies the bit offset from which the variant index starts. (Default is 0)
|
||||
- `bits` specifies the number of bits encoding the variant index. (Default is 4).
|
||||
Currently, the maximum number of variants is 16, so specifying more than 4 bits does not make practical sense.
|
||||
|
||||
Properties available for variance:
|
||||
- model
|
||||
- model-name
|
||||
- texture
|
||||
- texture-faces
|
||||
- model-primitives (deprecated)
|
||||
|
||||
Variants are managed via `block.set_variant(x, y, z, index)`.
|
||||
|
||||
## Lighting
|
||||
|
||||
### *emission*
|
||||
|
||||
@ -59,6 +59,15 @@ block.is_replaceable_at(x: int, y: int, z: int) -> bool
|
||||
|
||||
-- Returns count of available block IDs.
|
||||
block.defs_count() -> int
|
||||
|
||||
-- Returns the index of the item specified in the *picking-item* property.
|
||||
block.get_picking_item(id: int) -> int
|
||||
|
||||
-- Returns the block variant index
|
||||
block.get_variant(x: int, y: int, z: int) -> int
|
||||
|
||||
-- Sets the block variant by index
|
||||
block.set_variant(x: int, y: int, z: int, index: int) -> int
|
||||
```
|
||||
|
||||
## Rotation
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
- "none" - невидимый блок (пример: воздух)
|
||||
- "X" - модель травы (крест из двух спрайтов)
|
||||
- "aabb" - модель, соответствующая хитбоксу блока (составной хитбокс будет объединен в один). Примеры: трубы, лампочки, панели.
|
||||
- "custom" - используется при указании собственной модели через *model-name*
|
||||
|
||||
### Имя модели - *model-name*
|
||||
|
||||
@ -72,6 +73,39 @@
|
||||
При приближении к блоку движок создаст эмиттер, который будет работать
|
||||
до разрушения блока или отдаления камеры на некоторое расстояние.
|
||||
|
||||
## Варианты
|
||||
|
||||
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
||||
|
||||
Параметры вариативности указываются в блоке `state-based`:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"state-based": {
|
||||
"offset": 0,
|
||||
"bits": 4,
|
||||
"variants": [
|
||||
{...},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `offset` определяет битового смещения, с которого начинается индекс варианта. (По-умолчанию - 0)
|
||||
- `bits` определяет число бит, кодирующих индекс варианта. (По-умолчанию - 4).
|
||||
На данный момент максимальное число вариантов - 16, поэтому указание более 4 бит не имеет практического смысла.
|
||||
|
||||
Доступные для вариативности свойства:
|
||||
- model
|
||||
- model-name
|
||||
- texture
|
||||
- texture-faces
|
||||
- model-primitives (устарело)
|
||||
|
||||
Управление состоянием производится через `block.set_variant(x, y, z, index)`.
|
||||
|
||||
## Освещение
|
||||
|
||||
### Излучение - *emission*:
|
||||
|
||||
@ -61,6 +61,12 @@ block.defs_count() -> int
|
||||
|
||||
-- Возвращает числовой id предмета, указанного в свойстве *picking-item*.
|
||||
block.get_picking_item(id: int) -> int
|
||||
|
||||
-- Возвращает индекс варианта блока
|
||||
block.get_variant(x: int, y: int, z: int) -> int
|
||||
|
||||
-- Устанавливает вариант блока по индексу
|
||||
block.set_variant(x: int, y: int, z: int, index: int) -> int
|
||||
```
|
||||
|
||||
### Raycast
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
language = "VCM"
|
||||
extensions = ["vcm"]
|
||||
line-comment-start = "#"
|
||||
keywords = [
|
||||
"on", "off"
|
||||
]
|
||||
|
||||
@ -227,6 +227,17 @@ void AssetsLoader::processPreloadConfigs(const Content* content) {
|
||||
}
|
||||
}
|
||||
|
||||
static void add_variant(AssetsLoader& loader, const Variant& variant) {
|
||||
if (!variant.model.name.empty() &&
|
||||
variant.model.name.find(':') == std::string::npos) {
|
||||
loader.add(
|
||||
AssetType::MODEL,
|
||||
MODELS_FOLDER + "/" + variant.model.name,
|
||||
variant.model.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
|
||||
loader.processPreloadConfigs(content);
|
||||
if (content) {
|
||||
@ -261,13 +272,12 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
|
||||
}
|
||||
}
|
||||
for (const auto& [_, def] : content->blocks.getDefs()) {
|
||||
if (!def->model.name.empty() &&
|
||||
def->model.name.find(':') == std::string::npos) {
|
||||
loader.add(
|
||||
AssetType::MODEL,
|
||||
MODELS_FOLDER + "/" + def->model.name,
|
||||
def->model.name
|
||||
);
|
||||
if (def->variants) {
|
||||
for (const auto& variant : def->variants->variants) {
|
||||
add_variant(loader, variant);
|
||||
}
|
||||
} else {
|
||||
add_variant(loader, def->defaults);
|
||||
}
|
||||
}
|
||||
for (const auto& [_, def] : content->items.getDefs()) {
|
||||
|
||||
@ -19,10 +19,15 @@ static const std::unordered_map<std::string, int> side_indices {
|
||||
{"east", 5},
|
||||
};
|
||||
|
||||
static bool to_boolean(const xml::Attribute& attr) {
|
||||
return attr.getText() != "off";
|
||||
}
|
||||
|
||||
static void perform_rect(const xmlelement& root, model::Model& model) {
|
||||
auto from = root.attr("from").asVec3();
|
||||
auto right = root.attr("right").asVec3();
|
||||
auto up = root.attr("up").asVec3();
|
||||
bool shading = true;
|
||||
|
||||
right *= -1;
|
||||
from -= right;
|
||||
@ -37,6 +42,10 @@ static void perform_rect(const xmlelement& root, model::Model& model) {
|
||||
region.scale(root.attr("region-scale").asVec2());
|
||||
}
|
||||
|
||||
if (root.has("shading")) {
|
||||
shading = to_boolean(root.attr("shading"));
|
||||
}
|
||||
|
||||
auto flip = root.attr("flip", "").getText();
|
||||
if (flip == "h") {
|
||||
std::swap(region.u1, region.u2);
|
||||
@ -48,7 +57,7 @@ static void perform_rect(const xmlelement& root, model::Model& model) {
|
||||
from -= up;
|
||||
}
|
||||
std::string texture = root.attr("texture", "$0").getText();
|
||||
auto& mesh = model.addMesh(texture);
|
||||
auto& mesh = model.addMesh(texture, shading);
|
||||
|
||||
auto normal = glm::cross(glm::normalize(right), glm::normalize(up));
|
||||
mesh.addRect(
|
||||
@ -75,8 +84,20 @@ static void perform_box(const xmlelement& root, model::Model& model) {
|
||||
auto center = (from + to) * 0.5f;
|
||||
auto halfsize = (to - from) * 0.5f;
|
||||
|
||||
bool shading = true;
|
||||
std::string texfaces[6] {"$0","$1","$2","$3","$4","$5"};
|
||||
|
||||
if (root.has("texture")) {
|
||||
auto texture = root.attr("texture").getText();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
texfaces[i] = texture;
|
||||
}
|
||||
}
|
||||
|
||||
if (root.has("shading")) {
|
||||
shading = to_boolean(root.attr("shading"));
|
||||
}
|
||||
|
||||
for (const auto& elem : root.getElements()) {
|
||||
if (elem->getTag() == "part") {
|
||||
// todo: replace by expression parsing
|
||||
@ -120,7 +141,7 @@ static void perform_box(const xmlelement& root, model::Model& model) {
|
||||
}
|
||||
bool enabled[6] {};
|
||||
enabled[i] = true;
|
||||
auto& mesh = model.addMesh(texfaces[i]);
|
||||
auto& mesh = model.addMesh(texfaces[i], shading);
|
||||
mesh.addBox(center, halfsize, regions, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,13 +28,21 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
// Generating runtime info
|
||||
def.rt.id = blockDefsIndices.size();
|
||||
def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission);
|
||||
def.rt.solid = def.model.type == BlockModelType::BLOCK;
|
||||
def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1;
|
||||
|
||||
if (def.variants) {
|
||||
for (auto& variant : def.variants->variants) {
|
||||
variant.rt.solid = variant.model.type == BlockModelType::BLOCK;
|
||||
}
|
||||
def.defaults = def.variants->variants.at(0);
|
||||
} else {
|
||||
def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK;
|
||||
}
|
||||
|
||||
const float EPSILON = 0.01f;
|
||||
if (def.rt.extended && glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size) {
|
||||
def.rt.solid = true;
|
||||
}
|
||||
def.rt.solid =
|
||||
def.obstacle &&
|
||||
(glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size);
|
||||
def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1;
|
||||
|
||||
if (def.rotatable) {
|
||||
for (uint i = 0; i < BlockRotProfile::MAX_COUNT; i++) {
|
||||
@ -50,7 +58,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
}
|
||||
|
||||
blockDefsIndices.push_back(&def);
|
||||
groups->insert(def.drawGroup);
|
||||
groups->insert(def.defaults.drawGroup); // FIXME
|
||||
}
|
||||
|
||||
std::vector<ItemDef*> itemDefsIndices;
|
||||
|
||||
@ -40,6 +40,49 @@ static void perform_user_block_fields(
|
||||
layout = StructLayout::create(fields);
|
||||
}
|
||||
|
||||
static void load_variant(
|
||||
Variant& variant, const dv::value& root, const std::string& name
|
||||
) {
|
||||
// block texturing
|
||||
if (root.has("texture")) {
|
||||
const auto& texture = root["texture"].asString();
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
variant.textureFaces[i] = texture;
|
||||
}
|
||||
} else if (root.has("texture-faces")) {
|
||||
const auto& texarr = root["texture-faces"];
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
variant.textureFaces[i] = texarr[i].asString();
|
||||
}
|
||||
}
|
||||
|
||||
// block model
|
||||
auto& model = variant.model;
|
||||
std::string modelTypeName = BlockModelTypeMeta.getNameString(model.type);
|
||||
root.at("model").get(modelTypeName);
|
||||
root.at("model-name").get(model.name);
|
||||
if (BlockModelTypeMeta.getItem(modelTypeName, model.type)) {
|
||||
if (model.type == BlockModelType::CUSTOM && model.customRaw == nullptr) {
|
||||
if (root.has("model-primitives")) {
|
||||
model.customRaw = root["model-primitives"];
|
||||
} else if (model.name.empty()) {
|
||||
throw std::runtime_error(
|
||||
name + ": no 'model-primitives' or 'model-name' found"
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (!modelTypeName.empty()) {
|
||||
logger.error() << "unknown model: " << modelTypeName;
|
||||
model.type = BlockModelType::NONE;
|
||||
}
|
||||
std::string cullingModeName = CullingModeMeta.getNameString(variant.culling);
|
||||
root.at("culling").get(cullingModeName);
|
||||
if (!CullingModeMeta.getItem(cullingModeName, variant.culling)) {
|
||||
logger.error() << "unknown culling mode: " << cullingModeName;
|
||||
}
|
||||
root.at("draw-group").get(variant.drawGroup);
|
||||
}
|
||||
|
||||
template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
Block& def, const std::string& name, const io::path& file
|
||||
) {
|
||||
@ -72,43 +115,34 @@ template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
|
||||
root.at("caption").get(def.caption);
|
||||
|
||||
// block texturing
|
||||
if (root.has("texture")) {
|
||||
const auto& texture = root["texture"].asString();
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
def.textureFaces[i] = texture;
|
||||
}
|
||||
} else if (root.has("texture-faces")) {
|
||||
const auto& texarr = root["texture-faces"];
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
def.textureFaces[i] = texarr[i].asString();
|
||||
}
|
||||
}
|
||||
load_variant(def.defaults, root, name);
|
||||
|
||||
// block model
|
||||
auto& model = def.model;
|
||||
std::string modelTypeName = BlockModelTypeMeta.getNameString(model.type);
|
||||
root.at("model").get(modelTypeName);
|
||||
root.at("model-name").get(def.model.name);
|
||||
if (BlockModelTypeMeta.getItem(modelTypeName, model.type)) {
|
||||
if (model.type == BlockModelType::CUSTOM && def.model.customRaw == nullptr) {
|
||||
if (root.has("model-primitives")) {
|
||||
def.model.customRaw = root["model-primitives"];
|
||||
} else if (def.model.name.empty()) {
|
||||
throw std::runtime_error(
|
||||
name + ": no 'model-primitives' or 'model-name' found"
|
||||
);
|
||||
if (root.has("state-based")) {
|
||||
const auto& stateBased = root["state-based"];
|
||||
if (stateBased.has("variants")) {
|
||||
const auto& variants = stateBased["variants"];
|
||||
|
||||
int offset = 0;
|
||||
int bitsCount = 4;
|
||||
stateBased.at("offset").get(offset);
|
||||
stateBased.at("bits").get(bitsCount);
|
||||
if (offset < 0 || bitsCount <= 0 || offset + bitsCount > 8) {
|
||||
throw std::runtime_error("Invalid state-based bits configuration");
|
||||
}
|
||||
|
||||
def.variants = std::make_unique<Variants>();
|
||||
def.variants->offset = 0;
|
||||
def.variants->mask = 0xF;
|
||||
def.variants->variants.push_back(def.defaults);
|
||||
for (int i = 0; i < variants.size(); i++) {
|
||||
Variant variant = def.defaults;
|
||||
load_variant(variant, variants[i], name);
|
||||
def.variants->variants.push_back(variant);
|
||||
}
|
||||
while (def.variants->variants.size() < BLOCK_MAX_VARIANTS) {
|
||||
def.variants->variants.push_back(def.defaults);
|
||||
}
|
||||
}
|
||||
} else if (!modelTypeName.empty()) {
|
||||
logger.error() << "unknown model: " << modelTypeName;
|
||||
model.type = BlockModelType::NONE;
|
||||
}
|
||||
|
||||
std::string cullingModeName = CullingModeMeta.getNameString(def.culling);
|
||||
root.at("culling").get(cullingModeName);
|
||||
if (!CullingModeMeta.getItem(cullingModeName, def.culling)) {
|
||||
logger.error() << "unknown culling mode: " << cullingModeName;
|
||||
}
|
||||
|
||||
root.at("material").get(def.material);
|
||||
@ -176,9 +210,11 @@ template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
"block " + util::quote(def.name) + ": invalid block size"
|
||||
);
|
||||
}
|
||||
if (model.type == BlockModelType::BLOCK &&
|
||||
|
||||
// should variant modify hitbox?
|
||||
if (def.defaults.model.type == BlockModelType::BLOCK &&
|
||||
(def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) {
|
||||
model.type = BlockModelType::AABB;
|
||||
def.defaults.model.type = BlockModelType::AABB;
|
||||
def.hitboxes = {AABB(def.size)};
|
||||
}
|
||||
}
|
||||
@ -194,7 +230,6 @@ template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
root.at("selectable").get(def.selectable);
|
||||
root.at("grounded").get(def.grounded);
|
||||
root.at("hidden").get(def.hidden);
|
||||
root.at("draw-group").get(def.drawGroup);
|
||||
root.at("picking-item").get(def.pickingItem);
|
||||
root.at("surface-replacement").get(def.surfaceReplacement);
|
||||
root.at("script-name").get(def.scriptName);
|
||||
|
||||
@ -14,12 +14,12 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
|
||||
{
|
||||
Block& block = builder.blocks.create(CORE_AIR);
|
||||
block.replaceable = true;
|
||||
block.drawGroup = 1;
|
||||
block.lightPassing = true;
|
||||
block.skyLightPassing = true;
|
||||
block.obstacle = false;
|
||||
block.selectable = false;
|
||||
block.model.type = BlockModelType::NONE;
|
||||
block.defaults.drawGroup = 1;
|
||||
block.defaults.model.type = BlockModelType::NONE;
|
||||
block.pickingItem = CORE_EMPTY;
|
||||
}
|
||||
{
|
||||
@ -37,7 +37,7 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
|
||||
{
|
||||
Block& block = builder.blocks.create(CORE_OBSTACLE);
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
block.textureFaces[i] = "obstacle";
|
||||
block.defaults.textureFaces[i] = "obstacle";
|
||||
}
|
||||
block.hitboxes = {AABB()};
|
||||
block.breakable = false;
|
||||
@ -50,9 +50,9 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
|
||||
{
|
||||
Block& block = builder.blocks.create(CORE_STRUCT_AIR);
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
block.textureFaces[i] = "struct_air";
|
||||
block.defaults.textureFaces[i] = "struct_air";
|
||||
}
|
||||
block.drawGroup = -1;
|
||||
block.defaults.drawGroup = -1;
|
||||
block.skyLightPassing = true;
|
||||
block.lightPassing = true;
|
||||
block.hitboxes = {AABB()};
|
||||
|
||||
@ -22,21 +22,30 @@ ContentGfxCache::ContentGfxCache(
|
||||
refresh();
|
||||
}
|
||||
|
||||
void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
|
||||
static void refresh_variant(
|
||||
const Assets& assets,
|
||||
const Block& def,
|
||||
const Variant& variant,
|
||||
uint8_t variantIndex,
|
||||
std::unique_ptr<UVRegion[]>& sideregions,
|
||||
const Atlas& atlas,
|
||||
const GraphicsSettings& settings,
|
||||
std::unordered_map<blockid_t, model::Model>& models
|
||||
) {
|
||||
for (uint side = 0; side < 6; side++) {
|
||||
std::string tex = def.textureFaces[side];
|
||||
if (def.culling == CullingMode::OPTIONAL &&
|
||||
std::string tex = variant.textureFaces[side];
|
||||
if (variant.culling == CullingMode::OPTIONAL &&
|
||||
!settings.denseRender.get() && atlas.has(tex + "_opaque")) {
|
||||
tex = tex + "_opaque";
|
||||
}
|
||||
if (atlas.has(tex)) {
|
||||
sideregions[def.rt.id * 6 + side] = atlas.get(tex);
|
||||
sideregions[(def.rt.id * 6 + side) * MAX_VARIANTS + variantIndex] = atlas.get(tex);
|
||||
} else if (atlas.has(TEXTURE_NOTFOUND)) {
|
||||
sideregions[def.rt.id * 6 + side] = atlas.get(TEXTURE_NOTFOUND);
|
||||
sideregions[(def.rt.id * 6 + side) * MAX_VARIANTS + variantIndex] = atlas.get(TEXTURE_NOTFOUND);
|
||||
}
|
||||
}
|
||||
if (def.model.type == BlockModelType::CUSTOM) {
|
||||
auto model = assets.require<model::Model>(def.model.name);
|
||||
if (variant.model.type == BlockModelType::CUSTOM) {
|
||||
auto model = assets.require<model::Model>(variant.model.name);
|
||||
|
||||
for (auto& mesh : model.meshes) {
|
||||
size_t pos = mesh.texture.find(':');
|
||||
@ -53,15 +62,25 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
|
||||
}
|
||||
}
|
||||
|
||||
void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
|
||||
refresh_variant(assets, def, def.defaults, 0, sideregions, atlas, settings, models);
|
||||
if (def.variants) {
|
||||
const auto& variants = def.variants->variants;
|
||||
for (int i = 1; i < variants.size() - 1; i++) {
|
||||
refresh_variant(assets, def, variants[i], i + 1, sideregions, atlas, settings, models);
|
||||
}
|
||||
def.variants->variants.at(0) = def.defaults;
|
||||
}
|
||||
}
|
||||
|
||||
void ContentGfxCache::refresh() {
|
||||
auto indices = content.getIndices();
|
||||
sideregions = std::make_unique<UVRegion[]>(indices->blocks.count() * 6);
|
||||
sideregions = std::make_unique<UVRegion[]>(indices->blocks.count() * 6 * MAX_VARIANTS);
|
||||
const auto& atlas = assets.require<Atlas>("blocks");
|
||||
|
||||
const auto& blocks = indices->blocks.getIterable();
|
||||
for (blockid_t i = 0; i < blocks.size(); i++) {
|
||||
auto def = blocks[i];
|
||||
refresh(*def, atlas);
|
||||
refresh(*blocks[i], atlas);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@ class Block;
|
||||
struct UVRegion;
|
||||
struct GraphicsSettings;
|
||||
|
||||
inline constexpr int MAX_VARIANTS = 16;
|
||||
|
||||
class ContentGfxCache {
|
||||
const Content& content;
|
||||
const Assets& assets;
|
||||
@ -32,8 +34,8 @@ public:
|
||||
);
|
||||
~ContentGfxCache();
|
||||
|
||||
inline const UVRegion& getRegion(blockid_t id, int side) const {
|
||||
return sideregions[id * 6 + side];
|
||||
inline const UVRegion& getRegion(blockid_t id, uint8_t variant, int side) const {
|
||||
return sideregions[(id * 6 + side) * MAX_VARIANTS + variant];
|
||||
}
|
||||
|
||||
const model::Model& getModel(blockid_t id) const;
|
||||
|
||||
@ -16,7 +16,7 @@ namespace model {
|
||||
struct Mesh {
|
||||
std::string texture;
|
||||
std::vector<Vertex> vertices;
|
||||
bool lighting = true;
|
||||
bool shading = true;
|
||||
|
||||
void addPlane(
|
||||
const glm::vec3& pos,
|
||||
@ -54,14 +54,14 @@ namespace model {
|
||||
/// @brief Add mesh to the model
|
||||
/// @param texture texture name
|
||||
/// @return writeable Mesh
|
||||
Mesh& addMesh(const std::string& texture) {
|
||||
Mesh& addMesh(const std::string& texture, bool shading = true) {
|
||||
for (auto& mesh : meshes) {
|
||||
if (mesh.texture == texture) {
|
||||
if (mesh.texture == texture && mesh.shading == shading) {
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
meshes.push_back({texture, {}});
|
||||
return meshes[meshes.size()-1];
|
||||
meshes.push_back({texture, {}, shading});
|
||||
return meshes[meshes.size() - 1];
|
||||
}
|
||||
/// @brief Remove all empty meshes
|
||||
void clean();
|
||||
|
||||
@ -44,7 +44,7 @@ void BlockWrapsRenderer::draw(const BlockWrapper& wrapper) {
|
||||
}
|
||||
if (vox->id != BLOCK_VOID) {
|
||||
const auto& def = level.content.getIndices()->blocks.require(vox->id);
|
||||
switch (def.model.type) {
|
||||
switch (def.getModel(vox->state.userbits).type) {
|
||||
case BlockModelType::BLOCK:
|
||||
batch->cube(
|
||||
glm::vec3(wrapper.position) + glm::vec3(0.5f),
|
||||
|
||||
@ -27,12 +27,12 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
|
||||
){
|
||||
display::clear();
|
||||
blockid_t id = def.rt.id;
|
||||
const UVRegion texfaces[6]{cache.getRegion(id, 0), cache.getRegion(id, 1),
|
||||
cache.getRegion(id, 2), cache.getRegion(id, 3),
|
||||
cache.getRegion(id, 4), cache.getRegion(id, 5)};
|
||||
const UVRegion texfaces[6]{cache.getRegion(id, 0, 0), cache.getRegion(id, 0, 1),
|
||||
cache.getRegion(id, 0, 2), cache.getRegion(id, 0, 3),
|
||||
cache.getRegion(id, 0, 4), cache.getRegion(id, 0, 5)};
|
||||
|
||||
glm::vec3 offset(0.1f, 0.5f, 0.1f);
|
||||
switch (def.model.type) {
|
||||
switch (def.defaults.model.type) {
|
||||
case BlockModelType::NONE:
|
||||
// something went wrong...
|
||||
break;
|
||||
|
||||
@ -292,21 +292,22 @@ static bool is_aligned(const glm::vec3& v, float e = 1e-6f) {
|
||||
}
|
||||
|
||||
void BlocksRenderer::blockCustomModel(
|
||||
const glm::ivec3& icoord, const Block* block, ubyte rotation, bool lights, bool ao
|
||||
const glm::ivec3& icoord, const Block& block, blockstate states, bool lights, bool ao
|
||||
) {
|
||||
const auto& variant = block.getVariant(states.userbits);
|
||||
glm::vec3 X(1, 0, 0);
|
||||
glm::vec3 Y(0, 1, 0);
|
||||
glm::vec3 Z(0, 0, 1);
|
||||
glm::vec3 coord(icoord);
|
||||
if (block->rotatable) {
|
||||
auto& rotations = block->rotations;
|
||||
CoordSystem orient = rotations.variants[rotation];
|
||||
if (block.rotatable) {
|
||||
auto& rotations = block.rotations;
|
||||
CoordSystem orient = rotations.variants[states.rotation];
|
||||
X = orient.axes[0];
|
||||
Y = orient.axes[1];
|
||||
Z = orient.axes[2];
|
||||
}
|
||||
|
||||
const auto& model = cache.getModel(block->rt.id);
|
||||
const auto& model = cache.getModel(block.rt.id);
|
||||
for (const auto& mesh : model.meshes) {
|
||||
if (vertexCount + mesh.vertices.size() >= capacity) {
|
||||
overflow = true;
|
||||
@ -328,7 +329,7 @@ void BlocksRenderer::blockCustomModel(
|
||||
0.5f;
|
||||
vp = vp.x * X + vp.y * Y + vp.z * Z;
|
||||
|
||||
if (!isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), *block) && is_aligned(n)) {
|
||||
if (!isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant) && is_aligned(n)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -341,7 +342,7 @@ void BlocksRenderer::blockCustomModel(
|
||||
const auto& vcoord = vertex.coord - 0.5f;
|
||||
|
||||
glm::vec4 aoColor {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
if (ao) {
|
||||
if (mesh.shading && ao) {
|
||||
auto p = coord + vcoord.x * X + vcoord.y * Y +
|
||||
vcoord.z * Z + r * 0.5f + t * 0.5f + n * 0.5f;
|
||||
aoColor = pickSoftLight(p.x, p.y, p.z, glm::ivec3(r), glm::ivec3(t));
|
||||
@ -350,9 +351,9 @@ void BlocksRenderer::blockCustomModel(
|
||||
coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z,
|
||||
vertex.uv.x,
|
||||
vertex.uv.y,
|
||||
glm::vec4(d, d, d, d) * aoColor,
|
||||
mesh.shading ? (glm::vec4(d, d, d, d) * aoColor) : glm::vec4(1, 1, 1, d),
|
||||
n,
|
||||
0.0f
|
||||
mesh.shading ? 0.0f : 1.0
|
||||
);
|
||||
indexBuffer[indexCount++] = vertexOffset++;
|
||||
}
|
||||
@ -369,6 +370,7 @@ void BlocksRenderer::blockCube(
|
||||
bool lights,
|
||||
bool ao
|
||||
) {
|
||||
const auto& variant = block.getVariant(states.userbits);
|
||||
glm::ivec3 X(1, 0, 0);
|
||||
glm::ivec3 Y(0, 1, 0);
|
||||
glm::ivec3 Z(0, 0, 1);
|
||||
@ -382,41 +384,41 @@ void BlocksRenderer::blockCube(
|
||||
}
|
||||
|
||||
if (ao) {
|
||||
if (isOpen(coord + Z, block)) {
|
||||
if (isOpen(coord + Z, block, variant)) {
|
||||
faceAO(coord, X, Y, Z, texfaces[5], lights);
|
||||
}
|
||||
if (isOpen(coord - Z, block)) {
|
||||
if (isOpen(coord - Z, block, variant)) {
|
||||
faceAO(coord, -X, Y, -Z, texfaces[4], lights);
|
||||
}
|
||||
if (isOpen(coord + Y, block)) {
|
||||
if (isOpen(coord + Y, block, variant)) {
|
||||
faceAO(coord, X, -Z, Y, texfaces[3], lights);
|
||||
}
|
||||
if (isOpen(coord - Y, block)) {
|
||||
if (isOpen(coord - Y, block, variant)) {
|
||||
faceAO(coord, X, Z, -Y, texfaces[2], lights);
|
||||
}
|
||||
if (isOpen(coord + X, block)) {
|
||||
if (isOpen(coord + X, block, variant)) {
|
||||
faceAO(coord, -Z, Y, X, texfaces[1], lights);
|
||||
}
|
||||
if (isOpen(coord - X, block)) {
|
||||
if (isOpen(coord - X, block, variant)) {
|
||||
faceAO(coord, Z, Y, -X, texfaces[0], lights);
|
||||
}
|
||||
} else {
|
||||
if (isOpen(coord + Z, block)) {
|
||||
if (isOpen(coord + Z, block, variant)) {
|
||||
face(coord, X, Y, Z, texfaces[5], pickLight(coord + Z), lights);
|
||||
}
|
||||
if (isOpen(coord - Z, block)) {
|
||||
if (isOpen(coord - Z, block, variant)) {
|
||||
face(coord, -X, Y, -Z, texfaces[4], pickLight(coord - Z), lights);
|
||||
}
|
||||
if (isOpen(coord + Y, block)) {
|
||||
if (isOpen(coord + Y, block, variant)) {
|
||||
face(coord, X, -Z, Y, texfaces[3], pickLight(coord + Y), lights);
|
||||
}
|
||||
if (isOpen(coord - Y, block)) {
|
||||
if (isOpen(coord - Y, block, variant)) {
|
||||
face(coord, X, Z, -Y, texfaces[2], pickLight(coord - Y), lights);
|
||||
}
|
||||
if (isOpen(coord + X, block)) {
|
||||
if (isOpen(coord + X, block, variant)) {
|
||||
face(coord, -Z, Y, X, texfaces[1], pickLight(coord + X), lights);
|
||||
}
|
||||
if (isOpen(coord - X, block)) {
|
||||
if (isOpen(coord - X, block, variant)) {
|
||||
face(coord, Z, Y, -X, texfaces[0], pickLight(coord - X), lights);
|
||||
}
|
||||
}
|
||||
@ -486,21 +488,26 @@ void BlocksRenderer::render(
|
||||
blockid_t id = vox.id;
|
||||
blockstate state = vox.state;
|
||||
const auto& def = *blockDefsCache[id];
|
||||
if (id == 0 || def.drawGroup != drawGroup || state.segment) {
|
||||
const auto& variant = def.getVariant(state.userbits);
|
||||
uint8_t variantId = state.userbits;
|
||||
if (id == 0 || variant.drawGroup != drawGroup || state.segment) {
|
||||
continue;
|
||||
}
|
||||
if (def.translucent) {
|
||||
continue;
|
||||
}
|
||||
const UVRegion texfaces[6] {
|
||||
cache.getRegion(id, 0), cache.getRegion(id, 1),
|
||||
cache.getRegion(id, 2), cache.getRegion(id, 3),
|
||||
cache.getRegion(id, 4), cache.getRegion(id, 5)
|
||||
cache.getRegion(id, variantId, 0),
|
||||
cache.getRegion(id, variantId, 1),
|
||||
cache.getRegion(id, variantId, 2),
|
||||
cache.getRegion(id, variantId, 3),
|
||||
cache.getRegion(id, variantId, 4),
|
||||
cache.getRegion(id, variantId, 5)
|
||||
};
|
||||
int x = i % CHUNK_W;
|
||||
int y = i / (CHUNK_D * CHUNK_W);
|
||||
int z = (i / CHUNK_D) % CHUNK_W;
|
||||
switch (def.model.type) {
|
||||
switch (def.getModel(state.userbits).type) {
|
||||
case BlockModelType::BLOCK:
|
||||
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
||||
def.ambientOcclusion);
|
||||
@ -516,8 +523,13 @@ void BlocksRenderer::render(
|
||||
break;
|
||||
}
|
||||
case BlockModelType::CUSTOM: {
|
||||
blockCustomModel({x, y, z}, &def, vox.state.rotation,
|
||||
!def.shadeless, def.ambientOcclusion);
|
||||
blockCustomModel(
|
||||
{x, y, z},
|
||||
def,
|
||||
vox.state,
|
||||
!def.shadeless,
|
||||
def.ambientOcclusion
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -549,21 +561,26 @@ SortingMeshData BlocksRenderer::renderTranslucent(
|
||||
blockid_t id = vox.id;
|
||||
blockstate state = vox.state;
|
||||
const auto& def = *blockDefsCache[id];
|
||||
if (id == 0 || def.drawGroup != drawGroup || state.segment) {
|
||||
uint8_t variantId = state.userbits;
|
||||
const auto& variant = def.getVariant(variantId);
|
||||
if (id == 0 || variant.drawGroup != drawGroup || state.segment) {
|
||||
continue;
|
||||
}
|
||||
if (!def.translucent) {
|
||||
continue;
|
||||
}
|
||||
const UVRegion texfaces[6] {
|
||||
cache.getRegion(id, 0), cache.getRegion(id, 1),
|
||||
cache.getRegion(id, 2), cache.getRegion(id, 3),
|
||||
cache.getRegion(id, 4), cache.getRegion(id, 5)
|
||||
cache.getRegion(id, variantId, 0),
|
||||
cache.getRegion(id, variantId, 1),
|
||||
cache.getRegion(id, variantId, 2),
|
||||
cache.getRegion(id, variantId, 3),
|
||||
cache.getRegion(id, variantId, 4),
|
||||
cache.getRegion(id, variantId, 5)
|
||||
};
|
||||
int x = i % CHUNK_W;
|
||||
int y = i / (CHUNK_D * CHUNK_W);
|
||||
int z = (i / CHUNK_D) % CHUNK_W;
|
||||
switch (def.model.type) {
|
||||
switch (def.getModel(state.userbits).type) {
|
||||
case BlockModelType::BLOCK:
|
||||
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
||||
def.ambientOcclusion);
|
||||
@ -579,7 +596,7 @@ SortingMeshData BlocksRenderer::renderTranslucent(
|
||||
break;
|
||||
}
|
||||
case BlockModelType::CUSTOM: {
|
||||
blockCustomModel({x, y, z}, &def, vox.state.rotation,
|
||||
blockCustomModel({x, y, z}, def, vox.state,
|
||||
!def.shadeless, def.ambientOcclusion);
|
||||
break;
|
||||
}
|
||||
@ -670,11 +687,12 @@ void BlocksRenderer::build(const Chunk* chunk, const Chunks* chunks) {
|
||||
const voxel& vox = voxels[i];
|
||||
blockid_t id = vox.id;
|
||||
const auto& def = *blockDefsCache[id];
|
||||
const auto& variant = def.getVariant(vox.state.userbits);
|
||||
|
||||
if (beginEnds[def.drawGroup][0] == 0) {
|
||||
beginEnds[def.drawGroup][0] = i+1;
|
||||
if (beginEnds[variant.drawGroup][0] == 0) {
|
||||
beginEnds[variant.drawGroup][0] = i+1;
|
||||
}
|
||||
beginEnds[def.drawGroup][1] = i;
|
||||
beginEnds[variant.drawGroup][1] = i;
|
||||
}
|
||||
cancelled = false;
|
||||
|
||||
|
||||
@ -113,8 +113,8 @@ class BlocksRenderer {
|
||||
);
|
||||
void blockCustomModel(
|
||||
const glm::ivec3& icoord,
|
||||
const Block* block,
|
||||
ubyte rotation,
|
||||
const Block& block,
|
||||
blockstate states,
|
||||
bool lights,
|
||||
bool ao
|
||||
);
|
||||
@ -122,24 +122,26 @@ class BlocksRenderer {
|
||||
bool isOpenForLight(int x, int y, int z) const;
|
||||
|
||||
// Does block allow to see other blocks sides (is it transparent)
|
||||
inline bool isOpen(const glm::ivec3& pos, const Block& def) const {
|
||||
auto id = voxelsBuffer->pickBlockId(
|
||||
inline bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const {
|
||||
auto vox = voxelsBuffer->pickBlock(
|
||||
chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z
|
||||
);
|
||||
if (id == BLOCK_VOID) {
|
||||
if (vox.id == BLOCK_VOID) {
|
||||
return false;
|
||||
}
|
||||
const auto& block = *blockDefsCache[id];
|
||||
if (((block.drawGroup != def.drawGroup) && block.drawGroup) || !block.rt.solid) {
|
||||
const auto& block = *blockDefsCache[vox.id];
|
||||
const auto& blockVariant = block.getVariant(vox.state.userbits);
|
||||
uint8_t otherDrawGroup = blockVariant.drawGroup;
|
||||
if ((otherDrawGroup && (otherDrawGroup != variant.drawGroup)) || !blockVariant.rt.solid) {
|
||||
return true;
|
||||
}
|
||||
if ((def.culling == CullingMode::DISABLED ||
|
||||
(def.culling == CullingMode::OPTIONAL &&
|
||||
if ((variant.culling == CullingMode::DISABLED ||
|
||||
(variant.culling == CullingMode::OPTIONAL &&
|
||||
settings.graphics.denseRender.get())) &&
|
||||
id == def.rt.id) {
|
||||
vox.id == def.rt.id) {
|
||||
return true;
|
||||
}
|
||||
return !id;
|
||||
return !vox.id;
|
||||
}
|
||||
|
||||
glm::vec4 pickLight(int x, int y, int z) const;
|
||||
|
||||
@ -70,7 +70,7 @@ void ModelBatch::draw(
|
||||
const auto& vertexData = mesh.vertices.data();
|
||||
|
||||
glm::vec4 lights(1, 1, 1, 0);
|
||||
if (mesh.lighting) {
|
||||
if (mesh.shading) {
|
||||
glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
gpos += lightsOffset;
|
||||
lights = MainBatch::sampleLight(gpos, chunks, backlight);
|
||||
@ -81,7 +81,7 @@ void ModelBatch::draw(
|
||||
const auto vert = vertexData[i * 3 + j];
|
||||
float d = 1.0f;
|
||||
auto norm = rotation * vert.normal;
|
||||
if (mesh.lighting) {
|
||||
if (mesh.shading) {
|
||||
d = glm::dot(norm, SUN_VECTOR);
|
||||
d = 0.8f + d * 0.2f;
|
||||
}
|
||||
@ -91,7 +91,7 @@ void ModelBatch::draw(
|
||||
lights * d,
|
||||
tint,
|
||||
norm,
|
||||
mesh.lighting ? 0.0f : 1.0f
|
||||
mesh.shading ? 0.0f : 1.0f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@ static debug::Logger logger("models-generator");
|
||||
|
||||
static void configure_textures(
|
||||
model::Model& model,
|
||||
const Block& blockDef,
|
||||
const Assets& assets
|
||||
const Assets& assets,
|
||||
const std::array<std::string, 6>& textureFaces
|
||||
) {
|
||||
for (auto& mesh : model.meshes) {
|
||||
auto& texture = mesh.texture;
|
||||
@ -21,7 +21,7 @@ static void configure_textures(
|
||||
}
|
||||
try {
|
||||
int index = std::stoi(texture.substr(1));
|
||||
texture = "blocks:"+blockDef.textureFaces.at(index);
|
||||
texture = "blocks:" + textureFaces.at(index);
|
||||
} catch (const std::invalid_argument& err) {
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << err.what();
|
||||
@ -48,35 +48,43 @@ static inline UVRegion get_region_for(
|
||||
return texreg.region;
|
||||
}
|
||||
|
||||
void ModelsGenerator::prepare(Content& content, Assets& assets) {
|
||||
for (auto& [name, def] : content.blocks.getDefs()) {
|
||||
if (def->model.type == BlockModelType::CUSTOM) {
|
||||
if (def->model.name.empty()) {
|
||||
assets.store(
|
||||
std::make_unique<model::Model>(
|
||||
loadCustomBlockModel(
|
||||
def->model.customRaw, assets, !def->shadeless
|
||||
)
|
||||
),
|
||||
name + ".model"
|
||||
);
|
||||
def->model.name = def->name + ".model";
|
||||
} else {
|
||||
auto srcModel = assets.get<model::Model>(def->model.name);
|
||||
if (srcModel) {
|
||||
auto model = std::make_unique<model::Model>(*srcModel);
|
||||
for (auto& mesh : model->meshes) {
|
||||
if (mesh.texture.length() && mesh.texture[0] == '$') {
|
||||
int index = std::stoll(mesh.texture.substr(1));
|
||||
mesh.texture = "blocks:" + def->textureFaces[index];
|
||||
}
|
||||
void ModelsGenerator::prepareModel(
|
||||
Assets& assets, const Block& def, Variant& variant, uint8_t variantId
|
||||
) {
|
||||
BlockModel& blockModel = variant.model;
|
||||
if (blockModel.type == BlockModelType::CUSTOM) {
|
||||
std::string modelName = def.name + ".model" + (variantId == 0 ? "" : "$" + std::to_string(variantId));
|
||||
if (blockModel.name.empty()) {
|
||||
assets.store(
|
||||
std::make_unique<model::Model>(
|
||||
loadCustomBlockModel(
|
||||
blockModel.customRaw, assets, !def.shadeless
|
||||
)
|
||||
),
|
||||
modelName
|
||||
);
|
||||
blockModel.name = modelName;
|
||||
} else {
|
||||
auto srcModel = assets.get<model::Model>(blockModel.name);
|
||||
if (srcModel) {
|
||||
auto model = std::make_unique<model::Model>(*srcModel);
|
||||
for (auto& mesh : model->meshes) {
|
||||
if (mesh.texture.length() && mesh.texture[0] == '$') {
|
||||
int index = std::stoll(mesh.texture.substr(1));
|
||||
mesh.texture = "blocks:" + variant.textureFaces[index];
|
||||
}
|
||||
def->model.name = name + ".model";
|
||||
assets.store(std::move(model), def->model.name);
|
||||
}
|
||||
blockModel.name = modelName;
|
||||
assets.store(std::move(model), blockModel.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelsGenerator::prepare(Content& content, Assets& assets) {
|
||||
for (auto& [name, def] : content.blocks.getDefs()) {
|
||||
prepareModel(assets, *def, def->defaults, 0);
|
||||
}
|
||||
for (auto& [name, def] : content.items.getDefs()) {
|
||||
assets.store(
|
||||
std::make_unique<model::Model>(
|
||||
@ -97,7 +105,7 @@ model::Model ModelsGenerator::fromCustom(
|
||||
auto model = model::Model();
|
||||
for (size_t i = 0; i < modelBoxes.size(); i++) {
|
||||
auto& mesh = model.addMesh("blocks:");
|
||||
mesh.lighting = lighting;
|
||||
mesh.shading = lighting;
|
||||
UVRegion boxtexfaces[6] = {
|
||||
get_region_for(modelTextures[i * 6 + 5], assets),
|
||||
get_region_for(modelTextures[i * 6 + 4], assets),
|
||||
@ -132,7 +140,7 @@ model::Model ModelsGenerator::fromCustom(
|
||||
norm = glm::normalize(norm);
|
||||
|
||||
auto& mesh = model.addMesh(texture);
|
||||
mesh.lighting = lighting;
|
||||
mesh.shading = lighting;
|
||||
|
||||
auto reg = get_region_for(texture, assets);
|
||||
mesh.vertices.push_back({v0, glm::vec2(reg.u1, reg.v1), norm});
|
||||
@ -151,20 +159,22 @@ model::Model ModelsGenerator::generate(
|
||||
if (def.iconType == ItemIconType::BLOCK) {
|
||||
auto model = assets.require<model::Model>("block");
|
||||
const auto& blockDef = content.blocks.require(def.icon);
|
||||
if (blockDef.model.type == BlockModelType::XSPRITE) {
|
||||
const auto& variant = blockDef.defaults;
|
||||
const auto& blockModel = variant.model;
|
||||
if (blockModel.type == BlockModelType::XSPRITE) {
|
||||
return create_flat_model(
|
||||
"blocks:" + blockDef.textureFaces.at(0), assets
|
||||
"blocks:" + blockDef.defaults.textureFaces.at(0), assets
|
||||
);
|
||||
} else if (blockDef.model.type == BlockModelType::CUSTOM) {
|
||||
model = assets.require<model::Model>(blockDef.model.name);
|
||||
} else if (blockModel.type == BlockModelType::CUSTOM) {
|
||||
model = assets.require<model::Model>(blockModel.name);
|
||||
for (auto& mesh : model.meshes) {
|
||||
mesh.scale(glm::vec3(0.2f));
|
||||
}
|
||||
return model;
|
||||
}
|
||||
for (auto& mesh : model.meshes) {
|
||||
mesh.lighting = !blockDef.shadeless;
|
||||
switch (blockDef.model.type) {
|
||||
mesh.shading = !blockDef.shadeless;
|
||||
switch (blockModel.type) {
|
||||
case BlockModelType::AABB: {
|
||||
glm::vec3 size = blockDef.hitboxes.at(0).size();
|
||||
float m = glm::max(size.x, glm::max(size.y, size.z));
|
||||
@ -176,7 +186,7 @@ model::Model ModelsGenerator::generate(
|
||||
}
|
||||
mesh.scale(glm::vec3(0.2f));
|
||||
}
|
||||
configure_textures(model, blockDef, assets);
|
||||
configure_textures(model, assets, blockDef.defaults.textureFaces);
|
||||
return model;
|
||||
} else if (def.iconType == ItemIconType::SPRITE) {
|
||||
return create_flat_model(def.icon, assets);
|
||||
|
||||
@ -8,6 +8,7 @@ struct ItemDef;
|
||||
class Assets;
|
||||
class Content;
|
||||
class Block;
|
||||
struct Variant;
|
||||
|
||||
class ModelsGenerator {
|
||||
public:
|
||||
@ -28,4 +29,8 @@ public:
|
||||
static model::Model loadCustomBlockModel(
|
||||
const dv::value& primitives, const Assets& assets, bool lighting
|
||||
);
|
||||
|
||||
static void prepareModel(
|
||||
Assets& assets, const Block& def, Variant& variant, uint8_t variantId
|
||||
);
|
||||
};
|
||||
|
||||
@ -245,8 +245,25 @@ static int l_get_user_bits(lua::State* L) {
|
||||
}
|
||||
}
|
||||
uint mask = ((1 << bits) - 1) << offset;
|
||||
uint data = (blockstate2int(vox->state) & mask) >> offset;
|
||||
return lua::pushinteger(L, data);
|
||||
return lua::pushinteger(L, (blockstate2int(vox->state) & mask) >> offset);
|
||||
}
|
||||
|
||||
static int l_get_variant(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);
|
||||
if (vox == nullptr) {
|
||||
return lua::pushinteger(L, 0);
|
||||
}
|
||||
const auto& def = content->getIndices()->blocks.require(vox->id);
|
||||
if (def.variants == nullptr) {
|
||||
return lua::pushinteger(L, 0);
|
||||
}
|
||||
return lua::pushinteger(
|
||||
L, (vox->state.userbits >> def.variants->offset) & def.variants->mask
|
||||
);
|
||||
}
|
||||
|
||||
static int l_set_user_bits(lua::State* L) {
|
||||
@ -282,6 +299,43 @@ static int l_set_user_bits(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_variant(lua::State* L) {
|
||||
auto& chunks = *level->chunks;
|
||||
auto x = lua::tointeger(L, 1);
|
||||
auto y = lua::tointeger(L, 2);
|
||||
auto z = lua::tointeger(L, 3);
|
||||
|
||||
int cx = floordiv<CHUNK_W>(x);
|
||||
int cz = floordiv<CHUNK_D>(z);
|
||||
auto chunk = blocks_agent::get_chunk(chunks, cx, cz);
|
||||
if (chunk == nullptr || y < 0 || y >= CHUNK_H) {
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
|
||||
if (def.variants == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto offset = def.variants->offset;
|
||||
auto mask = def.variants->mask;
|
||||
auto value = (lua::tointeger(L, 4) << offset) & mask;
|
||||
|
||||
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);
|
||||
if (vox == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
vox->state.userbits = (vox->state.userbits & (~mask)) | value;
|
||||
chunk->setModifiedAndUnsaved();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_replaceable_at(lua::State* L) {
|
||||
auto x = lua::tointeger(L, 1);
|
||||
auto y = lua::tointeger(L, 2);
|
||||
@ -302,7 +356,7 @@ static int l_get_textures(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
lua::createtable(L, 6, 0);
|
||||
for (size_t i = 0; i < 6; i++) {
|
||||
lua::pushstring(L, def->textureFaces[i]);
|
||||
lua::pushstring(L, def->defaults.textureFaces[i]); // TODO: variant argument
|
||||
lua::rawseti(L, i + 1);
|
||||
}
|
||||
return 1;
|
||||
@ -312,7 +366,8 @@ static int l_get_textures(lua::State* L) {
|
||||
|
||||
static int l_get_model(lua::State* L) {
|
||||
if (auto def = require_block(L)) {
|
||||
return lua::pushlstring(L, BlockModelTypeMeta.getName(def->model.type));
|
||||
// TODO: variant argument
|
||||
return lua::pushlstring(L, BlockModelTypeMeta.getName(def->defaults.model.type));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -652,6 +707,8 @@ const luaL_Reg blocklib[] = {
|
||||
{"set_rotation", lua::wrap<l_set_rotation>},
|
||||
{"get_user_bits", lua::wrap<l_get_user_bits>},
|
||||
{"set_user_bits", lua::wrap<l_set_user_bits>},
|
||||
{"get_variant", lua::wrap<l_get_variant>},
|
||||
{"set_variant", lua::wrap<l_set_variant>},
|
||||
{"is_extended", lua::wrap<l_is_extended>},
|
||||
{"get_size", lua::wrap<l_get_size>},
|
||||
{"is_segment", lua::wrap<l_is_segment>},
|
||||
|
||||
@ -94,6 +94,22 @@ namespace util {
|
||||
bool full() const {
|
||||
return size_ == capacity;
|
||||
}
|
||||
|
||||
auto begin() {
|
||||
return data_;
|
||||
}
|
||||
|
||||
auto end() {
|
||||
return data_ + size_;
|
||||
}
|
||||
|
||||
auto begin() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
auto end() const {
|
||||
return data_ + size_;
|
||||
}
|
||||
private:
|
||||
T data_[capacity];
|
||||
int size_;
|
||||
|
||||
@ -108,34 +108,33 @@ const BlockRotProfile BlockRotProfile::STAIRS {
|
||||
};
|
||||
|
||||
Block::Block(const std::string& name)
|
||||
: name(name),
|
||||
caption(util::id_to_caption(name)),
|
||||
textureFaces {
|
||||
TEXTURE_NOTFOUND,
|
||||
TEXTURE_NOTFOUND,
|
||||
TEXTURE_NOTFOUND,
|
||||
TEXTURE_NOTFOUND,
|
||||
TEXTURE_NOTFOUND,
|
||||
TEXTURE_NOTFOUND
|
||||
} {
|
||||
: name(name), caption(util::id_to_caption(name)) {
|
||||
for (int i = 0; i < defaults.textureFaces.size(); i++) {
|
||||
defaults.textureFaces[i] = TEXTURE_NOTFOUND;
|
||||
}
|
||||
}
|
||||
|
||||
Block::~Block() {}
|
||||
Block::~Block() = default;
|
||||
|
||||
Block::Block(std::string name, const std::string& texture)
|
||||
: name(std::move(name)),
|
||||
textureFaces {texture, texture, texture, texture, texture, texture} {
|
||||
: name(std::move(name)) {
|
||||
for (int i = 0; i < defaults.textureFaces.size(); i++) {
|
||||
defaults.textureFaces[i] = TEXTURE_NOTFOUND;
|
||||
}
|
||||
}
|
||||
|
||||
void Block::cloneTo(Block& dst) {
|
||||
dst.caption = caption;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
dst.textureFaces[i] = textureFaces[i];
|
||||
dst.defaults = defaults;
|
||||
}
|
||||
dst.defaults = defaults;
|
||||
if (variants) {
|
||||
dst.variants = std::make_unique<Variants>(*variants);
|
||||
}
|
||||
dst.material = material;
|
||||
std::copy(&emission[0], &emission[3], dst.emission);
|
||||
dst.size = size;
|
||||
dst.model = model;
|
||||
dst.drawGroup = drawGroup;
|
||||
dst.lightPassing = lightPassing;
|
||||
dst.skyLightPassing = skyLightPassing;
|
||||
dst.shadeless = shadeless;
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "maths/aabb.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/EnumMetadata.hpp"
|
||||
#include "util/stack_vector.hpp"
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
struct ParticlesPreset;
|
||||
@ -33,6 +34,8 @@ inline constexpr uint BLOCK_AABB_GRID = 16;
|
||||
|
||||
inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240;
|
||||
|
||||
inline constexpr int BLOCK_MAX_VARIANTS = 16;
|
||||
|
||||
inline std::string DEFAULT_MATERIAL = "base:stone";
|
||||
|
||||
struct BlockFuncsSet {
|
||||
@ -141,6 +144,29 @@ struct BlockMaterial : Serializable {
|
||||
void deserialize(const dv::value& src) override;
|
||||
};
|
||||
|
||||
struct Variant {
|
||||
/// @brief Block model
|
||||
BlockModel model {};
|
||||
/// @brief Textures set applied to block sides
|
||||
std::array<std::string, 6> textureFaces; // -x,x, -y,y, -z,z
|
||||
/// @brief Culling mode
|
||||
CullingMode culling = CullingMode::DEFAULT;
|
||||
/// @brief Influences visible block sides for transparent blocks
|
||||
uint8_t drawGroup = 0;
|
||||
|
||||
struct {
|
||||
/// @brief is the block completely opaque for render
|
||||
bool solid = true;
|
||||
} rt;
|
||||
};
|
||||
|
||||
struct Variants {
|
||||
uint8_t offset;
|
||||
uint8_t mask;
|
||||
/// First variant is copy of Block::defaults
|
||||
util::stack_vector<Variant, BLOCK_MAX_VARIANTS> variants {};
|
||||
};
|
||||
|
||||
/// @brief Block properties definition
|
||||
class Block {
|
||||
public:
|
||||
@ -149,8 +175,7 @@ public:
|
||||
|
||||
std::string caption;
|
||||
|
||||
/// @brief Textures set applied to block sides
|
||||
std::array<std::string, 6> textureFaces; // -x,x, -y,y, -z,z
|
||||
Variant defaults {};
|
||||
|
||||
dv::value properties = nullptr;
|
||||
|
||||
@ -163,15 +188,6 @@ public:
|
||||
|
||||
glm::i8vec3 size {1, 1, 1};
|
||||
|
||||
/// @brief Influences visible block sides for transparent blocks
|
||||
uint8_t drawGroup = 0;
|
||||
|
||||
/// @brief Block model
|
||||
BlockModel model {};
|
||||
|
||||
/// @brief Culling mode
|
||||
CullingMode culling = CullingMode::DEFAULT;
|
||||
|
||||
/// @brief Does the block passing lights into itself
|
||||
bool lightPassing = false;
|
||||
|
||||
@ -243,12 +259,14 @@ public:
|
||||
|
||||
std::unique_ptr<ParticlesPreset> particles;
|
||||
|
||||
std::unique_ptr<Variants> variants;
|
||||
|
||||
/// @brief Runtime indices (content indexing results)
|
||||
struct {
|
||||
/// @brief block runtime integer id
|
||||
blockid_t id;
|
||||
|
||||
/// @brief is the block completely opaque for render and raycast
|
||||
/// @brief is the block completely opaque for raycast
|
||||
bool solid = true;
|
||||
|
||||
/// @brief does the block emit any lights
|
||||
@ -276,6 +294,18 @@ public:
|
||||
|
||||
void cloneTo(Block& dst);
|
||||
|
||||
constexpr const Variant& getVariant(uint8_t userbits) const {
|
||||
if (userbits == 0 || variants == nullptr)
|
||||
return defaults;
|
||||
return variants->variants[
|
||||
(userbits >> variants->offset) & variants->mask
|
||||
];
|
||||
}
|
||||
|
||||
constexpr const BlockModel& getModel(uint8_t bits) const {
|
||||
return getVariant(bits).model;
|
||||
}
|
||||
|
||||
static bool isReservedBlockField(std::string_view view);
|
||||
};
|
||||
|
||||
|
||||
@ -56,6 +56,15 @@ public:
|
||||
return voxels[vox_index(bx - x, by - y, bz - z, w, d)].id;
|
||||
}
|
||||
|
||||
|
||||
inline voxel pickBlock(int bx, int by, int bz) const {
|
||||
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
|
||||
bz >= z + d) {
|
||||
return {BLOCK_VOID, {}};
|
||||
}
|
||||
return voxels[vox_index(bx - x, by - y, bz - z, w, d)];
|
||||
}
|
||||
|
||||
inline light_t pickLight(int bx, int by, int bz) const {
|
||||
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
|
||||
bz >= z + d) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user