diff --git a/src/main/java/de/bixilon/minosoft/data/mappings/blocks/Block.kt b/src/main/java/de/bixilon/minosoft/data/mappings/blocks/Block.kt index 7b5eb32ee..1caa97515 100644 --- a/src/main/java/de/bixilon/minosoft/data/mappings/blocks/Block.kt +++ b/src/main/java/de/bixilon/minosoft/data/mappings/blocks/Block.kt @@ -15,7 +15,8 @@ package de.bixilon.minosoft.data.mappings.blocks import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.data.mappings.ModIdentifier import de.bixilon.minosoft.data.world.BlockPosition -import de.bixilon.minosoft.gui.rendering.chunk.models.BlockModel +import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModel +import de.bixilon.minosoft.gui.rendering.chunk.models.renderable.BlockRenderer import java.util.* import kotlin.random.Random @@ -23,6 +24,7 @@ data class Block(val identifier: ModIdentifier) { var rotation: BlockRotations = BlockRotations.NONE var properties: Set = setOf() val blockModels: MutableList = mutableListOf() + val blockRenderers: MutableList = mutableListOf() constructor(identifier: ModIdentifier, properties: Set, rotation: BlockRotations) : this(identifier) { this.properties = properties @@ -112,11 +114,11 @@ data class Block(val identifier: ModIdentifier) { return String.format("%s%s", identifier, out) } - fun getBlockModel(position: BlockPosition): BlockModel { + fun getBlockRenderer(position: BlockPosition): BlockRenderer { if (Minosoft.getConfig().config.game.other.antiMoirePattern) { // ToDo: Support weight attribute - return blockModels.random(Random(position.hashCode())) + return blockRenderers.random(Random(position.hashCode())) } - return blockModels.iterator().next() + return blockRenderers.iterator().next() } } diff --git a/src/main/java/de/bixilon/minosoft/data/mappings/versions/VersionMapping.kt b/src/main/java/de/bixilon/minosoft/data/mappings/versions/VersionMapping.kt index d07eaa361..7b5a7c01e 100644 --- a/src/main/java/de/bixilon/minosoft/data/mappings/versions/VersionMapping.kt +++ b/src/main/java/de/bixilon/minosoft/data/mappings/versions/VersionMapping.kt @@ -27,7 +27,9 @@ import de.bixilon.minosoft.data.mappings.blocks.BlockProperties import de.bixilon.minosoft.data.mappings.blocks.BlockRotations import de.bixilon.minosoft.data.mappings.particle.Particle import de.bixilon.minosoft.data.mappings.statistics.Statistic -import de.bixilon.minosoft.gui.rendering.chunk.models.BlockModel +import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockCondition +import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModel +import de.bixilon.minosoft.gui.rendering.chunk.models.renderable.BlockRenderer import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.logging.Log import javafx.util.Pair @@ -61,7 +63,7 @@ class VersionMapping(var version: Version?) { private val entityMetaIndexOffsetParentMapping: MutableMap> = mutableMapOf() // identifier, private val entityIdClassMap = HashBiMap.create>(120) - private val blockModels = HashBiMap.create(500) + val blockModels = HashBiMap.create(500) var parentMapping: VersionMapping? = null @@ -379,11 +381,7 @@ class VersionMapping(var version: Version?) { data["parent"]?.asString?.let { parent = loadBlockModel(mod, it, fullModData) } - - model = data["conditional"]?.let { - // ToDo - return@let BlockModel(parent, data) - } ?: BlockModel(parent, data) + model = BlockModel(parent, data) blockModels[identifier] = model return model @@ -396,31 +394,47 @@ class VersionMapping(var version: Version?) { } val blockData = fullModData.getAsJsonObject(identifierString) val identifier = ModIdentifier(mod, identifierString) - val states: JsonArray? = blockData["states"]?.asJsonArray - if (states == null) { - Log.warn("Block model state: Not states (%s)", identifier) - return - } - val blockStates = getBlockId(identifier)!!.blocks - for (value in states) { - check(value is JsonObject) { "Invalid model json" } - - val state = loadBlockState(identifier, value) - var ckecked = false - for (blockState in blockStates) { - if (blockState.bareEquals(state)) { - for (type in value.getAsJsonArray("types")) { - check(type is JsonObject) { "Invalid block type json" } - blockState.blockModels.add(BlockModel(blockModels[ModIdentifier(type["model"].asString.replace("block/", ""))], type)) + blockData["states"]?.let { + for (value in it.asJsonArray) { + check(value is JsonObject) { "Invalid model json" } + val state = loadBlockState(identifier, value) + var ckecked = false + for (blockState in blockStates) { + if (blockState.bareEquals(state)) { + for (type in value.getAsJsonArray("types")) { + check(type is JsonObject) { "Invalid block type json" } + blockState.blockRenderers.add(BlockRenderer(type, this)) + } + ckecked = true } - ckecked = true + } + if (!ckecked) { + Log.warn("Block model state: Block is null (%s)", state) + continue } } - if (!ckecked) { - Log.warn("Block model state: Block is null (%s)", state) - continue + } + blockData["conditional"]?.let { + val conditions = mutableListOf>() + for (entry in it.asJsonArray) { + entry.asJsonObject?.let conditionLet@ { condition -> + condition["properties"]?.let { properties -> + conditions.add(Pair(BlockCondition(properties.asJsonObject), condition)) + return@conditionLet + } + conditions.add(Pair(BlockCondition.TRUE_CONDITION, condition)) + } + } + for (blockState in blockStates) { + val apply = mutableListOf() + for (condition in conditions) { + if (condition.key.contains(blockState)) { + apply.add(condition.value) + } + } + blockState.blockRenderers.add(BlockRenderer(apply, this)) } } } @@ -466,7 +480,6 @@ class VersionMapping(var version: Version?) { data["id"]?.asInt.let { entityIdClassMap[it] = clazz } - } var parent: ModIdentifier? = null var metaDataIndexOffset = 0 @@ -496,7 +509,6 @@ class VersionMapping(var version: Version?) { throw RuntimeException("entities.json is invalid") } } - } entityMetaIndexOffsetParentMapping[identifier] = Pair(parent, metaDataIndexOffset) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt index 797c45a79..16fddfd68 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt @@ -84,12 +84,7 @@ class ChunkRenderer(private val connection: Connection, private val world: World } else { section.getBlock(position.getLocationByDirection(Directions.EAST)) } - - // if (block.identifier.fullIdentifier != "minecraft:dispenser") { - // continue - // } - - block.getBlockModel(BlockPosition(chunkLocation, sectionHeight, position)).render(Vec3(position.x + chunkLocation.x * ProtocolDefinition.SECTION_WIDTH_X, position.y + sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y, position.z + chunkLocation.z * ProtocolDefinition.SECTION_WIDTH_Z), data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast)) + block.getBlockRenderer(BlockPosition(chunkLocation, sectionHeight, position)).render(Vec3(position.x + chunkLocation.x * ProtocolDefinition.SECTION_WIDTH_X, position.y + sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y, position.z + chunkLocation.z * ProtocolDefinition.SECTION_WIDTH_Z), data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast)) } return data.toFloatArray() } @@ -136,7 +131,7 @@ class ChunkRenderer(private val connection: Connection, private val world: World textureMap[TextureArray.DEBUG_TEXTURE.name] = TextureArray.DEBUG_TEXTURE for (block in blocks) { - for (model in block.blockModels) { + for (model in block.blockRenderers) { model.resolveTextures(textures, textureMap) } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModelElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModelElement.kt deleted file mode 100644 index e660b2ba5..000000000 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModelElement.kt +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020 Moritz Zwerger - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program.If not, see . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.gui.rendering.chunk.models - -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import de.bixilon.minosoft.data.Axes -import de.bixilon.minosoft.data.Directions -import de.bixilon.minosoft.gui.rendering.textures.Texture -import de.bixilon.minosoft.gui.rendering.textures.TextureArray -import glm_.glm -import glm_.mat4x4.Mat4 -import glm_.vec2.Vec2 -import glm_.vec3.Vec3 -import glm_.vec4.Vec4 - - -open class BlockModelElement(data: JsonObject) { - private val faces: MutableMap = mutableMapOf() - val fullFaceDirections: MutableSet = mutableSetOf() - var fullFace = false - private var positions: Array - - init { - var from = Vec3(0, 0, 0) - var to = Vec3(16, 16, 16) - data["from"]?.let { - val array = it.asJsonArray - from = Vec3(array[0].asFloat, array[1].asFloat, array[2].asFloat) - } - data["to"]?.let { - val array = it.asJsonArray - to = Vec3(array[0].asFloat, array[1].asFloat, array[2].asFloat) - } - positions = arrayOf( - Vec3(from), - Vec3(to.x, from.y, from.z), - Vec3(from.x, from.y, to.z), - Vec3(to.x, from.y, to.z), - Vec3(from.x, to.y, from.z), - Vec3(to.x, to.y, from.z), - Vec3(from.x, to.y, to.z), - Vec3(to), - ) - var rotate = Vec3() - data["rotation"]?.let { - val rotation = it.asJsonObject - val axis = Axes.valueOf(rotation["axis"].asString.toUpperCase()) - val angle = glm.radians(rotation["angle"].asDouble) - rotate(axis, angle, jsonArrayToVec3(rotation["origin"].asJsonArray)) - rotate = when (axis) { - Axes.X -> Vec3(angle, 0, 0) - Axes.Y -> Vec3(0, angle, 0) - Axes.Z -> Vec3(0, 0, angle) - } - } - data["faces"]?.let { - for ((directionName, json) in it.asJsonObject.entrySet()) { - var direction = Directions.valueOf(directionName.toUpperCase()) - faces[direction] = BlockModelFace(json.asJsonObject) - direction = getRotatedDirection(rotate, direction) - fullFace = positions.containsAll(fullTestPositions[direction]) // TODO: check if texture is transparent ==> && ! texture.isTransparent - if (fullFace) { - fullFaceDirections.add(direction) - } - } - } - for ((i, position) in positions.withIndex()) { - positions[i] = BlockModel.transformPosition(position) - } - } - - fun rotate(axis: Axes, angle: Double, origin: Vec3) { - // TODO: optimize for 90deg, 180deg, 270deg rotations - for ((i, position) in positions.withIndex()) { - var transformedPosition = position - origin - transformedPosition = rotateVector(transformedPosition, angle, axis) - positions[i] = transformedPosition + origin - } - } - - private fun rotateVector(original: Vec3, angle: Double, axis: Axes): Vec3 { - fun getRotatedValues(x: Float, y: Float, sin: Double, cos: Double): Pair { - return Pair((x * cos - y * sin).toFloat(), (x * sin + y * cos).toFloat()) - } - return when (axis) { - Axes.X -> run { - val rotatedValues = getRotatedValues(original.y, original.z, glm.sin(angle), glm.cos(angle)) - return@run Vec3(original.x, rotatedValues.first, rotatedValues.second) - } - Axes.Y -> run { - val rotatedValues = getRotatedValues(original.x, original.z, glm.sin(angle), glm.cos(angle)) - return@run Vec3(rotatedValues.first, original.y, rotatedValues.second) - } - Axes.Z -> run { - val rotatedValues = getRotatedValues(original.x, original.y, glm.sin(angle), glm.cos(angle)) - return@run Vec3(rotatedValues.first, rotatedValues.second, original.z) - } - } - } - - private fun getRotatedDirection(rotation: Vec3, direction: Directions): Directions { - if (rotation == Vec3(0, 0, 0)) { - return direction - } - var rotatedDirectionVector = rotateVector(direction.directionVector, rotation.z.toDouble(), Axes.Z) - rotatedDirectionVector = rotateVector(rotatedDirectionVector, rotation.y.toDouble(), Axes.Y) - return Directions.byDirection(rotateVector(rotatedDirectionVector, rotation.x.toDouble(), Axes.X)) - } - - open fun render(textureMapping: MutableMap, modelMatrix: Mat4, direction: Directions, rotation: Vec3, data: MutableList) { - val realDirection = getRotatedDirection(rotation, direction) - val positionTemplate = FACE_POSITION_MAP_TEMPLATE[realDirection.ordinal] - - val face = faces[realDirection] ?: return // Not our face - val texture = textureMapping[face.textureName] ?: TextureArray.DEBUG_TEXTURE - // if (texture.isTransparent) { - // return // ToDo: force render transparent faces - // } - - val drawPositions = arrayOf(positions[positionTemplate[0]], positions[positionTemplate[1]], positions[positionTemplate[2]], positions[positionTemplate[3]]) - - fun addToData(vec3: Vec3, textureCoordinates: Vec2) { - val input = Vec4(vec3, 1.0f) - val output = modelMatrix * input - data.add(output.x) - data.add(output.y) - data.add(output.z) - data.add(textureCoordinates.x * texture.widthFactor) - data.add(textureCoordinates.y * texture.heightFactor) - data.add(Float.fromBits(texture.id)) // ToDo: Compact this - - // ToDo: Send this only once per texture - data.add(texture.animationFrameTime.toFloat()) - data.add(texture.animations.toFloat()) - data.add(texture.heightFactor) - } - - fun createQuad(drawPositions: Array, texturePositions: IntArray) { - addToData(drawPositions[0], face.positions[texturePositions[1]]) - addToData(drawPositions[3], face.positions[texturePositions[2]]) - addToData(drawPositions[2], face.positions[texturePositions[3]]) - addToData(drawPositions[2], face.positions[texturePositions[3]]) - addToData(drawPositions[1], face.positions[texturePositions[0]]) - addToData(drawPositions[0], face.positions[texturePositions[1]]) - } - - createQuad(drawPositions, FACE_TEXTURE_POSITION[realDirection.ordinal]) - } - - fun isCullFace(direction: Directions): Boolean { - return faces[direction]?.cullFace == direction - } - - fun getTexture(direction: Directions): String? { - return faces[direction]?.textureName - } - - companion object { - fun jsonArrayToVec3(array: JsonArray): Vec3 { - return Vec3(array[0].asFloat, array[1].asFloat, array[2].asFloat) - } - - private const val BLOCK_RESOLUTION = 16 - - val FACE_POSITION_MAP_TEMPLATE = arrayOf( - intArrayOf(0, 2, 3, 1), - intArrayOf(6, 4, 5, 7), - intArrayOf(1, 5, 4, 0), - intArrayOf(2, 6, 7, 3), - intArrayOf(6, 2, 0, 4), - intArrayOf(5, 1, 3, 7), - ) - - val FACE_TEXTURE_POSITION = arrayOf( - intArrayOf(0, 1, 2, 3), - intArrayOf(0, 1, 2, 3), - intArrayOf(3, 2, 1, 0), - intArrayOf(0, 1, 2, 3), - intArrayOf(2, 3, 0, 1), - intArrayOf(1, 0, 3, 2), - ) - - private val POSITION_1 = Vec3(0, 0, 0) - private val POSITION_2 = Vec3(BLOCK_RESOLUTION, 0, 0) - private val POSITION_3 = Vec3(0, 0, BLOCK_RESOLUTION) - private val POSITION_4 = Vec3(BLOCK_RESOLUTION, 0, BLOCK_RESOLUTION) - - private val POSITION_5 = Vec3(0, BLOCK_RESOLUTION, 0) - private val POSITION_6 = Vec3(BLOCK_RESOLUTION, BLOCK_RESOLUTION, 0) - private val POSITION_7 = Vec3(0, BLOCK_RESOLUTION, BLOCK_RESOLUTION) - private val POSITION_8 = Vec3(BLOCK_RESOLUTION, BLOCK_RESOLUTION, BLOCK_RESOLUTION) - - val fullTestPositions = mapOf( - Pair(Directions.EAST, setOf(POSITION_1, POSITION_3, POSITION_5, POSITION_7)), - Pair(Directions.WEST, setOf(POSITION_2, POSITION_4, POSITION_6, POSITION_8)), - Pair(Directions.DOWN, setOf(POSITION_1, POSITION_2, POSITION_3, POSITION_4)), - Pair(Directions.UP, setOf(POSITION_5, POSITION_6, POSITION_7, POSITION_8)), - Pair(Directions.SOUTH, setOf(POSITION_1, POSITION_2, POSITION_5, POSITION_6)), - Pair(Directions.NORTH, setOf(POSITION_3, POSITION_4, POSITION_7, POSITION_8)), - ) - } -} - -private fun Array.containsAll(set: Set?): Boolean { - set?.let { - for (value in set) { - if (!this.contains(value)) { - return false - } - } - return true - } - return false -} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModelFace.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModelFace.kt deleted file mode 100644 index e08807efc..000000000 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModelFace.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020 Moritz Zwerger - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program.If not, see . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.gui.rendering.chunk.models - -import com.google.gson.JsonObject -import de.bixilon.minosoft.data.Directions -import glm_.vec2.Vec2 - -class BlockModelFace(data: JsonObject) { - val textureName: String = data.get("texture").asString.removePrefix("#") - val cullFace: Directions? - - val positions: Array - - init { - var textureStart = Vec2(0, 0) - var textureEnd = Vec2(16, 16) - data["uv"]?.asJsonArray?.let { - textureStart = Vec2(it[0].asFloat, it[1].asFloat) - textureEnd = Vec2(it[2].asFloat, it[3].asFloat) - } - positions = arrayOf( - uvToFloat(Vec2(textureStart.x, textureStart.y)), - uvToFloat(Vec2(textureStart.x, textureEnd.y)), - uvToFloat(Vec2(textureEnd.x, textureEnd.y)), - uvToFloat(Vec2(textureEnd.x, textureStart.y)), - ) - - cullFace = data["cullface"]?.asString?.let { - return@let if (it == "bottom") { - Directions.DOWN - } else { - Directions.valueOf(it.toUpperCase()) - } - } - } - - - companion object { - private fun uvToFloat(uv: Float): Float { - return (uv) / 16f - } - - fun uvToFloat(vec2: Vec2): Vec2 { - return Vec2(uvToFloat(vec2.x), uvToFloat(vec2.y)) - } - } -} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockCondition.java b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockCondition.java new file mode 100644 index 000000000..1f8585fc5 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockCondition.java @@ -0,0 +1,45 @@ +package de.bixilon.minosoft.gui.rendering.chunk.models.loading; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.bixilon.minosoft.data.mappings.blocks.Block; +import de.bixilon.minosoft.data.mappings.blocks.BlockProperties; +import de.bixilon.minosoft.data.mappings.blocks.BlockRotations; + +import java.util.HashSet; +import java.util.Map; + +public class BlockCondition { + public static final BlockCondition TRUE_CONDITION = new BlockCondition() { + @Override + public boolean contains(Block block) { + return true; + } + }; + + private HashSet properties; + private BlockRotations rotation; + + public BlockCondition(JsonObject json) { + properties = new HashSet<>(); + rotation = BlockRotations.NONE; + for (Map.Entry entry : json.entrySet()) { + String value = entry.getValue().getAsString(); + if (BlockProperties.PROPERTIES_MAPPING.containsKey(entry.getKey())) { + properties.add(BlockProperties.PROPERTIES_MAPPING.get(entry.getKey()).get(value)); + continue; + } + rotation = BlockRotations.ROTATION_MAPPING.get(value); + } + } + + public BlockCondition() { + } + + public boolean contains(Block block) { + if (rotation != BlockRotations.NONE && rotation != block.getRotation()) { + return false; + } + return block.getProperties().containsAll(properties); + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModel.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModel.kt similarity index 54% rename from src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModel.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModel.kt index 6b66ae9ec..410f8feb4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/BlockModel.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModel.kt @@ -11,21 +11,19 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.gui.rendering.chunk.models +package de.bixilon.minosoft.gui.rendering.chunk.models.loading import com.google.gson.JsonObject import de.bixilon.minosoft.data.Directions -import de.bixilon.minosoft.data.mappings.blocks.Block import de.bixilon.minosoft.gui.rendering.textures.Texture import glm_.glm -import glm_.mat4x4.Mat4 import glm_.vec3.Vec3 open class BlockModel(val parent: BlockModel? = null, json: JsonObject) { - private val textures: MutableMap = parent?.textures?.toMutableMap() ?: mutableMapOf() + val textures: MutableMap = parent?.textures?.toMutableMap() ?: mutableMapOf() private val textureMapping: MutableMap = mutableMapOf() - private var elements: MutableList = parent?.elements?.toMutableList() ?: mutableListOf() - private val fullFaceDirections: MutableSet = parent?.fullFaceDirections?.toMutableSet() ?: mutableSetOf() + var elements: MutableList = parent?.elements?.toMutableList() ?: mutableListOf() + val fullFaceDirections: MutableSet = parent?.fullFaceDirections?.toMutableSet() ?: mutableSetOf() private var rotation: Vec3 private var uvLock = false // ToDo private var rescale = false // ToDo @@ -72,42 +70,8 @@ open class BlockModel(val parent: BlockModel? = null, json: JsonObject) { } - open fun render(position: Vec3, data: MutableList, neighbourBlocks: Array) { - val modelMatrix = Mat4().translate(position) - .rotate(rotation.z, Vec3(0, 0, -1)) - .rotate(rotation.y, Vec3(0, -1, 0)) - .rotate(rotation.x, Vec3(1, 0, 0)) - // ToDo: this should be made easier/more efficient - - for (direction in Directions.DIRECTIONS) { - for (element in elements) { - val blockFullFace = fullFaceDirections.contains(direction) - - var neighbourBlockFullFace = false - neighbourBlocks[direction.ordinal]?.blockModels?.let { // ToDo: Improve this - for (model in it) { - if (model.fullFaceDirections.contains(direction.inverse())) { - neighbourBlockFullFace = true - break - } - } - } - - if (blockFullFace && neighbourBlockFullFace) { - continue - } - if (!blockFullFace && neighbourBlockFullFace) { - continue - } - element.render(textureMapping, modelMatrix, direction, rotation, data) - } - } - } - - private fun getTextureByType(type: String): String { var currentValue: String = type - while (currentValue.startsWith("#")) { textures[currentValue.removePrefix("#")].let { if (it == null) { @@ -116,7 +80,6 @@ open class BlockModel(val parent: BlockModel? = null, json: JsonObject) { currentValue = it } } - return currentValue } @@ -129,25 +92,6 @@ open class BlockModel(val parent: BlockModel? = null, json: JsonObject) { return false } - fun resolveTextures(indexed: MutableList, textureMap: MutableMap) { - for ((key, textureName) in textures) { - if (!textureName.startsWith("#")) { - var texture: Texture? = null - var index: Int? = textureMap[textureName]?.let { - texture = it - indexed.indexOf(it) - } - if (index == null || index == -1) { - index = textureMap.size - texture = Texture(textureName, index) - textureMap[textureName] = texture!! - indexed.add(texture!!) - } - textureMapping[key] = texture!! - } - } - } - fun isTransparent(direction: Directions): Boolean { for (element in elements) { if (textureMapping[element.getTexture(direction)]?.isTransparent == true) { @@ -157,16 +101,4 @@ open class BlockModel(val parent: BlockModel? = null, json: JsonObject) { return false } - - - companion object { - fun transformPosition(position: Vec3): Vec3 { - fun positionToFloat(uv: Float): Float { - return (uv - 8f) / 16f - } - - return Vec3(positionToFloat(position.x), positionToFloat(position.y), positionToFloat(position.z)) - } - } - } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModelElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModelElement.kt new file mode 100644 index 000000000..2d585f10a --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModelElement.kt @@ -0,0 +1,173 @@ +package de.bixilon.minosoft.gui.rendering.chunk.models.loading + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import de.bixilon.minosoft.data.Axes +import de.bixilon.minosoft.data.Directions +import glm_.glm +import glm_.vec3.Vec3 + +open class BlockModelElement(data: JsonObject) { + val faces: MutableMap = mutableMapOf() + val fullFaceDirections: MutableSet = mutableSetOf() + var fullFace = false + var positions: Array + + init { + var from = Vec3(0, 0, 0) + var to = Vec3(16, 16, 16) + data["from"]?.let { + val array = it.asJsonArray + from = Vec3(array[0].asFloat, array[1].asFloat, array[2].asFloat) + } + data["to"]?.let { + val array = it.asJsonArray + to = Vec3(array[0].asFloat, array[1].asFloat, array[2].asFloat) + } + positions = arrayOf( + Vec3(from), + Vec3(to.x, from.y, from.z), + Vec3(from.x, from.y, to.z), + Vec3(to.x, from.y, to.z), + Vec3(from.x, to.y, from.z), + Vec3(to.x, to.y, from.z), + Vec3(from.x, to.y, to.z), + Vec3(to), + ) + var rotate = Vec3() + data["rotation"]?.let { + val rotation = it.asJsonObject + val axis = Axes.valueOf(rotation["axis"].asString.toUpperCase()) + val angle = glm.radians(rotation["angle"].asDouble) + rotatePositions(positions, axis, angle, jsonArrayToVec3(rotation["origin"].asJsonArray)) + rotate = when (axis) { + Axes.X -> run { return@run Vec3(angle, 0, 0) } + Axes.Y -> run { return@run Vec3(0, angle, 0) } + Axes.Z -> run { return@run Vec3(0, 0, angle) } + } + } + data["faces"]?.let { + for ((directionName, json) in it.asJsonObject.entrySet()) { + var direction = Directions.valueOf(directionName.toUpperCase()) + faces[direction] = BlockModelFace(json.asJsonObject, from, to, direction) + direction = getRotatedDirection(rotate, direction) + fullFace = positions.containsAll(fullTestPositions[direction]) // TODO: check if texture is transparent ==> && ! texture.isTransparent + if (fullFace) { + fullFaceDirections.add(direction) + } + } + } + for ((i, position) in positions.withIndex()) { + positions[i] = transformPosition(position) + } + } + + fun isCullFace(direction: Directions): Boolean { + return faces[direction]?.cullFace == direction + } + + fun getTexture(direction: Directions): String? { + return faces[direction]?.textureName + } + + companion object { + fun jsonArrayToVec3(array: JsonArray) : Vec3 { + return Vec3(array[0].asFloat, array[1].asFloat, array[2].asFloat) + } + private const val BLOCK_RESOLUTION = 16 + + val FACE_POSITION_MAP_TEMPLATE = arrayOf( + intArrayOf(0, 2, 3, 1), + intArrayOf(6, 4, 5, 7), + intArrayOf(1, 5, 4, 0), + intArrayOf(2, 6, 7, 3), + intArrayOf(6, 2, 0, 4), + intArrayOf(5, 1, 3, 7) + ) + + private val POSITION_1 = Vec3(0, 0, 0) + private val POSITION_2 = Vec3(BLOCK_RESOLUTION, 0, 0) + private val POSITION_3 = Vec3(0, 0, BLOCK_RESOLUTION) + private val POSITION_4 = Vec3(BLOCK_RESOLUTION, 0, BLOCK_RESOLUTION) + + private val POSITION_5 = Vec3(0, BLOCK_RESOLUTION, 0) + private val POSITION_6 = Vec3(BLOCK_RESOLUTION, BLOCK_RESOLUTION, 0) + private val POSITION_7 = Vec3(0, BLOCK_RESOLUTION, BLOCK_RESOLUTION) + private val POSITION_8 = Vec3(BLOCK_RESOLUTION, BLOCK_RESOLUTION, BLOCK_RESOLUTION) + + val fullTestPositions = mapOf( + Pair(Directions.EAST, setOf(POSITION_1, POSITION_3, POSITION_5, POSITION_7)), + Pair(Directions.WEST, setOf(POSITION_2, POSITION_4, POSITION_6, POSITION_8)), + Pair(Directions.DOWN, setOf(POSITION_1, POSITION_2, POSITION_3, POSITION_4)), + Pair(Directions.UP, setOf(POSITION_5, POSITION_6, POSITION_7, POSITION_8)), + Pair(Directions.SOUTH, setOf(POSITION_1, POSITION_2, POSITION_5, POSITION_6)), + Pair(Directions.NORTH, setOf(POSITION_3, POSITION_4, POSITION_7, POSITION_8)), + ) + + fun getRotatedDirection(rotation: Vec3, direction: Directions): Directions { + if (rotation == Vec3(0, 0, 0)) { + return direction + } + var rotatedDirectionVector = rotateVector(direction.directionVector, rotation.z.toDouble(), Axes.Z) + rotatedDirectionVector = rotateVector(rotatedDirectionVector, rotation.y.toDouble(), Axes.Y) + return Directions.byDirection(rotateVector(rotatedDirectionVector, rotation.x.toDouble(), Axes.X)) + } + + private fun rotateVector(original: Vec3, angle: Double, axis: Axes): Vec3 { + fun getRotatedValues(x: Float, y: Float, sin: Double, cos: Double): Pair { + return Pair((x * cos - y * sin).toFloat(), (x * sin + y * cos).toFloat()) + } + return when (axis) { + Axes.X -> run { + val rotatedValues = getRotatedValues(original.y, original.z, glm.sin(angle), glm.cos(angle)) + return@run Vec3(original.x, rotatedValues.first, rotatedValues.second) + } + Axes.Y -> run { + val rotatedValues = getRotatedValues(original.x, original.z, glm.sin(angle), glm.cos(angle)) + return@run Vec3(rotatedValues.first, original.y, rotatedValues.second) + } + Axes.Z -> run { + val rotatedValues = getRotatedValues(original.x, original.y, glm.sin(angle), glm.cos(angle)) + return@run Vec3(rotatedValues.first, rotatedValues.second, original.z) + } + } + } + + fun rotatePositions(positions: Array, axis: Axes, angle: Double, origin: Vec3) { + // TODO: optimize for 90deg, 180deg, 270deg rotations + if (angle == 0.0) { + return + } + for ((i, position) in positions.withIndex()) { + var transformedPosition = position - origin + transformedPosition = rotateVector(transformedPosition, angle, axis) + positions[i] = transformedPosition + origin + } + } + + fun rotatePositionsAxes(positions: Array, angles: Vec3) { + rotatePositions(positions, Axes.Z, angles.z.toDouble(), Vec3()) + rotatePositions(positions, Axes.Y, angles.y.toDouble(), Vec3()) + rotatePositions(positions, Axes.X, angles.x.toDouble(), Vec3()) + } + + fun transformPosition(position: Vec3): Vec3 { + fun positionToFloat(uv: Float): Float { + return (uv - 8f) / 16f + } + return Vec3(positionToFloat(position.x), positionToFloat(position.y), positionToFloat(position.z)) + } + } +} + +private fun Array.containsAll(set: Set?): Boolean { + if (set != null) { + for (value in set) { + if (! this.contains(value)) { + return false; + } + } + return true + } + return false +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModelFace.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModelFace.kt new file mode 100644 index 000000000..927ca443e --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/loading/BlockModelFace.kt @@ -0,0 +1,85 @@ +package de.bixilon.minosoft.gui.rendering.chunk.models.loading + +import com.google.gson.JsonObject +import de.bixilon.minosoft.data.Directions +import glm_.vec2.Vec2 +import glm_.vec3.Vec3 + +class BlockModelFace(data: JsonObject, from: Vec3, to: Vec3, direction: Directions) { + val textureName: String = data.get("texture").asString.removePrefix("#") + val cullFace: Directions? + + private var positions: Array + + init { + var textureStart = Vec2(0, 0) + var textureEnd = Vec2(16, 16) + when (direction) { + Directions.EAST, Directions.WEST -> run { + textureStart = Vec2(from.z.toInt(), 16 - from.y.toInt()); + textureEnd = Vec2(to.z.toInt(), 16 - to.y.toInt()); + } + Directions.UP, Directions.DOWN -> { + textureStart = Vec2(from.x.toInt(), 16 - from.z.toInt()); + textureEnd = Vec2(to.x.toInt(), 16 - to.z.toInt()); + } + Directions.NORTH, Directions.SOUTH -> { + textureStart = Vec2(from.x.toInt(), 16 - from.y.toInt()); + textureEnd = Vec2(to.x.toInt(), 16 - to.y.toInt()); + } + } + data["uv"]?.asJsonArray?.let { + textureStart = Vec2(it[0].asFloat, it[1].asFloat) + textureEnd = Vec2(it[2].asFloat, it[3].asFloat) + } + positions = arrayOf( + uvToFloat(Vec2(textureStart.x, textureStart.y)), + uvToFloat(Vec2(textureStart.x, textureEnd.y)), + uvToFloat(Vec2(textureEnd.x, textureEnd.y)), + uvToFloat(Vec2(textureEnd.x, textureStart.y)), + ) + + cullFace = data["cullface"]?.asString?.let { + return@let if (it == "bottom") { + Directions.DOWN + } else { + Directions.valueOf(it.toUpperCase()) + } + } + positions = arrayOf( + Vec2(uvToFloat(textureStart.x), uvToFloat(textureStart.y)), + Vec2(uvToFloat(textureStart.x), uvToFloat(textureEnd.y)), + Vec2(uvToFloat(textureEnd.x), uvToFloat(textureEnd.y)), + Vec2(uvToFloat(textureEnd.x), uvToFloat(textureStart.y)), + ) + } + + fun getTexturePositionArray(direction: Directions): Array { + val template = textureTemplate[direction.ordinal] + val result = arrayOfNulls(template.size) + for (i in template.indices) { + result[i] = positions[template[i]] + } + return result + } + + + companion object { + private fun uvToFloat(uv: Float): Float { + return (uv) / 16f + } + + fun uvToFloat(vec2: Vec2): Vec2 { + return Vec2(uvToFloat(vec2.x), uvToFloat(vec2.y)) + } + + val textureTemplate = arrayOf( + arrayOf(0, 1, 2, 3, ), + arrayOf(0, 1, 2, 3, ), + arrayOf(3, 2, 1, 0, ), + arrayOf(0, 1, 2, 3, ), + arrayOf(2, 3, 0, 1, ), + arrayOf(1, 0, 3, 2, ), + ) + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/BlockRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/BlockRenderer.kt new file mode 100644 index 000000000..16f252187 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/BlockRenderer.kt @@ -0,0 +1,84 @@ +package de.bixilon.minosoft.gui.rendering.chunk.models.renderable + +import com.google.gson.JsonObject +import de.bixilon.minosoft.data.Directions +import de.bixilon.minosoft.data.mappings.ModIdentifier +import de.bixilon.minosoft.data.mappings.blocks.Block +import de.bixilon.minosoft.data.mappings.versions.VersionMapping +import de.bixilon.minosoft.gui.rendering.textures.Texture +import glm_.mat4x4.Mat4 +import glm_.vec3.Vec3 + +class BlockRenderer() { + val textures: MutableMap = mutableMapOf() + private val fullFaceDirections: MutableSet = mutableSetOf() + private val elements: MutableSet = mutableSetOf() + private val rotation: Vec3 = Vec3() + private val textureMapping: MutableMap = mutableMapOf() + + constructor(entry: JsonObject, mapping: VersionMapping) : this() { + loadElements(entry, mapping) + } + + private fun loadElements(entry: JsonObject, mapping: VersionMapping) { + this.elements.addAll(ElementRenderer.createElements(entry, mapping)) + val parent = mapping.blockModels[ModIdentifier(entry["model"].asString.replace("block/", ""))] + textures.putAll(parent!!.textures) + } + + constructor(models: List, mapping: VersionMapping) : this() { + for (state in models) { + loadElements(state, mapping) + } + } + + fun resolveTextures(indexed: MutableList, textureMap: MutableMap) { + for ((key, textureName) in textures) { + if (!textureName.startsWith("#")) { + var texture: Texture? = null + var index: Int? = textureMap[textureName]?.let { + texture = it + indexed.indexOf(it) + } + if (index == null || index == -1) { + index = textureMap.size + texture = Texture(textureName, index) + textureMap[textureName] = texture!! + indexed.add(texture!!) + } + textureMapping[key] = texture!! + } + } + } + + fun render(position: Vec3, data: MutableList, neighbourBlocks: Array) { + val modelMatrix = Mat4().translate(Vec3(position.x, position.y, position.z)) + .rotate(rotation.z, Vec3(0, 0, -1)) + .rotate(rotation.y, Vec3(0, -1, 0)) + .rotate(rotation.x, Vec3(1, 0, 0 )) + // ToDo: this should be made easier/more efficient + + for (direction in Directions.DIRECTIONS) { + for (element in elements) { + val blockFullFace = fullFaceDirections.contains(direction) + + var neighbourBlockFullFace = false + neighbourBlocks[direction.ordinal]?.blockModels?.let { // ToDo: Improve this + for (model in it) { + if (model.fullFaceDirections.contains(direction.inverse())) { + neighbourBlockFullFace = true + break + } + } + } + if (blockFullFace && neighbourBlockFullFace) { + continue + } + if (!blockFullFace && neighbourBlockFullFace) { + continue + } + element.render(textureMapping, modelMatrix, direction, rotation, data) + } + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt new file mode 100644 index 000000000..624511ade --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt @@ -0,0 +1,85 @@ +package de.bixilon.minosoft.gui.rendering.chunk.models.renderable + +import com.google.gson.JsonObject +import de.bixilon.minosoft.data.Directions +import de.bixilon.minosoft.data.mappings.ModIdentifier +import de.bixilon.minosoft.data.mappings.versions.VersionMapping +import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelElement +import de.bixilon.minosoft.gui.rendering.chunk.models.loading.BlockModelFace +import de.bixilon.minosoft.gui.rendering.textures.Texture +import de.bixilon.minosoft.gui.rendering.textures.TextureArray +import glm_.Java.Companion.glm +import glm_.mat4x4.Mat4 +import glm_.vec2.Vec2 +import glm_.vec3.Vec3 +import glm_.vec4.Vec4 + +class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvlock: Boolean) { + private val faces: MutableMap = element.faces + private var positions: Array = element.positions.clone() + + init { + BlockModelElement.rotatePositionsAxes(positions, rotation) + // TODO : uvlock + } + + + fun render(textureMapping: MutableMap, modelMatrix: Mat4, direction: Directions, rotation: Vec3, data: MutableList) { + val realDirection = BlockModelElement.getRotatedDirection(rotation, direction) + val positionTemplate = BlockModelElement.FACE_POSITION_MAP_TEMPLATE[realDirection.ordinal] + + val face = faces[realDirection] ?: return // Not our face + val texture = textureMapping[face.textureName] ?: TextureArray.DEBUG_TEXTURE + // if (texture.isTransparent) { + // return // ToDo: force render transparent faces + // } + + val drawPositions = arrayOf(positions[positionTemplate[0]], positions[positionTemplate[1]], positions[positionTemplate[2]], positions[positionTemplate[3]]) + + fun addToData(vec3: Vec3, textureCoordinates: Vec2) { + val input = Vec4(vec3, 1.0f) + val output = modelMatrix * input + data.add(output.x) + data.add(output.y) + data.add(output.z) + data.add(textureCoordinates.x * texture.widthFactor) + data.add(textureCoordinates.y * texture.heightFactor) + data.add(Float.fromBits(texture.id)) // ToDo: Compact this + + // ToDo: Send this only once per texture + data.add(texture.animationFrameTime.toFloat()) + data.add(texture.animations.toFloat()) + data.add(texture.heightFactor) + } + + fun createQuad(drawPositions: Array, texturePositions: Array) { + addToData(drawPositions[0], texturePositions[1]!!) + addToData(drawPositions[3], texturePositions[2]!!) + addToData(drawPositions[2], texturePositions[3]!!) + addToData(drawPositions[2], texturePositions[3]!!) + addToData(drawPositions[1], texturePositions[0]!!) + addToData(drawPositions[0], texturePositions[1]!!) + } + + + val texturePositions = face.getTexturePositionArray(realDirection) + createQuad(drawPositions, texturePositions) + } + + companion object { + fun createElements(state: JsonObject, mapping: VersionMapping): MutableList { + val rotation = glm.radians(vec3InJsonObject(state)) + val uvlock = state["uvlock"]?.asBoolean ?: false + val parentElements = mapping.blockModels[ModIdentifier(state["model"].asString.replace("block/", ""))]!!.elements + val result: MutableList = mutableListOf() + for (parentElement in parentElements) { + result.add(ElementRenderer(parentElement, rotation, uvlock)) + } + return result + } + + private fun vec3InJsonObject(json: JsonObject): Vec3 { + return Vec3(json["x"]?.asFloat?: 0, json["y"]?.asFloat?: 0, json["z"]?.asFloat?: 0) + } + } +}