From 8c6d567c37c9e69bd36e597674f0147e3579ab62 Mon Sep 17 00:00:00 2001 From: Mona <59416038+meowora@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:23:16 +0100 Subject: [PATCH 1/3] feat: bedrock models --- .../catharsis/mixins/ModelManagerMixin.java | 29 +++ .../mixins/armor/HumanoidArmorModelMixin.java | 4 +- .../kotlin/me/owdding/catharsis/Catharsis.kt | 4 + .../features/armor/models/SimpleArmorModel.kt | 2 +- .../features/models/BedrockModels.kt | 53 +++++ .../me/owdding/catharsis/utils/DevUtils.kt | 57 ++++++ .../me/owdding/catharsis/utils/Utils.kt | 17 +- .../utils/extensions/JomlExtensions.kt | 1 + .../utils/geometry/BedrockGeometry.kt | 16 +- .../BedrockArmorGeometryRenderer.kt} | 13 +- .../{ => armor}/BedrockGeometryBaker.kt | 7 +- .../model/BedrockModelGeometryBakery.kt | 189 ++++++++++++++++++ .../model/UnbakedBedrockBlockStateModel.kt | 64 ++++++ .../utils/types/fabric/ModelLoadingPlugin.kt | 2 +- src/main/resources/catharsis.mixins.json | 1 + 15 files changed, 432 insertions(+), 27 deletions(-) create mode 100644 src/main/java/me/owdding/catharsis/mixins/ModelManagerMixin.java create mode 100644 src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt create mode 100644 src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt rename src/main/kotlin/me/owdding/catharsis/utils/geometry/{BedrockGeometryRenderer.kt => armor/BedrockArmorGeometryRenderer.kt} (91%) rename src/main/kotlin/me/owdding/catharsis/utils/geometry/{ => armor}/BedrockGeometryBaker.kt (95%) create mode 100644 src/main/kotlin/me/owdding/catharsis/utils/geometry/model/BedrockModelGeometryBakery.kt create mode 100644 src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt diff --git a/src/main/java/me/owdding/catharsis/mixins/ModelManagerMixin.java b/src/main/java/me/owdding/catharsis/mixins/ModelManagerMixin.java new file mode 100644 index 00000000..a1bc229e --- /dev/null +++ b/src/main/java/me/owdding/catharsis/mixins/ModelManagerMixin.java @@ -0,0 +1,29 @@ +package me.owdding.catharsis.mixins; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import me.owdding.catharsis.features.models.BedrockModels; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.server.packs.resources.PreparableReloadListener; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(ModelManager.class) +public class ModelManagerMixin { + + @WrapMethod(method = "reload") + public CompletableFuture reload( + PreparableReloadListener.SharedState sharedState, + Executor executor, + PreparableReloadListener.PreparationBarrier preparationBarrier, + Executor executor2, + Operation> original + ) { + return BedrockModels.INSTANCE.reload(sharedState, executor, preparationBarrier, executor2).thenCompose(i -> + original.call(sharedState, executor, preparationBarrier, executor2) + ); + } + +} diff --git a/src/main/java/me/owdding/catharsis/mixins/armor/HumanoidArmorModelMixin.java b/src/main/java/me/owdding/catharsis/mixins/armor/HumanoidArmorModelMixin.java index 8490b1ed..a76ddcb3 100644 --- a/src/main/java/me/owdding/catharsis/mixins/armor/HumanoidArmorModelMixin.java +++ b/src/main/java/me/owdding/catharsis/mixins/armor/HumanoidArmorModelMixin.java @@ -5,7 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import me.owdding.catharsis.features.armor.models.ArmorModelState; import me.owdding.catharsis.hooks.armor.LivingEntityRenderStateHook; -import me.owdding.catharsis.utils.geometry.BedrockGeometryRenderer; +import me.owdding.catharsis.utils.geometry.armor.BedrockArmorGeometryRenderer; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.Model; import net.minecraft.client.renderer.RenderType; @@ -48,7 +48,7 @@ public class HumanoidArmorModelMixin { model.setupAnim(state); - BedrockGeometryRenderer.render(renderer.getGeometry(), slot, model, pose, consumer, light, overlay); + BedrockArmorGeometryRenderer.render(renderer.getGeometry(), slot, model, pose, consumer, light, overlay); }); return false; } diff --git a/src/main/kotlin/me/owdding/catharsis/Catharsis.kt b/src/main/kotlin/me/owdding/catharsis/Catharsis.kt index d01f4fed..19d89d95 100644 --- a/src/main/kotlin/me/owdding/catharsis/Catharsis.kt +++ b/src/main/kotlin/me/owdding/catharsis/Catharsis.kt @@ -3,10 +3,13 @@ package me.owdding.catharsis import me.owdding.catharsis.events.BootstrapConditionalPropertiesEvent import me.owdding.catharsis.events.BootstrapNumericPropertiesEvent import me.owdding.catharsis.events.BootstrapSelectPropertiesEvent +import me.owdding.catharsis.generated.CatharsisCodecs import me.owdding.catharsis.generated.CatharsisModules import me.owdding.catharsis.utils.CatharsisLogger +import me.owdding.catharsis.utils.geometry.model.UnbakedBedrockBlockStateModel import me.owdding.ktmodules.Module import net.fabricmc.api.ClientModInitializer +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperties import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties @@ -24,6 +27,7 @@ object Catharsis : ClientModInitializer, CatharsisLogger by CatharsisLogger.auto BootstrapConditionalPropertiesEvent(ConditionalItemModelProperties.ID_MAPPER::put).post(SkyBlockAPI.eventBus) BootstrapNumericPropertiesEvent(RangeSelectItemModelProperties.ID_MAPPER::put).post(SkyBlockAPI.eventBus) BootstrapSelectPropertiesEvent(SelectItemModelProperties.ID_MAPPER::put).post(SkyBlockAPI.eventBus) + CustomUnbakedBlockStateModel.register(id("geo_model"), CatharsisCodecs.getMapCodec()) } fun id(@Pattern("[a-z_0-9\\/.-]+") path: String): ResourceLocation = ResourceLocation.fromNamespaceAndPath("catharsis", path) diff --git a/src/main/kotlin/me/owdding/catharsis/features/armor/models/SimpleArmorModel.kt b/src/main/kotlin/me/owdding/catharsis/features/armor/models/SimpleArmorModel.kt index 9ad1bec8..2dd889cf 100644 --- a/src/main/kotlin/me/owdding/catharsis/features/armor/models/SimpleArmorModel.kt +++ b/src/main/kotlin/me/owdding/catharsis/features/armor/models/SimpleArmorModel.kt @@ -28,7 +28,7 @@ class SimpleArmorModel(private val state: ArmorModelState) : ArmorModel { override fun bake(swapper: RegistryContextSwapper?, resources: TypedResourceManager): ArmorModel { val geometry = resources.getOrLoad(this.model, BedrockGeometry.RESOURCE_PARSER)?.getOrThrow() ?: error("Could not find referenced bedrock geometry $model") - return SimpleArmorModel(ArmorModelState.Bedrock(geometry.bake(), texture)) + return SimpleArmorModel(ArmorModelState.Bedrock(geometry.bakeToArmor(), texture)) } } diff --git a/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt b/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt new file mode 100644 index 00000000..bb9d2265 --- /dev/null +++ b/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt @@ -0,0 +1,53 @@ +package me.owdding.catharsis.features.models + +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import me.owdding.catharsis.Catharsis +import me.owdding.catharsis.utils.extensions.mapBothNotNull +import me.owdding.catharsis.utils.geometry.BedrockGeometry +import me.owdding.catharsis.utils.geometry.model.BedrockModelGeometryBaker +import me.owdding.catharsis.utils.geometry.model.MediumBakedBedrockModelGeometry +import me.owdding.ktmodules.Module +import net.minecraft.resources.FileToIdConverter +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.packs.resources.ResourceManager +import net.minecraft.server.packs.resources.SimplePreparableReloadListener +import net.minecraft.util.profiling.ProfilerFiller +import tech.thatgravyboat.skyblockapi.utils.json.Json.toDataOrThrow + +@Module +object BedrockModels : SimplePreparableReloadListener>() { + private val geoModelConverter = FileToIdConverter("catharsis/geo_models", ".geo.json") + private val logger = Catharsis.featureLogger("BlockReplacements") + private val gson = GsonBuilder().create() + + private val models: MutableMap = mutableMapOf() + + override fun prepare( + resourceManager: ResourceManager, + profiler: ProfilerFiller, + ): Map { + return geoModelConverter.listMatchingResources(resourceManager).mapBothNotNull { (id, resource) -> + geoModelConverter.fileToId(id) to logger.runCatching("Error loading block replacement definition $id") { + resource.openAsReader().use { reader -> + gson.fromJson(reader, JsonElement::class.java).toDataOrThrow(BedrockGeometry.CODEC).first() + } + } + } + } + + override fun apply( + loadedModels: Map, + resourceManager: ResourceManager, + profiler: ProfilerFiller, + ) { + models.clear() + models.putAll(loadedModels.mapValues { BedrockModelGeometryBaker.bake(it.value) }) + } + + fun getModel(location: ResourceLocation) = models[location] + + init { + // Utils.registerClientReloadListener(Catharsis.id("bedrock_models"), this, ResourceReloaderKeys.BEFORE_VANILLA) + } +} diff --git a/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt b/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt new file mode 100644 index 00000000..b341356c --- /dev/null +++ b/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt @@ -0,0 +1,57 @@ +package me.owdding.catharsis.utils + +import me.owdding.catharsis.Catharsis +import me.owdding.catharsis.utils.extensions.sendWithPrefix +import me.owdding.ktmodules.Module +import net.minecraft.network.chat.MutableComponent +import net.minecraft.resources.ResourceLocation +import tech.thatgravyboat.skyblockapi.api.events.base.Subscription +import tech.thatgravyboat.skyblockapi.api.events.misc.RegisterCommandsEvent +import tech.thatgravyboat.skyblockapi.helpers.McClient +import tech.thatgravyboat.skyblockapi.utils.DebugToggle +import tech.thatgravyboat.skyblockapi.utils.DevUtils +import tech.thatgravyboat.skyblockapi.utils.extentions.parseFormattedInt +import java.util.* +import kotlin.io.path.Path +import kotlin.io.path.notExists +import kotlin.io.path.reader + +internal fun debugToggle(path: String, description: String = path): DebugToggle { + return DebugToggle(Catharsis.id(path), description, CatharsisDevUtils) +} +@Module +internal object CatharsisDevUtils : DevUtils() { + override val commandName: String = "sbapi toggle" + override fun send(component: MutableComponent) = component.sendWithPrefix() + val properties: Map = loadFromProperties() + + fun getInt(key: String, default: Int = 0): Int { + return properties[key].parseFormattedInt(default) + } + + fun getBoolean(key: String): Boolean { + return properties[key] == "true" + } + + private fun loadFromProperties(): Map { + val properties = Properties() + val path = System.getProperty("sbapi.property_path")?.let { Path(it) } ?: McClient.config.resolve("catharsis.properties") + if (path.notExists()) return emptyMap() + path.reader(Charsets.UTF_8).use { + properties.load(it) + } + val map = mutableMapOf() + properties.forEach { (key, value) -> + ResourceLocation.tryBySeparator(key.toString(), '@')?.let { + if (value.toString() == "true") { + states[it] = true + } + } + map[key.toString()] = value.toString() + } + return map + } + + @Subscription + fun commandRegister(event: RegisterCommandsEvent) = super.onCommandRegister(event) +} diff --git a/src/main/kotlin/me/owdding/catharsis/utils/Utils.kt b/src/main/kotlin/me/owdding/catharsis/utils/Utils.kt index 0f7164cb..ec4a7449 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/Utils.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/Utils.kt @@ -1,17 +1,11 @@ package me.owdding.catharsis.utils -import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener -import net.fabricmc.fabric.api.resource.ResourceManagerHelper -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.packs.PackType -import net.minecraft.server.packs.resources.PreparableReloadListener -import net.minecraft.server.packs.resources.ResourceManager -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executor - //? >= 1.21.9 import net.fabricmc.fabric.api.resource.v1.ResourceLoader +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.packs.PackType +import net.minecraft.server.packs.resources.PreparableReloadListener object Utils { @@ -46,9 +40,12 @@ object Utils { } *///?} - fun registerClientReloadListener(id: ResourceLocation, listener: PreparableReloadListener) { + fun registerClientReloadListener(id: ResourceLocation, listener: PreparableReloadListener, second: ResourceLocation? = null) { //? >= 1.21.9 { ResourceLoader.get(PackType.CLIENT_RESOURCES).registerReloader(id, listener) + if (second != null) { + ResourceLoader.get(PackType.CLIENT_RESOURCES).addReloaderOrdering(id, second) + } //?} else { /*ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(ReloadListenerWrapper(id, listener)) *///?} diff --git a/src/main/kotlin/me/owdding/catharsis/utils/extensions/JomlExtensions.kt b/src/main/kotlin/me/owdding/catharsis/utils/extensions/JomlExtensions.kt index c3172da9..1d629ba8 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/extensions/JomlExtensions.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/extensions/JomlExtensions.kt @@ -39,3 +39,4 @@ fun Vector3ic.toVec3() = Vec3(toBlockPos()) fun Vec3.toVector3dc(): Vector3dc = toVector3d() fun Vec3.toVector3d(): Vector3d = Vector3d(x, y, z) fun Vector3dc.toVec3() = Vec3(x(), y(), z()) +fun Vector3fc.toVec3() = Vec3(x().toDouble(), y().toDouble(), z().toDouble()) diff --git a/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometry.kt b/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometry.kt index 3d8214a5..33e1c476 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometry.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometry.kt @@ -2,12 +2,16 @@ package me.owdding.catharsis.utils.geometry import com.google.gson.JsonElement import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec import me.owdding.catharsis.generated.CatharsisCodecs import me.owdding.catharsis.utils.TypedResourceParser +import me.owdding.catharsis.utils.geometry.armor.BedrockGeometryBaker import me.owdding.ktcodecs.FieldName import me.owdding.ktcodecs.GenerateCodec import net.minecraft.core.Direction +import org.joml.Vector3f import tech.thatgravyboat.skyblockapi.utils.json.Json.toDataOrThrow +import me.owdding.catharsis.utils.geometry.BakedBedrockGeometry as BakedBedrockArmorGeometry @GenerateCodec data class BedrockGeometry( @@ -15,14 +19,14 @@ data class BedrockGeometry( val bones: List, ) { - fun bake(): BakedBedrockGeometry { + fun bakeToArmor(): BakedBedrockArmorGeometry { return BedrockGeometryBaker.bake(this) } companion object { val RESOURCE_PARSER = TypedResourceParser.of(BedrockGeometry::parseSingle) - private val CODEC = CatharsisCodecs.getCodec() + val CODEC: Codec> = CatharsisCodecs.getCodec() .listOf() .fieldOf("minecraft:geometry") .codec() @@ -48,8 +52,8 @@ data class BedrockGeometryDescription( data class BedrockBone( val name: String, val parent: String?, - val pivot: List = listOf(0f, 0f, 0f), - val rotation: List = listOf(0f, 0f, 0f), + val pivot: Vector3f = Vector3f(), + val rotation: Vector3f = Vector3f(), val mirror: Boolean = false, val inflate: Float = 0f, // debug, the spec has no mention of what its purpose is @@ -64,8 +68,8 @@ data class BedrockBone( data class BedrockCube( val origin: List, val size: List, - val rotation: List = listOf(0f, 0f, 0f), - val pivot: List = listOf(0f, 0f, 0f), + val pivot: Vector3f = Vector3f(), + val rotation: Vector3f = Vector3f(), val inflate: Float?, val mirror: Boolean?, val uv: Either, Map>? diff --git a/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometryRenderer.kt b/src/main/kotlin/me/owdding/catharsis/utils/geometry/armor/BedrockArmorGeometryRenderer.kt similarity index 91% rename from src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometryRenderer.kt rename to src/main/kotlin/me/owdding/catharsis/utils/geometry/armor/BedrockArmorGeometryRenderer.kt index 59f28afc..c964dc85 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometryRenderer.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/geometry/armor/BedrockArmorGeometryRenderer.kt @@ -1,14 +1,18 @@ -package me.owdding.catharsis.utils.geometry +package me.owdding.catharsis.utils.geometry.armor import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.VertexConsumer +import me.owdding.catharsis.utils.debugToggle +import me.owdding.catharsis.utils.geometry.BakedBedrockBone +import me.owdding.catharsis.utils.geometry.BakedBedrockCube +import me.owdding.catharsis.utils.geometry.BakedBedrockGeometry import net.minecraft.client.model.HumanoidModel import net.minecraft.core.Direction import net.minecraft.util.Mth import net.minecraft.world.entity.EquipmentSlot import org.joml.* -private const val DEBUG = false + private const val HEAD_BONE = "head" private const val BODY_BONE = "body" private const val RIGHT_ARM_BONE = "right_arm" @@ -18,7 +22,8 @@ private const val LEFT_LEG_BONE = "left_leg" private const val RIGHT_FOOT_BONE = "right_foot" private const val LEFT_FOOT_BONE = "left_foot" -object BedrockGeometryRenderer { +object BedrockArmorGeometryRenderer { + private val debug by debugToggle("armor_debug", "Enables debug colors :3") @JvmStatic fun render(geometry: BakedBedrockGeometry, slot: EquipmentSlot, model: HumanoidModel<*>, pose: PoseStack.Pose, consumer: VertexConsumer, light: Int, overlay: Int) { @@ -102,7 +107,7 @@ object BedrockGeometryRenderer { for (vertex in quad.vertices) { - if (DEBUG) { + if (debug) { consumer .addVertex(pose, vertex.position.x / 16f, vertex.position.y / 16f, vertex.position.z / 16f) .setColor(quad.direction.color()) diff --git a/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometryBaker.kt b/src/main/kotlin/me/owdding/catharsis/utils/geometry/armor/BedrockGeometryBaker.kt similarity index 95% rename from src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometryBaker.kt rename to src/main/kotlin/me/owdding/catharsis/utils/geometry/armor/BedrockGeometryBaker.kt index 26d4bd48..c7b88498 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/geometry/BedrockGeometryBaker.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/geometry/armor/BedrockGeometryBaker.kt @@ -1,5 +1,6 @@ -package me.owdding.catharsis.utils.geometry +package me.owdding.catharsis.utils.geometry.armor +import me.owdding.catharsis.utils.geometry.* import net.minecraft.core.Direction import org.joml.Vector2f import org.joml.Vector3f @@ -21,8 +22,8 @@ object BedrockGeometryBaker { return BakedBedrockBone( bone.name, bone.parent, - Vector3f(bone.pivot[0], bone.pivot[1], bone.pivot[2]), - Vector3f(bone.rotation[0], bone.rotation[1], bone.rotation[2]), + bone.pivot, + bone.rotation, bone.mirror, bone.inflate, bone.cubes.map { bakeCube(bone, it, description) } diff --git a/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/BedrockModelGeometryBakery.kt b/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/BedrockModelGeometryBakery.kt new file mode 100644 index 00000000..76878736 --- /dev/null +++ b/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/BedrockModelGeometryBakery.kt @@ -0,0 +1,189 @@ +package me.owdding.catharsis.utils.geometry.model + +import com.mojang.blaze3d.vertex.PoseStack +import me.owdding.catharsis.Catharsis +import me.owdding.catharsis.features.models.BedrockModels +import me.owdding.catharsis.utils.geometry.* +import net.minecraft.client.renderer.block.model.BakedQuad +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.core.Direction +import net.minecraft.resources.ResourceLocation +import org.joml.Quaternionf +import org.joml.Vector2f +import org.joml.Vector3f +import kotlin.jvm.optionals.getOrNull + +data class BakedBedrockModelGeometry( + val bones: List, +) + +data class ModelBone( + val base: BedrockBone, + val parent: String? = base.parent, + val children: MutableList = mutableListOf(), +) + +data class MediumBakedQuad( + val data: IntArray, + val direction: Direction, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is MediumBakedQuad) return false + + if (!data.contentEquals(other.data)) return false + if (direction != other.direction) return false + + return true + } + + override fun hashCode(): Int { + var result = data.contentHashCode() + result = 31 * result + direction.hashCode() + return result + } +} + +data class MediumBakedBedrockGeometryBone( + val quads: List, + val bones: List, +) + +data class MediumBakedBedrockModelGeometry( + val bones: List, +) + +object BedrockModelGeometryBaker { + val logger = Catharsis.featureLogger("BedrockModelGeometryBaker") + + fun bakeBones(model: ResourceLocation, atlasSprite: TextureAtlasSprite): BakedBedrockModelGeometry = fry(BedrockModels.getModel(model)!!, atlasSprite) + + fun fry(geometry: MediumBakedBedrockModelGeometry, atlasSprite: TextureAtlasSprite): BakedBedrockModelGeometry { + return BakedBedrockModelGeometry(geometry.bones.map { fryBone(it, atlasSprite) }) + } + + fun fryBone(bone: MediumBakedBedrockGeometryBone, atlasSprite: TextureAtlasSprite): BakedBedrockModelBonePart = BakedBedrockModelBonePart( + bone.quads.map { fryQuad(it, atlasSprite) }, bone.bones.map { fryBone(it, atlasSprite) } + ) + + fun fryQuad(quad: MediumBakedQuad, atlasSprite: TextureAtlasSprite) = BakedQuad(quad.data, 0, quad.direction, atlasSprite, false, 0) + + fun bake(geometry: BedrockGeometry): MediumBakedBedrockModelGeometry { + val parts = geometry.bones.associate { it.name to ModelBone(it) } + parts.values.forEach { part -> part.parent?.let { parts[it]!!.children.add(part) } } + + val rootBones = parts.values.filter { it.parent == null } + + val stack = PoseStack() + val bakedBones = rootBones.map { bakeBone(it, stack, geometry.description) } + + if (!stack.isEmpty) { + logger.warn("Pose stack not empty after baking of $geometry") + } + return MediumBakedBedrockModelGeometry(bakedBones) + } + + private fun bakeBone(modelBone: ModelBone, stack: PoseStack, description: BedrockGeometryDescription): MediumBakedBedrockGeometryBone { + stack.pushPose() + val base = modelBone.base + val pivot = base.pivot + val rotation = base.rotation + stack.rotateAround(Quaternionf().rotateZYX(rotation.z, rotation.y, rotation.x), pivot.x, pivot.y, pivot.z) + + val quads = base.cubes.flatMap { bakeCube(base, it, stack, description) } + val bones = modelBone.children.map { bakeBone(it, stack, description) } + + stack.popPose() + return MediumBakedBedrockGeometryBone(quads, bones) + } + + private fun bakeCube(bone: BedrockBone, cube: BedrockCube, stack: PoseStack, description: BedrockGeometryDescription): List { + val uvs = cube.uv?.right()?.getOrNull() ?: TODO("Boxed UVs are not supported yet.") + stack.pushPose() + + val pivot = cube.pivot + val rotation = cube.rotation + stack.rotateAround(Quaternionf().rotateZYX(rotation.z, rotation.y, rotation.x), pivot.x, pivot.y, pivot.z) + + val inflation = cube.inflate ?: bone.inflate + val mirrored = cube.mirror ?: bone.mirror + + var minX = cube.origin[0] - inflation + val minY = cube.origin[1] - inflation + val minZ = cube.origin[2] - inflation + var maxX = cube.origin[0] + cube.size[0] + inflation + val maxY = cube.origin[1] + cube.size[1] + inflation + val maxZ = cube.origin[2] + cube.size[2] + inflation + + if (mirrored) { + val temp = minX + minX = maxX + maxX = temp + } + + val x0y0z0 = Vector3f(minX, minY, minZ) + val x1y0z0 = Vector3f(maxX, minY, minZ) + val x1y0z1 = Vector3f(maxX, minY, maxZ) + val x0y0z1 = Vector3f(minX, minY, maxZ) + + val x0y1z0 = Vector3f(minX, maxY, minZ) + val x1y1z0 = Vector3f(maxX, maxY, minZ) + val x1y1z1 = Vector3f(maxX, maxY, maxZ) + val x0y1z1 = Vector3f(minX, maxY, maxZ) + + val up = bakeQuad(stack, x0y1z0, x1y1z0, x1y1z1, x0y1z1, uvs, mirrored, Direction.UP, description) + val down = bakeQuad(stack, x0y0z0, x1y0z0, x1y0z1, x0y0z1, uvs, mirrored, Direction.DOWN, description) + + val north = bakeQuad(stack, x0y1z0, x1y1z0, x1y0z0, x0y0z0, uvs, mirrored, Direction.NORTH, description) + val south = bakeQuad(stack, x0y1z1, x1y1z1, x1y0z1, x0y0z1, uvs, mirrored, Direction.SOUTH, description) + + val west = bakeQuad(stack, x0y1z0, x0y1z1, x0y0z1, x0y0z0, uvs, mirrored, Direction.WEST, description) + val east = bakeQuad(stack, x1y1z0, x1y1z1, x1y0z1, x1y0z0, uvs, mirrored, Direction.EAST, description) + + stack.popPose() + return listOf(down, up, north, south, west, east) + } + + private fun bakeQuad( + stack: PoseStack, + p1: Vector3f, p2: Vector3f, p3: Vector3f, p4: Vector3f, + uvs: Map, mirror: Boolean, direction: Direction, description: BedrockGeometryDescription, + ): MediumBakedQuad { + val direction = if (!mirror && direction.axis == Direction.Axis.X) direction.opposite else direction + + val data = IntArray(32) + + val uvOffset = if (direction.axisDirection == Direction.AxisDirection.POSITIVE) 1 else 0 + val uvs = uvs[direction]!! + packVertex(data, 0, p1, bakeUv(uvs, 0 + uvOffset, description)) + packVertex(data, 1, p2, bakeUv(uvs, 1 - uvOffset, description)) + packVertex(data, 2, p3, bakeUv(uvs, 2 + uvOffset, description)) + packVertex(data, 3, p4, bakeUv(uvs, 3 - uvOffset, description)) + return MediumBakedQuad(data, direction) + } + + private fun packVertex( + data: IntArray, + index: Int, + pos: Vector3f, + uv: Vector2f, + ) { + val offset = index * 8 + data[offset + 0] = pos.x.toRawBits() + data[offset + 1] = pos.y.toRawBits() + data[offset + 2] = pos.z.toRawBits() + data[offset + 3] = -1 // color + data[offset + 4] = uv.x.toRawBits() + data[offset + 5] = uv.y.toRawBits() + } + + private fun bakeUv(face: UvFace, index: Int, description: BedrockGeometryDescription): Vector2f { + return when (index % 4) { + 0 -> Vector2f(face.uv[0], face.uv[1]) + 1 -> Vector2f(face.uv[0] + face.uvSize[0], face.uv[1]) + 2 -> Vector2f(face.uv[0] + face.uvSize[0], face.uv[1] + face.uvSize[1]) + 3 -> Vector2f(face.uv[0], face.uv[1] + face.uvSize[1]) + else -> error("Invalid UV index: $index") + }.div(description.textureWidth.toFloat(), description.textureHeight.toFloat()) + } +} diff --git a/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt b/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt new file mode 100644 index 00000000..c41fd5a0 --- /dev/null +++ b/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt @@ -0,0 +1,64 @@ +package me.owdding.catharsis.utils.geometry.model + +import com.mojang.serialization.MapCodec +import me.owdding.catharsis.generated.CatharsisCodecs +import me.owdding.ktcodecs.GenerateCodec +import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel +import net.minecraft.client.renderer.block.model.BakedQuad +import net.minecraft.client.renderer.block.model.BlockModelPart +import net.minecraft.client.renderer.block.model.BlockStateModel +import net.minecraft.client.renderer.texture.TextureAtlas +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.resources.model.Material +import net.minecraft.client.resources.model.ModelBaker +import net.minecraft.client.resources.model.ResolvableModel +import net.minecraft.core.Direction +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.RandomSource + +@GenerateCodec +data class UnbakedBedrockBlockStateModel( + val model: ResourceLocation, + val texture: ResourceLocation, + val particle: ResourceLocation?, +) : CustomUnbakedBlockStateModel { + override fun codec(): MapCodec = CatharsisCodecs.getMapCodec() + + override fun bake(baker: ModelBaker): BlockStateModel { + val texture = baker.sprites().get(Material(TextureAtlas.LOCATION_BLOCKS, texture)) { "Catharsis geo model $model/$texture" } + val particle = particle?.let { baker.sprites().get(Material(TextureAtlas.LOCATION_BLOCKS, particle)) { "Catharsis geo model $model/$particle" } } + val baked = BedrockModelGeometryBaker.bakeBones(model, texture).bones + + return BakedBedrockBlockStateModel(baked, particle) + } + + override fun resolveDependencies(resolver: ResolvableModel.Resolver) { + } +} + +data class BakedBedrockModelBonePart( + val quads: List, + val children: List, +) : BlockModelPart { + val allQuads: List = buildList { + addAll(quads) + children.forEach { addAll(it.allQuads) } + } + + override fun getQuads(direction: Direction?): List = allQuads + + override fun useAmbientOcclusion(): Boolean = false + + override fun particleIcon(): TextureAtlasSprite? = null +} + +data class BakedBedrockBlockStateModel( + val bones: List, + val particle: TextureAtlasSprite?, +) : BlockStateModel { + override fun collectParts(random: RandomSource, output: MutableList) { + output.addAll(bones) + } + + override fun particleIcon(): TextureAtlasSprite? = particle +} diff --git a/src/main/kotlin/me/owdding/catharsis/utils/types/fabric/ModelLoadingPlugin.kt b/src/main/kotlin/me/owdding/catharsis/utils/types/fabric/ModelLoadingPlugin.kt index 1255467b..ddc92ffc 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/types/fabric/ModelLoadingPlugin.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/types/fabric/ModelLoadingPlugin.kt @@ -16,7 +16,7 @@ interface PreparingModelLoadingPlugin : PreparableModelLoadingPlugin { private fun prepare(sharedState: PreparableReloadListener.SharedState, executor: Executor): CompletableFuture { return prepare(sharedState.resourceManager(), executor) } - //? } + //?} fun prepare(resourceManager: ResourceManager, executor: Executor): CompletableFuture } diff --git a/src/main/resources/catharsis.mixins.json b/src/main/resources/catharsis.mixins.json index 657dd01d..773fdb56 100644 --- a/src/main/resources/catharsis.mixins.json +++ b/src/main/resources/catharsis.mixins.json @@ -4,6 +4,7 @@ "package": "me.owdding.catharsis.mixins", "compatibilityLevel": "JAVA_21", "client": [ + "ModelManagerMixin", "armor.EquipmentLayerRendererMixin", "armor.HumanoidArmorModelMixin", "armor.HumanoidModelMixin", From 1f6b481f8a7ebe010756c18cf6bd2da276d56e31 Mon Sep 17 00:00:00 2001 From: Mona <59416038+meowora@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:41:09 +0100 Subject: [PATCH 2/3] feat: culling and such --- .../blocks/BlockStateModelReplacements.kt | 4 +- .../features/models/BedrockModels.kt | 6 +- .../model/BedrockModelGeometryBakery.kt | 138 +++++++++--------- .../model/UnbakedBedrockBlockStateModel.kt | 41 ++++-- 4 files changed, 105 insertions(+), 84 deletions(-) diff --git a/src/main/kotlin/me/owdding/catharsis/features/blocks/BlockStateModelReplacements.kt b/src/main/kotlin/me/owdding/catharsis/features/blocks/BlockStateModelReplacements.kt index fba8e772..5c4f038c 100644 --- a/src/main/kotlin/me/owdding/catharsis/features/blocks/BlockStateModelReplacements.kt +++ b/src/main/kotlin/me/owdding/catharsis/features/blocks/BlockStateModelReplacements.kt @@ -36,7 +36,7 @@ data class BlockStateModelReplacement( val replacementSelector: BlockReplacementSelector, ): FabricBlockStateModel by original as FabricBlockStateModel, BlockStateModel { override fun emitQuads(emitter: QuadEmitter, blockView: BlockAndTintGetter, pos: BlockPos, state: BlockState, random: RandomSource, cullTest: Predicate) { - val random = RandomSource.create(pos.asLong()) + val random = RandomSource.create(Mth.getSeed(pos)) val replacement = replacementSelector.select(state, pos, random) val model = replacement?.models[state] if (model != null) { @@ -63,7 +63,7 @@ data class BlockStateModelReplacement( return super.particleSprite(blockView, pos, state) } - override fun particleIcon(): TextureAtlasSprite? { + override fun particleIcon(): TextureAtlasSprite { return original.particleIcon() } } diff --git a/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt b/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt index bb9d2265..cdabe351 100644 --- a/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt +++ b/src/main/kotlin/me/owdding/catharsis/features/models/BedrockModels.kt @@ -5,8 +5,6 @@ import com.google.gson.JsonElement import me.owdding.catharsis.Catharsis import me.owdding.catharsis.utils.extensions.mapBothNotNull import me.owdding.catharsis.utils.geometry.BedrockGeometry -import me.owdding.catharsis.utils.geometry.model.BedrockModelGeometryBaker -import me.owdding.catharsis.utils.geometry.model.MediumBakedBedrockModelGeometry import me.owdding.ktmodules.Module import net.minecraft.resources.FileToIdConverter import net.minecraft.resources.ResourceLocation @@ -21,7 +19,7 @@ object BedrockModels : SimplePreparableReloadListener = mutableMapOf() + private val models: MutableMap = mutableMapOf() override fun prepare( resourceManager: ResourceManager, @@ -42,7 +40,7 @@ object BedrockModels : SimplePreparableReloadListener, @@ -23,89 +26,67 @@ data class ModelBone( val children: MutableList = mutableListOf(), ) -data class MediumBakedQuad( - val data: IntArray, - val direction: Direction, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is MediumBakedQuad) return false - - if (!data.contentEquals(other.data)) return false - if (direction != other.direction) return false - - return true - } - - override fun hashCode(): Int { - var result = data.contentHashCode() - result = 31 * result + direction.hashCode() - return result - } -} - -data class MediumBakedBedrockGeometryBone( - val quads: List, - val bones: List, -) - -data class MediumBakedBedrockModelGeometry( - val bones: List, +data class CulledQuad( + val bakedQuad: BakedQuad, + val occlusionCulling: Boolean, ) object BedrockModelGeometryBaker { val logger = Catharsis.featureLogger("BedrockModelGeometryBaker") - fun bakeBones(model: ResourceLocation, atlasSprite: TextureAtlasSprite): BakedBedrockModelGeometry = fry(BedrockModels.getModel(model)!!, atlasSprite) - - fun fry(geometry: MediumBakedBedrockModelGeometry, atlasSprite: TextureAtlasSprite): BakedBedrockModelGeometry { - return BakedBedrockModelGeometry(geometry.bones.map { fryBone(it, atlasSprite) }) + fun bakeBones(model: ResourceLocation, atlasSprite: TextureAtlasSprite, ambientOcclusion: Boolean?): BakedBedrockModelGeometry = runCatching { + bake(BedrockModels.getModel(model)!!, atlasSprite, ambientOcclusion) + }.getOrElse { + throw RuntimeException("Failed to create geo model $model", it) } - fun fryBone(bone: MediumBakedBedrockGeometryBone, atlasSprite: TextureAtlasSprite): BakedBedrockModelBonePart = BakedBedrockModelBonePart( - bone.quads.map { fryQuad(it, atlasSprite) }, bone.bones.map { fryBone(it, atlasSprite) } - ) - - fun fryQuad(quad: MediumBakedQuad, atlasSprite: TextureAtlasSprite) = BakedQuad(quad.data, 0, quad.direction, atlasSprite, false, 0) - - fun bake(geometry: BedrockGeometry): MediumBakedBedrockModelGeometry { + fun bake(geometry: BedrockGeometry, atlasSprite: TextureAtlasSprite, ambientOcclusion: Boolean?): BakedBedrockModelGeometry { val parts = geometry.bones.associate { it.name to ModelBone(it) } parts.values.forEach { part -> part.parent?.let { parts[it]!!.children.add(part) } } val rootBones = parts.values.filter { it.parent == null } val stack = PoseStack() - val bakedBones = rootBones.map { bakeBone(it, stack, geometry.description) } + stack.translate(8.0,0.0,8.0) + val bakedBones = rootBones.map { bakeBone(it, stack, geometry.description, atlasSprite, 0f, ambientOcclusion) } if (!stack.isEmpty) { logger.warn("Pose stack not empty after baking of $geometry") } - return MediumBakedBedrockModelGeometry(bakedBones) + return BakedBedrockModelGeometry(bakedBones) } - private fun bakeBone(modelBone: ModelBone, stack: PoseStack, description: BedrockGeometryDescription): MediumBakedBedrockGeometryBone { + private fun bakeBone( + modelBone: ModelBone, + stack: PoseStack, + description: BedrockGeometryDescription, + atlasSprite: TextureAtlasSprite, + inflation: Float, + ambientOcclusion: Boolean? + ): BakedBedrockModelBonePart { stack.pushPose() val base = modelBone.base + val newInflation = inflation + base.inflate val pivot = base.pivot - val rotation = base.rotation + val rotation = base.rotation.mul(Mth.DEG_TO_RAD, Vector3f()) stack.rotateAround(Quaternionf().rotateZYX(rotation.z, rotation.y, rotation.x), pivot.x, pivot.y, pivot.z) - val quads = base.cubes.flatMap { bakeCube(base, it, stack, description) } - val bones = modelBone.children.map { bakeBone(it, stack, description) } + val quads = base.cubes.flatMap { bakeCube(base, it, stack, description, atlasSprite, newInflation) } + val bones = modelBone.children.map { bakeBone(it, stack, description, atlasSprite, newInflation, ambientOcclusion) } stack.popPose() - return MediumBakedBedrockGeometryBone(quads, bones) + return BakedBedrockModelBonePart(quads, bones, ambientOcclusion) } - private fun bakeCube(bone: BedrockBone, cube: BedrockCube, stack: PoseStack, description: BedrockGeometryDescription): List { + private fun bakeCube(bone: BedrockBone, cube: BedrockCube, stack: PoseStack, description: BedrockGeometryDescription, atlasSprite: TextureAtlasSprite, inflation: Float): List { val uvs = cube.uv?.right()?.getOrNull() ?: TODO("Boxed UVs are not supported yet.") stack.pushPose() val pivot = cube.pivot - val rotation = cube.rotation + val rotation = cube.rotation.mul(Mth.DEG_TO_RAD, Vector3f()) stack.rotateAround(Quaternionf().rotateZYX(rotation.z, rotation.y, rotation.x), pivot.x, pivot.y, pivot.z) - val inflation = cube.inflate ?: bone.inflate + val inflation = cube.inflate?.plus(inflation) ?: inflation val mirrored = cube.mirror ?: bone.mirror var minX = cube.origin[0] - inflation @@ -131,35 +112,55 @@ object BedrockModelGeometryBaker { val x1y1z1 = Vector3f(maxX, maxY, maxZ) val x0y1z1 = Vector3f(minX, maxY, maxZ) - val up = bakeQuad(stack, x0y1z0, x1y1z0, x1y1z1, x0y1z1, uvs, mirrored, Direction.UP, description) - val down = bakeQuad(stack, x0y0z0, x1y0z0, x1y0z1, x0y0z1, uvs, mirrored, Direction.DOWN, description) + val up = bakeQuad(stack, atlasSprite, x0y1z0, x1y1z0, x1y1z1, x0y1z1, uvs, mirrored, Direction.UP, description) + val down = bakeQuad(stack, atlasSprite, x0y0z0, x1y0z0, x1y0z1, x0y0z1, uvs, mirrored, Direction.DOWN, description) - val north = bakeQuad(stack, x0y1z0, x1y1z0, x1y0z0, x0y0z0, uvs, mirrored, Direction.NORTH, description) - val south = bakeQuad(stack, x0y1z1, x1y1z1, x1y0z1, x0y0z1, uvs, mirrored, Direction.SOUTH, description) + val north = bakeQuad(stack, atlasSprite, x0y1z0, x1y1z0, x1y0z0, x0y0z0, uvs, mirrored, Direction.NORTH, description) + val south = bakeQuad(stack, atlasSprite, x0y1z1, x1y1z1, x1y0z1, x0y0z1, uvs, mirrored, Direction.SOUTH, description) - val west = bakeQuad(stack, x0y1z0, x0y1z1, x0y0z1, x0y0z0, uvs, mirrored, Direction.WEST, description) - val east = bakeQuad(stack, x1y1z0, x1y1z1, x1y0z1, x1y0z0, uvs, mirrored, Direction.EAST, description) + val west = bakeQuad(stack, atlasSprite, x0y1z0, x0y1z1, x0y0z1, x0y0z0, uvs, mirrored, Direction.WEST, description) + val east = bakeQuad(stack, atlasSprite, x1y1z0, x1y1z1, x1y0z1, x1y0z0, uvs, mirrored, Direction.EAST, description) stack.popPose() return listOf(down, up, north, south, west, east) } private fun bakeQuad( - stack: PoseStack, + stack: PoseStack, atlasSprite: TextureAtlasSprite, p1: Vector3f, p2: Vector3f, p3: Vector3f, p4: Vector3f, uvs: Map, mirror: Boolean, direction: Direction, description: BedrockGeometryDescription, - ): MediumBakedQuad { + ): CulledQuad { val direction = if (!mirror && direction.axis == Direction.Axis.X) direction.opposite else direction + val actualDirection = Direction.rotate(stack.pose().pose(), direction) + val data = IntArray(32) - val uvOffset = if (direction.axisDirection == Direction.AxisDirection.POSITIVE) 1 else 0 + val uvOffset = if (actualDirection.axisDirection == Direction.AxisDirection.POSITIVE) 1 else 0 + val vertexOffset = if (direction.axisDirection == Direction.AxisDirection.POSITIVE) 4 else 0 val uvs = uvs[direction]!! - packVertex(data, 0, p1, bakeUv(uvs, 0 + uvOffset, description)) - packVertex(data, 1, p2, bakeUv(uvs, 1 - uvOffset, description)) - packVertex(data, 2, p3, bakeUv(uvs, 2 + uvOffset, description)) - packVertex(data, 3, p4, bakeUv(uvs, 3 - uvOffset, description)) - return MediumBakedQuad(data, direction) + val p1 = stack.pose().pose().transformPosition(p1, Vector3f()) + val p2 = stack.pose().pose().transformPosition(p2, Vector3f()) + val p3 = stack.pose().pose().transformPosition(p3, Vector3f()) + val p4 = stack.pose().pose().transformPosition(p4, Vector3f()) + val f1 = abs(getCoordinate(actualDirection, p1) - 8) + val f2 = abs(getCoordinate(actualDirection, p2) - 8) + val f3 = abs(getCoordinate(actualDirection, p3) - 8) + val f4 = abs(getCoordinate(actualDirection, p4) - 8) + + val occlusionCulling = (f1 == 8f && f1 == f2 && f2 == f3 && f3 == f4) + + packVertex(data, abs(vertexOffset - 0) % 4, p1, bakeUv(uvs, 0 + uvOffset, description), atlasSprite) + packVertex(data, abs(vertexOffset - 1) % 4, p2, bakeUv(uvs, 1 - uvOffset, description), atlasSprite) + packVertex(data, abs(vertexOffset - 2) % 4, p3, bakeUv(uvs, 2 + uvOffset, description), atlasSprite) + packVertex(data, abs(vertexOffset - 3) % 4, p4, bakeUv(uvs, 3 - uvOffset, description), atlasSprite) + return CulledQuad(BakedQuad(data, -1, if (actualDirection.axis == Direction.Axis.X) actualDirection.opposite else actualDirection, atlasSprite, true, 0), occlusionCulling) + } + + fun getCoordinate(direction: Direction, vector3f: Vector3f) = when (direction.axis) { + Direction.Axis.X -> vector3f.x + Direction.Axis.Y -> vector3f.y + Direction.Axis.Z -> vector3f.z } private fun packVertex( @@ -167,14 +168,15 @@ object BedrockModelGeometryBaker { index: Int, pos: Vector3f, uv: Vector2f, + atlasSprite: TextureAtlasSprite, ) { val offset = index * 8 - data[offset + 0] = pos.x.toRawBits() - data[offset + 1] = pos.y.toRawBits() - data[offset + 2] = pos.z.toRawBits() + data[offset + 0] = (pos.x / 16).toRawBits() + data[offset + 1] = (pos.y / 16).toRawBits() + data[offset + 2] = (pos.z / 16).toRawBits() data[offset + 3] = -1 // color - data[offset + 4] = uv.x.toRawBits() - data[offset + 5] = uv.y.toRawBits() + data[offset + 4] = atlasSprite.getU(uv.x).toRawBits() + data[offset + 5] = atlasSprite.getV(uv.y).toRawBits() } private fun bakeUv(face: UvFace, index: Int, description: BedrockGeometryDescription): Vector2f { diff --git a/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt b/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt index c41fd5a0..e831dc97 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/geometry/model/UnbakedBedrockBlockStateModel.kt @@ -2,8 +2,12 @@ package me.owdding.catharsis.utils.geometry.model import com.mojang.serialization.MapCodec import me.owdding.catharsis.generated.CatharsisCodecs +import me.owdding.ktcodecs.FieldName import me.owdding.ktcodecs.GenerateCodec import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter +import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode +import net.fabricmc.fabric.api.util.TriState import net.minecraft.client.renderer.block.model.BakedQuad import net.minecraft.client.renderer.block.model.BlockModelPart import net.minecraft.client.renderer.block.model.BlockStateModel @@ -15,21 +19,23 @@ import net.minecraft.client.resources.model.ResolvableModel import net.minecraft.core.Direction import net.minecraft.resources.ResourceLocation import net.minecraft.util.RandomSource +import java.util.function.Predicate @GenerateCodec data class UnbakedBedrockBlockStateModel( val model: ResourceLocation, val texture: ResourceLocation, - val particle: ResourceLocation?, + val particle: ResourceLocation, + @FieldName("ambient_occlusion") val ambientOcclusion: Boolean? ) : CustomUnbakedBlockStateModel { override fun codec(): MapCodec = CatharsisCodecs.getMapCodec() override fun bake(baker: ModelBaker): BlockStateModel { val texture = baker.sprites().get(Material(TextureAtlas.LOCATION_BLOCKS, texture)) { "Catharsis geo model $model/$texture" } - val particle = particle?.let { baker.sprites().get(Material(TextureAtlas.LOCATION_BLOCKS, particle)) { "Catharsis geo model $model/$particle" } } - val baked = BedrockModelGeometryBaker.bakeBones(model, texture).bones + val particle = baker.sprites().get(Material(TextureAtlas.LOCATION_BLOCKS, particle)) { "Catharsis geo model $model/$particle" } + val baked = BedrockModelGeometryBaker.bakeBones(model, texture, ambientOcclusion).bones - return BakedBedrockBlockStateModel(baked, particle) + return BakedBedrockBlockStateModel(baked, particle, ambientOcclusion) } override fun resolveDependencies(resolver: ResolvableModel.Resolver) { @@ -37,28 +43,43 @@ data class UnbakedBedrockBlockStateModel( } data class BakedBedrockModelBonePart( - val quads: List, + val quads: List, val children: List, + val ambientOcclusion: Boolean? ) : BlockModelPart { - val allQuads: List = buildList { + val allQuads: List = buildList { addAll(quads) children.forEach { addAll(it.allQuads) } } - override fun getQuads(direction: Direction?): List = allQuads + override fun emitQuads(emitter: QuadEmitter, cullTest: Predicate) { + for (quad in allQuads) { + val (baked, occlusionCulling) = quad + if (occlusionCulling && cullTest.test(baked.direction)) continue - override fun useAmbientOcclusion(): Boolean = false + if (occlusionCulling) emitter.cullFace(baked.direction) + emitter.ambientOcclusion(TriState.of(ambientOcclusion)) + emitter.fromBakedQuad(baked) + emitter.shadeMode(ShadeMode.VANILLA) + emitter.emit() + } + } + + override fun getQuads(direction: Direction?): List = emptyList() + + override fun useAmbientOcclusion(): Boolean = ambientOcclusion == true override fun particleIcon(): TextureAtlasSprite? = null } data class BakedBedrockBlockStateModel( val bones: List, - val particle: TextureAtlasSprite?, + val particle: TextureAtlasSprite, + val occlusionCulling: Boolean?, ) : BlockStateModel { override fun collectParts(random: RandomSource, output: MutableList) { output.addAll(bones) } - override fun particleIcon(): TextureAtlasSprite? = particle + override fun particleIcon(): TextureAtlasSprite = particle } From ab164cb04baf6ecb65fbd173cfcc995365b255f8 Mon Sep 17 00:00:00 2001 From: Mona <59416038+meowora@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:42:05 +0100 Subject: [PATCH 3/3] Update src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt Co-authored-by: Empa <42304516+ItsEmpa@users.noreply.github.com> --- src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt b/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt index b341356c..15b7c82c 100644 --- a/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt +++ b/src/main/kotlin/me/owdding/catharsis/utils/DevUtils.kt @@ -21,7 +21,7 @@ internal fun debugToggle(path: String, description: String = path): DebugToggle } @Module internal object CatharsisDevUtils : DevUtils() { - override val commandName: String = "sbapi toggle" + override val commandName: String = "catharsis toggle" override fun send(component: MutableComponent) = component.sendWithPrefix() val properties: Map = loadFromProperties()