Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ru.hollowhorizon.hollowengine.common.utils.decodeToString
import ru.hollowhorizon.hollowengine.common.utils.inflate
import ru.hollowhorizon.hollowengine.common.utils.json.JsonFormat
import ru.hollowhorizon.hollowengine.common.utils.nbt.ListOrSingle
import ru.hollowhorizon.hollowengine.common.utils.rl
import java.nio.file.Path
import java.util.*

suspend fun loadGltf(location: ResourceLocation, side: ModelSide = ModelSide.CLIENT): Result<GltfFile> {
Expand All @@ -27,7 +27,6 @@ suspend fun loadGltf(location: ResourceLocation, side: ModelSide = ModelSide.CLI
else -> error("Invalid gltf file type: $type ($filePath)")
}

val modelBasePath = if (filePath.contains('/')) filePath.substringBeforeLast('/') else "."
gltfFile.let { m ->
coroutineScope {
withContext(Dispatchers.IO) {
Expand All @@ -47,15 +46,14 @@ suspend fun loadGltf(location: ResourceLocation, side: ModelSide = ModelSide.CLI
else -> throw IllegalStateException("Unknown data format: $uri")
}
return@async
} else {
"${location.namespace}:$modelBasePath/$uri"
}
it.data = Uint8Buffer(bufferUri.rl.readModelBytes(side))
} else resolveGltfResource(location, uri)
it.data = Uint8Buffer(bufferUri.readModelBytes(side))
}
}.awaitAll()
m.images.filter { it.uri != null }.forEach {
if (it.uri?.startsWith("data:") == false) it.uri =
"${location.namespace}:$modelBasePath/${it.uri}"
if (it.uri?.startsWith("data:") == false) {
it.uri = resolveGltfResource(location, it.uri!!).toString()
}
}
m.updateReferences()
}
Expand All @@ -67,6 +65,14 @@ suspend fun loadGltf(location: ResourceLocation, side: ModelSide = ModelSide.CLI
}
}

/** Resolves a relative glTF URI against the model folder; resource paths may not contain `..` segments. */
private fun resolveGltfResource(model: ResourceLocation, uri: String): ResourceLocation {
val parent = Path.of(model.path).parent ?: Path.of("")
val path = parent.resolve(uri).normalize()
require(!path.isAbsolute && !path.startsWith("..")) { "glTF resource escapes its namespace: $uri" }
return ResourceLocation.fromNamespaceAndPath(model.namespace, path.toString().replace('\\', '/').lowercase(Locale.ROOT))
}

private fun ResourceLocation.readModelBytes(side: ModelSide): ByteArray =
when (side) {
ModelSide.CLIENT -> stream.readBytes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.TextureManager
import org.lwjgl.opengl.GL13
import ru.hollowhorizon.hollowengine.bridge.mixins.client.ShaderInstanceAccessor
import ru.hollowhorizon.hollowengine.client.utils.shouldOverrideShaders
Expand Down Expand Up @@ -48,9 +47,9 @@ inline fun drawWithShader(

fun Material.packedLight(worldLight: Int): Int = if (emissive) LightTexture.FULL_BRIGHT else worldLight

fun opaqueShaderState(): RenderType = RenderType.entityCutoutNoCull(TextureManager.INTENTIONAL_MISSING_TEXTURE)
fun opaqueShaderState(): RenderType = RenderType.entityCutoutNoCull(Material.MISSING_TEXTURE)

fun translucentShaderState(): RenderType = RenderType.entityTranslucent(TextureManager.INTENTIONAL_MISSING_TEXTURE)
fun translucentShaderState(): RenderType = RenderType.entityTranslucent(Material.MISSING_TEXTURE)

val batchingRenderType: Function<Material, RenderType> = Util.memoize<Material, RenderType> { material: Material ->
val compositeState = RenderType.CompositeState.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ class AnimatorEvaluationContext {
var modelToWorld: TrsTransformF? = null

var partialTick: Float = 0f

var gameTicks: Long = 0L
var gameTime: Float = 0f
var time: Float = 0f
var deltaTime: Float = 0f
Expand Down Expand Up @@ -211,8 +213,13 @@ class AnimatorEvaluationContext {
* put in [variables] under that name, or zero.
*/
internal fun override(name: String): Float = variables[name] ?: 0f

/** Seconds of game time since [gameTick] on the level clock; negative while it is still ahead. */
fun secondsSince(gameTick: Long): Float = ((gameTicks - gameTick).toFloat() + partialTick) / TICKS_PER_SECOND
}

private const val TICKS_PER_SECOND = 20f

/**
* Evaluates the expressions of an [Animator].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ abstract class SpecLayer(initialSpec: AnimatorLayerSpec) : AnimationLayer {
}

override fun weight(context: AnimatorEvaluationContext): Float {
age += context.deltaTime.coerceAtLeast(0f)
age = nextAge(context)
context.layerAge = age
context.layerTime = time
val declared = evaluator.float(spec.weight, context).coerceIn(0f, 1f)
Expand All @@ -119,28 +119,44 @@ abstract class SpecLayer(initialSpec: AnimatorLayerSpec) : AnimationLayer {
return declared * fadeIn
}

/** The layer's age this frame: by default it grows by the frame's step. */
protected open fun nextAge(context: AnimatorEvaluationContext): Float = age + context.deltaTime.coerceAtLeast(0f)

protected abstract fun accepts(spec: AnimatorLayerSpec): Boolean

protected open fun onReconfigured(spec: AnimatorLayerSpec) = Unit

protected fun mask(target: PoseTarget): Set<Int> = target.mask(spec.mask)
}

/** Plays one clip. */
/**
* Plays one clip.
*
* A clip with a start time plays the game time elapsed since that tick, so a late first frame catches up
* and a long stall does not lose time; a clip without one plays the animator's frame step.
*/
class ClipLayer(clip: ClipAnimationLayerSpec) : SpecLayer(clip) {
private val playback = ClipPlayback()
private val clip: ClipAnimationLayerSpec get() = spec as ClipAnimationLayerSpec

/** Seconds since the start time that playback has already covered. */
private var played = 0f

override val time: Float get() = playback.time
override var finished: Boolean = false
private set

override fun accepts(spec: AnimatorLayerSpec): Boolean = spec is ClipAnimationLayerSpec
/** Another start time is another playback, so it gets a fresh layer rather than this one's position. */
override fun accepts(spec: AnimatorLayerSpec): Boolean =
spec is ClipAnimationLayerSpec && spec.startGameTime == clip.startGameTime

override fun nextAge(context: AnimatorEvaluationContext): Float =
clip.startGameTime?.let { context.secondsSince(it).coerceAtLeast(0f) } ?: super.nextAge(context)

override fun sample(target: PoseTarget, context: AnimatorEvaluationContext): LayerPose? {
val animation = target.animations[clip.animation] ?: return null
val speed = evaluator.float(clip.speed, context, 1f)
val sampleTime = playback.advance(animation.duration, clip.playMode, speed, context.deltaTime)
val sampleTime = playback.advance(animation.duration, clip.playMode, speed, step(context))

val fadeOut = fadeOutScale(context)
if (fadeOut <= 0f && (clip.stopAtGameTime != null || (playback.ended && clip.removeOnEnd))) {
Expand All @@ -158,14 +174,25 @@ class ClipLayer(clip: ClipAnimationLayerSpec) : SpecLayer(clip) {
)
}

/**
* Seconds to play this frame. With a start time that is what the clock moved past since the last frame,
* and nothing while the start is still ahead or the clock steps back.
*/
private fun step(context: AnimatorEvaluationContext): Float {
val start = clip.startGameTime ?: return context.deltaTime
val elapsed = context.secondsSince(start)
if (elapsed <= played) return 0f
return (elapsed - played).also { played = elapsed }
}

/**
* How much of the clip is left this frame: a stop request fades from the game time it was asked at,
* everything else fades only once a one-shot has played out.
*/
private fun fadeOutScale(context: AnimatorEvaluationContext): Float {
clip.stopAtGameTime?.let { stoppedAt ->
if (clip.fadeOut <= 0f) return 0f
val elapsed = (context.gameTime - stoppedAt) / TICKS_PER_SECOND
val elapsed = context.secondsSince(stoppedAt)
return (1f - elapsed / clip.fadeOut).coerceIn(0f, 1f)
}
return if (clip.playMode != AnimationPlayMode.Once || clip.fadeOut <= 0f || !playback.ended) 1f
Expand Down Expand Up @@ -268,25 +295,15 @@ internal fun wrapTime(time: Float, duration: Float, playMode: AnimationPlayMode,
}

AnimationPlayMode.PingPong -> {
var nextTime = time
var nextReversed = reversed
while (nextTime !in 0f..duration) {
if (nextTime > duration) {
nextTime = duration - (nextTime - duration)
nextReversed = !nextReversed
} else {
nextTime = -nextTime
nextReversed = !nextReversed
}
}
WrappedTime(nextTime, nextTime, nextReversed, ended = false)
val period = duration * 2f
val phase = (if (reversed) period - time else time).modPositive(period)
if (phase <= duration) WrappedTime(phase, phase, reversed = false, ended = false)
else WrappedTime(period - phase, period - phase, reversed = true, ended = false)
}
}

private fun Float.modPositive(divisor: Float): Float = (this % divisor + divisor) % divisor

private const val TICKS_PER_SECOND = 20f

/** Every node of the hierarchy, indexed the way poses address them. */
fun List<RuntimeNode>.byIndex(): Map<Int, RuntimeNode> =
flatMap { it.walk() }.associateBy { it.definition.index }
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import kotlin.math.abs
* Fills [context] with what an animation expression can read this frame.
*/
fun fillAnimationVariables(context: AnimatorEvaluationContext, entity: Entity?, partialTick: Float) {
val gameTime = Minecraft.getInstance().level?.gameTime?.toFloat() ?: 0f
val gameTicks = entity?.level()?.gameTime ?: Minecraft.getInstance().level?.gameTime ?: 0L

context.temporaries.clear()
context.entity = entity
context.partialTick = partialTick
context.gameTime = gameTime + partialTick
context.time = (entity?.tickCount?.toFloat() ?: gameTime) + partialTick
context.gameTicks = gameTicks
context.gameTime = gameTicks.toFloat() + partialTick
context.time = (entity?.tickCount?.toFloat() ?: gameTicks.toFloat()) + partialTick
context.data = entity?.let { AttachmentRegistry.entityDataOrNull(it)?.numericPaths() }.orEmpty()

if (entity == null) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ object HollowModelManager : SimplePreparableReloadListener<Map<ResourceLocation,
flow: MutableStateFlow<Model>,
update: PreparedModelUpdate<Model>,
) {
if (!RenderSystem.isOnRenderThreadOrInit()) {
RenderSystem.recordRenderCall { publish(location, flow, update) }
return
}

val swap = ModelReloadCoordinator.resolveSwap(flow.value, update, Model.EMPTY)
flow.value = swap.next
swap.retired?.let(::destroyLater)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fun groupByMaterial(
inline fun withInstancingRenderState(body: () -> Unit) {
val activeTexture = GlStateManager._getActiveTexture()
val currentVao = GL33.glGetInteger(GL33.GL_VERTEX_ARRAY_BINDING)
val currentElementArrayBuffer = GL33.glGetInteger(GL33.GL_ELEMENT_ARRAY_BUFFER_BINDING)
val currentArrayBuffer = GL33.glGetInteger(GL33.GL_ARRAY_BUFFER_BINDING)
val shaderTexture0 = RenderSystem.getShaderTexture(0)
val shaderTexture1 = RenderSystem.getShaderTexture(1)
val shaderTexture2 = RenderSystem.getShaderTexture(2)
Expand Down Expand Up @@ -121,7 +121,7 @@ inline fun withInstancingRenderState(body: () -> Unit) {
RenderSystem.activeTexture(activeTexture)

RenderSystem.glBindVertexArray(currentVao)
RenderSystem.glBindBuffer(GL33.GL_ELEMENT_ARRAY_BUFFER, currentElementArrayBuffer)
RenderSystem.glBindBuffer(GL33.GL_ARRAY_BUFFER, currentArrayBuffer)

GlStateManager._glUseProgram(0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,35 @@ class PipelineRenderer(private val primitive: Primitive) : MeshRenderer {
}

override fun init() {
vao = GL33.glGenVertexArrays()
GL33.glBindVertexArray(vao)

if (isDynamic) {
initDynamicBuffers()

deformer = GpuDeformer(primitive)
deformer?.init(
dstPos = posBuffer!!.id,
dstNor = norBuffer!!.id,
dstTan = tanBuffer!!.id
)
val previousVao = GL33.glGetInteger(GL33.GL_VERTEX_ARRAY_BINDING)
val previousArrayBuffer = GL33.glGetInteger(GL33.GL_ARRAY_BUFFER_BINDING)
try {
vao = GL33.glGenVertexArrays()
GL33.glBindVertexArray(vao)
} else {
initStaticBuffers()
}

initCommonBuffers()
if (supportsInstancing) {
initInstancingBuffers()
initInstancedVao()
}
if (isDynamic) {
initDynamicBuffers()

GL33.glBindVertexArray(0)
deformer = GpuDeformer(primitive)
deformer?.init(
dstPos = posBuffer!!.id,
dstNor = norBuffer!!.id,
dstTan = tanBuffer!!.id
)
GL33.glBindVertexArray(vao)
} else {
initStaticBuffers()
}

posBuffer?.unbind()
indexBuffer?.unbind()
initCommonBuffers()
if (supportsInstancing) {
initInstancingBuffers()
initInstancedVao()
}
} finally {
GL33.glBindVertexArray(previousVao)
GL33.glBindBuffer(GL33.GL_ARRAY_BUFFER, previousArrayBuffer)
}
}

private fun initStaticBuffers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ class ListRenderPipeline : RenderPipeline {
fun renderVAO(context: RenderContext) {
val activeTexture = GlStateManager._getActiveTexture()

//Получение текущих VAO и IBO
// Binding the saved VAO also restores its element buffer.
val currentVAO = GL33.glGetInteger(GL33.GL_VERTEX_ARRAY_BINDING)
val currentElementArrayBuffer = GL33.glGetInteger(GL33.GL_ELEMENT_ARRAY_BUFFER_BINDING)
val currentArrayBuffer = GL33.glGetInteger(GL33.GL_ARRAY_BUFFER_BINDING)

transformSkinning()

Expand Down Expand Up @@ -110,15 +110,15 @@ class ListRenderPipeline : RenderPipeline {
RenderSystem.activeTexture(activeTexture)

RenderSystem.glBindVertexArray(currentVAO)
RenderSystem.glBindBuffer(GL33.GL_ELEMENT_ARRAY_BUFFER, currentElementArrayBuffer)
RenderSystem.glBindBuffer(GL33.GL_ARRAY_BUFFER, currentArrayBuffer)

GlStateManager._glUseProgram(0)
}

fun renderInstanced(context: RenderContext) {
val activeTexture = GlStateManager._getActiveTexture()
val currentVAO = GL33.glGetInteger(GL33.GL_VERTEX_ARRAY_BINDING)
val currentElementArrayBuffer = GL33.glGetInteger(GL33.GL_ELEMENT_ARRAY_BUFFER_BINDING)
val currentArrayBuffer = GL33.glGetInteger(GL33.GL_ARRAY_BUFFER_BINDING)

GL33.glVertexAttribI2i(3, context.overlay and FFFF, context.overlay shr 16 and FFFF)
GL33.glVertexAttribI2i(4, context.light and FFFF, context.light shr 16 and FFFF)
Expand Down Expand Up @@ -148,7 +148,7 @@ class ListRenderPipeline : RenderPipeline {
RenderSystem.activeTexture(activeTexture)

RenderSystem.glBindVertexArray(currentVAO)
RenderSystem.glBindBuffer(GL33.GL_ELEMENT_ARRAY_BUFFER, currentElementArrayBuffer)
RenderSystem.glBindBuffer(GL33.GL_ARRAY_BUFFER, currentArrayBuffer)

GlStateManager._glUseProgram(0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ class VboWrapper(val id: Int, val target: Int = GL33.GL_ARRAY_BUFFER) {
}

fun uploadData(data: IntBuffer, usage: Int = GL33.GL_STATIC_DRAW) {
bind()
GL33.glBufferData(target, data, usage)
uploadData(data, usage, target)
}

/** Index data can be uploaded through GL_ARRAY_BUFFER, so the element buffer of whichever VAO is bound stays untouched. */
fun uploadData(data: IntBuffer, usage: Int = GL33.GL_STATIC_DRAW, bindingTarget: Int) {
GL33.glBindBuffer(bindingTarget, id)
GL33.glBufferData(bindingTarget, data, usage)
}

/**
Expand Down Expand Up @@ -55,4 +60,4 @@ fun <T> Array<T>.toFloatBuffer(elementsPerVertex: Int, putData: (T, FloatBuffer)
}
buffer.flip()
return buffer
}
}
Loading
Loading