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/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
# The rest of project files should use lf
*.gradle text eol=lf
*.kt text eol=lf
*.yml text eol=lf
*.java text eol=lf
*.fs text eol=lf
*.vs text eol=lf
*.properties text eol=lf
*.proto text eol=lf
gradlew text eol=lf
# Binary files
*.jar binary
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
# The rest of project files should use lf
*.gradle text eol=lf
*.kt text eol=lf
*.yml text eol=lf
*.java text eol=lf
*.fs text eol=lf
*.vs text eol=lf
*.properties text eol=lf
*.proto text eol=lf
gradlew text eol=lf
# Binary files
*.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
# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij
### Intellij ###
# 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
# User-specific stuff
.idea/
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# 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
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
### Textures and other resources ###
*.png
# End of 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
### Intellij ###
# 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
# User-specific stuff
.idea/
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# 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
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
### Textures and other resources ###
*.png
# 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 {
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.springframework.boot' version "$springBootVersion"
id 'io.spring.dependency-management' version "$springDependencyManagementVersion"
id 'idea'
}
group 'com.bartlomiejpluta.base'
version 'unspecified'
repositories {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
sourceSets {
main.kotlin.srcDirs += 'src/main/kotlin'
}
javafx {
version = "11.0.2"
modules = ['javafx.controls', 'javafx.graphics']
}
compileKotlin {
kotlinOptions.jvmTarget = "14"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "14"
}
dependencies {
implementation project(":proto")
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation "no.tornado:tornadofx:${tornadoFxVersion}"
implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}")
implementation 'org.kordamp.ikonli:ikonli-javafx'
implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack'
// Spring
implementation 'org.springframework.boot:spring-boot-starter'
}
build {
dependsOn(":proto:build")
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
id 'org.openjfx.javafxplugin' version '0.0.8'
id 'org.springframework.boot' version "$springBootVersion"
id 'io.spring.dependency-management' version "$springDependencyManagementVersion"
id 'idea'
}
group 'com.bartlomiejpluta.base'
version 'unspecified'
repositories {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
sourceSets {
main.kotlin.srcDirs += 'src/main/kotlin'
}
javafx {
version = "11.0.2"
modules = ['javafx.controls', 'javafx.graphics']
}
compileKotlin {
kotlinOptions.jvmTarget = "14"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "14"
}
dependencies {
implementation project(":proto")
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation "no.tornado:tornadofx:${tornadoFxVersion}"
implementation platform("org.kordamp.ikonli:ikonli-bom:${ikonliVersion}")
implementation 'org.kordamp.ikonli:ikonli-javafx'
implementation 'org.kordamp.ikonli:ikonli-fontawesome-pack'
// Spring
implementation 'org.springframework.boot:spring-boot-starter'
}
build {
dependsOn(":proto:build")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,29 @@
package com.bartlomiejpluta.base.editor.map.viewmodel
import com.bartlomiejpluta.base.editor.map.model.brush.Brush
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.tileset.model.Tile
import tornadofx.ItemViewModel
import tornadofx.getValue
class BrushVM : ItemViewModel<Brush>(Brush.of(arrayOf(arrayOf()))) {
val brush = bind(Brush::brush)
val rowsProperty = bind(Brush::rowsProperty)
val rows by rowsProperty
val columnsProperty = bind(Brush::columnsProperty)
val columns by columnsProperty
val rangeProperty = bind(Brush::rangeProperty)
val range by rangeProperty
val modeProperty = bind(Brush::modeProperty)
val mode by modeProperty
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 withMode(mode: BrushMode) = item.withMode(mode)
package com.bartlomiejpluta.base.editor.map.viewmodel
import com.bartlomiejpluta.base.editor.map.model.brush.Brush
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.tileset.model.Tile
import tornadofx.ItemViewModel
import tornadofx.getValue
class BrushVM : ItemViewModel<Brush>(Brush.of(arrayOf(arrayOf()))) {
val brush = bind(Brush::brush)
val rowsProperty = bind(Brush::rowsProperty)
val rows by rowsProperty
val columnsProperty = bind(Brush::columnsProperty)
val columns by columnsProperty
val rangeProperty = bind(Brush::rangeProperty)
val range by rangeProperty
val modeProperty = bind(Brush::modeProperty)
val mode by modeProperty
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 withMode(mode: BrushMode) = item.withMode(mode)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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