Migrate CRLF file endings to LF

This commit is contained in:
2021-02-10 17:16:37 +01:00
parent 98ac49cca0
commit e60add30c5
141 changed files with 4973 additions and 4978 deletions

38
.gitattributes vendored Executable file → Normal file
View File

@@ -1,19 +1,19 @@
# #
# https://help.github.com/articles/dealing-with-line-endings/ # https://help.github.com/articles/dealing-with-line-endings/
# #
# These are explicitly windows files and should use crlf # These are explicitly windows files and should use crlf
*.bat text eol=crlf *.bat text eol=crlf
# The rest of project files should use lf # The rest of project files should use lf
*.gradle text eol=lf *.gradle text eol=lf
*.kt text eol=lf *.kt text eol=lf
*.yml text eol=lf *.yml text eol=lf
*.java text eol=lf *.java text eol=lf
*.fs text eol=lf *.fs text eol=lf
*.vs text eol=lf *.vs text eol=lf
*.properties text eol=lf *.properties text eol=lf
*.proto text eol=lf *.proto text eol=lf
gradlew text eol=lf gradlew text eol=lf
# Binary files # Binary files
*.jar binary *.jar binary

276
.gitignore vendored Executable file → Normal file
View File

@@ -1,138 +1,138 @@
# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij # Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij
# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij # Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij
### Intellij ### ### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
.idea/ .idea/
# Gradle # Gradle
.idea/**/gradle.xml .idea/**/gradle.xml
.idea/**/libraries .idea/**/libraries
# Gradle and Maven with auto-import # Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files, # When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using # since they will be recreated, and may cause churn. Uncomment if using
# auto-import. # auto-import.
# .idea/artifacts # .idea/artifacts
# .idea/compiler.xml # .idea/compiler.xml
# .idea/jarRepositories.xml # .idea/jarRepositories.xml
# .idea/modules.xml # .idea/modules.xml
# .idea/*.iml # .idea/*.iml
# .idea/modules # .idea/modules
# *.iml # *.iml
# *.ipr # *.ipr
# CMake # CMake
cmake-build-*/ cmake-build-*/
# Mongo Explorer plugin # Mongo Explorer plugin
.idea/**/mongoSettings.xml .idea/**/mongoSettings.xml
# File-based project format # File-based project format
*.iws *.iws
# IntelliJ # IntelliJ
out/ out/
# mpeltonen/sbt-idea plugin # mpeltonen/sbt-idea plugin
.idea_modules/ .idea_modules/
# JIRA plugin # JIRA plugin
atlassian-ide-plugin.xml atlassian-ide-plugin.xml
# Cursive Clojure plugin # Cursive Clojure plugin
.idea/replstate.xml .idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ) # Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml com_crashlytics_export_strings.xml
crashlytics.properties crashlytics.properties
crashlytics-build.properties crashlytics-build.properties
fabric.properties fabric.properties
# Editor-based Rest Client # Editor-based Rest Client
.idea/httpRequests .idea/httpRequests
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
### Intellij Patch ### ### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml # *.iml
# modules.xml # modules.xml
# .idea/misc.xml # .idea/misc.xml
# *.ipr # *.ipr
# Sonarlint plugin # Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint # https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/ .idea/**/sonarlint/
# SonarQube Plugin # SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml .idea/**/sonarIssues.xml
# Markdown Navigator plugin # Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml .idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml .idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/ .idea/**/markdown-navigator/
# Cache file creation bug # Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257 # See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$ .idea/$CACHE_FILE$
# CodeStream plugin # CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream # https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml .idea/codestream.xml
### Java ### ### Java ###
# Compiled class file # Compiled class file
*.class *.class
# Log file # Log file
*.log *.log
# BlueJ files # BlueJ files
*.ctxt *.ctxt
# Mobile Tools for Java (J2ME) # Mobile Tools for Java (J2ME)
.mtj.tmp/ .mtj.tmp/
# Package Files # # Package Files #
*.jar *.jar
*.war *.war
*.nar *.nar
*.ear *.ear
*.zip *.zip
*.tar.gz *.tar.gz
*.rar *.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
### Gradle ### ### Gradle ###
.gradle .gradle
build/ build/
# Ignore Gradle GUI config # Ignore Gradle GUI config
gradle-app.setting gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar !gradle-wrapper.jar
# Cache of project # Cache of project
.gradletasknamecache .gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties # gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ### ### Gradle Patch ###
**/build/ **/build/
### Textures and other resources ### ### Textures and other resources ###
*.png *.png
# End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij # End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij

100
editor/build.gradle Executable file → Normal file
View File

@@ -1,50 +1,50 @@
plugins { plugins {
id 'org.jetbrains.kotlin.jvm' version '1.4.10' id 'org.jetbrains.kotlin.jvm' version '1.4.10'
id 'org.openjfx.javafxplugin' version '0.0.8' id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.springframework.boot' version "$springBootVersion" id 'org.springframework.boot' version "$springBootVersion"
id 'io.spring.dependency-management' version "$springDependencyManagementVersion" id 'io.spring.dependency-management' version "$springDependencyManagementVersion"
id 'idea' id 'idea'
} }
group 'com.bartlomiejpluta.base' group 'com.bartlomiejpluta.base'
version 'unspecified' version 'unspecified'
repositories { repositories {
mavenCentral() mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
} }
sourceSets { sourceSets {
main.kotlin.srcDirs += 'src/main/kotlin' main.kotlin.srcDirs += 'src/main/kotlin'
} }
javafx { javafx {
version = "11.0.2" version = "11.0.2"
modules = ['javafx.controls', 'javafx.graphics'] modules = ['javafx.controls', 'javafx.graphics']
} }
compileKotlin { compileKotlin {
kotlinOptions.jvmTarget = "14" kotlinOptions.jvmTarget = "14"
} }
compileTestKotlin { compileTestKotlin {
kotlinOptions.jvmTarget = "14" kotlinOptions.jvmTarget = "14"
} }
dependencies { dependencies {
implementation project(":proto") implementation project(":proto")
implementation "org.jetbrains.kotlin:kotlin-stdlib" implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation "no.tornado:tornadofx:${tornadoFxVersion}" implementation "no.tornado:tornadofx:${tornadoFxVersion}"
implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}") implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}")
implementation 'org.kordamp.ikonli:ikonli-javafx' implementation 'org.kordamp.ikonli:ikonli-javafx'
implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack' implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack'
// Spring // Spring
implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter'
} }
build { build {
dependsOn(":proto:build") dependsOn(":proto:build")
} }

View File

@@ -1,35 +1,35 @@
package com.bartlomiejpluta.base.editor package com.bartlomiejpluta.base.editor
import com.bartlomiejpluta.base.editor.main.view.MainView import com.bartlomiejpluta.base.editor.main.view.MainView
import org.springframework.boot.SpringApplication import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.ConfigurableApplicationContext import org.springframework.context.ConfigurableApplicationContext
import tornadofx.App import tornadofx.App
import tornadofx.DIContainer import tornadofx.DIContainer
import tornadofx.FX import tornadofx.FX
import tornadofx.launch import tornadofx.launch
import kotlin.reflect.KClass import kotlin.reflect.KClass
@SpringBootApplication @SpringBootApplication
open class EditorApp : App(MainView::class) { open class EditorApp : App(MainView::class) {
private lateinit var context: ConfigurableApplicationContext private lateinit var context: ConfigurableApplicationContext
override fun init() { override fun init() {
this.context = SpringApplication.run(this.javaClass) this.context = SpringApplication.run(this.javaClass)
context.autowireCapableBeanFactory.autowireBean(this) context.autowireCapableBeanFactory.autowireBean(this)
FX.dicontainer = object : DIContainer { FX.dicontainer = object : DIContainer {
override fun <T : Any> getInstance(type: KClass<T>): T = context.getBean(type.java) override fun <T : Any> getInstance(type: KClass<T>): T = context.getBean(type.java)
override fun <T : Any> getInstance(type: KClass<T>, name: String): T = context.getBean(name, type.java) override fun <T : Any> getInstance(type: KClass<T>, name: String): T = context.getBean(name, type.java)
} }
} }
override fun stop() { override fun stop() {
super.stop() super.stop()
context.close() context.close()
} }
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
launch<EditorApp>(args) launch<EditorApp>(args)
} }

View File

@@ -1,3 +1,3 @@
package com.bartlomiejpluta.base.editor.command.context package com.bartlomiejpluta.base.editor.command.context
interface UndoableContext interface UndoableContext

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.editor.command.context package com.bartlomiejpluta.base.editor.command.context
import tornadofx.Scope import tornadofx.Scope
class UndoableScope : UndoableContext, Scope() class UndoableScope : UndoableContext, Scope()

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.editor.command.model.base package com.bartlomiejpluta.base.editor.command.model.base
interface Command { interface Command {
fun execute() fun execute()
} }

View File

@@ -1,21 +1,21 @@
package com.bartlomiejpluta.base.editor.command.model.base package com.bartlomiejpluta.base.editor.command.model.base
class SimpleCommand<T>( class SimpleCommand<T>(
override val commandName: String, override val commandName: String,
private val formerValue: T, private val formerValue: T,
private val value: T, private val value: T,
private val execute: (T) -> Unit private val execute: (T) -> Unit
) : Undoable, Command { ) : Undoable, Command {
override fun undo() { override fun undo() {
execute(formerValue) execute(formerValue)
} }
override fun redo() { override fun redo() {
execute() execute()
} }
override fun execute() { override fun execute() {
execute(value) execute(value)
} }
} }

View File

@@ -1,7 +1,7 @@
package com.bartlomiejpluta.base.editor.command.model.base package com.bartlomiejpluta.base.editor.command.model.base
interface Undoable { interface Undoable {
fun undo() fun undo()
fun redo() fun redo()
val commandName: String val commandName: String
} }

View File

@@ -1,23 +1,23 @@
package com.bartlomiejpluta.base.editor.command.model.map package com.bartlomiejpluta.base.editor.command.model.map
import com.bartlomiejpluta.base.editor.command.model.base.Command import com.bartlomiejpluta.base.editor.command.model.base.Command
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
class CreateLayerCommand(private val map: GameMap, private val layer: Layer): Undoable, Command { class CreateLayerCommand(private val map: GameMap, private val layer: Layer): Undoable, Command {
override fun execute() { override fun execute() {
map.layers += layer map.layers += layer
} }
override fun undo() { override fun undo() {
map.layers -= layer map.layers -= layer
} }
override fun redo() { override fun redo() {
execute() execute()
} }
override val commandName = "Create map layer" override val commandName = "Create map layer"
} }

View File

@@ -1,22 +1,22 @@
package com.bartlomiejpluta.base.editor.command.model.map package com.bartlomiejpluta.base.editor.command.model.map
import com.bartlomiejpluta.base.editor.command.model.base.Command import com.bartlomiejpluta.base.editor.command.model.base.Command
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import tornadofx.swap import tornadofx.swap
class MoveLayerCommand(private val map: GameMap, private val currentIndex: Int, private val newIndex: Int) : Undoable, Command { class MoveLayerCommand(private val map: GameMap, private val currentIndex: Int, private val newIndex: Int) : Undoable, Command {
override fun execute() { override fun execute() {
map.layers.swap(currentIndex, newIndex) map.layers.swap(currentIndex, newIndex)
} }
override fun undo() { override fun undo() {
map.layers.swap(newIndex, currentIndex) map.layers.swap(newIndex, currentIndex)
} }
override fun redo() { override fun redo() {
execute() execute()
} }
override val commandName = "Move layer" override val commandName = "Move layer"
} }

View File

@@ -1,25 +1,25 @@
package com.bartlomiejpluta.base.editor.command.model.map package com.bartlomiejpluta.base.editor.command.model.map
import com.bartlomiejpluta.base.editor.command.model.base.Command import com.bartlomiejpluta.base.editor.command.model.base.Command
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
class RemoveLayerCommand(private val map: GameMap, private val layerIndex: Int) : Undoable, Command { class RemoveLayerCommand(private val map: GameMap, private val layerIndex: Int) : Undoable, Command {
private var layer: Layer? = null private var layer: Layer? = null
override fun execute() { override fun execute() {
layer = map.layers.removeAt(layerIndex) layer = map.layers.removeAt(layerIndex)
} }
override fun undo() { override fun undo() {
map.layers.add(layerIndex, layer) map.layers.add(layerIndex, layer)
layer = null layer = null
} }
override fun redo() { override fun redo() {
execute() execute()
} }
override val commandName = "Remove layer" override val commandName = "Remove layer"
} }

View File

@@ -1,23 +1,23 @@
package com.bartlomiejpluta.base.editor.command.model.map package com.bartlomiejpluta.base.editor.command.model.map
import com.bartlomiejpluta.base.editor.command.model.base.Command import com.bartlomiejpluta.base.editor.command.model.base.Command
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
class RenameLayerCommand(private val layer: Layer, private val newName: String) : Undoable, Command { class RenameLayerCommand(private val layer: Layer, private val newName: String) : Undoable, Command {
private val formerName = layer.name private val formerName = layer.name
override fun execute() { override fun execute() {
layer.name = newName layer.name = newName
} }
override fun undo() { override fun undo() {
layer.name = formerName layer.name = formerName
} }
override fun redo() { override fun redo() {
execute() execute()
} }
override val commandName = "Rename layer" override val commandName = "Rename layer"
} }

View File

@@ -1,122 +1,122 @@
package com.bartlomiejpluta.base.editor.command.service package com.bartlomiejpluta.base.editor.command.service
import com.bartlomiejpluta.base.editor.command.context.UndoableContext import com.bartlomiejpluta.base.editor.command.context.UndoableContext
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.lang.Integer.toHexString import java.lang.Integer.toHexString
import java.util.* import java.util.*
@Component @Component
class DefaultUndoRedoService : UndoRedoService { class DefaultUndoRedoService : UndoRedoService {
private val undo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque() private val undo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque()
private val redo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque() private val redo: Deque<Pair<Undoable, UndoableContext>> = ArrayDeque()
var sizeMax = 30 var sizeMax = 30
set(value) { set(value) {
if (value >= 0) { if (value >= 0) {
for (i in 0 until undo.size - value) { for (i in 0 until undo.size - value) {
undo.removeLast() undo.removeLast()
} }
field = value field = value
} }
} }
override fun push(undoable: Undoable) { override fun push(undoable: Undoable) {
push(undoable, GLOBAL_CONTEXT) push(undoable, GLOBAL_CONTEXT)
} }
override fun push(undoable: Undoable, context: UndoableContext) { override fun push(undoable: Undoable, context: UndoableContext) {
if (undo.size == sizeMax) { if (undo.size == sizeMax) {
log.debug("The max size of [undo] stack has been reached. Removing the last item...") log.debug("The max size of [undo] stack has been reached. Removing the last item...")
undo.removeLast() undo.removeLast()
} }
log.debug("Pushing item to [undo] stack: ${undoable.commandName} (ctx: ${toHexString(context.hashCode())})") log.debug("Pushing item to [undo] stack: ${undoable.commandName} (ctx: ${toHexString(context.hashCode())})")
undo.push(undoable to context) undo.push(undoable to context)
redo.clear() redo.clear()
} }
override fun undo() { override fun undo() {
if (undo.isNotEmpty()) { if (undo.isNotEmpty()) {
undo.pop().let { undo.pop().let {
log.debug("Performing undo: ${it.first.commandName}") log.debug("Performing undo: ${it.first.commandName}")
it.first.undo() it.first.undo()
redo.push(it) redo.push(it)
} }
} }
} }
override fun undo(context: UndoableContext) { override fun undo(context: UndoableContext) {
if (undo.isNotEmpty()) { if (undo.isNotEmpty()) {
undo.firstOrNull { it.second === context }?.let { undo.firstOrNull { it.second === context }?.let {
log.debug("Performing contextual (ctx: ${toHexString(context.hashCode())}) undo: ${it.first.commandName}") log.debug("Performing contextual (ctx: ${toHexString(context.hashCode())}) undo: ${it.first.commandName}")
undo.remove(it) undo.remove(it)
it.first.undo() it.first.undo()
redo.push(it) redo.push(it)
} }
} }
} }
override fun redo() { override fun redo() {
if (redo.isNotEmpty()) { if (redo.isNotEmpty()) {
redo.pop().let { redo.pop().let {
log.debug("Performing redo: ${it.first.commandName}") log.debug("Performing redo: ${it.first.commandName}")
it.first.redo() it.first.redo()
undo.push(it) undo.push(it)
} }
} }
} }
override fun redo(context: UndoableContext) { override fun redo(context: UndoableContext) {
if (redo.isNotEmpty()) { if (redo.isNotEmpty()) {
redo.firstOrNull { it.second === context }?.let { redo.firstOrNull { it.second === context }?.let {
log.debug("Performing contextual (ctx: ${toHexString(context.hashCode())}) redo: ${it.first.commandName}") log.debug("Performing contextual (ctx: ${toHexString(context.hashCode())}) redo: ${it.first.commandName}")
redo.remove(it) redo.remove(it)
it.first.redo() it.first.redo()
undo.push(it) undo.push(it)
} }
} }
} }
override fun clear() { override fun clear() {
log.debug("Clearing [undo] and [redo] stacks") log.debug("Clearing [undo] and [redo] stacks")
undo.clear() undo.clear()
redo.clear() redo.clear()
} }
override fun clear(context: UndoableContext) { override fun clear(context: UndoableContext) {
log.debug("Clearing [undo] and [redo] stacks (ctx: ${toHexString(context.hashCode())})") log.debug("Clearing [undo] and [redo] stacks (ctx: ${toHexString(context.hashCode())})")
undo.removeIf { it.second == context } undo.removeIf { it.second == context }
redo.removeIf { it.second == context } redo.removeIf { it.second == context }
} }
override val lastUndoable: Undoable? override val lastUndoable: Undoable?
get() = undo.first?.first get() = undo.first?.first
override val lastRedoable: Undoable? override val lastRedoable: Undoable?
get() = redo.first?.first get() = redo.first?.first
override val undoCommandName: String override val undoCommandName: String
get() = undo.first?.first?.commandName ?: "" get() = undo.first?.first?.commandName ?: ""
override val redoCommandName: String override val redoCommandName: String
get() = redo.first?.first?.commandName ?: "" get() = redo.first?.first?.commandName ?: ""
override fun lastUndoable(context: UndoableContext) = undo.firstOrNull { it.second === context }?.first override fun lastUndoable(context: UndoableContext) = undo.firstOrNull { it.second === context }?.first
override fun lastRedoable(context: UndoableContext) = redo.firstOrNull { it.second === context }?.first override fun lastRedoable(context: UndoableContext) = redo.firstOrNull { it.second === context }?.first
override fun undoCommandName(context: UndoableContext) = override fun undoCommandName(context: UndoableContext) =
undo.firstOrNull { it.second === context }?.first?.commandName ?: "" undo.firstOrNull { it.second === context }?.first?.commandName ?: ""
override fun redoCommandName(context: UndoableContext) = override fun redoCommandName(context: UndoableContext) =
redo.firstOrNull { it.second === context }?.first?.commandName ?: "" redo.firstOrNull { it.second === context }?.first?.commandName ?: ""
companion object { companion object {
private val log = LoggerFactory.getLogger(DefaultUndoRedoService::class.java) private val log = LoggerFactory.getLogger(DefaultUndoRedoService::class.java)
private val GLOBAL_CONTEXT = object : UndoableContext {} private val GLOBAL_CONTEXT = object : UndoableContext {}
} }
} }

View File

@@ -1,26 +1,26 @@
package com.bartlomiejpluta.base.editor.command.service package com.bartlomiejpluta.base.editor.command.service
import com.bartlomiejpluta.base.editor.command.context.UndoableContext import com.bartlomiejpluta.base.editor.command.context.UndoableContext
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
interface UndoRedoService { interface UndoRedoService {
fun push(undoable: Undoable) fun push(undoable: Undoable)
fun undo() fun undo()
fun redo() fun redo()
fun clear() fun clear()
fun push(undoable: Undoable, context: UndoableContext) fun push(undoable: Undoable, context: UndoableContext)
fun undo(context: UndoableContext) fun undo(context: UndoableContext)
fun redo(context: UndoableContext) fun redo(context: UndoableContext)
fun clear(context: UndoableContext) fun clear(context: UndoableContext)
val lastUndoable: Undoable? val lastUndoable: Undoable?
val lastRedoable: Undoable? val lastRedoable: Undoable?
val undoCommandName: String val undoCommandName: String
val redoCommandName: String val redoCommandName: String
fun lastUndoable(context: UndoableContext): Undoable? fun lastUndoable(context: UndoableContext): Undoable?
fun lastRedoable(context: UndoableContext): Undoable? fun lastRedoable(context: UndoableContext): Undoable?
fun undoCommandName(context: UndoableContext): String fun undoCommandName(context: UndoableContext): String
fun redoCommandName(context: UndoableContext): String fun redoCommandName(context: UndoableContext): String
} }

View File

@@ -1,7 +1,7 @@
package com.bartlomiejpluta.base.editor.common.serial package com.bartlomiejpluta.base.editor.common.serial
import java.io.InputStream import java.io.InputStream
interface Deserializer<T> { interface Deserializer<T> {
fun deserialize(input: InputStream): T fun deserialize(input: InputStream): T
} }

View File

@@ -1,8 +1,8 @@
package com.bartlomiejpluta.base.editor.common.serial package com.bartlomiejpluta.base.editor.common.serial
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
interface Serializer<T> { interface Serializer<T> {
fun serialize(item: T, output: OutputStream) fun serialize(item: T, output: OutputStream)
} }

View File

@@ -1,7 +1,7 @@
package com.bartlomiejpluta.base.editor.event package com.bartlomiejpluta.base.editor.event
import tornadofx.EventBus.RunOn.ApplicationThread import tornadofx.EventBus.RunOn.ApplicationThread
import tornadofx.EventBus.RunOn.BackgroundThread import tornadofx.EventBus.RunOn.BackgroundThread
import tornadofx.FXEvent import tornadofx.FXEvent
object RedrawMapRequestEvent : FXEvent(ApplicationThread) object RedrawMapRequestEvent : FXEvent(ApplicationThread)

View File

@@ -1,59 +1,59 @@
package com.bartlomiejpluta.base.editor.main.controller package com.bartlomiejpluta.base.editor.main.controller
import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.context.UndoableScope
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import com.bartlomiejpluta.base.editor.map.view.MapSettingsFragment import com.bartlomiejpluta.base.editor.map.view.MapSettingsFragment
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import com.bartlomiejpluta.base.editor.project.manager.ProjectManager import com.bartlomiejpluta.base.editor.project.manager.ProjectManager
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import com.bartlomiejpluta.base.editor.project.view.ProjectSettingsFragment import com.bartlomiejpluta.base.editor.project.view.ProjectSettingsFragment
import com.bartlomiejpluta.base.editor.project.viewmodel.ProjectVM import com.bartlomiejpluta.base.editor.project.viewmodel.ProjectVM
import com.bartlomiejpluta.base.editor.tileset.model.TileSet import com.bartlomiejpluta.base.editor.tileset.model.TileSet
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.stage.FileChooser import javafx.stage.FileChooser
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import tornadofx.* import tornadofx.*
import kotlin.collections.set import kotlin.collections.set
@Component @Component
class MainController : Controller() { class MainController : Controller() {
// In the future it'll be pulled from TileSetService or something like that // In the future it'll be pulled from TileSetService or something like that
private val tileset = TileSet(resources.image("/textures/tileset.png"), 160, 8) private val tileset = TileSet(resources.image("/textures/tileset.png"), 160, 8)
private val projectManager: ProjectManager by di() private val projectManager: ProjectManager by di()
val openProject = SimpleObjectProperty<Project?>() val openProject = SimpleObjectProperty<Project?>()
val openMaps = observableMapOf<Scope, GameMap>() val openMaps = observableMapOf<Scope, GameMap>()
fun createEmptyProject() { fun createEmptyProject() {
val project = Project() val project = Project()
val vm = ProjectVM(project) val vm = ProjectVM(project)
setInScope(vm) setInScope(vm)
val modal = find<ProjectSettingsFragment>().apply { openModal(block = true, resizable = false) } val modal = find<ProjectSettingsFragment>().apply { openModal(block = true, resizable = false) }
if(modal.result) { if(modal.result) {
openProject.value = project openProject.value = project
projectManager.saveProject(project) projectManager.saveProject(project)
} }
} }
fun createEmptyMap() { fun createEmptyMap() {
val map = GameMap(tileset) val map = GameMap(tileset)
val scope = UndoableScope() val scope = UndoableScope()
val vm = GameMapVM(map) val vm = GameMapVM(map)
setInScope(vm, scope) setInScope(vm, scope)
val modal = find<MapSettingsFragment>(scope).apply { openModal(block = true, resizable = false) } val modal = find<MapSettingsFragment>(scope).apply { openModal(block = true, resizable = false) }
if (modal.result) { if (modal.result) {
openMaps[scope] = map openMaps[scope] = map
} }
} }
fun loadProject() { fun loadProject() {
chooseFile( chooseFile(
title = "Load Project", title = "Load Project",
filters = arrayOf(FileChooser.ExtensionFilter("BASE Editor Project (*.bep)", "*.bep")), filters = arrayOf(FileChooser.ExtensionFilter("BASE Editor Project (*.bep)", "*.bep")),
).getOrNull(0)?.let { openProject.value = projectManager.openProject(it) } ).getOrNull(0)?.let { openProject.value = projectManager.openProject(it) }
} }
} }

View File

@@ -1,40 +1,40 @@
package com.bartlomiejpluta.base.editor.main.view package com.bartlomiejpluta.base.editor.main.view
import com.bartlomiejpluta.base.editor.main.controller.MainController import com.bartlomiejpluta.base.editor.main.controller.MainController
import tornadofx.* import tornadofx.*
class MainMenuView : View() { class MainMenuView : View() {
private val mainController: MainController by di() private val mainController: MainController by di()
override val root = menubar { override val root = menubar {
menu("File") { menu("File") {
menu("New") { menu("New") {
item("Project...") { item("Project...") {
action { action {
mainController.createEmptyProject() mainController.createEmptyProject()
} }
} }
item("Map...") { item("Map...") {
enableWhen(mainController.openProject.isNotNull) enableWhen(mainController.openProject.isNotNull)
action { action {
mainController.createEmptyMap() mainController.createEmptyMap()
} }
} }
} }
menu("Open") { menu("Open") {
item("Project...") { item("Project...") {
action { action {
mainController.loadProject() mainController.loadProject()
} }
} }
} }
} }
menu("Edit") { menu("Edit") {
item("Undo") item("Undo")
item("Redo") item("Redo")
} }
} }
} }

View File

@@ -1,34 +1,34 @@
package com.bartlomiejpluta.base.editor.main.view package com.bartlomiejpluta.base.editor.main.view
import com.bartlomiejpluta.base.editor.main.controller.MainController import com.bartlomiejpluta.base.editor.main.controller.MainController
import com.bartlomiejpluta.base.editor.map.view.MapFragment import com.bartlomiejpluta.base.editor.map.view.MapFragment
import javafx.scene.control.Tab import javafx.scene.control.Tab
import tornadofx.* import tornadofx.*
class MainView : View("BASE Game Editor") { class MainView : View("BASE Game Editor") {
private val mainController: MainController by di() private val mainController: MainController by di()
private val mainMenuView = find<MainMenuView>() private val mainMenuView = find<MainMenuView>()
init { init {
mainController.openProject.addListener { _, _, project -> mainController.openProject.addListener { _, _, project ->
val projectName = project?.let { " :: ${it.name} (${it.sourceDirectory.absolutePath})" } ?: "" val projectName = project?.let { " :: ${it.name} (${it.sourceDirectory.absolutePath})" } ?: ""
title = "BASE Game Editor$projectName" title = "BASE Game Editor$projectName"
} }
} }
override val root = borderpane { override val root = borderpane {
top = mainMenuView.root top = mainMenuView.root
center = tabpane { center = tabpane {
tabs.bind(mainController.openMaps) { scope, map -> tabs.bind(mainController.openMaps) { scope, map ->
Tab().apply { Tab().apply {
textProperty().bindBidirectional(map.nameProperty) textProperty().bindBidirectional(map.nameProperty)
content = find<MapFragment>(scope).root content = find<MapFragment>(scope).root
setOnClosed { mainController.openMaps.remove(scope) } setOnClosed { mainController.openMaps.remove(scope) }
} }
} }
} }
} }
} }

View File

@@ -1,98 +1,98 @@
package com.bartlomiejpluta.base.editor.map.canvas package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.render.model.Renderable import com.bartlomiejpluta.base.editor.render.model.Renderable
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.canvas.GraphicsContext import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color import javafx.scene.paint.Color
class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, private val painter: MapPainter) : Renderable { class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, private val painter: MapPainter) : Renderable {
var tileSet = map.tileSet var tileSet = map.tileSet
private var tileWidth = map.tileWidth private var tileWidth = map.tileWidth
private var tileHeight = map.tileHeight private var tileHeight = map.tileHeight
override fun render(gc: GraphicsContext) { override fun render(gc: GraphicsContext) {
gc.clearRect(0.0, 0.0, gc.canvas.width, gc.canvas.height) gc.clearRect(0.0, 0.0, gc.canvas.width, gc.canvas.height)
renderBackground(gc) renderBackground(gc)
renderUnderlyingLayers(gc) renderUnderlyingLayers(gc)
renderCover(gc) renderCover(gc)
renderSelectedLayer(gc) renderSelectedLayer(gc)
renderGrid(gc) renderGrid(gc)
painter.render(gc) painter.render(gc)
} }
private fun renderSelectedLayer(gc: GraphicsContext) { private fun renderSelectedLayer(gc: GraphicsContext) {
map.layers.getOrNull(editorStateVM.selectedLayer) ?. let { dispatchLayerRender(gc, it) } map.layers.getOrNull(editorStateVM.selectedLayer) ?. let { dispatchLayerRender(gc, it) }
} }
private fun renderCover(gc: GraphicsContext) { private fun renderCover(gc: GraphicsContext) {
if(!editorStateVM.coverUnderlyingLayers) { if(!editorStateVM.coverUnderlyingLayers) {
return return
} }
gc.fill = Color.color(0.0, 0.0, 0.0, 0.4) gc.fill = Color.color(0.0, 0.0, 0.0, 0.4)
gc.fillRect(0.0, 0.0, map.width, map.height) gc.fillRect(0.0, 0.0, map.width, map.height)
} }
private fun renderUnderlyingLayers(gc: GraphicsContext) { private fun renderUnderlyingLayers(gc: GraphicsContext) {
for(layer in map.layers.dropLast(if(editorStateVM.selectedLayer < 0) 0 else map.layers.size - editorStateVM.selectedLayer)) { for(layer in map.layers.dropLast(if(editorStateVM.selectedLayer < 0) 0 else map.layers.size - editorStateVM.selectedLayer)) {
dispatchLayerRender(gc, layer) dispatchLayerRender(gc, layer)
} }
} }
private fun dispatchLayerRender(gc: GraphicsContext, layer: Layer) { private fun dispatchLayerRender(gc: GraphicsContext, layer: Layer) {
when (layer) { when (layer) {
is TileLayer -> renderTileLayer(gc, layer) is TileLayer -> renderTileLayer(gc, layer)
} }
} }
private fun renderBackground(gc: GraphicsContext) { private fun renderBackground(gc: GraphicsContext) {
for (row in 0 until map.rows) { for (row in 0 until map.rows) {
for (column in 0 until map.columns) { for (column in 0 until map.columns) {
gc.fill = if ((row + column) % 2 == 0) BACKGROUND_COLOR1 else BACKGROUND_COLOR2 gc.fill = if ((row + column) % 2 == 0) BACKGROUND_COLOR1 else BACKGROUND_COLOR2
gc.fillRect(column * tileWidth, row * tileHeight, tileWidth, tileHeight) gc.fillRect(column * tileWidth, row * tileHeight, tileWidth, tileHeight)
} }
} }
} }
private fun renderTileLayer(gc: GraphicsContext, tileLayer: TileLayer) { private fun renderTileLayer(gc: GraphicsContext, tileLayer: TileLayer) {
for ((row, columns) in tileLayer.layer.withIndex()) { for ((row, columns) in tileLayer.layer.withIndex()) {
for ((column, tile) in columns.withIndex()) { for ((column, tile) in columns.withIndex()) {
if (tile != null) { if (tile != null) {
gc.drawImage(tile.image, column * tile.image.width, row * tile.image.height) gc.drawImage(tile.image, column * tile.image.width, row * tile.image.height)
} }
} }
} }
} }
private fun renderGrid(gc: GraphicsContext) { private fun renderGrid(gc: GraphicsContext) {
if(!editorStateVM.showGrid) { if(!editorStateVM.showGrid) {
return return
} }
gc.lineWidth = 1.5 gc.lineWidth = 1.5
gc.strokeLine(0.0, 0.0, map.width, 0.0) gc.strokeLine(0.0, 0.0, map.width, 0.0)
gc.strokeLine(0.0, 0.0, 0.0, map.height) gc.strokeLine(0.0, 0.0, 0.0, map.height)
gc.strokeLine(map.width, 0.0, map.width, map.height) gc.strokeLine(map.width, 0.0, map.width, map.height)
gc.strokeLine(0.0, map.height, map.width, map.height) gc.strokeLine(0.0, map.height, map.width, map.height)
for (row in 0 until map.rows) { for (row in 0 until map.rows) {
gc.strokeLine(0.0, row * tileHeight, map.width, row * tileHeight) gc.strokeLine(0.0, row * tileHeight, map.width, row * tileHeight)
} }
for (column in 0 until map.columns) { for (column in 0 until map.columns) {
gc.strokeLine(column * tileWidth, 0.0, column * tileWidth, map.height) gc.strokeLine(column * tileWidth, 0.0, column * tileWidth, map.height)
} }
} }
companion object { companion object {
private val BACKGROUND_COLOR1 = Color.color(1.0, 1.0, 1.0, 1.0) private val BACKGROUND_COLOR1 = Color.color(1.0, 1.0, 1.0, 1.0)
private val BACKGROUND_COLOR2 = Color.color(0.8, 0.8, 0.8, 1.0) private val BACKGROUND_COLOR2 = Color.color(0.8, 0.8, 0.8, 1.0)
} }
} }

View File

@@ -1,94 +1,94 @@
package com.bartlomiejpluta.base.editor.map.canvas package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import com.bartlomiejpluta.base.editor.render.input.MapMouseEventHandler import com.bartlomiejpluta.base.editor.render.input.MapMouseEventHandler
import com.bartlomiejpluta.base.editor.render.model.Renderable import com.bartlomiejpluta.base.editor.render.model.Renderable
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.canvas.GraphicsContext import javafx.scene.canvas.GraphicsContext
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.paint.Color import javafx.scene.paint.Color
class MapPainter( class MapPainter(
private val mapVM: GameMapVM, private val mapVM: GameMapVM,
private val brushVM: BrushVM, private val brushVM: BrushVM,
private val editorStateVM: EditorStateVM, private val editorStateVM: EditorStateVM,
private val paintingCallback: (MapPaintingTrace) -> Unit private val paintingCallback: (MapPaintingTrace) -> Unit
) : Renderable, MapMouseEventHandler { ) : Renderable, MapMouseEventHandler {
private val tileWidth = mapVM.tileSet.tileWidth.toDouble() private val tileWidth = mapVM.tileSet.tileWidth.toDouble()
private val tileHeight = mapVM.tileSet.tileHeight.toDouble() private val tileHeight = mapVM.tileSet.tileHeight.toDouble()
private var currentTrace: MapPaintingTrace? = null private var currentTrace: MapPaintingTrace? = null
override fun render(gc: GraphicsContext) { override fun render(gc: GraphicsContext) {
val alpha = gc.globalAlpha val alpha = gc.globalAlpha
gc.globalAlpha = 0.4 gc.globalAlpha = 0.4
brushVM.forEach { row, column, centerRow, centerColumn, tile -> brushVM.forEach { row, column, centerRow, centerColumn, tile ->
renderTile(gc, row, column, centerRow, centerColumn, tile) renderTile(gc, row, column, centerRow, centerColumn, tile)
} }
gc.globalAlpha = alpha gc.globalAlpha = alpha
} }
private fun renderTile(gc: GraphicsContext, row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) { private fun renderTile(gc: GraphicsContext, row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) {
val x = tileWidth * (editorStateVM.cursorColumn - centerColumn + column) val x = tileWidth * (editorStateVM.cursorColumn - centerColumn + column)
val y = tileHeight * (editorStateVM.cursorRow - centerRow + row) val y = tileHeight * (editorStateVM.cursorRow - centerRow + row)
when { when {
tile != null -> renderPaintingBrushTile(gc, tile, x, y) tile != null -> renderPaintingBrushTile(gc, tile, x, y)
else -> renderEraserTile(gc, x, y) else -> renderEraserTile(gc, x, y)
} }
} }
private fun renderPaintingBrushTile(gc: GraphicsContext, tile: Tile, x: Double, y: Double) = private fun renderPaintingBrushTile(gc: GraphicsContext, tile: Tile, x: Double, y: Double) =
gc.drawImage(tile.image, x, y) gc.drawImage(tile.image, x, y)
private fun renderEraserTile(gc: GraphicsContext, x: Double, y: Double) { private fun renderEraserTile(gc: GraphicsContext, x: Double, y: Double) {
gc.fill = Color.WHITE gc.fill = Color.WHITE
gc.fillRect(x, y, tileWidth, tileHeight) gc.fillRect(x, y, tileWidth, tileHeight)
} }
override fun handleMouseInput(event: MapMouseEvent) { override fun handleMouseInput(event: MapMouseEvent) {
editorStateVM.cursorRowProperty.value = event.row editorStateVM.cursorRowProperty.value = event.row
editorStateVM.cursorColumnProperty.value = event.column editorStateVM.cursorColumnProperty.value = event.column
when (event.type) { when (event.type) {
MouseEvent.MOUSE_PRESSED -> beginTrace(event) MouseEvent.MOUSE_PRESSED -> beginTrace(event)
MouseEvent.MOUSE_DRAGGED -> proceedTrace(event) MouseEvent.MOUSE_DRAGGED -> proceedTrace(event)
MouseEvent.MOUSE_RELEASED -> commitTrace(event) MouseEvent.MOUSE_RELEASED -> commitTrace(event)
} }
} }
private fun beginTrace(event: MapMouseEvent) { private fun beginTrace(event: MapMouseEvent) {
if (event.button == MouseButton.PRIMARY && editorStateVM.selectedLayer >= 0) { if (event.button == MouseButton.PRIMARY && editorStateVM.selectedLayer >= 0) {
currentTrace = MapPaintingTrace(mapVM, "Paint trace").apply { currentTrace = MapPaintingTrace(mapVM, "Paint trace").apply {
brushVM.forEach { row, column, centerRow, centerColumn, tile -> brushVM.forEach { row, column, centerRow, centerColumn, tile ->
paint(editorStateVM.selectedLayer, editorStateVM.cursorRow - centerRow + row, editorStateVM.cursorColumn - centerColumn + column, tile) paint(editorStateVM.selectedLayer, editorStateVM.cursorRow - centerRow + row, editorStateVM.cursorColumn - centerColumn + column, tile)
} }
} }
} }
} }
private fun proceedTrace(event: MapMouseEvent) { private fun proceedTrace(event: MapMouseEvent) {
if (event.button == MouseButton.PRIMARY) { if (event.button == MouseButton.PRIMARY) {
currentTrace?.apply { currentTrace?.apply {
brushVM.forEach { row, column, centerRow, centerColumn, tile -> brushVM.forEach { row, column, centerRow, centerColumn, tile ->
paint(editorStateVM.selectedLayer, editorStateVM.cursorRow - centerRow + row, editorStateVM.cursorColumn - centerColumn + column, tile) paint(editorStateVM.selectedLayer, editorStateVM.cursorRow - centerRow + row, editorStateVM.cursorColumn - centerColumn + column, tile)
} }
} }
} }
} }
private fun commitTrace(event: MapMouseEvent) { private fun commitTrace(event: MapMouseEvent) {
if (event.button == MouseButton.PRIMARY) { if (event.button == MouseButton.PRIMARY) {
currentTrace?.let { currentTrace?.let {
paintingCallback(it) paintingCallback(it)
currentTrace = null currentTrace = null
} }
} }
} }
} }

View File

@@ -1,56 +1,56 @@
package com.bartlomiejpluta.base.editor.map.canvas package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.command.model.base.Undoable import com.bartlomiejpluta.base.editor.command.model.base.Undoable
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
data class MapPaintingTrace(val map: GameMapVM, override val commandName: String) : Undoable { data class MapPaintingTrace(val map: GameMapVM, override val commandName: String) : Undoable {
private val trace = mutableListOf<Element>() private val trace = mutableListOf<Element>()
fun paint(layerIndex: Int, row: Int, column: Int, tile: Tile?) { fun paint(layerIndex: Int, row: Int, column: Int, tile: Tile?) {
if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) { if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) {
return return
} }
val layer = (map.layers[layerIndex] as TileLayer).layer val layer = (map.layers[layerIndex] as TileLayer).layer
val formerTile = layer[row][column] val formerTile = layer[row][column]
if (trace.isEmpty()) { if (trace.isEmpty()) {
trace += Element(layerIndex, row, column, formerTile, tile) trace += Element(layerIndex, row, column, formerTile, tile)
layer[row][column] = tile layer[row][column] = tile
return return
} }
val tileAlreadyPainted = val tileAlreadyPainted =
trace.find { it.layerIndex == layerIndex && it.row == row && it.column == column } != null trace.find { it.layerIndex == layerIndex && it.row == row && it.column == column } != null
if (!tileAlreadyPainted) { if (!tileAlreadyPainted) {
trace += Element(layerIndex, row, column, formerTile, tile) trace += Element(layerIndex, row, column, formerTile, tile)
layer[row][column] = tile layer[row][column] = tile
} }
} }
override fun undo() { override fun undo() {
trace.forEach { trace.forEach {
(map.layers[it.layerIndex] as TileLayer).layer[it.row][it.column] = it.formerTile (map.layers[it.layerIndex] as TileLayer).layer[it.row][it.column] = it.formerTile
} }
} }
override fun redo() { override fun redo() {
trace.forEach { trace.forEach {
(map.layers[it.layerIndex] as TileLayer).layer[it.row][it.column] = it.tile (map.layers[it.layerIndex] as TileLayer).layer[it.row][it.column] = it.tile
} }
} }
companion object { companion object {
private data class Element( private data class Element(
val layerIndex: Int, val layerIndex: Int,
val row: Int, val row: Int,
val column: Int, val column: Int,
val formerTile: Tile?, val formerTile: Tile?,
val tile: Tile? val tile: Tile?
) )
} }
} }

View File

@@ -1,53 +1,53 @@
package com.bartlomiejpluta.base.editor.map.component package com.bartlomiejpluta.base.editor.map.component
import com.bartlomiejpluta.base.editor.map.canvas.MapCanvas import com.bartlomiejpluta.base.editor.map.canvas.MapCanvas
import com.bartlomiejpluta.base.editor.map.canvas.MapPainter import com.bartlomiejpluta.base.editor.map.canvas.MapPainter
import com.bartlomiejpluta.base.editor.map.canvas.MapPaintingTrace import com.bartlomiejpluta.base.editor.map.canvas.MapPaintingTrace
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.event.EventHandler import javafx.event.EventHandler
import javafx.scene.canvas.Canvas import javafx.scene.canvas.Canvas
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
class MapPane( class MapPane(
private val mapVM: GameMapVM, private val mapVM: GameMapVM,
brushVM: BrushVM, brushVM: BrushVM,
editorStateVM: EditorStateVM, editorStateVM: EditorStateVM,
paintingCallback: (MapPaintingTrace) -> Unit paintingCallback: (MapPaintingTrace) -> Unit
) : Canvas(), EventHandler<MouseEvent> { ) : Canvas(), EventHandler<MouseEvent> {
private val painter = MapPainter(mapVM, brushVM, editorStateVM, paintingCallback) private val painter = MapPainter(mapVM, brushVM, editorStateVM, paintingCallback)
private val mapCanvas = MapCanvas(mapVM, editorStateVM, painter) private val mapCanvas = MapCanvas(mapVM, editorStateVM, painter)
init { init {
onMouseMoved = this onMouseMoved = this
onMouseDragged = this onMouseDragged = this
onMousePressed = this onMousePressed = this
onMouseReleased = this onMouseReleased = this
widthProperty().bind(mapVM.widthProperty) widthProperty().bind(mapVM.widthProperty)
heightProperty().bind(mapVM.heightProperty) heightProperty().bind(mapVM.heightProperty)
mapVM.item.rowsProperty.addListener { _, _, _ -> render() } mapVM.item.rowsProperty.addListener { _, _, _ -> render() }
mapVM.item.columnsProperty.addListener { _, _, _ -> render() } mapVM.item.columnsProperty.addListener { _, _, _ -> render() }
editorStateVM.showGridProperty.addListener { _, _, _ -> render() } editorStateVM.showGridProperty.addListener { _, _, _ -> render() }
editorStateVM.selectedLayerProperty.addListener { _, _, _ -> render() } editorStateVM.selectedLayerProperty.addListener { _, _, _ -> render() }
editorStateVM.coverUnderlyingLayersProperty.addListener { _, _, _ -> render() } editorStateVM.coverUnderlyingLayersProperty.addListener { _, _, _ -> render() }
render() render()
} }
fun render() { fun render() {
mapCanvas.render(graphicsContext2D) mapCanvas.render(graphicsContext2D)
} }
override fun handle(event: MouseEvent?) { override fun handle(event: MouseEvent?) {
if (event != null) { if (event != null) {
painter.handleMouseInput(MapMouseEvent.of(event, mapVM.tileSet)) painter.handleMouseInput(MapMouseEvent.of(event, mapVM.tileSet))
} }
mapCanvas.render(graphicsContext2D) mapCanvas.render(graphicsContext2D)
} }
} }

View File

@@ -1,97 +1,97 @@
package com.bartlomiejpluta.base.editor.map.model.brush package com.bartlomiejpluta.base.editor.map.model.brush
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.collections.ObservableList import javafx.collections.ObservableList
import tornadofx.asObservable import tornadofx.asObservable
import tornadofx.getValue import tornadofx.getValue
import tornadofx.observableListOf import tornadofx.observableListOf
import tornadofx.setValue import tornadofx.setValue
class Brush { class Brush {
val brush: ObservableList<Tile> val brush: ObservableList<Tile>
val rowsProperty = SimpleIntegerProperty(0) val rowsProperty = SimpleIntegerProperty(0)
var rows by rowsProperty var rows by rowsProperty
private set private set
val columnsProperty = SimpleIntegerProperty(0) val columnsProperty = SimpleIntegerProperty(0)
var columns by columnsProperty var columns by columnsProperty
private set private set
val rangeProperty = SimpleIntegerProperty(1) val rangeProperty = SimpleIntegerProperty(1)
var range by rangeProperty var range by rangeProperty
private set private set
val modeProperty = SimpleObjectProperty(BrushMode.PAINTING_MODE) val modeProperty = SimpleObjectProperty(BrushMode.PAINTING_MODE)
var mode by modeProperty var mode by modeProperty
private set private set
private constructor(brushArray: Array<Array<Tile>>) { private constructor(brushArray: Array<Array<Tile>>) {
rowsProperty.value = brushArray.size rowsProperty.value = brushArray.size
brush = observableListOf() brush = observableListOf()
brushArray.forEach { brush.addAll(it) } brushArray.forEach { brush.addAll(it) }
if (rowsProperty.value > 0) { if (rowsProperty.value > 0) {
columns = brush.size / rowsProperty.value columns = brush.size / rowsProperty.value
} }
} }
private constructor(brush: List<Tile>, rows: Int, columns: Int) { private constructor(brush: List<Tile>, rows: Int, columns: Int) {
this.rows = rows this.rows = rows
this.columns = columns this.columns = columns
this.brush = brush.asObservable() this.brush = brush.asObservable()
} }
fun forEach(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) { fun forEach(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) {
return when { return when {
range > 1 || mode == BrushMode.ERASING_MODE -> forEachInRangedBrush(consumer) range > 1 || mode == BrushMode.ERASING_MODE -> forEachInRangedBrush(consumer)
else -> forEachInRegularBrush(consumer) else -> forEachInRegularBrush(consumer)
} }
} }
private fun forEachInRangedBrush(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) { private fun forEachInRangedBrush(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) {
val center = range / 2 val center = range / 2
(0 until range).forEach { row -> (0 until range).forEach { row ->
(0 until range).forEach { column -> (0 until range).forEach { column ->
consumer(row, column, center, center, getTileByMode(brush[0])) consumer(row, column, center, center, getTileByMode(brush[0]))
} }
} }
} }
private fun getTileByMode(tile: Tile) = when (mode) { private fun getTileByMode(tile: Tile) = when (mode) {
BrushMode.PAINTING_MODE -> tile BrushMode.PAINTING_MODE -> tile
BrushMode.ERASING_MODE -> null BrushMode.ERASING_MODE -> null
} }
private fun forEachInRegularBrush(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) { private fun forEachInRegularBrush(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) {
val centerRow = rows / 2 val centerRow = rows / 2
val centerColumn = columns / 2 val centerColumn = columns / 2
brush.forEachIndexed { id, tile -> brush.forEachIndexed { id, tile ->
consumer(id / columns, id % columns, centerRow, centerColumn, getTileByMode(tile)) consumer(id / columns, id % columns, centerRow, centerColumn, getTileByMode(tile))
} }
} }
private fun clone() = Brush(brush, rows, columns).apply { private fun clone() = Brush(brush, rows, columns).apply {
this.range = this@Brush.range this.range = this@Brush.range
this.mode = this@Brush.mode this.mode = this@Brush.mode
} }
fun withRange(range: Int) = clone().apply { fun withRange(range: Int) = clone().apply {
this.range = range this.range = range
} }
fun withMode(mode: BrushMode) = clone().apply { fun withMode(mode: BrushMode) = clone().apply {
this.mode = mode this.mode = mode
} }
companion object { companion object {
fun of(brushArray: Array<Array<Tile>>) = Brush(brushArray) fun of(brushArray: Array<Array<Tile>>) = Brush(brushArray)
} }
} }

View File

@@ -1,6 +1,6 @@
package com.bartlomiejpluta.base.editor.map.model.brush package com.bartlomiejpluta.base.editor.map.model.brush
enum class BrushMode { enum class BrushMode {
PAINTING_MODE, PAINTING_MODE,
ERASING_MODE ERASING_MODE
} }

View File

@@ -1,12 +1,12 @@
package com.bartlomiejpluta.base.editor.map.model.layer package com.bartlomiejpluta.base.editor.map.model.layer
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
interface Layer { interface Layer {
var name: String var name: String
val nameProperty: StringProperty val nameProperty: StringProperty
fun resize(rows: Int, columns: Int) fun resize(rows: Int, columns: Int)
fun clone(): Layer fun clone(): Layer
} }

View File

@@ -1,31 +1,31 @@
package com.bartlomiejpluta.base.editor.map.model.layer package com.bartlomiejpluta.base.editor.map.model.layer
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import tornadofx.getValue import tornadofx.getValue
import tornadofx.setValue import tornadofx.setValue
class TileLayer( class TileLayer(
name: String, name: String,
rows: Int, rows: Int,
columns: Int, columns: Int,
layer: Array<Array<Tile?>> = Array(rows) { Array(columns) { null } } layer: Array<Array<Tile?>> = Array(rows) { Array(columns) { null } }
) : Layer { ) : Layer {
var layer = layer var layer = layer
private set private set
override val nameProperty = SimpleStringProperty(name) override val nameProperty = SimpleStringProperty(name)
override var name: String by nameProperty override var name: String by nameProperty
override fun resize(rows: Int, columns: Int) { override fun resize(rows: Int, columns: Int) {
layer = Array(rows) { row -> layer = Array(rows) { row ->
Array(columns) { column -> Array(columns) { column ->
layer.getOrNull(row)?.getOrNull(column) layer.getOrNull(row)?.getOrNull(column)
} }
} }
} }
override fun clone() = TileLayer(name, 0, 0).apply { override fun clone() = TileLayer(name, 0, 0).apply {
layer = this@TileLayer.layer layer = this@TileLayer.layer
} }
} }

View File

@@ -1,54 +1,54 @@
package com.bartlomiejpluta.base.editor.map.model.map package com.bartlomiejpluta.base.editor.map.model.map
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.tileset.model.TileSet import com.bartlomiejpluta.base.editor.tileset.model.TileSet
import javafx.beans.property.SimpleDoubleProperty import javafx.beans.property.SimpleDoubleProperty
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import tornadofx.getValue import tornadofx.getValue
import tornadofx.observableListOf import tornadofx.observableListOf
import tornadofx.setValue import tornadofx.setValue
class GameMap(val tileSet: TileSet) { class GameMap(val tileSet: TileSet) {
val layers = observableListOf<Layer>() val layers = observableListOf<Layer>()
val nameProperty = SimpleStringProperty() val nameProperty = SimpleStringProperty()
var name by nameProperty var name by nameProperty
val tileWidth = tileSet.tileWidth.toDouble() val tileWidth = tileSet.tileWidth.toDouble()
val tileHeight = tileSet.tileHeight.toDouble() val tileHeight = tileSet.tileHeight.toDouble()
val rowsProperty = SimpleIntegerProperty(INITIAL_ROWS) val rowsProperty = SimpleIntegerProperty(INITIAL_ROWS)
var rows by rowsProperty var rows by rowsProperty
val columnsProperty = SimpleIntegerProperty(INITIAL_COLUMNS) val columnsProperty = SimpleIntegerProperty(INITIAL_COLUMNS)
var columns by columnsProperty var columns by columnsProperty
val widthProperty = SimpleDoubleProperty(INITIAL_COLUMNS * tileWidth) val widthProperty = SimpleDoubleProperty(INITIAL_COLUMNS * tileWidth)
var width by widthProperty var width by widthProperty
private set private set
val heightProperty = SimpleDoubleProperty(INITIAL_ROWS * tileHeight) val heightProperty = SimpleDoubleProperty(INITIAL_ROWS * tileHeight)
var height by heightProperty var height by heightProperty
private set private set
init { init {
rowsProperty.addListener { _, _, newValue -> rowsProperty.addListener { _, _, newValue ->
val newRows = newValue.toInt() val newRows = newValue.toInt()
height = newRows * tileWidth height = newRows * tileWidth
layers.forEach { it.resize(newRows, columns) } layers.forEach { it.resize(newRows, columns) }
} }
columnsProperty.addListener { _, _, newValue -> columnsProperty.addListener { _, _, newValue ->
val newColumns = newValue.toInt() val newColumns = newValue.toInt()
width = newColumns * tileWidth width = newColumns * tileWidth
layers.forEach { it.resize(rows, newColumns) } layers.forEach { it.resize(rows, newColumns) }
} }
} }
companion object { companion object {
private const val INITIAL_ROWS = 20 private const val INITIAL_ROWS = 20
private const val INITIAL_COLUMNS = 20 private const val INITIAL_COLUMNS = 20
} }
} }

View File

@@ -1,6 +1,6 @@
package com.bartlomiejpluta.base.editor.map.serial package com.bartlomiejpluta.base.editor.map.serial
import com.bartlomiejpluta.base.editor.common.serial.Deserializer import com.bartlomiejpluta.base.editor.common.serial.Deserializer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
interface MapDeserializer : Deserializer<GameMap> interface MapDeserializer : Deserializer<GameMap>

View File

@@ -1,6 +1,6 @@
package com.bartlomiejpluta.base.editor.map.serial package com.bartlomiejpluta.base.editor.map.serial
import com.bartlomiejpluta.base.editor.common.serial.Serializer import com.bartlomiejpluta.base.editor.common.serial.Serializer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
interface MapSerializer : Serializer<GameMap> interface MapSerializer : Serializer<GameMap>

View File

@@ -1,52 +1,52 @@
package com.bartlomiejpluta.base.editor.map.serial package com.bartlomiejpluta.base.editor.map.serial
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
import com.bartlomiejpluta.base.editor.tileset.model.TileSet import com.bartlomiejpluta.base.editor.tileset.model.TileSet
import com.bartlomiejpluta.base.proto.GameMapProto import com.bartlomiejpluta.base.proto.GameMapProto
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import tornadofx.ResourceLookup import tornadofx.ResourceLookup
import java.io.InputStream import java.io.InputStream
@Component @Component
class ProtobufMapDeserializer : MapDeserializer { class ProtobufMapDeserializer : MapDeserializer {
private val resources = ResourceLookup(this) private val resources = ResourceLookup(this)
private val tileset = TileSet(resources.image("/textures/tileset.png"), 160, 8) private val tileset = TileSet(resources.image("/textures/tileset.png"), 160, 8)
override fun deserialize(input: InputStream): GameMap { override fun deserialize(input: InputStream): GameMap {
val map = GameMap(tileset) val map = GameMap(tileset)
val proto = GameMapProto.GameMap.parseFrom(input) val proto = GameMapProto.GameMap.parseFrom(input)
map.name = proto.name map.name = proto.name
map.rows = proto.rows map.rows = proto.rows
map.columns = proto.columns map.columns = proto.columns
proto.layersList.forEach { proto.layersList.forEach {
map.layers.add(deserializeLayer(map.rows, map.columns, tileset, it)) map.layers.add(deserializeLayer(map.rows, map.columns, tileset, it))
} }
return map return map
} }
private fun deserializeLayer(rows: Int, columns: Int, tileSet: TileSet, proto: GameMapProto.Layer): Layer { private fun deserializeLayer(rows: Int, columns: Int, tileSet: TileSet, proto: GameMapProto.Layer): Layer {
return when { return when {
proto.hasTileLayer() -> deserializeTileLayer(rows, columns, tileSet, proto) proto.hasTileLayer() -> deserializeTileLayer(rows, columns, tileSet, proto)
else -> throw IllegalStateException("Not supported layer type") else -> throw IllegalStateException("Not supported layer type")
} }
} }
private fun deserializeTileLayer(rows: Int, columns: Int, tileSet: TileSet, proto: GameMapProto.Layer): Layer { private fun deserializeTileLayer(rows: Int, columns: Int, tileSet: TileSet, proto: GameMapProto.Layer): Layer {
val layer: Array<Array<Tile?>> = Array(rows) { Array(columns) { null } } val layer: Array<Array<Tile?>> = Array(rows) { Array(columns) { null } }
proto.tileLayer.tilesList.forEachIndexed { index, tile -> proto.tileLayer.tilesList.forEachIndexed { index, tile ->
layer[index / columns][index % columns] = when(tile) { layer[index / columns][index % columns] = when(tile) {
0 -> null 0 -> null
else -> tileSet.getTile(tile-1) else -> tileSet.getTile(tile-1)
} }
} }
return TileLayer(proto.name, rows, columns, layer) return TileLayer(proto.name, rows, columns, layer)
} }
} }

View File

@@ -1,34 +1,34 @@
package com.bartlomiejpluta.base.editor.map.serial package com.bartlomiejpluta.base.editor.map.serial
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import com.bartlomiejpluta.base.proto.GameMapProto import com.bartlomiejpluta.base.proto.GameMapProto
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.OutputStream import java.io.OutputStream
@Component @Component
class ProtobufMapSerializer : MapSerializer { class ProtobufMapSerializer : MapSerializer {
override fun serialize(item: GameMap, output: OutputStream) { override fun serialize(item: GameMap, output: OutputStream) {
val protoMap = GameMapProto.GameMap.newBuilder() val protoMap = GameMapProto.GameMap.newBuilder()
protoMap.name = item.name protoMap.name = item.name
protoMap.rows = item.rows protoMap.rows = item.rows
protoMap.columns = item.columns protoMap.columns = item.columns
item.layers.forEach { layer -> protoMap.addLayers(serializeLayer(layer)) } item.layers.forEach { layer -> protoMap.addLayers(serializeLayer(layer)) }
protoMap.build().writeTo(output) protoMap.build().writeTo(output)
} }
private fun serializeLayer(layer: Layer): GameMapProto.Layer { private fun serializeLayer(layer: Layer): GameMapProto.Layer {
return when (layer) { return when (layer) {
is TileLayer -> layer.layer.flatMap { it.asIterable() } is TileLayer -> layer.layer.flatMap { it.asIterable() }
.fold(GameMapProto.TileLayer.newBuilder()) { acc, tile -> acc.addTiles((tile?.id?.plus(1)) ?: 0) } .fold(GameMapProto.TileLayer.newBuilder()) { acc, tile -> acc.addTiles((tile?.id?.plus(1)) ?: 0) }
.build() .build()
.let { GameMapProto.Layer.newBuilder().setName(layer.name).setTileLayer(it).build() } .let { GameMapProto.Layer.newBuilder().setName(layer.name).setTileLayer(it).build() }
else -> throw IllegalStateException("Not supported layer type") else -> throw IllegalStateException("Not supported layer type")
} }
} }
} }

View File

@@ -1,36 +1,36 @@
package com.bartlomiejpluta.base.editor.map.view package com.bartlomiejpluta.base.editor.map.view
import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.context.UndoableScope
import com.bartlomiejpluta.base.editor.tileset.view.TileSetView import com.bartlomiejpluta.base.editor.tileset.view.TileSetView
import tornadofx.* import tornadofx.*
class MapFragment : Fragment() { class MapFragment : Fragment() {
override val scope = super.scope as UndoableScope override val scope = super.scope as UndoableScope
private val mapView = find<MapView>() private val mapView = find<MapView>()
private val layersView = find<MapLayersView>() private val layersView = find<MapLayersView>()
private val tileSetView = find<TileSetView>() private val tileSetView = find<TileSetView>()
private val toolbarView = find<MapToolbarView>() private val toolbarView = find<MapToolbarView>()
private val statusBarView = find<MapStatusBarView>() private val statusBarView = find<MapStatusBarView>()
override val root = borderpane { override val root = borderpane {
top = toolbarView.root top = toolbarView.root
center = mapView.root center = mapView.root
right = drawer(multiselect = true) { right = drawer(multiselect = true) {
item("Layers", expanded = true) { item("Layers", expanded = true) {
this += layersView.root this += layersView.root
} }
item("Tile Set", expanded = true) { item("Tile Set", expanded = true) {
this += tileSetView.root.apply { this += tileSetView.root.apply {
maxHeightProperty().bind(this@item.heightProperty()) maxHeightProperty().bind(this@item.heightProperty())
} }
} }
} }
bottom = statusBarView.root bottom = statusBarView.root
} }
} }

View File

@@ -1,95 +1,95 @@
package com.bartlomiejpluta.base.editor.map.view package com.bartlomiejpluta.base.editor.map.view
import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.context.UndoableScope
import com.bartlomiejpluta.base.editor.command.model.map.CreateLayerCommand import com.bartlomiejpluta.base.editor.command.model.map.CreateLayerCommand
import com.bartlomiejpluta.base.editor.command.model.map.MoveLayerCommand import com.bartlomiejpluta.base.editor.command.model.map.MoveLayerCommand
import com.bartlomiejpluta.base.editor.command.model.map.RemoveLayerCommand import com.bartlomiejpluta.base.editor.command.model.map.RemoveLayerCommand
import com.bartlomiejpluta.base.editor.command.model.map.RenameLayerCommand import com.bartlomiejpluta.base.editor.command.model.map.RenameLayerCommand
import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.command.service.UndoRedoService
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.control.TableView import javafx.scene.control.TableView
import org.kordamp.ikonli.javafx.FontIcon import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.* import tornadofx.*
class MapLayersView : View() { class MapLayersView : View() {
private val undoRedoService: UndoRedoService by di() private val undoRedoService: UndoRedoService by di()
override val scope = super.scope as UndoableScope override val scope = super.scope as UndoableScope
private val mapVM = find<GameMapVM>() private val mapVM = find<GameMapVM>()
private val editorStateVM = find<EditorStateVM>() private val editorStateVM = find<EditorStateVM>()
private var layersPane = TableView(mapVM.layers).apply { private var layersPane = TableView(mapVM.layers).apply {
column("Layer Name", Layer::nameProperty).makeEditable().setOnEditCommit { column("Layer Name", Layer::nameProperty).makeEditable().setOnEditCommit {
val command = RenameLayerCommand(it.rowValue, it.newValue) val command = RenameLayerCommand(it.rowValue, it.newValue)
command.execute() command.execute()
undoRedoService.push(command, scope) undoRedoService.push(command, scope)
} }
editorStateVM.selectedLayerProperty.bind(selectionModel.selectedIndexProperty()) editorStateVM.selectedLayerProperty.bind(selectionModel.selectedIndexProperty())
} }
override val root = borderpane { override val root = borderpane {
center = layersPane center = layersPane
bottom = toolbar { bottom = toolbar {
button(graphic = FontIcon("fa-plus")) { button(graphic = FontIcon("fa-plus")) {
action { action {
val tileLayer = TileLayer("Layer ${mapVM.layers.size + 1}", mapVM.rows, mapVM.columns) val tileLayer = TileLayer("Layer ${mapVM.layers.size + 1}", mapVM.rows, mapVM.columns)
val command = CreateLayerCommand(mapVM.item, tileLayer) val command = CreateLayerCommand(mapVM.item, tileLayer)
command.execute() command.execute()
layersPane.selectionModel.select(mapVM.layers.size - 1) layersPane.selectionModel.select(mapVM.layers.size - 1)
undoRedoService.push(command, scope) undoRedoService.push(command, scope)
} }
} }
button(graphic = FontIcon("fa-chevron-up")) { button(graphic = FontIcon("fa-chevron-up")) {
enableWhen(editorStateVM.selectedLayerProperty.greaterThan(0)) enableWhen(editorStateVM.selectedLayerProperty.greaterThan(0))
action { action {
val newIndex = editorStateVM.selectedLayer - 1 val newIndex = editorStateVM.selectedLayer - 1
val command = MoveLayerCommand(mapVM.item, editorStateVM.selectedLayer, newIndex) val command = MoveLayerCommand(mapVM.item, editorStateVM.selectedLayer, newIndex)
command.execute() command.execute()
layersPane.selectionModel.select(newIndex) layersPane.selectionModel.select(newIndex)
fire(RedrawMapRequestEvent) fire(RedrawMapRequestEvent)
undoRedoService.push(command, scope) undoRedoService.push(command, scope)
} }
} }
button(graphic = FontIcon("fa-chevron-down")) { button(graphic = FontIcon("fa-chevron-down")) {
enableWhen( enableWhen(
editorStateVM.selectedLayerProperty.lessThan(mapVM.layers.sizeProperty().minus(1)) editorStateVM.selectedLayerProperty.lessThan(mapVM.layers.sizeProperty().minus(1))
.and(editorStateVM.selectedLayerProperty.greaterThanOrEqualTo(0)) .and(editorStateVM.selectedLayerProperty.greaterThanOrEqualTo(0))
) )
action { action {
val newIndex = editorStateVM.selectedLayer + 1 val newIndex = editorStateVM.selectedLayer + 1
val command = MoveLayerCommand(mapVM.item, editorStateVM.selectedLayer, newIndex) val command = MoveLayerCommand(mapVM.item, editorStateVM.selectedLayer, newIndex)
command.execute() command.execute()
layersPane.selectionModel.select(newIndex) layersPane.selectionModel.select(newIndex)
fire(RedrawMapRequestEvent) fire(RedrawMapRequestEvent)
undoRedoService.push(command, scope) undoRedoService.push(command, scope)
} }
} }
button(graphic = FontIcon("fa-trash")) { button(graphic = FontIcon("fa-trash")) {
enableWhen(editorStateVM.selectedLayerProperty.greaterThanOrEqualTo(0)) enableWhen(editorStateVM.selectedLayerProperty.greaterThanOrEqualTo(0))
action { action {
var index = editorStateVM.selectedLayer var index = editorStateVM.selectedLayer
val command = RemoveLayerCommand(mapVM.item, index) val command = RemoveLayerCommand(mapVM.item, index)
command.execute() command.execute()
if (--index >= 0) { if (--index >= 0) {
layersPane.selectionModel.select(index) layersPane.selectionModel.select(index)
} }
fire(RedrawMapRequestEvent) fire(RedrawMapRequestEvent)
undoRedoService.push(command, scope) undoRedoService.push(command, scope)
} }
} }
} }
} }
} }

View File

@@ -1,83 +1,83 @@
package com.bartlomiejpluta.base.editor.map.view package com.bartlomiejpluta.base.editor.map.view
import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.context.UndoableScope
import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.command.service.UndoRedoService
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import org.kordamp.ikonli.javafx.FontIcon import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.* import tornadofx.*
class MapSettingsFragment : Fragment("Map Settings") { class MapSettingsFragment : Fragment("Map Settings") {
override val scope = super.scope as UndoableScope override val scope = super.scope as UndoableScope
private val undoRedoService: UndoRedoService by di() private val undoRedoService: UndoRedoService by di()
private val mapVM = find<GameMapVM>() private val mapVM = find<GameMapVM>()
var result = false var result = false
private set private set
override val root = form { override val root = form {
icon = FontIcon("fa-map-o") icon = FontIcon("fa-map-o")
fieldset("Map Settings") { fieldset("Map Settings") {
field("Map name") { field("Map name") {
textfield(mapVM.nameProperty) { textfield(mapVM.nameProperty) {
required() required()
whenDocked { requestFocus() } whenDocked { requestFocus() }
} }
} }
field("Rows") { field("Rows") {
textfield(mapVM.rowsProperty) { textfield(mapVM.rowsProperty) {
stripNonInteger() stripNonInteger()
required() required()
validator { validator {
when (it?.toIntOrNull()) { when (it?.toIntOrNull()) {
in 1..50 -> null in 1..50 -> null
in 50..100 -> warning("The map sizes over 50 can impact game performance") in 50..100 -> warning("The map sizes over 50 can impact game performance")
else -> error("The map size must be between 1 and 100") else -> error("The map size must be between 1 and 100")
} }
} }
} }
} }
field("Columns") { field("Columns") {
textfield(mapVM.columnsProperty) { textfield(mapVM.columnsProperty) {
stripNonInteger() stripNonInteger()
required() required()
validator { validator {
when (it?.toIntOrNull()) { when (it?.toIntOrNull()) {
in 1..50 -> null in 1..50 -> null
in 50..100 -> warning("The map sizes over 50 can impact game performance") in 50..100 -> warning("The map sizes over 50 can impact game performance")
else -> error("The map size must be between 1 and 100") else -> error("The map size must be between 1 and 100")
} }
} }
} }
} }
label("Warning: Submitting the form will clear related undo/redo stacks!") label("Warning: Submitting the form will clear related undo/redo stacks!")
} }
buttonbar { buttonbar {
button("Ok") { button("Ok") {
shortcut("Enter") shortcut("Enter")
action { action {
mapVM.commit { mapVM.commit {
result = true result = true
undoRedoService.clear(scope) undoRedoService.clear(scope)
close() close()
} }
} }
} }
button("Reset") { button("Reset") {
action { mapVM.rollback() } action { mapVM.rollback() }
} }
button("Cancel") { button("Cancel") {
action { close() } action { close() }
} }
} }
} }
} }

View File

@@ -1,34 +1,34 @@
package com.bartlomiejpluta.base.editor.map.view package com.bartlomiejpluta.base.editor.map.view
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import org.kordamp.ikonli.javafx.FontIcon import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.* import tornadofx.*
class MapStatusBarView : View() { class MapStatusBarView : View() {
private val editorStateVM = find<EditorStateVM>() private val editorStateVM = find<EditorStateVM>()
override val root = hbox { override val root = hbox {
spacing = 50.0 spacing = 50.0
paddingAll = 5.0 paddingAll = 5.0
hbox { hbox {
this += FontIcon("fa-search-minus") this += FontIcon("fa-search-minus")
slider(0.5..5.0) { slider(0.5..5.0) {
bind(editorStateVM.zoomProperty) bind(editorStateVM.zoomProperty)
} }
this += FontIcon("fa-search-plus") this += FontIcon("fa-search-plus")
} }
label( label(
Bindings.format( Bindings.format(
"Cursor: %d, %d", "Cursor: %d, %d",
editorStateVM.cursorColumnProperty.add(1), editorStateVM.cursorColumnProperty.add(1),
editorStateVM.cursorRowProperty.add(1) editorStateVM.cursorRowProperty.add(1)
) )
) )
} }
} }

View File

@@ -1,105 +1,105 @@
package com.bartlomiejpluta.base.editor.map.view package com.bartlomiejpluta.base.editor.map.view
import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.context.UndoableScope
import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.command.service.UndoRedoService
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.control.ToggleGroup import javafx.scene.control.ToggleGroup
import org.kordamp.ikonli.javafx.FontIcon import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.* import tornadofx.*
class MapToolbarView : View() { class MapToolbarView : View() {
private val undoRedoService: UndoRedoService by di() private val undoRedoService: UndoRedoService by di()
override val scope = super.scope as UndoableScope override val scope = super.scope as UndoableScope
private val mapVM = find<GameMapVM>() private val mapVM = find<GameMapVM>()
private val brushVM = find<BrushVM>() private val brushVM = find<BrushVM>()
private val editorStateVM = find<EditorStateVM>() private val editorStateVM = find<EditorStateVM>()
private val brushMode = ToggleGroup().apply { private val brushMode = ToggleGroup().apply {
brushVM.itemProperty.addListener { _, _, brush -> brushVM.itemProperty.addListener { _, _, brush ->
selectedValueProperty<BrushMode>().value = brush.mode selectedValueProperty<BrushMode>().value = brush.mode
} }
} }
override val root = toolbar { override val root = toolbar {
button(graphic = FontIcon("fa-undo")) { button(graphic = FontIcon("fa-undo")) {
shortcut("Ctrl+Z") shortcut("Ctrl+Z")
action { action {
undoRedoService.undo(scope) undoRedoService.undo(scope)
fire(RedrawMapRequestEvent) fire(RedrawMapRequestEvent)
} }
} }
button(graphic = FontIcon("fa-repeat")) { button(graphic = FontIcon("fa-repeat")) {
shortcut("Ctrl+Shift+Z") shortcut("Ctrl+Shift+Z")
action { action {
undoRedoService.redo(scope) undoRedoService.redo(scope)
fire(RedrawMapRequestEvent) fire(RedrawMapRequestEvent)
} }
} }
togglebutton { togglebutton {
graphic = FontIcon("fa-window-restore") graphic = FontIcon("fa-window-restore")
action { action {
editorStateVM.coverUnderlyingLayers = isSelected editorStateVM.coverUnderlyingLayers = isSelected
} }
} }
togglebutton { togglebutton {
graphic = FontIcon("fa-th") graphic = FontIcon("fa-th")
action { action {
editorStateVM.showGrid = isSelected editorStateVM.showGrid = isSelected
} }
} }
togglebutton(value = BrushMode.PAINTING_MODE, group = brushMode) { togglebutton(value = BrushMode.PAINTING_MODE, group = brushMode) {
graphic = FontIcon("fa-paint-brush") graphic = FontIcon("fa-paint-brush")
action { action {
brushVM.item = brushVM.withMode(BrushMode.PAINTING_MODE) brushVM.item = brushVM.withMode(BrushMode.PAINTING_MODE)
brushVM.commit() brushVM.commit()
} }
} }
togglebutton(value = BrushMode.ERASING_MODE, group = brushMode) { togglebutton(value = BrushMode.ERASING_MODE, group = brushMode) {
graphic = FontIcon("fa-eraser") graphic = FontIcon("fa-eraser")
action { action {
brushVM.item = brushVM.withMode(BrushMode.ERASING_MODE) brushVM.item = brushVM.withMode(BrushMode.ERASING_MODE)
brushVM.commit() brushVM.commit()
} }
} }
this += FontIcon("fa-paint-brush").apply { iconSize = 10 } this += FontIcon("fa-paint-brush").apply { iconSize = 10 }
slider(1..5) { slider(1..5) {
majorTickUnit = 1.0 majorTickUnit = 1.0
isSnapToTicks = true isSnapToTicks = true
minorTickCount = 0 minorTickCount = 0
valueProperty().addListener { _, _, newValue -> valueProperty().addListener { _, _, newValue ->
brushVM.item = brushVM.withRange(newValue.toInt()) brushVM.item = brushVM.withRange(newValue.toInt())
brushVM.commit() brushVM.commit()
} }
brushVM.itemProperty.addListener { _, _, brush -> brushVM.itemProperty.addListener { _, _, brush ->
value = brush.range.toDouble() value = brush.range.toDouble()
} }
} }
this += FontIcon("fa-paint-brush").apply { iconSize = 15 } this += FontIcon("fa-paint-brush").apply { iconSize = 15 }
button(graphic = FontIcon("fa-sliders")) { button(graphic = FontIcon("fa-sliders")) {
action { action {
find<MapSettingsFragment>().openModal(block = true, resizable = false) find<MapSettingsFragment>().openModal(block = true, resizable = false)
} }
} }
} }
} }

View File

@@ -1,64 +1,64 @@
package com.bartlomiejpluta.base.editor.map.view package com.bartlomiejpluta.base.editor.map.view
import com.bartlomiejpluta.base.editor.command.context.UndoableScope import com.bartlomiejpluta.base.editor.command.context.UndoableScope
import com.bartlomiejpluta.base.editor.command.service.UndoRedoService import com.bartlomiejpluta.base.editor.command.service.UndoRedoService
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
import com.bartlomiejpluta.base.editor.map.component.MapPane import com.bartlomiejpluta.base.editor.map.component.MapPane
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.transform.Scale import javafx.scene.transform.Scale
import tornadofx.View import tornadofx.View
import tornadofx.group import tornadofx.group
import tornadofx.plusAssign import tornadofx.plusAssign
import tornadofx.scrollpane import tornadofx.scrollpane
class MapView : View() { class MapView : View() {
private val undoRedoService: UndoRedoService by di() private val undoRedoService: UndoRedoService by di()
override val scope = super.scope as UndoableScope override val scope = super.scope as UndoableScope
private val mapVM = find<GameMapVM>() private val mapVM = find<GameMapVM>()
private val brushVM = find<BrushVM>() private val brushVM = find<BrushVM>()
private val editorStateVM = find<EditorStateVM>() private val editorStateVM = find<EditorStateVM>()
private val mapPane = MapPane(mapVM, brushVM, editorStateVM) { undoRedoService.push(it, scope) } private val mapPane = MapPane(mapVM, brushVM, editorStateVM) { undoRedoService.push(it, scope) }
private val zoom = Scale(1.0, 1.0, 0.0, 0.0).apply { private val zoom = Scale(1.0, 1.0, 0.0, 0.0).apply {
xProperty().bind(editorStateVM.zoomProperty) xProperty().bind(editorStateVM.zoomProperty)
yProperty().bind(editorStateVM.zoomProperty) yProperty().bind(editorStateVM.zoomProperty)
} }
init { init {
brushVM.item = mapVM.tileSet.baseBrush brushVM.item = mapVM.tileSet.baseBrush
brushVM.commit() brushVM.commit()
subscribe<RedrawMapRequestEvent> { mapPane.render() } subscribe<RedrawMapRequestEvent> { mapPane.render() }
} }
override val root = scrollpane { override val root = scrollpane {
prefWidth = 640.0 prefWidth = 640.0
prefHeight = 480.0 prefHeight = 480.0
isPannable = true isPannable = true
group { group {
// Let the ScrollPane.viewRect only pan on middle button. // Let the ScrollPane.viewRect only pan on middle button.
addEventHandler(MouseEvent.ANY) { addEventHandler(MouseEvent.ANY) {
if (it.button != MouseButton.MIDDLE) { if (it.button != MouseButton.MIDDLE) {
it.consume() it.consume()
} }
} }
group { group {
this += mapPane this += mapPane
transforms += zoom transforms += zoom
} }
} }
} }
} }

View File

@@ -1,29 +1,29 @@
package com.bartlomiejpluta.base.editor.map.viewmodel package com.bartlomiejpluta.base.editor.map.viewmodel
import com.bartlomiejpluta.base.editor.map.model.brush.Brush import com.bartlomiejpluta.base.editor.map.model.brush.Brush
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.tileset.model.Tile import com.bartlomiejpluta.base.editor.tileset.model.Tile
import tornadofx.ItemViewModel import tornadofx.ItemViewModel
import tornadofx.getValue import tornadofx.getValue
class BrushVM : ItemViewModel<Brush>(Brush.of(arrayOf(arrayOf()))) { class BrushVM : ItemViewModel<Brush>(Brush.of(arrayOf(arrayOf()))) {
val brush = bind(Brush::brush) val brush = bind(Brush::brush)
val rowsProperty = bind(Brush::rowsProperty) val rowsProperty = bind(Brush::rowsProperty)
val rows by rowsProperty val rows by rowsProperty
val columnsProperty = bind(Brush::columnsProperty) val columnsProperty = bind(Brush::columnsProperty)
val columns by columnsProperty val columns by columnsProperty
val rangeProperty = bind(Brush::rangeProperty) val rangeProperty = bind(Brush::rangeProperty)
val range by rangeProperty val range by rangeProperty
val modeProperty = bind(Brush::modeProperty) val modeProperty = bind(Brush::modeProperty)
val mode by modeProperty val mode by modeProperty
fun forEach(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) = item.forEach(consumer) fun forEach(consumer: (row: Int, column: Int, centerRow: Int, centerColumn: Int, tile: Tile?) -> Unit) = item.forEach(consumer)
fun withRange(range: Int) = item.withRange(range) fun withRange(range: Int) = item.withRange(range)
fun withMode(mode: BrushMode) = item.withMode(mode) fun withMode(mode: BrushMode) = item.withMode(mode)
} }

View File

@@ -1,28 +1,28 @@
package com.bartlomiejpluta.base.editor.map.viewmodel package com.bartlomiejpluta.base.editor.map.viewmodel
import javafx.beans.property.SimpleBooleanProperty import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleDoubleProperty import javafx.beans.property.SimpleDoubleProperty
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import tornadofx.ViewModel import tornadofx.ViewModel
import tornadofx.getValue import tornadofx.getValue
import tornadofx.setValue import tornadofx.setValue
class EditorStateVM : ViewModel() { class EditorStateVM : ViewModel() {
val selectedLayerProperty = SimpleIntegerProperty(0) val selectedLayerProperty = SimpleIntegerProperty(0)
var selectedLayer by selectedLayerProperty var selectedLayer by selectedLayerProperty
val showGridProperty = SimpleBooleanProperty(true) val showGridProperty = SimpleBooleanProperty(true)
var showGrid by showGridProperty var showGrid by showGridProperty
val coverUnderlyingLayersProperty = SimpleBooleanProperty(true) val coverUnderlyingLayersProperty = SimpleBooleanProperty(true)
var coverUnderlyingLayers by coverUnderlyingLayersProperty var coverUnderlyingLayers by coverUnderlyingLayersProperty
val zoomProperty = SimpleDoubleProperty(1.0) val zoomProperty = SimpleDoubleProperty(1.0)
var zoom by zoomProperty var zoom by zoomProperty
val cursorRowProperty = SimpleIntegerProperty(-1) val cursorRowProperty = SimpleIntegerProperty(-1)
val cursorRow by cursorRowProperty val cursorRow by cursorRowProperty
val cursorColumnProperty = SimpleIntegerProperty(-1) val cursorColumnProperty = SimpleIntegerProperty(-1)
val cursorColumn by cursorColumnProperty val cursorColumn by cursorColumnProperty
} }

View File

@@ -1,39 +1,39 @@
package com.bartlomiejpluta.base.editor.map.viewmodel package com.bartlomiejpluta.base.editor.map.viewmodel
import com.bartlomiejpluta.base.editor.map.model.layer.Layer import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.map.GameMap import com.bartlomiejpluta.base.editor.map.model.map.GameMap
import javafx.beans.property.SimpleListProperty import javafx.beans.property.SimpleListProperty
import tornadofx.ItemViewModel import tornadofx.ItemViewModel
import tornadofx.getValue import tornadofx.getValue
import tornadofx.setValue import tornadofx.setValue
class GameMapVM(map: GameMap) : ItemViewModel<GameMap>(map) { class GameMapVM(map: GameMap) : ItemViewModel<GameMap>(map) {
val layers: SimpleListProperty<Layer> = bind(GameMap::layers) val layers: SimpleListProperty<Layer> = bind(GameMap::layers)
val nameProperty = bind(GameMap::nameProperty) val nameProperty = bind(GameMap::nameProperty)
var name by nameProperty var name by nameProperty
val tileSetProperty = bind(GameMap::tileSet) val tileSetProperty = bind(GameMap::tileSet)
val tileSet by tileSetProperty val tileSet by tileSetProperty
val rowsProperty = bind(GameMap::rowsProperty) val rowsProperty = bind(GameMap::rowsProperty)
var rows by rowsProperty var rows by rowsProperty
val columnsProperty = bind(GameMap::columnsProperty) val columnsProperty = bind(GameMap::columnsProperty)
var columns by columnsProperty var columns by columnsProperty
val tileWidthProperty = bind(GameMap::tileWidth) val tileWidthProperty = bind(GameMap::tileWidth)
val tileWidth by tileWidthProperty val tileWidth by tileWidthProperty
val tileHeightProperty = bind(GameMap::tileHeight) val tileHeightProperty = bind(GameMap::tileHeight)
val tileHeight by tileHeightProperty val tileHeight by tileHeightProperty
val widthProperty = bind(GameMap::widthProperty) val widthProperty = bind(GameMap::widthProperty)
val width by widthProperty val width by widthProperty
val heightProperty = bind(GameMap::heightProperty) val heightProperty = bind(GameMap::heightProperty)
val height by heightProperty val height by heightProperty
} }

View File

@@ -1,32 +1,32 @@
package com.bartlomiejpluta.base.editor.project.manager package com.bartlomiejpluta.base.editor.project.manager
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import com.bartlomiejpluta.base.editor.project.serial.ProjectDeserializer import com.bartlomiejpluta.base.editor.project.serial.ProjectDeserializer
import com.bartlomiejpluta.base.editor.project.serial.ProjectSerializer import com.bartlomiejpluta.base.editor.project.serial.ProjectSerializer
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
@Component @Component
class DefaultProjectManager : ProjectManager { class DefaultProjectManager : ProjectManager {
@Autowired @Autowired
private lateinit var projectSerializer: ProjectSerializer private lateinit var projectSerializer: ProjectSerializer
@Autowired @Autowired
private lateinit var projectDeserializer: ProjectDeserializer private lateinit var projectDeserializer: ProjectDeserializer
override fun saveProject(project: Project) { override fun saveProject(project: Project) {
project.sourceDirectory.mkdirs() project.sourceDirectory.mkdirs()
FileOutputStream(File(project.sourceDirectory, "project.bep")).use { FileOutputStream(File(project.sourceDirectory, "project.bep")).use {
projectSerializer.serialize(project, it) projectSerializer.serialize(project, it)
} }
} }
override fun openProject(file: File) = FileInputStream(file) override fun openProject(file: File) = FileInputStream(file)
.use { projectDeserializer.deserialize(it) } .use { projectDeserializer.deserialize(it) }
.apply { sourceDirectoryProperty.value = file.parentFile } .apply { sourceDirectoryProperty.value = file.parentFile }
} }

View File

@@ -1,9 +1,9 @@
package com.bartlomiejpluta.base.editor.project.manager package com.bartlomiejpluta.base.editor.project.manager
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import java.io.File import java.io.File
interface ProjectManager { interface ProjectManager {
fun saveProject(project: Project) fun saveProject(project: Project)
fun openProject(file: File): Project fun openProject(file: File): Project
} }

View File

@@ -1,14 +1,14 @@
package com.bartlomiejpluta.base.editor.project.model package com.bartlomiejpluta.base.editor.project.model
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import tornadofx.* import tornadofx.*
import java.io.File import java.io.File
class Project { class Project {
val nameProperty = SimpleStringProperty() val nameProperty = SimpleStringProperty()
var name by nameProperty var name by nameProperty
val sourceDirectoryProperty = SimpleObjectProperty<File>() val sourceDirectoryProperty = SimpleObjectProperty<File>()
val sourceDirectory by sourceDirectoryProperty val sourceDirectory by sourceDirectoryProperty
} }

View File

@@ -1,6 +1,6 @@
package com.bartlomiejpluta.base.editor.project.serial package com.bartlomiejpluta.base.editor.project.serial
import com.bartlomiejpluta.base.editor.common.serial.Deserializer import com.bartlomiejpluta.base.editor.common.serial.Deserializer
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
interface ProjectDeserializer : Deserializer<Project> interface ProjectDeserializer : Deserializer<Project>

View File

@@ -1,6 +1,6 @@
package com.bartlomiejpluta.base.editor.project.serial package com.bartlomiejpluta.base.editor.project.serial
import com.bartlomiejpluta.base.editor.common.serial.Serializer import com.bartlomiejpluta.base.editor.common.serial.Serializer
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
interface ProjectSerializer : Serializer<Project> interface ProjectSerializer : Serializer<Project>

View File

@@ -1,18 +1,18 @@
package com.bartlomiejpluta.base.editor.project.serial package com.bartlomiejpluta.base.editor.project.serial
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import com.bartlomiejpluta.base.proto.ProjectProto import com.bartlomiejpluta.base.proto.ProjectProto
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.InputStream import java.io.InputStream
@Component @Component
class ProtobufProjectDeserializer : ProjectDeserializer { class ProtobufProjectDeserializer : ProjectDeserializer {
override fun deserialize(input: InputStream): Project { override fun deserialize(input: InputStream): Project {
val proto = ProjectProto.Project.parseFrom(input) val proto = ProjectProto.Project.parseFrom(input)
val project = Project() val project = Project()
project.name = proto.name project.name = proto.name
return project return project
} }
} }

View File

@@ -1,16 +1,16 @@
package com.bartlomiejpluta.base.editor.project.serial package com.bartlomiejpluta.base.editor.project.serial
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import com.bartlomiejpluta.base.proto.ProjectProto import com.bartlomiejpluta.base.proto.ProjectProto
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.io.OutputStream import java.io.OutputStream
@Component @Component
class ProtobufProjectSerializer : ProjectSerializer { class ProtobufProjectSerializer : ProjectSerializer {
override fun serialize(item: Project, output: OutputStream) { override fun serialize(item: Project, output: OutputStream) {
val proto = ProjectProto.Project.newBuilder() val proto = ProjectProto.Project.newBuilder()
proto.name = item.name proto.name = item.name
proto.build().writeTo(output) proto.build().writeTo(output)
} }
} }

View File

@@ -1,96 +1,96 @@
package com.bartlomiejpluta.base.editor.project.view package com.bartlomiejpluta.base.editor.project.view
import com.bartlomiejpluta.base.editor.project.viewmodel.ProjectVM import com.bartlomiejpluta.base.editor.project.viewmodel.ProjectVM
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.beans.binding.Bindings.createStringBinding import javafx.beans.binding.Bindings.createStringBinding
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import tornadofx.* import tornadofx.*
import java.io.File import java.io.File
class ProjectSettingsFragment : Fragment("Project Settings") { class ProjectSettingsFragment : Fragment("Project Settings") {
private val projectVM = find<ProjectVM>(FX.defaultScope) private val projectVM = find<ProjectVM>(FX.defaultScope)
private val rootDirectory = SimpleStringProperty("") private val rootDirectory = SimpleStringProperty("")
private val projectDirectory = SimpleStringProperty("") private val projectDirectory = SimpleStringProperty("")
private val directory = createStringBinding({ private val directory = createStringBinding({
File(rootDirectory.value, projectDirectory.value).absolutePath File(rootDirectory.value, projectDirectory.value).absolutePath
}, rootDirectory, projectDirectory) }, rootDirectory, projectDirectory)
init { init {
directory.addListener { _, _, path -> projectVM.sourceDirectoryProperty.value = File(path) } directory.addListener { _, _, path -> projectVM.sourceDirectoryProperty.value = File(path) }
} }
var result = false var result = false
private set private set
override val root = form { override val root = form {
fieldset("Project Settings") { fieldset("Project Settings") {
field("Project Name") { field("Project Name") {
textfield(projectVM.nameProperty) { textfield(projectVM.nameProperty) {
required() required()
trimWhitespace() trimWhitespace()
whenDocked { requestFocus() } whenDocked { requestFocus() }
} }
} }
field("Project Location") { field("Project Location") {
hbox { hbox {
textfield(rootDirectory) { textfield(rootDirectory) {
trimWhitespace() trimWhitespace()
projectVM.validationContext.addValidator(this, rootDirectory) { projectVM.validationContext.addValidator(this, rootDirectory) {
when { when {
it.isNullOrBlank() -> error("Field is required") it.isNullOrBlank() -> error("Field is required")
!File(it).exists() -> error("Provide valid path to the direction") !File(it).exists() -> error("Provide valid path to the direction")
else -> null else -> null
} }
} }
} }
button("Choose") { button("Choose") {
action { action {
rootDirectory.value = chooseDirectory("Project Location")?.absolutePath rootDirectory.value = chooseDirectory("Project Location")?.absolutePath
} }
} }
} }
} }
field("Project Directory Name") { field("Project Directory Name") {
textfield(projectDirectory) { textfield(projectDirectory) {
trimWhitespace() trimWhitespace()
projectVM.validationContext.addValidator(this, projectDirectory) { projectVM.validationContext.addValidator(this, projectDirectory) {
when { when {
it.isNullOrBlank() -> error("Field is required") it.isNullOrBlank() -> error("Field is required")
File(directory.value).exists() -> error("The directory ${directory.value} already exists") File(directory.value).exists() -> error("The directory ${directory.value} already exists")
else -> null else -> null
} }
} }
} }
} }
label(Bindings.format("Directory:\n%s", directory)) label(Bindings.format("Directory:\n%s", directory))
} }
buttonbar { buttonbar {
button("Ok") { button("Ok") {
action { action {
projectVM.commit { projectVM.commit {
result = true result = true
close() close()
} }
} }
shortcut("Enter") shortcut("Enter")
} }
button("Reset") { button("Reset") {
action { projectVM.rollback() } action { projectVM.rollback() }
} }
button("Cancel") { button("Cancel") {
action { close() } action { close() }
} }
} }
} }
} }

View File

@@ -1,12 +1,12 @@
package com.bartlomiejpluta.base.editor.project.viewmodel package com.bartlomiejpluta.base.editor.project.viewmodel
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
import tornadofx.* import tornadofx.*
class ProjectVM(project: Project) : ItemViewModel<Project>(project) { class ProjectVM(project: Project) : ItemViewModel<Project>(project) {
val nameProperty = bind(Project::nameProperty) val nameProperty = bind(Project::nameProperty)
val name by nameProperty val name by nameProperty
val sourceDirectoryProperty = bind(Project::sourceDirectoryProperty) val sourceDirectoryProperty = bind(Project::sourceDirectoryProperty)
val sourceDirectory by sourceDirectoryProperty val sourceDirectory by sourceDirectoryProperty
} }

View File

@@ -1,18 +1,18 @@
package com.bartlomiejpluta.base.editor.render.input package com.bartlomiejpluta.base.editor.render.input
import com.bartlomiejpluta.base.editor.tileset.model.TileSet import com.bartlomiejpluta.base.editor.tileset.model.TileSet
import javafx.event.EventType import javafx.event.EventType
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
class MapMouseEvent(val row: Int, val column: Int, val type: EventType<out MouseEvent>, val button: MouseButton) { class MapMouseEvent(val row: Int, val column: Int, val type: EventType<out MouseEvent>, val button: MouseButton) {
companion object { companion object {
fun of(event: MouseEvent, tileSet: TileSet) = MapMouseEvent( fun of(event: MouseEvent, tileSet: TileSet) = MapMouseEvent(
(event.y / tileSet.tileHeight).toInt(), (event.y / tileSet.tileHeight).toInt(),
(event.x / tileSet.tileWidth).toInt(), (event.x / tileSet.tileWidth).toInt(),
event.eventType, event.eventType,
event.button event.button
) )
} }
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.editor.render.input package com.bartlomiejpluta.base.editor.render.input
interface MapMouseEventHandler { interface MapMouseEventHandler {
fun handleMouseInput(event: MapMouseEvent) fun handleMouseInput(event: MapMouseEvent)
} }

View File

@@ -1,7 +1,7 @@
package com.bartlomiejpluta.base.editor.render.model package com.bartlomiejpluta.base.editor.render.model
import javafx.scene.canvas.GraphicsContext import javafx.scene.canvas.GraphicsContext
interface Renderable { interface Renderable {
fun render(gc: GraphicsContext) fun render(gc: GraphicsContext)
} }

View File

@@ -1,8 +1,8 @@
package com.bartlomiejpluta.base.editor.resource.uid.manager package com.bartlomiejpluta.base.editor.resource.uid.manager
import com.bartlomiejpluta.base.editor.resource.uid.model.UIDTarget import com.bartlomiejpluta.base.editor.resource.uid.model.UIDTarget
interface UIDManager { interface UIDManager {
fun nextUID(target: UIDTarget): String fun nextUID(target: UIDTarget): String
fun loadData(target: UIDTarget, uids: Set<String>) fun loadData(target: UIDTarget, uids: Set<String>)
} }

View File

@@ -1,26 +1,26 @@
package com.bartlomiejpluta.base.editor.resource.uid.manager package com.bartlomiejpluta.base.editor.resource.uid.manager
import com.bartlomiejpluta.base.editor.resource.uid.model.UIDTarget import com.bartlomiejpluta.base.editor.resource.uid.model.UIDTarget
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.util.* import java.util.*
@Component @Component
class UUIDBasedUIDManager : UIDManager { class UUIDBasedUIDManager : UIDManager {
private val registry = mutableMapOf<UIDTarget, Set<String>>() private val registry = mutableMapOf<UIDTarget, Set<String>>()
override fun nextUID(target: UIDTarget): String { override fun nextUID(target: UIDTarget): String {
val set = registry.putIfAbsent(target, mutableSetOf())!! val set = registry.putIfAbsent(target, mutableSetOf())!!
var uid = "" var uid = ""
do { do {
uid = UUID.randomUUID().toString() uid = UUID.randomUUID().toString()
} while (uid !in set) } while (uid !in set)
return uid return uid
} }
override fun loadData(target: UIDTarget, uids: Set<String>) { override fun loadData(target: UIDTarget, uids: Set<String>) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.editor.resource.uid.model package com.bartlomiejpluta.base.editor.resource.uid.model
enum class UIDTarget { enum class UIDTarget {
MAP MAP
} }

View File

@@ -1,69 +1,69 @@
package com.bartlomiejpluta.base.editor.tileset.canvas package com.bartlomiejpluta.base.editor.tileset.canvas
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import com.bartlomiejpluta.base.editor.render.input.MapMouseEventHandler import com.bartlomiejpluta.base.editor.render.input.MapMouseEventHandler
import com.bartlomiejpluta.base.editor.render.model.Renderable import com.bartlomiejpluta.base.editor.render.model.Renderable
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.canvas.GraphicsContext import javafx.scene.canvas.GraphicsContext
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.paint.Color import javafx.scene.paint.Color
class TileSetCanvas(private val gameMapVM: GameMapVM, private val selection: TileSetSelection) : Renderable, MapMouseEventHandler { class TileSetCanvas(private val gameMapVM: GameMapVM, private val selection: TileSetSelection) : Renderable, MapMouseEventHandler {
private var mouseRow = -1 private var mouseRow = -1
private var mouseColumn = -1 private var mouseColumn = -1
override fun render(gc: GraphicsContext) { override fun render(gc: GraphicsContext) {
gc.clearRect(0.0, 0.0, gc.canvas.width, gc.canvas.height) gc.clearRect(0.0, 0.0, gc.canvas.width, gc.canvas.height)
renderTiles(gc) renderTiles(gc)
selection.render(gc) selection.render(gc)
} }
private fun renderTiles(gc: GraphicsContext) { private fun renderTiles(gc: GraphicsContext) {
gameMapVM.tileSet.forEach { row, column, tile -> gameMapVM.tileSet.forEach { row, column, tile ->
gc.fill = if ((row + column) % 2 == 0) BACKGROUND_COLOR1 else BACKGROUND_COLOR2 gc.fill = if ((row + column) % 2 == 0) BACKGROUND_COLOR1 else BACKGROUND_COLOR2
gc.fillRect( gc.fillRect(
column * tile.image.width, column * tile.image.width,
row * tile.image.height, row * tile.image.height,
gameMapVM.tileSet.tileWidth.toDouble(), gameMapVM.tileSet.tileWidth.toDouble(),
gameMapVM.tileSet.tileHeight.toDouble() gameMapVM.tileSet.tileHeight.toDouble()
) )
gc.drawImage(tile.image, column * tile.image.width, row * tile.image.height) gc.drawImage(tile.image, column * tile.image.width, row * tile.image.height)
} }
} }
override fun handleMouseInput(event: MapMouseEvent) { override fun handleMouseInput(event: MapMouseEvent) {
mouseRow = event.row mouseRow = event.row
mouseColumn = event.column mouseColumn = event.column
when (event.type) { when (event.type) {
MouseEvent.MOUSE_PRESSED -> beginSelection(event) MouseEvent.MOUSE_PRESSED -> beginSelection(event)
MouseEvent.MOUSE_DRAGGED -> proceedSelection(event) MouseEvent.MOUSE_DRAGGED -> proceedSelection(event)
MouseEvent.MOUSE_RELEASED -> finishSelection(event) MouseEvent.MOUSE_RELEASED -> finishSelection(event)
} }
} }
private fun beginSelection(event: MapMouseEvent) { private fun beginSelection(event: MapMouseEvent) {
if (event.button == MouseButton.PRIMARY) { if (event.button == MouseButton.PRIMARY) {
selection.begin(event.row.toDouble(), event.column.toDouble()) selection.begin(event.row.toDouble(), event.column.toDouble())
} }
} }
private fun proceedSelection(event: MapMouseEvent) { private fun proceedSelection(event: MapMouseEvent) {
if (event.button == MouseButton.PRIMARY) { if (event.button == MouseButton.PRIMARY) {
selection.proceed(event.row.toDouble(), event.column.toDouble()) selection.proceed(event.row.toDouble(), event.column.toDouble())
} }
} }
private fun finishSelection(event: MapMouseEvent) { private fun finishSelection(event: MapMouseEvent) {
if (event.button == MouseButton.PRIMARY) { if (event.button == MouseButton.PRIMARY) {
selection.finish(event.row.toDouble(), event.column.toDouble()) selection.finish(event.row.toDouble(), event.column.toDouble())
} }
} }
companion object { companion object {
private val BACKGROUND_COLOR1 = Color.color(1.0, 1.0, 1.0, 1.0) private val BACKGROUND_COLOR1 = Color.color(1.0, 1.0, 1.0, 1.0)
private val BACKGROUND_COLOR2 = Color.color(0.95, 0.95, 0.95, 0.95) private val BACKGROUND_COLOR2 = Color.color(0.95, 0.95, 0.95, 0.95)
} }
} }

View File

@@ -1,80 +1,80 @@
package com.bartlomiejpluta.base.editor.tileset.canvas package com.bartlomiejpluta.base.editor.tileset.canvas
import com.bartlomiejpluta.base.editor.map.model.brush.Brush import com.bartlomiejpluta.base.editor.map.model.brush.Brush
import com.bartlomiejpluta.base.editor.render.model.Renderable import com.bartlomiejpluta.base.editor.render.model.Renderable
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.scene.canvas.GraphicsContext import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color import javafx.scene.paint.Color
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
class TileSetSelection(private val gameMapVM: GameMapVM, private val brushVM: BrushVM) : Renderable { class TileSetSelection(private val gameMapVM: GameMapVM, private val brushVM: BrushVM) : Renderable {
private var startRow = 0.0 private var startRow = 0.0
private var startColumn = 0.0 private var startColumn = 0.0
private var offsetRow = 0.0 private var offsetRow = 0.0
private var offsetColumn = 0.0 private var offsetColumn = 0.0
private var x = 0.0 private var x = 0.0
private var y = 0.0 private var y = 0.0
private var width = gameMapVM.tileSet.tileWidth.toDouble() private var width = gameMapVM.tileSet.tileWidth.toDouble()
private var height = gameMapVM.tileSet.tileHeight.toDouble() private var height = gameMapVM.tileSet.tileHeight.toDouble()
fun begin(row: Double, column: Double) { fun begin(row: Double, column: Double) {
startRow = row startRow = row
offsetRow = 0.0 offsetRow = 0.0
startColumn = column startColumn = column
offsetColumn = 0.0 offsetColumn = 0.0
updateRect(row, column) updateRect(row, column)
} }
private fun updateRect(row: Double, column: Double) { private fun updateRect(row: Double, column: Double) {
x = min(column, startColumn) * gameMapVM.tileSet.tileWidth x = min(column, startColumn) * gameMapVM.tileSet.tileWidth
y = min(row, startRow) * gameMapVM.tileSet.tileHeight y = min(row, startRow) * gameMapVM.tileSet.tileHeight
width = (offsetColumn + 1) * gameMapVM.tileSet.tileWidth width = (offsetColumn + 1) * gameMapVM.tileSet.tileWidth
height = (offsetRow + 1) * gameMapVM.tileSet.tileHeight height = (offsetRow + 1) * gameMapVM.tileSet.tileHeight
} }
fun proceed(row: Double, column: Double) { fun proceed(row: Double, column: Double) {
offsetRow = abs(row - startRow) offsetRow = abs(row - startRow)
offsetColumn = abs(column - startColumn) offsetColumn = abs(column - startColumn)
updateRect(row, column) updateRect(row, column)
} }
fun finish(row: Double, column: Double) { fun finish(row: Double, column: Double) {
proceed(row, column) proceed(row, column)
startRow = min(row, startRow) startRow = min(row, startRow)
startColumn = min(column, startColumn) startColumn = min(column, startColumn)
val firstRow = startRow.toInt() val firstRow = startRow.toInt()
val firstColumn = startColumn.toInt() val firstColumn = startColumn.toInt()
val rows = offsetRow.toInt() + 1 val rows = offsetRow.toInt() + 1
val columns = offsetColumn.toInt() + 1 val columns = offsetColumn.toInt() + 1
val brushArray = Array(rows) { rowIndex -> val brushArray = Array(rows) { rowIndex ->
Array(columns) { columnIndex -> Array(columns) { columnIndex ->
gameMapVM.tileSet.getTile(firstRow + rowIndex, firstColumn + columnIndex) gameMapVM.tileSet.getTile(firstRow + rowIndex, firstColumn + columnIndex)
} }
} }
brushVM.item = Brush.of(brushArray) brushVM.item = Brush.of(brushArray)
brushVM.commit() brushVM.commit()
} }
override fun render(gc: GraphicsContext) { override fun render(gc: GraphicsContext) {
gc.fill = SELECTION_FILL_COLOR gc.fill = SELECTION_FILL_COLOR
gc.fillRect(x, y, width, height) gc.fillRect(x, y, width, height)
gc.stroke = SELECTION_STROKE_COLOR gc.stroke = SELECTION_STROKE_COLOR
gc.strokeRect(x, y, width, height) gc.strokeRect(x, y, width, height)
} }
companion object { companion object {
private val SELECTION_FILL_COLOR = Color.color(0.0, 0.7, 1.0, 0.4) private val SELECTION_FILL_COLOR = Color.color(0.0, 0.7, 1.0, 0.4)
private val SELECTION_STROKE_COLOR = Color.color(0.0, 0.7, 1.0, 1.0) private val SELECTION_STROKE_COLOR = Color.color(0.0, 0.7, 1.0, 1.0)
} }
} }

View File

@@ -1,42 +1,42 @@
package com.bartlomiejpluta.base.editor.tileset.component package com.bartlomiejpluta.base.editor.tileset.component
import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent import com.bartlomiejpluta.base.editor.render.input.MapMouseEvent
import com.bartlomiejpluta.base.editor.tileset.canvas.TileSetCanvas import com.bartlomiejpluta.base.editor.tileset.canvas.TileSetCanvas
import com.bartlomiejpluta.base.editor.tileset.canvas.TileSetSelection import com.bartlomiejpluta.base.editor.tileset.canvas.TileSetSelection
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.event.EventHandler import javafx.event.EventHandler
import javafx.scene.canvas.Canvas import javafx.scene.canvas.Canvas
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
class TileSetPane(private val gameMapVM: GameMapVM, brushVM: BrushVM) : Canvas(), EventHandler<MouseEvent> { class TileSetPane(private val gameMapVM: GameMapVM, brushVM: BrushVM) : Canvas(), EventHandler<MouseEvent> {
private val selection = TileSetSelection(gameMapVM, brushVM) private val selection = TileSetSelection(gameMapVM, brushVM)
private val tileSetCanvas = TileSetCanvas(gameMapVM, selection) private val tileSetCanvas = TileSetCanvas(gameMapVM, selection)
init { init {
onMouseMoved = this onMouseMoved = this
onMouseDragged = this onMouseDragged = this
onMousePressed = this onMousePressed = this
onMouseReleased = this onMouseReleased = this
width = gameMapVM.tileSet.width.toDouble() width = gameMapVM.tileSet.width.toDouble()
height = gameMapVM.tileSet.height.toDouble() height = gameMapVM.tileSet.height.toDouble()
brushVM.itemProperty.addListener { _, _, _ -> render() } brushVM.itemProperty.addListener { _, _, _ -> render() }
render() render()
} }
private fun render() { private fun render() {
tileSetCanvas.render(graphicsContext2D) tileSetCanvas.render(graphicsContext2D)
} }
override fun handle(event: MouseEvent?) { override fun handle(event: MouseEvent?) {
if (event != null) { if (event != null) {
tileSetCanvas.handleMouseInput(MapMouseEvent.of(event, gameMapVM.tileSet)) tileSetCanvas.handleMouseInput(MapMouseEvent.of(event, gameMapVM.tileSet))
} }
tileSetCanvas.render(graphicsContext2D) tileSetCanvas.render(graphicsContext2D)
} }
} }

View File

@@ -1,55 +1,55 @@
package com.bartlomiejpluta.base.editor.tileset.model package com.bartlomiejpluta.base.editor.tileset.model
import javafx.scene.image.Image import javafx.scene.image.Image
import javafx.scene.image.PixelReader import javafx.scene.image.PixelReader
import javafx.scene.image.WritableImage import javafx.scene.image.WritableImage
class Tile( class Tile(
tileSet: TileSet, tileSet: TileSet,
row: Int, row: Int,
column: Int, column: Int,
val image: Image, val image: Image,
) { ) {
val id = row * tileSet.columns + column val id = row * tileSet.columns + column
private fun cloneImage(image: Image): Image { private fun cloneImage(image: Image): Image {
val height = image.height.toInt() val height = image.height.toInt()
val width = image.width.toInt() val width = image.width.toInt()
val pixelReader = image.pixelReader val pixelReader = image.pixelReader
val writableImage = WritableImage(width, height) val writableImage = WritableImage(width, height)
val pixelWriter = writableImage.pixelWriter val pixelWriter = writableImage.pixelWriter
for (y in 0 until height) { for (y in 0 until height) {
for (x in 0 until width) { for (x in 0 until width) {
val color = pixelReader.getColor(x, y) val color = pixelReader.getColor(x, y)
pixelWriter.setColor(x, y, color) pixelWriter.setColor(x, y, color)
} }
} }
return writableImage return writableImage
} }
fun scale(image: Image, factor: Int): Image { fun scale(image: Image, factor: Int): Image {
val width = image.width.toInt() val width = image.width.toInt()
val height = image.height.toInt() val height = image.height.toInt()
val output = WritableImage(width * factor, height * factor) val output = WritableImage(width * factor, height * factor)
val reader: PixelReader = image.pixelReader val reader: PixelReader = image.pixelReader
val writer = output.pixelWriter val writer = output.pixelWriter
for (y in 0 until height) { for (y in 0 until height) {
for (x in 0 until width) { for (x in 0 until width) {
val argb = reader.getArgb(x, y) val argb = reader.getArgb(x, y)
for (dy in 0 until factor) { for (dy in 0 until factor) {
for (dx in 0 until factor) { for (dx in 0 until factor) {
writer.setArgb(x * factor + dx, y * factor + dy, argb) writer.setArgb(x * factor + dx, y * factor + dy, argb)
} }
} }
} }
} }
return output return output
} }
} }

View File

@@ -1,63 +1,63 @@
package com.bartlomiejpluta.base.editor.tileset.model package com.bartlomiejpluta.base.editor.tileset.model
import com.bartlomiejpluta.base.editor.map.model.brush.Brush import com.bartlomiejpluta.base.editor.map.model.brush.Brush
import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleIntegerProperty
import javafx.scene.image.Image import javafx.scene.image.Image
import javafx.scene.image.PixelFormat import javafx.scene.image.PixelFormat
import javafx.scene.image.WritableImage import javafx.scene.image.WritableImage
import tornadofx.getValue import tornadofx.getValue
import tornadofx.toObservable import tornadofx.toObservable
import java.nio.ByteBuffer import java.nio.ByteBuffer
class TileSet(private val image: Image, rows: Int, columns: Int) { class TileSet(private val image: Image, rows: Int, columns: Int) {
val rowsProperty = SimpleIntegerProperty(rows) val rowsProperty = SimpleIntegerProperty(rows)
val rows by rowsProperty val rows by rowsProperty
val columnsProperty = SimpleIntegerProperty(columns) val columnsProperty = SimpleIntegerProperty(columns)
val columns by columnsProperty val columns by columnsProperty
val tileWidthProperty = SimpleIntegerProperty(image.width.toInt() / columns) val tileWidthProperty = SimpleIntegerProperty(image.width.toInt() / columns)
val tileWidth by tileWidthProperty val tileWidth by tileWidthProperty
val tileHeightProperty = SimpleIntegerProperty(image.height.toInt() / rows) val tileHeightProperty = SimpleIntegerProperty(image.height.toInt() / rows)
val tileHeight by tileHeightProperty val tileHeight by tileHeightProperty
val widthProperty = SimpleIntegerProperty(tileWidth * columns) val widthProperty = SimpleIntegerProperty(tileWidth * columns)
val width by widthProperty val width by widthProperty
val heightProperty = SimpleIntegerProperty(tileHeight * rows) val heightProperty = SimpleIntegerProperty(tileHeight * rows)
val height by heightProperty val height by heightProperty
val tiles = (0 until rows * columns).map { cropTile(it / columns, it % columns) }.toObservable() val tiles = (0 until rows * columns).map { cropTile(it / columns, it % columns) }.toObservable()
val baseBrush: Brush val baseBrush: Brush
get() = Brush.of(arrayOf(arrayOf(tiles[0]))) get() = Brush.of(arrayOf(arrayOf(tiles[0])))
private fun cropTile(row: Int, column: Int): Tile { private fun cropTile(row: Int, column: Int): Tile {
val reader = image.pixelReader val reader = image.pixelReader
val buffer = ByteBuffer.allocate(tileWidth * tileHeight * 4) val buffer = ByteBuffer.allocate(tileWidth * tileHeight * 4)
reader.getPixels( reader.getPixels(
column * tileWidth, column * tileWidth,
row * tileHeight, row * tileHeight,
tileWidth, tileWidth,
tileHeight, tileHeight,
PixelFormat.getByteBgraInstance(), PixelFormat.getByteBgraInstance(),
buffer, buffer,
4 * tileWidth 4 * tileWidth
) )
val tile = WritableImage(tileWidth, tileHeight) val tile = WritableImage(tileWidth, tileHeight)
val writer = tile.pixelWriter val writer = tile.pixelWriter
writer.setPixels(0, 0, tileWidth, tileHeight, PixelFormat.getByteBgraInstance(), buffer, 4 * tileWidth) writer.setPixels(0, 0, tileWidth, tileHeight, PixelFormat.getByteBgraInstance(), buffer, 4 * tileWidth)
return Tile(this, row, column, tile) return Tile(this, row, column, tile)
} }
fun getTile(row: Int, column: Int) = tiles[(row.coerceIn(0 until rows)) * columns + column.coerceIn(0 until columns)] fun getTile(row: Int, column: Int) = tiles[(row.coerceIn(0 until rows)) * columns + column.coerceIn(0 until columns)]
fun getTile(id: Int) = tiles[id] fun getTile(id: Int) = tiles[id]
fun forEach(consumer: (row: Int, column: Int, tile: Tile) -> Unit) = tiles.forEachIndexed { id, tile -> fun forEach(consumer: (row: Int, column: Int, tile: Tile) -> Unit) = tiles.forEachIndexed { id, tile ->
consumer(id / columns, id % columns, tile) consumer(id / columns, id % columns, tile)
} }
} }

View File

@@ -1,19 +1,19 @@
package com.bartlomiejpluta.base.editor.tileset.view package com.bartlomiejpluta.base.editor.tileset.view
import com.bartlomiejpluta.base.editor.tileset.component.TileSetPane import com.bartlomiejpluta.base.editor.tileset.component.TileSetPane
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import tornadofx.View import tornadofx.View
import tornadofx.plusAssign import tornadofx.plusAssign
import tornadofx.scrollpane import tornadofx.scrollpane
class TileSetView : View() { class TileSetView : View() {
private val gameMapVM = find<GameMapVM>() private val gameMapVM = find<GameMapVM>()
private val brushVM = find<BrushVM>() private val brushVM = find<BrushVM>()
private val tileSetPane = TileSetPane(gameMapVM, brushVM) private val tileSetPane = TileSetPane(gameMapVM, brushVM)
override val root = scrollpane { override val root = scrollpane {
this += tileSetPane this += tileSetPane
} }
} }

4
editor/src/main/resources/application.yml Executable file → Normal file
View File

@@ -1,3 +1,3 @@
logging: logging:
level: level:
com.bartlomiejpluta.base.editor: DEBUG com.bartlomiejpluta.base.editor: DEBUG

0
engine/build.gradle Executable file → Normal file
View File

View File

@@ -1,111 +1,111 @@
package com.bartlomiejpluta.base.core.engine; package com.bartlomiejpluta.base.core.engine;
import com.bartlomiejpluta.base.core.gc.OffHeapGarbageCollector; import com.bartlomiejpluta.base.core.gc.OffHeapGarbageCollector;
import com.bartlomiejpluta.base.core.logic.GameLogic; import com.bartlomiejpluta.base.core.logic.GameLogic;
import com.bartlomiejpluta.base.core.thread.ThreadManager; import com.bartlomiejpluta.base.core.thread.ThreadManager;
import com.bartlomiejpluta.base.core.time.ChronoMeter; import com.bartlomiejpluta.base.core.time.ChronoMeter;
import com.bartlomiejpluta.base.core.ui.Window; import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.ui.WindowManager; import com.bartlomiejpluta.base.core.ui.WindowManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j @Slf4j
@Component @Component
public class DefaultGameEngine implements GameEngine { public class DefaultGameEngine implements GameEngine {
private static final String THREAD_NAME = "Game Main Thread"; private static final String THREAD_NAME = "Game Main Thread";
private final WindowManager windowManager; private final WindowManager windowManager;
private final ThreadManager threadManager; private final ThreadManager threadManager;
private final GameLogic logic; private final GameLogic logic;
private final OffHeapGarbageCollector garbageCollector; private final OffHeapGarbageCollector garbageCollector;
private final Thread thread; private final Thread thread;
private final Window window; private final Window window;
private final ChronoMeter chrono; private final ChronoMeter chrono;
private final int targetUps; private final int targetUps;
private boolean running = false; private boolean running = false;
@Autowired @Autowired
public DefaultGameEngine(WindowManager windowManager, public DefaultGameEngine(WindowManager windowManager,
ThreadManager threadManager, ThreadManager threadManager,
GameLogic logic, GameLogic logic,
OffHeapGarbageCollector garbageCollector, OffHeapGarbageCollector garbageCollector,
@Value("${app.window.title}") String title, @Value("${app.window.title}") String title,
@Value("${app.window.width}") int width, @Value("${app.window.width}") int width,
@Value("${app.window.height}") int height, @Value("${app.window.height}") int height,
@Value("${app.core.targetUps}") int targetUps) { @Value("${app.core.targetUps}") int targetUps) {
this.windowManager = windowManager; this.windowManager = windowManager;
this.threadManager = threadManager; this.threadManager = threadManager;
this.logic = logic; this.logic = logic;
this.garbageCollector = garbageCollector; this.garbageCollector = garbageCollector;
this.window = windowManager.createWindow(title, width, height); this.window = windowManager.createWindow(title, width, height);
this.thread = threadManager.createThread(THREAD_NAME, this::run); this.thread = threadManager.createThread(THREAD_NAME, this::run);
this.chrono = new ChronoMeter(); this.chrono = new ChronoMeter();
this.targetUps = targetUps; this.targetUps = targetUps;
} }
private void run() { private void run() {
try { try {
init(); init();
loop(); loop();
} finally { } finally {
cleanUp(); cleanUp();
} }
} }
private void init() { private void init() {
log.info("Initializing game engine"); log.info("Initializing game engine");
window.init(); window.init();
chrono.init(); chrono.init();
logic.init(window); logic.init(window);
} }
private void loop() { private void loop() {
log.info("Starting game loop"); log.info("Starting game loop");
running = true; running = true;
var dt = 0.0f; var dt = 0.0f;
var accumulator = 0.0f; var accumulator = 0.0f;
var step = 1.0f / targetUps; var step = 1.0f / targetUps;
while (running && !window.shouldClose()) { while (running && !window.shouldClose()) {
dt = chrono.getElapsedTime(); dt = chrono.getElapsedTime();
accumulator += dt; accumulator += dt;
input(); input();
while (accumulator >= step) { while (accumulator >= step) {
update(dt); update(dt);
accumulator -= step; accumulator -= step;
} }
render(); render();
} }
} }
private void input() { private void input() {
logic.input(window); logic.input(window);
} }
private void update(float dt) { private void update(float dt) {
logic.update(dt); logic.update(dt);
} }
private void render() { private void render() {
window.update(); window.update();
logic.render(window); logic.render(window);
} }
private void cleanUp() { private void cleanUp() {
log.info("Performing off heap garbage collection"); log.info("Performing off heap garbage collection");
garbageCollector.cleanUp(); garbageCollector.cleanUp();
} }
@Override @Override
public void start() { public void start() {
thread.start(); thread.start();
} }
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.core.engine; package com.bartlomiejpluta.base.core.engine;
public interface GameEngine { public interface GameEngine {
void start(); void start();
} }

View File

@@ -1,22 +1,22 @@
package com.bartlomiejpluta.base.core.error; package com.bartlomiejpluta.base.core.error;
public class AppException extends RuntimeException { public class AppException extends RuntimeException {
public AppException() { public AppException() {
} }
public AppException(String message, Object... args) { public AppException(String message, Object... args) {
super(String.format(message, args)); super(String.format(message, args));
} }
public AppException(String message, Throwable cause) { public AppException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
public AppException(Throwable cause) { public AppException(Throwable cause) {
super(cause); super(cause);
} }
public AppException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { public AppException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace); super(message, cause, enableSuppression, writableStackTrace);
} }
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.core.gc; package com.bartlomiejpluta.base.core.gc;
public interface Cleanable { public interface Cleanable {
void cleanUp(); void cleanUp();
} }

View File

@@ -1,22 +1,22 @@
package com.bartlomiejpluta.base.core.gc; package com.bartlomiejpluta.base.core.gc;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultOffHeapGarbageCollector implements OffHeapGarbageCollector { public class DefaultOffHeapGarbageCollector implements OffHeapGarbageCollector {
private final List<Cleanable> cleanables; private final List<Cleanable> cleanables;
@Override @Override
public void cleanUp() { public void cleanUp() {
cleanables.stream() cleanables.stream()
.peek(cleanable -> log.info("Performing {} cleaning", cleanable.getClass().getSimpleName())) .peek(cleanable -> log.info("Performing {} cleaning", cleanable.getClass().getSimpleName()))
.forEach(Cleanable::cleanUp); .forEach(Cleanable::cleanUp);
} }
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.core.gc; package com.bartlomiejpluta.base.core.gc;
public interface Disposable { public interface Disposable {
void dispose(); void dispose();
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.core.gc; package com.bartlomiejpluta.base.core.gc;
public interface OffHeapGarbageCollector { public interface OffHeapGarbageCollector {
void cleanUp(); void cleanUp();
} }

View File

@@ -1,83 +1,83 @@
package com.bartlomiejpluta.base.core.gl.object.material; package com.bartlomiejpluta.base.core.gl.object.material;
import com.bartlomiejpluta.base.core.gl.object.texture.Texture; import com.bartlomiejpluta.base.core.gl.object.texture.Texture;
import lombok.Getter; import lombok.Getter;
import org.joml.Vector2f; import org.joml.Vector2f;
import org.joml.Vector4f; import org.joml.Vector4f;
@Getter @Getter
public class Material { public class Material {
private final Vector4f color = new Vector4f(); private final Vector4f color = new Vector4f();
private final Vector2f spriteSize = new Vector2f(1, 1); private final Vector2f spriteSize = new Vector2f(1, 1);
private final Vector2f spritePosition = new Vector2f(0, 0); private final Vector2f spritePosition = new Vector2f(0, 0);
private final Texture texture; private final Texture texture;
private Material(Texture texture, float r, float g, float b, float alpha) { private Material(Texture texture, float r, float g, float b, float alpha) {
this.texture = texture; this.texture = texture;
setColor(r, g, b, alpha); setColor(r, g, b, alpha);
} }
public void setAlpha(float alpha) { public void setAlpha(float alpha) {
this.color.w = alpha; this.color.w = alpha;
} }
public void setColor(Vector4f color) { public void setColor(Vector4f color) {
this.color.x = color.x; this.color.x = color.x;
this.color.y = color.y; this.color.y = color.y;
this.color.z = color.z; this.color.z = color.z;
this.color.w = color.w; this.color.w = color.w;
} }
public void setColor(float r, float g, float b, float alpha) { public void setColor(float r, float g, float b, float alpha) {
color.x = r; color.x = r;
color.y = g; color.y = g;
color.z = b; color.z = b;
color.w = alpha; color.w = alpha;
} }
public void setSpriteSize(Vector2f spriteSize) { public void setSpriteSize(Vector2f spriteSize) {
this.spriteSize.x = spriteSize.x; this.spriteSize.x = spriteSize.x;
this.spriteSize.y = spriteSize.y; this.spriteSize.y = spriteSize.y;
} }
public void setSpriteSize(float w, float h) { public void setSpriteSize(float w, float h) {
this.spriteSize.x = w; this.spriteSize.x = w;
this.spriteSize.y = h; this.spriteSize.y = h;
} }
public void setSpritePosition(Vector2f spritePosition) { public void setSpritePosition(Vector2f spritePosition) {
this.spritePosition.x = spritePosition.x; this.spritePosition.x = spritePosition.x;
this.spritePosition.y = spritePosition.y; this.spritePosition.y = spritePosition.y;
} }
public void setSpritePosition(float x, float y) { public void setSpritePosition(float x, float y) {
this.spritePosition.x = x; this.spritePosition.x = x;
this.spritePosition.y = y; this.spritePosition.y = y;
} }
public boolean hasTexture() { public boolean hasTexture() {
return texture != null; return texture != null;
} }
public void activateTextureIfExists() { public void activateTextureIfExists() {
if(hasTexture()) { if(hasTexture()) {
texture.activate(); texture.activate();
} }
} }
public static Material colored(float r, float g, float b, float alpha) { public static Material colored(float r, float g, float b, float alpha) {
return new Material(null, r, g, b, alpha); return new Material(null, r, g, b, alpha);
} }
public static Material textured(Texture texture) { public static Material textured(Texture texture) {
return new Material(texture, 1, 1, 1, 1); return new Material(texture, 1, 1, 1, 1);
} }
public static Material textured(Texture texture, float r, float g, float b, float alpha) { public static Material textured(Texture texture, float r, float g, float b, float alpha) {
return new Material(texture, r, g, b, alpha); return new Material(texture, r, g, b, alpha);
} }
public static Material textured(Texture texture, float alpha) { public static Material textured(Texture texture, float alpha) {
return new Material(texture, 1, 1, 1, alpha); return new Material(texture, 1, 1, 1, alpha);
} }
} }

View File

@@ -1,94 +1,94 @@
package com.bartlomiejpluta.base.core.gl.object.mesh; package com.bartlomiejpluta.base.core.gl.object.mesh;
import com.bartlomiejpluta.base.core.gc.Disposable; import com.bartlomiejpluta.base.core.gc.Disposable;
import com.bartlomiejpluta.base.core.gl.render.Renderable; import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager; import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window; import com.bartlomiejpluta.base.core.ui.Window;
import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL15;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*; import static org.lwjgl.opengl.GL30.*;
public class Mesh implements Renderable, Disposable { public class Mesh implements Renderable, Disposable {
private final int vaoId; private final int vaoId;
private final List<Integer> vboIds = new ArrayList<>(2); private final List<Integer> vboIds = new ArrayList<>(2);
private final int elementsCount; private final int elementsCount;
public Mesh(float[] vertices, float[] texCoords, int[] elements) { public Mesh(float[] vertices, float[] texCoords, int[] elements) {
try(var stack = MemoryStack.stackPush()) { try(var stack = MemoryStack.stackPush()) {
elementsCount = elements.length; elementsCount = elements.length;
var verticesBuffer = stack.mallocFloat(vertices.length); var verticesBuffer = stack.mallocFloat(vertices.length);
var texCoordsBuffer = stack.mallocFloat(texCoords.length); var texCoordsBuffer = stack.mallocFloat(texCoords.length);
var elementsBuffer = stack.mallocInt(elementsCount); var elementsBuffer = stack.mallocInt(elementsCount);
verticesBuffer.put(vertices).flip(); verticesBuffer.put(vertices).flip();
texCoordsBuffer.put(texCoords).flip(); texCoordsBuffer.put(texCoords).flip();
elementsBuffer.put(elements).flip(); elementsBuffer.put(elements).flip();
vaoId = glGenVertexArrays(); vaoId = glGenVertexArrays();
glBindVertexArray(vaoId); glBindVertexArray(vaoId);
int vboId = glGenBuffers(); int vboId = glGenBuffers();
vboIds.add(vboId); vboIds.add(vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0); glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
vboId = glGenBuffers(); vboId = glGenBuffers();
vboIds.add(vboId); vboIds.add(vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, texCoordsBuffer, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, texCoordsBuffer, GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0); glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
vboId = glGenBuffers(); vboId = glGenBuffers();
vboIds.add(vboId); vboIds.add(vboId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementsBuffer, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementsBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0); glBindVertexArray(0);
} }
} }
@Override @Override
public void render(Window window, ShaderManager shaderManager) { public void render(Window window, ShaderManager shaderManager) {
glBindVertexArray(vaoId); glBindVertexArray(vaoId);
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1); glEnableVertexAttribArray(1);
glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0); glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1); glDisableVertexAttribArray(1);
glBindVertexArray(0); glBindVertexArray(0);
} }
@Override @Override
public void dispose() { public void dispose() {
glDisableVertexAttribArray(0); glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
vboIds.forEach(GL15::glDeleteBuffers); vboIds.forEach(GL15::glDeleteBuffers);
glBindVertexArray(0); glBindVertexArray(0);
glDeleteVertexArrays(vaoId); glDeleteVertexArrays(vaoId);
} }
public static Mesh quad(float width, float height, float originX, float originY) { public static Mesh quad(float width, float height, float originX, float originY) {
var vertices = new float[] { var vertices = new float[] {
-originX, -originY, -originX, -originY,
-originX, height - originY, -originX, height - originY,
width - originX, height - originY, width - originX, height - originY,
width - originX, - originY width - originX, - originY
}; };
var texCoords = new float[] { 0, 0, 0, 1, 1, 1, 1, 0 }; var texCoords = new float[] { 0, 0, 0, 1, 1, 1, 1, 0 };
var elements = new int[] { 0, 1, 2, 2, 3, 0 }; var elements = new int[] { 0, 1, 2, 2, 3, 0 };
return new Mesh(vertices, texCoords, elements); return new Mesh(vertices, texCoords, elements);
} }
} }

View File

@@ -1,39 +1,39 @@
package com.bartlomiejpluta.base.core.gl.object.texture; package com.bartlomiejpluta.base.core.gl.object.texture;
import com.bartlomiejpluta.base.core.util.res.ResourcesManager; import com.bartlomiejpluta.base.core.util.res.ResourcesManager;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultTextureManager implements TextureManager { public class DefaultTextureManager implements TextureManager {
private final ResourcesManager resourcesManager; private final ResourcesManager resourcesManager;
private final Map<String, Texture> loadedTextures = new HashMap<>(); private final Map<String, Texture> loadedTextures = new HashMap<>();
@Override @Override
public Texture loadTexture(String textureFileName) { public Texture loadTexture(String textureFileName) {
var texture = loadedTextures.get(textureFileName); var texture = loadedTextures.get(textureFileName);
if (texture == null) { if (texture == null) {
log.info("Loading [{}] texture to cache", textureFileName); log.info("Loading [{}] texture to cache", textureFileName);
var buffer = resourcesManager.loadResourceAsByteBuffer(textureFileName); var buffer = resourcesManager.loadResourceAsByteBuffer(textureFileName);
texture = new Texture(textureFileName, buffer); texture = new Texture(textureFileName, buffer);
loadedTextures.put(textureFileName, texture); loadedTextures.put(textureFileName, texture);
} }
return texture; return texture;
} }
@Override @Override
public void cleanUp() { public void cleanUp() {
log.info("Disposing textures"); log.info("Disposing textures");
loadedTextures.forEach((name, texture) -> texture.dispose()); loadedTextures.forEach((name, texture) -> texture.dispose());
log.info("{} textures has been disposed", loadedTextures.size()); log.info("{} textures has been disposed", loadedTextures.size());
} }
} }

View File

@@ -1,66 +1,66 @@
package com.bartlomiejpluta.base.core.gl.object.texture; package com.bartlomiejpluta.base.core.gl.object.texture;
import com.bartlomiejpluta.base.core.error.AppException; import com.bartlomiejpluta.base.core.error.AppException;
import com.bartlomiejpluta.base.core.gc.Disposable; import com.bartlomiejpluta.base.core.gc.Disposable;
import lombok.Getter; import lombok.Getter;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0; import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture; import static org.lwjgl.opengl.GL13.glActiveTexture;
import static org.lwjgl.stb.STBImage.stbi_failure_reason; import static org.lwjgl.stb.STBImage.stbi_failure_reason;
import static org.lwjgl.stb.STBImage.stbi_load_from_memory; import static org.lwjgl.stb.STBImage.stbi_load_from_memory;
public class Texture implements Disposable { public class Texture implements Disposable {
private static final int DESIRED_CHANNELS = 4; private static final int DESIRED_CHANNELS = 4;
private final int textureId; private final int textureId;
@Getter @Getter
private final String fileName; private final String fileName;
@Getter @Getter
private final int width; private final int width;
@Getter @Getter
private final int height; private final int height;
Texture(String textureFilename, ByteBuffer buffer) { Texture(String textureFilename, ByteBuffer buffer) {
try (var stack = MemoryStack.stackPush()) { try (var stack = MemoryStack.stackPush()) {
var widthBuffer = stack.mallocInt(1); var widthBuffer = stack.mallocInt(1);
var heightBuffer = stack.mallocInt(1); var heightBuffer = stack.mallocInt(1);
var channelsBuffer = stack.mallocInt(1); var channelsBuffer = stack.mallocInt(1);
buffer = stbi_load_from_memory(buffer, widthBuffer, heightBuffer, channelsBuffer, DESIRED_CHANNELS); buffer = stbi_load_from_memory(buffer, widthBuffer, heightBuffer, channelsBuffer, DESIRED_CHANNELS);
if (buffer == null) { if (buffer == null) {
throw new AppException("Image file [%s] could not be loaded: %s", textureFilename, stbi_failure_reason()); throw new AppException("Image file [%s] could not be loaded: %s", textureFilename, stbi_failure_reason());
} }
width = widthBuffer.get(); width = widthBuffer.get();
height = heightBuffer.get(); height = heightBuffer.get();
textureId = glGenTextures(); textureId = glGenTextures();
glBindTexture(GL_TEXTURE_2D, textureId); glBindTexture(GL_TEXTURE_2D, textureId);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
fileName = textureFilename; fileName = textureFilename;
} }
} }
public void activate() { public void activate() {
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId); glBindTexture(GL_TEXTURE_2D, textureId);
} }
@Override @Override
public void dispose() { public void dispose() {
glDeleteTextures(textureId); glDeleteTextures(textureId);
} }
} }

View File

@@ -1,7 +1,7 @@
package com.bartlomiejpluta.base.core.gl.object.texture; package com.bartlomiejpluta.base.core.gl.object.texture;
import com.bartlomiejpluta.base.core.gc.Cleanable; import com.bartlomiejpluta.base.core.gc.Cleanable;
public interface TextureManager extends Cleanable { public interface TextureManager extends Cleanable {
Texture loadTexture(String textureFileName); Texture loadTexture(String textureFileName);
} }

View File

@@ -1,62 +1,62 @@
package com.bartlomiejpluta.base.core.gl.render; package com.bartlomiejpluta.base.core.gl.render;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName; import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager; import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window; import com.bartlomiejpluta.base.core.ui.Window;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL15.*;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultRenderer implements Renderer { public class DefaultRenderer implements Renderer {
private final ShaderManager shaderManager; private final ShaderManager shaderManager;
@Override @Override
public void init() { public void init() {
log.info("Initializing renderer"); log.info("Initializing renderer");
shaderManager shaderManager
.createShader("default", "/shaders/default.vs", "/shaders/default.fs") .createShader("default", "/shaders/default.vs", "/shaders/default.fs")
.selectShader("default") .selectShader("default")
.createUniform(UniformName.UNI_MODEL_MATRIX) .createUniform(UniformName.UNI_MODEL_MATRIX)
.createUniform(UniformName.UNI_VIEW_MATRIX) .createUniform(UniformName.UNI_VIEW_MATRIX)
.createUniform(UniformName.UNI_PROJECTION_MATRIX) .createUniform(UniformName.UNI_PROJECTION_MATRIX)
.createUniform(UniformName.UNI_OBJECT_COLOR) .createUniform(UniformName.UNI_OBJECT_COLOR)
.createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE) .createUniform(UniformName.UNI_HAS_OBJECT_TEXTURE)
.createUniform(UniformName.UNI_TEXTURE_SAMPLER) .createUniform(UniformName.UNI_TEXTURE_SAMPLER)
.createUniform(UniformName.UNI_SPRITE_SIZE) .createUniform(UniformName.UNI_SPRITE_SIZE)
.createUniform(UniformName.UNI_SPRITE_POSITION); .createUniform(UniformName.UNI_SPRITE_POSITION);
} }
@Override @Override
public void render(Window window, Renderable renderable) { public void render(Window window, Renderable renderable) {
clear(); clear();
updateViewport(window); updateViewport(window);
shaderManager.selectShader("default").useSelectedShader(); shaderManager.selectShader("default").useSelectedShader();
renderable.render(window, shaderManager); renderable.render(window, shaderManager);
shaderManager.detachCurrentShader(); shaderManager.detachCurrentShader();
} }
private void clear() { private void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
} }
private void updateViewport(Window window) { private void updateViewport(Window window) {
if (window.isResized()) { if (window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight()); glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false); window.setResized(false);
} }
} }
@Override @Override
public void cleanUp() { public void cleanUp() {
log.info("There is nothing to clean up here"); log.info("There is nothing to clean up here");
} }
} }

View File

@@ -1,8 +1,8 @@
package com.bartlomiejpluta.base.core.gl.render; package com.bartlomiejpluta.base.core.gl.render;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager; import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window; import com.bartlomiejpluta.base.core.ui.Window;
public interface Renderable { public interface Renderable {
void render(Window window, ShaderManager shaderManager); void render(Window window, ShaderManager shaderManager);
} }

View File

@@ -1,9 +1,9 @@
package com.bartlomiejpluta.base.core.gl.render; package com.bartlomiejpluta.base.core.gl.render;
import com.bartlomiejpluta.base.core.gc.Cleanable; import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.core.ui.Window; import com.bartlomiejpluta.base.core.ui.Window;
public interface Renderer extends Cleanable { public interface Renderer extends Cleanable {
void init(); void init();
void render(Window window, Renderable renderable); void render(Window window, Renderable renderable);
} }

View File

@@ -1,12 +1,12 @@
package com.bartlomiejpluta.base.core.gl.shader.constant; package com.bartlomiejpluta.base.core.gl.shader.constant;
public interface UniformName { public interface UniformName {
String UNI_MODEL_MATRIX = "modelMatrix"; String UNI_MODEL_MATRIX = "modelMatrix";
String UNI_VIEW_MATRIX = "viewMatrix"; String UNI_VIEW_MATRIX = "viewMatrix";
String UNI_PROJECTION_MATRIX = "projectionMatrix"; String UNI_PROJECTION_MATRIX = "projectionMatrix";
String UNI_OBJECT_COLOR = "objectColor"; String UNI_OBJECT_COLOR = "objectColor";
String UNI_HAS_OBJECT_TEXTURE = "hasTexture"; String UNI_HAS_OBJECT_TEXTURE = "hasTexture";
String UNI_TEXTURE_SAMPLER = "sampler"; String UNI_TEXTURE_SAMPLER = "sampler";
String UNI_SPRITE_SIZE = "spriteSize"; String UNI_SPRITE_SIZE = "spriteSize";
String UNI_SPRITE_POSITION = "spritePosition"; String UNI_SPRITE_POSITION = "spritePosition";
} }

View File

@@ -1,149 +1,149 @@
package com.bartlomiejpluta.base.core.gl.shader.manager; package com.bartlomiejpluta.base.core.gl.shader.manager;
import com.bartlomiejpluta.base.core.gl.shader.program.ShaderProgram; import com.bartlomiejpluta.base.core.gl.shader.program.ShaderProgram;
import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform; import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform;
import com.bartlomiejpluta.base.core.util.res.ResourcesManager; import com.bartlomiejpluta.base.core.util.res.ResourcesManager;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.joml.*; import org.joml.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultShaderManager implements ShaderManager { public class DefaultShaderManager implements ShaderManager {
private final ResourcesManager resourcesManager; private final ResourcesManager resourcesManager;
private final Map<String, ShaderProgram> shaders = new HashMap<>(); private final Map<String, ShaderProgram> shaders = new HashMap<>();
private ShaderProgram current; private ShaderProgram current;
@Override @Override
public ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename) { public ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename) {
log.info("Creating {} shader", programName); log.info("Creating {} shader", programName);
var vertexShaderCode = resourcesManager.loadResourceAsString(vertexShaderFilename); var vertexShaderCode = resourcesManager.loadResourceAsString(vertexShaderFilename);
var fragmentShaderCode = resourcesManager.loadResourceAsString(fragmentShaderFilename); var fragmentShaderCode = resourcesManager.loadResourceAsString(fragmentShaderFilename);
var program = ShaderProgram.compile(vertexShaderCode, fragmentShaderCode); var program = ShaderProgram.compile(vertexShaderCode, fragmentShaderCode);
shaders.put(programName, program); shaders.put(programName, program);
return this; return this;
} }
@Override @Override
public ShaderManager selectShader(String programName) { public ShaderManager selectShader(String programName) {
current = shaders.get(programName); current = shaders.get(programName);
return this; return this;
} }
@Override @Override
public ShaderManager useSelectedShader() { public ShaderManager useSelectedShader() {
current.use(); current.use();
return this; return this;
} }
@Override @Override
public ShaderManager detachCurrentShader() { public ShaderManager detachCurrentShader() {
current.detach(); current.detach();
return this; return this;
} }
@Override @Override
public ShaderManager createUniform(String uniformName) { public ShaderManager createUniform(String uniformName) {
current.createUniform(uniformName); current.createUniform(uniformName);
return this; return this;
} }
@Override @Override
public ShaderManager createUniform(String uniformName, Uniform uniform) { public ShaderManager createUniform(String uniformName, Uniform uniform) {
current.createUniform(uniformName, uniform); current.createUniform(uniformName, uniform);
return this; return this;
} }
@Override @Override
public ShaderManager createUniforms(String uniformName, int size) { public ShaderManager createUniforms(String uniformName, int size) {
current.createUniforms(uniformName, size); current.createUniforms(uniformName, size);
return this; return this;
} }
@Override @Override
public ShaderManager createUniforms(String uniformName, int size, Uniform uniform) { public ShaderManager createUniforms(String uniformName, int size, Uniform uniform) {
current.createUniforms(uniformName, size, uniform); current.createUniforms(uniformName, size, uniform);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, int value) { public ShaderManager setUniform(String uniformName, int value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, boolean value) { public ShaderManager setUniform(String uniformName, boolean value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, float value) { public ShaderManager setUniform(String uniformName, float value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, Vector2f value) { public ShaderManager setUniform(String uniformName, Vector2f value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, Vector3f value) { public ShaderManager setUniform(String uniformName, Vector3f value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, Vector4f value) { public ShaderManager setUniform(String uniformName, Vector4f value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, Matrix3f value) { public ShaderManager setUniform(String uniformName, Matrix3f value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, Matrix4f value) { public ShaderManager setUniform(String uniformName, Matrix4f value) {
current.setUniform(uniformName, value); current.setUniform(uniformName, value);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, Uniform uniform) { public ShaderManager setUniform(String uniformName, Uniform uniform) {
current.setUniform(uniformName, uniform); current.setUniform(uniformName, uniform);
return this; return this;
} }
@Override @Override
public ShaderManager setUniform(String uniformName, int index, Uniform uniform) { public ShaderManager setUniform(String uniformName, int index, Uniform uniform) {
current.setUniform(uniformName, index, uniform); current.setUniform(uniformName, index, uniform);
return this; return this;
} }
@Override @Override
public ShaderManager setUniforms(String uniformName, Uniform[] uniforms) { public ShaderManager setUniforms(String uniformName, Uniform[] uniforms) {
current.setUniforms(uniformName, uniforms); current.setUniforms(uniformName, uniforms);
return this; return this;
} }
@Override @Override
public void cleanUp() { public void cleanUp() {
log.info("Disposing shaders"); log.info("Disposing shaders");
shaders.forEach((name, program) -> program.dispose()); shaders.forEach((name, program) -> program.dispose());
log.info("{} shaders has been disposed", shaders.size()); log.info("{} shaders has been disposed", shaders.size());
} }
} }

View File

@@ -1,45 +1,45 @@
package com.bartlomiejpluta.base.core.gl.shader.manager; package com.bartlomiejpluta.base.core.gl.shader.manager;
import com.bartlomiejpluta.base.core.gc.Cleanable; import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform; import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform;
import org.joml.*; import org.joml.*;
public interface ShaderManager extends Cleanable { public interface ShaderManager extends Cleanable {
ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename); ShaderManager createShader(String programName, String vertexShaderFilename, String fragmentShaderFilename);
ShaderManager selectShader(String programName); ShaderManager selectShader(String programName);
ShaderManager useSelectedShader(); ShaderManager useSelectedShader();
ShaderManager detachCurrentShader(); ShaderManager detachCurrentShader();
ShaderManager createUniform(String uniformName); ShaderManager createUniform(String uniformName);
ShaderManager createUniform(String uniformName, Uniform uniform); ShaderManager createUniform(String uniformName, Uniform uniform);
ShaderManager createUniforms(String uniformName, int size); ShaderManager createUniforms(String uniformName, int size);
ShaderManager createUniforms(String uniformName, int size, Uniform uniform); ShaderManager createUniforms(String uniformName, int size, Uniform uniform);
ShaderManager setUniform(String uniformName, int value); ShaderManager setUniform(String uniformName, int value);
ShaderManager setUniform(String uniformName, boolean value); ShaderManager setUniform(String uniformName, boolean value);
ShaderManager setUniform(String uniformName, float value); ShaderManager setUniform(String uniformName, float value);
ShaderManager setUniform(String uniformName, Vector2f value); ShaderManager setUniform(String uniformName, Vector2f value);
ShaderManager setUniform(String uniformName, Vector3f value); ShaderManager setUniform(String uniformName, Vector3f value);
ShaderManager setUniform(String uniformName, Vector4f value); ShaderManager setUniform(String uniformName, Vector4f value);
ShaderManager setUniform(String uniformName, Matrix3f value); ShaderManager setUniform(String uniformName, Matrix3f value);
ShaderManager setUniform(String uniformName, Matrix4f value); ShaderManager setUniform(String uniformName, Matrix4f value);
ShaderManager setUniform(String uniformName, Uniform uniform); ShaderManager setUniform(String uniformName, Uniform uniform);
ShaderManager setUniform(String uniformName, int index, Uniform uniform); ShaderManager setUniform(String uniformName, int index, Uniform uniform);
ShaderManager setUniforms(String uniformName, Uniform[] uniforms); ShaderManager setUniforms(String uniformName, Uniform[] uniforms);
} }

View File

@@ -1,175 +1,175 @@
package com.bartlomiejpluta.base.core.gl.shader.program; package com.bartlomiejpluta.base.core.gl.shader.program;
import com.bartlomiejpluta.base.core.error.AppException; import com.bartlomiejpluta.base.core.error.AppException;
import com.bartlomiejpluta.base.core.gc.Disposable; import com.bartlomiejpluta.base.core.gc.Disposable;
import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform; import com.bartlomiejpluta.base.core.gl.shader.uniform.Uniform;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.joml.*; import org.joml.*;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static java.lang.String.format; import static java.lang.String.format;
import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL20.*;
@Slf4j @Slf4j
public class ShaderProgram implements Disposable { public class ShaderProgram implements Disposable {
private final int programId; private final int programId;
private final int vertexShaderId; private final int vertexShaderId;
private final int fragmentShaderId; private final int fragmentShaderId;
private final Map<String, Integer> uniforms = new HashMap<>(); private final Map<String, Integer> uniforms = new HashMap<>();
private ShaderProgram(String vertexShaderCode, String fragmentShaderCode) { private ShaderProgram(String vertexShaderCode, String fragmentShaderCode) {
this.programId = glCreateProgram(); this.programId = glCreateProgram();
if(this.programId == 0) { if(this.programId == 0) {
throw new AppException("Could not create shader program"); throw new AppException("Could not create shader program");
} }
this.vertexShaderId = createShader(vertexShaderCode, GL_VERTEX_SHADER); this.vertexShaderId = createShader(vertexShaderCode, GL_VERTEX_SHADER);
this.fragmentShaderId = createShader(fragmentShaderCode, GL_FRAGMENT_SHADER); this.fragmentShaderId = createShader(fragmentShaderCode, GL_FRAGMENT_SHADER);
linkProgram(); linkProgram();
} }
private int createShader(String shaderCode, int shaderType) { private int createShader(String shaderCode, int shaderType) {
int shaderId = glCreateShader(shaderType); int shaderId = glCreateShader(shaderType);
if(shaderId == 0) { if(shaderId == 0) {
throw new AppException("Could not create shader of type: %s", shaderType); throw new AppException("Could not create shader of type: %s", shaderType);
} }
glShaderSource(shaderId, shaderCode); glShaderSource(shaderId, shaderCode);
glCompileShader(shaderId); glCompileShader(shaderId);
if(glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) { if(glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) {
throw new AppException("Could not compile shader code: %s", glGetShaderInfoLog(shaderId, 1024)); throw new AppException("Could not compile shader code: %s", glGetShaderInfoLog(shaderId, 1024));
} }
glAttachShader(programId, shaderId); glAttachShader(programId, shaderId);
return shaderId; return shaderId;
} }
private void linkProgram() { private void linkProgram() {
glLinkProgram(programId); glLinkProgram(programId);
if(glGetProgrami(programId, GL_LINK_STATUS) == 0) { if(glGetProgrami(programId, GL_LINK_STATUS) == 0) {
throw new AppException("Could not link shader program: %s", glGetProgramInfoLog(programId, 1024)); throw new AppException("Could not link shader program: %s", glGetProgramInfoLog(programId, 1024));
} }
if(vertexShaderId != 0) { if(vertexShaderId != 0) {
glDetachShader(programId, vertexShaderId); glDetachShader(programId, vertexShaderId);
} }
if(fragmentShaderId != 0) { if(fragmentShaderId != 0) {
glDetachShader(programId, fragmentShaderId); glDetachShader(programId, fragmentShaderId);
} }
glValidateProgram(programId); glValidateProgram(programId);
if(glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) { if(glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) {
log.warn("Program validation failed: {}", glGetProgramInfoLog(programId, 1024)); log.warn("Program validation failed: {}", glGetProgramInfoLog(programId, 1024));
} }
} }
public void createUniform(String uniformName) { public void createUniform(String uniformName) {
int location = glGetUniformLocation(programId, uniformName); int location = glGetUniformLocation(programId, uniformName);
if(location < 0) { if(location < 0) {
throw new AppException("Could not find uniform: %s", uniformName); throw new AppException("Could not find uniform: %s", uniformName);
} }
uniforms.put(uniformName, location); uniforms.put(uniformName, location);
} }
public void createUniform(String uniformName, Uniform uniform) { public void createUniform(String uniformName, Uniform uniform) {
uniform.createUniform(this, uniformName); uniform.createUniform(this, uniformName);
} }
public void createUniforms(String uniformName, int size) { public void createUniforms(String uniformName, int size) {
for(int i=0; i<size; ++i) { for(int i=0; i<size; ++i) {
createUniform(format("%s[%d]", uniformName, i)); createUniform(format("%s[%d]", uniformName, i));
} }
} }
public void createUniforms(String uniformName, int size, Uniform uniform) { public void createUniforms(String uniformName, int size, Uniform uniform) {
for(int i=0; i<size; ++i) { for(int i=0; i<size; ++i) {
createUniform(format("%s[%d]", uniformName, i), uniform); createUniform(format("%s[%d]", uniformName, i), uniform);
} }
} }
public void setUniform(String uniformName, int value) { public void setUniform(String uniformName, int value) {
glUniform1i(uniforms.get(uniformName), value); glUniform1i(uniforms.get(uniformName), value);
} }
public void setUniform(String uniformName, boolean value) { public void setUniform(String uniformName, boolean value) {
glUniform1i(uniforms.get(uniformName), value ? 1 : 0); glUniform1i(uniforms.get(uniformName), value ? 1 : 0);
} }
public void setUniform(String uniformName, float value) { public void setUniform(String uniformName, float value) {
glUniform1f(uniforms.get(uniformName), value); glUniform1f(uniforms.get(uniformName), value);
} }
public void setUniform(String uniformName, Vector2f value) { public void setUniform(String uniformName, Vector2f value) {
glUniform2f(uniforms.get(uniformName), value.x, value.y); glUniform2f(uniforms.get(uniformName), value.x, value.y);
} }
public void setUniform(String uniformName, Vector3f value) { public void setUniform(String uniformName, Vector3f value) {
glUniform3f(uniforms.get(uniformName), value.x, value.y, value.z); glUniform3f(uniforms.get(uniformName), value.x, value.y, value.z);
} }
public void setUniform(String uniformName, Vector4f value) { public void setUniform(String uniformName, Vector4f value) {
glUniform4f(uniforms.get(uniformName), value.x, value.y, value.z, value.w); glUniform4f(uniforms.get(uniformName), value.x, value.y, value.z, value.w);
} }
public void setUniform(String uniformName, Matrix3f value) { public void setUniform(String uniformName, Matrix3f value) {
try(var stack = MemoryStack.stackPush()) { try(var stack = MemoryStack.stackPush()) {
var buffer = stack.mallocFloat(3 * 3); var buffer = stack.mallocFloat(3 * 3);
value.get(buffer); value.get(buffer);
glUniformMatrix4fv(uniforms.get(uniformName), false, buffer); glUniformMatrix4fv(uniforms.get(uniformName), false, buffer);
} }
} }
public void setUniform(String uniformName, Matrix4f value) { public void setUniform(String uniformName, Matrix4f value) {
try(var stack = MemoryStack.stackPush()) { try(var stack = MemoryStack.stackPush()) {
var buffer = stack.mallocFloat(4 * 4); var buffer = stack.mallocFloat(4 * 4);
value.get(buffer); value.get(buffer);
glUniformMatrix4fv(uniforms.get(uniformName), false, buffer); glUniformMatrix4fv(uniforms.get(uniformName), false, buffer);
} }
} }
public void setUniform(String uniformName, Uniform uniform) { public void setUniform(String uniformName, Uniform uniform) {
uniform.setUniform(this, uniformName); uniform.setUniform(this, uniformName);
} }
public void setUniform(String uniformName, int index, Uniform uniform) { public void setUniform(String uniformName, int index, Uniform uniform) {
setUniform(format("%s[%d]", uniformName, index), uniform); setUniform(format("%s[%d]", uniformName, index), uniform);
} }
public void setUniforms(String uniformName, Uniform[] uniforms) { public void setUniforms(String uniformName, Uniform[] uniforms) {
var size = uniforms != null ? uniforms.length : 0; var size = uniforms != null ? uniforms.length : 0;
for(int i=0; i<size; ++i) { for(int i=0; i<size; ++i) {
setUniform(format("%s[%d]", uniformName, i), uniforms[i]); setUniform(format("%s[%d]", uniformName, i), uniforms[i]);
} }
} }
public void use() { public void use() {
glUseProgram(programId); glUseProgram(programId);
} }
public void detach() { public void detach() {
glUseProgram(0); glUseProgram(0);
} }
@Override @Override
public void dispose() { public void dispose() {
glUseProgram(0); glUseProgram(0);
if(programId != 0) { if(programId != 0) {
glDeleteProgram(programId); glDeleteProgram(programId);
} }
} }
public static ShaderProgram compile(String vertexShaderCode, String fragmentShaderCode) { public static ShaderProgram compile(String vertexShaderCode, String fragmentShaderCode) {
return new ShaderProgram(vertexShaderCode, fragmentShaderCode); return new ShaderProgram(vertexShaderCode, fragmentShaderCode);
} }
} }

View File

@@ -1,9 +1,9 @@
package com.bartlomiejpluta.base.core.gl.shader.uniform; package com.bartlomiejpluta.base.core.gl.shader.uniform;
import com.bartlomiejpluta.base.core.gl.shader.program.ShaderProgram; import com.bartlomiejpluta.base.core.gl.shader.program.ShaderProgram;
public interface Uniform { public interface Uniform {
void createUniform(ShaderProgram shaderProgram, String uniformName); void createUniform(ShaderProgram shaderProgram, String uniformName);
void setUniform(ShaderProgram shaderProgram, String uniformName); void setUniform(ShaderProgram shaderProgram, String uniformName);
} }

View File

@@ -1,39 +1,39 @@
package com.bartlomiejpluta.base.core.image; package com.bartlomiejpluta.base.core.image;
import com.bartlomiejpluta.base.core.gl.object.material.Material; import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.texture.TextureManager; import com.bartlomiejpluta.base.core.gl.object.texture.TextureManager;
import com.bartlomiejpluta.base.core.util.math.MathUtil; import com.bartlomiejpluta.base.core.util.math.MathUtil;
import com.bartlomiejpluta.base.core.util.mesh.MeshManager; import com.bartlomiejpluta.base.core.util.mesh.MeshManager;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) @RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultImageManager implements ImageManager { public class DefaultImageManager implements ImageManager {
private final MeshManager meshManager; private final MeshManager meshManager;
private final TextureManager textureManager; private final TextureManager textureManager;
@Override @Override
public Image createImage(String imageFileName) { public Image createImage(String imageFileName) {
var texture = textureManager.loadTexture(imageFileName); var texture = textureManager.loadTexture(imageFileName);
var width = texture.getWidth(); var width = texture.getWidth();
var height = texture.getHeight(); var height = texture.getHeight();
var gcd = MathUtil.gcdEuclidean(width, height); var gcd = MathUtil.gcdEuclidean(width, height);
var initialWidth = width / gcd; var initialWidth = width / gcd;
var initialHeight = height / gcd; var initialHeight = height / gcd;
var mesh = meshManager.createQuad(initialWidth, initialHeight, 0, 0); var mesh = meshManager.createQuad(initialWidth, initialHeight, 0, 0);
var image = new Image(mesh, Material.textured(texture), initialWidth, initialHeight); var image = new Image(mesh, Material.textured(texture), initialWidth, initialHeight);
image.setScale(gcd); image.setScale(gcd);
return image; return image;
} }
@Override @Override
public void cleanUp() { public void cleanUp() {
log.info("There is nothing to clean up here"); log.info("There is nothing to clean up here");
} }
} }

View File

@@ -1,19 +1,19 @@
package com.bartlomiejpluta.base.core.image; package com.bartlomiejpluta.base.core.image;
import com.bartlomiejpluta.base.core.gl.object.material.Material; import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh; import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.world.object.RenderableObject; import com.bartlomiejpluta.base.core.world.object.RenderableObject;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class Image extends RenderableObject { public class Image extends RenderableObject {
private final int initialWidth; private final int initialWidth;
private final int initialHeight; private final int initialHeight;
Image(Mesh mesh, Material texture, int initialWidth, int initialHeight) { Image(Mesh mesh, Material texture, int initialWidth, int initialHeight) {
super(mesh); super(mesh);
this.initialWidth = initialWidth; this.initialWidth = initialWidth;
this.initialHeight = initialHeight; this.initialHeight = initialHeight;
setMaterial(texture); setMaterial(texture);
} }
} }

View File

@@ -1,14 +1,14 @@
package com.bartlomiejpluta.base.core.logic; package com.bartlomiejpluta.base.core.logic;
import com.bartlomiejpluta.base.core.gc.Cleanable; import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.core.ui.Window; import com.bartlomiejpluta.base.core.ui.Window;
public interface GameLogic extends Cleanable { public interface GameLogic extends Cleanable {
void init(Window window); void init(Window window);
void input(Window window); void input(Window window);
void update(float dt); void update(float dt);
void render(Window window); void render(Window window);
} }

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.core.logic; package com.bartlomiejpluta.base.core.logic;
public interface Updatable { public interface Updatable {
void update(float dt); void update(float dt);
} }

View File

@@ -1,7 +1,7 @@
package com.bartlomiejpluta.base.core.profiling.fps; package com.bartlomiejpluta.base.core.profiling.fps;
import com.bartlomiejpluta.base.core.gc.Cleanable; import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.core.logic.Updatable; import com.bartlomiejpluta.base.core.logic.Updatable;
public interface FPSMonitor extends Updatable, Cleanable { public interface FPSMonitor extends Updatable, Cleanable {
} }

View File

@@ -1,63 +1,63 @@
package com.bartlomiejpluta.base.core.profiling.fps; package com.bartlomiejpluta.base.core.profiling.fps;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static java.util.Comparator.comparingInt; import static java.util.Comparator.comparingInt;
import static java.util.function.Function.identity; import static java.util.function.Function.identity;
import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingBy;
@Slf4j @Slf4j
@Component @Component
public class LogFPSMonitor implements FPSMonitor { public class LogFPSMonitor implements FPSMonitor {
private static final int MOD = 30; private static final int MOD = 30;
private final List<Double> values = new LinkedList<>(); private final List<Double> values = new LinkedList<>();
private float fpsAccumulator = 0; private float fpsAccumulator = 0;
private int pointer = 0; private int pointer = 0;
private double fps = 0; private double fps = 0;
@Override @Override
public void update(float dt) { public void update(float dt) {
fpsAccumulator += dt; fpsAccumulator += dt;
if (++pointer % MOD == 0) { if (++pointer % MOD == 0) {
fps = pointer / fpsAccumulator; fps = pointer / fpsAccumulator;
fpsAccumulator = 0; fpsAccumulator = 0;
pointer = 0; pointer = 0;
values.add(fps); values.add(fps);
} }
} }
@Override @Override
public void cleanUp() { public void cleanUp() {
log.info("Min FPS: {}, max FPS: {}, avg FPS: {}", log.info("Min FPS: {}, max FPS: {}, avg FPS: {}",
values.stream().min(Double::compareTo).orElse(-1.0), values.stream().min(Double::compareTo).orElse(-1.0),
values.stream().max(Double::compareTo).orElse(-1.0), values.stream().max(Double::compareTo).orElse(-1.0),
totalAverage() totalAverage()
); );
printHistogram(); printHistogram();
} }
private double totalAverage() { private double totalAverage() {
return values.stream().reduce(0.0, Double::sum) / values.size(); return values.stream().reduce(0.0, Double::sum) / values.size();
} }
private void printHistogram() { private void printHistogram() {
values values
.stream() .stream()
.mapToInt(Double::intValue) .mapToInt(Double::intValue)
.boxed() .boxed()
.collect(groupingBy(identity(), counting())) .collect(groupingBy(identity(), counting()))
.entrySet() .entrySet()
.stream() .stream()
.sorted(comparingInt(Map.Entry::getKey)) .sorted(comparingInt(Map.Entry::getKey))
.forEach(e -> log.info("{} FPS: {}% ({} occurrences)", e.getKey(), e.getValue() * 100.0f / values.size(), e.getValue())); .forEach(e -> log.info("{} FPS: {}% ({} occurrences)", e.getKey(), e.getValue() * 100.0f / values.size(), e.getValue()));
} }
} }

View File

@@ -1,11 +1,11 @@
package com.bartlomiejpluta.base.core.profiling.time.annotation; package com.bartlomiejpluta.base.core.profiling.time.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface MeasureExecutionTime { public @interface MeasureExecutionTime {
} }

View File

@@ -1,38 +1,38 @@
package com.bartlomiejpluta.base.core.profiling.time.aspect; package com.bartlomiejpluta.base.core.profiling.time.aspect;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j @Slf4j
@Aspect @Aspect
@Component @Component
@ConditionalOnExpression("${app.profiling.aspects:false}") @ConditionalOnExpression("${app.profiling.aspects:false}")
public class ExecutionTimeAspect { public class ExecutionTimeAspect {
@Around("@annotation(com.bartlomiejpluta.base.core.stat.metrics.annotation.MeasureExecutionTime)") @Around("@annotation(com.bartlomiejpluta.base.core.stat.metrics.annotation.MeasureExecutionTime)")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = 0; long start = 0;
long end = 0; long end = 0;
long elapsed; long elapsed;
Object result; Object result;
try { try {
start = System.nanoTime(); start = System.nanoTime();
result = joinPoint.proceed(); result = joinPoint.proceed();
} finally { } finally {
end = System.nanoTime(); end = System.nanoTime();
} }
elapsed = end - start; elapsed = end - start;
log.debug("[{}.{}] = [{}s] [{}ms] [{}ns]", log.debug("[{}.{}] = [{}s] [{}ms] [{}ns]",
joinPoint.getTarget().getClass().getSimpleName(), joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(), joinPoint.getSignature().getName(),
elapsed / 1000000.0, elapsed / 1000.0, elapsed elapsed / 1000000.0, elapsed / 1000.0, elapsed
); );
return result; return result;
} }
} }

View File

@@ -1,10 +1,10 @@
package com.bartlomiejpluta.base.core.thread; package com.bartlomiejpluta.base.core.thread;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class ThreadManager { public class ThreadManager {
public Thread createThread(String name, Runnable runnable) { public Thread createThread(String name, Runnable runnable) {
return new Thread(runnable, name); return new Thread(runnable, name);
} }
} }

View File

@@ -1,20 +1,20 @@
package com.bartlomiejpluta.base.core.time; package com.bartlomiejpluta.base.core.time;
public class ChronoMeter { public class ChronoMeter {
private double latchedTime; private double latchedTime;
public void init() { public void init() {
latchedTime = getTime(); latchedTime = getTime();
} }
private double getTime() { private double getTime() {
return System.nanoTime() / 1_000_000_000.0; return System.nanoTime() / 1_000_000_000.0;
} }
public float getElapsedTime() { public float getElapsedTime() {
double time = getTime(); double time = getTime();
float elapsedTime = (float) (time - latchedTime); float elapsedTime = (float) (time - latchedTime);
latchedTime = time; latchedTime = time;
return elapsedTime; return elapsedTime;
} }
} }

View File

@@ -1,119 +1,119 @@
package com.bartlomiejpluta.base.core.ui; package com.bartlomiejpluta.base.core.ui;
import com.bartlomiejpluta.base.core.error.AppException; import com.bartlomiejpluta.base.core.error.AppException;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.joml.Vector2f; import org.joml.Vector2f;
import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode; import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.NULL; import static org.lwjgl.system.MemoryUtil.NULL;
@AllArgsConstructor(access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Window { public class Window {
private final String title; private final String title;
private long windowHandle; private long windowHandle;
@Getter @Getter
private int width; private int width;
@Getter @Getter
private int height; private int height;
@Getter @Getter
@Setter @Setter
private boolean resized; private boolean resized;
public void init() { public void init() {
// Setup an error callback. The default implementation // Setup an error callback. The default implementation
// will print the error message in System.err. // will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set(); GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this. // Initialize GLFW. Most GLFW functions will not work before doing this.
if (!glfwInit()) { if (!glfwInit()) {
throw new AppException("Unable to initialize GLFW"); throw new AppException("Unable to initialize GLFW");
} }
glfwDefaultWindowHints(); // optional, the current window hints are already the default glfwDefaultWindowHints(); // optional, the current window hints are already the default
glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // the window will stay hidden after creation glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // the window will stay hidden after creation
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // the window will be resizable glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // the window will be resizable
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// Create the window // Create the window
windowHandle = glfwCreateWindow(width, height, title, NULL, NULL); windowHandle = glfwCreateWindow(width, height, title, NULL, NULL);
if (windowHandle == NULL) { if (windowHandle == NULL) {
throw new AppException("Failed to create the GLFW window"); throw new AppException("Failed to create the GLFW window");
} }
// Setup resize callback // Setup resize callback
glfwSetFramebufferSizeCallback(windowHandle, (window, width, height) -> { glfwSetFramebufferSizeCallback(windowHandle, (window, width, height) -> {
Window.this.width = width; Window.this.width = width;
Window.this.height = height; Window.this.height = height;
Window.this.resized = true; Window.this.resized = true;
}); });
// Setup a key callback. It will be called every time a key is pressed, repeated or released. // Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> { glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
} }
}); });
// Get the resolution of the primary monitor // Get the resolution of the primary monitor
GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Center our window // Center our window
glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2); glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2);
// Make the OpenGL context current // Make the OpenGL context current
glfwMakeContextCurrent(windowHandle); glfwMakeContextCurrent(windowHandle);
// Enable V-Sync // Enable V-Sync
// glfwSwapInterval(1); // glfwSwapInterval(1);
// Make the window visible // Make the window visible
glfwShowWindow(windowHandle); glfwShowWindow(windowHandle);
GL.createCapabilities(); GL.createCapabilities();
// Support for transparencies // Support for transparencies
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Set the clear color // Set the clear color
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
} }
public void update() { public void update() {
glfwSwapBuffers(windowHandle); glfwSwapBuffers(windowHandle);
glfwPollEvents(); glfwPollEvents();
} }
public boolean isKeyPressed(int keyCode) { public boolean isKeyPressed(int keyCode) {
return glfwGetKey(windowHandle, keyCode) == GLFW_PRESS; return glfwGetKey(windowHandle, keyCode) == GLFW_PRESS;
} }
public void clear(float r, float g, float b, float alpha) { public void clear(float r, float g, float b, float alpha) {
glClearColor(r, g, b, alpha); glClearColor(r, g, b, alpha);
} }
public boolean shouldClose() { public boolean shouldClose() {
return glfwWindowShouldClose(windowHandle); return glfwWindowShouldClose(windowHandle);
} }
public Vector2f getSize() { public Vector2f getSize() {
return new Vector2f(width, height); return new Vector2f(width, height);
} }
public static Window create(String title, int width, int height) { public static Window create(String title, int width, int height) {
return new Window(title, -1, width, height, false); return new Window(title, -1, width, height, false);
} }
} }

View File

@@ -1,10 +1,10 @@
package com.bartlomiejpluta.base.core.ui; package com.bartlomiejpluta.base.core.ui;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class WindowManager { public class WindowManager {
public Window createWindow(String title, int width, int height) { public Window createWindow(String title, int width, int height) {
return Window.create(title, width, height); return Window.create(title, width, height);
} }
} }

View File

@@ -1,32 +1,32 @@
package com.bartlomiejpluta.base.core.util.math; package com.bartlomiejpluta.base.core.util.math;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
public class MathUtil { public class MathUtil {
public static int gcdEuclidean(int a, int b) { public static int gcdEuclidean(int a, int b) {
int x = a; int x = a;
int y = b; int y = b;
int z; int z;
while(y != 0) { while(y != 0) {
z = x % y; z = x % y;
x = y; x = y;
y = z; y = z;
} }
return x; return x;
} }
public static int clamp(int value, int min, int max) { public static int clamp(int value, int min, int max) {
return min(max, max(value, min)); return min(max, max(value, min));
} }
public static float clamp(float value, float min, float max) { public static float clamp(float value, float min, float max) {
return min(max, max(value, min)); return min(max, max(value, min));
} }
public static double clamp(double value, double min, double max) { public static double clamp(double value, double min, double max) {
return min(max, max(value, min)); return min(max, max(value, min));
} }
} }

Some files were not shown because too many files have changed in this diff Show More