rendering: add support for conditional models

This commit is contained in:
Lukas 2021-02-19 16:01:18 +01:00
parent 467b8c2415
commit 74e5482096
11 changed files with 525 additions and 397 deletions

View File

@ -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<BlockProperties> = setOf()
val blockModels: MutableList<BlockModel> = mutableListOf()
val blockRenderers: MutableList<BlockRenderer> = mutableListOf()
constructor(identifier: ModIdentifier, properties: Set<BlockProperties>, 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()
}
}

View File

@ -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<ModIdentifier, Pair<ModIdentifier, Int>> = mutableMapOf() // identifier, <Parent, Offset>
private val entityIdClassMap = HashBiMap.create<Int, Class<out Entity?>>(120)
private val blockModels = HashBiMap.create<ModIdentifier, BlockModel>(500)
val blockModels = HashBiMap.create<ModIdentifier, BlockModel>(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,24 +394,18 @@ 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) {
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.blockModels.add(BlockModel(blockModels[ModIdentifier(type["model"].asString.replace("block/", ""))], type))
blockState.blockRenderers.add(BlockRenderer(type, this))
}
ckecked = true
}
@ -424,6 +416,28 @@ class VersionMapping(var version: Version?) {
}
}
}
blockData["conditional"]?.let {
val conditions = mutableListOf<Pair<BlockCondition, JsonObject>>()
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<JsonObject>()
for (condition in conditions) {
if (condition.key.contains(blockState)) {
apply.add(condition.value)
}
}
blockState.blockRenderers.add(BlockRenderer(apply, this))
}
}
}
private fun loadBlockState(identifier: ModIdentifier, blockStateJson: JsonObject): Block {
@ -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)
}

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<Directions, BlockModelFace> = mutableMapOf()
val fullFaceDirections: MutableSet<Directions> = mutableSetOf()
var fullFace = false
private var positions: Array<Vec3>
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<Float, Float> {
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<String, Texture>, modelMatrix: Mat4, direction: Directions, rotation: Vec3, data: MutableList<Float>) {
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<Vec3>, 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 <T> Array<T>.containsAll(set: Set<T>?): Boolean {
set?.let {
for (value in set) {
if (!this.contains(value)) {
return false
}
}
return true
}
return false
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<Vec2>
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))
}
}
}

View File

@ -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<BlockProperties> properties;
private BlockRotations rotation;
public BlockCondition(JsonObject json) {
properties = new HashSet<>();
rotation = BlockRotations.NONE;
for (Map.Entry<String, JsonElement> 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);
}
}

View File

@ -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<String, String> = parent?.textures?.toMutableMap() ?: mutableMapOf()
val textures: MutableMap<String, String> = parent?.textures?.toMutableMap() ?: mutableMapOf()
private val textureMapping: MutableMap<String, Texture> = mutableMapOf()
private var elements: MutableList<BlockModelElement> = parent?.elements?.toMutableList() ?: mutableListOf()
private val fullFaceDirections: MutableSet<Directions> = parent?.fullFaceDirections?.toMutableSet() ?: mutableSetOf()
var elements: MutableList<BlockModelElement> = parent?.elements?.toMutableList() ?: mutableListOf()
val fullFaceDirections: MutableSet<Directions> = 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<Float>, neighbourBlocks: Array<Block?>) {
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<Texture>, textureMap: MutableMap<String, Texture>) {
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))
}
}
}

View File

@ -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<Directions, BlockModelFace> = mutableMapOf()
val fullFaceDirections: MutableSet<Directions> = mutableSetOf()
var fullFace = false
var positions: Array<Vec3>
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<Float, Float> {
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<Vec3>, 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<Vec3>, 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 <T> Array<T>.containsAll(set: Set<T>?): Boolean {
if (set != null) {
for (value in set) {
if (! this.contains(value)) {
return false;
}
}
return true
}
return false
}

View File

@ -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<Vec2>
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<Vec2?> {
val template = textureTemplate[direction.ordinal]
val result = arrayOfNulls<Vec2>(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, ),
)
}
}

View File

@ -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<String, String> = mutableMapOf()
private val fullFaceDirections: MutableSet<Directions> = mutableSetOf()
private val elements: MutableSet<ElementRenderer> = mutableSetOf()
private val rotation: Vec3 = Vec3()
private val textureMapping: MutableMap<String, Texture> = 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<JsonObject>, mapping: VersionMapping) : this() {
for (state in models) {
loadElements(state, mapping)
}
}
fun resolveTextures(indexed: MutableList<Texture>, textureMap: MutableMap<String, Texture>) {
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<Float>, neighbourBlocks: Array<Block?>) {
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)
}
}
}
}

View File

@ -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<Directions, BlockModelFace> = element.faces
private var positions: Array<Vec3> = element.positions.clone()
init {
BlockModelElement.rotatePositionsAxes(positions, rotation)
// TODO : uvlock
}
fun render(textureMapping: MutableMap<String, Texture>, modelMatrix: Mat4, direction: Directions, rotation: Vec3, data: MutableList<Float>) {
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<Vec3>, texturePositions: Array<Vec2?>) {
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<ElementRenderer> {
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<ElementRenderer> = 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)
}
}
}