diff --git a/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt b/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt index c6e97426..791f8260 100644 --- a/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt +++ b/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt @@ -1,14 +1,17 @@ package solve.rendering.canvas +import javafx.application.Platform import org.joml.Vector2f import org.joml.Vector2i import solve.rendering.engine.rendering.renderers.FramesRenderer import solve.rendering.engine.scene.Scene import solve.rendering.engine.utils.minus +import solve.rendering.engine.utils.times import solve.rendering.engine.utils.toFloatVector import solve.scene.controller.SceneController import solve.scene.model.VisualizationFrame import solve.utils.ServiceLocator +import solve.utils.ceilToInt class SceneCanvas : OpenGLCanvas() { private var sceneController: SceneController? = null @@ -19,21 +22,36 @@ class SceneCanvas : OpenGLCanvas() { private var dragStartCameraPoint = Vector2f() private var dragStartPoint = Vector2i() + private var leftUpperCornerCameraPosition = Vector2f() + private var rightLowerCornerCameraPosition = Vector2f() + + private var framesSelectionSize = 0 + private var framesSize = Vector2i() + private var columnsNumber = 0 + private val rowsNumber: Int + get() = (framesSelectionSize.toFloat() / columnsNumber.toFloat()).ceilToInt() init { initializeCanvasEvents() } - fun setNewSceneFrames(frames: List) { + fun setNewSceneFrames(frames: List, framesSize: Vector2i) { framesRenderer?.setNewSceneFrames(frames) + this.framesSize = framesSize } fun setFramesSelection(framesSelection: List) { framesRenderer?.setFramesSelection(framesSelection) + framesSelectionSize = framesSelection.count() + Platform.runLater { + recalculateCameraCornersPositions() + window.camera.position = leftUpperCornerCameraPosition + } } fun setColumnsNumber(columnsNumber: Int) { framesRenderer?.setGridWidth(columnsNumber) + this.columnsNumber = columnsNumber } fun dragTo(toScreenPoint: Vector2i) { @@ -41,6 +59,7 @@ class SceneCanvas : OpenGLCanvas() { if (isDraggingScene) { val dragVector = mousePoint - dragStartPoint window.camera.position = dragStartCameraPoint - dragVector.toFloatVector() / window.camera.scaledZoom + constraintCameraPosition() } } @@ -57,11 +76,12 @@ class SceneCanvas : OpenGLCanvas() { fun zoomToPoint(screenPoint: Vector2i, newZoom: Float) { val cameraPoint = fromScreenToCameraPoint(screenPoint) window.camera.zoomToPoint(cameraPoint, newZoom) + + recalculateCameraCornersPositions() + constraintCameraPosition() } override fun onInit() { - moveCameraToLeftUpperCorner() - val controller = ServiceLocator.getService() ?: return sceneController = controller @@ -76,15 +96,31 @@ class SceneCanvas : OpenGLCanvas() { private fun fromScreenToCameraPoint(screenPoint: Vector2i) = screenPoint - (window.size / 2) - private fun moveCameraToLeftUpperCorner() { - window.camera.position = - Vector2f(window.width.toFloat(), window.height.toFloat()) / (2f * window.camera.scaledZoom) + private fun constraintCameraPosition() { + window.camera.position.x = + window.camera.position.x.coerceIn(leftUpperCornerCameraPosition.x, rightLowerCornerCameraPosition.x) + window.camera.position.y = + window.camera.position.y.coerceIn(leftUpperCornerCameraPosition.y, rightLowerCornerCameraPosition.y) } + private fun recalculateCameraCornersPositions() { + val halfScreenSize = (Vector2f(window.width.toFloat(), window.height.toFloat()) / 2f) / window.camera.scaledZoom + leftUpperCornerCameraPosition = halfScreenSize + val framesSelectionSize = + Vector2i(columnsNumber * framesSize.x, rowsNumber * framesSize.y).toFloatVector() + val framesSelectionScreenSize = + framesSelectionSize * window.camera.zoom / IdentityFramesSizeScale / window.camera.scaledZoom + + rightLowerCornerCameraPosition = framesSelectionScreenSize - leftUpperCornerCameraPosition + + rightLowerCornerCameraPosition.x = + rightLowerCornerCameraPosition.x.coerceAtLeast(leftUpperCornerCameraPosition.x) + rightLowerCornerCameraPosition.y = + rightLowerCornerCameraPosition.y.coerceAtLeast(leftUpperCornerCameraPosition.y) + } companion object { - private const val DefaultMinZoom = 0.1f - private const val DefaultMaxZoom = 10f + const val IdentityFramesSizeScale = 1.605f } } diff --git a/src/main/kotlin/solve/rendering/engine/rendering/renderers/DefaultRenderer.kt b/src/main/kotlin/solve/rendering/engine/rendering/renderers/DefaultRenderer.kt index d43cdf40..02211fc1 100644 --- a/src/main/kotlin/solve/rendering/engine/rendering/renderers/DefaultRenderer.kt +++ b/src/main/kotlin/solve/rendering/engine/rendering/renderers/DefaultRenderer.kt @@ -62,6 +62,7 @@ class DefaultRenderer( val gameObject = spriteRenderer.sceneObject ?: return@forEach val texture = sprite.texture + val textureSidesRatio = texture.width.toFloat() / texture.height.toFloat() val batch = getAvailableBatch(texture, gameObject.transform.zIndex) val textureID = batch.getTextureLocalID(texture) val scale = gameObject.transform.scale @@ -70,7 +71,10 @@ class DefaultRenderer( val uvCoordinates = sprite.uvCoordinates spriteLocalVerticesPositions.forEachIndexed { index, vertexPosition -> - val vertexLocalVector = Vector2f(vertexPosition.x * scale.x, vertexPosition.y * scale.y) + val vertexLocalVector = Vector2f( + vertexPosition.x * scale.x * textureSidesRatio, + vertexPosition.y * scale.y + ) batch.pushVector2f(position + vertexLocalVector) batch.pushVector4f(color.toVector4f()) diff --git a/src/main/kotlin/solve/rendering/engine/rendering/renderers/FramesRenderer.kt b/src/main/kotlin/solve/rendering/engine/rendering/renderers/FramesRenderer.kt index 8ddb3143..687bb6e3 100644 --- a/src/main/kotlin/solve/rendering/engine/rendering/renderers/FramesRenderer.kt +++ b/src/main/kotlin/solve/rendering/engine/rendering/renderers/FramesRenderer.kt @@ -21,15 +21,22 @@ import solve.rendering.engine.shader.ShaderProgram import solve.rendering.engine.shader.ShaderType import solve.rendering.engine.structures.IntRect import solve.rendering.engine.utils.minus +import solve.rendering.engine.utils.toFloatVector import solve.rendering.engine.utils.toIntVector import solve.scene.model.VisualizationFrame import solve.utils.ceilToInt +import java.util.Date import kotlin.math.abs +import kotlin.math.min class FramesRenderer( window: Window ) : Renderer(window) { - private data class LoadedBufferFrameData(val textureData: Texture2DData, val bufferIndex: Int) + private data class LoadedBufferFrameData( + val textureData: Texture2DData, + val bufferIndex: Int, + val time: Long + ) override val maxBatchSize = 1000 private var modelsCommonMatrix = Matrix4f().identity() @@ -46,7 +53,10 @@ class FramesRenderer( private var framesChannelsType = TextureChannelsType.RGBA private var selectedFrames = emptyList() - private var cameraLastGridCellPosition = getCameraGridCellPosition() + private val framesRatio: Float + get() = framesWidth.toFloat() / framesHeight.toFloat() + + private var cameraLastGridCellPosition = getScreenCenterGridCellPosition() private val bufferFramesToUpload = mutableListOf() private val framesLoadingCoroutineScope = CoroutineScope(Dispatchers.Default) @@ -64,7 +74,7 @@ class FramesRenderer( return } - this.gridWidth = gridWidth + this.gridWidth = min(gridWidth, selectedFrames.count()) } fun setNewSceneFrames(frames: List) { @@ -78,10 +88,6 @@ class FramesRenderer( } fun setFramesSelection(frames: List) { - if (frames.isEmpty()) { - return - } - this.selectedFrames = frames haveNewFramesSelection = true } @@ -102,6 +108,8 @@ class FramesRenderer( shaderProgram.uploadInt(GridWidthUniformName, gridWidth) shaderProgram.uploadVector2i(BuffersSizeUniformName, buffersSize) shaderProgram.uploadInt(TexturesArrayUniformName, 0) + shaderProgram.uploadFloat(TexturesRatioUniformName, framesRatio) + shaderProgram.uploadVector2f(CameraPositionUniformName, getScreenCenterGridCellPosition().toFloatVector()) } override fun createNewBatch(zIndex: Int) = @@ -142,14 +150,14 @@ class FramesRenderer( private fun uploadLoadedFramesToBuffers() { bufferFramesToUpload.toList().forEach { frame -> - bufferFramesArrayTexture?.uploadTexture(frame.textureData, frame.bufferIndex) bufferFramesToUpload.remove(frame) + bufferFramesArrayTexture?.uploadTexture(frame.textureData, frame.bufferIndex) Texture2D.freeData(frame.textureData) } } private fun updateBuffersTextures() { - val cameraGridCellPosition = getCameraGridCellPosition() + val cameraGridCellPosition = getScreenCenterGridCellPosition() if (cameraGridCellPosition != cameraLastGridCellPosition) { loadNewTexturesToBuffers(cameraGridCellPosition) } @@ -196,7 +204,7 @@ class FramesRenderer( val framesRect = mutableListOf>() for (y in rect.y0 until rect.y0 + rect.height) { val framesFromIndex = (gridWidth * y + rect.x0).coerceIn(0..selectedFrames.lastIndex) - val framesToIndex = (framesFromIndex + rect.width).coerceIn(0..selectedFrames.lastIndex) + val framesToIndex = (framesFromIndex + rect.width).coerceIn(0..selectedFrames.count()) framesRect.add(selectedFrames.subList(framesFromIndex, framesToIndex)) } @@ -212,10 +220,10 @@ class FramesRenderer( } val buffersOffset = Vector2i(framesRect.x0 % buffersSize.x, framesRect.y0 % buffersSize.y) - if (framesRect.width > buffersSize.x || framesRect.height > buffersSize.y) { + /*if (framesRect.width > buffersSize.x || framesRect.height > buffersSize.y) { println("The size of the loading frames is out of buffers bounds!") return - } + }*/ for (y in 0 until rectFrames.count()) { for (x in 0 until rectFrames[y].count()) { @@ -226,8 +234,9 @@ class FramesRenderer( } } - private fun getCameraGridCellPosition(): Vector2i { - return (window.camera.position - Vector2f(buffersSize) / 2f).toIntVector() + private fun getScreenCenterGridCellPosition(): Vector2i { + val cameraGridCellPosition = Vector2f(window.camera.position.x / framesRatio, window.camera.position.y) + return (cameraGridCellPosition - Vector2f(buffersSize) / 2f).toIntVector() } private fun uploadAllFramesToBuffer() { @@ -250,6 +259,7 @@ class FramesRenderer( } private fun uploadFrameToBuffersArray(frame: VisualizationFrame, index: Int) { + val loadTime = Date().time framesLoadingCoroutineScope.launch { val textureData = Texture2D.loadData(frame.imagePath.toString()) if (textureData == null) { @@ -257,7 +267,7 @@ class FramesRenderer( return@launch } - bufferFramesToUpload.add(LoadedBufferFrameData(textureData, index)) + bufferFramesToUpload.add(LoadedBufferFrameData(textureData, index, loadTime)) } } @@ -267,9 +277,11 @@ class FramesRenderer( private const val GridWidthUniformName = "uGridWidth" private const val BuffersSizeUniformName = "uBuffersSize" private const val TexturesArrayUniformName = "uTextures" + private const val TexturesRatioUniformName = "uTexturesRatio" + private const val CameraPositionUniformName = "uCameraPosition" private const val DefaultGridWidth = 10 - private val defaultBuffersSize = Vector2i(4, 4) + private val defaultBuffersSize = Vector2i(5, 5) } } diff --git a/src/main/kotlin/solve/scene/controller/SceneController.kt b/src/main/kotlin/solve/scene/controller/SceneController.kt index 46eeeefd..4f4f20de 100644 --- a/src/main/kotlin/solve/scene/controller/SceneController.kt +++ b/src/main/kotlin/solve/scene/controller/SceneController.kt @@ -3,6 +3,7 @@ package solve.scene.controller import javafx.beans.property.SimpleDoubleProperty import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleObjectProperty +import solve.rendering.canvas.SceneCanvas import solve.scene.model.Scene import solve.scene.view.SceneView import solve.utils.ServiceLocator @@ -95,7 +96,10 @@ class SceneController : Controller() { var scale: Double get() = scaleProperty.value private set(value) { - scaleProperty.value = value.coerceIn(installedMinScale, installedMaxScale) + scaleProperty.value = value.coerceIn( + max(installedMinScale, minScale), + min(installedMaxScale, maxScale) + ) } private val minScale: Double @@ -137,7 +141,7 @@ class SceneController : Controller() { fun increaseScale() { scale *= ScaleFactor } - + fun decreaseScale() { scale /= ScaleFactor } @@ -194,8 +198,8 @@ class SceneController : Controller() { return min( max( installedMinScale, - sceneWidthProperty.value / - ((scene.frameSize.width + SceneView.framesMargin) * columnsNumber) + sceneWidthProperty.value / ((scene.frameSize.width + SceneView.framesMargin) * columnsNumber) * + SceneCanvas.IdentityFramesSizeScale ), DefaultMaxScale ) @@ -210,7 +214,7 @@ class SceneController : Controller() { const val DefaultMinScale = 0.2 const val DefaultMaxScale = 20.0 - const val MaxColumnsNumber = 5 + const val MaxColumnsNumber = 6 private const val DefaultX = 0.0 private const val DefaultY = 0.0 diff --git a/src/main/kotlin/solve/scene/model/Scene.kt b/src/main/kotlin/solve/scene/model/Scene.kt index 67b8432e..ad143cb2 100644 --- a/src/main/kotlin/solve/scene/model/Scene.kt +++ b/src/main/kotlin/solve/scene/model/Scene.kt @@ -1,5 +1,6 @@ package solve.scene.model +import solve.utils.loadBufferedImage import solve.utils.structures.Size as DoubleSize /** @@ -17,8 +18,8 @@ class Scene( */ val frameSize: DoubleSize by lazy { frames.firstOrNull()?.let { - // TODO - DoubleSize(0.0, 0.0) + val image = loadBufferedImage(it.imagePath.toString()) ?: return@let null + return@let DoubleSize(image.width.toDouble(), image.height.toDouble()) } ?: DoubleSize(0.0, 0.0) } diff --git a/src/main/kotlin/solve/scene/view/SceneView.kt b/src/main/kotlin/solve/scene/view/SceneView.kt index 71dd8a23..a169e599 100644 --- a/src/main/kotlin/solve/scene/view/SceneView.kt +++ b/src/main/kotlin/solve/scene/view/SceneView.kt @@ -30,7 +30,9 @@ class SceneView : View() { override val root = canvas.canvas private val projectChangedEventHandler = InvalidationListener { - canvas.setNewSceneFrames(controller.scene.frames) + val framesSize = controller.scene.frameSize + val framesSizeVector = Vector2i(framesSize.width.toInt(), framesSize.height.toInt()) + canvas.setNewSceneFrames(controller.scene.frames, framesSizeVector) } private val framesChangedEventHandler = InvalidationListener { canvas.setFramesSelection(controller.scene.frames) @@ -108,7 +110,7 @@ class SceneView : View() { private const val OnWheelScrolledScaleMultiplier = 1.1f private val MouseDragButton = MouseButton.MIDDLE - const val framesMargin = 10.0 + const val framesMargin = 0.0 const val scrollSpeed = 20.0 } } diff --git a/src/main/resources/engine/shaders/frame/frame.frag b/src/main/resources/engine/shaders/frame/frame.frag index 63931eba..74b0be4d 100644 --- a/src/main/resources/engine/shaders/frame/frame.frag +++ b/src/main/resources/engine/shaders/frame/frame.frag @@ -9,5 +9,9 @@ out vec4 color; void main() { - color = texture(uTextures, vec3(fTexCoords, fTexID)); + if (fTexID == -1.0) { + color = vec4(0.0, 0.0, 0.0, 0.0); + } else { + color = texture(uTextures, vec3(fTexCoords, fTexID)); + } } diff --git a/src/main/resources/engine/shaders/frame/frame.geom b/src/main/resources/engine/shaders/frame/frame.geom index 100f0430..4de5a370 100644 --- a/src/main/resources/engine/shaders/frame/frame.geom +++ b/src/main/resources/engine/shaders/frame/frame.geom @@ -4,9 +4,11 @@ uniform mat4 uProjection; uniform mat4 uModel; uniform int uGridWidth; uniform ivec2 uBuffersSize; +uniform float uTexturesRatio; in VS_OUT { int frameID; + bool isColored; } gs_in[]; out float fTexID; @@ -16,33 +18,50 @@ layout (points) in; layout (triangle_strip, max_vertices = 4) out; void main() { - int frameID = gs_in[0].frameID; - int frameX = int(mod(frameID, uGridWidth)); - int frameY = frameID / uGridWidth; - - int bufferX = int(mod(frameX, uBuffersSize.x)); - int bufferY = int(mod(frameY, uBuffersSize.y)); - float texID = float(bufferY * uBuffersSize.x + bufferX); - - gl_Position = uProjection * uModel * gl_in[0].gl_Position; - fTexID = texID; - fTexCoords = vec2(0.0, 1.0); - EmitVertex(); - - gl_Position = uProjection * uModel * (gl_in[0].gl_Position + vec4(1.0, 0.0, 0.0, 0.0)); - fTexID = texID; - fTexCoords = vec2(1.0, 1.0); - EmitVertex(); - - gl_Position = uProjection * uModel * (gl_in[0].gl_Position + vec4(0.0, 1.0, 0.0, 0.0)); - fTexID = texID; - fTexCoords = vec2(0.0, 0.0); - EmitVertex(); - - gl_Position = uProjection * uModel * (gl_in[0].gl_Position + vec4(1.0, 1.0, 0.0, 0.0)); - fTexID = texID; - fTexCoords = vec2(1.0, 0.0); - EmitVertex(); - - EndPrimitive(); + bool isColored = gs_in[0].isColored; + + if (isColored) { + int frameID = gs_in[0].frameID; + int frameX = int(mod(frameID, uGridWidth)); + int frameY = frameID / uGridWidth; + + int bufferX = int(mod(frameX, uBuffersSize.x)); + int bufferY = int(mod(frameY, uBuffersSize.y)); + float texID = float(bufferY * uBuffersSize.x + bufferX); + + vec4 initialPosition = vec4( + gl_in[0].gl_Position.x * uTexturesRatio, + gl_in[0].gl_Position.y, + gl_in[0].gl_Position.z, + gl_in[0].gl_Position.w + ); + gl_Position = uProjection * uModel * initialPosition; + fTexID = texID; + fTexCoords = vec2(0.0, 1.0); + EmitVertex(); + + gl_Position = uProjection * uModel * (initialPosition + vec4(uTexturesRatio, 0.0, 0.0, 0.0)); + fTexID = texID; + fTexCoords = vec2(1.0, 1.0); + EmitVertex(); + + gl_Position = uProjection * uModel * (initialPosition + vec4(0.0, 1.0, 0.0, 0.0)); + fTexID = texID; + fTexCoords = vec2(0.0, 0.0); + EmitVertex(); + + gl_Position = uProjection * uModel * (initialPosition + vec4(uTexturesRatio, 1.0, 0.0, 0.0)); + fTexID = texID; + fTexCoords = vec2(1.0, 0.0); + EmitVertex(); + + EndPrimitive(); + } else { + gl_Position = vec4(0.0, 0.0, 0.0, 0.0); + fTexID = -1.0; + fTexCoords = vec2(0.0, 0.0); + EmitVertex(); + + EndPrimitive(); + } } diff --git a/src/main/resources/engine/shaders/frame/frame.vert b/src/main/resources/engine/shaders/frame/frame.vert index 9c8c1069..7cc4eec2 100644 --- a/src/main/resources/engine/shaders/frame/frame.vert +++ b/src/main/resources/engine/shaders/frame/frame.vert @@ -5,16 +5,31 @@ layout (location=0) in float aIndex; uniform mat4 uProjection; uniform mat4 uModel; uniform int uGridWidth; +uniform ivec2 uBuffersSize; +uniform vec2 uCameraPosition; +uniform float uTexturesRatio; out VS_OUT { int frameID; + bool isColored; } vs_out; void main() { int index = int(aIndex); - int xPos = index % uGridWidth; - int yPos = index / uGridWidth; - gl_Position = vec4(xPos, yPos, 0.0, 1.0); vs_out.frameID = index; + + vec2 cameraGridPosition = vec2(uCameraPosition.x / uTexturesRatio, uCameraPosition.y); + vec2 framePosition = vec2(index % uGridWidth, index / uGridWidth); + vec2 frameCameraPosition = framePosition - cameraGridPosition; + if (frameCameraPosition.x < 0 || + frameCameraPosition.x >= uBuffersSize.x || + frameCameraPosition.y < 0 || + frameCameraPosition.y >= uBuffersSize.y) { + vs_out.isColored = false; + } else { + vs_out.isColored = true; + } + vs_out.isColored = true; + gl_Position = vec4(framePosition, 0.0, 1.0); }