diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index cfa690e7..4cdb748a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -153,14 +153,14 @@ fun main() { //ent.position += Vector2d(y = 14.0, x = -10.0) ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0) - client.camera.pos = Vector2f(238f, 685f) + client.camera.pos = Vector2d(238.0, 685.0) //client.camera.pos = Vector2f(0f, 0f) client.onDrawGUI { client.font.render("${ent.position}", y = 100f, scale = 0.25f) client.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f) client.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) - client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f) + client.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos)}", y = 160f, scale = 0.25f) } client.onPreDrawWorld { @@ -241,9 +241,9 @@ fun main() { //client.camera.pos.x = ent.position.x.toFloat() //client.camera.pos.y = ent.position.y.toFloat() - client.camera.pos += Vector2f( - (if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f) + (if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f), - (if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f) + (if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE.toFloat() * 32f / client.settings.zoom else 0f) + client.camera.pos += Vector2d( + (if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0), + (if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) ) //println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index f34939ae..0bae2600 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -9,7 +9,6 @@ import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFWErrorCallback import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL11 -import org.lwjgl.opengl.GL15 import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GLCapabilities import org.lwjgl.system.MemoryStack @@ -34,9 +33,8 @@ import ru.dbotthepony.kstarbound.client.gl.properties.GLStateSwitchTracker import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms import ru.dbotthepony.kstarbound.client.gl.shader.GLShader import ru.dbotthepony.kstarbound.client.gl.shader.GLShaderProgram -import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes +import ru.dbotthepony.kstarbound.client.gl.shader.UberShader import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType -import ru.dbotthepony.kstarbound.client.gl.vertex.StreamVertexBuilder import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.render.Box2DRenderer @@ -62,6 +60,7 @@ import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2i +import ru.dbotthepony.kvector.vector.Vector4f import java.io.Closeable import java.io.File import java.lang.ref.Cleaner @@ -91,6 +90,7 @@ class StarboundClient : Closeable { var viewportHeight: Int = 0 private set + // potentially visible cells var viewportCellX = 0 private set var viewportCellY = 0 @@ -102,7 +102,12 @@ class StarboundClient : Closeable { var viewportRectangle = AABB.rectangle(Vector2d.ZERO, 0.0, 0.0) private set - var fullbright = true + var viewportBottomLeft = Vector2d() + private set + var viewportTopRight = Vector2d() + private set + + var fullbright = false var clientTerminated = false private set @@ -169,6 +174,7 @@ class StarboundClient : Closeable { GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4) GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 5) GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE) + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE) window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) require(window != MemoryUtil.NULL) { "Unable to create GLFW window" } @@ -228,18 +234,19 @@ class StarboundClient : Closeable { GLFW.glfwShowWindow(window) putDebugLog("Initialized GLFW window") - - val v = glGenBuffers() - glBindBuffer(GL_ARRAY_BUFFER, v) - glBindBuffer(GL_ARRAY_BUFFER, 0) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) - GL15.glDeleteBuffers(v) } val maxTextureBlocks = glGetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) val maxVertexAttribBindPoints = glGetInteger(GL_MAX_VERTEX_ATTRIB_BINDINGS) + init { + LOGGER.info("OpenGL Version: ${glGetString(GL_VERSION)}") + LOGGER.info("OpenGL Vendor: ${glGetString(GL_VENDOR)}") + LOGGER.info("OpenGL Renderer: ${glGetString(GL_RENDERER)}") + LOGGER.debug("Max supported texture image units: $maxTextureBlocks") + LOGGER.debug("Max supported vertex attribute bind points: $maxVertexAttribBindPoints") + } + val stack = Matrix3fStack() // минимальное время хранения 5 минут и... @@ -261,8 +268,15 @@ class StarboundClient : Closeable { private val missingTexturePath = "/assetmissing.png" private val regularShaderPrograms = ArrayList>() + private val uberShaderPrograms = ArrayList>() + + val lightMapLocation = maxTextureBlocks - 1 fun addShaderProgram(program: GLShaderProgram) { + if (program is UberShader) { + uberShaderPrograms.add(WeakReference(program)) + } + if (program is GLShaderProgram.Regular) { regularShaderPrograms.add(WeakReference(program)) } @@ -529,50 +543,20 @@ class StarboundClient : Closeable { return Vector2f(xMousePos.get().toFloat(), yMousePos.get().toFloat()) } - /** - * Преобразует экранные координаты в мировые - */ fun screenToWorld(x: Double, y: Double): Vector2d { - val relativeX = camera.pos.x - (viewportWidth / 2.0 - x) / PIXELS_IN_STARBOUND_UNIT / settings.zoom - val relativeY = (viewportHeight / 2.0 - y) / PIXELS_IN_STARBOUND_UNIT / settings.zoom + camera.pos.y - + val relativeX = (-viewportWidth / 2.0 + x) / settings.zoom / PIXELS_IN_STARBOUND_UNIT + camera.pos.x + val relativeY = (-viewportHeight / 2.0 + y) / settings.zoom / PIXELS_IN_STARBOUND_UNIT + camera.pos.y return Vector2d(relativeX, relativeY) } - /** - * Преобразует экранные координаты в мировые - */ + fun screenToWorld(x: Int, y: Int): Vector2d { + return screenToWorld(x.toDouble(), y.toDouble()) + } + fun screenToWorld(value: Vector2d): Vector2d { return screenToWorld(value.x, value.y) } - /** - * Преобразует экранные координаты в мировые - */ - fun screenToWorld(x: Float, y: Float): Vector2f { - val relativeX = camera.pos.x - (viewportWidth / 2f - x) / PIXELS_IN_STARBOUND_UNITf / settings.zoom - val relativeY = (viewportHeight / 2f - y) / PIXELS_IN_STARBOUND_UNITf / settings.zoom + camera.pos.y - - return Vector2f(relativeX, relativeY) - } - - /** - * Преобразует экранные координаты в мировые - */ - fun screenToWorld(value: Vector2f): Vector2f { - return screenToWorld(value.x, value.y) - } - - /** - * Преобразует мировые координаты в экранные - */ - fun worldToScreen(x: Float, y: Float): Vector2f { - val relativeX = (x - camera.pos.x) * PIXELS_IN_STARBOUND_UNITf * settings.zoom + viewportWidth / 2f - val relativeY = (camera.pos.y - y) * PIXELS_IN_STARBOUND_UNITf * settings.zoom + viewportHeight / 2f - - return Vector2f(relativeX, relativeY) - } - val tileRenderers = TileRenderers(this) var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true) @@ -641,7 +625,7 @@ class StarboundClient : Closeable { fun updateViewportParams() { viewportRectangle = AABB.rectangle( - camera.pos.toDoubleVector(), + camera.pos, viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT, viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT) @@ -650,6 +634,9 @@ class StarboundClient : Closeable { viewportCellWidth = roundTowardsPositiveInfinity(viewportRectangle.width) + 32 viewportCellHeight = roundTowardsPositiveInfinity(viewportRectangle.height) + 32 + viewportTopRight = screenToWorld(viewportWidth, viewportHeight) + viewportBottomLeft = screenToWorld(0, 0) + if (viewportLighting.width != viewportCellWidth || viewportLighting.height != viewportCellHeight) { viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight) viewportLighting.multithreaded = true @@ -720,6 +707,12 @@ class StarboundClient : Closeable { return true } + uberShaderPrograms.forEachValid { + if (it.flags.contains(UberShader.Flag.NEEDS_SCREEN_SIZE)) { + it.screenSize = Vector2f(viewportWidth.toFloat(), viewportHeight.toFloat()) + } + } + if (world != null) { updateViewportParams() val layers = LayeredRenderer() @@ -734,7 +727,7 @@ class StarboundClient : Closeable { val viewMatrix = viewportMatrixWorld.copy() .translate(viewportWidth / 2f, viewportHeight / 2f) // центр экрана + координаты отрисовки мира .scale(x = settings.zoom * PIXELS_IN_STARBOUND_UNITf, y = settings.zoom * PIXELS_IN_STARBOUND_UNITf) // масштабируем до нужного размера - .translate(-camera.pos.x, -camera.pos.y) // перемещаем вид к камере + .translate(-camera.pos.x.toFloat(), -camera.pos.y.toFloat()) // перемещаем вид к камере regularShaderPrograms.forEachValid { it.viewMatrix = viewMatrix } @@ -748,15 +741,12 @@ class StarboundClient : Closeable { } viewportLighting.clear() + val viewportLightingMem = viewportLightingMem world.addLayers( layers = layers, size = viewportRectangle) - layers.render() - - val viewportLightingMem = viewportLightingMem - if (viewportLightingMem != null && !fullbright) { viewportLightingMem.position(0) BufferUtils.zeroBuffer(viewportLightingMem) @@ -777,27 +767,29 @@ class StarboundClient : Closeable { ) textureUnpackAlignment = old - - viewportLightingTexture.textureMinFilter = GL_LINEAR - //viewportLightingTexture.textureMagFilter = GL_NEAREST - - //viewportLightingTexture.generateMips() - - blendFunc = BlendFunc.MULTIPLY_BY_SRC - - /*quadTexture(viewportLightingTexture) { - it.quad( - (viewportCellX).toFloat(), - (viewportCellY).toFloat(), - (viewportCellX + viewportCellWidth).toFloat(), - (viewportCellY + viewportCellHeight).toFloat(), - QuadTransformers.uv() - ) - }*/ - - blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA + } else { + viewportLightingTexture.upload(GL_RGBA, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, WHITE) } + viewportLightingTexture.textureMinFilter = GL_LINEAR + + activeTexture = lightMapLocation + texture2D = viewportLightingTexture + activeTexture = 0 + + val lightmapUV = Vector4f( + ((viewportBottomLeft.x - viewportCellX) / viewportLighting.width).toFloat(), + ((viewportBottomLeft.y - viewportCellY) / viewportLighting.height).toFloat(), + (1f - (viewportCellX + viewportCellWidth - viewportTopRight.x) / viewportLighting.width).toFloat(), + (1f - (viewportCellY + viewportCellHeight - viewportTopRight.y) / viewportLighting.height).toFloat()) + + uberShaderPrograms.forEachValid { + it.lightmapTexture = lightMapLocation + it.lightmapUV = lightmapUV + } + + layers.render() + world.physics.debugDraw() for (lambda in onPostDrawWorld) { @@ -898,6 +890,13 @@ class StarboundClient : Closeable { companion object { private val LOGGER = LogManager.getLogger(StarboundClient::class.java) private val CLIENTS = ThreadLocal() + private val WHITE = ByteBuffer.allocateDirect(4).also { + it.put(0xFF.toByte()) + it.put(0xFF.toByte()) + it.put(0xFF.toByte()) + it.put(0xFF.toByte()) + it.position(0) + } @JvmStatic fun current() = checkNotNull(CLIENTS.get()) { "No client registered to current thread (${Thread.currentThread()})" } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt index a79d815c..d22b73b3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/Programs.kt @@ -43,8 +43,9 @@ class GLLiquidProgram : GLShaderProgram(shaders("liquid"), VertexAttributes.POSI class GLPrograms { val position = UberShader.Builder().build() val positionTexture = UberShader.Builder().withTexture().build() + val positionTextureLightmap = UberShader.Builder().withTexture().withLightMap().build() val positionColor = UberShader.Builder().withColor().build() - val tile = UberShader.Builder().withTexture().withHueShift().build() + val tile = UberShader.Builder().withTexture().withHueShift().withLightMap().build() val font = FontProgram() val liquid = GLLiquidProgram() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt index 33e28fe0..99ee3cf4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt @@ -11,8 +11,20 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.VertexAttributes import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.vector.RGBAColor +import java.util.Collections +import java.util.EnumSet + +class UberShader private constructor( + fragment: GLShader, + vertex: GLShader, + attributes: VertexAttributes, + val flags: Set, +) : GLShaderProgram(listOf(fragment, vertex), attributes), GLShaderProgram.Regular { + enum class Flag { + LIGHT_MAP, + NEEDS_SCREEN_SIZE; + } -class UberShader private constructor(fragment: GLShader, vertex: GLShader, attributes: VertexAttributes) : GLShaderProgram(listOf(fragment, vertex), attributes), GLShaderProgram.Regular { override var viewMatrix: Matrix3f by F3x3Uniform("viewMatrix") override var worldMatrix: Matrix3f by F3x3Uniform("worldMatrix") override var modelMatrix: Matrix3f by F3x3Uniform("modelMatrix") @@ -20,6 +32,10 @@ class UberShader private constructor(fragment: GLShader, vertex: GLShader, attri var texture0 by IUniform("texture0", VertexAttributeType.UV !in attributes) + var lightmapTexture by IUniform("lightmap", Flag.LIGHT_MAP !in flags) + var lightmapUV by F4Uniform("lightmapUV", Flag.LIGHT_MAP !in flags) + var screenSize by F2Uniform("screenSize", Flag.NEEDS_SCREEN_SIZE !in flags) + val builder = StreamVertexBuilder(attributes) init { @@ -32,6 +48,15 @@ class UberShader private constructor(fragment: GLShader, vertex: GLShader, attri class Builder { private val directives = Object2ObjectArrayMap() private val attributes = ObjectArraySet() + private val flags = EnumSet.noneOf(Flag::class.java) + + private fun addFlag(flag: Flag): Builder { + if (flags.add(flag)) { + directives[flag.name] = "" + } + + return this + } init { attributes.add(VertexAttributeType.POSITION) @@ -60,9 +85,12 @@ class UberShader private constructor(fragment: GLShader, vertex: GLShader, attri return attribute(VertexAttributeType.HUE_SHIFT) } - fun build(): UberShader { - val client = StarboundClient.current() + fun withLightMap(): Builder { + addFlag(Flag.NEEDS_SCREEN_SIZE) + return addFlag(Flag.LIGHT_MAP) + } + fun build(): UberShader { val fragment = GLShader("#version 450\n" + directives.entries.joinToString("\n") { "#define ${it.key}" + (if (it.value != "") " " + it.value else "") } + fragment, GL_FRAGMENT_SHADER) @@ -73,7 +101,7 @@ class UberShader private constructor(fragment: GLShader, vertex: GLShader, attri val attributes = VertexAttributes.of(attributes) - return UberShader(fragment, vertex, attributes) + return UberShader(fragment, vertex, attributes, Collections.unmodifiableSet(EnumSet.copyOf(flags))) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt index a3a0950c..8b266ca2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/Camera.kt @@ -2,13 +2,14 @@ package ru.dbotthepony.kstarbound.client.render import org.lwjgl.glfw.GLFW.* import ru.dbotthepony.kstarbound.client.StarboundClient +import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f class Camera(val client: StarboundClient) { /** * Позиция этой камеры в Starbound Unit'ах */ - var pos = Vector2f() + var pos = Vector2d() var pressedLeft = false private set @@ -31,31 +32,31 @@ class Camera(val client: StarboundClient) { } } - val velocity: Vector2f get() { - var x = 0f - var y = 0f + val velocity: Vector2d get() { + var x = 0.0 + var y = 0.0 if (pressedLeft) { - x -= (FREEVIEW_SENS).toFloat() + x -= (FREEVIEW_SENS) } if (pressedRight) { - x += (FREEVIEW_SENS).toFloat() + x += (FREEVIEW_SENS) } if (pressedUp) { - y += (FREEVIEW_SENS).toFloat() + y += (FREEVIEW_SENS) } if (pressedDown) { - y -= (FREEVIEW_SENS).toFloat() + y -= (FREEVIEW_SENS) } - return Vector2f(x, y) + return Vector2d(x, y) } fun think(delta: Double) { - pos += velocity * delta.toFloat() + pos += velocity * delta } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt index c04e63c8..0c6e10df 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt @@ -82,7 +82,7 @@ sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbrig override fun render(client: StarboundClient, x: Float, y: Float) { val sprite = path.sprite ?: return val texture = client.loadTexture(path.imagePath.value!!) - val program = client.programs.positionTexture + val program = if (fullbright) client.programs.positionTexture else client.programs.positionTextureLightmap program.modelMatrix = transform.map({ it }, { val mat = Matrix3f.identity() diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 202f56cd..220f0833 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -6,7 +6,7 @@ - + diff --git a/src/main/resources/shaders/fragment/configurable.fsh b/src/main/resources/shaders/fragment/configurable.fsh index 260715b0..e13e9093 100644 --- a/src/main/resources/shaders/fragment/configurable.fsh +++ b/src/main/resources/shaders/fragment/configurable.fsh @@ -15,6 +15,16 @@ in vec4 vertexColor; in float hueShiftOut; #endif +#ifdef NEEDS_SCREEN_SIZE +uniform vec2 screenSize; +#endif + +#ifdef LIGHT_MAP +uniform sampler2D lightmap; +uniform vec4 lightmapUV; +in vec4 gl_FragCoord; +#endif + // https://gist.github.com/983/e170a24ae8eba2cd174f // http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl // https://stackoverflow.com/questions/15095909/from-rgb-to-hsv-in-opengl-glsl @@ -56,4 +66,13 @@ void main() { #endif colorResult *= colorMultiplier; + +#ifdef LIGHT_MAP + vec4 lightmapSample = texture(lightmap, vec2( + mix(lightmapUV.x, lightmapUV.z, gl_FragCoord.x / screenSize.x), + mix(lightmapUV.y, lightmapUV.w, gl_FragCoord.y / screenSize.y) + )); + + colorResult *= vec4(lightmapSample.xyz, 1.0); +#endif }