Parallax definition loading
This commit is contained in:
parent
97e28ca0ee
commit
b1ee5bf66d
@ -4,11 +4,11 @@ import com.google.gson.*
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.api.IVFS
|
||||
import ru.dbotthepony.kstarbound.api.PhysicalFS
|
||||
import ru.dbotthepony.kstarbound.api.getPathFilename
|
||||
import ru.dbotthepony.kstarbound.api.getPathFolder
|
||||
import ru.dbotthepony.kstarbound.defs.*
|
||||
import ru.dbotthepony.kstarbound.defs.projectile.*
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
|
||||
import ru.dbotthepony.kstarbound.io.*
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
@ -25,8 +25,8 @@ import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
const val METRES_IN_STARBOUND_UNIT = 0.25
|
||||
const val METRES_IN_STARBOUND_UNITf = 0.25f
|
||||
const val METRES_IN_STARBOUND_UNIT = 0.5
|
||||
const val METRES_IN_STARBOUND_UNITf = 0.5f
|
||||
|
||||
const val PIXELS_IN_STARBOUND_UNIT = 8.0
|
||||
const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
||||
@ -36,12 +36,16 @@ class ProjectileDefLoadingException(message: String, cause: Throwable? = null) :
|
||||
|
||||
object Starbound : IVFS {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
private val tiles = HashMap<String, TileDefinition>()
|
||||
private val projectiles = HashMap<String, ConfiguredProjectile>()
|
||||
val tilesAccess = Collections.unmodifiableMap(tiles)
|
||||
val projectilesAccess = Collections.unmodifiableMap(projectiles)
|
||||
private val parallax = HashMap<String, Parallax>()
|
||||
|
||||
val gson = GsonBuilder()
|
||||
val tilesAccess: Map<String, TileDefinition> = Collections.unmodifiableMap(tiles)
|
||||
val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles)
|
||||
val parallaxAccess: Map<String, Parallax> = Collections.unmodifiableMap(parallax)
|
||||
|
||||
val gson: Gson = GsonBuilder()
|
||||
.enableComplexMapKeySerialization()
|
||||
.serializeNulls()
|
||||
.setDateFormat(DateFormat.LONG)
|
||||
@ -60,6 +64,7 @@ object Starbound : IVFS {
|
||||
.also(ConfigurableProjectile::registerGson)
|
||||
.also(SkyParameters::registerGson)
|
||||
.also(DungeonWorldDef::registerGson)
|
||||
.also(Parallax::registerGson)
|
||||
|
||||
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
||||
|
||||
@ -99,6 +104,25 @@ object Starbound : IVFS {
|
||||
fun getTileDefinition(name: String) = tiles[name]
|
||||
private val initCallbacks = ArrayList<() -> Unit>()
|
||||
|
||||
private fun loadStage(
|
||||
callback: (Boolean, Boolean, String) -> Unit,
|
||||
loader: ((String) -> Unit) -> Unit,
|
||||
name: String,
|
||||
) {
|
||||
val time = System.currentTimeMillis()
|
||||
callback(false, false, "Loading $name...")
|
||||
|
||||
loader {
|
||||
if (terminateLoading) {
|
||||
throw InterruptedException("Game is terminating")
|
||||
}
|
||||
|
||||
callback(false, true, it)
|
||||
}
|
||||
|
||||
callback(false, true, "Loaded $name in ${System.currentTimeMillis() - time}ms")
|
||||
}
|
||||
|
||||
fun initializeGame(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) {
|
||||
if (initializing) {
|
||||
throw IllegalStateException("Already initializing!")
|
||||
@ -125,36 +149,9 @@ object Starbound : IVFS {
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
val localTime = System.currentTimeMillis()
|
||||
|
||||
callback(false, false, "Loading materials...")
|
||||
|
||||
loadTileMaterials {
|
||||
if (terminateLoading) {
|
||||
throw InterruptedException("Game is terminating")
|
||||
}
|
||||
|
||||
callback(false, true, it)
|
||||
}
|
||||
|
||||
callback(false, true, "Loaded materials in ${System.currentTimeMillis() - localTime}ms")
|
||||
}
|
||||
|
||||
run {
|
||||
val localTime = System.currentTimeMillis()
|
||||
callback(false, false, "Loading projectiles...")
|
||||
|
||||
loadProjectiles {
|
||||
if (terminateLoading) {
|
||||
throw InterruptedException("Game is terminating")
|
||||
}
|
||||
|
||||
callback(false, true, it)
|
||||
}
|
||||
|
||||
callback(false, true, "Loaded Projectiles in ${System.currentTimeMillis() - localTime}ms")
|
||||
}
|
||||
loadStage(callback, this::loadTileMaterials, "materials")
|
||||
loadStage(callback, this::loadProjectiles, "projectiles")
|
||||
loadStage(callback, this::loadParallax, "parallax definitions")
|
||||
|
||||
initializing = false
|
||||
initialized = true
|
||||
@ -257,4 +254,21 @@ object Starbound : IVFS {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadParallax(callback: (String) -> Unit) {
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFiles("parallax")) {
|
||||
if (listedFile.endsWith(".parallax")) {
|
||||
try {
|
||||
callback("Loading $listedFile")
|
||||
|
||||
val def = gson.fromJson(getReader(listedFile), Parallax::class.java)
|
||||
parallax[getPathFilename(listedFile).substringBefore('.')] = def
|
||||
} catch(err: Throwable) {
|
||||
LOGGER.error("Loading parallax file $listedFile", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,11 @@ interface IVFS {
|
||||
}
|
||||
|
||||
fun getPathFolder(path: String): String {
|
||||
return path.substring(0, path.lastIndexOf('/'))
|
||||
return path.substringBeforeLast('/')
|
||||
}
|
||||
|
||||
fun getPathFilename(path: String): String {
|
||||
return path.substringAfterLast('/')
|
||||
}
|
||||
|
||||
class PhysicalFS(root: File) : IVFS {
|
||||
|
@ -1,11 +1,39 @@
|
||||
package ru.dbotthepony.kstarbound.client
|
||||
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.client.gl.VertexTransformers
|
||||
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
|
||||
import ru.dbotthepony.kstarbound.defs.Parallax
|
||||
import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
||||
import ru.dbotthepony.kstarbound.world.*
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
|
||||
class DoubleEdgeProgression : Iterator<Int> {
|
||||
var value = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun next(): Int {
|
||||
return nextInt()
|
||||
}
|
||||
|
||||
fun nextInt(): Int {
|
||||
return if (value > 0) {
|
||||
val ret = value
|
||||
value = -value
|
||||
ret
|
||||
} else {
|
||||
val ret = value
|
||||
value = -value + 1
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWorld, ClientChunk>(seed) {
|
||||
init {
|
||||
physics.debugDraw = client.gl.box2dRenderer
|
||||
@ -18,17 +46,77 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
|
||||
)
|
||||
}
|
||||
|
||||
var parallax: Parallax? = null
|
||||
|
||||
/**
|
||||
* Отрисовывает этот с обрезкой невидимой геометрии с точки зрения [size] в Starbound Units
|
||||
*
|
||||
* Все координаты "местности" сохраняются, поэтому, если отрисовывать слишком далеко от 0, 0
|
||||
* то геометрия может начать искажаться из-за погрешности плавающей запятой
|
||||
*
|
||||
* Обрезает всю заведомо невидимую геометрию на основе аргументов mins и maxs (в пикселях)
|
||||
*/
|
||||
fun render(
|
||||
size: AABB,
|
||||
) {
|
||||
val parallax = parallax
|
||||
|
||||
if (parallax != null) {
|
||||
client.gl.matrixStack.push()
|
||||
|
||||
client.gl.matrixStack.translateWithMultiplication(y = parallax.verticalOrigin.toFloat() - 20f)
|
||||
|
||||
client.gl.shaderVertexTexture.use()
|
||||
|
||||
val stateful = client.gl.flat2DTexturedQuads.statefulSmall
|
||||
val builder = stateful.builder
|
||||
|
||||
client.gl.activeTexture = 0
|
||||
client.gl.shaderVertexTexture["_texture"] = 0
|
||||
val centre = size.centre
|
||||
|
||||
for (layer in parallax.layers) {
|
||||
client.gl.matrixStack.push()
|
||||
client.gl.matrixStack.translateWithMultiplication(x = layer.offset.x.toFloat() / PIXELS_IN_STARBOUND_UNITf, y = layer.offset.y.toFloat() / PIXELS_IN_STARBOUND_UNITf)
|
||||
|
||||
client.gl.shaderVertexTexture.transform.set(client.gl.matrixStack.last)
|
||||
|
||||
val texture = client.gl.loadNamedTextureSafe("/parallax/images/${layer.kind}/base/1.png")
|
||||
|
||||
texture.bind()
|
||||
texture.textureMagFilter = GL_NEAREST
|
||||
texture.textureMinFilter = GL_NEAREST
|
||||
|
||||
builder.begin()
|
||||
|
||||
for (xPos in DoubleEdgeProgression()) {
|
||||
var x0 = xPos.toFloat() * texture.width / PIXELS_IN_STARBOUND_UNITf
|
||||
var x1 = (xPos + 1f) * texture.width / PIXELS_IN_STARBOUND_UNITf
|
||||
|
||||
val diffx = layer.parallax.x * centre.x - centre.x
|
||||
val diffy = (layer.parallax.y * (centre.y + 20.0) - centre.y - 20.0).toFloat() / PIXELS_IN_STARBOUND_UNITf
|
||||
|
||||
x0 += diffx.toFloat() / PIXELS_IN_STARBOUND_UNITf
|
||||
x1 += diffx.toFloat() / PIXELS_IN_STARBOUND_UNITf
|
||||
|
||||
builder.quadZ(x0, diffy, x1, diffy + texture.height.toFloat() / PIXELS_IN_STARBOUND_UNITf, 1f, VertexTransformers.uv(0f, 1f, 1f, 0f))
|
||||
|
||||
/*if (x1 < size.mins.x) {
|
||||
break
|
||||
}*/
|
||||
|
||||
if (xPos < -40) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stateful.upload()
|
||||
stateful.draw()
|
||||
|
||||
client.gl.matrixStack.pop()
|
||||
}
|
||||
|
||||
client.gl.matrixStack.pop()
|
||||
}
|
||||
|
||||
val determineRenderers = ArrayList<ClientChunk>()
|
||||
|
||||
for (chunk in collectInternal(size.encasingChunkPosAABB())) {
|
||||
|
@ -216,9 +216,9 @@ class StarboundClient : AutoCloseable {
|
||||
|
||||
world?.render(
|
||||
AABB.rectangle(
|
||||
camera.pos.toDoubleVector(),
|
||||
viewportWidth / settings.scale / PIXELS_IN_STARBOUND_UNIT,
|
||||
viewportHeight / settings.scale / PIXELS_IN_STARBOUND_UNIT))
|
||||
camera.pos.toDoubleVector(),
|
||||
viewportWidth / settings.scale / PIXELS_IN_STARBOUND_UNIT,
|
||||
viewportHeight / settings.scale / PIXELS_IN_STARBOUND_UNIT))
|
||||
|
||||
for (lambda in onPostDrawWorld) {
|
||||
lambda.invoke()
|
||||
|
@ -252,7 +252,7 @@ class GLStateTracker {
|
||||
|
||||
private val named2DTextures = HashMap<String, GLTexture2D>()
|
||||
|
||||
fun loadNamedTexture(path: String, memoryFormat: Int = GL_RGBA, fileFormat: Int = GL_RGBA): GLTexture2D {
|
||||
fun loadNamedTexture(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
throw FileNotFoundException("Unable to locate $path")
|
||||
@ -262,10 +262,20 @@ class GLStateTracker {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadNamedTexture(path: String): GLTexture2D {
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
throw FileNotFoundException("Unable to locate $path")
|
||||
}
|
||||
|
||||
return@computeIfAbsent newTexture(path).upload(Starbound.readDirect(path)).generateMips()
|
||||
}
|
||||
}
|
||||
|
||||
private var loadedEmptyTexture = false
|
||||
private val missingTexturePath = "/assetmissing.png"
|
||||
|
||||
fun loadNamedTextureSafe(path: String, memoryFormat: Int = GL_RGBA, fileFormat: Int = GL_RGBA): GLTexture2D {
|
||||
fun loadNamedTextureSafe(path: String, memoryFormat: Int, fileFormat: Int): GLTexture2D {
|
||||
if (!loadedEmptyTexture) {
|
||||
loadedEmptyTexture = true
|
||||
named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), memoryFormat, fileFormat).generateMips()
|
||||
@ -281,6 +291,22 @@ class GLStateTracker {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadNamedTextureSafe(path: String): GLTexture2D {
|
||||
if (!loadedEmptyTexture) {
|
||||
loadedEmptyTexture = true
|
||||
named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips()
|
||||
}
|
||||
|
||||
return named2DTextures.computeIfAbsent(path) {
|
||||
if (!Starbound.pathExists(path)) {
|
||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||
return@computeIfAbsent named2DTextures[missingTexturePath]!!
|
||||
}
|
||||
|
||||
return@computeIfAbsent newTexture(path).upload(Starbound.readDirect(path)).generateMips()
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(obj: GLVertexBufferObject): GLVertexBufferObject {
|
||||
if (obj.type == VBOType.ARRAY)
|
||||
VBO = obj
|
||||
|
@ -162,6 +162,40 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
|
||||
return this
|
||||
}
|
||||
|
||||
fun upload(path: File): GLTexture2D {
|
||||
state.ensureSameThread()
|
||||
|
||||
if (!path.exists()) {
|
||||
throw FileNotFoundException("${path.absolutePath} does not exist")
|
||||
}
|
||||
|
||||
if (!path.isFile) {
|
||||
throw FileNotFoundException("${path.absolutePath} is not a file")
|
||||
}
|
||||
|
||||
val getwidth = intArrayOf(0)
|
||||
val getheight = intArrayOf(0)
|
||||
val getchannels = intArrayOf(0)
|
||||
|
||||
val bytes = STBImage.stbi_load(path.absolutePath, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${path.absolutePath}. Is it a valid image?")
|
||||
|
||||
require(getwidth[0] > 0) { "Image ${path.absolutePath} has bad width of ${getwidth[0]}" }
|
||||
require(getheight[0] > 0) { "Image ${path.absolutePath} has bad height of ${getheight[0]}" }
|
||||
|
||||
val bufferFormat = when (val numChannels = getchannels[0]) {
|
||||
1 -> GL_R
|
||||
2 -> GL_RG
|
||||
3 -> GL_RGB
|
||||
4 -> GL_RGBA
|
||||
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
|
||||
}
|
||||
|
||||
upload(bufferFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
||||
STBImage.stbi_image_free(bytes)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun upload(buff: ByteBuffer, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
|
||||
state.ensureSameThread()
|
||||
|
||||
@ -180,6 +214,32 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
|
||||
return this
|
||||
}
|
||||
|
||||
fun upload(buff: ByteBuffer): GLTexture2D {
|
||||
state.ensureSameThread()
|
||||
|
||||
val getwidth = intArrayOf(0)
|
||||
val getheight = intArrayOf(0)
|
||||
val getchannels = intArrayOf(0)
|
||||
|
||||
val bytes = STBImage.stbi_load_from_memory(buff, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${buff}. Is it a valid image?")
|
||||
|
||||
require(getwidth[0] > 0) { "Image $name has bad width of ${getwidth[0]}" }
|
||||
require(getheight[0] > 0) { "Image $name has bad height of ${getheight[0]}" }
|
||||
|
||||
val bufferFormat = when (val numChannels = getchannels[0]) {
|
||||
1 -> GL_R
|
||||
2 -> GL_RG
|
||||
3 -> GL_RGB
|
||||
4 -> GL_RGBA
|
||||
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
|
||||
}
|
||||
|
||||
upload(bufferFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
||||
STBImage.stbi_image_free(bytes)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
var isValid = true
|
||||
private set
|
||||
|
||||
|
103
src/main/kotlin/ru/dbotthepony/kstarbound/defs/Parallax.kt
Normal file
103
src/main/kotlin/ru/dbotthepony/kstarbound/defs/Parallax.kt
Normal file
@ -0,0 +1,103 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kstarbound.io.KTypeAdapter
|
||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class Parallax {
|
||||
var verticalOrigin = 0.0
|
||||
|
||||
var layers = Array(0) { ParallaxLayer() }
|
||||
|
||||
companion object {
|
||||
val ADAPTER = KTypeAdapter(::Parallax,
|
||||
Parallax::verticalOrigin,
|
||||
Parallax::layers,
|
||||
)
|
||||
|
||||
fun registerGson(gsonBuilder: GsonBuilder) {
|
||||
gsonBuilder.registerTypeAdapter(Parallax::class.java, ADAPTER)
|
||||
gsonBuilder.registerTypeAdapter(ParallaxLayer::class.java, ParallaxLayer.ADAPTER)
|
||||
gsonBuilder.registerTypeAdapter(ParallaxLayer.Parallax::class.java, ParallaxLayer.LAYER_PARALLAX_ADAPTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ParallaxLayer {
|
||||
class Parallax(val x: Double, val y: Double)
|
||||
|
||||
var timeOfDayCorrelation: String? = null
|
||||
var offset = Vector2d.ZERO
|
||||
var repeatY = false
|
||||
var lightMapped = false
|
||||
var tileLimitTop: Int? = null
|
||||
var parallax by Delegates.notNull<Parallax>()
|
||||
|
||||
var unlit = false
|
||||
var nohueshift = false
|
||||
var minSpeed = 0
|
||||
var maxSpeed = 0
|
||||
var fadePercent = 0.0
|
||||
|
||||
var frequency = 1.0
|
||||
|
||||
var modCount = 0
|
||||
|
||||
var noRandomOffset = false
|
||||
var directives: String? = null
|
||||
|
||||
var kind by Delegates.notNull<String>()
|
||||
var baseCount = 1
|
||||
|
||||
companion object {
|
||||
val LAYER_PARALLAX_ADAPTER = object : TypeAdapter<Parallax>() {
|
||||
override fun write(out: JsonWriter?, value: Parallax?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): Parallax {
|
||||
return when (val type = reader.peek()) {
|
||||
JsonToken.BEGIN_ARRAY -> {
|
||||
reader.beginArray()
|
||||
val instance = Parallax(reader.nextDouble(), reader.nextDouble())
|
||||
reader.endArray()
|
||||
instance
|
||||
}
|
||||
|
||||
JsonToken.NUMBER -> {
|
||||
val num = reader.nextDouble()
|
||||
Parallax(num, num)
|
||||
}
|
||||
|
||||
else -> throw JsonSyntaxException("Unexpected token $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ADAPTER = KTypeAdapter(::ParallaxLayer,
|
||||
ParallaxLayer::timeOfDayCorrelation,
|
||||
ParallaxLayer::offset,
|
||||
ParallaxLayer::repeatY,
|
||||
ParallaxLayer::lightMapped,
|
||||
ParallaxLayer::tileLimitTop,
|
||||
ParallaxLayer::parallax,
|
||||
ParallaxLayer::unlit,
|
||||
ParallaxLayer::nohueshift,
|
||||
ParallaxLayer::minSpeed,
|
||||
ParallaxLayer::maxSpeed,
|
||||
ParallaxLayer::fadePercent,
|
||||
ParallaxLayer::kind,
|
||||
ParallaxLayer::baseCount,
|
||||
ParallaxLayer::noRandomOffset,
|
||||
ParallaxLayer::directives,
|
||||
ParallaxLayer::frequency,
|
||||
ParallaxLayer::modCount,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user