diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..437394e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Introduction + +Thank you for considering contributing to Mechanica. + +This project is very new and it is just now being released as open source. +For that reason we don't have a full cohesive plan just yet. We mainly +just want to make sure everything is working the way we want it to. + +Saying that, there is still a lot of work to do going forward. +So if you do have an idea or a suggestion, or you found a bug, +feel free to create an issue in the issue tracker. + +# Filing Bug Reports + +When filing a bug report, make sure to include the following where necessary: + +1. What operating system are you using? +2. What version of Kotlin are you using? +3. What version of the JDK did you use to compile? +4. What did you do and can you recreate the bug consistently? +5. What did you expect to see or why do you think this is a bug? + +# Suggesting Features or Enhancements + +Because this is a very new project, there are a lot of +features and enhancements to be added. + +Right now there is not a solid plan in place for what features we +are looking for but if you have an idea feel free to create an issue +and make sure to describe the reasons, motivation and benefits of such +a feature \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ebedd82f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License (MIT) + +Copyright (c) 2020 Dominic Dolan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md index ff7ab329..25e66352 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,25 @@ ## Mechanica, a 2D Game Engine in Kotlin + A powerful 2D Game Engine written in Kotlin. This project is still under development and it is not -thoroughly tested or documented but feel free to try it out nonetheless +thoroughly tested or documented but feel free to try it out nonetheless. + +Get started coding games by checking out the [Wiki](https://github.com/DominicDolan/Mechanica/wiki) +or consider [Contributing](https://github.com/DominicDolan/Mechanica/blob/master/CONTRIBUTING.md) + +### Building from source with Gradle + +Clone or download the repository and build with gradle. + +Building it will require java 12 or later and note that later versions of Java require the latest version of Gradle. -### Setting up the project with Gradle -Clone or download the repository and build with gradle +Try and run one of the samples in the samples module to check that everything is working. -Create a new Project with gradle. In the `settings.gradle` file add: +To create a new project using this repository, create a new Gradle project and In the `settings.gradle.kts` file add: ```kotlin includeBuild("C:/path/to/mechanica") ``` -And in the `build.gradle` file add a dependency on the project: +And in the `build.gradle.kts` file add a dependency on the project: ```kotlin dependencies { implementation("com.mechanica.engine:mechanica:1.0") diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/context/Application.kt b/application-interface/src/main/kotlin/com/mechanica/engine/context/Application.kt index 4bafd922..4fce7fe5 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/context/Application.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/context/Application.kt @@ -4,10 +4,12 @@ import com.mechanica.engine.display.Window interface Application { - fun initialize(window: Window) + fun initialize(mainWindow: Window) fun terminate() fun startFrame() + fun activateContext(window: Window?) + } \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/DisplayLoader.kt b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/DisplayLoader.kt index 37ddd905..6e860b82 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/DisplayLoader.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/DisplayLoader.kt @@ -5,9 +5,9 @@ import com.mechanica.engine.display.Monitor import com.mechanica.engine.display.Window interface DisplayLoader { - fun createWindow(title: String, width: Int, height: Int): Window - fun createWindow(title: String, monitor: Monitor): Window - fun createWindow(title: String, width: Int, height: Int, monitor: Monitor): Window + fun createWindow(title: String, width: Int, height: Int, sharedWith: Window? = null): Window + fun createWindow(title: String, monitor: Monitor, sharedWith: Window? = null): Window + fun createWindow(title: String, width: Int, height: Int, monitor: Monitor, sharedWith: Window? = null): Window val allMonitors: Array diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/FontLoader.kt b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/FontLoader.kt index a9380c8e..f54dc76c 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/FontLoader.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/FontLoader.kt @@ -1,11 +1,10 @@ package com.mechanica.engine.context.loader -import com.mechanica.engine.text.Font import com.mechanica.engine.resources.Resource +import com.mechanica.engine.text.Font import com.mechanica.engine.text.FontAtlasConfiguration interface FontLoader { - val defaultFont: Font fun font(res: Resource, initializer: FontAtlasConfiguration.() -> Unit): Font } \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GLDrawerLoader.kt b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GLDrawerLoader.kt new file mode 100644 index 00000000..9afdacbb --- /dev/null +++ b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GLDrawerLoader.kt @@ -0,0 +1,135 @@ +package com.mechanica.engine.context.loader + +import com.mechanica.engine.models.Model +import java.nio.IntBuffer + +interface GLDrawerLoader { + /** + * Defines a sequence of geometric primitives using [model]'s + * elements, whose indices are stored in the buffer bound to the + * GL_ELEMENT_ARRAY_BUFFER buffer + */ + fun drawElements(model: Model) + + /** + * Behaves identically to glDrawElements() except that the ith element + * transferred by the corresponding draw command will be taken from + * element indices`[i]` + basevertex of each enabled vertex attribute array. + */ + @Suppress("KDocUnresolvedReference") + fun drawElementsBaseVertex(model: Model, baseVertex: Int) + + /** + * This is a restricted form of glDrawElements() in that it forms a contract + * between the application (i.e., you) and OpenGL that guarantees that any + * index contained in the section of the element array buffer referenced by + * indices and model.vertexCount will fall within the range specified by start and end. + */ + fun drawRangeElements(model: Model, start: Int, end: Int) + + /** + * Forms a contractual agreement between the application similar to that of + * [drawRangeElements], while allowing the base vertex to be specified in + * basevertex. In this case, the contract states that the values stored in the + * element array buffer will fall between start and end before basevertex is added. + */ + fun drawRangeElementsBaseVertex(model: Model, start: Int, end: Int, baseVertex: Int) + + /** + * Behaves exactly as [drawElements], except that the parameters for the + * drawing command are taken from a structure stored in the buffer bound + * to the GL_DRAW_INDIRECT_BUFFER binding point. + */ + fun drawElementsIndirect(model: Model) + + /** + * Draws multiple sets of geometric primitives with a single OpenGL + * function call. + */ + fun multiDrawElements(models: Array) + + /** + * Draws multiple sets of geometric primitives with a single OpenGL + * function call. first, indices, and baseVertex are arrays of primcount + * parameters that would be valid for a call to + * glDrawElementsBaseVertex(). + */ + fun multiDrawElementsBaseVertex(models: Array, baseVertex: IntBuffer) + + /** + * The same as [multiDrawElementsBaseVertex] but the same [baseVertex] is + * passed in for every primitive + */ + fun multiDrawElementsBaseVertex(models: Array, baseVertex: Int) + + /** + * Draws primCount instances of the geometric primitives specified by [model] + * as if specified by individual calls to [drawElements]. + * As with [drawArraysInstanced], the built-in variable gl_InstanceID + * is incremented for each instance, and new values are presented to the + * vertex shader for each instanced vertex attribute. + */ + fun drawElementsInstanced(model: Model, instanceCount: Int) + + /** + * Draws [instanceCount] instances of the geometric primitives specified by [model] + * and [baseVertex] as if specified by individual calls to + * [drawElementsBaseVertex]. As with [drawArraysInstanced], the + * built-in variable gl_InstanceID is incremented for each instance, and + * new values are presented to the vertex shader for each instanced vertex + * attribute. + */ + fun drawElementsInstancedBaseVertex(model: Model, instanceCount: Int, baseVertex: Int) + + /** + * Draws [instanceCount] instances of the geometric primitives specified by [model] + * as if specified by individual calls to [drawElements]. + * As with [drawArraysInstanced], the built-in variable gl_InstanceID + * is incremented for each instance, and new values are presented to the + * vertex shader for each instanced vertex attribute. Furthermore, the + * implied index used to fetch any instanced vertex attributes is offset by + * the value of [baseInstance] by OpenGL. + */ + fun drawElementsInstancedBaseInstance(model: Model, instanceCount: Int, baseInstance: Int) + + + /** + * Constructs a sequence of geometric primitives using array elements + * specified within [model] + */ + fun drawArrays(model: Model) + + /** + * Behaves exactly as [drawArraysInstanced], except that the parameters + * for the drawing command are taken from a structure stored in the buffer + * bound to the GL_DRAW_INDIRECT_BUFFER binding point (the draw + * indirect buffer) + */ + fun drawArraysIndirect(model: Model) + + /** + * Draws multiple sets of geometric primitives with a single OpenGL + * function call. + */ + fun multiDrawArrays(models: Array) + + /** + * Draws [instanceCount] instances of the geometric primitives specified by [model] + * as if specified by individual calls to [drawArrays]. The + * built-in variable gl_InstanceID is incremented for each instance, and + * new values are presented to the vertex shader for each instanced vertex + * attribute. + */ + fun drawArraysInstanced(model: Model, instanceCount: Int) + + /** + * Draws [instanceCount] instances of the geometric primitives specified by [model] + * as if specified by individual calls to [drawArrays]. The + * built-in variable gl_InstanceID is incremented for each instance, and + * new values are presented to the vertex shader for each instanced vertex + * attribute. Furthermore, the implied index used to fetch any instanced + * vertex attributes is offset by the value of [baseInstance] by OpenGL. + */ + fun drawArraysInstancedBaseInstance(model: Model, instanceCount: Int, baseInstance: Int) + +} \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GraphicsLoader.kt b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GraphicsLoader.kt index a20fb298..58e0f8e5 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GraphicsLoader.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/context/loader/GraphicsLoader.kt @@ -1,15 +1,17 @@ package com.mechanica.engine.context.loader import com.mechanica.engine.models.Image -import com.mechanica.engine.models.Model import com.mechanica.engine.resources.Resource interface GraphicsLoader { + val glDrawer: GLDrawerLoader + fun loadImage(id: Int): Image fun loadImage(res: Resource): Image - fun drawArrays(model: Model) - fun drawElements(model: Model) - + val glPointDrawer: GLDrawerLoader + val glLineLoopDrawer: GLDrawerLoader + val glLinesDrawer: GLDrawerLoader + val glLineStripDrawer: GLDrawerLoader } \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/display/Window.kt b/application-interface/src/main/kotlin/com/mechanica/engine/display/Window.kt index c370ed12..1db16c68 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/display/Window.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/display/Window.kt @@ -51,16 +51,16 @@ interface Window { } companion object { - fun create(title: String, width: Int, height: Int): Window { - return DisplayLoader.createWindow(title, width, height) + fun create(title: String, width: Int, height: Int, sharedWith: Window? = null): Window { + return DisplayLoader.createWindow(title, width, height, sharedWith) } - fun create(title: String, monitor: Monitor): Window { - return DisplayLoader.createWindow(title, monitor) + fun create(title: String, monitor: Monitor, sharedWith: Window? = null): Window { + return DisplayLoader.createWindow(title, monitor, sharedWith) } - fun create(title: String, width: Int, height: Int, monitor: Monitor): Window { - return DisplayLoader.createWindow(title, width, height, monitor) + fun create(title: String, width: Int, height: Int, monitor: Monitor, sharedWith: Window? = null): Window { + return DisplayLoader.createWindow(title, width, height, monitor, sharedWith) } } } \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/graphics/BaseVertex.kt b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/BaseVertex.kt new file mode 100644 index 00000000..eaeffb74 --- /dev/null +++ b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/BaseVertex.kt @@ -0,0 +1,40 @@ +package com.mechanica.engine.graphics + +import com.mechanica.engine.context.loader.GLDrawerLoader +import com.mechanica.engine.models.Model +import java.nio.IntBuffer + + +class DrawBaseVertex(private val glDrawCommands: GLDrawerLoader) : ElementsDrawCommand, MultiElementsDrawCommand { + var baseVertex: Int = 0 + + override fun elements(model: Model) { + glDrawCommands.drawElementsBaseVertex(model, baseVertex) + } + + override fun elements(models: Array) { + glDrawCommands.multiDrawElementsBaseVertex(models, baseVertex) + } +} + +class MultiDrawBaseVertex(private val glDrawCommands: GLDrawerLoader) : MultiElementsDrawCommand { + var baseVertex: IntBuffer? = null + + override fun elements(models: Array) { + val buffer = baseVertex + if (buffer != null) { + glDrawCommands.multiDrawElementsBaseVertex(models, buffer) + } + } +} + +class DrawRangeBaseVertex( + private val glDrawCommands: GLDrawerLoader, + private val drawRange: DrawRange): ElementsDrawCommand { + internal var baseVertex: Int = 0 + + override fun elements(model: Model) { + glDrawCommands.drawRangeElementsBaseVertex(model, drawRange.start, drawRange.end, baseVertex) + } + +} diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/graphics/DrawCommands.kt b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/DrawCommands.kt new file mode 100644 index 00000000..d109ad0b --- /dev/null +++ b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/DrawCommands.kt @@ -0,0 +1,31 @@ +package com.mechanica.engine.graphics + +import com.mechanica.engine.models.Model + +interface ArrayDrawCommand { + fun arrays(model: Model) +} + +interface ElementsDrawCommand { + fun elements(model: Model) +} + +interface MultiArrayDrawCommand { + fun arrays(models: Array) +} + +interface MultiElementsDrawCommand { + fun elements(models: Array) +} + +interface DrawCommands : ArrayDrawCommand, ElementsDrawCommand { + fun model(model: Model) { + if (model.hasIndexArray) { + elements(model) + } else { + arrays(model) + } + } +} + +interface MultiDrawCommands : DrawCommands, MultiArrayDrawCommand, MultiElementsDrawCommand \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/graphics/GLDrawCommands.kt b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/GLDrawCommands.kt new file mode 100644 index 00000000..c8ed8588 --- /dev/null +++ b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/GLDrawCommands.kt @@ -0,0 +1,86 @@ +package com.mechanica.engine.graphics + +import com.mechanica.engine.context.loader.GLDrawerLoader +import com.mechanica.engine.context.loader.GLLoader +import com.mechanica.engine.models.Model +import java.nio.IntBuffer + +class GLDraw { + private val glDrawer = GLLoader.graphicsLoader.glDrawer + + val glDraw by lazy { GLDrawCommands(glDrawer) } + val glDrawPoints by lazy { GLDrawCommands(GLLoader.graphicsLoader.glPointDrawer) } + val glDrawLines by lazy { GLDrawCommands(GLLoader.graphicsLoader.glLinesDrawer) } + val glDrawLineLoop by lazy { GLDrawCommands(GLLoader.graphicsLoader.glLineLoopDrawer) } + val glDrawLineStrip by lazy { GLDrawCommands(GLLoader.graphicsLoader.glLineStripDrawer) } + + fun drawModel(model: Model) { + if (model.hasIndexArray) { + glDrawer.drawElements(model) + } else { + glDrawer.drawArrays(model) + } + } + +} + +class GLDrawCommands(private val glDrawCommands: GLDrawerLoader) : MultiDrawCommands { + + private val instancedDraw = DrawInstanced(glDrawCommands) + private val baseVertexDraw = DrawBaseVertex(glDrawCommands) + private val baseVertexMultiDraw = MultiDrawBaseVertex(glDrawCommands) + private val drawRange = DrawRange(glDrawCommands) + + override fun arrays(model: Model) { + glDrawCommands.drawArrays(model) + } + + override fun arrays(models: Array) { + glDrawCommands.multiDrawArrays(models) + } + + override fun elements(model: Model) { + glDrawCommands.drawElements(model) + } + + override fun elements(models: Array) { + glDrawCommands.multiDrawElements(models) + } + + fun instanced(instanceCount: Int): DrawInstanced { + instancedDraw.instanceCount = instanceCount + return instancedDraw + } + + fun baseVertex(baseVertex: Int): DrawBaseVertex { + baseVertexDraw.baseVertex = baseVertex + return baseVertexDraw + } + + fun baseVertex(baseVertex: IntBuffer): MultiDrawBaseVertex { + baseVertexMultiDraw.baseVertex = baseVertex + return baseVertexMultiDraw + } + + fun range(start: Int, end: Int): DrawRange { + drawRange.start = start + drawRange.end = end + return drawRange + } + +} + +class DrawRange(private val glDrawCommands: GLDrawerLoader) : ElementsDrawCommand { + internal var start: Int = 0 + internal var end: Int = 1 + private val drawRangeBaseVertex = DrawRangeBaseVertex(glDrawCommands, this) + + override fun elements(model: Model) { + glDrawCommands.drawRangeElements(model, start, end) + } + + fun baseVertex(baseVertex: Int): ElementsDrawCommand { + drawRangeBaseVertex.baseVertex = baseVertex + return drawRangeBaseVertex + } +} \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/graphics/Instanced.kt b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/Instanced.kt new file mode 100644 index 00000000..96c438a3 --- /dev/null +++ b/application-interface/src/main/kotlin/com/mechanica/engine/graphics/Instanced.kt @@ -0,0 +1,53 @@ +package com.mechanica.engine.graphics + +import com.mechanica.engine.context.loader.GLDrawerLoader +import com.mechanica.engine.models.Model + + +class DrawInstanced(private val glDrawCommands: GLDrawerLoader) : DrawCommands { + internal var instanceCount: Int = 1 + + private val drawInstancedBaseInstance = DrawInstancedBaseInstance(glDrawCommands, this) + private val drawInstancedBaseVertex = BaseVertex() + + fun baseInstance(baseInstance: Int) : DrawInstancedBaseInstance { + drawInstancedBaseInstance.baseInstance = baseInstance + return drawInstancedBaseInstance + } + + fun baseVertex(baseVertex: Int) : ElementsDrawCommand { + drawInstancedBaseVertex.baseVertex = baseVertex + return drawInstancedBaseVertex + } + + override fun arrays(model: Model) { + glDrawCommands.drawArraysInstanced(model, instanceCount) + } + + override fun elements(model: Model) { + glDrawCommands.drawElementsInstanced(model, instanceCount) + } + + private inner class BaseVertex : ElementsDrawCommand { + var baseVertex: Int = 0 + override fun elements(model: Model) { + glDrawCommands.drawElementsInstancedBaseVertex(model, instanceCount, baseVertex) + } + + } +} + +class DrawInstancedBaseInstance( + private val glDrawCommands: GLDrawerLoader, + private val drawInstanced: DrawInstanced) : DrawCommands { + internal var baseInstance: Int = 0 + + override fun arrays(model: Model) { + glDrawCommands.drawArraysInstancedBaseInstance(model, drawInstanced.instanceCount, baseInstance) + } + + override fun elements(model: Model) { + glDrawCommands.drawElementsInstancedBaseInstance(model, drawInstanced.instanceCount, baseInstance) + } + +} \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/models/Model.kt b/application-interface/src/main/kotlin/com/mechanica/engine/models/Model.kt index 5243239b..6a94a566 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/models/Model.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/models/Model.kt @@ -1,11 +1,11 @@ package com.mechanica.engine.models -import com.mechanica.engine.context.loader.GLLoader +import com.mechanica.engine.shader.qualifiers.Attribute +import com.mechanica.engine.unit.vector.Vector import com.mechanica.engine.vertices.IndexArray import com.mechanica.engine.vertices.VertexBuffer -open class Model(vararg inputs: Bindable, - draw: ((Model) -> Unit)? = null) : Iterable { +open class Model(vararg inputs: Bindable) : Iterable { protected val inputs: Array = arrayOf(*inputs) private val maxVertices: Int @@ -20,7 +20,18 @@ open class Model(vararg inputs: Bindable, } var vertexCount = maxVertices - private val draw: ((Model) -> Unit) by lazy { draw ?: defaultDraw(this) } + val hasIndexArray: Boolean + + init { + var hasElementArrayBuffer = false + for (input in inputs) { + if (input is IndexArray) { + hasElementArrayBuffer = true + } + } + + this.hasIndexArray = hasElementArrayBuffer + } fun bind() { for (vbo in inputs) { @@ -32,17 +43,24 @@ open class Model(vararg inputs: Bindable, } } - fun draw() { - this.draw(this) - } - override fun iterator() = inputs.iterator() companion object { - fun defaultDraw(model: Model) = if (model.inputs.any { it is IndexArray }) { - GLLoader.graphicsLoader::drawElements - } else { - GLLoader.graphicsLoader::drawArrays - } + + fun createUnitSquare(): Model { + val position = Attribute.location(0).vec3().createUnitQuad() + val tc = Attribute.location(1).vec2().createInvertedUnitQuad() + return Model(position, tc) + } + + fun createFromFloatArray(array: FloatArray): Model { + val position = Attribute.location(0).vec3().createBuffer(array) + return Model(position) + } + + fun createFromVecArray(array: Array): Model { + val position = Attribute.location(0).vec3().createBuffer(array) + return Model(position) + } } } \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/models/PolygonModel.kt b/application-interface/src/main/kotlin/com/mechanica/engine/models/PolygonModel.kt index a3d1b2da..fa54d0d1 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/models/PolygonModel.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/models/PolygonModel.kt @@ -2,24 +2,21 @@ package com.mechanica.engine.models import com.mechanica.engine.geometry.triangulation.GrahamScanTriangulator import com.mechanica.engine.geometry.triangulation.Triangulator -import com.mechanica.engine.context.loader.GLLoader import com.mechanica.engine.shader.qualifiers.Attribute -import com.mechanica.engine.unit.vector.LightweightVector -import com.mechanica.engine.vertices.IndexArray +import com.mechanica.engine.unit.vector.Vector import com.mechanica.engine.vertices.FloatBufferMaker +import com.mechanica.engine.vertices.IndexArray -open class PolygonModel(vertices: Array, +open class PolygonModel(vertices: Array, positionAttribute: FloatBufferMaker = Attribute(0).vec2(), triangulator: Triangulator = GrahamScanTriangulator(vertices)) : Model( positionAttribute.createBuffer(vertices), IndexArray.create(*triangulator.triangulate()), - draw = { - GLLoader.graphicsLoader.drawElements(it) - }) + ) { - constructor(vertices: List) : this(vertices.toTypedArray()) + constructor(vertices: List) : this(vertices.toTypedArray()) init { vertexCount = triangulator.indexCount diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/models/TextModel.kt b/application-interface/src/main/kotlin/com/mechanica/engine/models/TextModel.kt index afc6bd85..f912aa79 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/models/TextModel.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/models/TextModel.kt @@ -1,13 +1,12 @@ package com.mechanica.engine.models +import com.mechanica.engine.shader.qualifiers.Attribute import com.mechanica.engine.text.Font import com.mechanica.engine.text.Text -import com.mechanica.engine.context.loader.GLLoader -import com.mechanica.engine.shader.qualifiers.Attribute import com.mechanica.engine.utils.createIndicesArrayForQuads import com.mechanica.engine.vertices.AttributeArray -import com.mechanica.engine.vertices.IndexArray import com.mechanica.engine.vertices.FloatBufferMaker +import com.mechanica.engine.vertices.IndexArray import kotlin.math.max class TextModel(text: Text, @@ -17,9 +16,6 @@ class TextModel(text: Text, texCoordsBufferMaker.createBuffer(text.texCoords), IndexArray.create(*createIndicesArrayForQuads(max(text.positions.size/2, 20))), Image.invoke(text.font.atlas.id), - draw = { model -> - GLLoader.graphicsLoader.drawElements(model) - } ) { private val positionAttribute = inputs[0] as AttributeArray private val texCoordsAttribute = inputs[1] as AttributeArray diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/resources/ExternalResource.kt b/application-interface/src/main/kotlin/com/mechanica/engine/resources/ExternalResource.kt index f488830d..50fbaf91 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/resources/ExternalResource.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/resources/ExternalResource.kt @@ -4,7 +4,6 @@ import com.mechanica.engine.util.calculateIsJar import java.io.* import java.net.URI import java.nio.file.Paths -import kotlin.system.exitProcess class ExternalResource(filePath: String, createIfAbsent: Boolean = false) : Resource { private val absoluteFile: File @@ -31,13 +30,15 @@ class ExternalResource(filePath: String, createIfAbsent: Boolean = false) : Reso fun write(content: String) { try { + absoluteFile.absoluteFile.parentFile.mkdirs() val fw = FileWriter(absoluteFile.absoluteFile) val bw = BufferedWriter(fw) bw.write(content) bw.close() } catch (e: IOException) { - e.printStackTrace() - exitProcess(-1) + System.err.println("Error handling persistence file for: $absoluteFile") + } catch (e: FileNotFoundException) { + System.err.println("Error handling persistence file for: $absoluteFile, it is possible that access was denied") } } diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/shader/script/Shader.kt b/application-interface/src/main/kotlin/com/mechanica/engine/shader/script/Shader.kt index 355441bb..46861bc2 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/shader/script/Shader.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/shader/script/Shader.kt @@ -1,12 +1,13 @@ package com.mechanica.engine.shader.script import com.mechanica.engine.context.loader.GLLoader +import com.mechanica.engine.graphics.GLDraw import com.mechanica.engine.models.Bindable import com.mechanica.engine.models.Model +import com.mechanica.engine.util.extensions.fori abstract class Shader { abstract val id: Int - abstract val vertex: ShaderScript abstract val fragment: ShaderScript open val tessellation: ShaderScript? = null @@ -14,6 +15,8 @@ abstract class Shader { private var locationsFound = false + private val glDraw = GLDraw() + protected fun load() { loadProgram(id) loadUniforms() @@ -32,12 +35,12 @@ abstract class Shader { abstract fun loadUniformLocation(name: String): Int - open fun render(inputs: Array, draw: () -> Unit) { + open fun render(inputs: Array, draw: GLDraw.() -> Unit) { load() - inputs.forEach { it.bind() } + inputs.fori { it.bind() } - draw() + draw(glDraw) } open fun render(model: Model) { @@ -45,7 +48,11 @@ abstract class Shader { model.bind() - model.draw() + glDraw.draw(model) + } + + protected open fun GLDraw.draw(model: Model) { + drawModel(model) } private fun findLocations() { diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/shader/uniforms/vars/UniformVector4f.kt b/application-interface/src/main/kotlin/com/mechanica/engine/shader/uniforms/vars/UniformVector4f.kt index 84674ba4..c8291743 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/shader/uniforms/vars/UniformVector4f.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/shader/uniforms/vars/UniformVector4f.kt @@ -1,7 +1,7 @@ package com.mechanica.engine.shader.uniforms.vars import com.mechanica.engine.color.Color -import com.mechanica.engine.color.LightweightColor +import com.mechanica.engine.color.InlineColor import com.mechanica.engine.shader.qualifiers.Qualifier import com.mechanica.engine.shader.vars.ShaderType import org.joml.Vector4f @@ -25,7 +25,7 @@ abstract class UniformVector4f ( value.w = color.a.toFloat() } - fun set(color: LightweightColor) { + fun set(color: InlineColor) { value.x = color.r.toFloat() value.y = color.g.toFloat() value.z = color.b.toFloat() diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/text/Font.kt b/application-interface/src/main/kotlin/com/mechanica/engine/text/Font.kt index 670373f8..9cceaebc 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/text/Font.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/text/Font.kt @@ -18,7 +18,14 @@ abstract class Font { companion object { - val defaultFont = GLLoader.fontLoader.defaultFont + val defaultFont by lazy { defaults.regular(false) } + + val defaults: FontSet + get() = CachedFontSet("roboto/Roboto") { + width = 1024 + height = 1024 + characterSize = 175f + } fun create(resource: Resource, configureAtlas: FontAtlasConfiguration.() -> Unit = { }): Font { return GLLoader.fontLoader.font(resource, configureAtlas) diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/text/FontSet.kt b/application-interface/src/main/kotlin/com/mechanica/engine/text/FontSet.kt new file mode 100644 index 00000000..c1660a9e --- /dev/null +++ b/application-interface/src/main/kotlin/com/mechanica/engine/text/FontSet.kt @@ -0,0 +1,81 @@ +package com.mechanica.engine.text + +import com.mechanica.engine.resources.Resource + +open class FontSet(protected val name: String, protected val configureAtlas: FontAtlasConfiguration.() -> Unit = { }) { + protected var modifier = Modifiers.REGULAR + protected var italic = "" + + protected val fileName: String + get() = "res/fonts/${toString()}.ttf" + + fun regular(italic: Boolean): Font { + if (italic) this.italic = italicString + modifier = Modifiers.REGULAR + return create() + } + + fun bold(italic: Boolean): Font { + if (italic) this.italic = italicString + modifier = Modifiers.BOLD + return create() + } + + fun medium(italic: Boolean): Font { + if (italic) this.italic = italicString + modifier = Modifiers.MEDIUM + return create() + } + + fun black(italic: Boolean): Font { + if (italic) this.italic = italicString + modifier = Modifiers.BLACK + return create() + } + + fun light(italic: Boolean): Font { + if (italic) this.italic = italicString + modifier = Modifiers.LIGHT + return create() + } + + fun thin(italic: Boolean): Font { + if (italic) this.italic = italicString + modifier = Modifiers.THIN + return create() + } + + open fun create() = Font.create(Resource(fileName), configureAtlas) + + override fun toString(): String { + return "$name-$modifier$italic" + } + + enum class Modifiers(val string: String) { + REGULAR("Regular"), + BOLD("Bold"), + BLACK("Black"), + LIGHT("Light"), + THIN("Thin"), + MEDIUM("Medium"); + + override fun toString(): String { + return string + } + } + + companion object { + const val italicString = "Italic" + } +} + +class CachedFontSet(name: String, configureAtlas: FontAtlasConfiguration.() -> Unit = { }) : FontSet(name, configureAtlas) { + override fun create(): Font { + val fileName = this.fileName + return fontMap[fileName] ?: super.create().also { fontMap[fileName] = it } + } + + companion object { + private val fontMap = HashMap() + } +} \ No newline at end of file diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/text/Text.kt b/application-interface/src/main/kotlin/com/mechanica/engine/text/Text.kt index 3cb4fa03..6182b159 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/text/Text.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/text/Text.kt @@ -1,18 +1,22 @@ package com.mechanica.engine.text -import com.mechanica.engine.context.loader.GLLoader import com.mechanica.engine.unit.vector.DynamicVector import com.mechanica.engine.util.extensions.constrain import kotlin.math.abs import kotlin.math.max -class Text(text: String, val font: Font = GLLoader.fontLoader.defaultFont) { +class Text(text: String, font: Font = Font.defaultFont) { var string = text set(value) { field = value update(value, font) } + var font = font + set(value) { + field = value + update(string, font) + } var positions: Array = Array(text.length*8){ DynamicVector.create() } private set diff --git a/application-interface/src/main/kotlin/com/mechanica/engine/vertices/VertexBufferObjects.kt b/application-interface/src/main/kotlin/com/mechanica/engine/vertices/VertexBufferObjects.kt index 31a0cb1f..2b472d11 100644 --- a/application-interface/src/main/kotlin/com/mechanica/engine/vertices/VertexBufferObjects.kt +++ b/application-interface/src/main/kotlin/com/mechanica/engine/vertices/VertexBufferObjects.kt @@ -7,7 +7,6 @@ import com.mechanica.engine.utils.createIndicesArrayForQuads import org.joml.Vector3f import org.joml.Vector4f - interface VertexBuffer : Bindable { val id: Int val vertexCount: Int diff --git a/build.gradle.kts b/build.gradle.kts index 27f9b75e..28bf419c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile val lwjglVersion = "3.2.3" val lwjglNatives = "natives-windows" -val kotlinVersion = "1.3.70" +val kotlinVersion: String? = "1.4.0" buildscript { repositories { @@ -18,8 +18,8 @@ buildscript { } plugins { - kotlin("jvm") version "1.3.70" - kotlin("plugin.serialization") version "1.3.70" + kotlin("jvm") + kotlin("plugin.serialization") `java-library` maven } @@ -56,7 +56,7 @@ val supplementaryLwjgl: DependencyHandlerScope.() -> Unit = { allprojects { group = "com.mechanica.engine" - version = 1.0 + version = 0.1 repositories { // Use jcenter for resolving your dependencies. @@ -74,6 +74,7 @@ allprojects { val compileKotlin: KotlinCompile by tasks compileKotlin.kotlinOptions { freeCompilerArgs = listOf("-XXLanguage:+InlineClasses") + languageVersion = "1.4" jvmTarget = "12" } diff --git a/common/src/main/kotlin/com/mechanica/engine/color/Color.kt b/common/src/main/kotlin/com/mechanica/engine/color/Color.kt index 52af4765..ddc8c09e 100644 --- a/common/src/main/kotlin/com/mechanica/engine/color/Color.kt +++ b/common/src/main/kotlin/com/mechanica/engine/color/Color.kt @@ -1,6 +1,5 @@ package com.mechanica.engine.color -import com.mechanica.engine.unit.angle.Angle import com.mechanica.engine.unit.angle.Degree interface Color { @@ -27,4 +26,18 @@ interface Color { } fun toLong(): Long + + companion object { + fun rgba(r: Double, g: Double, b: Double, a: Double): InlineColor = com.mechanica.engine.color.rgba(r, g, b, a) + fun hex(hex: Long): InlineColor = com.mechanica.engine.color.hex(hex) + fun hsl(hue: Degree, saturation: Double, lightness: Double, alpha: Double = 1.0): InlineColor + = com.mechanica.engine.color.hsl(hue, saturation, lightness, alpha) + + val red = rgba(1.0, 0.0, 0.0, 1.0) + val green = rgba(0.0, 1.0, 0.0, 1.0) + val blue = rgba(0.0, 0.0, 1.0, 1.0) + + val black = rgba(0.0, 0.0, 0.0, 1.0) + val white = rgba(1.0, 1.0, 1.0, 1.0) + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/mechanica/engine/color/ColorTools.kt b/common/src/main/kotlin/com/mechanica/engine/color/ColorTools.kt index b1e670bd..d686fa41 100644 --- a/common/src/main/kotlin/com/mechanica/engine/color/ColorTools.kt +++ b/common/src/main/kotlin/com/mechanica/engine/color/ColorTools.kt @@ -10,11 +10,11 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min -fun hex(color: Long) = LightweightColor(color) +fun hex(color: Long) = InlineColor(color) -fun rgba(r: Double, g: Double, b: Double, a: Double) = LightweightColor(rgba2Hex(r, g, b, a)) +fun rgba(r: Double, g: Double, b: Double, a: Double) = InlineColor(rgba2Hex(r, g, b, a)) -fun Color.linearBlend(p: Double, other: Color): LightweightColor { +fun Color.linearBlend(p: Double, other: Color): InlineColor { val (r0, g0, b0, a0) = this val (r1, g1, b1, a1) = other @@ -25,7 +25,7 @@ fun Color.linearBlend(p: Double, other: Color): LightweightColor { val green = blend(g0, g1, p) val blue = blend(b0, b1, p) - return LightweightColor(rgba2Hex(red, green, blue, alpha)) + return InlineColor(rgba2Hex(red, green, blue, alpha)) } fun Color.logBlend(percent: Double, other: Color): Color { @@ -42,11 +42,11 @@ fun Color.logBlend(percent: Double, other: Color): Color { val green = blend(g0, g1, p) val blue = blend(b0, b1, p) - return LightweightColor(rgba2Hex(red, green, blue, alpha)) + return InlineColor(rgba2Hex(red, green, blue, alpha)) } -fun Color.alphaBlend(src: Color): LightweightColor { +fun Color.alphaBlend(src: Color): InlineColor { val alpha = src.a + a*(1 - src.a) val red = alphaBlendComponent(0, src, this) @@ -62,7 +62,7 @@ private fun alphaBlendComponent(index: Int, src: Color, dst: Color): Double { return srcComponent*alpha + dstComponent*(1 - alpha) } -fun LightweightColor.alphaBlend(src: LightweightColor): LightweightColor { +fun InlineColor.alphaBlend(src: InlineColor): InlineColor { val alpha = src.a + a*(1 - src.a) val red = alphaBlendComponent(0, src, this) @@ -71,7 +71,7 @@ fun LightweightColor.alphaBlend(src: LightweightColor): LightweightColor { return rgba(red, green, blue, alpha) } -private fun alphaBlendComponent(index: Int, src: LightweightColor, dst: LightweightColor): Double { +private fun alphaBlendComponent(index: Int, src: InlineColor, dst: InlineColor): Double { val srcComponent = src[index] val dstComponent = dst[index] val alpha = src.a @@ -79,11 +79,11 @@ private fun alphaBlendComponent(index: Int, src: LightweightColor, dst: Lightwei } fun Color.lighten(amount: Int): Color { - return LightweightColor(adjustShade(this.toLong(), amount)) + return InlineColor(adjustShade(this.toLong(), amount)) } fun Color.darken(amount: Int): Color { - return LightweightColor(adjustShade(this.toLong(), -amount)) + return InlineColor(adjustShade(this.toLong(), -amount)) } private fun adjustShade(hex: Long, amount: Int): Long { @@ -103,15 +103,15 @@ private fun adjustShade(hex: Long, amount: Int): Long { } -fun Color.lightness(level: Double): LightweightColor { +fun Color.lightness(level: Double): InlineColor { return hsl(hue, saturation, level) } -fun Color.hue(level: Degree): LightweightColor { +fun Color.hue(level: Degree): InlineColor { return hsl(level, saturation, lightness) } -fun Color.saturation(level: Double): LightweightColor { +fun Color.saturation(level: Double): InlineColor { return hsl(hue, level, lightness) } @@ -182,15 +182,15 @@ fun rgb2Lightness(r: Double, g: Double, b: Double): Double { return (max + min)/2.0 } -fun hsl(hue: Degree, saturation: Double, lightness: Double, alpha: Double = 1.0): LightweightColor { +fun hsl(hue: Degree, saturation: Double, lightness: Double, alpha: Double = 1.0): InlineColor { return hsl(hue.toDouble(), saturation, lightness, alpha) } -fun hsl(hue: Radian, saturation: Double, lightness: Double, alpha: Double = 1.0): LightweightColor { +fun hsl(hue: Radian, saturation: Double, lightness: Double, alpha: Double = 1.0): InlineColor { return hsl(hue.toDegrees().toDouble(), saturation, lightness, alpha) } -fun hsl(hue: Double, saturation: Double, lightness: Double, alpha: Double = 1.0): LightweightColor { +fun hsl(hue: Double, saturation: Double, lightness: Double, alpha: Double = 1.0): InlineColor { fun f(n: Int, h: Double, s: Double, l: Double): Double { val a = s* min(l, 1.0-l) val k = (n + h/30.0)%12 @@ -204,7 +204,7 @@ fun hsl(hue: Double, saturation: Double, lightness: Double, alpha: Double = 1.0) return rgba(f(0, h, s, l), f(8, h, s, l), f(4, h, s, l), alpha) } -fun FloatArray.toColor(): LightweightColor { +fun FloatArray.toColor(): InlineColor { fun getComponent(i: Int) = if (this.size > i) this[i].toDouble() else 0.0 val r = getComponent(0) val g = getComponent(1) @@ -213,7 +213,7 @@ fun FloatArray.toColor(): LightweightColor { return rgba(r, g, b, a) } -fun Vector4f.toColor(): LightweightColor { +fun Vector4f.toColor(): InlineColor { val r = this.x().toDouble() val g = this.y().toDouble() val b = this.z().toDouble() diff --git a/common/src/main/kotlin/com/mechanica/engine/color/DynamicColor.kt b/common/src/main/kotlin/com/mechanica/engine/color/DynamicColor.kt index e792c0a9..4498834e 100644 --- a/common/src/main/kotlin/com/mechanica/engine/color/DynamicColor.kt +++ b/common/src/main/kotlin/com/mechanica/engine/color/DynamicColor.kt @@ -17,7 +17,7 @@ class DynamicColor( set(color.r, color.g, color.b, color.a) } - fun set(color: LightweightColor) { + fun set(color: InlineColor) { set(color.r, color.g, color.b, color.a) } diff --git a/common/src/main/kotlin/com/mechanica/engine/color/LightweightColor.kt b/common/src/main/kotlin/com/mechanica/engine/color/InlineColor.kt similarity index 82% rename from common/src/main/kotlin/com/mechanica/engine/color/LightweightColor.kt rename to common/src/main/kotlin/com/mechanica/engine/color/InlineColor.kt index 907b40ac..96ac6d39 100644 --- a/common/src/main/kotlin/com/mechanica/engine/color/LightweightColor.kt +++ b/common/src/main/kotlin/com/mechanica/engine/color/InlineColor.kt @@ -1,9 +1,8 @@ package com.mechanica.engine.color -import com.mechanica.engine.unit.angle.Angle import com.mechanica.engine.unit.angle.Degree -inline class LightweightColor(private val hex: Long): Color { +inline class InlineColor(private val hex: Long): Color { override val a: Double get() = hex2Alpha(hex) override val r: Double diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/lines/Line.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/lines/Line.kt index f768b778..65b37c21 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/lines/Line.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/lines/Line.kt @@ -1,6 +1,6 @@ package com.mechanica.engine.geometry.lines -import com.mechanica.engine.unit.vector.LightweightVector +import com.mechanica.engine.unit.vector.InlineVector import com.mechanica.engine.unit.vector.vec interface Line { @@ -9,7 +9,7 @@ interface Line { val n: Double val c: Double - fun intersect(other: Line): LightweightVector { + fun intersect(other: Line): InlineVector { val x = if (n != 0.0 && other.n !=0.0) (other.b - b) / (m - other.m) else if (n == 0.0) c else other.c diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/lines/LineSegment.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/lines/LineSegment.kt index ce45f8fc..9ee26b18 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/lines/LineSegment.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/lines/LineSegment.kt @@ -1,6 +1,6 @@ package com.mechanica.engine.geometry.lines -import com.mechanica.engine.unit.vector.LightweightVector +import com.mechanica.engine.unit.vector.InlineVector import com.mechanica.engine.unit.vector.Vector import kotlin.math.max import kotlin.math.min @@ -25,7 +25,7 @@ abstract class LineSegment() : Line { abstract val p1: Vector abstract val p2: Vector - fun isInBoundingBox(point: LightweightVector): Boolean { + fun isInBoundingBox(point: InlineVector): Boolean { val xBoolean = point.x in min(p1.x, p2.x)..max(p1.x, p2.x) val yBoolean = point.y in min(p1.y,p2.y)..max(p2.y, p1.y) return xBoolean && yBoolean diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/lines/PolygonLine.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/lines/PolygonLine.kt index 030c940e..42b9d743 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/lines/PolygonLine.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/lines/PolygonLine.kt @@ -1,7 +1,7 @@ package com.mechanica.engine.geometry.lines -import com.mechanica.engine.unit.vector.LightweightVector import com.mechanica.engine.geometry.triangulation.Triangulator +import com.mechanica.engine.unit.vector.InlineVector class PolygonLine(private val list: ArrayList, val listIndex: Int) : LineSegment() { @@ -14,14 +14,14 @@ class PolygonLine(private val list: ArrayList, val listIndex: this.hasChanged = hasChange } - fun setP1(vec: LightweightVector, previous: PolygonLine? = null) { + fun setP1(vec: InlineVector, previous: PolygonLine? = null) { p1.x = vec.x p1.y = vec.y hasChanged = true previous?.hasChanged(true) } - fun setP2(vec: LightweightVector, next: PolygonLine? = null) { + fun setP2(vec: InlineVector, next: PolygonLine? = null) { p2.x = vec.x p2.y = vec.y hasChanged = true diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/path/PathUtils.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/path/PathUtils.kt index cf873aae..91ab088d 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/path/PathUtils.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/path/PathUtils.kt @@ -1,20 +1,23 @@ package com.mechanica.engine.geometry.path -import com.mechanica.engine.unit.vector.* +import com.mechanica.engine.unit.vector.DynamicVector +import com.mechanica.engine.unit.vector.InlineVector +import com.mechanica.engine.unit.vector.Vector +import com.mechanica.engine.unit.vector.vec import kotlin.math.sqrt fun Iterable.moveToOrigin() { - val minX = (this.minBy { it.x } ?: vec(0.0, 0.0)).x - val minY = (this.minBy { it.y } ?: vec(0.0, 0.0)).y + val minX = (this.minByOrNull { it.x } ?: vec(0.0, 0.0)).x + val minY = (this.minByOrNull { it.y } ?: vec(0.0, 0.0)).y this.forEach { vec -> vec.x -= minX vec.y -= minY } } -fun List.moveToOrigin(): List { - val minX = (this.minBy { it.x } ?: vec(0.0, 0.0)).x - val minY = (this.minBy { it.y } ?: vec(0.0, 0.0)).y +fun List.moveToOrigin(): List { + val minX = (this.minByOrNull { it.x } ?: vec(0.0, 0.0)).x + val minY = (this.minByOrNull { it.y } ?: vec(0.0, 0.0)).y return this.map { vec(it.x - minX, it.y - minY) } } @@ -27,14 +30,14 @@ fun Iterable.scale(scaleX: Double, scaleY: Double) { } } -fun List.scale(scale: Double) = scale(scale, scale) +fun List.scale(scale: Double) = scale(scale, scale) -fun List.scale(scaleX: Double, scaleY: Double): List { +fun List.scale(scaleX: Double, scaleY: Double): List { return this.map { vec(it.x*scaleX, it.y*scaleY) } } -fun Iterable.centroid(): LightweightVector { +fun Iterable.centroid(): InlineVector { var xAverage = 0.0 var yAverage = 0.0 this.forEachIndexed { i, vec -> @@ -65,17 +68,17 @@ fun Iterable.flipVertically() { } } -fun Iterable.flipVertically() = this.map { vec(it.x, -it.y) } +fun Iterable.flipVertically() = this.map { vec(it.x, -it.y) } fun Iterable.xRange(): Double { - val max = this.maxBy { it.x } ?: vec(0.0, 0.0) - val min = this.minBy { it.x } ?: vec(0.0, 0.0) + val max = this.maxByOrNull { it.x } ?: vec(0.0, 0.0) + val min = this.minByOrNull { it.x } ?: vec(0.0, 0.0) return max.x - min.x } fun Iterable.yRange(): Double { - val max = this.maxBy { it.y } ?: vec(0.0, 0.0) - val min = this.minBy { it.y } ?: vec(0.0, 0.0) + val max = this.maxByOrNull { it.y } ?: vec(0.0, 0.0) + val min = this.minByOrNull { it.y } ?: vec(0.0, 0.0) return max.y - min.y } diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/GrahamScanTriangulator.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/GrahamScanTriangulator.kt index 32dfe118..18570c8e 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/GrahamScanTriangulator.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/GrahamScanTriangulator.kt @@ -4,9 +4,10 @@ import com.mechanica.engine.geometry.lines.LineSegment import com.mechanica.engine.geometry.triangulation.iterators.ConcaveVertexIterable import com.mechanica.engine.geometry.triangulation.iterators.TriangulatorIterable import com.mechanica.engine.geometry.triangulation.iterators.VertexLoopIterable -import com.mechanica.engine.unit.vector.LightweightVector +import com.mechanica.engine.unit.vector.Vector -class GrahamScanTriangulator(path: Array) : Triangulator(path) { +// From: "The Graham scan triangulates simple polygons" Pattern Recognition Letters, 1990. Kong, X., H. Everett, and G.T. Toussaint +class GrahamScanTriangulator(path: Array) : Triangulator(path) { override val indices = ShortArray(500) override var indexCount = 0 var current: Node? = null diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/TriangulationUtils.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/TriangulationUtils.kt index e13e91b2..a8a9f77e 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/TriangulationUtils.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/TriangulationUtils.kt @@ -1,11 +1,11 @@ package com.mechanica.engine.geometry.triangulation -import com.mechanica.engine.unit.vector.LightweightVector -import com.mechanica.engine.unit.vector.Vector import com.mechanica.engine.geometry.isInTriangle import com.mechanica.engine.geometry.rectangleArea +import com.mechanica.engine.unit.vector.InlineVector +import com.mechanica.engine.unit.vector.Vector -fun calculateLineArea(p1: LightweightVector, p2: LightweightVector): Double { +fun calculateLineArea(p1: InlineVector, p2: InlineVector): Double { return (p2.x - p1.x)*(p2.y + p1.y) } diff --git a/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/Triangulator.kt b/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/Triangulator.kt index 75f5d46b..ae77543d 100644 --- a/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/Triangulator.kt +++ b/common/src/main/kotlin/com/mechanica/engine/geometry/triangulation/Triangulator.kt @@ -1,11 +1,12 @@ package com.mechanica.engine.geometry.triangulation import com.mechanica.engine.geometry.triangulation.iterators.TriangulatorIterable -import com.mechanica.engine.unit.vector.LightweightVector +import com.mechanica.engine.unit.vector.InlineVector import com.mechanica.engine.unit.vector.Vector +import com.mechanica.engine.unit.vector.vec import com.mechanica.engine.util.extensions.indexLooped -abstract class Triangulator(path: Array) { +abstract class Triangulator(path: Array) { val ccw: Boolean val vertices = ArrayList() @@ -24,25 +25,25 @@ abstract class Triangulator(path: Array) { ccw = addAllFromPath(path) } - fun add(vector: LightweightVector): Node { + fun add(vector: InlineVector): Node { val n = Node(vector) rewind() return n } - fun add(index: Int, vector: LightweightVector): Node { + fun add(index: Int, vector: InlineVector): Node { val n = Node(vector, index) rewind() return n } - private fun addAllFromPath(path: Array): Boolean { + private fun addAllFromPath(path: Array): Boolean { var totalArea = 0.0 for (i in 1 until path.size) { Node(path[i]) - if (i > 0) totalArea += calculateLineArea(path[i - 1], path[i]) + if (i > 0) totalArea += calculateLineArea(vec(path[i - 1]), vec(path[i])) } return totalArea < 0.0 } diff --git a/common/src/main/kotlin/com/mechanica/engine/matrix/Matrices.kt b/common/src/main/kotlin/com/mechanica/engine/matrix/Matrices.kt index 4a6fa7b2..32906196 100644 --- a/common/src/main/kotlin/com/mechanica/engine/matrix/Matrices.kt +++ b/common/src/main/kotlin/com/mechanica/engine/matrix/Matrices.kt @@ -1,48 +1,10 @@ package com.mechanica.engine.matrix import org.joml.Matrix4f -import org.joml.Vector3f -import kotlin.math.min interface Matrices { val projection: Matrix4f - val view: Matrix4f - val uiView: Matrix4f + val worldCamera: Matrix4f + val uiCamera: Matrix4f - companion object { - - private val vec3 = Vector3f() - fun calculatePixelSize(projection: Matrix4f, view: Matrix4f, resolutionHeight: Int, zTranslation: Float = 0f): Float { - val zTranslate = (zTranslation + getZTranslate(view)) - - - /* The following equations help to work out the y height: - zTranslate = height / (2 * tan(fov / 2)) - For Projection Matrix: - ScaleY = (1f / tan(fov / 2f)) * aspectRatio */ - val yHeight = zTranslate/getYScale(projection) - - val scale = getMinScale(view) - val fractionOfScreen = scale/yHeight - val sizeOnScreen = fractionOfScreen* resolutionHeight - - return 2f/sizeOnScreen - } - - fun getYScale(mat: Matrix4f): Float { - mat.getScale(vec3) - return vec3.y - } - - private fun getMinScale(mat: Matrix4f): Float { - mat.getScale(vec3) - return min(vec3.y, vec3.x) - } - - private fun getZTranslate(mat: Matrix4f): Float { - mat.getTranslation(vec3) - return -vec3.z - } - - } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/mechanica/engine/matrix/MatrixUtils.kt b/common/src/main/kotlin/com/mechanica/engine/matrix/MatrixUtils.kt new file mode 100644 index 00000000..177d1954 --- /dev/null +++ b/common/src/main/kotlin/com/mechanica/engine/matrix/MatrixUtils.kt @@ -0,0 +1,42 @@ +package com.mechanica.engine.matrix + +import org.joml.Matrix4f +import org.joml.Vector3f +import kotlin.math.min + +private val vec3 = Vector3f() + +fun calculatePixelSize(projection: Matrix4f, view: Matrix4f, resolutionHeight: Int, zTranslation: Float = 0f): Float { + val zTranslate = (zTranslation + view.zTranslation) + + + /* The following equations help to work out the y height: + zTranslate = height / (2 * tan(fov / 2)) + For Projection Matrix: + ScaleY = (1f / tan(fov / 2f)) * aspectRatio */ + val yHeight = zTranslate/projection.yScale + + val scale = view.minScale + val fractionOfScreen = scale/yHeight + val sizeOnScreen = fractionOfScreen* resolutionHeight + + return 2f/sizeOnScreen +} + +val Matrix4f.yScale: Float + get() { + this.getScale(vec3) + return vec3.y + } + +private val Matrix4f.minScale: Float + get() { + this.getScale(vec3) + return min(vec3.y, vec3.x) + } + +private val Matrix4f.zTranslation: Float + get() { + this.getTranslation(vec3) + return -vec3.z + } diff --git a/common/src/main/kotlin/com/mechanica/engine/unit/vector/DynamicVector.kt b/common/src/main/kotlin/com/mechanica/engine/unit/vector/DynamicVector.kt index 0b9bb76d..384ffff0 100644 --- a/common/src/main/kotlin/com/mechanica/engine/unit/vector/DynamicVector.kt +++ b/common/src/main/kotlin/com/mechanica/engine/unit/vector/DynamicVector.kt @@ -10,27 +10,25 @@ interface DynamicVector : Vector { } fun set(vector: Vector) = set(vector.x, vector.y) - fun set(vector: LightweightVector) = set(vector.x, vector.y) + fun set(vector: InlineVector) = set(vector.x, vector.y) companion object { fun create(x: Double = 0.0, y: Double = 0.0) = object : DynamicVector { override var x = x override var y = y - override fun toString() = Companion.toString(this) + override fun toString() = Vector.toString(this) } - fun create(vector: LightweightVector) = object : DynamicVector { + fun create(vector: InlineVector) = object : DynamicVector { override var x = vector.x override var y = vector.y - override fun toString() = Companion.toString(this) + override fun toString() = Vector.toString(this) } fun create(vector: Vector) = object : DynamicVector { override var x = vector.x override var y = vector.y - override fun toString() = Companion.toString(this) + override fun toString() = Vector.toString(this) } - - fun toString(vector: DynamicVector) = "(${vector.x}, ${vector.y})" } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/mechanica/engine/unit/vector/LightweightVector.kt b/common/src/main/kotlin/com/mechanica/engine/unit/vector/InlineVector.kt similarity index 74% rename from common/src/main/kotlin/com/mechanica/engine/unit/vector/LightweightVector.kt rename to common/src/main/kotlin/com/mechanica/engine/unit/vector/InlineVector.kt index ff9db56e..a9f9d263 100644 --- a/common/src/main/kotlin/com/mechanica/engine/unit/vector/LightweightVector.kt +++ b/common/src/main/kotlin/com/mechanica/engine/unit/vector/InlineVector.kt @@ -1,9 +1,8 @@ package com.mechanica.engine.unit.vector -import com.mechanica.engine.unit.vector.Vector import java.lang.Float.intBitsToFloat -inline class LightweightVector(private val xy: Long): Vector { +inline class InlineVector(private val xy: Long): Vector { override val x get() = intBitsToFloat((xy shr 32).toInt()).toDouble() override val y get() = intBitsToFloat(xy.toInt()).toDouble() diff --git a/common/src/main/kotlin/com/mechanica/engine/unit/vector/Vector.kt b/common/src/main/kotlin/com/mechanica/engine/unit/vector/Vector.kt index da21b568..c27cb3f4 100644 --- a/common/src/main/kotlin/com/mechanica/engine/unit/vector/Vector.kt +++ b/common/src/main/kotlin/com/mechanica/engine/unit/vector/Vector.kt @@ -12,4 +12,25 @@ interface Vector { val theta get() = atan2(this.y, this.x).radians + companion object { + fun create(x: Double = 0.0, y: Double = 0.0) = object : Vector { + override val x = x + override val y = y + override fun toString() = Companion.toString(this) + } + + fun create(vector: InlineVector) = object : Vector { + override val x = vector.x + override val y = vector.y + override fun toString() = Companion.toString(this) + } + + fun create(vector: Vector) = object : Vector { + override val x = vector.x + override val y = vector.y + override fun toString() = Companion.toString(this) + } + + fun toString(vector: Vector) = "(${vector.x}, ${vector.y})" + } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorArray.kt b/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorArray.kt index 62308a31..bd114ab1 100644 --- a/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorArray.kt +++ b/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorArray.kt @@ -1,20 +1,20 @@ package com.mechanica.engine.unit.vector -class VectorArray(size: Int, init: (Int) -> LightweightVector) { +class VectorArray(size: Int, init: (Int) -> InlineVector) { private val longArray: LongArray = LongArray(size) { init(it).toLong() } val size: Int get() = longArray.size - operator fun get(index: Int) = LightweightVector(longArray[index]) + operator fun get(index: Int) = InlineVector(longArray[index]) - operator fun set(index: Int, vector: LightweightVector) { + operator fun set(index: Int, vector: InlineVector) { longArray[index] = vector.toLong() } - fun forEach(block: (LightweightVector) -> Unit) { + fun forEach(block: (InlineVector) -> Unit) { for (l in longArray) { - block(LightweightVector(l)) + block(InlineVector(l)) } } } \ No newline at end of file diff --git a/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorTools.kt b/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorTools.kt index 5ce3e100..81820a88 100644 --- a/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorTools.kt +++ b/common/src/main/kotlin/com/mechanica/engine/unit/vector/VectorTools.kt @@ -1,8 +1,10 @@ @file:Suppress("unused") // There will be many functions here that go unused most of the time package com.mechanica.engine.unit.vector -import com.mechanica.engine.unit.angle.Angle -import kotlin.math.* +import com.mechanica.engine.unit.angle.Degree +import com.mechanica.engine.unit.angle.Radian +import kotlin.math.cos +import kotlin.math.sin /** @@ -10,49 +12,70 @@ import kotlin.math.* */ -fun vec(x: Number, y: Number): LightweightVector { +fun vec(x: Number, y: Number): InlineVector { val xBits = java.lang.Float.floatToRawIntBits(x.toFloat()) val yBits = java.lang.Float.floatToRawIntBits(y.toFloat()) val xyBits = (yBits.toLong() shl 32 ushr 32) or (xBits.toLong() shl 32) - return LightweightVector(xyBits) + return InlineVector(xyBits) } -fun vec(r: Number, theta: Angle): LightweightVector { +fun vec(r: Number, theta: Degree): InlineVector { val x = r.toDouble()*cos(theta.toRadians().toDouble()) val y = r.toDouble()*sin(theta.toRadians().toDouble()) return vec(x, y) } +fun vec(r: Number, theta: Radian): InlineVector { + val x = r.toDouble()*cos(theta.toDouble()) + val y = r.toDouble()*sin(theta.toDouble()) + return vec(x, y) +} + fun vec(vector: Vector) = vec(vector.x, vector.y) operator fun Vector.component1() = this.x operator fun Vector.component2() = this.y -operator fun LightweightVector.plus(other: LightweightVector) : LightweightVector { +operator fun InlineVector.plus(other: InlineVector) : InlineVector { return vec(this.x.toFloat() + other.x.toFloat(), this.y.toFloat() + other.y.toFloat()) } -operator fun LightweightVector.minus(other: LightweightVector) : LightweightVector { +operator fun InlineVector.minus(other: InlineVector) : InlineVector { return vec(this.x.toFloat() - other.x.toFloat(), this.y.toFloat() - other.y.toFloat()) } -operator fun LightweightVector.times(other: LightweightVector): Float = x.toFloat()* other.x.toFloat() + y.toFloat()* other.y.toFloat() +operator fun InlineVector.times(other: InlineVector): Float = x.toFloat()* other.x.toFloat() + y.toFloat()* other.y.toFloat() + +operator fun InlineVector.times(scale: Number) = vec(this.x.toFloat() * scale.toFloat(), this.y.toFloat() * scale.toFloat()) + +operator fun InlineVector.div(scale: Number) = vec(this.x.toFloat() / scale.toFloat(), this.y.toFloat() / scale.toFloat()) -operator fun LightweightVector.times(scale: Number) = vec(this.x.toFloat() * scale.toFloat(), this.y.toFloat() * scale.toFloat()) +operator fun InlineVector.unaryMinus() = vec(-this.x.toFloat(), -this.y.toFloat()) -operator fun LightweightVector.div(scale: Number) = vec(this.x.toFloat() / scale.toFloat(), this.y.toFloat() / scale.toFloat()) +infix fun InlineVector.equals(other: InlineVector) = this.x.toFloat() == other.x.toFloat() && this.y.toFloat() == other.y.toFloat() -operator fun LightweightVector.unaryMinus() = vec(-this.x.toFloat(), -this.y.toFloat()) +operator fun DynamicVector.plusAssign(vector: Vector) { + this.plusAssign(vec(vector)) +} -infix fun LightweightVector.equals(other: LightweightVector) = this.x.toFloat() == other.x.toFloat() && this.y.toFloat() == other.y.toFloat() +operator fun DynamicVector.minusAssign(vector: Vector) { + this.minusAssign(vec(vector)) +} +operator fun DynamicVector.plusAssign(vector: InlineVector) { + this.set(vec(this) + vector) +} + +operator fun DynamicVector.minusAssign(vector: InlineVector) { + this.set(vec(this) - vector) +} fun Vector.distanceTo(other: Vector): Float { return (vec(this) - vec(other)).r.toFloat() } -fun LightweightVector.normalize() = vec(1.0, this.theta) +fun InlineVector.normalize() = vec(1.0, this.theta) -fun LightweightVector.dot(other: LightweightVector) = this.x*other.x + this.y*other.y +fun InlineVector.dot(other: InlineVector) = this.x*other.x + this.y*other.y -fun LightweightVector.cross(other: LightweightVector) = (this.x * other.y - this.y * other.x) +fun InlineVector.cross(other: InlineVector) = (this.x * other.y - this.y * other.x) diff --git a/common/src/main/kotlin/com/mechanica/engine/util/ValueStatistics.kt b/common/src/main/kotlin/com/mechanica/engine/util/ValueStatistics.kt new file mode 100644 index 00000000..9ad8f62e --- /dev/null +++ b/common/src/main/kotlin/com/mechanica/engine/util/ValueStatistics.kt @@ -0,0 +1,58 @@ +package com.mechanica.engine.util + +import kotlin.math.sqrt + +class ValueStatistics( + private val startingValue: Double, + val sampleSize: Double) { + + var average: Double = startingValue + private set + + var variance: Double = startingValue*0.5 + private set + + val standardDeviation: Double + get() = sqrt(variance) + + var lastValue = startingValue + private set + + var lastVariance = variance + private set + + val lastStandardDeviation: Double + get() = sqrt(variance) + + val isSteady: Boolean + get() = variance < 0.25*average*average + + fun add(value: Double): ValueStatistics { + lastValue = value + addToAverage(value) + lastVariance = varianceOf(value) + addToVariance(lastVariance) + return this + } + + fun lastValueIsWithinDeviation(multiple: Double) = isWithinDeviation(lastValue, multiple) + + fun isWithinDeviation(value: Double, multiple: Double): Boolean { + val deviation = multiple*standardDeviation + return value > (average - deviation) && value < (average + deviation) + } + + private fun addToAverage(value: Double) { + average += (value - average) / sampleSize + } + + private fun addToVariance(value: Double) { + variance += (value - variance)/sampleSize + } + + private fun varianceOf(value: Double): Double { + val diff = (value - average) + return diff*diff + } + +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/mechanica/engine/util/extensions/Arrays.kt b/common/src/main/kotlin/com/mechanica/engine/util/extensions/Arrays.kt index a5e67492..708023e0 100644 --- a/common/src/main/kotlin/com/mechanica/engine/util/extensions/Arrays.kt +++ b/common/src/main/kotlin/com/mechanica/engine/util/extensions/Arrays.kt @@ -2,10 +2,9 @@ package com.mechanica.engine.util.extensions -import com.mechanica.engine.unit.vector.LightweightVector +import com.mechanica.engine.unit.vector.InlineVector import com.mechanica.engine.unit.vector.Vector import com.mechanica.engine.unit.vector.VectorArray -import java.nio.FloatBuffer /** * Created by domin on 02/11/2017. @@ -97,14 +96,14 @@ fun FloatArray.fill(array: VectorArray, start: Int = 0, end: Int = (array.size + } } -fun FloatArray.fill(arrayList: Array, start: Int = 0, end: Int = (arrayList.size + start)) { +fun FloatArray.fill(arrayList: Array, start: Int = 0, end: Int = (arrayList.size + start)) { for (i in start until end) { this[i*3] = arrayList[i].x.toFloat() this[i*3 + 1] = arrayList[i].y.toFloat() } } -fun FloatArray.fillLightWeight(iterator: Iterator) { +fun FloatArray.fillLightWeight(iterator: Iterator) { for ((i, v) in iterator.withIndex()) { this[i*3] = v.x.toFloat() this[i*3 + 1] = v.y.toFloat() @@ -137,6 +136,12 @@ inline fun Array.foriIndexed(action: (E, Int) -> Unit) { } } +inline fun withEach(array: Array, operation: E.() -> Unit) { + for (i in array.indices) { + operation(array[i]) + } +} + fun List<*>.indexLooped(index: Int): Int { return when { index < 0 -> this.size + index diff --git a/common/src/main/kotlin/com/mechanica/engine/util/extensions/Matrices.kt b/common/src/main/kotlin/com/mechanica/engine/util/extensions/Matrices.kt index 9dba3805..a2d00795 100644 --- a/common/src/main/kotlin/com/mechanica/engine/util/extensions/Matrices.kt +++ b/common/src/main/kotlin/com/mechanica/engine/util/extensions/Matrices.kt @@ -1,10 +1,10 @@ package com.mechanica.engine.util.extensions import com.mechanica.engine.memory.SimulatedStack +import com.mechanica.engine.unit.vector.InlineVector +import com.mechanica.engine.unit.vector.vec import org.joml.Matrix4f import org.joml.Vector3f -import com.mechanica.engine.unit.vector.LightweightVector -import com.mechanica.engine.unit.vector.vec private val stack = SimulatedStack { Vector3f() } @@ -20,7 +20,7 @@ val Matrix4f.scaleY: Float it.y } -val Matrix4f.scaleXY: LightweightVector +val Matrix4f.scaleXY: InlineVector get() = getProperty2f { getScale(it) vec(it.x, it.y) @@ -44,7 +44,7 @@ val Matrix4f.translateY: Float it.y } -val Matrix4f.translateXY: LightweightVector +val Matrix4f.translateXY: InlineVector get() = getProperty2f { getTranslation(it) vec(it.x, it.y) @@ -63,7 +63,7 @@ private inline fun getProperty(getter: (Vector3f) -> Float): Float { return value } -private inline fun getProperty2f(getter: (Vector3f) -> LightweightVector): LightweightVector { +private inline fun getProperty2f(getter: (Vector3f) -> InlineVector): InlineVector { val vec = stack.allocate() val value = getter(vec) stack.freeMemory(vec) diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/context/DesktopApplication.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/context/DesktopApplication.kt index b48c782f..f41d3ec9 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/context/DesktopApplication.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/context/DesktopApplication.kt @@ -2,12 +2,13 @@ package com.mechanica.engine.context import com.mechanica.engine.context.loader.LwjglLoader import com.mechanica.engine.display.Window +import org.lwjgl.glfw.GLFW class DesktopApplication : Application { - override fun initialize(window: Window) { - GLContext.initialize(window) + override fun initialize(mainWindow: Window) { + GLContext.initialize(mainWindow) val callbacks = GLInitializer.initialize(LwjglLoader()) - GLContext.setCallbacks(window, callbacks) + GLContext.setCallbacks(mainWindow, callbacks) ALContext.initialize() } @@ -20,4 +21,11 @@ class DesktopApplication : Application { override fun startFrame() { GLContext.startFrame() } + + override fun activateContext(window: Window?) { + val windowId = window?.id ?: 0L + if (GLFW.glfwGetCurrentContext() != windowId) { + GLContext.initContext(windowId) + } + } } \ No newline at end of file diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/context/GLContext.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/context/GLContext.kt index a13dd4ed..114d2d62 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/context/GLContext.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/context/GLContext.kt @@ -53,7 +53,7 @@ object GLContext : Version { initialized = true } - private fun initContext(windowId: Long) { + fun initContext(windowId: Long) { GLFW.glfwMakeContextCurrent(windowId) GL.createCapabilities() } diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDisplayLoader.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDisplayLoader.kt index 2dc116f4..b6a00333 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDisplayLoader.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDisplayLoader.kt @@ -6,16 +6,16 @@ import com.mechanica.engine.display.Monitor import com.mechanica.engine.display.Window class LwjglDisplayLoader : DisplayLoader { - override fun createWindow(title: String, width: Int, height: Int): Window { - return GLFWWindow.create(title, width, height) + override fun createWindow(title: String, width: Int, height: Int, sharedWith: Window?): Window { + return GLFWWindow.create(title, width, height, sharedWith) } - override fun createWindow(title: String, monitor: Monitor): Window { - return GLFWWindow.create(title, monitor) + override fun createWindow(title: String, monitor: Monitor, sharedWith: Window?): Window { + return GLFWWindow.create(title, monitor, sharedWith) } - override fun createWindow(title: String, width: Int, height: Int, monitor: Monitor): Window { - return GLFWWindow.create(title, width, height, monitor) + override fun createWindow(title: String, width: Int, height: Int, monitor: Monitor, sharedWith: Window?): Window { + return GLFWWindow.create(title, width, height, monitor, sharedWith) } override val allMonitors: Array diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDrawerLoader.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDrawerLoader.kt new file mode 100644 index 00000000..39ee8a5f --- /dev/null +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglDrawerLoader.kt @@ -0,0 +1,102 @@ +package com.mechanica.engine.context.loader + +import com.mechanica.engine.models.Model +import org.lwjgl.opengl.GL42.* +import java.nio.IntBuffer + +class LwjglDrawerLoader(val mode: Int, val type: Int) : GLDrawerLoader { + + override fun drawElements(model: Model) { + model.isValid { + glDrawElements(mode, model.vertexCount, type, 0) + } + } + + override fun drawElementsBaseVertex(model: Model, baseVertex: Int) { + model.isValid { + glDrawElementsBaseVertex(mode, model.vertexCount, type, 0, baseVertex) + } + } + + override fun drawRangeElements(model: Model, start: Int, end: Int) { + model.isValid { + glDrawRangeElements(mode, start, end, model.vertexCount, type, 0) + } + } + + override fun drawRangeElementsBaseVertex(model: Model, start: Int, end: Int, baseVertex: Int) { + model.isValid { + glDrawRangeElementsBaseVertex(mode, start, end, model.vertexCount, type, 0, baseVertex) + } + } + + override fun drawElementsIndirect(model: Model) { + model.isValid { + glDrawElementsIndirect(mode, type, 0) + } + } + + override fun multiDrawElements(models: Array) { + TODO("not implemented") + } + + override fun multiDrawElementsBaseVertex(models: Array, baseVertex: IntBuffer) { + TODO("not implemented") + } + + override fun multiDrawElementsBaseVertex(models: Array, baseVertex: Int) { + TODO("not implemented") + } + + override fun drawElementsInstanced(model: Model, instanceCount: Int) { + model.isValid { + glDrawElementsInstanced(mode, model.vertexCount, type, 0, instanceCount) + } + } + + override fun drawElementsInstancedBaseVertex(model: Model, instanceCount: Int, baseVertex: Int) { + model.isValid { + glDrawElementsInstancedBaseVertex(mode, model.vertexCount, type, 0, instanceCount, baseVertex) + } + } + + override fun drawElementsInstancedBaseInstance(model: Model, instanceCount: Int, baseInstance: Int) { + model.isValid { + glDrawElementsInstancedBaseInstance(mode, model.vertexCount, type, 0, instanceCount, baseInstance) + } + } + + override fun drawArrays(model: Model) { + model.isValid { + glDrawArrays(mode, 0, model.vertexCount) + } + } + + override fun drawArraysIndirect(model: Model) { + model.isValid { + glDrawArraysIndirect(mode, 0) + } + } + + override fun multiDrawArrays(models: Array) { + TODO("not implemented") + } + + override fun drawArraysInstanced(model: Model, instanceCount: Int) { + model.isValid { + glDrawArraysInstanced(mode, 0, model.vertexCount, instanceCount) + } + } + + override fun drawArraysInstancedBaseInstance(model: Model, instanceCount: Int, baseInstance: Int) { + model.isValid { + glDrawArraysInstancedBaseInstance(mode, 0, model.vertexCount, instanceCount, baseInstance) + } + } + + private inline fun Model.isValid(action: () -> Unit) { + if (vertexCount > 0) { + action() + } + } +} \ No newline at end of file diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglFontLoader.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglFontLoader.kt index 1a6fca54..18a4126e 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglFontLoader.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglFontLoader.kt @@ -1,16 +1,9 @@ package com.mechanica.engine.context.loader -import com.mechanica.engine.text.LwjglStandardFont -import com.mechanica.engine.text.Font import com.mechanica.engine.resources.Resource import com.mechanica.engine.text.FontAtlasConfiguration +import com.mechanica.engine.text.LwjglStandardFont class LwjglFontLoader : FontLoader { - override val defaultFont: Font by lazy { LwjglStandardFont(Resource("res/fonts/Roboto-Regular.ttf")) { - width = 1024 - height = 1024 - characterSize = 200f - } } - override fun font(res: Resource, initializer: FontAtlasConfiguration.() -> Unit) = LwjglStandardFont(res, initializer) } diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglGraphicsLoader.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglGraphicsLoader.kt index 9758e1c3..17ed653a 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglGraphicsLoader.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/context/loader/LwjglGraphicsLoader.kt @@ -1,25 +1,17 @@ package com.mechanica.engine.context.loader -import com.mechanica.engine.models.Image -import com.mechanica.engine.models.Model import com.mechanica.engine.resources.Resource import com.mechanica.engine.shader.LwjglImage -import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL42 class LwjglGraphicsLoader : GraphicsLoader { + override val glDrawer: GLDrawerLoader = LwjglDrawerLoader(GL42.GL_TRIANGLES, GL42.GL_UNSIGNED_SHORT) + override val glPointDrawer: GLDrawerLoader = LwjglDrawerLoader(GL42.GL_POINTS, GL42.GL_UNSIGNED_SHORT) + override val glLineLoopDrawer: GLDrawerLoader = LwjglDrawerLoader(GL42.GL_LINE_LOOP, GL42.GL_UNSIGNED_SHORT) + override val glLinesDrawer: GLDrawerLoader = LwjglDrawerLoader(GL42.GL_LINES, GL42.GL_UNSIGNED_SHORT) + override val glLineStripDrawer: GLDrawerLoader = LwjglDrawerLoader(GL42.GL_LINE_STRIP, GL42.GL_UNSIGNED_SHORT) override fun loadImage(id: Int) = LwjglImage(id) override fun loadImage(res: Resource) = com.mechanica.engine.utils.loadImage(res) - override fun drawArrays(model: Model) { - if (model.vertexCount > 0) { - GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, model.vertexCount) - } - } - - override fun drawElements(model: Model) { - if (model.vertexCount > 0) { - GL11.glDrawElements(GL11.GL_TRIANGLES, model.vertexCount, GL11.GL_UNSIGNED_SHORT, 0) - } - } } \ No newline at end of file diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/display/GLFWWindow.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/display/GLFWWindow.kt index 0ac38f20..d24d6528 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/display/GLFWWindow.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/display/GLFWWindow.kt @@ -12,8 +12,8 @@ import org.lwjgl.system.MemoryUtil import java.nio.ByteBuffer -class GLFWWindow private constructor(width: Int, height: Int, override val title: String, monitor: Monitor?) : Window { - override val id: Long= glfwCreateWindow(width, height, title, monitor?.id ?: MemoryUtil.NULL, MemoryUtil.NULL) +class GLFWWindow private constructor(width: Int, height: Int, override val title: String, monitor: Monitor?, sharedWith: Window?) : Window { + override val id: Long var hasInitialized = false private set @@ -117,6 +117,11 @@ class GLFWWindow private constructor(width: Int, height: Int, override val title private val callbackList = ArrayList<((Window) -> Unit)>() init { + if (sharedWith != null) { + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE) + } + id = glfwCreateWindow(width, height, title, monitor?.id ?: MemoryUtil.NULL, sharedWith?.id ?: MemoryUtil.NULL) + if (id == MemoryUtil.NULL) throw RuntimeException("Failed to create the GLFW window") hasInitialized = true @@ -237,12 +242,16 @@ class GLFWWindow private constructor(width: Int, height: Int, override val title } override fun setFullscreen(monitor: Monitor) { + val vSync = vSync val vidMode = if (monitor is GLFWMonitor) monitor.currentVideoMode else throw IllegalStateException("Unable to put window into fullscreen mode") glfwSetWindowMonitor(id, monitor.id, 0, 0, vidMode.width(), vidMode.height(), vidMode.refreshRate()) + this.vSync = vSync } override fun setFullscreen(monitor: Monitor, width: Int, height: Int, refreshRate: Int) { + val vSync = vSync glfwSetWindowMonitor(id, monitor.id, 0, 0, width, height, refreshRate) + this.vSync = vSync } override fun exitFullscreen(width: Int, height: Int) { @@ -302,12 +311,12 @@ class GLFWWindow private constructor(width: Int, height: Int, override val title var isChanging: Boolean) : Window.Dimension companion object { - fun create(title: String, width: Int, height: Int): Window { + fun create(title: String, width: Int, height: Int, sharedWith: Window? = null): Window { GLFWContext.initialize() - return GLFWWindow(width, height, title, null) + return GLFWWindow(width, height, title, null, sharedWith) } - fun create(title: String, monitor: Monitor): Window { + fun create(title: String, monitor: Monitor, sharedWith: Window? = null): Window { GLFWContext.initialize() val vidMode = if (monitor is GLFWMonitor) monitor.currentVideoMode else throw IllegalStateException("Unable to create window") val width = vidMode.width() @@ -317,12 +326,12 @@ class GLFWWindow private constructor(width: Int, height: Int, override val title glfwWindowHint(GLFW_BLUE_BITS, vidMode.blueBits()) glfwWindowHint(GLFW_REFRESH_RATE, vidMode.refreshRate()) - return GLFWWindow(width, height, title, monitor) + return GLFWWindow(width, height, title, monitor, sharedWith) } - fun create(title: String, width: Int, height: Int, monitor: Monitor): Window { + fun create(title: String, width: Int, height: Int, monitor: Monitor, sharedWith: Window? = null): Window { GLFWContext.initialize() - return GLFWWindow(width, height, title, monitor) + return GLFWWindow(width, height, title, monitor, sharedWith) } } diff --git a/desktop-application/src/main/kotlin/com/mechanica/engine/utils/GLUtils.kt b/desktop-application/src/main/kotlin/com/mechanica/engine/utils/GLUtils.kt index 052edcce..c2be454e 100644 --- a/desktop-application/src/main/kotlin/com/mechanica/engine/utils/GLUtils.kt +++ b/desktop-application/src/main/kotlin/com/mechanica/engine/utils/GLUtils.kt @@ -3,11 +3,12 @@ package com.mechanica.engine.utils import com.mechanica.engine.models.Image import com.mechanica.engine.resources.Resource import org.lwjgl.BufferUtils -import org.lwjgl.opengl.* +import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11.GL_RGBA -import org.lwjgl.opengl.GL11.GL_STENCIL_BUFFER_BIT +import org.lwjgl.opengl.GL12 +import org.lwjgl.opengl.GL30 +import org.lwjgl.opengl.GL40 import org.lwjgl.stb.STBImage -import java.lang.UnsupportedOperationException import java.nio.ByteBuffer @@ -36,6 +37,8 @@ fun loadImage(buffer: ByteBuffer, width: Int, height: Int, levels: Int = 4, form setMipmapping(buffer, width, height, levels, format) + check(GL11.glIsTexture(image)) { "Unable to load texture" } + return Image(image) } @@ -59,7 +62,7 @@ class ImageData(resource: Resource) { val heightBuffer = BufferUtils.createIntBuffer(1) val componentsBuffer = BufferUtils.createIntBuffer(1) - data = STBImage.stbi_load_from_memory(resource.buffer as ByteBuffer, widthBuffer, heightBuffer, componentsBuffer, 4) + data = STBImage.stbi_load_from_memory(resource.buffer, widthBuffer, heightBuffer, componentsBuffer, 4) width = widthBuffer.get() height = heightBuffer.get() } diff --git a/mechanica-ui/build.gradle.kts b/mechanica-ui/build.gradle.kts index b7dd86a1..9de6b49b 100644 --- a/mechanica-ui/build.gradle.kts +++ b/mechanica-ui/build.gradle.kts @@ -1,4 +1,3 @@ - plugins { kotlin("jvm") `java-library` @@ -9,6 +8,6 @@ dependencies { implementation(project(":desktop-application")) implementation(project(":mechanica")) - api("com.dubulduke.ui:DukeUI:0.1") + api(files("libs/DukeUI-0.1.jar")) } \ No newline at end of file diff --git a/mechanica-ui/libs/DukeUI-0.1.jar b/mechanica-ui/libs/DukeUI-0.1.jar new file mode 100644 index 00000000..9148343f Binary files /dev/null and b/mechanica-ui/libs/DukeUI-0.1.jar differ diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/UIUtils.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/UIUtils.kt new file mode 100644 index 00000000..726ebca7 --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/UIUtils.kt @@ -0,0 +1,42 @@ +package com.mechanica.engine.duke + +import com.duke.ui.hierarchy.NodeData +import com.duke.ui.output.RenderDescription +import com.mechanica.engine.drawer.Drawer +import com.mechanica.engine.duke.elements.ElementDrawer +import com.mechanica.engine.duke.style.Style + + +fun RenderDescription.defaultDraw(draw: Drawer, style: Style) { + if (style.color.a > 0.0 && style.isVisible) { + draw.ui.color(style.color).rectangle(x, y, width, height) + } +} + +class NodeRendererToDrawer(private val drawOperation: ElementDrawer) : NodeData.Renderer { + private val dataToDrawer = NodeDataToDrawer() + override fun render(e: NodeData) { + dataToDrawer.render(e) {drawer, style -> + with(drawOperation) { + draw(drawer, style) + } + } + } + +} + +class NodeDataToDrawer { + inline fun render(e: NodeData, drawOperation: RenderDescription.(draw: Drawer, style: Style) -> Unit) { + val draw = e.contextData + val d = e.description + val style = e.nodeData ?: defaultStyle + + if (draw is Drawer && style is Style) { + drawOperation(d, draw, style) + } + } + + companion object { + val defaultStyle = Style() + } +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/context/DukeUIScene.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/context/DukeUIScene.kt new file mode 100644 index 00000000..8cc68b00 --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/context/DukeUIScene.kt @@ -0,0 +1,50 @@ +package com.mechanica.engine.duke.context + +import com.duke.ui.hierarchy.DukeUI +import com.duke.ui.hierarchy.NodeData +import com.duke.ui.hierarchy.Viewport +import com.duke.ui.hierarchy.Window +import com.mechanica.engine.drawer.Drawer +import com.mechanica.engine.duke.NodeRendererToDrawer +import com.mechanica.engine.duke.defaultDraw +import com.mechanica.engine.duke.elements.Element +import com.mechanica.engine.duke.elements.ElementDrawer +import com.mechanica.engine.scenes.scenes.UIScene +import com.mechanica.engine.util.extensions.fori + +abstract class DukeUIScene : UIScene() { + private val context: DukeUI + internal val firstNode: NodeData + get() = context.firstElement + + val list = ArrayList() + + init { + val viewport = Viewport(0.0, 0.0, camera.width, -camera.height) + val window = Window(-camera.width/2.0, -camera.height/2.0, camera.width, camera.height) + + context = DukeUI(window, viewport) + + context.defaultRender = NodeRendererToDrawer { draw, style -> defaultDraw(draw, style) } + } + + override fun render(draw: Drawer) { + context.data = draw + context.ui { getElement { OuterElement() }.ui() } + } + + inline fun getElement(initiator: (DukeUIScene) -> E): E { + list.fori { + if (it is E) return it + } + val new = initiator(this) + list.add(new) + return new + } + + abstract fun Element.ui() + + private inner class OuterElement : Element(this) { + override val elementDrawer = ElementDrawer {_, _ -> } + } +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/CustomElement.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/CustomElement.kt new file mode 100644 index 00000000..a0457c9a --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/CustomElement.kt @@ -0,0 +1,10 @@ +package com.mechanica.engine.duke.elements + +import com.mechanica.engine.duke.context.DukeUIScene +import com.mechanica.engine.duke.defaultDraw + +class CustomElement(uiScene: DukeUIScene) : Element(uiScene) { + override var elementDrawer = ElementDrawer {draw, style -> + this.defaultDraw(draw, style) + } +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/DefaultElement.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/DefaultElement.kt new file mode 100644 index 00000000..36d6b512 --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/DefaultElement.kt @@ -0,0 +1,11 @@ +package com.mechanica.engine.duke.elements + +import com.mechanica.engine.duke.context.DukeUIScene +import com.mechanica.engine.duke.defaultDraw + +class DefaultElement(scene: DukeUIScene) : Element(scene) { + + override val elementDrawer = ElementDrawer {draw, style -> + this.defaultDraw(draw, style) + } +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/Element.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/Element.kt new file mode 100644 index 00000000..a7c825ee --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/Element.kt @@ -0,0 +1,73 @@ +package com.mechanica.engine.duke.elements + +import com.duke.ui.hierarchy.NodeData +import com.duke.ui.layout.DynamicLayout +import com.mechanica.engine.duke.NodeRendererToDrawer +import com.mechanica.engine.duke.context.DukeUIScene +import com.mechanica.engine.duke.style.Style +import com.mechanica.engine.input.mouse.Mouse +import com.mechanica.engine.text.Text + +abstract class Element(val scene: DukeUIScene) { + var node: NodeData = scene.firstNode + + abstract val elementDrawer: ElementDrawer + + private val nodeToDrawer by lazy { NodeRendererToDrawer(elementDrawer) } + + val layout: DynamicLayout + get() = node.layout + + val virtualLayout: DynamicLayout + get() = node.virtualLayout + + open val style: Style + get() = (node.nodeData as? Style) ?: Style().also { node.nodeData = it } + + val isHovering: Boolean + get() = node.description.contains(Mouse.ui.x, Mouse.ui.y) + + inline fun switchElement(initializer: (DukeUIScene) -> E) : E { + val new = scene.getElement(initializer) + new.node = node + return new + } + + inline fun switchElement(initializer: (DukeUIScene) -> E, operation: E.() -> Unit) { + val new = switchElement(initializer) + operation(new) + node = new.node + } + + inline fun switchElementAndAddChild(content: Element.() -> Unit, initializer: (DukeUIScene) -> E) { + switchElement(initializer) { + addChild(content) + } + } + + inline fun addChild(content: Element.() -> Unit) { + val oldNode = updateForNextNode(node) + content(this) + node.renderOnce() + this.node = oldNode + } + + fun updateForNextNode(node: NodeData): NodeData { + val oldNode = this.node + this.node = node.insertNode() + + oldNode.renderOnce() + this.node.setRenderer(nodeToDrawer) + + return oldNode + } + + operator fun String.unaryPlus() { + text(this) + } + + operator fun Text.unaryPlus() { + text(this) + } + +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/ElementUtils.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/ElementUtils.kt new file mode 100644 index 00000000..a8a10fcf --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/ElementUtils.kt @@ -0,0 +1,46 @@ +package com.mechanica.engine.duke.elements + +import com.duke.ui.layout.DynamicLayout +import com.duke.ui.layout.Layout +import com.duke.ui.output.RenderDescription +import com.mechanica.engine.animation.AnimationFormula +import com.mechanica.engine.drawer.Drawer +import com.mechanica.engine.duke.style.Style + + +fun interface ElementDrawer { + fun RenderDescription.draw(draw: Drawer, style: Style) +} + +inline fun Element.ifHovering(action: () -> Unit) { + if (isHovering) { + action() + } +} + +inline fun Element.ifNotHovering(action: () -> Unit) { + if (!isHovering) { + action() + } +} + +inline fun Element.animateLayoutUsing( + animationFormula: AnimationFormula, + block: DynamicLayout.(parent: Layout, sibling: Layout) -> Unit) { + + val x = layout.x + val y = layout.y + + val width = layout.width + val height = layout.height + + block(layout, layout.parent, layout.sibling) + + val value = animationFormula.value + + layout.x = x + (layout.x - x)*value + layout.y = y + (layout.y - y)*value + layout.width = width + (layout.width - width)*value + layout.height = height + (layout.height - height)*value +} + diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/Elements.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/Elements.kt new file mode 100644 index 00000000..a7126e06 --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/Elements.kt @@ -0,0 +1,56 @@ +package com.mechanica.engine.duke.elements + +import com.duke.ui.output.RenderDescription +import com.mechanica.engine.drawer.Drawer +import com.mechanica.engine.duke.NodeDataToDrawer +import com.mechanica.engine.models.Image +import com.mechanica.engine.text.Text + +inline fun Element.e(content: Element.() -> Unit) { + switchElementAndAddChild(content) { DefaultElement(it) } +} + +inline fun Element.image(image: Image? = null, content: ImageElement.() -> Unit) { + switchElement({ ImageElement(it) }, { addChildToImageElement(image, content) }) +} + +fun Element.text(text: String?) { + val style = this.style + switchElement({ TextElement(it) }, { addChildToTextElement(text, style) }) +} + +fun Element.text(text: Text) { + val style = this.style + switchElement({ TextElement(it) }, { addChildToTextElement(text, style) }) +} + +inline fun Element.div(content: Element.() -> Unit) { + e { + layout.edit { _, s -> top = s.bottom } + content() + } +} + +inline fun Element.span(content: Element.() -> Unit) { + e { + layout.edit { _, s -> left = s.right } + content() + } +} + +fun Element.custom(drawer: ElementDrawer) { + val e = switchElement { CustomElement(it) } + e.elementDrawer = drawer + node = e.node +} + +val nodeDataToDrawer = NodeDataToDrawer() + +inline fun Element.drawer(content: RenderDescription.(draw: Drawer) -> Unit) { + this.node.renderOnce() + nodeDataToDrawer.render(this.node) {draw, _ -> + content(draw) + } +} + + diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/ImageElement.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/ImageElement.kt new file mode 100644 index 00000000..a3309043 --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/ImageElement.kt @@ -0,0 +1,26 @@ +package com.mechanica.engine.duke.elements + +import com.mechanica.engine.duke.context.DukeUIScene +import com.mechanica.engine.models.Image + +class ImageElement(scene: DukeUIScene) : Element(scene) { + var image: Image? = null + override val elementDrawer = ElementDrawer { draw, style -> + val image = this@ImageElement.image + if (image != null) { + draw.ui.image(image, x, y + height, width, -height) + } + } + + inline fun addChildToImageElement(image: Image?, block: ImageElement.() -> Unit) { + val oldNode = updateForNextNode(node) + val oldImage = this.image + this.image = image + + block(this) + node.renderOnce() + + this.node = oldNode + this.image = oldImage + } +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/TextElement.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/TextElement.kt new file mode 100644 index 00000000..fbcd269a --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/TextElement.kt @@ -0,0 +1,51 @@ +package com.mechanica.engine.duke.elements + +import com.mechanica.engine.duke.context.DukeUIScene +import com.mechanica.engine.duke.style.Style +import com.mechanica.engine.text.Text + +class TextElement(scene: DukeUIScene) : Element(scene) { + var text = Text("") + + private val emptyString = "" + + override var style: Style + get() = super.style + private set(value) { + node.nodeData = value + } + + override val elementDrawer = ElementDrawer { draw, style -> + with(style.textFormat) { + draw.ui.color(color) + .origin.normalized(alignment.x, 1.0 - alignment.y) + .text(text, size, x + alignment.x*width, y + alignment.y*height) + } + } + + fun addChildToTextElement(text: String?, parentStyle: Style) { + this.text.update(text ?: emptyString, parentStyle.textFormat.font ?: this.text.font) + + addChildToTextElement(this.text, parentStyle) + + this.text.string = emptyString + } + + fun addChildToTextElement(text: Text, parentStyle: Style) { + val oldNode = updateForNextNode(node) + val oldText = this.text + + this.style = parentStyle + this.text = text + + val font = style.textFormat.font + if (font != null) { + text.update(text.string, font) + } + + node.renderOnce() + + this.node = oldNode + this.text = oldText + } +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/UIComponent.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/UIComponent.kt new file mode 100644 index 00000000..69406ae3 --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/elements/UIComponent.kt @@ -0,0 +1,11 @@ +package com.mechanica.engine.duke.elements + +import com.mechanica.engine.scenes.processes.Process + +abstract class UIComponent : Process() { + operator fun invoke(element: Element) { + element.ui() + } + + abstract fun Element.ui() +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/style/Style.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/style/Style.kt new file mode 100644 index 00000000..fb9ed13c --- /dev/null +++ b/mechanica-ui/src/main/kotlin/com/mechanica/engine/duke/style/Style.kt @@ -0,0 +1,41 @@ +package com.mechanica.engine.duke.style + +import com.mechanica.engine.color.DynamicColor +import com.mechanica.engine.text.Font +import com.mechanica.engine.unit.vector.DynamicVector + +class Style { + val color: DynamicColor = DynamicColor(1.0, 1.0, 1.0, 0.0) + + var isVisible: Boolean = true + + val textFormat = TextFormat() + + var radius: Double = 0.0 + + inline fun edit(editor: Style.() -> Unit) { + editor(this) + } + + class TextFormat { + val color: DynamicColor = DynamicColor(0.7, 0.7, 0.7, 1.0) + + var size: Double = 1.0 + + val alignment: DynamicVector = DynamicVector.create(0.0, 0.0) + + var font: Font? = null + + inline fun edit(editor: TextFormat.() -> Unit) { + editor(this) + } + } + + companion object { + fun createStyleSetter(block: Style.() -> Unit) = Styler(block) + } +} + +fun interface Styler { + fun Style.set() +} \ No newline at end of file diff --git a/mechanica-ui/src/main/kotlin/com/mechanica/engine/ui/Events.kt b/mechanica-ui/src/main/kotlin/com/mechanica/engine/ui/Events.kt deleted file mode 100644 index 257b7e21..00000000 --- a/mechanica-ui/src/main/kotlin/com/mechanica/engine/ui/Events.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.mechanica.engine.ui - -import com.dubulduke.ui.event.Event -import com.dubulduke.ui.render.RenderDescription -import com.mechanica.engine.input.mouse.Mouse -import com.mechanica.engine.ui.style.Style -import com.mechanica.engine.util.EventBoolean - -class Events(description: RenderDescription