Тест аниматора, RebindableSprite и ещё приборка в GL
This commit is contained in:
parent
0a00595520
commit
9da968695e
@ -6,6 +6,8 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import org.lwjgl.Version
|
import org.lwjgl.Version
|
||||||
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
|
import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.Animator
|
||||||
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.DynamicItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.DynamicItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.io.BTreeDB
|
import ru.dbotthepony.kstarbound.io.BTreeDB
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
@ -189,6 +191,16 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// println(Starbound.statusEffects["firecharge"])
|
// println(Starbound.statusEffects["firecharge"])
|
||||||
|
|
||||||
|
starbound.pathStack.push("/animations/dust4")
|
||||||
|
val def = starbound.gson.fromJson(starbound.locate("/animations/dust4/dust4.animation").reader(), AnimationDefinition::class.java)
|
||||||
|
starbound.pathStack.pop()
|
||||||
|
|
||||||
|
val animator = Animator(client.world!!, def)
|
||||||
|
|
||||||
|
client.onPostDrawWorld {
|
||||||
|
animator.render(client.gl.matrixStack)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||||
|
@ -424,7 +424,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
private val entityRenderers = HashMap<Entity, EntityRenderer>()
|
private val entityRenderers = HashMap<Entity, EntityRenderer>()
|
||||||
|
|
||||||
override fun onEntityAdded(entity: Entity) {
|
override fun onEntityAdded(entity: Entity) {
|
||||||
entityRenderers[entity] = EntityRenderer.getRender(state, entity, this)
|
entityRenderers[entity] = EntityRenderer.getRender(world.client, entity, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEntityTransferedToThis(entity: Entity, otherChunk: ClientChunk) {
|
override fun onEntityTransferedToThis(entity: Entity, otherChunk: ClientChunk) {
|
||||||
|
@ -18,15 +18,12 @@ import ru.dbotthepony.kstarbound.client.render.Box2DRenderer
|
|||||||
import ru.dbotthepony.kstarbound.client.render.Font
|
import ru.dbotthepony.kstarbound.client.render.Font
|
||||||
import ru.dbotthepony.kvector.api.IStruct4f
|
import ru.dbotthepony.kvector.api.IStruct4f
|
||||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||||
import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f
|
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.Color
|
import ru.dbotthepony.kvector.vector.Color
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -437,61 +434,30 @@ class GLStateTracker(val locator: ISBFileLocator) {
|
|||||||
|
|
||||||
private val named2DTextures = HashMap<String, GLTexture2D>()
|
private val named2DTextures = HashMap<String, GLTexture2D>()
|
||||||
|
|
||||||
fun loadNamedTexture(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
private var missingTexture: GLTexture2D? = null
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
|
||||||
if (!locator.exists(path)) {
|
|
||||||
throw FileNotFoundException("Unable to locate $path")
|
|
||||||
}
|
|
||||||
|
|
||||||
return@computeIfAbsent newTexture(path).upload(locator.readDirect(path), memoryFormat, fileFormat).generateMips()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadNamedTexture(path: String): GLTexture2D {
|
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
|
||||||
if (!locator.exists(path)) {
|
|
||||||
throw FileNotFoundException("Unable to locate $path")
|
|
||||||
}
|
|
||||||
|
|
||||||
return@computeIfAbsent newTexture(path).upload(locator.readDirect(path)).generateMips()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var loadedEmptyTexture = false
|
|
||||||
private val missingTexturePath = "/assetmissing.png"
|
private val missingTexturePath = "/assetmissing.png"
|
||||||
|
|
||||||
fun loadNamedTextureSafe(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
fun loadTexture(path: String): GLTexture2D {
|
||||||
if (!loadedEmptyTexture) {
|
ensureSameThread()
|
||||||
loadedEmptyTexture = true
|
|
||||||
named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), memoryFormat, fileFormat).generateMips()
|
|
||||||
}
|
|
||||||
|
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
if (missingTexture == null) {
|
||||||
if (!locator.exists(path)) {
|
missingTexture = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
|
||||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
it.textureMinFilter = GL_NEAREST
|
||||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
return@computeIfAbsent newTexture(path).upload(locator.readDirect(path), memoryFormat, fileFormat).generateMips()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadNamedTextureSafe(path: String): GLTexture2D {
|
|
||||||
if (!loadedEmptyTexture) {
|
|
||||||
loadedEmptyTexture = true
|
|
||||||
named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips()
|
|
||||||
}
|
|
||||||
|
|
||||||
return named2DTextures.computeIfAbsent(path) {
|
|
||||||
if (!locator.exists(path)) {
|
|
||||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
|
||||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
return@computeIfAbsent newTexture(path).upload(locator.readDirect(path)).generateMips().also {
|
|
||||||
it.textureMagFilter = GL_NEAREST
|
it.textureMagFilter = GL_NEAREST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return named2DTextures.computeIfAbsent(path) {
|
||||||
|
if (!locator.exists(path)) {
|
||||||
|
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||||
|
missingTexture!!
|
||||||
|
} else {
|
||||||
|
newTexture(path).upload(locator.readDirect(path)).generateMips().also {
|
||||||
|
it.textureMinFilter = GL_NEAREST
|
||||||
|
it.textureMagFilter = GL_NEAREST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(obj: VertexBufferObject): VertexBufferObject {
|
fun bind(obj: VertexBufferObject): VertexBufferObject {
|
||||||
|
@ -13,7 +13,7 @@ class TextureLoadingException(message: String) : Throwable(message)
|
|||||||
|
|
||||||
data class UVCoord(val u: Float, val v: Float)
|
data class UVCoord(val u: Float, val v: Float)
|
||||||
|
|
||||||
class GLTexturePropertyTracker(private val flag: Int, var value: Int) {
|
private class GLTexturePropertyTracker(private val flag: Int, private var value: Int) {
|
||||||
operator fun getValue(thisRef: GLTexture2D, property: KProperty<*>): Int {
|
operator fun getValue(thisRef: GLTexture2D, property: KProperty<*>): Int {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@ -62,8 +62,6 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
|
|||||||
return height.toFloat() / width.toFloat()
|
return height.toFloat() / width.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mipsWarning = 2
|
|
||||||
|
|
||||||
var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR)
|
var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR)
|
||||||
var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||||
|
|
||||||
@ -71,15 +69,6 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
|
|||||||
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
|
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||||
|
|
||||||
fun bind(): GLTexture2D {
|
fun bind(): GLTexture2D {
|
||||||
if (textureMinFilter != GL_LINEAR && textureMinFilter != GL_NEAREST) {
|
|
||||||
if (mipsWarning == 1) {
|
|
||||||
LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this)
|
|
||||||
mipsWarning = 0
|
|
||||||
} else if (mipsWarning == 2) {
|
|
||||||
mipsWarning = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.texture2D = this
|
state.texture2D = this
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@ -87,8 +76,7 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
|
|||||||
fun generateMips(): GLTexture2D {
|
fun generateMips(): GLTexture2D {
|
||||||
state.ensureSameThread()
|
state.ensureSameThread()
|
||||||
glGenerateTextureMipmap(pointer)
|
glGenerateTextureMipmap(pointer)
|
||||||
checkForGLError()
|
checkForGLError("Generating texture mipmaps")
|
||||||
mipsWarning = 0
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,24 +1,51 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
import ru.dbotthepony.kstarbound.client.ClientWorld
|
import ru.dbotthepony.kstarbound.client.ClientWorld
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||||
|
import ru.dbotthepony.kvector.vector.nfloat.Vector3f
|
||||||
|
|
||||||
class Animator(
|
class Animator(
|
||||||
val world: ClientWorld,
|
val world: ClientWorld,
|
||||||
val def: AnimationDefinition
|
val def: AnimationDefinition,
|
||||||
|
val renderParams: ((String) -> String?)? = null
|
||||||
) {
|
) {
|
||||||
|
inline val state get() = world.client.gl
|
||||||
val frameAnimator: FrameAnimator?
|
val frameAnimator: FrameAnimator?
|
||||||
|
val mainSprite: RebindableSprite?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (def.frames != null && def.animationCycle != null && def.frameNumber != null) {
|
if (def.frames != null && def.animationCycle != null && def.frameNumber != null) {
|
||||||
frameAnimator = FrameAnimator(lastFrame = def.frameNumber - 1, time = world.client.time, animationCycle = def.animationCycle)
|
frameAnimator = FrameAnimator(lastFrame = def.frameNumber - 1, time = world.client.time, animationCycle = def.animationCycle)
|
||||||
|
mainSprite = RebindableSprite(world.client, def.frames, ::getRenderParam)
|
||||||
} else {
|
} else {
|
||||||
frameAnimator = null
|
frameAnimator = null
|
||||||
|
mainSprite = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(stack: Matrix4fStack) {
|
private fun getRenderParam(name: String): String? {
|
||||||
|
if (name == "frame") {
|
||||||
|
return frameAnimator?.frameString
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderParams?.invoke(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(stack: Matrix4fStack) {
|
||||||
|
frameAnimator?.advance()
|
||||||
|
val sprite = mainSprite?.update() ?: return
|
||||||
|
sprite.texture.bind()
|
||||||
|
|
||||||
|
stack.push().translateWithMultiplication(Vector3f(world.client.camera.pos))
|
||||||
|
|
||||||
|
state.programs.textured.use()
|
||||||
|
state.programs.textured.transform = stack.last
|
||||||
|
state.activeTexture = 0
|
||||||
|
state.programs.textured.texture = 0
|
||||||
|
|
||||||
|
state.flat2DTexturedQuads.singleSprite(sprite.width / PIXELS_IN_STARBOUND_UNITf, sprite.height / PIXELS_IN_STARBOUND_UNITf, 0.0, sprite.transformer)
|
||||||
|
stack.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,12 @@ class BoundSprite(
|
|||||||
/**
|
/**
|
||||||
* Настоящая ширина спрайта, в пикселях
|
* Настоящая ширина спрайта, в пикселях
|
||||||
*/
|
*/
|
||||||
inline val width get() = sprite.width(texture.width)
|
val width = sprite.width(texture.width)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Настоящая высота спрайта, в пикселях
|
* Настоящая высота спрайта, в пикселях
|
||||||
*/
|
*/
|
||||||
inline val height get() = sprite.height(texture.height)
|
val height = sprite.height(texture.height)
|
||||||
|
|
||||||
override val u0: Float
|
override val u0: Float
|
||||||
override val v0: Float
|
override val v0: Float
|
||||||
@ -41,24 +41,3 @@ class BoundSprite(
|
|||||||
fun bind() = texture.bind()
|
fun bind() = texture.bind()
|
||||||
val transformer = QuadTransformers.uv(u0, v0, u1, v1)
|
val transformer = QuadTransformers.uv(u0, v0, u1, v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Создаёт связку текстуры-атласа + координгат спрайта на ней
|
|
||||||
*/
|
|
||||||
fun ImageReference.bind(texture: GLTexture2D): BoundSprite {
|
|
||||||
return BoundSprite(sprite!!, texture)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Создаёт связку текстуры-атласа, которая загружается через [GLStateTracker.loadNamedTextureSafe] + координгат спрайта на ней
|
|
||||||
*/
|
|
||||||
fun ImageReference.bind(state: GLStateTracker): BoundSprite {
|
|
||||||
return BoundSprite(sprite!!, state.loadNamedTextureSafe(imagePath.value!!))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Создаёт связку текстуры-атласа + координгат спрайта на ней
|
|
||||||
*/
|
|
||||||
fun AtlasConfiguration.Sprite.bind(texture: GLTexture2D): BoundSprite {
|
|
||||||
return BoundSprite(this, texture)
|
|
||||||
}
|
|
||||||
|
@ -32,6 +32,13 @@ class FrameAnimator(
|
|||||||
var frame = 0
|
var frame = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Эффективное преобразование [frame] в строку
|
||||||
|
*/
|
||||||
|
val frameString: String get() {
|
||||||
|
return framenames.getOrNull(frame) ?: frame.toString()
|
||||||
|
}
|
||||||
|
|
||||||
private var counter = 0.0
|
private var counter = 0.0
|
||||||
private var lastRender = time.seconds
|
private var lastRender = time.seconds
|
||||||
|
|
||||||
@ -73,4 +80,14 @@ class FrameAnimator(
|
|||||||
lastRender = time.seconds
|
lastRender = time.seconds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val framenames = ArrayList<String>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (i in 0 .. 500) {
|
||||||
|
framenames.add(i.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
|
|
||||||
|
class RebindableSprite(
|
||||||
|
val client: StarboundClient,
|
||||||
|
ref: ImageReference,
|
||||||
|
val renderParams: ((String) -> String?)? = null
|
||||||
|
) {
|
||||||
|
var sprite: BoundSprite? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
var ref: ImageReference = ref
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
val unbound = ref.sprite
|
||||||
|
|
||||||
|
if (unbound != null) {
|
||||||
|
sprite = BoundSprite(unbound, client.gl.loadTexture(ref.imagePath.value!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет [ref] и [sprite] по значениям, которые выдаст [renderParams]
|
||||||
|
*
|
||||||
|
* Возвращает новое значение [sprite]
|
||||||
|
*/
|
||||||
|
fun update(): BoundSprite? {
|
||||||
|
client.gl.ensureSameThread()
|
||||||
|
|
||||||
|
if (renderParams == null)
|
||||||
|
return sprite
|
||||||
|
|
||||||
|
val newRef = ref.with(renderParams)
|
||||||
|
|
||||||
|
if (newRef !== ref) {
|
||||||
|
ref = newRef
|
||||||
|
sprite = null
|
||||||
|
val unbound = newRef.sprite
|
||||||
|
|
||||||
|
if (unbound != null) {
|
||||||
|
sprite = BoundSprite(unbound, client.gl.loadTexture(newRef.imagePath.value!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprite
|
||||||
|
}
|
||||||
|
}
|
@ -186,7 +186,7 @@ private class ModifierEqualityTester(val definition: MaterialModifier) : Equalit
|
|||||||
|
|
||||||
class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
||||||
val state get() = renderers.state
|
val state get() = renderers.state
|
||||||
val texture = state.loadNamedTexture(def.renderParameters.texture.imagePath.value!!).also {
|
val texture = state.loadTexture(def.renderParameters.texture.imagePath.value!!).also {
|
||||||
it.textureMagFilter = GL_NEAREST
|
it.textureMagFilter = GL_NEAREST
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,9 +255,9 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
for (renderPiece in matchPiece.pieces) {
|
for (renderPiece in matchPiece.pieces) {
|
||||||
if (renderPiece.piece.texture != null) {
|
if (renderPiece.piece.texture != null) {
|
||||||
val program = if (background) {
|
val program = if (background) {
|
||||||
renderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
|
renderers.background(state.loadTexture(renderPiece.piece.texture!!))
|
||||||
} else {
|
} else {
|
||||||
renderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!))
|
renderers.foreground(state.loadTexture(renderPiece.piece.texture!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
tesselateAt(self, renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset, isModifier)
|
tesselateAt(self, renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset, isModifier)
|
||||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.client.render.entity
|
|||||||
|
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||||
import ru.dbotthepony.kstarbound.client.ClientChunk
|
import ru.dbotthepony.kstarbound.client.ClientChunk
|
||||||
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||||
@ -13,7 +14,8 @@ import java.io.Closeable
|
|||||||
*
|
*
|
||||||
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
|
* Считается, что процесс отрисовки ограничен лишь одним слоем (т.е. отрисовка происходит в один проход)
|
||||||
*/
|
*/
|
||||||
open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open var chunk: ClientChunk?) : Closeable {
|
open class EntityRenderer(val client: StarboundClient, val entity: Entity, open var chunk: ClientChunk?) : Closeable {
|
||||||
|
inline val state: GLStateTracker get() = client.gl
|
||||||
open val renderPos: Vector2d get() = entity.position
|
open val renderPos: Vector2d get() = entity.position
|
||||||
|
|
||||||
open fun render(stack: Matrix4fStack) {
|
open fun render(stack: Matrix4fStack) {
|
||||||
@ -39,20 +41,20 @@ open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open va
|
|||||||
*/
|
*/
|
||||||
const val Z_LEVEL_ENTITIES = 30000
|
const val Z_LEVEL_ENTITIES = 30000
|
||||||
|
|
||||||
private val renderers = Reference2ObjectOpenHashMap<Class<*>, (state: GLStateTracker, entity: Entity, chunk: ClientChunk?) -> EntityRenderer>()
|
private val renderers = Reference2ObjectOpenHashMap<Class<*>, (client: StarboundClient, entity: Entity, chunk: ClientChunk?) -> EntityRenderer>()
|
||||||
|
|
||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
fun <T : Entity> registerRenderer(clazz: Class<T>, renderer: (state: GLStateTracker, entity: T, chunk: ClientChunk?) -> EntityRenderer) {
|
fun <T : Entity> registerRenderer(clazz: Class<T>, renderer: (client: StarboundClient, entity: T, chunk: ClientChunk?) -> EntityRenderer) {
|
||||||
check(renderers.put(clazz, renderer as (state: GLStateTracker, entity: Entity, chunk: ClientChunk?) -> EntityRenderer) == null) { "Already has renderer for ${clazz.canonicalName}!" }
|
check(renderers.put(clazz, renderer as (client: StarboundClient, entity: Entity, chunk: ClientChunk?) -> EntityRenderer) == null) { "Already has renderer for ${clazz.canonicalName}!" }
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Entity> registerRenderer(noinline renderer: (state: GLStateTracker, entity: T, chunk: ClientChunk?) -> EntityRenderer) {
|
inline fun <reified T : Entity> registerRenderer(noinline renderer: (client: StarboundClient, entity: T, chunk: ClientChunk?) -> EntityRenderer) {
|
||||||
registerRenderer(T::class.java, renderer)
|
registerRenderer(T::class.java, renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRender(state: GLStateTracker, entity: Entity, chunk: ClientChunk? = null): EntityRenderer {
|
fun getRender(client: StarboundClient, entity: Entity, chunk: ClientChunk? = null): EntityRenderer {
|
||||||
val factory = renderers[entity::class.java] ?: return EntityRenderer(state, entity, chunk)
|
val factory = renderers[entity::class.java] ?: return EntityRenderer(client, entity, chunk)
|
||||||
return factory.invoke(state, entity, chunk)
|
return factory.invoke(client, entity, chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -2,14 +2,14 @@ package ru.dbotthepony.kstarbound.client.render.entity
|
|||||||
|
|
||||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
import ru.dbotthepony.kstarbound.client.ClientChunk
|
import ru.dbotthepony.kstarbound.client.ClientChunk
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.client.render.bind
|
import ru.dbotthepony.kstarbound.client.render.RebindableSprite
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
||||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||||
|
|
||||||
class ItemRenderer(state: GLStateTracker, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(state, entity, chunk) {
|
class ItemRenderer(client: StarboundClient, entity: ItemEntity, chunk: ClientChunk?) : EntityRenderer(client, entity, chunk) {
|
||||||
private val def = entity.def
|
private val def = entity.def
|
||||||
private val textures = def.inventoryIcon?.stream()?.map { it.image.bind(state) }?.toList() ?: listOf()
|
private val textures = def.inventoryIcon?.stream()?.map { RebindableSprite(client, it.image) }?.toList() ?: listOf()
|
||||||
|
|
||||||
override fun render(stack: Matrix4fStack) {
|
override fun render(stack: Matrix4fStack) {
|
||||||
if (textures.isEmpty())
|
if (textures.isEmpty())
|
||||||
@ -20,10 +20,10 @@ class ItemRenderer(state: GLStateTracker, entity: ItemEntity, chunk: ClientChunk
|
|||||||
state.activeTexture = 0
|
state.activeTexture = 0
|
||||||
state.programs.textured.texture = 0
|
state.programs.textured.texture = 0
|
||||||
|
|
||||||
for (texture in textures) {
|
for (unbound in textures) {
|
||||||
texture.bind()
|
val sprite = unbound.update() ?: continue
|
||||||
|
sprite.texture.bind()
|
||||||
state.flat2DTexturedQuads.singleSprite(texture.width / PIXELS_IN_STARBOUND_UNITf, texture.height / PIXELS_IN_STARBOUND_UNITf, entity.movement.angle, texture.transformer)
|
state.flat2DTexturedQuads.singleSprite(sprite.width / PIXELS_IN_STARBOUND_UNITf, sprite.height / PIXELS_IN_STARBOUND_UNITf, entity.movement.angle, sprite.transformer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,16 +58,25 @@ class AtlasConfiguration private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return [Sprite] если он существует с данным [name], или `null`
|
||||||
|
*/
|
||||||
operator fun get(name: String): Sprite? {
|
operator fun get(name: String): Sprite? {
|
||||||
return sprites[name]
|
return sprites[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return [Sprite] если он существует с данным [name], или `null`
|
||||||
|
*/
|
||||||
operator fun get(name: Int): Sprite? {
|
operator fun get(name: Int): Sprite? {
|
||||||
return intIndexed[name]
|
return intIndexed[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return [Sprite] если он существует с данным [name], или первый спрайт в атласе
|
||||||
|
*/
|
||||||
fun any(name: String): Sprite {
|
fun any(name: String): Sprite {
|
||||||
return get(name) ?: first
|
return get(name) ?: any()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val any by lazy { get("root") ?: get("0") ?: get("default") ?: first }
|
private val any by lazy { get("root") ?: get("0") ?: get("default") ?: first }
|
||||||
|
@ -13,7 +13,7 @@ import ru.dbotthepony.kstarbound.util.PathStack
|
|||||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see [AtlasConfiguration.Companion.get]
|
* @see [AtlasConfiguration.Registry.get]
|
||||||
*/
|
*/
|
||||||
class ImageReference private constructor(
|
class ImageReference private constructor(
|
||||||
val raw: DirectAssetReference,
|
val raw: DirectAssetReference,
|
||||||
@ -26,8 +26,9 @@ class ImageReference private constructor(
|
|||||||
* Спрайт, на которое ссылается данный референс, или `null` если:
|
* Спрайт, на которое ссылается данный референс, или `null` если:
|
||||||
* * [atlas] равен `null`
|
* * [atlas] равен `null`
|
||||||
* * [spritePath] является шаблоном и определены не все значения
|
* * [spritePath] является шаблоном и определены не все значения
|
||||||
|
* * [spritePath] не является правильным именем спрайта внутри [atlas] (смотрим [AtlasConfiguration.get])
|
||||||
*/
|
*/
|
||||||
val sprite by lazy {
|
val sprite by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
if (atlas == null)
|
if (atlas == null)
|
||||||
null
|
null
|
||||||
else if (spritePath == null)
|
else if (spritePath == null)
|
||||||
@ -38,7 +39,15 @@ class ImageReference private constructor(
|
|||||||
|
|
||||||
fun with(values: (String) -> String?): ImageReference {
|
fun with(values: (String) -> String?): ImageReference {
|
||||||
val imagePath = this.imagePath.with(values)
|
val imagePath = this.imagePath.with(values)
|
||||||
val spritePath = this.spritePath?.with(values)
|
var spritePath = this.spritePath?.with(values)
|
||||||
|
|
||||||
|
if (spritePath == null) {
|
||||||
|
val frame = values.invoke("frame")
|
||||||
|
|
||||||
|
if (frame != null) {
|
||||||
|
spritePath = SBPattern.FRAME.with(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (imagePath != this.imagePath || spritePath != this.spritePath) {
|
if (imagePath != this.imagePath || spritePath != this.spritePath) {
|
||||||
if (imagePath != this.imagePath) {
|
if (imagePath != this.imagePath) {
|
||||||
|
@ -52,8 +52,11 @@ class SBPattern private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun resolve(values: (String) -> String?): String? {
|
fun resolve(values: (String) -> String?): String? {
|
||||||
if (names.isEmpty())
|
if (names.isEmpty()) {
|
||||||
return raw
|
return raw
|
||||||
|
} else if (pieces.size == 1) {
|
||||||
|
return pieces[0].resolve(values, params::get)
|
||||||
|
}
|
||||||
|
|
||||||
val buffer = ArrayList<String>(pieces.size)
|
val buffer = ArrayList<String>(pieces.size)
|
||||||
|
|
||||||
@ -116,6 +119,9 @@ class SBPattern private constructor(
|
|||||||
@JvmField
|
@JvmField
|
||||||
val EMPTY = raw("")
|
val EMPTY = raw("")
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val FRAME = of("<frame>")
|
||||||
|
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == SBPattern::class.java) {
|
if (type.rawType == SBPattern::class.java) {
|
||||||
return object : TypeAdapter<SBPattern>() {
|
return object : TypeAdapter<SBPattern>() {
|
||||||
@ -126,7 +132,7 @@ class SBPattern private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): SBPattern? {
|
override fun read(`in`: JsonReader): SBPattern? {
|
||||||
return interner.intern(of(strings.read(`in`) ?: return null))
|
return of(strings.read(`in`) ?: return null)
|
||||||
}
|
}
|
||||||
} as TypeAdapter<T>
|
} as TypeAdapter<T>
|
||||||
}
|
}
|
||||||
@ -159,7 +165,7 @@ class SBPattern private constructor(
|
|||||||
throw IllegalArgumentException("Malformed pattern string: $raw")
|
throw IllegalArgumentException("Malformed pattern string: $raw")
|
||||||
}
|
}
|
||||||
|
|
||||||
pieces.add(Piece(name = raw.substring(open + 1, closing - 1)))
|
pieces.add(Piece(name = raw.substring(open + 1, closing)))
|
||||||
i = closing + 1
|
i = closing + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user