Compare commits

...
8 changed files with 380 additions and 92 deletions
@@ -11,7 +11,14 @@ package com.cobblemon.mod.common.client.render
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseTransitionAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockPoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.TextureInterpolationType
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
@@ -21,10 +28,13 @@ import com.cobblemon.mod.common.util.toRGBA
import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
import com.mojang.blaze3d.platform.NativeImage
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.texture.DynamicTexture
import kotlin.math.floor
import net.minecraft.resources.ResourceLocation
import org.joml.Vector2d
import org.joml.Vector3f
import org.joml.Vector4f
@@ -53,7 +63,7 @@ class VaryingRenderableResolver(
}
fun getResolvedTexture(state: PosableState): ResourceLocation {
return getVariationValue(state) { texture }?.invoke(state)
return getVariationValue(state) { texture }?.getTexture(state)
?: throw IllegalStateException("Unable to find a texture for $name with aspects ${state.currentAspects.joinToString()}. This shouldn't be possible if you've defined the fallback variation.")
}
@@ -180,6 +190,13 @@ class ModelAssetVariation(
}
}
/** Needed a custom one, so I can make it take in a dynamic texture instead of resource location */
class DynamicStateShard(texture: DynamicTexture) : RenderStateShard.EmptyTextureStateShard(Runnable {
RenderSystem.setShaderTexture(0, texture.id)
}, Runnable {
texture.close() // Cleanup
})
/**
* Given the animation seconds, returns a texture to use. Only implemented
* by [StaticModelTextureSupplier] and [AnimatedModelTextureSupplier].
@@ -187,12 +204,17 @@ class ModelAssetVariation(
* @author Hiroku
* @since February 6th, 2023
*/
fun interface ModelTextureSupplier {
operator fun invoke(state: PosableState): ResourceLocation
interface ModelTextureSupplier {
operator fun invoke(state: PosableState): RenderStateShard.EmptyTextureStateShard
fun getTexture(state: PosableState): ResourceLocation
}
class StaticModelTextureSupplier(val texture: ResourceLocation): ModelTextureSupplier {
override fun invoke(state: PosableState): ResourceLocation {
override fun invoke(state: PosableState): RenderStateShard.EmptyTextureStateShard {
return RenderStateShard.TextureStateShard(texture, false, false)
}
override fun getTexture(state: PosableState): ResourceLocation {
return texture
}
}
@@ -203,80 +225,196 @@ class AnimatedModelTextureSupplier(
val frames: List<ResourceLocation>,
val interpolation: Boolean
): ModelTextureSupplier {
override fun invoke(state: PosableState): ResourceLocation {
val frameIndex = floor(state.animationSeconds * fps).toInt()
return if (frameIndex >= frames.size && !loop) {
frames.last()
} else {
frames[frameIndex % frames.size]
override fun invoke(state: PosableState): RenderStateShard.EmptyTextureStateShard {
var frameIndex = state.animationSeconds * fps
if (frameIndex > frames.size - 1.0f && !loop) frameIndex = frames.size.toFloat() - 1.0f
val i = frameIndex % 1.0
if (interpolation && i != 0.0) {
val o = floor(frameIndex).toInt() % frames.size
val s = if (o == frames.size - 1) 0 else o + 1
val resourceManager = Minecraft.getInstance().resourceManager
val texture1 = NativeImage.read(resourceManager.getResourceOrThrow(frames[o]).open())
val texture2 = NativeImage.read(resourceManager.getResourceOrThrow(frames[s]).open())
val image = getInterpolatedTexture(texture1, texture2, i)
val newTexture = DynamicTexture(image)
newTexture.setFilter(false, false)
return DynamicStateShard(newTexture)
}
else {
val frame = floor(frameIndex).toInt() % frames.size
return RenderStateShard.TextureStateShard(frames[frame], false, false)
}
}
fun interpolatedTexture(state: PosableState): DynamicTexture? {
override fun getTexture(state: PosableState): ResourceLocation {
val frameIndex = floor(state.animationSeconds * fps).toInt()
return frames[frameIndex]
}
}
class KeyframableTextureSupplier(val textures: MutableList<ResourceLocation>) : ModelTextureSupplier {
var layerName = ""
override fun invoke(state: PosableState): RenderStateShard.EmptyTextureStateShard {
val resourceManager = Minecraft.getInstance().resourceManager
val frameIndex = floor(state.animationSeconds * fps).toInt()
try {
if (frameIndex >= frames.size && !loop) {
return DynamicTexture(NativeImage.read(resourceManager.getResourceOrThrow(frames.last()).open()))
if (state.primaryAnimation?.animation is PoseTransitionAnimation) {
val poseTransition = state.primaryAnimation!!.animation as PoseTransitionAnimation
val (animation1, startTime1) = getMainAnimFromPose(poseTransition.beforePose, state, true)
val (animation2, startTime2) = getMainAnimFromPose(poseTransition.afterPose, state, true)
val texture2 = if (animation2 != null) {
getImageFromAnimation(animation2, state.animationSeconds - startTime2)
} else {
val image = NativeImage.read(resourceManager.getResourceOrThrow(textures[0]).open())
getEmptyTexture(image.width, image.height)
}
val (interpType, cubicValues) = poseTransition.beforePose.texturePhaseOut
?: poseTransition.afterPose.texturePhaseIn
if (interpType == TextureInterpolationType.STEP) {
val newTexture = DynamicTexture(texture2)
newTexture.setFilter(false, false)
return DynamicStateShard(newTexture)
}
val texture1 = if (animation1 != null) {
getImageFromAnimation(animation1, state.animationSeconds - startTime1)
} else {
val image = NativeImage.read(resourceManager.getResourceOrThrow(textures[0]).open())
getEmptyTexture(image.width, image.height)
}
var i =
((state.animationSeconds - poseTransition.startTime) / (poseTransition.endTime - poseTransition.startTime)).toDouble() //Linear
if (interpType == TextureInterpolationType.BEZIER) { //Switch to Bezier
i = resolveCubicValues(cubicValues, i)
}
val newImage = getInterpolatedTexture(texture1, texture2, i)
val newTexture = DynamicTexture(newImage)
newTexture.setFilter(false, false)
return DynamicStateShard(newTexture)
} else {
var animation = if ((state.primaryAnimation?.animation as? BedrockActiveAnimation)?.animation?.cobblemonTextures?.get(layerName) != null) {
(state.primaryAnimation?.animation as? BedrockActiveAnimation)?.animation
} else null
var startTime = 0F
if (animation == null) {
val data = getMainAnimFromPose(state.currentPose?.let(state.currentModel!!.poses::get), state)
animation = data.first
startTime = data.second
}
if (animation != null) {
val image = getImageFromAnimation(animation, state.animationSeconds - startTime)
val newTexture = DynamicTexture(image)
newTexture.setFilter(false, false)
return DynamicStateShard(newTexture)
}
} catch (e : Exception) {
return null
}
val o = frameIndex % frames.size
val i = state.animationSeconds * fps - frameIndex
val image = NativeImage.read(resourceManager.getResourceOrThrow(textures[0]).open())
val newTexture = DynamicTexture(getEmptyTexture(image.width, image.height))
newTexture.setFilter(false, false)
return DynamicStateShard(newTexture)
}
override fun getTexture(state: PosableState): ResourceLocation {
return textures[0]
}
private fun getMainAnimFromPose(pose: Pose?, state: PosableState, transitionAnimation: Boolean = false): Pair<BedrockAnimation?, Float> {
var animation: BedrockAnimation? = null
var startTime = 0F
pose?.animations?.filterIsInstance<BedrockPoseAnimation>()?.filter{ transitionAnimation || state.shouldIdleRun(it, 0F) }?.forEach {
if (it.animation.cobblemonTextures[layerName] != null) {
animation = it.animation
}
}
if (state.primaryAnimation == null && state.currentModel!!.context.request(RenderContext.DO_QUIRKS) != false) {
pose?.quirks?.forEach { quirk ->
val quirkData = quirk.getOrCreateData(state)
quirkData.animations.filterIsInstance<BedrockActiveAnimation>().forEach {
if (it.animation.cobblemonTextures[layerName] != null) {
startTime = it.startedSeconds
animation = it.animation
}
}
}
}
return Pair(animation, startTime)
}
private fun getImageFromAnimation(animation: BedrockAnimation, animationSeconds: Float): NativeImage {
val resourceManager = Minecraft.getInstance().resourceManager
val keyframeMap = animation.cobblemonTextures[layerName]
if (keyframeMap.isNullOrEmpty()) {
val image1 = NativeImage.read(resourceManager.getResourceOrThrow(textures[0]).open())
return getEmptyTexture(image1.width, image1.height)
}
var adjustedTime = animationSeconds
if (animation.shouldLoop) adjustedTime %= animation.animationLength.toFloat()
val keyIndex = keyframeMap.keys.indexOfLast { it <= adjustedTime }
val keyframe1 = when(keyIndex) {
-1 -> keyframeMap.firstEntry()
else -> keyframeMap.entries.toMutableList()[keyIndex]
}
val keyframe2 = when {
keyIndex + 1 >= keyframeMap.size -> keyframeMap.lastEntry()
else -> keyframeMap.entries.toMutableList()[keyIndex+1]
}
val texture1 : NativeImage
val texture2 : NativeImage
var texture2 : NativeImage? = null
try { //Try getting images of the frames so we can use them to make a new one
texture1 = NativeImage.read(resourceManager.getResourceOrThrow(frames[o]).open())
val s = if (o == frames.size - 1) 0 else o + 1
texture2 = NativeImage.read(resourceManager.getResourceOrThrow(frames[s]).open())
} catch (e : Exception) {
return null
if (keyframe1.value.textureSlot == -1 && (keyframe2.value.textureSlot == -1 || keyframe1.value.interpolationType == TextureInterpolationType.STEP)) { // If both -1 we just return empty texture or if Step
val image1 = NativeImage.read(resourceManager.getResourceOrThrow(textures[0]).open())
return getEmptyTexture(image1.width, image1.height)
}
for (x in 0..<texture1.width) {
for (y in 0..<texture1.height) {
val color1 = texture1.getPixelRGBA(x,y).toRGBA()
val color2 = texture2.getPixelRGBA(x,y).toRGBA()
var newRed : Int
var newGreen : Int
var newBlue : Int
var newAlpha : Int
//Usually full transparent pixel is black, so this makes it copy the other texture colours to look better, however considering if we should allow to disable this feature
if (color1.w == 0F && color2.w == 1F) {
newRed = floor(color2.x * 255).toInt()
newGreen = floor(color2.y * 255).toInt()
newBlue = floor(color2.z * 255).toInt()
} else if (color1.w == 1F && color2.w == 0F) {
newRed = floor(color1.x * 255).toInt()
newGreen = floor(color1.y * 255).toInt()
newBlue = floor(color1.z * 255).toInt()
} else {
newRed = floor((color1.x + (color2.x - color1.x) * i) * 255).toInt()
newGreen = floor((color1.y + (color2.y - color1.y) * i) * 255).toInt()
newBlue = floor((color1.z + (color2.z - color1.z) * i) * 255).toInt()
}
newAlpha = floor((color1.w + (color2.w - color1.w) * i) * 255).toInt()
val finalColor = (newAlpha shl 24) or (newRed shl 16) or (newGreen shl 8) or newBlue
texture1.setPixelRGBA(x,y,finalColor) //We aren't using texture 1 anymore so it's easier to override it
}
var inverseInterp = false
if (keyframe1.value.textureSlot == -1) {
texture1 = NativeImage.read(resourceManager.getResourceOrThrow(textures[keyframe2.value.textureSlot]).open())
inverseInterp = true
} else if (keyframe2.value.textureSlot == -1) {
texture1 = NativeImage.read(resourceManager.getResourceOrThrow(textures[keyframe1.value.textureSlot]).open())
} else {
texture1 = NativeImage.read(resourceManager.getResourceOrThrow(textures[keyframe1.value.textureSlot]).open())
texture2 = NativeImage.read(resourceManager.getResourceOrThrow(textures[keyframe2.value.textureSlot]).open())
}
val newTexture = DynamicTexture(texture1)
newTexture.setFilter(false, false)
return newTexture
val i = (adjustedTime - keyframe1.key.toFloat()) / (keyframe2.key - keyframe1.key)
if (keyframe1.value.textureSlot != keyframe2.value.textureSlot && keyframe1.value.interpolationType == TextureInterpolationType.LINEAR) {
var interp = i
if (inverseInterp) interp = 1 - i
return getInterpolatedTexture(texture1, texture2, interp)
} else if (keyframe1.value.textureSlot != keyframe2.value.textureSlot && keyframe1.value.interpolationType == TextureInterpolationType.BEZIER) {
var cubicInterp = resolveCubicValues(keyframe1.value.cubicLerpFunctionValues, i)
if (inverseInterp) cubicInterp = 1 - cubicInterp
return getInterpolatedTexture(texture1, texture2, cubicInterp)
}
return NativeImage.read(resourceManager.getResourceOrThrow(textures[keyframe1.value.textureSlot]).open())
}
}
class VariableModelTextureSupplier : ModelTextureSupplier {
override fun invoke(state: PosableState): ResourceLocation {
override fun invoke(state: PosableState): RenderStateShard.EmptyTextureStateShard {
return RenderStateShard.TextureStateShard(getTexture(state), false, false)
}
override fun getTexture(state: PosableState): ResourceLocation {
return state.runtime.environment.variable.map["texture"]?.asString()?.asIdentifierDefaultingNamespace()
?: cobblemonResource("textures/npcs/default.png")
}
@@ -295,4 +433,75 @@ class ModelLayer {
enum class SpriteType {
PORTRAIT,
PROFILE
}
private fun getInterpolatedTexture(texture1: NativeImage, texture2: NativeImage?, i: Double): NativeImage {
for (x in 0..<texture1.width) {
for (y in 0..<texture1.height) {
val color1 = texture1.getPixelRGBA(x,y).toRGBA()
val color2 = texture2?.getPixelRGBA(x,y)?.toRGBA() ?: Vector4f(0F,0F,0F,0F)
var newRed : Int
var newGreen : Int
var newBlue : Int
//Usually full transparent pixel is black, so this makes it copy the other texture colours to look better, however considering if we should allow to disable this feature
if (color1.w == 0F && color2.w > 0F) {
newRed = floor(color2.x * 255).toInt()
newGreen = floor(color2.y * 255).toInt()
newBlue = floor(color2.z * 255).toInt()
} else if (color1.w > 0F && color2.w == 0F) {
newRed = floor(color1.x * 255).toInt()
newGreen = floor(color1.y * 255).toInt()
newBlue = floor(color1.z * 255).toInt()
} else {
newRed = floor((color1.x + (color2.x - color1.x) * i) * 255).toInt()
newGreen = floor((color1.y + (color2.y - color1.y) * i) * 255).toInt()
newBlue = floor((color1.z + (color2.z - color1.z) * i) * 255).toInt()
}
val newAlpha = floor((color1.w + (color2.w - color1.w) * i) * 255).toInt()
val finalColor = (newAlpha shl 24) or (newRed shl 16) or (newGreen shl 8) or newBlue
texture1.setPixelRGBA(x,y,finalColor) //We aren't using texture 1 anymore so it's easier to override it
}
}
return texture1
}
private fun getEmptyTexture(width: Int, height: Int): NativeImage {
return NativeImage(width, height, false)
}
private fun resolveCubicValues(clampedCubicValues : MutableList<Double>, i: Double): Double {
for (value in 0..<clampedCubicValues.size) {
clampedCubicValues[value] = Math.clamp(clampedCubicValues[value],0.0,1.0)
}
val points = mutableListOf<Vector2d>()
for (o in 0..200) {
val t = o/200.0
val omt = (1.0-t)
val point = Vector2d()
point.x = 3.0 * omt * omt * t * clampedCubicValues[0] + 3.0 * omt * t * t * clampedCubicValues[2] + t * t * t
point.y = 3.0 * omt * omt * t * clampedCubicValues[1] + 3.0 * omt * t * t * clampedCubicValues[3] + t * t * t
points.add(point)
}
var beforePoint = Vector2d(0.0,0.0)
var afterPoint = Vector2d(1.0, 1.0)
for (point in points) {
if (point.x > beforePoint.x && point.x < i) {
beforePoint = point
}
if (point.x < afterPoint.x && point.x > i) {
afterPoint = point
}
}
return beforePoint.y + (afterPoint.y - beforePoint.y) * ((i - beforePoint.x) / (afterPoint.x - beforePoint.x))
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.SingleBoneLookAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.TextureInterpolationType
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Pose
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.SimpleQuirk
@@ -177,4 +178,38 @@ class JsonPose(model: PosableModel, json: JsonObject) {
val transitions = json.get("transitions")?.takeIf { it is JsonObject }?.asJsonObject?.entrySet()?.map { (key, value) ->
JsonPoseTransition(key, value.asString.asExpressionLike())
} ?: emptyList()
val texturePhaseIn = json.get("texturePhaseIn")?.takeIf { it is JsonObject }?.asJsonObject?.let { theObject ->
val interpolationType = when(theObject.get("interpolation")?.asString) {
"step" -> TextureInterpolationType.STEP
"linear" -> TextureInterpolationType.LINEAR
"bezier" -> TextureInterpolationType.BEZIER
else -> TextureInterpolationType.STEP
}
val cubicValues = (theObject.get("cubicValues")?.asString ?: "0.0, 0.0, 1.0, 1.0").let { values ->
val list = mutableListOf<Double>()
values.split(",").forEach {
list.add(it.toDouble())
}
list
}
Pair(interpolationType, cubicValues)
} ?: Pair(TextureInterpolationType.STEP, mutableListOf(0.0, 0.0, 1.0, 1.0))
val texturePhaseOut = json.get("texturePhaseOut")?.takeIf { it is JsonObject }?.asJsonObject?.let { theObject ->
val interpolationType = when(theObject.get("interpolation")?.asString) {
"step" -> TextureInterpolationType.STEP
"linear" -> TextureInterpolationType.LINEAR
"bezier" -> TextureInterpolationType.BEZIER
else -> TextureInterpolationType.STEP
}
val cubicValues = (theObject.get("cubicValues")?.asString ?: "0.0, 0.0, 1.0, 1.0").let { values ->
val list = mutableListOf<Double>()
values.split(",").forEach {
list.add(it.toDouble())
}
list
}
Pair(interpolationType, cubicValues)
}
}
@@ -19,11 +19,13 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.animationFunctions
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.KeyframableTextureSupplier
import com.cobblemon.mod.common.client.render.ModelLayer
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockPoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.TextureInterpolationType
import com.cobblemon.mod.common.client.render.models.blockbench.frame.ModelFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.CryProvider
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
@@ -276,7 +278,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
onTransitionedInto: (PosableState) -> Unit = {},
animations: Array<PoseAnimation> = emptyArray(),
transformedParts: Array<ModelPartTransformation> = emptyArray(),
quirks: Array<ModelQuirk<*>> = emptyArray()
quirks: Array<ModelQuirk<*>> = emptyArray(),
texturePhaseIn: Pair<TextureInterpolationType, MutableList<Double>> = Pair(TextureInterpolationType.STEP, mutableListOf(0.0, 0.0, 1.0, 1.0)),
texturePhaseOut: Pair<TextureInterpolationType, MutableList<Double>>? = null
): Pose {
return Pose(
poseType.name,
@@ -288,7 +292,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
namedAnimations,
animations,
transformedParts,
quirks
quirks,
texturePhaseIn,
texturePhaseOut
).also {
poses[poseType.name] = it
}
@@ -304,7 +310,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
onTransitionedInto: (PosableState) -> Unit = {},
animations: Array<PoseAnimation> = emptyArray(),
transformedParts: Array<ModelPartTransformation> = emptyArray(),
quirks: Array<ModelQuirk<*>> = emptyArray()
quirks: Array<ModelQuirk<*>> = emptyArray(),
texturePhaseIn: Pair<TextureInterpolationType, MutableList<Double>> = Pair(TextureInterpolationType.STEP, mutableListOf(0.0, 0.0, 1.0, 1.0)),
texturePhaseOut: Pair<TextureInterpolationType, MutableList<Double>>? = null
): Pose {
return Pose(
poseName,
@@ -316,7 +324,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
namedAnimations,
animations,
transformedParts,
quirks
quirks,
texturePhaseIn,
texturePhaseOut
).also {
poses[poseName] = it
}
@@ -332,7 +342,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
onTransitionedInto: (PosableState) -> Unit = {},
animations: Array<PoseAnimation> = emptyArray(),
transformedParts: Array<ModelPartTransformation> = emptyArray(),
quirks: Array<ModelQuirk<*>> = emptyArray()
quirks: Array<ModelQuirk<*>> = emptyArray(),
texturePhaseIn: Pair<TextureInterpolationType, MutableList<Double>> = Pair(TextureInterpolationType.STEP, mutableListOf(0.0, 0.0, 1.0, 1.0)),
texturePhaseOut: Pair<TextureInterpolationType, MutableList<Double>>? = null
): Pose {
return Pose(
poseName,
@@ -344,7 +356,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
namedAnimations,
animations,
transformedParts,
quirks
quirks,
texturePhaseIn,
texturePhaseOut
).also {
poses[poseName] = it
}
@@ -412,13 +426,6 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
fun registerRelevantPart(pairing: Pair<String, ModelPart>) = registerRelevantPart(pairing.first, pairing.second)
/** Needed a custom one, so I can make it take in a dynamic texture instead of resource location */
class DynamicStateShard(texture: DynamicTexture) : RenderStateShard.EmptyTextureStateShard(Runnable {
RenderSystem.setShaderTexture(0, texture.id)
}, Runnable {
texture.close() // Cleanup
})
/** Renders the model. Assumes rotations have been set. Will simply render the base model and then any extra layers. */
fun render(
context: RenderContext,
@@ -453,16 +460,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
val provider = bufferProvider
if (provider != null) {
for (layer in currentLayers) {
var renderLayer : RenderType
if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
//Handle Interpolation
val texture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
renderLayer = makeLayer(DynamicStateShard(texture), layer.emissive, layer.translucent, layer.translucent_cull)
}
else {
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
renderLayer = getLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
}
(layer.texture as? KeyframableTextureSupplier)?.layerName = layer.name
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
val renderLayer = makeLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
val consumer = provider.getBuffer(renderLayer)
val tint = layer.tint
val tintRed = (tint.x * r2 * 255).toInt()
@@ -90,7 +90,9 @@ class PoseAdapter(
animations = pose.idleAnimations,
transformedParts = pose.transformedParts,
quirks = pose.quirks.toTypedArray(),
namedAnimations = pose.animations
namedAnimations = pose.animations,
texturePhaseIn = pose.texturePhaseIn,
texturePhaseOut = pose.texturePhaseOut
).also {
it.transitions.putAll(
pose.transitions
@@ -132,7 +132,9 @@ data class BedrockAnimation(
val shouldLoop: Boolean,
val animationLength: Double,
val effects: List<BedrockEffectKeyframe>,
val boneTimelines: Map<String, BedrockBoneTimeline>
val boneTimelines: Map<String, BedrockBoneTimeline>,
//This is custom data added by our custom BB plugin
val cobblemonTextures: MutableMap<String, TreeMap<Double, CobblemonBedrockTextureKeyframe>>
) {
fun checkForErrors() {
boneTimelines.forEach { (_, timeline) ->
@@ -396,4 +398,14 @@ enum class InterpolationType {
enum class Transformation {
POSITION, ROTATION, SCALE
}
data class CobblemonBedrockTextureKeyframe(
val textureSlot: Int,
val cubicLerpFunctionValues: MutableList<Double>,
val interpolationType: TextureInterpolationType
)
enum class TextureInterpolationType {
STEP, LINEAR, BEZIER
}
@@ -20,6 +20,7 @@ import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import java.lang.reflect.Type
import java.util.TreeMap
/**
* Gson adapter for converting bedrock/blockbench json data into a friendlier object model.
@@ -89,7 +90,27 @@ object BedrockAnimationAdapter : JsonDeserializer<BedrockAnimation> {
)
}
return BedrockAnimation(shouldLoop, animationLength, effects, boneTimelines)
//Custom Data reading added by our custom BB plugin
val cobblemonTextures = mutableMapOf<String, TreeMap<Double, CobblemonBedrockTextureKeyframe>>()
json["cobblemon_texture"]?.asJsonObject?.entrySet()?.forEach { (texture, timeline) ->
val textureTimeline = TreeMap<Double, CobblemonBedrockTextureKeyframe>()
timeline.asJsonObject.entrySet().forEach { (frame, values) ->
val lerpFunctionValues = mutableListOf<Double>()
values.asJsonObject["cobblemon_cubic_lerp_function"].asString.split(",").forEach {
lerpFunctionValues.add(it.toDouble())
}
val interpolation = when(values.asJsonObject["cobblemon_interpolation"].asString) {
"step" -> TextureInterpolationType.STEP
"linear" -> TextureInterpolationType.LINEAR
"bezier" -> TextureInterpolationType.BEZIER
else -> TextureInterpolationType.STEP
}
textureTimeline[frame.toDouble()] = CobblemonBedrockTextureKeyframe(values.asJsonObject["cobblemon_texture_slot"].asInt, lerpFunctionValues, interpolation)
}
cobblemonTextures[texture] = textureTimeline
}
return BedrockAnimation(shouldLoop, animationLength, effects, boneTimelines, cobblemonTextures)
}
else {
throw IllegalStateException("animation json could not be parsed")
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
import com.cobblemon.mod.common.client.render.models.blockbench.animation.ActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.TextureInterpolationType
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
import com.cobblemon.mod.common.entity.PoseType
@@ -39,7 +40,10 @@ class Pose(
val namedAnimations: MutableMap<String, ExpressionLike> = mutableMapOf(),
val animations: Array<PoseAnimation>,
val transformedParts: Array<ModelPartTransformation>,
val quirks: Array<ModelQuirk<*>>
val quirks: Array<ModelQuirk<*>>,
/** These are very specific to the animated texture**/
val texturePhaseIn: Pair<TextureInterpolationType, MutableList<Double>>,
val texturePhaseOut: Pair<TextureInterpolationType, MutableList<Double>>?,
) {
fun isSuitable(state: PosableState) = condition?.invoke(state) ?: true
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.util.adapters
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.ModelTextureSupplier
import com.cobblemon.mod.common.client.render.StaticModelTextureSupplier
import com.cobblemon.mod.common.client.render.VariableModelTextureSupplier
import com.cobblemon.mod.common.client.render.*
import com.google.gson.JsonArray
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
@@ -47,6 +45,13 @@ object ModelTextureSupplierAdapter : JsonDeserializer<ModelTextureSupplier> {
frames = frames,
interpolation = interpolation
)
} else if (json.isJsonArray) {
val jsonArray = json as JsonArray
val textures = mutableListOf<ResourceLocation>()
jsonArray.forEach {
textures.add(ResourceLocation.parse(it.asString))
}
return KeyframableTextureSupplier(textures)
} else {
throw IllegalArgumentException("Invalid JSON provided for model texture, it was of type ${json::class.simpleName} instead of a String or Object.")
}