непонятно как делать animator
This commit is contained in:
parent
ed12e99d43
commit
0052adf89a
@ -44,6 +44,7 @@ import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.InternedStringAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.NothingAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
||||
@ -110,6 +111,8 @@ class Starbound : ISBFileLocator {
|
||||
registerTypeAdapter(InternedStringAdapter(stringInterner))
|
||||
registerTypeAdapter(InternedJsonElementAdapter(stringInterner))
|
||||
|
||||
registerTypeAdapter(Nothing::class.java, NothingAdapter)
|
||||
|
||||
// Обработчик @JsonImplementation
|
||||
registerTypeAdapterFactory(JsonImplementationTypeFactory)
|
||||
|
||||
@ -387,7 +390,7 @@ class Starbound : ISBFileLocator {
|
||||
loadStage(callback, _particles, ext2files["particle"] ?: listOf())
|
||||
|
||||
pathStack.block("/") {
|
||||
playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java)
|
||||
//playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java)
|
||||
}
|
||||
|
||||
initializing = false
|
||||
|
@ -11,8 +11,8 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.quad
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.shadowLine
|
||||
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
|
||||
import ru.dbotthepony.kstarbound.client.render.entity.EntityRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.GPULightRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.TileLayerList
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.world.*
|
||||
@ -166,22 +166,6 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
backgroundRenderer.loadRenderers(getBackgroundView())
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовывает всю геометрию напрямую
|
||||
*/
|
||||
fun render(stack: Matrix4fStack) {
|
||||
backgroundRenderer.render(stack)
|
||||
foregroundRenderer.render(stack)
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовывает всю геометрию напрямую, с проверкой, изменился ли чанк
|
||||
*/
|
||||
fun bakeAndRender(stack: Matrix4fStack) {
|
||||
backgroundRenderer.bakeAndRender(stack, this::getBackgroundView)
|
||||
foregroundRenderer.bakeAndRender(stack, this::getForegroundView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Тесселирует "статичную" геометрию в builders (к примеру тайлы), с проверкой, изменилось ли что либо,
|
||||
* и загружает её в видеопамять.
|
||||
@ -293,15 +277,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Хранит состояние отрисовки этого чанка
|
||||
*
|
||||
* Должен быть использован только один раз, после выкинут, иначе поведение
|
||||
* кода невозможно будет предсказать
|
||||
*/
|
||||
inner class OneShotRenderer constructor(val origin: ChunkPos = pos) : ILayeredRenderer, GPULightRenderer.ShadowGeometryRenderer {
|
||||
private val layerQueue = ArrayDeque<Pair<(Matrix4fStack) -> Unit, Int>>()
|
||||
|
||||
inner class Renderer(val renderOrigin: ChunkPos = pos) : GPULightRenderer.ShadowGeometryRenderer {
|
||||
override fun renderHardGeometry(
|
||||
renderer: GPULightRenderer,
|
||||
lightPosition: Vector2f,
|
||||
@ -309,7 +285,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
stack: Matrix4fStack,
|
||||
program: GLHardLightGeometryProgram
|
||||
) {
|
||||
if (!intersectCircleRectangle(lightPosition, lightRadius, origin.x * CHUNK_SIZEf, origin.y * CHUNK_SIZEf, (origin.x + 1) * CHUNK_SIZEf, (origin.y + 1) * CHUNK_SIZEf)) {
|
||||
if (!intersectCircleRectangle(lightPosition, lightRadius, renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf, (renderOrigin.x + 1) * CHUNK_SIZEf, (renderOrigin.y + 1) * CHUNK_SIZEf)) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -319,16 +295,16 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
if (intersectCircleRectangle(
|
||||
lightPosition,
|
||||
lightRadius,
|
||||
origin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
origin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
origin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
origin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE)
|
||||
renderOrigin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
renderOrigin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
renderOrigin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
renderOrigin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE)
|
||||
) {
|
||||
if (!setOnce) {
|
||||
program.localToWorldTransform.set(
|
||||
Matrix4f.IDENTITY.translateWithMultiplication(
|
||||
Vector3f(x = origin.x * CHUNK_SIZEf,
|
||||
y = origin.y * CHUNK_SIZEf)))
|
||||
Vector3f(x = renderOrigin.x * CHUNK_SIZEf,
|
||||
y = renderOrigin.y * CHUNK_SIZEf)))
|
||||
|
||||
setOnce = true
|
||||
}
|
||||
@ -345,7 +321,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
stack: Matrix4fStack,
|
||||
program: GLSoftLightGeometryProgram
|
||||
) {
|
||||
if (!intersectCircleRectangle(lightPosition, lightRadius, origin.x * CHUNK_SIZEf, origin.y * CHUNK_SIZEf, (origin.x + 1) * CHUNK_SIZEf, (origin.y + 1) * CHUNK_SIZEf)) {
|
||||
if (!intersectCircleRectangle(lightPosition, lightRadius, renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf, (renderOrigin.x + 1) * CHUNK_SIZEf, (renderOrigin.y + 1) * CHUNK_SIZEf)) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -353,18 +329,18 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
|
||||
for (geometry in shadowGeometry) {
|
||||
if (intersectCircleRectangle(
|
||||
lightPosition,
|
||||
lightRadius,
|
||||
origin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
origin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
origin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
origin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE)
|
||||
lightPosition,
|
||||
lightRadius,
|
||||
renderOrigin.x * CHUNK_SIZEf + geometry.x * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
renderOrigin.y * CHUNK_SIZEf + geometry.y * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
renderOrigin.x * CHUNK_SIZEf + (geometry.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE,
|
||||
renderOrigin.y * CHUNK_SIZEf + (geometry.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE)
|
||||
) {
|
||||
if (!setOnce) {
|
||||
program.localToWorldTransform.set(
|
||||
Matrix4f.IDENTITY.translateWithMultiplication(
|
||||
Vector3f(x = origin.x * CHUNK_SIZEf,
|
||||
y = origin.y * CHUNK_SIZEf)))
|
||||
Vector3f(x = renderOrigin.x * CHUNK_SIZEf,
|
||||
y = renderOrigin.y * CHUNK_SIZEf)))
|
||||
|
||||
setOnce = true
|
||||
}
|
||||
@ -374,26 +350,34 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
fun addLayers(layers: LayeredRenderer) {
|
||||
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
|
||||
layerQueue.add(baked::renderStacked to (zLevel + Z_LEVEL_BACKGROUND))
|
||||
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
||||
it.push().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf)
|
||||
baked.renderStacked(it)
|
||||
it.pop()
|
||||
}
|
||||
}
|
||||
|
||||
for ((baked, zLevel) in foregroundRenderer.bakedMeshes) {
|
||||
layerQueue.add(baked::renderStacked to zLevel)
|
||||
layers.add(zLevel) {
|
||||
it.push().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf)
|
||||
baked.renderStacked(it)
|
||||
it.pop()
|
||||
}
|
||||
}
|
||||
|
||||
for (renderer in entityRenderers.values) {
|
||||
layerQueue.add(lambda@{ it: Matrix4fStack ->
|
||||
layers.add(renderer.layer) {
|
||||
val relative = renderer.renderPos - posVector2d
|
||||
it.push().translateWithMultiplication(relative.x.toFloat(), relative.y.toFloat())
|
||||
it.push().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf + relative.x.toFloat(), renderOrigin.y * CHUNK_SIZEf + relative.y.toFloat())
|
||||
renderer.render(it)
|
||||
it.pop()
|
||||
Unit
|
||||
} to renderer.layer)
|
||||
}
|
||||
}
|
||||
|
||||
layerQueue.add({ it: Matrix4fStack ->
|
||||
layers.add(Z_LEVEL_LIQUID) {
|
||||
it.push().translateWithMultiplication(renderOrigin.x * CHUNK_SIZEf, renderOrigin.y * CHUNK_SIZEf)
|
||||
val types = ArrayList<LiquidDefinition>()
|
||||
|
||||
for (x in 0 until CHUNK_SIZE) {
|
||||
@ -431,44 +415,10 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
builder.upload()
|
||||
builder.draw()
|
||||
}
|
||||
} to Z_LEVEL_LIQUID)
|
||||
|
||||
layerQueue.sortBy {
|
||||
return@sortBy it.second
|
||||
it.pop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int {
|
||||
if (layerQueue.isEmpty())
|
||||
return Int.MIN_VALUE
|
||||
|
||||
stack.push().translateWithMultiplication(x = origin.x * CHUNK_SIZEf, y = origin.y * CHUNK_SIZEf)
|
||||
var pair = layerQueue.last()
|
||||
|
||||
while (pair.second >= zPos) {
|
||||
pair.first.invoke(stack)
|
||||
|
||||
layerQueue.removeLast()
|
||||
|
||||
if (layerQueue.isEmpty()) {
|
||||
stack.pop()
|
||||
return Int.MIN_VALUE
|
||||
}
|
||||
|
||||
pair = layerQueue.last()
|
||||
}
|
||||
|
||||
stack.pop()
|
||||
return layerQueue.last().second
|
||||
}
|
||||
|
||||
override fun bottomMostZLevel(): Int {
|
||||
if (layerQueue.isEmpty()) {
|
||||
return Int.MIN_VALUE
|
||||
}
|
||||
|
||||
return layerQueue.last().second
|
||||
}
|
||||
}
|
||||
|
||||
private val entityRenderers = HashMap<Entity, EntityRenderer>()
|
||||
|
@ -1,13 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.client
|
||||
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
|
||||
import ru.dbotthepony.kstarbound.client.gl.vertex.quadZ
|
||||
import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
||||
import ru.dbotthepony.kstarbound.util.DoubleEdgeProgression
|
||||
import ru.dbotthepony.kstarbound.world.*
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
@ -38,18 +32,17 @@ class ClientWorld(
|
||||
size: AABB,
|
||||
isScreenspaceRender: Boolean = true
|
||||
) {
|
||||
val determineRenderers = ArrayList<ILayeredRenderer>()
|
||||
|
||||
val layers = LayeredRenderer()
|
||||
client.lightRenderer.begin()
|
||||
|
||||
for (chunk in collectPositionAware(size.encasingChunkPosAABB())) {
|
||||
val renderer = chunk.second.OneShotRenderer(chunk.first)
|
||||
determineRenderers.add(renderer)
|
||||
val renderer = chunk.second.Renderer(chunk.first)
|
||||
renderer.addLayers(layers)
|
||||
chunk.second.bake()
|
||||
//client.lightRenderer.addShadowGeometry(renderer)
|
||||
}
|
||||
|
||||
renderLayeredList(client.gl.matrixStack, determineRenderers)
|
||||
layers.render(client.gl.matrixStack)
|
||||
/*
|
||||
for ((lightPosition, color) in listOf(
|
||||
(client.screenToWorld(client.mouseCoordinatesF)) to Color.RED,
|
||||
|
@ -18,6 +18,8 @@ import ru.dbotthepony.kstarbound.client.render.Camera
|
||||
import ru.dbotthepony.kstarbound.client.render.GPULightRenderer
|
||||
import ru.dbotthepony.kstarbound.client.render.TextAlignY
|
||||
import ru.dbotthepony.kstarbound.client.render.TileRenderers
|
||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||
import ru.dbotthepony.kstarbound.util.PausableTimeSource
|
||||
import ru.dbotthepony.kstarbound.util.formatBytesShort
|
||||
import ru.dbotthepony.kvector.matrix.nfloat.Matrix4f
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
@ -25,6 +27,7 @@ import ru.dbotthepony.kvector.vector.Color
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.nfloat.Vector2f
|
||||
import ru.dbotthepony.kvector.vector.nfloat.Vector3f
|
||||
import java.io.Closeable
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.*
|
||||
@ -32,7 +35,8 @@ import java.util.concurrent.locks.LockSupport
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class StarboundClient(val starbound: Starbound) : AutoCloseable {
|
||||
class StarboundClient(val starbound: Starbound) : Closeable {
|
||||
val time = PausableTimeSource(JVMTimeSource.INSTANCE)
|
||||
val window: Long
|
||||
val camera = Camera(this)
|
||||
val input = UserInput()
|
||||
|
@ -4,6 +4,10 @@ import org.lwjgl.opengl.GL46
|
||||
import ru.dbotthepony.kstarbound.client.gl.VertexBufferObject
|
||||
import ru.dbotthepony.kstarbound.util.ByteBufferOutputStream
|
||||
|
||||
/**
|
||||
* Создаёт буфер для данных вне кучи, записывает данные напрямую в него,
|
||||
* при [upload] загружает данные из буфера напрямую в память видеокарты.
|
||||
*/
|
||||
open class DirectVertexBuilder<T : DirectVertexBuilder<T>>(
|
||||
attributes: GLAttributeList,
|
||||
type: GeometryType,
|
||||
|
@ -1,7 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.client.gl.vertex
|
||||
|
||||
import org.lwjgl.opengl.GL46
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
|
||||
import java.io.Closeable
|
||||
|
||||
@ -48,4 +50,21 @@ class StreamVertexBuilder(
|
||||
vbo.close()
|
||||
ebo.close()
|
||||
}
|
||||
|
||||
fun singleSprite(x: Float, y: Float, width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) {
|
||||
begin()
|
||||
|
||||
quadRotatedZ(x, y, width, height, z, 0f, 0f, angle, transformer)
|
||||
|
||||
upload()
|
||||
draw()
|
||||
}
|
||||
|
||||
fun singleSprite(width: Float, height: Float, z: Float = 5f, angle: Double = 0.0, transformer: QuadVertexTransformer) {
|
||||
singleSprite(-width / 2f, -height / 2f, width / 2f, height / 2f, z, angle, transformer)
|
||||
}
|
||||
|
||||
fun singleSprite(width: Float, height: Float, angle: Double = 0.0, transformer: QuadVertexTransformer) {
|
||||
singleSprite(-width / 2f, -height / 2f, width / 2f, height / 2f, 0f, angle, transformer)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import ru.dbotthepony.kstarbound.client.ClientWorld
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||
|
||||
class Animator(
|
||||
val world: ClientWorld,
|
||||
val def: AnimationDefinition
|
||||
) {
|
||||
val frameAnimator: FrameAnimator?
|
||||
|
||||
init {
|
||||
if (def.frames != null && def.animationCycle != null && def.frameNumber != null) {
|
||||
frameAnimator = FrameAnimator(lastFrame = def.frameNumber - 1, time = world.client.time, animationCycle = def.animationCycle)
|
||||
} else {
|
||||
frameAnimator = null
|
||||
}
|
||||
}
|
||||
|
||||
fun render(stack: Matrix4fStack) {
|
||||
|
||||
}
|
||||
}
|
@ -1,20 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import org.lwjgl.glfw.GLFW.glfwGetTime
|
||||
import ru.dbotthepony.kstarbound.util.ITimeSource
|
||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||
|
||||
/**
|
||||
* Таймер для анимирования набора спрайтов
|
||||
*/
|
||||
open class FrameAnimator(
|
||||
class FrameAnimator(
|
||||
/**
|
||||
* Первый кадр в анимации
|
||||
*/
|
||||
var firstFrame: Int = 0,
|
||||
val firstFrame: Int = 0,
|
||||
|
||||
/**
|
||||
* Последний кадр в анимации
|
||||
*/
|
||||
var lastFrame: Int,
|
||||
val lastFrame: Int,
|
||||
|
||||
/**
|
||||
* Сколько времени занимает один кадр
|
||||
@ -25,38 +26,31 @@ open class FrameAnimator(
|
||||
* Зациклить ли анимацию
|
||||
*/
|
||||
var animationLoops: Boolean = true,
|
||||
|
||||
val time: ITimeSource = JVMTimeSource.INSTANCE
|
||||
) {
|
||||
var frame = 0
|
||||
private set
|
||||
|
||||
/**
|
||||
* Возвращает разницу между последним и первым кадром анимации
|
||||
*/
|
||||
val frameDiff get() = lastFrame - firstFrame
|
||||
|
||||
private val initial = glfwGetTime()
|
||||
private var lastRender = initial
|
||||
|
||||
/**
|
||||
* Сколько времени прошло с момента последнего кадра
|
||||
*/
|
||||
val delta get() = glfwGetTime() - lastRender
|
||||
|
||||
private var counter = 0.0
|
||||
private var lastRender = time.seconds
|
||||
|
||||
/**
|
||||
* Проверяет glfw таймер и продвигает фрейм анимации
|
||||
* Разница между последним и первым кадром анимации
|
||||
*/
|
||||
fun advance() {
|
||||
val frameDiff = lastFrame - firstFrame
|
||||
|
||||
/**
|
||||
* Проверяет таймер на [seconds] и продвигает фрейм анимации
|
||||
*/
|
||||
fun advance(seconds: Double) {
|
||||
if (frameDiff == 0)
|
||||
return
|
||||
|
||||
if (frame + firstFrame >= lastFrame && !animationLoops) {
|
||||
if (frame + firstFrame >= lastFrame && !animationLoops)
|
||||
return
|
||||
}
|
||||
|
||||
counter += delta / animationCycle
|
||||
lastRender = glfwGetTime()
|
||||
counter += seconds / animationCycle
|
||||
|
||||
if (counter >= 1.0) {
|
||||
val desired = frame + counter.toInt()
|
||||
@ -69,4 +63,14 @@ open class FrameAnimator(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет таймер используя [time] и продвигает фрейм анимации
|
||||
*/
|
||||
fun advance() {
|
||||
if (frameDiff != 0) {
|
||||
advance(time.seconds - lastRender)
|
||||
lastRender = time.seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||
|
||||
/**
|
||||
* Интерфейс для отрисовки комплексных объектов, в которых множество слоёв, который
|
||||
* определяет лишь один метод: [renderLayer]
|
||||
*
|
||||
* Используется вместе с другими [ILayeredRenderer], где необходимо отрисовывать комплексную сцену,
|
||||
* реализуя ручную сортировку геометрии.
|
||||
*/
|
||||
interface ILayeredRenderer {
|
||||
/**
|
||||
* Главный метод отрисовки данной стопки слоёв. Вызов данного метода может что-либо
|
||||
* отрисовать, а может вообще ничего не отрисовать.
|
||||
*
|
||||
* zLevel всегда положителен, и указывает на то, какой слой (или все слои за) надо отрисовать,
|
||||
* т.е. при вызове этого метода отрисовываются все слои, у которых z позиция >= [zPos]
|
||||
*
|
||||
* Возвращается zNew следующего слоя (такой, что [zPos] > zNew).
|
||||
*
|
||||
* Если следующего слоя нет, вернуть [Int.MIN_VALUE], и данный объект
|
||||
* будет считаться отрисованным.
|
||||
*/
|
||||
fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int
|
||||
|
||||
/**
|
||||
* Возвращает наибольшее zPos в данной стопке.
|
||||
*
|
||||
* Если стопка пуста, то необходимо вернуть [Int.MIN_VALUE].
|
||||
*
|
||||
* В зависимости от сцены, которую необходимо отрисовать,
|
||||
* [renderLayerFromStack] может быть вызван сразу с этим же значением,
|
||||
* если этот объект имеет самый дальний слой
|
||||
*/
|
||||
fun bottomMostZLevel(): Int
|
||||
}
|
||||
|
||||
fun renderLayeredList(transform: Matrix4fStack, potentialRenderers: List<ILayeredRenderer>): Int {
|
||||
val renderers = ArrayList<ILayeredRenderer>(potentialRenderers.size)
|
||||
var bottomMost = Int.MIN_VALUE
|
||||
|
||||
for (render in potentialRenderers) {
|
||||
val zLevel = render.bottomMostZLevel()
|
||||
|
||||
if (zLevel > Int.MIN_VALUE) {
|
||||
bottomMost = bottomMost.coerceAtLeast(zLevel)
|
||||
renderers.add(render)
|
||||
}
|
||||
}
|
||||
|
||||
var lastBottom = bottomMost
|
||||
var renderCalls = 0
|
||||
|
||||
while (lastBottom != Int.MIN_VALUE && renderers.isNotEmpty()) {
|
||||
var newBottom = Int.MIN_VALUE
|
||||
|
||||
for (i in renderers.size - 1 downTo 0) {
|
||||
val renderer = renderers[i]
|
||||
|
||||
val newLevel = renderer.renderLayerFromStack(lastBottom, transform)
|
||||
|
||||
renderCalls++
|
||||
|
||||
if (newLevel == Int.MIN_VALUE) {
|
||||
renderers.removeAt(i)
|
||||
} else {
|
||||
newBottom = newBottom.coerceAtLeast(newLevel)
|
||||
}
|
||||
}
|
||||
|
||||
lastBottom = newBottom
|
||||
}
|
||||
|
||||
return renderCalls
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
||||
import ru.dbotthepony.kvector.matrix.Matrix4fStack
|
||||
|
||||
/**
|
||||
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности
|
||||
*/
|
||||
class LayeredRenderer {
|
||||
private val layers = Int2ObjectAVLTreeMap<ArrayList<(Matrix4fStack) -> Unit>>()
|
||||
|
||||
/**
|
||||
* Сортировка [layer] происходит от дальнего (БОЛЬШЕ!) к ближнему (МЕНЬШЕ!)
|
||||
*
|
||||
* Пример:
|
||||
* `8 -> 6 -> 4 -> 1 -> -4 -> -7`
|
||||
*/
|
||||
fun add(layer: Int, renderer: (Matrix4fStack) -> Unit) {
|
||||
layers.computeIfAbsent(-layer, Int2ObjectFunction { ArrayList() }).add(renderer)
|
||||
}
|
||||
|
||||
fun render(stack: Matrix4fStack) {
|
||||
for (list in layers.values) {
|
||||
for (renderer in list) {
|
||||
renderer.invoke(stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.client.render
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||
import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration
|
||||
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||
|
||||
class SpriteAnimator(
|
||||
val sprites: List<BoundSprite>,
|
||||
animationCycle: Double,
|
||||
animationLoops: Boolean = true,
|
||||
firstFrame: Int = 0,
|
||||
lastFrame: Int = sprites.size - 1
|
||||
) : FrameAnimator(
|
||||
firstFrame = firstFrame,
|
||||
lastFrame = lastFrame,
|
||||
animationCycle = animationCycle,
|
||||
animationLoops = animationLoops,
|
||||
) {
|
||||
constructor(
|
||||
image: GLTexture2D,
|
||||
atlas: AtlasConfiguration,
|
||||
animationCycle: Double,
|
||||
animationLoops: Boolean = true,
|
||||
firstFrame: Int = 0,
|
||||
lastFrame: Int = atlas.spriteList.size - 1
|
||||
) : this(
|
||||
atlas.spriteList.stream().map { it.bind(image) }.collect(ImmutableList.toImmutableList()),
|
||||
animationCycle = animationCycle,
|
||||
animationLoops = animationLoops,
|
||||
firstFrame = firstFrame,
|
||||
lastFrame = lastFrame,
|
||||
)
|
||||
|
||||
constructor(
|
||||
image: ImageReference,
|
||||
state: GLStateTracker,
|
||||
animationCycle: Double,
|
||||
animationLoops: Boolean = true,
|
||||
firstFrame: Int = 0,
|
||||
lastFrame: Int = image.config.spriteList.size - 1
|
||||
) : this(state.loadNamedTextureSafe(image.image.fullPath), image.config, animationCycle, animationLoops, firstFrame, lastFrame)
|
||||
|
||||
val sprite get() = sprites[frame]
|
||||
}
|
||||
|
||||
fun ImageReference.makeSpriteAnimator(
|
||||
state: GLStateTracker,
|
||||
animationCycle: Double,
|
||||
animationLoops: Boolean = true,
|
||||
firstFrame: Int = 0,
|
||||
lastFrame: Int = config.spriteList.size - 1
|
||||
): SpriteAnimator {
|
||||
return SpriteAnimator(this, state, animationCycle, animationLoops, firstFrame, lastFrame)
|
||||
}
|
@ -25,17 +25,7 @@ class ItemRenderer(state: GLStateTracker, entity: ItemEntity, chunk: ClientChunk
|
||||
for (texture in textures) {
|
||||
texture.bind()
|
||||
|
||||
val builder = state.flat2DTexturedQuads.small
|
||||
|
||||
builder.begin()
|
||||
|
||||
val width = (texture.width / PIXELS_IN_STARBOUND_UNITf) / 2f
|
||||
val height = (texture.height / PIXELS_IN_STARBOUND_UNITf) / 2f
|
||||
|
||||
builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, texture.transformer)
|
||||
|
||||
builder.upload()
|
||||
builder.draw()
|
||||
state.flat2DTexturedQuads.small.singleSprite(texture.width / PIXELS_IN_STARBOUND_UNITf, texture.height / PIXELS_IN_STARBOUND_UNITf, entity.movement.angle, texture.transformer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,14 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
@JsonFactory
|
||||
data class AnimationDefinition(
|
||||
val frames: ImageReference? = null,
|
||||
val animatedParts: AnimatedParts? = null,
|
||||
val variants: Int? = null,
|
||||
val frameNumber: Int? = null,
|
||||
val animationCycle: Double? = null,
|
||||
val offset: Vector2d? = null,
|
||||
|
||||
val sounds: ImmutableMap<String, Either<ImmutableList<String>, CustomSound>> = ImmutableMap.of(),
|
||||
val animatedParts: AnimatedParts? = null,
|
||||
|
||||
val sounds: ImmutableMap<String, ImmutableList<String>> = ImmutableMap.of(),
|
||||
val transformationGroups: ImmutableMap<String, TransformConfig> = ImmutableMap.of(),
|
||||
val particleEmitters: ImmutableMap<String, ParticleEmitter> = ImmutableMap.of(),
|
||||
) {
|
||||
@ -27,12 +28,6 @@ data class AnimationDefinition(
|
||||
val interpolated: Boolean? = null
|
||||
)
|
||||
|
||||
// TODO
|
||||
@JsonFactory
|
||||
data class CustomSound(
|
||||
val sound: String? = null
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class AnimatedParts(
|
||||
val stateTypes: ImmutableMap<String, StateType> = ImmutableMap.of(),
|
||||
@ -41,6 +36,7 @@ data class AnimationDefinition(
|
||||
@JsonFactory
|
||||
data class StateType(
|
||||
val default: String,
|
||||
val priority: Int = 0,
|
||||
val states: ImmutableMap<String, State> = ImmutableMap.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
|
@ -9,6 +9,7 @@ import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
import com.google.gson.stream.JsonReader
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import ru.dbotthepony.kstarbound.api.ISBFileLocator
|
||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||
import ru.dbotthepony.kstarbound.io.json.stream
|
||||
@ -18,7 +19,7 @@ import ru.dbotthepony.kvector.vector.nint.Vector4i
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Атлас спрайтов, собранный вручную артистом
|
||||
* Конфигурация атласа спрайтов, собранный вручную артистом
|
||||
*
|
||||
* В файлах игры именуется frames
|
||||
*
|
||||
@ -45,10 +46,26 @@ class AtlasConfiguration private constructor(
|
||||
*/
|
||||
val first: Sprite = sprites[sprites.keys.stream().sorted().findFirst().orElseThrow { NoSuchElementException("No a single key present in $name") }] ?: throw NoSuchElementException("IMPOSSIBRU in $name")
|
||||
|
||||
private val intIndexed = Int2ObjectOpenHashMap<Sprite>()
|
||||
|
||||
init {
|
||||
for ((k, v) in sprites.entries) {
|
||||
val int = k.toIntOrNull()
|
||||
|
||||
if (int != null) {
|
||||
intIndexed[int] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(name: String): Sprite? {
|
||||
return sprites[name]
|
||||
}
|
||||
|
||||
operator fun get(name: Int): Sprite? {
|
||||
return intIndexed[name]
|
||||
}
|
||||
|
||||
fun any(name: String): Sprite {
|
||||
return get(name) ?: first
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package ru.dbotthepony.kstarbound.io.json
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
|
||||
object NothingAdapter : TypeAdapter<Nothing>() {
|
||||
override fun write(out: JsonWriter, value: Nothing?) {
|
||||
out.nullValue()
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): Nothing? {
|
||||
`in`.skipValue()
|
||||
return null
|
||||
}
|
||||
}
|
@ -52,6 +52,9 @@ class SBPattern private constructor(
|
||||
}
|
||||
|
||||
fun resolve(values: (String) -> String?): String? {
|
||||
if (names.isEmpty())
|
||||
return raw
|
||||
|
||||
val buffer = ArrayList<String>(pieces.size)
|
||||
|
||||
for (piece in pieces) {
|
||||
@ -70,6 +73,9 @@ class SBPattern private constructor(
|
||||
}
|
||||
|
||||
fun with(params: Map<String, String>): SBPattern {
|
||||
if (names.isEmpty())
|
||||
return this
|
||||
|
||||
val map = Object2ObjectArrayMap<String, String>()
|
||||
map.putAll(this.params)
|
||||
|
||||
@ -89,14 +95,6 @@ class SBPattern private constructor(
|
||||
fun resolve(map0: (String) -> String?, map1: (String) -> String?): String? {
|
||||
return contents ?: map0.invoke(name!!) ?: map1.invoke(name!!)
|
||||
}
|
||||
|
||||
fun resolve(map0: (String) -> String?): String? {
|
||||
return contents ?: map0.invoke(name!!)
|
||||
}
|
||||
|
||||
fun resolve(): String? {
|
||||
return contents
|
||||
}
|
||||
}
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
|
68
src/main/kotlin/ru/dbotthepony/kstarbound/util/TimeSource.kt
Normal file
68
src/main/kotlin/ru/dbotthepony/kstarbound/util/TimeSource.kt
Normal file
@ -0,0 +1,68 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
|
||||
interface ITimeSource {
|
||||
/**
|
||||
* Время в наносекундах
|
||||
*/
|
||||
val nanos: Long
|
||||
|
||||
/**
|
||||
* Время в микросекундах
|
||||
*/
|
||||
val micros: Long get() = nanos / 1_000L
|
||||
|
||||
/**
|
||||
* Время в миллисекундах
|
||||
*/
|
||||
val millis: Long get() = nanos / 1_000_000L
|
||||
|
||||
/**
|
||||
* Время в секундах, с точностью до микросекунд
|
||||
*/
|
||||
val seconds: Double get() = (nanos / 1_000L) / 1_000_000.0
|
||||
}
|
||||
|
||||
class JVMTimeSource : ITimeSource {
|
||||
private val origin = System.nanoTime()
|
||||
|
||||
override val nanos: Long
|
||||
get() = System.nanoTime() - origin
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val INSTANCE = JVMTimeSource()
|
||||
}
|
||||
}
|
||||
|
||||
class ArtificialTimeSource(nanos: Long = 0L) : ITimeSource {
|
||||
override var nanos: Long = nanos
|
||||
private set
|
||||
|
||||
fun advance(nanos: Long) {
|
||||
this.nanos += nanos
|
||||
}
|
||||
}
|
||||
|
||||
class PausableTimeSource(private val parent: ITimeSource) : ITimeSource {
|
||||
override val nanos: Long get() {
|
||||
if (isPaused) {
|
||||
return pausedSince - skipped
|
||||
} else {
|
||||
return parent.nanos - skipped
|
||||
}
|
||||
}
|
||||
|
||||
private var isPaused = false
|
||||
private var pausedSince = 0L
|
||||
private var skipped = 0L
|
||||
|
||||
fun pause() {
|
||||
if (!isPaused) {
|
||||
isPaused = true
|
||||
pausedSince = parent.nanos
|
||||
} else {
|
||||
isPaused = false
|
||||
skipped += parent.nanos - pausedSince
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user