add variants (WIP)

This commit is contained in:
MihailRis 2025-07-13 21:28:57 +03:00
parent 01181b6403
commit 5478d121f0
16 changed files with 275 additions and 177 deletions

View File

@ -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) { void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
loader.processPreloadConfigs(content); loader.processPreloadConfigs(content);
if (content) { if (content) {
@ -261,13 +272,11 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
} }
} }
for (const auto& [_, def] : content->blocks.getDefs()) { for (const auto& [_, def] : content->blocks.getDefs()) {
if (!def->model.name.empty() && add_variant(loader, def->defaults);
def->model.name.find(':') == std::string::npos) { if (def->variants) {
loader.add( for (const auto& variant : def->variants->variants) {
AssetType::MODEL, add_variant(loader, variant);
MODELS_FOLDER + "/" + def->model.name, }
def->model.name
);
} }
} }
for (const auto& [_, def] : content->items.getDefs()) { for (const auto& [_, def] : content->items.getDefs()) {

View File

@ -28,7 +28,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
// Generating runtime info // Generating runtime info
def.rt.id = blockDefsIndices.size(); def.rt.id = blockDefsIndices.size();
def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission); def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission);
def.rt.solid = def.model.type == BlockModelType::BLOCK; def.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; // FIXME
def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1;
const float EPSILON = 0.01f; const float EPSILON = 0.01f;
@ -50,7 +50,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
} }
blockDefsIndices.push_back(&def); blockDefsIndices.push_back(&def);
groups->insert(def.drawGroup); groups->insert(def.defaults.drawGroup); // FIXME
} }
std::vector<ItemDef*> itemDefsIndices; std::vector<ItemDef*> itemDefsIndices;

View File

@ -40,6 +40,49 @@ static void perform_user_block_fields(
layout = StructLayout::create(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( template<> void ContentUnitLoader<Block>::loadUnit(
Block& def, const std::string& name, const io::path& file Block& def, const std::string& name, const io::path& file
) { ) {
@ -72,44 +115,7 @@ template<> void ContentUnitLoader<Block>::loadUnit(
root.at("caption").get(def.caption); root.at("caption").get(def.caption);
// block texturing load_variant(def.defaults, root, name);
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();
}
}
// 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"
);
}
}
} 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); root.at("material").get(def.material);
@ -176,9 +182,10 @@ template<> void ContentUnitLoader<Block>::loadUnit(
"block " + util::quote(def.name) + ": invalid block size" "block " + util::quote(def.name) + ": invalid block size"
); );
} }
if (model.type == BlockModelType::BLOCK && // FIXME
if (def.defaults.model.type == BlockModelType::BLOCK &&
(def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) { (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)}; def.hitboxes = {AABB(def.size)};
} }
} }
@ -194,7 +201,6 @@ template<> void ContentUnitLoader<Block>::loadUnit(
root.at("selectable").get(def.selectable); root.at("selectable").get(def.selectable);
root.at("grounded").get(def.grounded); root.at("grounded").get(def.grounded);
root.at("hidden").get(def.hidden); root.at("hidden").get(def.hidden);
root.at("draw-group").get(def.drawGroup);
root.at("picking-item").get(def.pickingItem); root.at("picking-item").get(def.pickingItem);
root.at("surface-replacement").get(def.surfaceReplacement); root.at("surface-replacement").get(def.surfaceReplacement);
root.at("script-name").get(def.scriptName); root.at("script-name").get(def.scriptName);

View File

@ -14,12 +14,12 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
{ {
Block& block = builder.blocks.create(CORE_AIR); Block& block = builder.blocks.create(CORE_AIR);
block.replaceable = true; block.replaceable = true;
block.drawGroup = 1;
block.lightPassing = true; block.lightPassing = true;
block.skyLightPassing = true; block.skyLightPassing = true;
block.obstacle = false; block.obstacle = false;
block.selectable = false; block.selectable = false;
block.model.type = BlockModelType::NONE; block.defaults.drawGroup = 1;
block.defaults.model.type = BlockModelType::NONE;
block.pickingItem = CORE_EMPTY; block.pickingItem = CORE_EMPTY;
} }
{ {
@ -37,7 +37,7 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
{ {
Block& block = builder.blocks.create(CORE_OBSTACLE); Block& block = builder.blocks.create(CORE_OBSTACLE);
for (uint i = 0; i < 6; i++) { for (uint i = 0; i < 6; i++) {
block.textureFaces[i] = "obstacle"; block.defaults.textureFaces[i] = "obstacle";
} }
block.hitboxes = {AABB()}; block.hitboxes = {AABB()};
block.breakable = false; block.breakable = false;
@ -50,9 +50,9 @@ void corecontent::setup(Input& input, ContentBuilder& builder) {
{ {
Block& block = builder.blocks.create(CORE_STRUCT_AIR); Block& block = builder.blocks.create(CORE_STRUCT_AIR);
for (uint i = 0; i < 6; i++) { 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.skyLightPassing = true;
block.lightPassing = true; block.lightPassing = true;
block.hitboxes = {AABB()}; block.hitboxes = {AABB()};

View File

@ -22,21 +22,30 @@ ContentGfxCache::ContentGfxCache(
refresh(); 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++) { for (uint side = 0; side < 6; side++) {
std::string tex = def.textureFaces[side]; std::string tex = variant.textureFaces[side];
if (def.culling == CullingMode::OPTIONAL && if (variant.culling == CullingMode::OPTIONAL &&
!settings.denseRender.get() && atlas.has(tex + "_opaque")) { !settings.denseRender.get() && atlas.has(tex + "_opaque")) {
tex = tex + "_opaque"; tex = tex + "_opaque";
} }
if (atlas.has(tex)) { 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)) { } 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) { if (variant.model.type == BlockModelType::CUSTOM) {
auto model = assets.require<model::Model>(def.model.name); auto model = assets.require<model::Model>(variant.model.name);
for (auto& mesh : model.meshes) { for (auto& mesh : model.meshes) {
size_t pos = mesh.texture.find(':'); size_t pos = mesh.texture.find(':');
@ -53,9 +62,19 @@ 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 = 0; i < variants.size(); i++) {
refresh_variant(assets, def, variants[i], i + 1, sideregions, atlas, settings, models);
}
}
}
void ContentGfxCache::refresh() { void ContentGfxCache::refresh() {
auto indices = content.getIndices(); 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& atlas = assets.require<Atlas>("blocks");
const auto& blocks = indices->blocks.getIterable(); const auto& blocks = indices->blocks.getIterable();

View File

@ -16,6 +16,8 @@ class Block;
struct UVRegion; struct UVRegion;
struct GraphicsSettings; struct GraphicsSettings;
inline constexpr int MAX_VARIANTS = 16;
class ContentGfxCache { class ContentGfxCache {
const Content& content; const Content& content;
const Assets& assets; const Assets& assets;
@ -32,8 +34,8 @@ public:
); );
~ContentGfxCache(); ~ContentGfxCache();
inline const UVRegion& getRegion(blockid_t id, int side) const { inline const UVRegion& getRegion(blockid_t id, uint8_t variant, int side) const {
return sideregions[id * 6 + side]; return sideregions[(id * 6 + side) * MAX_VARIANTS + variant];
} }
const model::Model& getModel(blockid_t id) const; const model::Model& getModel(blockid_t id) const;

View File

@ -44,7 +44,7 @@ void BlockWrapsRenderer::draw(const BlockWrapper& wrapper) {
} }
if (vox->id != BLOCK_VOID) { if (vox->id != BLOCK_VOID) {
const auto& def = level.content.getIndices()->blocks.require(vox->id); const auto& def = level.content.getIndices()->blocks.require(vox->id);
switch (def.model.type) { switch (def.getModel(vox->state.userbits).type) {
case BlockModelType::BLOCK: case BlockModelType::BLOCK:
batch->cube( batch->cube(
glm::vec3(wrapper.position) + glm::vec3(0.5f), glm::vec3(wrapper.position) + glm::vec3(0.5f),

View File

@ -27,12 +27,12 @@ std::unique_ptr<ImageData> BlocksPreview::draw(
){ ){
display::clear(); display::clear();
blockid_t id = def.rt.id; blockid_t id = def.rt.id;
const UVRegion texfaces[6]{cache.getRegion(id, 0), cache.getRegion(id, 1), const UVRegion texfaces[6]{cache.getRegion(id, 0, 0), cache.getRegion(id, 0, 1),
cache.getRegion(id, 2), cache.getRegion(id, 3), cache.getRegion(id, 0, 2), cache.getRegion(id, 0, 3),
cache.getRegion(id, 4), cache.getRegion(id, 5)}; cache.getRegion(id, 0, 4), cache.getRegion(id, 0, 5)};
glm::vec3 offset(0.1f, 0.5f, 0.1f); glm::vec3 offset(0.1f, 0.5f, 0.1f);
switch (def.model.type) { switch (def.defaults.model.type) {
case BlockModelType::NONE: case BlockModelType::NONE:
// something went wrong... // something went wrong...
break; break;

View File

@ -292,21 +292,22 @@ static bool is_aligned(const glm::vec3& v, float e = 1e-6f) {
} }
void BlocksRenderer::blockCustomModel( 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 X(1, 0, 0);
glm::vec3 Y(0, 1, 0); glm::vec3 Y(0, 1, 0);
glm::vec3 Z(0, 0, 1); glm::vec3 Z(0, 0, 1);
glm::vec3 coord(icoord); glm::vec3 coord(icoord);
if (block->rotatable) { if (block.rotatable) {
auto& rotations = block->rotations; auto& rotations = block.rotations;
CoordSystem orient = rotations.variants[rotation]; CoordSystem orient = rotations.variants[states.rotation];
X = orient.axes[0]; X = orient.axes[0];
Y = orient.axes[1]; Y = orient.axes[1];
Z = orient.axes[2]; 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) { for (const auto& mesh : model.meshes) {
if (vertexCount + mesh.vertices.size() >= capacity) { if (vertexCount + mesh.vertices.size() >= capacity) {
overflow = true; overflow = true;
@ -328,7 +329,7 @@ void BlocksRenderer::blockCustomModel(
0.5f; 0.5f;
vp = vp.x * X + vp.y * Y + vp.z * Z; 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; continue;
} }
@ -369,6 +370,7 @@ void BlocksRenderer::blockCube(
bool lights, bool lights,
bool ao bool ao
) { ) {
const auto& variant = block.getVariant(states.userbits);
glm::ivec3 X(1, 0, 0); glm::ivec3 X(1, 0, 0);
glm::ivec3 Y(0, 1, 0); glm::ivec3 Y(0, 1, 0);
glm::ivec3 Z(0, 0, 1); glm::ivec3 Z(0, 0, 1);
@ -382,41 +384,41 @@ void BlocksRenderer::blockCube(
} }
if (ao) { if (ao) {
if (isOpen(coord + Z, block)) { if (isOpen(coord + Z, block, variant)) {
faceAO(coord, X, Y, Z, texfaces[5], lights); 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); 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); 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); 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); 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); faceAO(coord, Z, Y, -X, texfaces[0], lights);
} }
} else { } else {
if (isOpen(coord + Z, block)) { if (isOpen(coord + Z, block, variant)) {
face(coord, X, Y, Z, texfaces[5], pickLight(coord + Z), lights); 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); 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); 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); 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); 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); face(coord, Z, Y, -X, texfaces[0], pickLight(coord - X), lights);
} }
} }
@ -486,21 +488,26 @@ void BlocksRenderer::render(
blockid_t id = vox.id; blockid_t id = vox.id;
blockstate state = vox.state; blockstate state = vox.state;
const auto& def = *blockDefsCache[id]; 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; continue;
} }
if (def.translucent) { if (def.translucent) {
continue; continue;
} }
const UVRegion texfaces[6] { const UVRegion texfaces[6] {
cache.getRegion(id, 0), cache.getRegion(id, 1), cache.getRegion(id, variantId, 0),
cache.getRegion(id, 2), cache.getRegion(id, 3), cache.getRegion(id, variantId, 1),
cache.getRegion(id, 4), cache.getRegion(id, 5) 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 x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W); int y = i / (CHUNK_D * CHUNK_W);
int z = (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: case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion); def.ambientOcclusion);
@ -516,8 +523,13 @@ void BlocksRenderer::render(
break; break;
} }
case BlockModelType::CUSTOM: { case BlockModelType::CUSTOM: {
blockCustomModel({x, y, z}, &def, vox.state.rotation, blockCustomModel(
!def.shadeless, def.ambientOcclusion); {x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion
);
break; break;
} }
default: default:
@ -549,21 +561,26 @@ SortingMeshData BlocksRenderer::renderTranslucent(
blockid_t id = vox.id; blockid_t id = vox.id;
blockstate state = vox.state; blockstate state = vox.state;
const auto& def = *blockDefsCache[id]; 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; continue;
} }
if (!def.translucent) { if (!def.translucent) {
continue; continue;
} }
const UVRegion texfaces[6] { const UVRegion texfaces[6] {
cache.getRegion(id, 0), cache.getRegion(id, 1), cache.getRegion(id, variantId, 0),
cache.getRegion(id, 2), cache.getRegion(id, 3), cache.getRegion(id, variantId, 1),
cache.getRegion(id, 4), cache.getRegion(id, 5) 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 x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W); int y = i / (CHUNK_D * CHUNK_W);
int z = (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: case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion); def.ambientOcclusion);
@ -579,7 +596,7 @@ SortingMeshData BlocksRenderer::renderTranslucent(
break; break;
} }
case BlockModelType::CUSTOM: { case BlockModelType::CUSTOM: {
blockCustomModel({x, y, z}, &def, vox.state.rotation, blockCustomModel({x, y, z}, def, vox.state,
!def.shadeless, def.ambientOcclusion); !def.shadeless, def.ambientOcclusion);
break; break;
} }
@ -670,11 +687,12 @@ void BlocksRenderer::build(const Chunk* chunk, const Chunks* chunks) {
const voxel& vox = voxels[i]; const voxel& vox = voxels[i];
blockid_t id = vox.id; blockid_t id = vox.id;
const auto& def = *blockDefsCache[id]; const auto& def = *blockDefsCache[id];
const auto& variant = def.getVariant(vox.state.userbits);
if (beginEnds[def.drawGroup][0] == 0) { if (beginEnds[variant.drawGroup][0] == 0) {
beginEnds[def.drawGroup][0] = i+1; beginEnds[variant.drawGroup][0] = i+1;
} }
beginEnds[def.drawGroup][1] = i; beginEnds[variant.drawGroup][1] = i;
} }
cancelled = false; cancelled = false;

View File

@ -113,8 +113,8 @@ class BlocksRenderer {
); );
void blockCustomModel( void blockCustomModel(
const glm::ivec3& icoord, const glm::ivec3& icoord,
const Block* block, const Block& block,
ubyte rotation, blockstate states,
bool lights, bool lights,
bool ao bool ao
); );
@ -122,24 +122,25 @@ class BlocksRenderer {
bool isOpenForLight(int x, int y, int z) const; bool isOpenForLight(int x, int y, int z) const;
// Does block allow to see other blocks sides (is it transparent) // Does block allow to see other blocks sides (is it transparent)
inline bool isOpen(const glm::ivec3& pos, const Block& def) const { inline bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const {
auto id = voxelsBuffer->pickBlockId( auto vox = voxelsBuffer->pickBlock(
chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z 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; return false;
} }
const auto& block = *blockDefsCache[id]; const auto& block = *blockDefsCache[vox.id];
if (((block.drawGroup != def.drawGroup) && block.drawGroup) || !block.rt.solid) { uint8_t otherDrawGroup = block.getVariant(vox.state.userbits).drawGroup;
if ((otherDrawGroup && (otherDrawGroup != variant.drawGroup)) || !block.rt.solid) {
return true; return true;
} }
if ((def.culling == CullingMode::DISABLED || if ((variant.culling == CullingMode::DISABLED ||
(def.culling == CullingMode::OPTIONAL && (variant.culling == CullingMode::OPTIONAL &&
settings.graphics.denseRender.get())) && settings.graphics.denseRender.get())) &&
id == def.rt.id) { vox.id == def.rt.id) {
return true; return true;
} }
return !id; return !vox.id;
} }
glm::vec4 pickLight(int x, int y, int z) const; glm::vec4 pickLight(int x, int y, int z) const;

View File

@ -11,8 +11,8 @@ static debug::Logger logger("models-generator");
static void configure_textures( static void configure_textures(
model::Model& model, model::Model& model,
const Block& blockDef, const Assets& assets,
const Assets& assets const std::array<std::string, 6>& textureFaces
) { ) {
for (auto& mesh : model.meshes) { for (auto& mesh : model.meshes) {
auto& texture = mesh.texture; auto& texture = mesh.texture;
@ -21,7 +21,7 @@ static void configure_textures(
} }
try { try {
int index = std::stoi(texture.substr(1)); 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::invalid_argument& err) {
} catch (const std::runtime_error& err) { } catch (const std::runtime_error& err) {
logger.error() << err.what(); logger.error() << err.what();
@ -48,34 +48,42 @@ static inline UVRegion get_region_for(
return texreg.region; return texreg.region;
} }
void ModelsGenerator::prepare(Content& content, Assets& assets) { void ModelsGenerator::prepareModel(
for (auto& [name, def] : content.blocks.getDefs()) { Assets& assets, const Block& def, Variant& variant, uint8_t variantId
if (def->model.type == BlockModelType::CUSTOM) { ) {
if (def->model.name.empty()) { 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( assets.store(
std::make_unique<model::Model>( std::make_unique<model::Model>(
loadCustomBlockModel( loadCustomBlockModel(
def->model.customRaw, assets, !def->shadeless blockModel.customRaw, assets, !def.shadeless
) )
), ),
name + ".model" modelName
); );
def->model.name = def->name + ".model"; blockModel.name = modelName;
} else { } else {
auto srcModel = assets.get<model::Model>(def->model.name); auto srcModel = assets.get<model::Model>(blockModel.name);
if (srcModel) { if (srcModel) {
auto model = std::make_unique<model::Model>(*srcModel); auto model = std::make_unique<model::Model>(*srcModel);
for (auto& mesh : model->meshes) { for (auto& mesh : model->meshes) {
if (mesh.texture.length() && mesh.texture[0] == '$') { if (mesh.texture.length() && mesh.texture[0] == '$') {
int index = std::stoll(mesh.texture.substr(1)); int index = std::stoll(mesh.texture.substr(1));
mesh.texture = "blocks:" + def->textureFaces[index]; mesh.texture = "blocks:" + variant.textureFaces[index];
} }
} }
def->model.name = name + ".model"; blockModel.name = modelName;
assets.store(std::move(model), def->model.name); 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()) { for (auto& [name, def] : content.items.getDefs()) {
assets.store( assets.store(
@ -151,12 +159,14 @@ model::Model ModelsGenerator::generate(
if (def.iconType == ItemIconType::BLOCK) { if (def.iconType == ItemIconType::BLOCK) {
auto model = assets.require<model::Model>("block"); auto model = assets.require<model::Model>("block");
const auto& blockDef = content.blocks.require(def.icon); 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( return create_flat_model(
"blocks:" + blockDef.textureFaces.at(0), assets "blocks:" + blockDef.defaults.textureFaces.at(0), assets
); );
} else if (blockDef.model.type == BlockModelType::CUSTOM) { } else if (blockModel.type == BlockModelType::CUSTOM) {
model = assets.require<model::Model>(blockDef.model.name); model = assets.require<model::Model>(blockModel.name);
for (auto& mesh : model.meshes) { for (auto& mesh : model.meshes) {
mesh.scale(glm::vec3(0.2f)); mesh.scale(glm::vec3(0.2f));
} }
@ -164,7 +174,7 @@ model::Model ModelsGenerator::generate(
} }
for (auto& mesh : model.meshes) { for (auto& mesh : model.meshes) {
mesh.shading = !blockDef.shadeless; mesh.shading = !blockDef.shadeless;
switch (blockDef.model.type) { switch (blockModel.type) {
case BlockModelType::AABB: { case BlockModelType::AABB: {
glm::vec3 size = blockDef.hitboxes.at(0).size(); glm::vec3 size = blockDef.hitboxes.at(0).size();
float m = glm::max(size.x, glm::max(size.y, size.z)); 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)); mesh.scale(glm::vec3(0.2f));
} }
configure_textures(model, blockDef, assets); configure_textures(model, assets, blockDef.defaults.textureFaces);
return model; return model;
} else if (def.iconType == ItemIconType::SPRITE) { } else if (def.iconType == ItemIconType::SPRITE) {
return create_flat_model(def.icon, assets); return create_flat_model(def.icon, assets);

View File

@ -8,6 +8,7 @@ struct ItemDef;
class Assets; class Assets;
class Content; class Content;
class Block; class Block;
struct Variant;
class ModelsGenerator { class ModelsGenerator {
public: public:
@ -28,4 +29,8 @@ public:
static model::Model loadCustomBlockModel( static model::Model loadCustomBlockModel(
const dv::value& primitives, const Assets& assets, bool lighting const dv::value& primitives, const Assets& assets, bool lighting
); );
static void prepareModel(
Assets& assets, const Block& def, Variant& variant, uint8_t variantId
);
}; };

View File

@ -302,7 +302,7 @@ static int l_get_textures(lua::State* L) {
if (auto def = require_block(L)) { if (auto def = require_block(L)) {
lua::createtable(L, 6, 0); lua::createtable(L, 6, 0);
for (size_t i = 0; i < 6; i++) { for (size_t i = 0; i < 6; i++) {
lua::pushstring(L, def->textureFaces[i]); lua::pushstring(L, def->defaults.textureFaces[i]); // TODO: variant argument
lua::rawseti(L, i + 1); lua::rawseti(L, i + 1);
} }
return 1; return 1;
@ -312,7 +312,8 @@ static int l_get_textures(lua::State* L) {
static int l_get_model(lua::State* L) { static int l_get_model(lua::State* L) {
if (auto def = require_block(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; return 0;
} }

View File

@ -108,34 +108,32 @@ const BlockRotProfile BlockRotProfile::STAIRS {
}; };
Block::Block(const std::string& name) Block::Block(const std::string& name)
: name(name), : name(name), caption(util::id_to_caption(name)) {
caption(util::id_to_caption(name)), for (int i = 0; i < defaults.textureFaces.size(); i++) {
textureFaces { defaults.textureFaces[i] = TEXTURE_NOTFOUND;
TEXTURE_NOTFOUND, }
TEXTURE_NOTFOUND,
TEXTURE_NOTFOUND,
TEXTURE_NOTFOUND,
TEXTURE_NOTFOUND,
TEXTURE_NOTFOUND
} {
} }
Block::~Block() {} Block::~Block() = default;
Block::Block(std::string name, const std::string& texture) Block::Block(std::string name, const std::string& texture)
: name(std::move(name)), : name(std::move(name)) {
textureFaces {texture, texture, texture, texture, texture, texture} { for (int i = 0; i < defaults.textureFaces.size(); i++) {
defaults.textureFaces[i] = TEXTURE_NOTFOUND;
}
} }
void Block::cloneTo(Block& dst) { void Block::cloneTo(Block& dst) {
dst.caption = caption; dst.caption = caption;
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
dst.textureFaces[i] = textureFaces[i]; dst.defaults = defaults;
}
if (variants) {
dst.variants = std::make_unique<Variants>(*variants);
} }
dst.material = material; dst.material = material;
std::copy(&emission[0], &emission[3], dst.emission); std::copy(&emission[0], &emission[3], dst.emission);
dst.size = size; dst.size = size;
dst.model = model;
dst.drawGroup = drawGroup;
dst.lightPassing = lightPassing; dst.lightPassing = lightPassing;
dst.skyLightPassing = skyLightPassing; dst.skyLightPassing = skyLightPassing;
dst.shadeless = shadeless; dst.shadeless = shadeless;

View File

@ -10,6 +10,7 @@
#include "maths/aabb.hpp" #include "maths/aabb.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/EnumMetadata.hpp" #include "util/EnumMetadata.hpp"
#include "util/stack_vector.hpp"
#include "interfaces/Serializable.hpp" #include "interfaces/Serializable.hpp"
struct ParticlesPreset; 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 size_t MAX_USER_BLOCK_FIELDS_SIZE = 240;
inline constexpr int BLOCK_MAX_VARIANTS = 16;
inline std::string DEFAULT_MATERIAL = "base:stone"; inline std::string DEFAULT_MATERIAL = "base:stone";
struct BlockFuncsSet { struct BlockFuncsSet {
@ -141,6 +144,21 @@ struct BlockMaterial : Serializable {
void deserialize(const dv::value& src) override; 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 Variants {
util::stack_vector<Variant, BLOCK_MAX_VARIANTS> variants;
};
/// @brief Block properties definition /// @brief Block properties definition
class Block { class Block {
public: public:
@ -149,8 +167,7 @@ public:
std::string caption; std::string caption;
/// @brief Textures set applied to block sides Variant defaults {};
std::array<std::string, 6> textureFaces; // -x,x, -y,y, -z,z
dv::value properties = nullptr; dv::value properties = nullptr;
@ -163,15 +180,6 @@ public:
glm::i8vec3 size {1, 1, 1}; 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 /// @brief Does the block passing lights into itself
bool lightPassing = false; bool lightPassing = false;
@ -243,6 +251,8 @@ public:
std::unique_ptr<ParticlesPreset> particles; std::unique_ptr<ParticlesPreset> particles;
std::unique_ptr<Variants> variants;
/// @brief Runtime indices (content indexing results) /// @brief Runtime indices (content indexing results)
struct { struct {
/// @brief block runtime integer id /// @brief block runtime integer id
@ -276,6 +286,16 @@ public:
void cloneTo(Block& dst); void cloneTo(Block& dst);
constexpr const Variant& getVariant(uint8_t bits) const {
if (bits == 0 || variants == nullptr)
return defaults;
return variants->variants[(bits - 1) % variants->variants.size()];
}
constexpr const BlockModel& getModel(uint8_t bits) const {
return getVariant(bits).model;
}
static bool isReservedBlockField(std::string_view view); static bool isReservedBlockField(std::string_view view);
}; };

View File

@ -56,6 +56,15 @@ public:
return voxels[vox_index(bx - x, by - y, bz - z, w, d)].id; 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 { inline light_t pickLight(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) { bz >= z + d) {