316 lines
9.9 KiB
Kotlin
316 lines
9.9 KiB
Kotlin
package ru.dbotthepony.kstarbound.defs
|
|
|
|
import com.google.common.collect.ImmutableList
|
|
import com.google.gson.Gson
|
|
import com.google.gson.JsonObject
|
|
import com.google.gson.TypeAdapter
|
|
import com.google.gson.annotations.JsonAdapter
|
|
import com.google.gson.reflect.TypeToken
|
|
import com.google.gson.stream.JsonReader
|
|
import com.google.gson.stream.JsonWriter
|
|
import org.apache.logging.log4j.LogManager
|
|
import ru.dbotthepony.kommons.util.Either
|
|
import ru.dbotthepony.kommons.math.RGBAColor
|
|
import ru.dbotthepony.kommons.matrix.Matrix3f
|
|
import ru.dbotthepony.kommons.vector.Vector2f
|
|
import ru.dbotthepony.kommons.vector.Vector3f
|
|
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
|
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
|
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
|
|
import ru.dbotthepony.kstarbound.client.render.IGeometryLayer
|
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|
import ru.dbotthepony.kommons.gson.consumeNull
|
|
import ru.dbotthepony.kommons.gson.contains
|
|
import ru.dbotthepony.kommons.util.AABB
|
|
import ru.dbotthepony.kommons.vector.Vector2d
|
|
import ru.dbotthepony.kstarbound.Starbound
|
|
import ru.dbotthepony.kstarbound.math.Line2d
|
|
|
|
@JsonAdapter(Drawable.Adapter::class)
|
|
sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) {
|
|
@JsonFactory
|
|
data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either<Float, Vector2f> = Either.left(1f))
|
|
|
|
class Line(
|
|
val line: Line2d,
|
|
val width: Float,
|
|
position: Vector2f = Vector2f.ZERO,
|
|
color: RGBAColor = RGBAColor.WHITE,
|
|
fullbright: Boolean = false
|
|
) : Drawable(position, color, fullbright) {
|
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun flop(): Drawable {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun boundingBox(cropImages: Boolean): AABB {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun scale(scale: Vector2f): Drawable {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun translate(translation: Vector2f): Drawable {
|
|
TODO("Not yet implemented")
|
|
}
|
|
}
|
|
|
|
class Poly(
|
|
val vertices: ImmutableList<Vector2f>,
|
|
position: Vector2f = Vector2f.ZERO,
|
|
color: RGBAColor = RGBAColor.WHITE,
|
|
fullbright: Boolean = false
|
|
) : Drawable(position, color, fullbright) {
|
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun flop(): Drawable {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun boundingBox(cropImages: Boolean): AABB {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun scale(scale: Vector2f): Drawable {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun translate(translation: Vector2f): Drawable {
|
|
TODO("Not yet implemented")
|
|
}
|
|
}
|
|
|
|
class Image(
|
|
val path: SpriteReference,
|
|
val transform: Either<Matrix3f, Transformations>,
|
|
position: Vector2f = Vector2f.ZERO,
|
|
color: RGBAColor = RGBAColor.WHITE,
|
|
fullbright: Boolean = false
|
|
) : Drawable(position, color, fullbright) {
|
|
override fun with(values: (String) -> String?): Image {
|
|
val newPath = path.with(values)
|
|
|
|
if (newPath == path) {
|
|
return this
|
|
}
|
|
|
|
return Image(newPath, transform, position, color, fullbright)
|
|
}
|
|
|
|
override fun flop(): Drawable {
|
|
return Image(path, transform.flatMap({ it.copy().scale(-1f, 1f) }, { it.copy(mirrored = !it.mirrored) }), position, color, fullbright)
|
|
}
|
|
|
|
override fun scale(scale: Vector2f): Drawable {
|
|
return Image(path, transform.flatMap({ it.scale(scale) }, { it.copy(scale = Either.right(it.scale.map({ scale * it }, { scale * it }))) }), position, color, fullbright)
|
|
}
|
|
|
|
override fun translate(translation: Vector2f): Drawable {
|
|
return Image(path, transform, position + translation, color, fullbright)
|
|
}
|
|
|
|
private fun getTransforms(sprite: ru.dbotthepony.kstarbound.defs.image.Image.Sprite) = transform.map({ it.copy() }, {
|
|
val mat = Matrix3f.identity()
|
|
|
|
it.scale.map({ mat.scale(it / PIXELS_IN_STARBOUND_UNITf, it / PIXELS_IN_STARBOUND_UNITf) }, { mat.scale(it / PIXELS_IN_STARBOUND_UNITf) })
|
|
|
|
if (it.centered) {
|
|
mat.translate(sprite.width / -2f, sprite.height / -2f)
|
|
}
|
|
|
|
if (it.rotation != 0f) {
|
|
mat.rotateAroundZ(it.rotation)
|
|
}
|
|
|
|
if (it.mirrored) {
|
|
mat.translate(sprite.width.toFloat(), 0f)
|
|
mat.scale(-1f, 1f)
|
|
}
|
|
|
|
mat
|
|
})
|
|
|
|
// Original engine here calculates bounding box wrong
|
|
// if "cropImages" is false
|
|
override fun boundingBox(cropImages: Boolean): AABB {
|
|
val sprite = path.sprite ?: return AABB.ZERO
|
|
var result: AABB
|
|
|
|
if (cropImages) {
|
|
result = AABB(
|
|
Vector2d(sprite.nonEmptyRegion.x.toDouble(), sprite.nonEmptyRegion.y.toDouble()),
|
|
Vector2d(sprite.nonEmptyRegion.z.toDouble(), sprite.nonEmptyRegion.w.toDouble()),
|
|
)
|
|
} else {
|
|
result = AABB(
|
|
Vector2d.ZERO,
|
|
Vector2d(sprite.width.toDouble(), sprite.height.toDouble())
|
|
)
|
|
}
|
|
|
|
val transforms = getTransforms(sprite)
|
|
|
|
result = result.expand((Vector2f(result.mins.x.toFloat(), result.mins.y.toFloat()) * transforms).toDoubleVector())
|
|
result = result.expand((Vector2f(result.mins.x.toFloat(), result.maxs.y.toFloat()) * transforms).toDoubleVector())
|
|
result = result.expand((Vector2f(result.maxs.x.toFloat(), result.mins.y.toFloat()) * transforms).toDoubleVector())
|
|
result = result.expand((Vector2f(result.maxs.x.toFloat(), result.maxs.y.toFloat()) * transforms).toDoubleVector())
|
|
|
|
result += position.toDoubleVector()
|
|
|
|
return result
|
|
}
|
|
|
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {
|
|
val sprite = path.sprite ?: return
|
|
val texture = path.image!!.texture
|
|
val program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap
|
|
|
|
val mat = getTransforms(sprite)
|
|
|
|
val builder = layer.getBuilder(program.config(texture))
|
|
|
|
mat.preTranslate(position.x + x, position.y + y)
|
|
client.stack.last().mulIntoOther(mat)
|
|
|
|
builder.mode(GeometryType.QUADS)
|
|
builder.vertex(mat, 0f, 0f).uv(sprite.u0, sprite.v0)
|
|
builder.vertex(mat, 0f + sprite.width, 0f).uv(sprite.u1, sprite.v0)
|
|
builder.vertex(mat, 0f + sprite.width, 0f + sprite.height).uv(sprite.u1, sprite.v1)
|
|
builder.vertex(mat, 0f, 0f + sprite.height).uv(sprite.u0, sprite.v1)
|
|
}
|
|
}
|
|
|
|
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
|
|
override fun render(client: StarboundClient, layer: IGeometryLayer, x: Float, y: Float) {}
|
|
|
|
override fun flop(): Drawable {
|
|
return this
|
|
}
|
|
|
|
override fun boundingBox(cropImages: Boolean): AABB {
|
|
return AABB.ZERO
|
|
}
|
|
|
|
override fun scale(scale: Vector2f): Drawable {
|
|
return this
|
|
}
|
|
|
|
override fun translate(translation: Vector2f): Drawable {
|
|
return Empty(position + translation, color, fullbright)
|
|
}
|
|
}
|
|
|
|
open fun with(values: (String) -> String?): Drawable {
|
|
return this
|
|
}
|
|
|
|
abstract fun render(client: StarboundClient = StarboundClient.current(), layer: IGeometryLayer, x: Float = 0f, y: Float = 0f)
|
|
|
|
abstract fun boundingBox(cropImages: Boolean = false): AABB
|
|
|
|
abstract fun scale(scale: Vector2f): Drawable
|
|
|
|
fun scale(scale: Float): Drawable {
|
|
if (scale == 1f)
|
|
return this
|
|
|
|
return scale(Vector2f(scale, scale))
|
|
}
|
|
|
|
abstract fun translate(translation: Vector2f): Drawable
|
|
|
|
/**
|
|
* mirror along X axis
|
|
*/
|
|
abstract fun flop(): Drawable
|
|
|
|
companion object {
|
|
val EMPTY = Empty()
|
|
val CENTERED = Transformations(true)
|
|
private val LOGGER = LogManager.getLogger()
|
|
}
|
|
|
|
class Adapter(gson: Gson) : TypeAdapter<Drawable>() {
|
|
private val lines = gson.getAdapter(Line2d::class.java)
|
|
private val vectors = gson.getAdapter(Vector2f::class.java)
|
|
private val vectors3 = gson.getAdapter(Vector3f::class.java)
|
|
private val colors = gson.getAdapter(RGBAColor::class.java)
|
|
private val images = gson.getAdapter(SpriteReference::class.java)
|
|
private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter<ImmutableList<Vector2f>>
|
|
private val transformations = gson.getAdapter(Transformations::class.java)
|
|
|
|
override fun write(out: JsonWriter?, value: Drawable?) {
|
|
TODO("Not yet implemented")
|
|
}
|
|
|
|
override fun read(`in`: JsonReader): Drawable {
|
|
if (`in`.consumeNull()) {
|
|
return EMPTY
|
|
} else {
|
|
val value = Starbound.ELEMENTS_ADAPTER.objects.read(`in`)!!
|
|
|
|
val position = value["position"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO
|
|
val color = value["color"]?.let { colors.fromJsonTree(it) } ?: RGBAColor.WHITE
|
|
val fullbright = value["fullbright"]?.asBoolean ?: false
|
|
|
|
if ("line" in value) {
|
|
return Line(lines.fromJsonTree(value["line"]), value["width"].asFloat, position, color, fullbright)
|
|
} else if ("poly" in value) {
|
|
return Poly(vertices.fromJsonTree(value["poly"]), position, color, fullbright)
|
|
} else if ("image" in value) {
|
|
val image = images.fromJsonTree(value["image"])
|
|
|
|
if ("transformation" in value) {
|
|
val mat = Matrix3f.identity()
|
|
val array = value["transformation"].asJsonArray
|
|
|
|
// original starbound use GLM, which reflects OpenGL, which in turn make matrices row-major
|
|
val row0 = vectors3.fromJsonTree(array[0])
|
|
val row1 = vectors3.fromJsonTree(array[1])
|
|
val row2 = vectors3.fromJsonTree(array[2])
|
|
|
|
mat.r00 = row0.x
|
|
mat.r01 = row0.y
|
|
mat.r02 = row0.z
|
|
|
|
mat.r10 = row1.x
|
|
mat.r11 = row1.y
|
|
mat.r12 = row1.z
|
|
|
|
mat.r20 = row2.x
|
|
mat.r21 = row2.y
|
|
mat.r22 = row2.z
|
|
|
|
return Image(
|
|
image,
|
|
Either.left(mat),
|
|
position,
|
|
color,
|
|
fullbright
|
|
)
|
|
} else {
|
|
return Image(
|
|
image,
|
|
Either.right(transformations.fromJsonTree(value)),
|
|
position,
|
|
color,
|
|
fullbright
|
|
)
|
|
}
|
|
|
|
|
|
} else {
|
|
return Empty(position, color, fullbright)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|