diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ce1c62c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..df543e3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..87a20fc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..807ddda --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +
+

+ +[Back to Main Page](./README.md) +

+ +# Contributing + +
+ +Contributions to KaylibKit are more than welcome! I would highly suggest opening up an issue first to discuss the upcoming change. + +## Small guideline + +- Follow the same code structure. +- Comment your code. +- Ensure that your code is in the correct package. + +## How to + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..1273e5c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,50 @@ +
+

+ +[Back to Main Page](./README.md) +

+ +# Development + +
+ +## Table of Contents + +- [Development](#development) + - [Table of Contents](#table-of-contents) + - [Summary](#summary) + - [Roadmap](#roadmap) + - [Covered API](#covered-api) + - [Known Issues](#known-issues) + +## Summary + +**KaylibKit** is still in early stages of development. + +Kotlin/Native is known for its terrible performance on the desktop as it's currently not the focus for JetBrains so please don't expect amazing performance coming out of this just yet! +We can only hope and dream that JetBrains will one day decide to prioritise performance over portability at some point, and so this is why this library exists, for that one day to come. + +For most cases, while KaylibKit is still in early stages of development, all parts of Raylib have been wrapped apart from raygui which is planned for future. + +## Roadmap + +The current roadmap is: + +- [x] Add support for Window MinGW. +- [x] Add support for MacOS. +- [x] Better approach towards CStruct constructors. +- [ ] WASM Export. *(Needs new WASI/WASM backend from Jetbrains)* +- [ ] Cleanup the code and remove unnecessary mess. +- [ ] Optimise example codes. + +## Covered API + +- [x] raylib.h +- [x] raymath.h +- [x] easings.h +- [ ] raygui.h + +## Known Issues + +1. Performance on Windows is abysmal due to the `-femulated-tls` flag passed by Kotlin/Native that can't be removed. Linux and macOS is unaffected. +2. You're unable to pass path to resources module when using IntelliJ IDEA, all resources have to be located in the directory as your main entry code. Hopefully to be fixed by Jetbrains at some point. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..d25429b --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,54 @@ +
+

+ +[Back to Main Page](./README.md) +

+ +# Installation Guide + +
+ +## Table of Contents + +- [Installation Guide](#installation-guide) + - [Table of Contents](#table-of-contents) + - [Compatibility](#compatibility) + - [Dependencies](#dependencies) + - [Pre-compiled klibs](#pre-compiled-klibs) + - [Build It Yourself](#build-it-yourself) + +## Compatibility + +**Supported Platforms:** + +- [x] Linux +- [X] Windows +- [X] MacOS +- [ ] WASM *(Awaiting few WASM backend from Jetbrains)* + +## Dependencies + +You don't need to have Raylib installed on your system to use KaylibKit. + +## Gradle Package + +KaylibKit has its own Gradle package hosted on Codeberg as the previous usage of `klibs` is now broken. + +To add KaylibKit as a dependency to your project, firstly you have to add Codeberg URL to Maven Repository: + +```kotlin +repositories { + mavenCentral() + maven { url = uri("https://codeberg.org/api/packages/Kenta/maven") } +} +``` + +Now that Maven is aware of KaylibKit, we can simply add it as a dependency: + +```kotlin +implementation("com.prism-architect:kaylibkit-macos64-native:1.0.3") +// OR if you're on Linux: +implementation("com.prism-architect-kaylibkit-linux64-native:1.0.3") +``` + +You're now done and ready to play with KaylibKit! \ No newline at end of file diff --git a/LICENSE b/LICENSE index e0e3605..22bcd72 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,19 @@ zlib License -This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. +Copyright (c) 2023 Krystian Alabrudzinski (@Kenta) -Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. - 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: - 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source distribution. +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md index aa2c85f..5920f23 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,276 @@ -# KaylibKit +
+

-Raylib for Kotlin/Native \ No newline at end of file +# Kaylib - Raylib on Kotlin/Native + +

+ + [Documentation](https://Soutaisei.codeberg.page/kaylib/@pages/) • [Installation](./INSTALL.md) • [Development](./DEVELOPMENT.md) • [Contributing](./CONTRIBUTING.md) • [License](./LICENSE) + +
+ +**KaylibKit** is a [Kotlin/Native](https://kotlinlang.org/docs/native-overview.html) binding for [Raylib 4.5](http://www.raylib.com/) a simple and easy-to-use library to learn videogames programming. +_A successor to Kaylib (Now more user friendly!)_ + +Its purpose is to hide away the pain and horrid syntax of K/N cinterop and joy in using Kotlin with an amazing game development framework. + +This library is heavily influenced by [Raylib on Swift by STREGAsGate](https://github.com/STREGAsGate/Raylib). + +### Code Example + +
+Basic Window + +```kotlin +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kText.drawText +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - basic window") + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + drawing { + + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawText("Hello from Kotlin/Native", 190, 200, 20, lightGray) + //---------------------------------------------------------------------------------- + } + + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} +``` + +
+ +
+Basic Keyboard Input + +```kotlin +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.KeyboardKey +import kaylibkit.kMath.kVector2 +import kaylibkit.kShapes.drawCircle +import kaylibkit.kText.drawText +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - keyboard input") + + val ballPosition = kVector2(SCREEN_WIDTH/2F, SCREEN_HEIGHT/2F) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + if (isKeyDown(KeyboardKey.RIGHT)) { ballPosition.x += 2 } + if (isKeyDown(KeyboardKey.LEFT)) { ballPosition.x -= 2 } + if (isKeyDown(KeyboardKey.UP)) { ballPosition.y -= 2 } + if (isKeyDown(KeyboardKey.DOWN)) { ballPosition.y += 2 } + //---------------------------------------------------------------------------------- + + drawing { + + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawText("move the ball with arrow keys", 10, 10, 20, darkGray) + + drawCircle(ballPosition, 50F, maroon) + //---------------------------------------------------------------------------------- + } + + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} +``` + +
+ +
+Texture Loading and Drawing + +```kotlin +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kText.drawText +import kaylibkit.kTextures.drawTexture +import kaylibkit.kTextures.loadTexture +import kaylibkit.kTextures.unloadTexture +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [textures] example - texture loading and drawing") + //-------------------------------------------------------------------------------------- + + // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) + val texture = loadTexture("resources/raylib_logo.png") // Texture loading + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + drawing { + + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawTexture(texture, SCREEN_WIDTH/2 - texture.width/2, SCREEN_HEIGHT/2 - texture.height/2, white) + + drawText("this IS a texture!", 360, 370, 10, gray) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + unloadTexture(texture) // Texture unloading + + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} +``` + +
+ +
+3D Free Camera + +```kotlin +iimport kaylibkit.kCamera.setCameraMode + import kaylibkit.kCamera.updateCamera + import kaylibkit.kCore.* + import kaylibkit.kEnums.CameraMode + import kaylibkit.kEnums.CameraProjection + import kaylibkit.kEnums.ConfigFlag + import kaylibkit.kEnums.KeyboardKey + import kaylibkit.kMath.kVector3 + import kaylibkit.kMath.setZero + import kaylibkit.kModels.drawCube + import kaylibkit.kModels.drawCubeWires + import kaylibkit.kModels.drawGrid + import kaylibkit.kShapes.drawRectangle + import kaylibkit.kShapes.drawRectangleLines + import kaylibkit.kText.drawText + import kaylibkit.kTypes.kCamera3D + import kaylibkit.kUtils.fade + import kaylibc.* + + const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - 3d camera free") + + val camera = kCamera3D(kVector3(10F, 10F, 10F), kVector3(), kVector3(0F, 1F, 0F), 45F, CameraProjection.PERSPECTIVE) + val cubePosition = kVector3() + + setCameraMode(camera, CameraMode.FREE) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + updateCamera(camera) + if (isKeyDown(KeyboardKey.Z)) { camera.target.setZero() } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + mode3D(camera) { + drawCube(cubePosition, 2F, 2F, 2F, red) + drawCubeWires(cubePosition, 2F, 2F, 2F, maroon) + + drawGrid(10, 1F) + + } + + drawRectangle(10, 10, 320, 133, fade(skyBlue, .5F)) + drawRectangleLines(10, 10, 320, 133, blue) + + drawText("Free camera default controls:", 20, 20, 10, black) + drawText("- Mouse Wheel to Zoom in-out", 40, 40, 10, darkGray) + drawText("- Mouse Wheel Pressed to Pan", 40, 60, 10, darkGray) + drawText("- Alt + Mouse Wheel Pressed to Rotate", 40, 80, 10, darkGray) + drawText("- Alt + Ctrl + Mouse Wheel Pressed for Smooth Zoom", 40, 100, 10, darkGray) + drawText("- Z to zoom to (0, 0, 0)", 40, 120, 10, darkGray) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} +``` + +
+ +More examples can be found [here.](https://codeberg.org/Kenta/KaylibKit/src/branch/main/examples) + +## Contributors + +- [Soutaisei](https://codeberg.org/Soutaisei) - Creator and maintainer +- [fredthedeadhead](https://codeberg.org/fredthedeadhead) - Contributor +- [dallas-hyde](https://codeberg.org/dallas-hyde) - Contributor + +## Credits & Mentions + +- [STREGAsGate](https://github.com/STREGAsGate/Raylib) - An amazing person who drove me into learning how to do bindings, and now I can't stop. Creator of Raylib on Swift that drove this project to exist. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..b419728 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,86 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + +plugins { + kotlin("multiplatform") version "1.9.10" + id("maven-publish") +} + +group = "com.prism-architect" +version = "1.0.3" + +// Codeberg repository properties providers +val tokenProvider: Provider = providers.gradleProperty("KaylibKitToken") +val mavenUrlProvider: Provider = providers.gradleProperty("KaylibKitMavenUrl") + +val raylibVersion = "4.5.0" +val raylibMakeVersion = "VERSION=$raylibVersion" + +repositories { + mavenCentral() + mavenLocal() + + extensions.configure { + repositories { + if (tokenProvider.isPresent && mavenUrlProvider.isPresent) { + maven(mavenUrlProvider) { + name = "CodebergPackages" + + credentials(HttpHeaderCredentials::class) { + name = "Authorization" + value = "token ${tokenProvider.get()}" + } + authentication.create("header") + } + } + } + } + +} + +@Suppress("UNUSED_VARIABLE") +kotlin { + linuxX64() + mingwX64() + macosX64() + macosArm64() + + targets.withType { + compilations.getByName("main") { + cinterops { + val kaylibc by creating + } + } + binaries { + binaries.staticLib() + binaries.RELEASE + } + } + + sourceSets { + val commonMain by getting + + val macosMain by creating { dependsOn(commonMain) } + val macosX64Main by getting { dependsOn(macosMain) } + val macosArm64Main by getting { dependsOn(macosMain) } + + val linuxMain by creating { dependsOn(commonMain) } + val linuxX64Main by getting { dependsOn(linuxMain) } + + val mingwMain by creating { dependsOn(commonMain) } + val mingwX64Main by getting { dependsOn(mingwMain) } + } +} + +val sourceRaylib by tasks.registering(Exec::class) { + group = project.name + description = "Run Make to download static libraries and headers" + workingDir = file("src/nativeInterop/cinterop") + commandLine("make", raylibMakeVersion) +} + +val cleanRaylib by tasks.registering(Exec::class) { + group = project.name + description = "Run Make to cleanup static libraries and headers (Useful for version update)" + workingDir = file("src/nativeInterop/cinterop") + commandLine("make", "clean") +} \ No newline at end of file diff --git a/examples/core/core_2d_camera.kt b/examples/core/core_2d_camera.kt new file mode 100644 index 0000000..80026c2 --- /dev/null +++ b/examples/core/core_2d_camera.kt @@ -0,0 +1,122 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.KeyboardKey +import kaylibkit.kMath.kVector2 +import kaylibkit.kMath.set +import kaylibkit.kShapes.drawLine +import kaylibkit.kShapes.drawRectangle +import kaylibkit.kShapes.drawRectangleLines +import kaylibkit.kShapes.kRectangle +import kaylibkit.kText.drawText +import kaylibkit.kTypes.kCamera2D +import kaylibkit.kTypes.kColor +import kaylibkit.kUtils.fade +import kaylibc.* +import kotlin.random.Random +import kotlin.random.nextInt + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 +const val MAX_BUILDINGS = 100 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - 2d camera") + + val player = kRectangle(400F, 280F, 40F, 40F) + val buildings = Array(MAX_BUILDINGS) { kRectangle() } + val buildColor = Array(MAX_BUILDINGS) { + kColor(Random.nextInt(200..240).toUByte(), Random.nextInt(200..240).toUByte(), Random.nextInt(200..250).toUByte(), 255U) + } + var spacing = 0 + + buildings.forEach { building -> + building.width = Random.nextInt(50..200).toFloat() + building.height = Random.nextInt(100..800).toFloat() + building.y = SCREEN_HEIGHT - 130F - building.height + building.x = -6000F + spacing + + spacing += building.width.toInt() + } + + val camera = kCamera2D(kVector2(SCREEN_WIDTH/2F, SCREEN_HEIGHT/2F), kVector2(player.x + 20F, player.y + 20F), 0F, 1F) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + // Player movement + if (isKeyDown(KeyboardKey.RIGHT)) { player.x += 2 } + else if (isKeyDown(KeyboardKey.LEFT)) { player.x -= 2 } + + // Camera target follows player + // We're unable to modify .target directly due to how cinterop works with K/N as cinterop will translate any nested CStruct as a immutable (val) + // if they're not pointing to anything (pointer to), but we can easily modify it by passing immutable CStruct value: + camera.target.set(kVector2(player.x + 20F, player.y + 20F)) + + // We can also do it a different way by simply modifying the CStruct members + + // Camera rotation controls + if (isKeyDown(KeyboardKey.A)) { camera.rotation-- } + else if (isKeyDown(KeyboardKey.S)) { camera.rotation++ } + + // Limit camera rotation to 80 degrees (-40 to 40) + if (camera.rotation > 40) { camera.rotation = 40F } + else if (camera.rotation < -40) { camera.rotation = -40F } + + // Camera zoom controls + camera.zoom += getMouseWheelMove() * .05F + + if (camera.zoom > 3F) { camera.zoom = 3F } + else if (camera.zoom < .1F) { camera.zoom = .1F } + + // Camera reset (zoom and rotation) + if (isKeyPressed(KeyboardKey.R)) { + camera.zoom = 1F + camera.rotation = 0F + } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + mode2D(camera) { + clearBackground(rayWhite) + + drawRectangle(-6000, 320, 13000, 8000, darkGray) + + buildings.forEachIndexed { index, rect -> drawRectangle(rect, buildColor[index]) } + + drawRectangle(player, red) + + drawLine(camera.target.x.toInt(), -SCREEN_HEIGHT*10, camera.target.x.toInt(), SCREEN_HEIGHT*10, green) + drawLine(-SCREEN_WIDTH*10, camera.target.y.toInt(), SCREEN_WIDTH*10, camera.target.y.toInt(), green) + + } + + drawText("SCREEN AREA", 640, 10, 20, red) + drawRectangle(0, 0, SCREEN_WIDTH, 5, red) + drawRectangle(0, 5, 5, SCREEN_HEIGHT - 10, red) + drawRectangle(SCREEN_WIDTH - 5, 5, 5, SCREEN_HEIGHT - 10, red) + drawRectangle(0, SCREEN_HEIGHT - 5, SCREEN_WIDTH, 5, red) + + drawRectangle(10, 10, 250, 113, fade(skyBlue, 0.5F)) + drawRectangleLines(10, 10, 250, 113, blue) + + drawText("Free 2D camera controls:", 20, 20, 10, black) + drawText("- Right/Left to move Offset", 40, 40, 10, darkGreen) + drawText("- Mouse Wheel to Zoom in-out", 40, 60, 10, darkGreen) + drawText("- A / S to Rotate", 40, 80, 10, darkGreen) + drawText("- R to reset Zoom and Rotation", 40, 100, 10, darkGreen) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context +//-------------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/core/core_3d_camera.kt b/examples/core/core_3d_camera.kt new file mode 100644 index 0000000..97bf45d --- /dev/null +++ b/examples/core/core_3d_camera.kt @@ -0,0 +1,55 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.CameraProjection +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kMath.kVector3 +import kaylibkit.kModels.drawCube +import kaylibkit.kModels.drawCubeWires +import kaylibkit.kModels.drawGrid +import kaylibkit.kText.drawFPS +import kaylibkit.kText.drawText +import kaylibkit.kTypes.kCamera3D +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - 3d camera mode") + + val camera = kCamera3D(kVector3(0F, 10F, 10F), kVector3(0F, 0F, 0F), kVector3(0F, 1F, 0F), 45F, CameraProjection.PERSPECTIVE) + val cubePosition = kVector3(0F, 0F, 0F) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + mode3D(camera) { + drawCube(cubePosition, 2F, 2F, 2F, red) + drawCubeWires(cubePosition, 2F, 2F, 2F, maroon) + + drawGrid(10, 1F) + } + + drawText("Welcome to the third dimension!", 10, 40, 20, darkGray) + drawFPS(10, 10) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/core/core_3d_camera_first_person.kt b/examples/core/core_3d_camera_first_person.kt new file mode 100644 index 0000000..b01f18d --- /dev/null +++ b/examples/core/core_3d_camera_first_person.kt @@ -0,0 +1,88 @@ +import kaylibkit.kCamera.setCameraMode +import kaylibkit.kCamera.updateCamera +import kaylibkit.kCore.* +import kaylibkit.kEnums.CameraMode +import kaylibkit.kEnums.CameraProjection +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kMath.kVector2 +import kaylibkit.kMath.kVector3 +import kaylibkit.kModels.drawCube +import kaylibkit.kModels.drawCubeWires +import kaylibkit.kModels.drawPlane +import kaylibkit.kShapes.drawRectangle +import kaylibkit.kShapes.drawRectangleLines +import kaylibkit.kText.drawText +import kaylibkit.kTypes.kCamera3D +import kaylibkit.kTypes.kColor +import kaylibkit.kUtils.fade +import kaylibc.* +import kotlin.random.Random +import kotlin.random.nextInt + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 +const val MAX_COLUMNS = 20 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - 3d camera first person") + + val camera = kCamera3D(kVector3(4F, 2F, 4F), kVector3(0F, 1.8F, 0F), kVector3(0F, 1F, 0F), 60F, CameraProjection.PERSPECTIVE) + + val heights = Array(MAX_COLUMNS) { + Random.nextInt(1..12).toFloat() + } + val position = Array(MAX_COLUMNS) { i -> + kVector3(Random.nextInt(-15..15).toFloat(), heights[i]/2F, Random.nextInt(-15..15).toFloat()) + } + val colors = Array(MAX_COLUMNS) { + kColor(Random.nextInt(20..255).toUByte(), Random.nextInt(10..55).toUByte(), 30U, 255U) + } + + setCameraMode(camera, CameraMode.FIRST_PERSON) + + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + updateCamera(camera) + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + mode3D(camera) { + drawPlane(kVector3(0F, 0F, 0F), kVector2(32F, 32F), lightGray) // Draw ground + drawCube(kVector3(-16F, 2.5F, 0F), 1F, 5F, 32F, blue) // Draw a blue wall + drawCube(kVector3(16F, 2.5F, 0F), 1F, 5F, 32F, blue) // Draw a green wall + drawCube(kVector3(0F, 2.5F, 16F), 32F, 5F, 1F, blue) // Draw a yellow wall + + // Draw some cubes around + heights.indices.forEach { i -> + drawCube(position[i], 2F, heights[i], 2F, colors[i]) + drawCubeWires(position[i], 2F, heights[i], 2F, maroon) + } + + } + + drawRectangle(10, 10, 220, 70, fade(skyBlue, .5F)) + drawRectangleLines(10, 10, 220, 70, blue) + drawText("First person camera default controls:", 20, 20, 10, black); + drawText("- Move with keys: W, A, S, D", 40, 40, 10, darkGray); + drawText("- Mouse move to look around", 40, 60, 10, darkGray); + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/core/core_3d_camera_free.kt b/examples/core/core_3d_camera_free.kt new file mode 100644 index 0000000..d6abfe8 --- /dev/null +++ b/examples/core/core_3d_camera_free.kt @@ -0,0 +1,73 @@ +import kaylibkit.kCamera.setCameraMode +import kaylibkit.kCamera.updateCamera +import kaylibkit.kCore.* +import kaylibkit.kEnums.CameraMode +import kaylibkit.kEnums.CameraProjection +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.KeyboardKey +import kaylibkit.kMath.kVector3 +import kaylibkit.kMath.setZero +import kaylibkit.kModels.drawCube +import kaylibkit.kModels.drawCubeWires +import kaylibkit.kModels.drawGrid +import kaylibkit.kShapes.drawRectangle +import kaylibkit.kShapes.drawRectangleLines +import kaylibkit.kText.drawText +import kaylibkit.kTypes.kCamera3D +import kaylibkit.kUtils.fade +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - 3d camera free") + + val camera = kCamera3D(kVector3(10F, 10F, 10F), kVector3(), kVector3(0F, 1F, 0F), 45F, CameraProjection.PERSPECTIVE) + val cubePosition = kVector3() + + setCameraMode(camera, CameraMode.FREE) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + updateCamera(camera) + if (isKeyDown(KeyboardKey.Z)) { camera.target.setZero() } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + mode3D(camera) { + drawCube(cubePosition, 2F, 2F, 2F, red) + drawCubeWires(cubePosition, 2F, 2F, 2F, maroon) + + drawGrid(10, 1F) + + } + + drawRectangle(10, 10, 320, 133, fade(skyBlue, .5F)) + drawRectangleLines(10, 10, 320, 133, blue) + + drawText("Free camera default controls:", 20, 20, 10, black) + drawText("- Mouse Wheel to Zoom in-out", 40, 40, 10, darkGray) + drawText("- Mouse Wheel Pressed to Pan", 40, 60, 10, darkGray) + drawText("- Alt + Mouse Wheel Pressed to Rotate", 40, 80, 10, darkGray) + drawText("- Alt + Ctrl + Mouse Wheel Pressed for Smooth Zoom", 40, 100, 10, darkGray) + drawText("- Z to zoom to (0, 0, 0)", 40, 120, 10, darkGray) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/core/core_3d_picking.kt b/examples/core/core_3d_picking.kt new file mode 100644 index 0000000..c1e3adb --- /dev/null +++ b/examples/core/core_3d_picking.kt @@ -0,0 +1,96 @@ +import kaylib.kCamera.setCameraMode +import kaylib.kCamera.updateCamera +import kaylib.kCore.* +import kaylib.kCore.endDrawing +import kaylib.kEnums.CameraMode +import kaylib.kEnums.CameraProjection +import kaylib.kEnums.ConfigFlag +import kaylib.kEnums.MouseButton +import kaylib.kMath.kVector3 +import kaylib.kModels.* +import kaylib.kText.drawFPS +import kaylib.kText.drawText +import kaylib.kText.measureText +import kaylib.kTypes.kBoundingBox +import kaylib.kTypes.kCamera3D +import kaylib.kTypes.kRay +import kaylib.kTypes.kRayCollision +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - 3d picking") + + val camera = kCamera3D(kVector3(10F, 10F, 10F), kVector3(), kVector3(0F, 1F, 0F), 45F, CameraProjection.PERSPECTIVE) + + val cubePosition = kVector3(0F, 1F, 0F) + val cubeSize = kVector3(2F, 2F, 2F) + + var ray = kRay() // Picking line ray + var collision = kRayCollision() + + setCameraMode(camera, CameraMode.FREE) // Set a free camera mode + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + updateCamera(camera) + + if (isMouseButtonPressed(MouseButton.LEFT)) { + if (!collision.hit) { + ray = getMouseRay(getMousePosition(), camera) + + // Check collision between ray and box + collision = getRayCollisionBox(ray, + kBoundingBox( + kVector3(cubePosition.x - cubeSize.x/2, cubePosition.y - cubeSize.y/2, cubePosition.z - cubeSize.z/2), + kVector3(cubePosition.x + cubeSize.x/2, cubePosition.y + cubeSize.y/2, cubePosition.z + cubeSize.z/2))) + } else { + collision.hit = false + } + } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + mode3D(camera) { + if (collision.hit) { + drawCube(cubePosition, cubeSize.x, cubeSize.y, cubeSize.z, red) + drawCubeWires(cubePosition, cubeSize.x, cubeSize.y, cubeSize.z, maroon) + + drawCubeWires(cubePosition, cubeSize.x + 0.2f, cubeSize.y + 0.2f, cubeSize.z + 0.2f, green) + } else { + drawCube(cubePosition, cubeSize.x, cubeSize.y, cubeSize.z, gray) + drawCubeWires(cubePosition, cubeSize.x, cubeSize.y, cubeSize.z, darkGray) + } + + drawRay(ray, maroon) + drawGrid(10, 1F) + } + + drawText("Try selecting the box with mouse!", 240, 10, 20, darkGray); + + if (collision.hit) drawText("BOX SELECTED", (SCREEN_WIDTH - measureText("BOX SELECTED", 30)) / 2, (SCREEN_HEIGHT * .1F).toInt(), 30, green) + + drawFPS(10, 10); + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} + + diff --git a/examples/core/core_basic_window.kt b/examples/core/core_basic_window.kt new file mode 100644 index 0000000..d08899b --- /dev/null +++ b/examples/core/core_basic_window.kt @@ -0,0 +1,40 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kText.drawText +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - basic window") + + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + drawing { + + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawText("Hello from Kotlin/Native", 190, 200, 20, lightGray) + //---------------------------------------------------------------------------------- + } + + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/core/core_input_keys.kt b/examples/core/core_input_keys.kt new file mode 100644 index 0000000..8d79ea6 --- /dev/null +++ b/examples/core/core_input_keys.kt @@ -0,0 +1,49 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.KeyboardKey +import kaylibkit.kMath.kVector2 +import kaylibkit.kShapes.drawCircle +import kaylibkit.kText.drawText +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - keyboard input") + + val ballPosition = kVector2(SCREEN_WIDTH/2F, SCREEN_HEIGHT/2F) + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + if (isKeyDown(KeyboardKey.RIGHT)) { ballPosition.x += 2 } + if (isKeyDown(KeyboardKey.LEFT)) { ballPosition.x -= 2 } + if (isKeyDown(KeyboardKey.UP)) { ballPosition.y -= 2 } + if (isKeyDown(KeyboardKey.DOWN)) { ballPosition.y += 2 } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawText("move the ball with arrow keys", 10, 10, 20, darkGray) + + drawCircle(ballPosition, 50F, maroon) + //---------------------------------------------------------------------------------- + } + + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} + diff --git a/examples/core/core_input_mouse.kt b/examples/core/core_input_mouse.kt new file mode 100644 index 0000000..428d594 --- /dev/null +++ b/examples/core/core_input_mouse.kt @@ -0,0 +1,53 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.MouseButton +import kaylibkit.kShapes.drawCircle +import kaylibkit.kText.drawText +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - mouse input") + + var ballPosition: Vector2 + var ballColor = darkBlue + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + ballPosition = getMousePosition() + + if (isMouseButtonPressed(MouseButton.LEFT)) ballColor = maroon; + else if (isMouseButtonPressed(MouseButton.MIDDLE)) ballColor = lime; + else if (isMouseButtonPressed(MouseButton.RIGHT)) ballColor = darkBlue; + else if (isMouseButtonPressed(MouseButton.SIDE)) ballColor = purple; + else if (isMouseButtonPressed(MouseButton.EXTRA)) ballColor = yellow; + else if (isMouseButtonPressed(MouseButton.FORWARD)) ballColor = orange; + else if (isMouseButtonPressed(MouseButton.BACK)) ballColor = beige; + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawText("move ball with mouse and click mouse button to change color", 10, 10, 20, darkGray) + + drawCircle(ballPosition, 50F, ballColor) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} + diff --git a/examples/core/core_input_mouse_wheel.kt b/examples/core/core_input_mouse_wheel.kt new file mode 100644 index 0000000..bc52a23 --- /dev/null +++ b/examples/core/core_input_mouse_wheel.kt @@ -0,0 +1,45 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kShapes.drawRectangle +import kaylibkit.kText.drawText +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [core] example - mouse wheel input") + + var boxPositionY = SCREEN_HEIGHT/2 - 40 + val scrollSpeed = 4 + //-------------------------------------------------------------------------------------- + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + boxPositionY -= (getMouseWheelMove() * scrollSpeed).toInt() + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawRectangle(SCREEN_WIDTH/2 - 40, boxPositionY, 80, 80, maroon) + + drawText("Use mouse wheel to move the cube up and down!", 10, 10, 20, gray) + drawText("Box Position Y: $boxPositionY", 10, 40, 20, lightGray) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} + diff --git a/examples/models/models_heightmap.kt b/examples/models/models_heightmap.kt new file mode 100644 index 0000000..0688ff6 --- /dev/null +++ b/examples/models/models_heightmap.kt @@ -0,0 +1,78 @@ +import kaylibkit.kCamera.setCameraMode +import kaylibkit.kCamera.updateCamera +import kaylibkit.kCore.* +import kaylibkit.kCore.closeWindow +import kaylibkit.kEnums.CameraMode +import kaylibkit.kEnums.CameraProjection +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kImage.loadImage +import kaylibkit.kImage.unloadImage +import kaylibkit.kMath.kVector3 +import kaylibkit.kModels.* +import kaylibkit.kShapes.drawRectangleLines +import kaylibkit.kText.drawFPS +import kaylibkit.kTextures.* +import kaylibkit.kTypes.kCamera3D +import kaylibc.* +import kotlinx.cinterop.get + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [models] example - heightmap loading and drawing") + //-------------------------------------------------------------------------------------- + + val camera = kCamera3D(kVector3(18F, 18F, 18F), kVector3(0F, 0F, 0F), kVector3(0F, 1F, 0F), 45F, CameraProjection.PERSPECTIVE) + + + val image = loadImage("resources/heightmap.png") // Load heightmap image (RAM) + val texture = loadTextureFromImage(image) // Convert image to texture (VRAM) + + val mesh = genMeshHeightmap(image, kVector3(16F, 8F, 16F)) // Generate heightmap mesh (RAM and VRAM) + val model = loadModelFromMesh(mesh) // Load model from generated mesh + + // Set map diffuse texture. NOTE that we have to use .get and check for nullability as its possible materials is null + model.materials?.get(0)?.maps?.get(MATERIAL_MAP_DIFFUSE)?.texture?.set(texture) // Also note that we have to use K/N function of .get to retrieve the data from the material + val mapPosition = kVector3(-8F, 0F, -8F) // Define model position + + unloadImage(image) // Unload heightmap image from RAM, already uploaded to VRAM + + setCameraMode(camera, CameraMode.ORBITAL) // Set an orbital camera mode + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + updateCamera(camera) + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + mode3D(camera) { + drawModel(model, mapPosition, 1F, red) + drawGrid(20, 1F) + } + + drawTexture(texture, SCREEN_WIDTH - texture.width - 20, 20, white) + drawRectangleLines(SCREEN_WIDTH - texture.width - 20, 20, texture.width, texture.height, green) + + drawFPS(10, 10) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + unloadTexture(texture) // Texture unloading + unloadModel(model) // Unload model + + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} diff --git a/examples/models/resources/heightmap.png b/examples/models/resources/heightmap.png new file mode 100644 index 0000000..474db87 Binary files /dev/null and b/examples/models/resources/heightmap.png differ diff --git a/examples/shapes/shapes_easings_box_anim.kt b/examples/shapes/shapes_easings_box_anim.kt new file mode 100644 index 0000000..721e80a --- /dev/null +++ b/examples/shapes/shapes_easings_box_anim.kt @@ -0,0 +1,112 @@ +import kaylibkit.kCore.* +import kaylibkit.kEasing.* +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.KeyboardKey +import kaylibkit.kMath.kVector2 +import kaylibkit.kShapes.drawRectangle +import kaylibkit.kShapes.kRectangle +import kaylibkit.kText.drawText +import kaylibkit.kUtils.fade +import kaylibc.* + +const val SCREEN_WIDTH = 800 +const val SCREEN_HEIGHT = 450 + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +fun main() { + setConfigFlags(ConfigFlag.VSYNC_HINT) // Turn VSYNC On + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [shapes] example - easings box anim") + + var rec: Rectangle = kRectangle(kVector2(getScreenWidth()/2F, -100F), 100F, 100F) + var rotation: Float = .0F + var alpha: Float = 1.0F + + var state: Int = 0 + var framesCounter: Int = 0 + //-------------------------------------------------------------------------------------- + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + when (state) { + 0 -> { + framesCounter++ + + // NOTE: Remember that 3rd parameter of easing function refers to + // desired value variation, do not confuse it with expected final value! + rec.y = elasticOut(framesCounter.toFloat(), -100F, getScreenHeight() / 2.0F + 100, 120F) + if (framesCounter >= 120) { + framesCounter = 0 + state = 1 + } + } + + 1 -> { + framesCounter++ + rec.height = bounceOut(framesCounter.toFloat(), 100F, -90F, 120F) + rec.width = bounceOut(framesCounter.toFloat(), 100F, getScreenWidth().toFloat(), 120F) + if (framesCounter >= 120) { + framesCounter = 0 + state = 2 + } + } + + 2 -> { + framesCounter++ + rotation = quadOut(framesCounter.toFloat(), .0F, 270.0F, 240F) + if (framesCounter >= 240) { + framesCounter = 0 + state = 3 + } + } + + 3 -> { + framesCounter++ + rec.height = circOut(framesCounter.toFloat(), 10F, GetScreenWidth().toFloat(), 120F) + if (framesCounter >= 120) { + framesCounter = 0 + state = 4 + } + } + + 4 -> { + framesCounter++ + alpha = sineOut(framesCounter.toFloat(), 1.0F, -1.0F, 160F) + if (framesCounter >= 160) { + framesCounter = 0 + state = 5 + } + } + + else -> {} + } + + if (isKeyPressed(KeyboardKey.SPACE)) { + rec = kRectangle(kVector2(getScreenWidth()/2F, -100F), 100F, 100F) + rotation = .0F + alpha = 1.0F + state = 0 + framesCounter = 0 + } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + + drawRectangle(rec, kVector2(rec.width/2, rec.height/2), rotation, fade(black, alpha)) + + drawText("PRESS [SPACE] TO RESET BOX ANIMATION!", 10, getScreenHeight() - 25, 20, lightGray) + //---------------------------------------------------------------------------------- + } + } + // De-Initialization + //-------------------------------------------------------------------------------------- + + closeWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} \ No newline at end of file diff --git a/examples/textures/resources/raylib_logo.png b/examples/textures/resources/raylib_logo.png new file mode 100644 index 0000000..15bbaa2 Binary files /dev/null and b/examples/textures/resources/raylib_logo.png differ diff --git a/examples/textures/resources/wabbit_alpha.png b/examples/textures/resources/wabbit_alpha.png new file mode 100644 index 0000000..79c3167 Binary files /dev/null and b/examples/textures/resources/wabbit_alpha.png differ diff --git a/examples/textures/textures_bunnymark.kt b/examples/textures/textures_bunnymark.kt new file mode 100644 index 0000000..03e4ddb --- /dev/null +++ b/examples/textures/textures_bunnymark.kt @@ -0,0 +1,104 @@ +import kaylibkit.kCore.* +import kaylibkit.kEnums.MouseButton +import kaylibkit.kMath.kVector2 +import kaylibkit.kShapes.drawRectangle +import kaylibkit.kText.drawFPS +import kaylibkit.kText.drawText +import kaylibkit.kTextures.drawTexture +import kaylibkit.kTextures.loadTexture +import kaylibkit.kTextures.unloadTexture +import kaylibkit.kTypes.kColor +import kaylibc.* +import kotlin.random.Random + +const val SCREEN_WIDTH: Int = 800 +const val SCREEN_HEIGHT: Int = 450 + +data class Bunny(var position: Vector2, var speed: Vector2, var color: Color) + +var MAX_BUNNIES = 500000 +var MAX_BATCH_ELEMENTS = 8192 + +fun main() { + // Initialization + //-------------------------------------------------------------------------------------- + initWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raylib [textures] example - bunnymark") + setTargetFPS(60) + + // Load bunny texture + val texBunny = loadTexture("/Users/kenta/Documents/Repositories/untitled/src/nativeMain/kotlin/resources/wabbit_alpha.png") + val bunnies = Array(MAX_BUNNIES) { Bunny(kVector2(), kVector2(), kColor()) } + var bunniesCount = 0 // Bunnies counter + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!windowShouldClose) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + if (isMouseButtonDown(MouseButton.LEFT)) { + // Create more bunnies + repeat(100) { + if (bunniesCount < MAX_BUNNIES) { + bunnies[bunniesCount].position = getMousePosition() + bunnies[bunniesCount].speed.x = (Random.nextInt(-250, 250) / 60.0f) + bunnies[bunniesCount].speed.y = (Random.nextInt(-250, 250) / 60.0f) + bunnies[bunniesCount].color.apply { + this.r = getRandomValue(50, 240).toUByte() + this.g = getRandomValue(80, 240).toUByte() + this.b = getRandomValue(100, 200).toUByte() + this.a = 255U + } + bunniesCount++ + } + } + } + + // Update bunnies + for (i in 0.. SCREEN_WIDTH || + bunnies[i].position.x + texBunny.width / 2.0 < 0 + ) { + bunnies[i].speed.x *= -1f + } + if (bunnies[i].position.y + texBunny.height / 2.0 > SCREEN_HEIGHT || + bunnies[i].position.y + texBunny.height / 2.0 - 40 < 0 + ) { + bunnies[i].speed.y *= -1f + } + } + //---------------------------------------------------------------------------------- + + drawing { + // Draw + //---------------------------------------------------------------------------------- + clearBackground(rayWhite) + for (i in 0.. '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..1fb5632 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = "KaylibKit" \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kAudio/KAudio.kt b/src/commonMain/kotlin/kaylibkit/kAudio/KAudio.kt new file mode 100644 index 0000000..9d3731f --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kAudio/KAudio.kt @@ -0,0 +1,476 @@ +package kaylibkit.kAudio + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kAudio + +//=======================================================// +// AUDIO DEVICE MANAGEMENT +//=======================================================// + +/** + * Initialize audio device and context + */ +inline fun initAudioDevice() { + InitAudioDevice() +} + +/** + * Close the audio device and context + */ +inline fun closeAudioDevice() { + CloseAudioDevice() +} + +/** + * Check if audio device has been initialized successfully + * @return [Boolean] + */ +inline val isAudioDeviceReady: Boolean + get() { return IsAudioDeviceReady() } + +/** + * Set master volume (listener) + */ +inline fun setMasterVolume(volume: Float) { + SetMasterVolume(volume) +} + +//=======================================================// +// WAVE/SOUND LOADING & UNLOADING FUNCTIONS +//=======================================================// + +/** + * Load wave data from file + * @return [Wave] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadWave(fileName: String) : Wave { + return LoadWave(fileName).getPointer(MemScope()).pointed +} + +/** + * Load wave from memory buffer, [fileType] refers to extension: i.e. ".wav" + * @return [Wave] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadWaveFromMemory(fileType: String, fileData: UByteVar, dataSize: Int) : Wave { + return LoadWaveFromMemory(fileType, fileData.ptr, dataSize).getPointer(MemScope()).pointed +} + +/** + * Load [Sound] from file + * @return [Sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadSound(fileName: String) : Sound { + return LoadSound(fileName).getPointer(MemScope()).pointed +} + +/** + * Load [Sound] from [wave] data + * @return [Sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadSoundFromWave(wave: Wave) : Sound { + return LoadSoundFromWave(wave.readValue()).getPointer(MemScope()).pointed +} + +/** + * Update [sound] buffer with new [data] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateSound(sound: Sound, data: COpaquePointer, samplesCount: Int) { + return UpdateSound(sound.readValue(), data, samplesCount) +} + +/** + * Unload [wave] data + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadWave(wave: Wave) { + UnloadWave(wave.readValue()) +} + +/** + * Unload [sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadSound(sound: Sound) { + UnloadSound(sound.readValue()) +} + +/** + * Export [wave] data to file, returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun exportWave(wave: Wave, fileName: String) : Boolean { + return ExportWave(wave.readValue(), fileName) +} + +/** + * Export wave sample data to code (.h), returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun exportWaveAsCode(wave: Wave, fileName: String) : Boolean { + return ExportWaveAsCode(wave.readValue(), fileName) +} + +//=======================================================// +// WAVE/SOUND MANAGEMENT FUNCTIONS +//=======================================================// + +/** + * Play a [sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun playSound(sound: Sound) { + PlaySound(sound.readValue()) +} + +/** + * Stop playing a [sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun stopSound(sound: Sound) { + StopSound(sound.readValue()) +} + +/** + * Pause a [sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun pauseSound(sound: Sound) { + PauseSound(sound.readValue()) +} + +/** + * Resume a paused [sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun resumeSound(sound: Sound) { + ResumeSound(sound.readValue()) +} + +/** + * Check if a [sound] is currently playing + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isSoundPlaying(sound: Sound) : Boolean { + return IsSoundPlaying(sound.readValue()) +} + +/** + * Check if a [sound] is currently playing + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isSoundReady(sound: Sound) : Boolean { + return IsSoundReady(sound.readValue()) +} + +/** + * Set [volume] for a [sound] (1.0 is max level) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setSoundVolume(sound: Sound, volume: Float) { + SetSoundVolume(sound.readValue(), volume) +} + + +/** + * Set [pitch] for a [sound] (1.0 is base level) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setSoundPitch(sound: Sound, pitch: Float) { + SetSoundPitch(sound.readValue(), pitch) +} + + +/** + * Convert [wave] data to desired format + */ +@OptIn(ExperimentalForeignApi::class) +inline fun waveFormat(wave: Wave, sampleRate: Int, sampleSize: Int, channels: Int) { + WaveFormat(wave.readValue(), sampleRate, sampleSize, channels) +} + +/** + * Copy a [wave] to a new [wave] + * @return [Wave] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun waveCopy(wave: Wave) : Wave { + return WaveCopy(wave.readValue()).getPointer(MemScope()).pointed +} + +/** + * Crop a [wave] to defined samples range + */ +@OptIn(ExperimentalForeignApi::class) +inline fun waveCrop(wave: Wave, initSample: Int, finalSample: Int) { + WaveCrop(wave.readValue(), initSample, finalSample) +} + +/** + * Load samples data from wave as a floats array + * @return [FloatVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadWaveSamples(wave: Wave) : FloatVar? { + return LoadWaveSamples(wave.readValue())?.getPointer(MemScope())?.pointed +} + +/** + * Unload samples data loaded with LoadWaveSamples() + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadWaveSamples(samples: FloatVar) { + UnloadWaveSamples(samples.ptr) +} + +/** + * Checks if wave data is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isWaveReady(wave: Wave) : Boolean { + return IsWaveReady(wave.readValue()) +} + +//=======================================================// +// MUSIC MANAGEMENT FUNCTIONS +//=======================================================// + + +/** + * Load music stream from file + * @return [Music] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadMusicStream(fileName: String) : Music { + return LoadMusicStream(fileName).getPointer(MemScope()).pointed +} + +/** + *Load music stream from data + * @return [Music] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadMusicStreamFromMemory(fileType: String, data: CPointer, dataSize: Int) : Music { + return LoadMusicStreamFromMemory(fileType, data, dataSize).getPointer(MemScope()).pointed +} + +/** + * Unload [music] stream + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadMusicStream(music: Music) { + UnloadMusicStream(music.readValue()) +} + +/** + * Start [music] playing + */ +@OptIn(ExperimentalForeignApi::class) +inline fun playMusicStream(music: Music) { + PlayMusicStream(music.readValue()) +} + +/** + * Check if [music] is playing + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isMusicStreamPlaying(music: Music) : Boolean { + return IsMusicStreamPlaying(music.readValue()) +} + +/** + * Check if [music] is playing + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isMusicReady(music: Music) : Boolean { + return IsMusicReady(music.readValue()) +} + +/** + * Updates buffers for [music] streaming + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateMusicStream(music: Music) { + UpdateMusicStream(music.readValue()) +} + +/** + * Stop [music] playing + */ +@OptIn(ExperimentalForeignApi::class) +inline fun stopMusicStream(music: Music) { + StopMusicStream(music.readValue()) +} + +/** + * Pause [music] playing + */ +@OptIn(ExperimentalForeignApi::class) +inline fun pauseMusicStream(music: Music) { + PauseMusicStream(music.readValue()) +} + +/** + * Resume playing paused [music] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun resumeMusicStream(music: Music) { + ResumeMusicStream(music.readValue()) +} + +/** + * Seek [music] to a [position] (in seconds) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun seekMusicStream(music: Music, position: Float) { + SeekMusicStream(music.readValue(), position) +} + +/** + * Set [volume] for [music] (1.0 is max level) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setMusicVolume(music: Music, volume: Float) { + SetMusicVolume(music.readValue(), volume) +} + +/** + * Set [pitch] for a [music] (1.0 is base level) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setMusicPitch(music: Music, pitch: Float) { + SetMusicPitch(music.readValue(), pitch) +} + +/** + * Get [music] time length (in seconds) + * @return [Float] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMusicTimeLength(music: Music) : Float { + return GetMusicTimeLength(music.readValue()) +} + +/** + * Get current [music] time played (in seconds) + * @return [Float] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMusicTimePlayed(music: Music) : Float { + return GetMusicTimeLength(music.readValue()) +} + +//=======================================================// +// AUDIOSTREAM MANAGEMENT FUNCTIONS +//=======================================================// + +/** + * Init audio stream (to stream raw audio pcm data) + * @return [AudioStream] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadAudioStream(sampleRate: UInt, sampleSize: UInt, channels: UInt) : AudioStream { + return LoadAudioStream(sampleRate, sampleSize, channels).getPointer(MemScope()).pointed +} + +/** + * Unload audio [stream] and free memory + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadAudioStream(stream: AudioStream) { + UnloadAudioStream(stream.readValue()) +} + +/** + * Update audio [stream] buffers with data + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateAudioStream(stream: AudioStream, data: COpaquePointer, samplesCount: Int) { + UpdateAudioStream(stream.readValue(), data, samplesCount) +} + +/** + * Check if any audio [stream] buffers requires refill + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isAudioStreamProcessed(stream: AudioStream) : Boolean { + return IsAudioStreamProcessed(stream.readValue()) +} + +/** + * Play audio [stream] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun playAudioStream(stream: AudioStream) { + PlayAudioStream(stream.readValue()) +} + +/** + * Pause audio [stream] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun pauseAudioStream(stream: AudioStream) { + PauseAudioStream(stream.readValue()) +} + +/** + * Resume audio [stream] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun resumeAudioStream(stream: AudioStream) { + ResumeAudioStream(stream.readValue()) +} + +/** + * Check if audio [stream] is playing + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isAudioStreamPlaying(stream: AudioStream) : Boolean { + return IsAudioStreamPlaying(stream.readValue()) +} + +/** + * Stop audio [stream] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun stopAudioStream(stream: AudioStream) { + StopAudioStream(stream.readValue()) +} + +/** + * Set [volume] for audio [stream] (1.0 is max level) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setAudioStreamVolume(stream: AudioStream, volume: Float) { + SetAudioStreamVolume(stream.readValue(), volume) +} + +/** + * Set [pitch] for [audio] stream (1.0 is base level) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setAudioStreamPitch(stream: AudioStream, pitch: Float) { + SetAudioStreamPitch(stream.readValue(), pitch) +} + +/** + * Default [size] for new audio streams + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setAudioStreamBufferSizeDefault(size: Int) { + SetAudioStreamBufferSizeDefault(size) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kCamera/KCamera.kt b/src/commonMain/kotlin/kaylibkit/kCamera/KCamera.kt new file mode 100644 index 0000000..84dd924 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kCamera/KCamera.kt @@ -0,0 +1,137 @@ +package kaylibkit.kCamera + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kCamera + +//=======================================================// +// CAMERA SYSTEM FUNCTIONS +//=======================================================// + +/** + * Returns the [camera] forward [Vector3] (normalized) + * @return [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraForward(camera: Camera) : Vector3 { + return GetCameraForward(camera.ptr).getPointer(MemScope()).pointed +} + +/** + * Returns the [camera] up [Vector3] (normalized) + * Note: The up vector might not be perpendicular to the forward vector + * @return [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraUp(camera: Camera) : Vector3 { + return GetCameraUp(camera.ptr).getPointer(MemScope()).pointed +} + +/** + * Returns the [camera] right [Vector3] (normalized) + * @return [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraRight(camera: Camera) : Vector3 { + return GetCameraRight(camera.ptr).getPointer(MemScope()).pointed +} + +/** + * Moves the camera in its forward direction + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraMoveForward(camera: Camera, distance: Float, moveInWorldPlane: Boolean) { + CameraMoveForward(camera.ptr, distance, moveInWorldPlane) +} + +/** + * Moves the camera in its up direction + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraMoveUp(camera: Camera, distance: Float) { + CameraMoveUp(camera.ptr, distance) +} + +/** + * Moves the camera target in its current right direction + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraMoveRight(camera: Camera, distance: Float, moveInWorldPlane: Boolean) { + CameraMoveRight(camera.ptr, distance, moveInWorldPlane) +} + +/** + * Moves the camera position closer/farther to/from the camera target + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraMoveToTarget(camera: Camera, delta: Float) { + CameraMoveToTarget(camera.ptr, delta) +} + +/** + * Rotates the [camera] around its up vector + * Yaw is "looking left and right" + * If [rotateAroundTarget] is false, the [camera] rotates around its position + * Note: [angle] must be provided in radians + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraYaw(camera: Camera, angle: Float, rotateAroundTarget: Boolean) { + CameraYaw(camera.ptr, angle, rotateAroundTarget) +} + +/** + * Rotates the [camera] around its right vector, pitch is "looking up and down" + * lockView prevents [camera] overrotation (aka "somersaults") + * [rotateAroundTarget] defines if rotation is around target or around its position + * [rotateUp] rotates the up direction as well (typically only usefull in CAMERA_FREE) + * NOTE: [angle] must be provided in radians + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraPitch(camera: Camera, angle: Float, lockView: Boolean, rotateAroundTarget: Boolean, rotateUp: Boolean) { + CameraPitch(camera.ptr, angle, lockView, rotateAroundTarget, rotateUp) +} + +/** + * Rotates the [camera] around its forward vector + * Roll is "turning your head sideways to the left or right" + * Note: [angle] must be provided in radians + */ +@OptIn(ExperimentalForeignApi::class) +inline fun cameraRoll(camera: Camera, angle: Float) { + CameraRoll(camera.ptr, angle) +} + +/** + * Returns the [camera] view [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraViewMatrix(camera: Camera) : Matrix { + return GetCameraViewMatrix(camera.ptr).getPointer(MemScope()).pointed +} + +/** + * Returns the [camera] projection [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraProjectionMatrix(camera: Camera, aspect: Float) : Matrix { + return GetCameraProjectionMatrix(camera.ptr, aspect).getPointer(MemScope()).pointed +} + +/** + * Update [camera] position for selected [mode] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateCamera(camera: Camera, mode: kaylibkit.kEnums.CameraMode) { + UpdateCamera(camera.ptr, mode.value) +} + +/** + * Update [camera] movement, [movement]/[rotation] values should be provided by user + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateCameraPro(camera: Camera, movement: Vector3, rotation: Vector3, zoom: Float) { + UpdateCameraPro(camera.ptr, movement.readValue(), rotation.readValue(), zoom) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kCore/KCore.kt b/src/commonMain/kotlin/kaylibkit/kCore/KCore.kt new file mode 100644 index 0000000..630cedf --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kCore/KCore.kt @@ -0,0 +1,1422 @@ +package kaylibkit.kCore + +import kaylibkit.kEnums.ConfigFlag +import kaylibkit.kEnums.TraceLogLevel +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kCore + +//=======================================================// +// RAYLIB CORE FUNCTIONS +//=======================================================// + +/** +* Initialize window and OpenGL context +*/ +inline fun initWindow(width: Int, height: Int, title: String?) { + return InitWindow(width, height, title) +} + +/** + * Check if KEY_ESCAPE pressed or Close icon pressed + */ +inline val windowShouldClose: Boolean + get() { return WindowShouldClose() } + +/** + * Return Kaylib version + */ +val kaylibVersion: String = "1.0.3 - Cosmic Clash" + +/** + * Setup canvas (framebuffer) to start drawing, then calls [block]. + * + * Drawing will end after [block] has finished. + */ +inline fun drawing(block: () -> Unit) { + beginDrawing() + block() + endDrawing() +} + +/** + * Close window and unload OpenGL context + */ +inline fun closeWindow() { + return CloseWindow() +} + +/** + * Check if window has been initialized successfully + */ +inline val isWindowReady: Boolean + get() { return IsWindowReady() } + +/** + * Check if window is currently fullscreen + */ +inline val isWindowFullscreen: Boolean + get() { return IsWindowFullscreen() } + +/** + * Check if window is currently hidden (only PLATFORM_DESKTOP) + */ +inline val isWindowHidden: Boolean + get() { return IsWindowHidden() } + +/** + * Check if window is currently minimized (only PLATFORM_DESKTOP) + */ +inline val isWindowMinimized: Boolean + get() { return IsWindowMinimized() } + +/** + * Check if window is currently maximized (only PLATFORM_DESKTOP) + */ +inline val isWindowMaximized: Boolean + get() { return IsWindowMaximized() } + +/** + * Check if window is currently focused (only PLATFORM_DESKTOP) + */ +inline val isWindowFocused: Boolean + get() { return IsWindowFocused() } + +/** + * Check if window has been resized last frame + */ +inline val isWindowResized: Boolean + get() { return IsWindowResized() } + +/** + * Check if one specific window [flags] is enabled + */ +inline fun isWindowState(flags: ConfigFlag) : Boolean { + return IsWindowState(flags.value) +} + +/** + * Set window configuration state using [flags] + */ +inline fun setWindowState(flags: ConfigFlag) { + SetWindowState(flags.value) +} + +/** + * Clear window configuration state [flags] + */ +inline fun clearWindowState(flags: ConfigFlag) { + ClearWindowState(flags.value) +} + +/** + * Clear window configuration state flags + */ +inline fun toggleFullscreen() { + ToggleFullscreen() +} + +/** + * Clear window configuration state flags + */ +inline fun maximizeWindow() { + MaximizeWindow() +} + +/** + * Set window state: minimized, if resizable (only PLATFORM_DESKTOP) + */ +inline fun minimizeWindow() { + MinimizeWindow() +} + +/** + * Set window state: not minimized/maximized (only PLATFORM_DESKTOP) + */ +inline fun restoreWindow() { + RestoreWindow() +} + +/** + * Set icon for window (only PLATFORM_DESKTOP) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setWindowIcon(image: Image) { + SetWindowIcon(image.readValue()) +} + +/** + * Set [title] for window (only PLATFORM_DESKTOP) + */ +inline fun setWindowTitle(title: String) { + title.apply { SetWindowTitle(this) } +} + +/** + * Set window position on screen (only PLATFORM_DESKTOP) + */ +inline fun setWindowPosition(x: Int, y: Int) { + SetWindowPosition(x, y) +} + +/** + * Set window position on screen (only PLATFORM_DESKTOP) + */ +inline fun setWindowMonitor(monitor: Int) { + SetWindowMonitor(monitor) +} + +/** + * Set window position on screen (only PLATFORM_DESKTOP) + */ +inline fun setWindowMinSize(width: Int, height: Int) { + SetWindowMinSize(width, height) +} + +/** + * Get native window handle + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWindowHandle(): COpaquePointer? { + return GetWindowHandle() +} + +/** + * Get current screen width + * @return [Int] + */ +inline fun getScreenWidth() : Int { + return GetScreenWidth() +} + +/** + *Get current screen height + * @return [Int] + */ +inline fun getScreenHeight() : Int { + return GetScreenHeight() +} + + +/** + * Get number of connected monitors + * @return [Int] + */ +inline fun getMonitorCount() : Int { + return GetMonitorCount() +} + +/** + * Get current connected monitor + * @return [Int] + */ +inline fun getCurrentMonitor() : Int { + return GetCurrentMonitor() +} + +/** + * Get specified [monitor] position + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMonitorPosition(monitor: Int) : Vector2 { + return GetMonitorPosition(monitor).getPointer(MemScope()).pointed +} + +/** + * Get specified [monitor] width (max available by monitor) + * @return [Int] + */ +inline fun getMonitorWidth(monitor: Int) : Int { + return GetMonitorWidth(monitor) +} + +/** + * Get specified [monitor] height (max available by monitor) + * @return [Int] + */ +inline fun getMonitorHeight(monitor: Int) : Int { + return GetMonitorHeight(monitor) +} + +/** + * Get specified [monitor] physical width in millimetres + * @return [Int] + */ +inline fun getMonitorPhysicalWidth(monitor: Int) : Int { + return GetMonitorPhysicalWidth(monitor) +} + +/** + * Get specified [monitor] physical height in millimetres + * @return [Int] + */ +inline fun getMonitorPhysicalHeight(monitor: Int) : Int { + return GetMonitorPhysicalHeight(monitor) +} + +/** + * Get window position XY on monitor + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWindowPosition(): Vector2 { + return GetWindowPosition().getPointer(MemScope()).pointed +} + +/** + * Get window scale DPI factor + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWindowScaleDPI() : Vector2 { + return GetWindowScaleDPI().getPointer(MemScope()).pointed +} +/** + * Get the human-readable, UTF-8 encoded name of the primary [monitor] + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMonitorName(monitor: Int) : String { + return GetMonitorName(monitor)?.toKStringFromUtf8() ?: "WARNING: GLFW: Failed to find selected monitor" +} + +/** + * Set clipboard [text] content + */ +inline fun setClipboardText(text: String) { + SetClipboardText(text) +} + +/** + * Get clipboard text content + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getClipboardText() : String { + return GetClipboardText()?.toKStringFromUtf8() ?: "" +} +//=======================================================// +// FRAME CONTROLS FUNCTIONS +//=======================================================// +/** + * Swap back buffer with front buffer (screen drawing) + */ +inline fun swapScreenBuffer() { + SwapScreenBuffer() +} + +/** + * Register all input events + */ +inline fun pollInputEvents() { + PollInputEvents() +} + +/** + * Wait for some milliseconds (halt program execution) + * NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could + * take longer than expected... for that reason we use the busy wait loop + */ +inline fun waitTime(ms: Double) { + WaitTime(ms) +} + +//=======================================================// +// CURSOR FUNCTIONS +//=======================================================// + +/** + * Show cursor + */ +inline fun showCursor() { + ShowCursor() +} + +/** + * Hide cursor + */ +inline fun hideCursor() { + HideCursor() +} + +/** + * Check if cursor is not visible + * @return [Boolean] + */ +inline fun isCursorHidden() : Boolean { + return IsCursorHidden() +} + +/** + * Enables cursor (unlock cursor) + */ +inline fun enableCursor() { + EnableCursor() +} + +/** + * Disables cursor (lock cursor) + */ +inline fun disableCursor() { + DisableCursor() +} + +inline val isCursorOnScreen: Boolean + get() { return IsCursorOnScreen() } + +//=======================================================// +// DRAWING FUNCTIONS +//=======================================================// + +/** + * Set background [color] (framebuffer clear [color]) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun clearBackground(color: Color) { + ClearBackground(color.readValue()) +} + +/** + * Setup canvas (framebuffer) to start drawing + */ +inline fun beginDrawing() { + BeginDrawing() +} + +/** + * End canvas drawing and swap buffers (double buffering) + */ +inline fun endDrawing() { + EndDrawing() +} + +/** + * Begin 2D mode with custom [camera] (2D) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun beginMode2D(camera: Camera2D) { + BeginMode2D(camera.readValue()) +} + +/** + * Ends 2D mode with custom camera + */ +inline fun endMode2D() { + EndMode2D() +} + +/** + * Setup 2D mode with custom [camera] to start 2D Mode, then calls [block]. + * + * Mode2D will end after [block] has finished. + * @param [camera] accepts [Camera2D] + */ +inline fun mode2D(camera: Camera2D, block: () -> Unit) { + beginMode2D(camera) + block() + endMode2D() +} + + +/** + * Begin 3D mode with custom [camera] (3D) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun beginMode3D(camera: Camera3D) { + BeginMode3D(camera.readValue()) +} + + +/** + * Ends 3D mode and returns to default 2D orthographic mode + */ +inline fun endMode3D() { + EndMode3D() +} + +/** + * Setup 3D mode with custom [camera] to start 3D Mode, then calls [block]. + * + * Mode3D will end after [block] has finished. + * @param [camera] accepts [Camera3D] + */ +inline fun mode3D(camera: Camera3D, block: () -> Unit) { + beginMode3D(camera) + block() + endMode3D() +} + +/** + * Begin drawing to render texture + */ +@OptIn(ExperimentalForeignApi::class) +inline fun beginTextureMode(target: RenderTexture2D) { + BeginTextureMode(target.readValue()) +} + +/** + * Ends drawing to render texture + */ +inline fun endTextureMode() { + EndTextureMode() +} + +/** + * Setup [texture] mode to draw to render texture, then calls [block]. + * + * texture mode will end after [block] has finished. + * @param [texture] accepts [RenderTexture2D] + */ +inline fun textureMode(texture: RenderTexture2D, block: () -> Unit) { + beginTextureMode(texture) + block() + endTextureMode() +} + +/** + * Begin custom [shader] drawing + */ +@OptIn(ExperimentalForeignApi::class) +inline fun beginShaderMode(shader: Shader) { + BeginShaderMode(shader.readValue()) +} + +/** + * End custom shader drawing (use default shader) + */ +inline fun endShaderMode() { + EndShaderMode() +} + +/** + * Setup custom [shader] mode then calls [block]. + * + * shader mode will end after [block] has finished. + * @param [shader] accepts [Shader] + */ +inline fun shaderMode(shader: Shader, block: () -> Unit) { + beginShaderMode(shader) + block() + endShaderMode() +} + +/** + * Begin blending [mode] (alpha, additive, multiplied), subtract, custom) + */ +inline fun beginBlendMode(mode: kaylibkit.kEnums.BlendMode) { + BeginBlendMode(mode.value) +} + +/** + * End blending mode (reset to default: alpha blending) + */ +inline fun endBlendMode() { + EndBlendMode() +} + +/** + * Setup blending [mode], then calls [block]. + * + * blend mode will end after [block] has finished. + * @param [mode] accepts [kaylibkit.kEnums.BlendMode] + */ +inline fun blendMode(mode: kaylibkit.kEnums.BlendMode, block: () -> Unit) { + beginBlendMode(mode) + block() + endBlendMode() +} + +/** + * Begin scissor mode (define screen area for following drawing) + */ +inline fun beginScissorMode(x: Int, y: Int, width: Int, height: Int) { + BeginScissorMode(x, y, width, height) +} + +/** + * End scissor mode + */ +inline fun endScissorMode() { + EndScissorMode() +} + +/** + * Setup scissor mode then calls [block]. + * + * scissor mode will end after [block] has finished. + * @param [x] accept [Int] + * @param [y] accept [Int] + * @param [width] accept [Int] + * @param [height] accept [Int] + */ +inline fun scissorMode(x: Int, y: Int, width: Int, height: Int, block: () -> Unit) { + beginScissorMode(x, y, width, height) + block() + endShaderMode() +} + +/** + * Begin stereo rendering (requires VR simulator) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun beginVrStereoMode(config: VrStereoConfig) { + BeginVrStereoMode(config.readValue()) +} + +/** + * End stereo rendering (requires VR simulator) + */ +inline fun endVrStereoMode() { + EndVrStereoMode() +} + +/** + * Setup stereo rendering mode, then calls [block]. + * + * stereo rendering mode will end after [block] has finished. + * @param [config] accepts [VrStereoConfig] + */ +inline fun vrMode(config: VrStereoConfig, block: () -> Unit) { + beginVrStereoMode(config) + block() + endVrStereoMode() +} + +//=======================================================// +// VR STEREO CONFIG FUNCTIONS +//=======================================================// + +/** + * Load VR stereo config for VR simulator [device] parameters + * @return [VrStereoConfig] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadVrStereoConfig(device: VrDeviceInfo) : VrStereoConfig { + return LoadVrStereoConfig(device.readValue()).getPointer(MemScope()).pointed +} + +/** + * Unload VR stereo [config] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadVrStereoConfig(config: VrStereoConfig) { + UnloadVrStereoConfig(config.readValue()) +} + +//=======================================================// +// SHADER MANAGEMENT FUNCTIONS +//=======================================================// + +/** + * Load [shader] from files and bind default locations + * @return [Shader] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadShader(vsFileName: String, fsFileName: String) : Shader { + return LoadShader(vsFileName, fsFileName).getPointer(MemScope()).pointed +} + +/** + * Load [shader] from code strings and bind default locations + * @return [Shader] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadShaderFromMemory(vsCode: String, fsCode: String) : Shader { + return LoadShaderFromMemory(vsCode, fsCode).getPointer(MemScope()).pointed +} + +/** + * Get [shader] uniform location + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getShaderLocation(shader: Shader, uniformName: String) : Int { + return GetShaderLocation(shader.readValue(), uniformName) +} + +/** + * Get [shader] attribute location + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getShaderLocationAttrib(shader: Shader, attribName: String) : Int { + return GetShaderLocationAttrib(shader.readValue(), attribName) +} + +/** + * Set [shader] uniform value + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setShaderValue(shader: Shader, locIndex: kaylibkit.kEnums.ShaderLocationIndex, value: COpaquePointer, uniformType: kaylibkit.kEnums.ShaderUniformDataType) { + return SetShaderValue(shader.readValue(), locIndex.value, value, uniformType.value) +} + +/** + * Set [shader] uniform value vector + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setShaderValueV(shader: Shader, locIndex: kaylibkit.kEnums.ShaderLocationIndex, value: COpaquePointer, uniformType: kaylibkit.kEnums.ShaderUniformDataType, count: Int) { + SetShaderValueV(shader.readValue(), locIndex.value, value, uniformType.value, count) +} + +/** + * Set [shader] uniform value ([Matrix] 4x4) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setShaderValueMatrix(shader: Shader, locIndex: kaylibkit.kEnums.ShaderLocationIndex, mat: Matrix) { + SetShaderValueMatrix(shader.readValue(), locIndex.value, mat.readValue()) +} + +/** + * Set [shader] uniform value for texture (sampler2d) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setShaderValueTexture(shader: Shader, locIndex: kaylibkit.kEnums.ShaderLocationIndex, texture: Texture2D) { + SetShaderValueTexture(shader.readValue(), locIndex.value, texture.readValue()) +} + +/** + * Unload [shader] from GPU memory (VRAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadShader(shader: Shader) { + UnloadShader(shader.readValue()) +} + +//=======================================================// +// SCREEN-SPACE MANAGEMENT FUNCTIONS +//=======================================================// + +/** + * Get a [Ray] trace from mouse position + * @return [Ray] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMouseRay(mousePosition: Vector2, camera: Camera) : Ray { + return GetMouseRay(mousePosition.readValue(), camera.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get [camera] transform [Matrix] (view [Matrix]]) + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraMatrix(camera: Camera) : Matrix { + return GetCameraMatrix(camera.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get [camera] 2d transform [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCameraMatrix2D(camera: Camera2D) : Matrix { + return GetCameraMatrix2D(camera.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get the screen space position for a 3d world space [position] + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWorldToScreen(position: Vector3, camera: Camera) : Vector2 { + return GetWorldToScreen(position.readValue(), camera.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get size position for a 3d world space [position] + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWorldToScreenEx(position: Vector3, camera: Camera, width: Int, height: Int) : Vector2 { + return GetWorldToScreenEx(position.readValue(), camera.readValue(), width, height).getPointer(MemScope()).pointed +} + +/** + * Get the screen space [position] for a 2d [camera] world space position + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWorldToScreen2D(position: Vector2, camera: Camera2D) : Vector2 { + return GetWorldToScreen2D(position.readValue(), camera.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get the world space position for a 2d [camera] screen space position + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getScreenToWorld2D(position: Vector2, camera: Camera2D) : Vector2 { + return GetScreenToWorld2D(position.readValue(), camera.readValue()).getPointer(MemScope()).pointed +} + +//=======================================================// +// TIMING FUNCTIONS +//=======================================================// + +/** + * Set target FPS (maximum) + */ +inline fun setTargetFPS(fps: Int) { + SetTargetFPS(fps) +} + +/** + * Get current FPS + * @return [Int] + */ +inline fun getFPS() : Int { + return GetFPS() +} + +/** + * Get time in seconds for last frame drawn (delta time) + * @return [Float] + */ +inline fun getFrameTime() : Float { + return GetFrameTime() +} + +/** + * Get elapsed time in seconds since InitWindow() + * @return [Double] + */ +inline fun getTime() : Double { + return GetTime() +} + +//=======================================================// +// MISC FUNCTIONS +//=======================================================// + +/** + * Returns a random value between [min] and [max] (both included) + * @return [Int] + */ +inline fun getRandomValue(min: Int, max: Int) : Int { + return GetRandomValue(min, max) +} + +/** + * Set the [seed] for the random number generator + */ +inline fun setRandomSeed(seed: UInt) { + SetRandomSeed(seed) +} + +/** + * Takes a screenshot of current screen (filename extension defines format) + */ +inline fun takeScreenshot(fileName: String) { + TakeScreenshot(fileName) +} + +/** + * Setup init configuration [flags] (view FLAGS) + */ +inline fun setConfigFlags(flags: kaylibkit.kEnums.ConfigFlag) { + SetConfigFlags(flags.value) +} + +/** + * Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) + * Vararg parameter of type Any does not translate to any C type. You can only use traceLog with log level and text(String) + */ +inline fun traceLog(logLevel: TraceLogLevel, text: String) { + return TraceLog(logLevel.level, text) +} + +/** + * Set the current threshold (minimum) log level + */ +inline fun setTraceLogLevel(logLevel: TraceLogLevel) { + SetTraceLogLevel(logLevel.level) +} + +//=======================================================// +// CUSTOM CALLBACKS FUNCTIONS +//=======================================================// + + +/** + * Logging: Redirect trace log messages + */ +@OptIn(ExperimentalForeignApi::class) +typealias TraceLogCallback = CPointer?, platform.posix.va_list?) -> Unit>> + + +/** + * Set custom trace log + * @param callback takes in a pointer to a C Function. + */ +@OptIn(ExperimentalForeignApi::class) +@PublishedApi +internal expect fun setTraceLogCallbackInternal(callback: TraceLogCallback) + +/** + * Set custom file binary data loader + * @param callback takes in a pointer to a C Function. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setLoadFileDataCallback(callback: LoadFileDataCallback) { + SetLoadFileDataCallback(callback) +} + +/** + * Set custom file binary data saver + * @param callback takes in a pointer to a C Function. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setSaveFileDataCallback(callback: SaveFileDataCallback) { + SetSaveFileDataCallback(callback) +} + +/** + * Set custom file text data loader + * @param callback takes in a pointer to a C Function. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setLoadFileTextCallback(callback: LoadFileTextCallback) { + SetLoadFileTextCallback(callback) +} + +/** + * Set custom file text data saver + * @param callback takes in a pointer to a C Function. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setSaveFileTextCallback(callback: SaveFileTextCallback) { + SetSaveFileTextCallback(callback) +} + +//=======================================================// +// FILE MANAGEMENT FUNCTIONS +//=======================================================// + +/** + * Load file data as byte array (read) + * @return [UByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFileData(fileName: String, bytesRead: UIntVar) : UByteVar? { + return LoadFileData(fileName, bytesRead.ptr)?.getPointer(MemScope())?.pointed +} + +/** + * Unload file [data] allocated by LoadFileData() + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadFileData(data: UByteVar) { + UnloadFileData(data.ptr) +} + +/** + * Save [data] to file from byte array (write), returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun saveFileData(fileName: String, data: COpaquePointer, bytesToWrite: UInt) : Boolean { + return SaveFileData(fileName, data, bytesToWrite) +} + +/** + * Load text data from file (read), returns a '\0' terminated string + * @return [ByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFileText(fileName: String) : ByteVar? { + return LoadFileText(fileName)?.getPointer(MemScope())?.pointed +} + +/** + * Unload file [text] data allocated by LoadFileText() + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadFileText(text: ByteVar) { + UnloadFileText(text.ptr) +} + +/** + * Save text data to file (write), string must be '\0' terminated, returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun saveFileText(fileName: String, text: String) : Boolean { + return SaveFileText(fileName, text.cstr) +} + +/** + * Check if file exists + * @return [Boolean] + */ +inline fun fileExists(fileName: String) : Boolean { + return FileExists(fileName) +} + +/** + * Check if a directory path exists + * @return [Boolean] + */ +inline fun directoryExists(dirPath: String) : Boolean { + return DirectoryExists(dirPath) +} + +/** + * Check file extension (including point: .png, .wav) + * @return [Boolean] + */ +inline fun isFileExtension(fileName: String, ext: String) : Boolean { + return IsFileExtension(fileName, ext) +} + +/** + * Get pointer to extension for a filename string (includes dot: `.png`) + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getFileExtension(fileName: String) : String { + return GetFileExtension(fileName)?.toKString() ?: "" +} + +/** + * Get pointer to filename for a path string + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getFileName(filePath: String) : String { + return GetFileName(filePath)?.toKString() ?: "" +} + +/** + * Get filename string without extension (uses inline string) + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getFileNameWithoutExt(filePath: String) : String { + return GetFileNameWithoutExt(filePath)?.toKString() ?: "" +} + +/** + * Get full path for a given fileName with path (uses inline string) + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getDirectoryPath(filePath: String) : String { + return GetDirectoryPath(filePath)?.toKString() ?: "" +} + +/** + * Get previous directory path for a given path (uses inline string) + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getPrevDirectoryPath(dirPath: String) : String { + return GetPrevDirectoryPath(dirPath)?.toKString() ?: "" +} + +/** + * Get current working directory (uses inline string) + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getWorkingDirectory() : String { + return GetWorkingDirectory()?.toKString() ?: "" +} + +/** + * Get filenames in a directory path (memory should be freed) + * @return [FilePathList] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadDirectoryFiles(dirPath: String) : FilePathList { + return LoadDirectoryFiles(dirPath).getPointer(MemScope()).pointed +} + +/** + * Clear directory [files] paths buffers (free memory) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadDirectoryFiles(files: FilePathList) { + return UnloadDirectoryFiles(files.readValue()) +} + +/** + * Change working directory, return true on success + * @return [Boolean] + */ +inline fun changeDirectory(dir: String) : Boolean { + return ChangeDirectory(dir) +} + +/** + * Check if a file has been dropped into window + * @return [Boolean] + */ +inline val isFileDropped: Boolean + get() { return IsFileDropped() } + +/** + * Get dropped files names (memory should be freed) + * @return [FilePathList] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadDroppedFiles() : FilePathList { + return LoadDroppedFiles().getPointer(MemScope()).pointed +} + +/** + * Clear dropped [files] paths buffer (free memory) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadDroppedFiles(files: FilePathList) { + UnloadDroppedFiles(files.readValue()) +} + +/** + * Get file modification time (last write time) + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class) +inline fun getFileModTime(fileName: String): Int { + return GetFileModTime(fileName).convert() +} + +//=======================================================// +// COMPRESSION/ENCODING FUNCTIONS +//=======================================================// + +/** + * Compress [data] (DEFLATE algorithm) + * @return [UByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun compressData(data: UByteVar, dataLength: Int, compDataLength: IntVar) : UByteVar? { + return CompressData(data.ptr, dataLength, compDataLength.ptr)?.getPointer(MemScope())?.pointed +} + +/** + * Decompress [data] (DEFLATE algorithm) + * @return [UByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun decompressData(compData: UByteVar, compDataLength: Int, dataLength: IntVar) : UByteVar? { + return DecompressData(compData.ptr, compDataLength, dataLength.ptr)?.getPointer(MemScope())?.pointed +} + +/** + * Encode [data] to Base64 string + * @return [ByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun encodeDataBase64(data: UByteVar, dataLength: Int, outputLength: IntVar) : ByteVar? { + return EncodeDataBase64(data.ptr, dataLength, outputLength.ptr)?.getPointer(MemScope())?.pointed +} + +/** + * Encode [data] to Base64 string + * @return [UByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun decodeDataBase64(data: UByteVar, outputLength: IntVar) : UByteVar? { + return DecodeDataBase64(data.ptr, outputLength.ptr)?.getPointer(MemScope())?.pointed +} + +//=======================================================// +// WEB RELATED FUNCTIONS +//=======================================================// + +/** + * Open [url] with default system browser (if available) + */ +inline fun openURL(url: String) { + OpenURL(url) +} + +//=======================================================// +// INPUT HANDLING FUNCTIONS - KEYBOARDS +//=======================================================// + +/** + * Check if a key has been pressed once + * @return [Boolean] + */ +inline fun isKeyPressed(keyboardKey: kaylibkit.kEnums.KeyboardKey) : Boolean { + return IsKeyPressed(keyboardKey.key) +} + +/** + * Check if a key is being pressed + * @return [Boolean] + */ +inline fun isKeyDown(keyboardKey: kaylibkit.kEnums.KeyboardKey) : Boolean { + return IsKeyDown(keyboardKey.key) +} + +/** + * Check if a key has been released once + * @return [Boolean] + */ +inline fun isKeyReleased(keyboardKey: kaylibkit.kEnums.KeyboardKey) : Boolean { + return IsKeyReleased(keyboardKey.key) +} + +/** + * Check if a key is NOT being pressed + * @return [Boolean] + */ +inline fun isKeyUp(keyboardKey: kaylibkit.kEnums.KeyboardKey) : Boolean { + return IsKeyUp(keyboardKey.key) +} + +/** + * Set a custom key to exit program (default is ESC) + */ +inline fun setExitKey(keyboardKey: kaylibkit.kEnums.KeyboardKey) { + SetExitKey(keyboardKey.key) +} + +/** + * Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty + * @return [Int] + */ +inline fun getKeyPressed(): Int { + return GetKeyPressed() +} + +/** + * Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty + * @return [Char] + */ +inline fun getCharPressed() : Char { + return GetCharPressed().toChar() +} + +//=======================================================// +// INPUT HANDLING FUNCTIONS - GAMEPADS +//=======================================================// + +/** + * Check if a [gamepad] is available + * @return [Boolean] + */ +inline fun isGamepadAvailable(gamepad: Int) : Boolean { + return IsGamepadAvailable(gamepad) +} + +/** + * Get [gamepad] internal name id + * @return [String] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getGamepadName(gamepad: Int) : String { + return GetGamepadName(gamepad)?.toKString() ?: "WARNING: GamePad not found!" +} + +/** + * Check if a [gamepad] [button] has been pressed once + * @return [Boolean] + */ +inline fun isGamepadButtonPressed(gamepad: Int, button: kaylibkit.kEnums.GamepadButton) : Boolean { + return IsGamepadButtonPressed(gamepad, button.value) +} + +/** + * Check if a [gamepad] [button] is being pressed + * @return [Boolean] + */ +inline fun isGamepadButtonDown(gamepad: Int, button: kaylibkit.kEnums.GamepadButton) : Boolean { + return IsGamepadButtonDown(gamepad, button.value) +} + +/** + * Check if a [gamepad] [button] has been released once + * @return [Boolean] + */ +inline fun isGamepadButtonReleased(gamepad: Int, button: kaylibkit.kEnums.GamepadButton) : Boolean { + return IsGamepadButtonReleased(gamepad, button.value) +} + +/** + * Check if a [gamepad] [button] is NOT being pressed + * @return [Boolean] + */ +inline fun isGamepadButtonUp(gamepad: Int, button: kaylibkit.kEnums.GamepadButton) : Boolean { + return IsGamepadButtonUp(gamepad, button.value) +} + +/** + * Get the last gamepad button pressed - Will always return + * @return [Int] + */ +inline fun getGamepadButtonPressed() : Int { + return GetGamepadButtonPressed() +} + +/** + * Get gamepad axis count for a [gamepad] axis + * @return [Int] + */ +inline fun getGamepadAxisCount(gamepad: Int) : Int { + return GetGamepadAxisCount(gamepad) +} + +/** + * Get axis movement value for a [gamepad] [axis] + * @return [Float] + */ +inline fun getGamepadAxisMovement(gamepad: Int, axis: kaylibkit.kEnums.GamepadAxis) : Float { + return GetGamepadAxisMovement(gamepad, axis.value) +} + +/** + * Set internal gamepad [mappings] (SDL_GameControllerDB) + * @return [Int] + */ +inline fun setGamepadMappings(mappings: String) : Int { + return SetGamepadMappings(mappings) +} + +//=======================================================// +// INPUT HANDLING FUNCTIONS - MOUSE +//=======================================================// + +/** + * Check if a mouse [button] has been pressed once + * @return [Boolean] + */ +inline fun isMouseButtonPressed(button: kaylibkit.kEnums.MouseButton) : Boolean { + return IsMouseButtonPressed(button.value) +} + +/** + * Check if a mouse [button] is being pressed + * @return [Boolean] + */ +inline fun isMouseButtonDown(button: kaylibkit.kEnums.MouseButton) : Boolean { + return IsMouseButtonDown(button.value) +} + +/** + * Check if a mouse [button] has been released once + * @return [Boolean] + */ +inline fun isMouseButtonReleased(button: kaylibkit.kEnums.MouseButton) : Boolean { + return IsMouseButtonReleased(button.value) +} + +/** + * Check if a mouse [button] is NOT being pressed + * @return [Boolean] + */ +inline fun isMouseButtonUp(button: kaylibkit.kEnums.MouseButton) : Boolean { + return IsMouseButtonUp(button.value) +} + +/** + * Get mouse position X + * @return [Int] + */ +inline fun getMouseX() : Int { + return GetMouseX() +} + +/** + * Get mouse position Y + * @return [Int] + */ +inline fun getMouseY() : Int { + return GetMouseY() +} + +/** + * Get mouse position XY - This function uses getPointer with passed MemScope() as scope! You can also allocate your arena in the parameter + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMousePosition() : Vector2 { + return GetMousePosition().getPointer(MemScope()).pointed + +} + +/** + * Set mouse position [x] [y] + */ +inline fun setMousePosition(x: Int, y : Int) { + SetMousePosition(x, y) +} + +/** + * Set mouse offset + */ +inline fun setMouseOffset(offsetX: Int, offsetY: Int) { + SetMouseOffset(offsetX, offsetY) +} + +/** + * Set mouse scaling + */ +inline fun setMouseScale(scaleX: Float, scaleY: Float) { + SetMouseScale(scaleX, scaleY) +} + +/** + * Get mouse wheel movement Y + * @return [Float] + */ +inline fun getMouseWheelMove() : Float { + return GetMouseWheelMove() +} + +/** + * Set mouse [cursor] + */ +inline fun setMouseCursor(cursor: kaylibkit.kEnums.MouseCursor) { + SetMouseCursor(cursor.value) +} + +//=======================================================// +// INPUT HANDLING FUNCTIONS - TOUCH +//=======================================================// + +/** + * Get touch position X for touch point 0 (relative to screen size) + * @return [Int] + */ +inline fun getTouchX() : Int { + return GetTouchX() +} + +/** + * Get touch position Y for touch point 0 (relative to screen size) + * @return [Int] + */ +inline fun getTouchY() : Int { + return GetTouchY() +} + +/** + * Get touch position XY for a touch point [index] (relative to screen size) + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getTouchPosition(index: Int) : Vector2 { + return GetTouchPosition(index).getPointer(MemScope()).pointed +} + +/** + * Get touch point identifier for given [index] + * @return [Int] + */ +inline fun getTouchPointId(index: Int) : Int{ + return GetTouchPointId(index) +} + +/** + * Get number of touch points + * @return [Int] + */ +inline fun getTouchPointCount() : Int { + return GetTouchPointCount() +} diff --git a/src/commonMain/kotlin/kaylibkit/kEasing/KEasing.kt b/src/commonMain/kotlin/kaylibkit/kEasing/KEasing.kt new file mode 100644 index 0000000..f5f2477 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEasing/KEasing.kt @@ -0,0 +1,289 @@ +package kaylibkit.kEasing + +import platform.posix.pow +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +// -- Module: kEasing + +//=======================================================// +// EASINGS FUNCTIONS +//=======================================================// + +/** + * * A port of Robert Penner's easing equations to Kotlin from C (Raylib) (http://robertpenner.com/easing/) +* +* Robert Penner License +* --------------------------------------------------------------------------------- +* Open source under the BSD License. +* +* Copyright (c) 2001 Robert Penner. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* - Neither the name of the author nor the names of contributors may be used +* to endorse or promote products derived from this software without specific +* prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +* --------------------------------------------------------------------------------- +* +* Copyright (c) 2015-2023 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + + +/* + How to use: +* The four inputs t,b,c,d are defined as follows: +* t = current time (in any unit measure, but same unit as duration) +* b = starting value to interpolate +* c = the total change in value of b that needs to occur +* d = total time it should take to complete (duration) + */ + +// Linear Easing Functions +inline fun linearNone(t: Float, b: Float, c: Float, d: Float): Float { + return (c*t/d + b) +} + +inline fun linearIn(t: Float, b: Float, c: Float, d: Float): Float { + return (c*t/d + b) +} + +inline fun linearOut(t: Float, b: Float, c: Float, d: Float): Float { + return (c*t/d + b) +} + +inline fun linearInOut(t: Float, b: Float, c: Float, d: Float): Float { + return (c*t/d + b) +} + +// Sine Easing Functions +inline fun sineIn(t: Float, b: Float, c: Float, d: Float): Float { + return (-c*cos(t/d*(PI/2.0f)) + c + b).toFloat() +} + +inline fun sineOut(t: Float, b: Float, c: Float, d: Float): Float { + return c * sin(t / d * (PI / 2.0f)).toFloat() + b +} + +inline fun sineInOut(t: Float, b: Float, c: Float, d: Float): Float { + return -c / 2.0f * (cos(PI * t / d) - 1.0f).toFloat() + b +} + +// Circular Easing Functions +inline fun circIn(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + t2 /= d + return -c * (sqrt(1.0f - t2 * t2) - 1.0f) + b +} + +inline fun circOut(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + t2 = t2 / d - 1.0f + return c * sqrt(1.0f - t2 * t2) + b +} + +inline fun circInOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + if (d / 2.0f.let { t2 /= it; t2 } < 1.0f) return -c / 2.0f * (sqrt(1.0f - t2 * t2) - 1.0f) + b + t2 -= 2.0f + return c / 2.0f * (sqrt(1.0f - t2 * t2) + 1.0f) + b +} + +// Cubic Easing Functions +inline fun cubicIn(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + t2 /= d + return c * t2 * t2 * t2 + b +} + +inline fun cubicOut(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + t2 = t2 / d - 1.0f + return c * (t2 * t2 * t2 + 1.0f) + b +} + +inline fun cubicInOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + if (d / 2.0f.let { t2 /= it; t2 } < 1.0f) return c / 2.0f * t2 * t2 * t2 + b + t2 -= 2.0f + return c / 2.0f * (t2 * t2 * t2 + 2.0f) + b +} + +// Quadratic Easing Functions +inline fun quadIn(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + t2 /= d + return c * t2 * t2 + b +} + +inline fun quadOut(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + t2 /= d + return -c * t2 * (t2 - 2.0f) + b +} + +inline fun quadInOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + return if (d / 2.let { t2 /= it; t2 } < 1) c / 2 * (t2 * t2) + b else -c / 2.0f * ((t2 - 1.0f) * (t2 - 3.0f) - 1.0f) + b +} + +// Exponential Easing Functions +inline fun expoIn(t: Float, b: Float, c: Float, d: Float): Float { + return if (t == 0.0f) b else c * pow(2.0, 10.0 * (t / d - 1.0f)).toFloat() + b +} + +inline fun expoOut(t: Float, b: Float, c: Float, d: Float): Float { + return if (t == d) b + c else c * (-pow(2.0, -10.0 * t / d) + 1.0f).toFloat() + b +} + +inline fun expoInOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + if (t2 == 0.0f) return b + if (t2 == d) return b + c + return if (d / 2.0f.let { t2 /= it; t2 } < 1.0f) (c / 2.0f * pow(2.0, 10.0 * (t2 - 1.0f)) + b).toFloat() else c / 2.0f * (-pow( + 2.0, + -10.0 * (t2 - 1.0f) + ) + 2.0f).toFloat() + b +} + +// Back Easing Functions +inline fun backIn(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + val s = 1.70158f + t2 /= d + val postFix = t2 + return c * postFix * t2 * ((s + 1.0f) * t2 - s) + b +} + +fun backOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + val s = 1.70158f + t2 = t2 / d - 1.0f + return c * (t2 * t2 * ((s + 1.0f) * t2 + s) + 1.0f) + b +} + +fun backInOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + var s = 1.70158f + if (d / 2.0f.let { t2 /= it; t2 } < 1.0f) { + s *= 1.525f + return c / 2.0f * (t2 * t2 * ((s + 1.0f) * t2 - s)) + b + } + t2 -= 2.0f + val postFix = t2 + s *= 1.525f + return c / 2.0f * (postFix * t2 * ((s + 1.0f) * t2 + s) + 2.0f) + b +} + +// Bounce Easing Function +inline fun bounceOut(t: Float, b: Float, c: Float, d: Float): Float +{ + var t2 = t + return if (d.let { t2 /= it; t2 } < 1.0f / 2.75f) { + c * (7.5625f * t2 * t2) + b + } else if (t2 < 2.0f / 2.75f) { + t2 -= 1.5f / 2.75f + val postFix = t2 + c * (7.5625f * postFix * t2 + 0.75f) + b + } else if (t2 < 2.5 / 2.75) { + t2 -= 2.25f / 2.75f + val postFix = t2 + c * (7.5625f * postFix * t2 + 0.9375f) + b + } else { + t2 -= 2.625f / 2.75f + val postFix = t2 + c * (7.5625f * postFix * t2 + 0.984375f) + b + } +} + +inline fun bounceIn(t: Float, b: Float, c: Float, d: Float): Float { + return c - bounceOut(d - t, 0.0f, c, d) + b +} + +inline fun bounceInOut(t: Float, b: Float, c: Float, d: Float): Float // Ease: Bounce In Out +{ + return if (t < d / 2.0f) bounceIn(t * 2.0f, 0.0f, c, d) * 0.5f + b else bounceOut( + t * 2.0f - d, + 0.0f, + c, + d + ) * 0.5f + c * 0.5f + b +} + +// Elastic Easing Function +inline fun elasticIn(t: Float, b: Float, c: Float, d: Float): Float // Ease: Elastic In +{ + var t2 = t + if (t2 == 0.0f) return b + if (d.let { t2 /= it; t2 } == 1.0f) return b + c + val p = d * 0.3f + val s = p / 4.0f + val postFix: Float = c * pow(2.0, 10.0 * 1.0f.let { t2 -= it; t2 }).toFloat() + return -(postFix * sin((t2 * d - s) * (2.0f * PI) / p)).toFloat() + b +} + +inline fun elasticOut(t: Float, b: Float, c: Float, d: Float): Float // Ease: Elastic Out +{ + var t2 = t + if (t2 == 0.0f) return b + if (d.let { t2 /= it; t2 } == 1.0f) return b + c + val p = d * 0.3f + val s = p / 4.0f + return c * pow(2.0, -10.0 * t2).toFloat() * sin((t2 * d - s) * (2.0f * PI).toFloat() / p) + c + b +} + +fun elasticInOut(t: Float, b: Float, c: Float, d: Float): Float { + var t2 = t + if (t2 == 0.0f) return b + if (d / 2.0f.let { t2 /= it; t2 } == 2.0f) return b + c + val p = d * (0.3f * 1.5f) + val s = p / 4.0f + if (t2 < 1.0f) { + val postFix: Float = c * pow(2.0, 10.0 * 1.0f.let { t2 -= it; t2 }).toFloat() + return -0.5f * (postFix * sin((t2 * d - s) * (2.0f * PI) / p)).toFloat() + b + } + val postFix: Float = c * pow(2.0, -10.0 * 1.0f.let { t2 -= it; t2 }).toFloat() + return postFix * sin((t2 * d - s) * (2.0f * PI) / p).toFloat() * 0.5f + c + b +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/BlendMode.kt b/src/commonMain/kotlin/kaylibkit/kEnums/BlendMode.kt new file mode 100644 index 0000000..db26c19 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/BlendMode.kt @@ -0,0 +1,41 @@ +package kaylibkit.kEnums + +/** + * Color blending modes (pre-defined) + */ +enum class BlendMode(val value: Int) { + /** + * Blend textures considering alpha (default) + */ + ALPHA(0), + + /** + * Blend textures adding colors + */ + ADDITIVE(1), + + /** + * Blend textures multiplying colors + */ + MULTIPLIED(2), + + /** + * Blend textures adding colors (alternative) + */ + ADDCOLORS(3), + + /** + * Blend textures subtracting colors (alternative) + */ + SUBTRACT_COLORS(4), + + /** + * Blend premultiplied textures considering alpha + */ + ALPHA_PRE_MULTIPLIED(5), + + /** + * Blend textures using custom src/dst factors (use rlSetBlendMode()) + */ + CUSTOM(6,) +} diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/CameraMode.kt b/src/commonMain/kotlin/kaylibkit/kEnums/CameraMode.kt new file mode 100644 index 0000000..9e31b00 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/CameraMode.kt @@ -0,0 +1,31 @@ +package kaylibkit.kEnums + +/** + * Camera system modes + */ +enum class CameraMode(val value: Int) { + /** + * Custom camera + */ + CUSTOM(0), + + /** + * Free camera + */ + FREE(1), + + /** + * Orbital camera + */ + ORBITAL(2), + + /** + * First person camera + */ + FIRST_PERSON(3), + + /** + * Third person camera + */ + THIRD_PERSON(4), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/CameraProjection.kt b/src/commonMain/kotlin/kaylibkit/kEnums/CameraProjection.kt new file mode 100644 index 0000000..f98757f --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/CameraProjection.kt @@ -0,0 +1,16 @@ +package kaylibkit.kEnums + +/** + * Camera projection + */ +enum class CameraProjection(val value: Int) { + /** + * Perspective Projection + */ + PERSPECTIVE(0), + + /** + * Orthographic projection + */ + ORTHOGRAPHIC(1), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/ConfigFlag.kt b/src/commonMain/kotlin/kaylibkit/kEnums/ConfigFlag.kt new file mode 100644 index 0000000..aac3a06 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/ConfigFlag.kt @@ -0,0 +1,83 @@ +package kaylibkit.kEnums + +/** + * System/Window config flags + * NOTE: Every bit registers one state (use it with bit masks) + * By default all flags are set to 0 + */ +enum class ConfigFlag(val value: UInt) { + /** + * Set to try enabling V-Sync on GPU + */ + VSYNC_HINT(0x00000040u), + + /** + * Set to run program in fullscreen + */ + FULLSCREEN_MODE(0x00000002u), + + /** + * Set to allow resizable window + */ + WINDOW_RESIZABLE(0x00000004u), + + /** + * Set to disable window decoration (frame and buttons) + */ + WINDOW_UNDECORATED(0x00000008u), + + /** + * Set to hide window + */ + WINDOW_HIDDEN(0x00000080u), + + /** + * Set to minimize window (iconify) + */ + WINDOW_MINIMIZED(0x00000200u), + + /** + * Set to maximize window (expanded to monitor) + */ + WINDOW_MAXIMIZED(0x00000400u), + + /** + * Set to window non focused + */ + WINDOW_UNFOCUSED(0x00000800u), + + /** + * Set to window always on top + */ + WINDOW_TOPMOST(0x00001000u), + + /** + * Set to allow windows running while minimized + */ + WINDOW_ALWAYS_RUN(0x00000100u), + + /** + * Set to allow transparent framebuffer + */ + WINDOW_TRANSPARENT(0x00000010u), + + /** + * Set to support HighDPI + */ + WINDOW_HIGHDPI(0x00002000u), + + /** + * Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED + */ + WINDOW_MOUSE_PASSTHROUGH(0x00004000u), + + /** + * Set to try enabling MSAA 4X + */ + MSAA_4X_HINT(0x00000020u), + + /** + * Set to try enabling interlaced video format (for V3D) + */ + INTERLACED_HINT(0x00010000u) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/CubemapLayout.kt b/src/commonMain/kotlin/kaylibkit/kEnums/CubemapLayout.kt new file mode 100644 index 0000000..f70ea07 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/CubemapLayout.kt @@ -0,0 +1,36 @@ +package kaylibkit.kEnums + +/** + * Cubemap layouts + */ +enum class CubemapLayout(val value: Int) { + /** + * Automatically detect layout type + */ + AUTO_DETECT(0), + + /** + * Layout is defined by a vertical line with faces + */ + LINE_VERTICAL(1), + + /** + * Layout is defined by a horizontal line with faces + */ + LINE_HORIZONTAL(2), + + /** + * Layout is defined by a 3x4 cross with cubemap face + */ + CROSS_THREE_BY_FOUR(3), + + /** + * Layout is defined by a 4x3 cross with cubemap face + */ + CROSS_FOUR_BY_THREE(4), + + /** + * Layout is defined by a panorama image (equirectangular map) + */ + PANORAMA(5), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/FontType.kt b/src/commonMain/kotlin/kaylibkit/kEnums/FontType.kt new file mode 100644 index 0000000..7fb2be4 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/FontType.kt @@ -0,0 +1,21 @@ +package kaylibkit.kEnums + +/** + * Font type defines generation method + */ +enum class FontType(val value: Int) { + /** + * Default font generation, anti-aliased + */ + DEFAULT(0), + + /** + * Bitmap font generation, no anti-aliasing + */ + BITMAP(1), + + /** + * SDF Font generation, requires external shader + */ + SDF(2), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/GamepadAxis.kt b/src/commonMain/kotlin/kaylibkit/kEnums/GamepadAxis.kt new file mode 100644 index 0000000..83f4a64 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/GamepadAxis.kt @@ -0,0 +1,33 @@ +package kaylibkit.kEnums + +enum class GamepadAxis(val value: Int) { + /** + * Gamepad left stick X axis + */ + LEFT_X(0), + + /** + * Gamepad left stick Y axis + */ + LEFT_Y(1), + + /** + * Gamepad right stick X axis + */ + RIGHT_X(2), + + /** + * Gamepad right stick Y axis + */ + RIGHT_Y(3), + + /** + * Gamepad back trigger left, pressure level: [1..-1] + */ + LEFT_TRIGGER(4), + + /** + * Gamepad back trigger right, pressure level: [1..-1] + */ + RIGHT_TRIGGER(5), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/GamepadButton.kt b/src/commonMain/kotlin/kaylibkit/kEnums/GamepadButton.kt new file mode 100644 index 0000000..954f48c --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/GamepadButton.kt @@ -0,0 +1,93 @@ +package kaylibkit.kEnums + +enum class GamepadButton(val value: Int) { + /** + * Unknown button, just for error checking + */ + UNKNOWN(0), + + /** + * Gamepad left DPAD up button + */ + LEFT_FACE_UP(1), + + /** + * Gamepad left DPAD right button + */ + LEFT_FACE_RIGHT(2), + + /** + * Gamepad left DPAD down button + */ + LEFT_FACE_DOWN(3), + + /** + * Gamepad left DPAD left button + */ + LEFT_FACE_LEFT(4), + + /** + * Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + */ + RIGHT_FACE_UP(5), + + /** + * Gamepad right button right (i.e. PS3: Square, Xbox: X) + */ + RIGHT_FACE_RIGHT(6), + + /** + * Gamepad right button down (i.e. PS3: Cross, Xbox: A) + */ + RIGHT_FACE_DOWN(7), + + /** + * Gamepad right button left (i.e. PS3: Circle, Xbox: B) + */ + RIGHT_FACE_LEFT(8), + + /** + * Gamepad top/back trigger left (first), it could be a trailing button + */ + LEFT_TRIGGER1(9), + + /** + * Gamepad top/back trigger left (second), it could be a trailing button + */ + LEFT_TRIGGER2(10), + + /** + * Gamepad top/back trigger right (one), it could be a trailing button + */ + RIGHT_TRIGGER1(11), + + /** + * Gamepad top/back trigger right (second), it could be a trailing button + */ + RIGHT_TRIGGER2(12), + + /** + * Gamepad center buttons, left one (i.e. PS3: Select) + */ + MIDDLE_LEFT(13), + + /** + * Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + */ + MIDDLE(14), + + /** + * Gamepad center buttons, right one (i.e. PS3: Start) + */ + MIDDLE_RIGHT(15), + + /** + * Gamepad joystick pressed button left + */ + LEFT_THUMB(16), + + /** + * Gamepad joystick pressed button right + */ + RIGHT_THUMB(17), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/Gesture.kt b/src/commonMain/kotlin/kaylibkit/kEnums/Gesture.kt new file mode 100644 index 0000000..2d2f041 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/Gesture.kt @@ -0,0 +1,19 @@ +package kaylibkit.kEnums + +/** + * Gestures + * It could be used as flags to enable only some gestures + */ +enum class Gesture(val value: Int) { + NONE(0), + TAP(1,), + DOUBLE_TAP(2), + HOLD(4), + DRAG(8), + SWIPE_RIGHT(16), + SWIPE_LEFT(32), + SWIPE_UP(64), + SWIPE_DOWN(128), + PINCH_IN(256), + PINCH_OUT(512), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/KeyboardKey.kt b/src/commonMain/kotlin/kaylibkit/kEnums/KeyboardKey.kt new file mode 100644 index 0000000..5eec1dc --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/KeyboardKey.kt @@ -0,0 +1,130 @@ +package kaylibkit.kEnums + +/** + * Keyboard keys (US keyboard layout) + * + * Use GetKeyPressed() to allow redefining + * + * required keys for alternative layouts + */ +enum class KeyboardKey(val key: Int) { + // Alphanumeric keys + APOSTROPHE(39), + COMMA(44), + MINUS(45), + PERIOD(46), + SLASH(47), + NUMBER0(48), + NUMBER1(49), + NUMBER2(50), + NUMBER3(51), + NUMBER4(52), + NUMBER5(53), + NUMBER6(54), + NUMBER7(55), + NUMBER8(56), + NUMBER(57), + SEMICOLON(59), + EQUAL(61), + A(65), + B(66), + C(67), + D(68), + E(69), + F(70), + G(71), + H(72), + I(73), + J(74), + K(75), + L(76), + M(77), + N(78), + O(79), + P(80), + Q(81), + R(82), + S(83), + T(84), + U(85), + V(86), + W(87), + X(88), + Y(89), + Z(90), + + // Function keys + SPACE(32), + ESCAPE(256), + ENTER(257), + TAB(258), + BACKSPACE(259), + INSERT(260), + DELETE(261), + RIGHT(262), + LEFT(263), + DOWN(264), + UP(265), + PAGEUP(266), + PAGEDOWN(267), + HOME(268), + END(269), + CAPSLOCK(280), + SCROLLLOCK(281), + NUMLOCK(282), + PRINTSCREEM(283), + PAUSE(284), + FUNCTION1(290), + FUNCTION2(291), + FUNCTION3(292), + FUNCTION4(293), + FUNCTION5(294), + FUNCTION6(295), + FUNCTION7(296), + FUNCTION8(297), + FUNCTION9(298), + FUNCTION10(299), + FUNCTION11(300), + FUNCTION12(301), + LEFTSHIFT(340), + LEFTCONTROL(341), + LEFTALT(342), + LEFTSUPER(343), + RIGHTSHIFT(344), + RIGHTCONTROL(345), + RIGHTALT(346), + RIGHTSUPER(347), + KBMENU(348), + LEFTBRACKET(91), + BACKSLASH(92), + RIGHTBRACKET(93), + GRAVE(96), + + // Keypad keys + KEYPAD0(320), + KEYPAD1(321), + KEYPAD2(322), + KEYPAD3(323), + KEYPAD4(324), + KEYPAD5(325), + KEYPAD6(326), + KEYPAD7(327), + KEYPAD8(328), + KEYPAD9(329), + KEYPADDECIMAL(330), + KEYPADDIVIDE(331), + KEYPADMULTIPLY(332), + KEYPADSUBTRACT(333), + KEYPADADD(334), + KEYPADENTER(335), + KEYPADEQUAL(336), + UNKNOWN(-1); + + // Android key buttons + val ANDROIDBACK:Int = 4 + val ANDROIDMENU:Int = 82 + val ANDROIDVOLUMEUP:Int = 24 + val ANDROIDVOLUMEDOWN:Int = 25 + + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/MaterialMapIndex.kt b/src/commonMain/kotlin/kaylibkit/kEnums/MaterialMapIndex.kt new file mode 100644 index 0000000..dd96ef9 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/MaterialMapIndex.kt @@ -0,0 +1,63 @@ +package kaylibkit.kEnums + +enum class MaterialMapIndex(val value: Int) { + /** + * Albedo material (same as: MATERIAL_MAP_DIFFUSE) + */ + ALBEDO(0), + + /** + * Metalness material (same as: MATERIAL_MAP_SPECULAR) + */ + METALNESS(1), + + /** + * Normal material + */ + NORMAL(2), + + /** + * Roughness material + */ + ROUGHNESS(3), + + /** + * Ambient occlusion material + */ + OCCLUSION(4), + + /** + * Emission material + */ + EMISSION(5), + + /** + * Heightmap material + */ + HEIGHT(6), + + /** + * Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + */ + CUBEMAP(7), + + /** + * Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + */ + IRRADIANCE(8), + + /** + * Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + */ + PREFILTER(9), + + /** + * Brdf material + */ + BRDF(10), + + + DIFFUSE(0), + + SPECULAR(1), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/MouseButton.kt b/src/commonMain/kotlin/kaylibkit/kEnums/MouseButton.kt new file mode 100644 index 0000000..b9976aa --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/MouseButton.kt @@ -0,0 +1,41 @@ +package kaylibkit.kEnums + +/** + * Mouse Buttons + */ +enum class MouseButton(val value: Int) { + /** + * Mouse button left + */ + LEFT(0), + + /** + * Mouse button right + */ + RIGHT(1), + + /** + * Mouse button middle (pressed wheel) + */ + MIDDLE(2), + + /** + * Mouse button side (advanced mouse device) + */ + SIDE(3), + + /** + * Mouse button extra (advanced mouse device) + */ + EXTRA(4), + + /** + * Mouse button fordward (advanced mouse device) + */ + FORWARD(5), + + /** + * Mouse button back (advanced mouse device) + */ + BACK(6), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/MouseCursor.kt b/src/commonMain/kotlin/kaylibkit/kEnums/MouseCursor.kt new file mode 100644 index 0000000..479ce7b --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/MouseCursor.kt @@ -0,0 +1,61 @@ +package kaylibkit.kEnums + +/** + * Mouse Cursors + */ +enum class MouseCursor(val value: Int) { + /** + * Default pointer shape + */ + DEFAULT(0), + + /** + * Arrow shape + */ + ARROW(1), + + /** + * Text writing cursor shape + */ + IBEAM(2), + + /** + * Cross shape + */ + CROSSHAIR(3), + + /** + * Pointing hand cursor + */ + POINTINGHAND(4), + + /** + * The horizontal resize/move arrow shape + */ + RESIZE_EW(5), + + /** + * The vertical resize/move arrow shape + */ + RESIZE_NS(6), + + /** + * The top-left to bottom-right diagonal resize/move arrow shape + */ + RESIZE_NWSE(7), + + /** + * The top-right to bottom-left diagonal resize/move arrow shape + */ + RESIZENESW(8), + + /** + * The omni-directional resize/move cursor shape + */ + RESIZE_ALL(9), + + /** + * The operation-not-allowed shape + */ + NOT_ALLOWED(10), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/NPatchLayout.kt b/src/commonMain/kotlin/kaylibkit/kEnums/NPatchLayout.kt new file mode 100644 index 0000000..b28def9 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/NPatchLayout.kt @@ -0,0 +1,21 @@ +package kaylibkit.kEnums + +/** + * N-patch layout + */ +enum class NPatchLayout(val value: Int) { + /** + * Npatch layout: 3x3 tiles + */ + NINE_PATCH(0), + + /** + * Npatch layout: 1x3 tiles + */ + THREE_PATCH_VERTICAL(1), + + /** + * Npatch layout: 3x1 tiles + */ + THREE_PATCH_HORIZONTAL(2), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/PixelFormat.kt b/src/commonMain/kotlin/kaylibkit/kEnums/PixelFormat.kt new file mode 100644 index 0000000..81c3403 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/PixelFormat.kt @@ -0,0 +1,107 @@ +package kaylibkit.kEnums + +/** + * Pixel formats + * Support depends on OpenGL version and platform + */ +enum class PixelFormat(val value: Int) { + /** + * 8 bit per pixel (no alpha) + */ + UNCOMPRESSED_GRAYSCALE(1), + + /** + * 8*2 bpp (2 channels) + */ + UNCOMPRESSED_GRAYALPHA(2), + + /** + * 16 bpp + */ + UNCOMPRESSED_R5G6B5(3), + + /** + * 24 bpp + */ + UNCOMPRESSED_R8G8B8(4), + + /** + * 16 bpp (1 bit alpha) + */ + UNCOMPRESSED_R5G5B5A1(5), + + /** + * 16 bpp (4 bit alpha) + */ + UNCOMPRESSED_R4G4B4A4(6), + + /** + *32 bpp + */ + UNCOMPRESSED_R8G8B8A8(7), + + /** + * 32 bpp (1 channel - float) + */ + UNCOMPRESSED_R32(8), + + /** + * 32*3 bpp (3 channels - float) + */ + UNCOMPRESSED_R32G32B32(9), + + /** + * 32*4 bpp (4 channels - float) + */ + UNCOMPRESSED_R32G32B32A32(10), + + /** + * 4 bpp (no alpha) + */ + COMPRESSED_DXT_1RGB(11), + + /** + * 4 bpp (1 bit alpha) + */ + COMPRESSED_DXT1_RGBA(12), + + /** + * 8 bpp + */ + COMPRESSED_DXT5_RGBA(13), + + /** + * 4 bpp + */ + COMPRESSED_ETC1_RGB(14), + + /** + * 4 bpp + */ + COMPRESSED_ETC2_RGB(15), + + /** + * 8 bpp + */ + COMPRESSED_ETC2EAC_RGBA(16), + + /** + * 4 bpp + */ + COMPRESSED_PVRT_RGB(17), + + /** + * 4 bpp + */ + COMPRESSED_PVRT_RGBA(18), + + /** + * 8 bpp + */ + COMPRESSED_ASTC4X4_RGBA(19), + + /** + * 2 bpp + */ + COMPRESSED_ASTC8X8_RGBA(20), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/ShaderLocationIndex.kt b/src/commonMain/kotlin/kaylibkit/kEnums/ShaderLocationIndex.kt new file mode 100644 index 0000000..7af8acb --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/ShaderLocationIndex.kt @@ -0,0 +1,137 @@ +package kaylibkit.kEnums + +/** + * Shader location index + */ +enum class ShaderLocationIndex(val value: Int) { + + /** + * Shader location: vertex attribute: position + */ + VERTEX_POSITION_0(0), + + /** + * Shader location: vertex attribute: texcoord01 + */ + VERTEX_TEXCOORD01_1(1), + + /** + * Shader location: vertex attribute: texcoord02 + */ + VERTEX_TEXCOORD02_2(2), + + /** + * Shader location: vertex attribute: normal + */ + VERTEX_NORMAL(3), + + /** + * Shader location: vertex attribute: tangent + */ + VERTEX_TANGENT(4), + + /** + * Shader location: vertex attribute: tangent + */ + VERTEX_COLOR(5), + + /** + * Shader location: matrix uniform: model-view-projection + */ + MATRIX_MVP(6), + + /** + * Shader location: matrix uniform: view (camera transform) + */ + MATRIX_VIEW(7), + + /** + * Shader location: matrix uniform: projection + */ + MATRIX_PROJECTION(8), + + /** + * Shader location: matrix uniform: model (transform) + */ + MATRIX_MODEL(9), + + /** + * Shader location: matrix uniform: normal + */ + MATRIX_NORMAL(10), + + /** + * Shader location: vector uniform: view + */ + VECTOR_VIEW(11), + + /** + * Shader location: vector uniform: diffuse color + */ + COLOR_DIFFUSE(12), + + /** + * Shader location: vector uniform: specular color + */ + COLOR_SPECULAR(13), + + /** + * Shader location: vector uniform: ambient color + */ + COLOR_AMBIENT(14), + + /** + * Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + */ + MAP_ALBEDO(15), + + /** + * Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + */ + MAP_METALNESS(16), + + /** + * Shader location: sampler2d texture: normal + */ + MAP_NORMAL(17), + + /** + * Shader location: sampler2d texture: roughness + */ + MAP_ROUGHNESS(18), + + /** + * Shader location: sampler2d texture: occlusion + */ + MAP_OCCLUSION(19), + + /** + * Shader location: sampler2d texture: emission + */ + MAP_EMISSION(20), + + /** + * Shader location: sampler2d texture: height + */ + MAP_HEIGHT(21), + + /** + * Shader location: samplerCube texture: cubemap + */ + MAP_CUBEMAP(22), + + /** + * Shader location: samplerCube texture: irradiance + */ + MAP_IRRADIANCE(23), + + /** + * Shader location: samplerCube texture: prefilter + */ + MAP_PREFILTER(24), + + /** + * Shader location: sampler2d texture: brdf + */ + MAP_BRDF(25), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/ShaderUniformDataType.kt b/src/commonMain/kotlin/kaylibkit/kEnums/ShaderUniformDataType.kt new file mode 100644 index 0000000..4117f23 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/ShaderUniformDataType.kt @@ -0,0 +1,48 @@ +package kaylibkit.kEnums + +enum class ShaderUniformDataType(val value: Int) { + /** + * Shader uniform type: float + */ + FLOAT(0), + + /** + * Shader uniform type: vec2 (2 float) + */ + VEC2(1), + + /** + * Shader uniform type: vec3 (3 float) + */ + VEC3(2), + + /** + * Shader uniform type: vec4 (4 float) + */ + VEC4(3), + + /** + * Shader uniform type: int + */ + INT(4), + + /** + * Shader uniform type: ivec2 (2 int) + */ + IVEC2(5), + + /** + * Shader uniform type: ivec3 (3 int) + */ + IVEC3(6), + + /** + * Shader uniform type: ivec4 (4 int) + */ + IVEC4(7), + + /** + * Shader uniform type: sampler2d + */ + SAMPLER2D(8), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/TextureFilter.kt b/src/commonMain/kotlin/kaylibkit/kEnums/TextureFilter.kt new file mode 100644 index 0000000..ef0aa56 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/TextureFilter.kt @@ -0,0 +1,33 @@ +package kaylibkit.kEnums + +enum class TextureFilter(val value: Int) { + /** + * No filter, just pixel aproximation + */ + POINT(0), + + /** + * Linear filtering + */ + BILINEAR(1), + + /** + * Trilinear filtering (linear with mipmaps) + */ + TRILINEAR(2), + + /** + * Anisotropic filtering 4x + */ + ANISOTROPIC_4X(3), + + /** + * Anisotropic filtering 8x + */ + ANISOTROPIC_8X(4), + + /** + * Anisotropic filtering 16x + */ + ANISOTROPIC_16X(5), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/TextureWrap.kt b/src/commonMain/kotlin/kaylibkit/kEnums/TextureWrap.kt new file mode 100644 index 0000000..2511188 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/TextureWrap.kt @@ -0,0 +1,23 @@ +package kaylibkit.kEnums + +enum class TextureWrap(val value: Int) { + /** + * Repeats texture in tiled mode + */ + REPEAT(0), + + /** + * Clamps texture to edge pixel in tiled mode + */ + CLAMP(1), + + /** + * Mirrors and repeats the texture in tiled mode + */ + MIRROR_REPEAT(2), + + /** + * Mirrors and clamps to border the texture in tiled mode + */ + MIRROR_CLAMP(3), +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kEnums/TraceLogLevel.kt b/src/commonMain/kotlin/kaylibkit/kEnums/TraceLogLevel.kt new file mode 100644 index 0000000..305355f --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kEnums/TraceLogLevel.kt @@ -0,0 +1,43 @@ +package kaylibkit.kEnums + +enum class TraceLogLevel(val level: Int) { + /** + * Display all logs + */ + ALL(0), + + /** + * Trace logging, intended for internal use only + */ + TRACE(1), + + /** + * Debug logging, used for internal debugging, it should be disabled on release builds + */ + DEBUG(2), + + /** + * Info logging, used for program execution info + */ + INFO(3), + + /** + * Warning logging, used on recoverable failures + */ + WARNING(4), + + /** + * Error logging, used on unrecoverable failures + */ + ERROR(5), + + /** + * Fatal logging, used to abort program: exit(EXIT_FAILURE) + */ + FATAL(6), + + /** + * Disable logging + */ + NONE(7) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kGestures/KGestures.kt b/src/commonMain/kotlin/kaylibkit/kGestures/KGestures.kt new file mode 100644 index 0000000..1949c8a --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kGestures/KGestures.kt @@ -0,0 +1,77 @@ +package kaylibkit.kGestures + +import kaylibc.* +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.pointed + +// -- Module: kGestures + +//=======================================================// +// GESTURES AND TOUCH HANDLING FUNCTIONS +//=======================================================// + +/** + * Enable a set of gestures using [flags] + */ +inline fun setGesturesEnabled(flags: Gesture) { + SetGesturesEnabled(flags) +} + +/** + * Check if a gesture have been detected + * @return [Boolean] + */ +inline fun isGestureDetected(gesture: kaylibkit.kEnums.Gesture) : Boolean { + return IsGestureDetected(gesture.value) +} + +/** + * Get latest detected gesture + * @return [Int] + */ +inline fun getGestureDetected() : Int { + return GetGestureDetected() +} + +/** + * Get gesture hold time in milliseconds + * @return [Float] + */ +inline fun getGestureHoldDuration() : Float { + return GetGestureHoldDuration() +} + +/** + * Get gesture drag [Vector2] + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getGestureDragVector() : Vector2 { + return GetGestureDragVector().getPointer(MemScope()).pointed +} + +/** + * Get gesture drag angle + * @return [Float] + */ +inline fun getGestureDragAngle() : Float { + return GetGestureDragAngle() +} + +/** + * Get gesture pinch delta + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getGesturePinchVector() : Vector2 { + return GetGesturePinchVector().getPointer(MemScope()).pointed +} + +/** + * Get gesture pinch angle + * @return [Float] + */ +inline fun getGesturePinchAngle() : Float { + return GetGesturePinchAngle() +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kImage/KImage.kt b/src/commonMain/kotlin/kaylibkit/kImage/KImage.kt new file mode 100644 index 0000000..43b4f95 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kImage/KImage.kt @@ -0,0 +1,615 @@ +package kaylibkit.kImage + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kImage + +//=======================================================// +// IMAGE CONSTRUCTOR +//=======================================================// + +/** + * Constructor function for [Image]. + * Important to note that this uses MemScope() by default. + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kImage(data: COpaquePointer?, width: Int, height: Int, mipmaps: Int, format: Int, allocator: AutofreeScope = MemScope()) : Image { + return allocator.alloc { + this.data = data + this.width = width + this.height = height + this.mipmaps = mipmaps + this.format = format + } +} + +//=======================================================// +// IMAGE LOADING FUNCTIONS +//=======================================================// + +/** + * Load [Image] from file into CPU memory (RAM) + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImage(fileName: String) : Image { + return LoadImage(fileName).getPointer(MemScope()).pointed +} + +/** + * Load [Image] from RAW file data + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImageRaw(fileName: String, width: Int, height: Int, format: PixelFormat, headerSize: Int) : Image { + return LoadImageRaw(fileName, width, height, format.toInt(), headerSize).getPointer(MemScope()).pointed +} + +/** + * Load [Image] sequence from file (frames appended to image.data) + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImageAnim(fileName: String, frames: IntVar) : Image { + return LoadImageAnim(fileName, frames.ptr).getPointer(MemScope()).pointed +} + +/** + * Load [Image] from memory buffer, fileType refers to extension: i.e. `.png` + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImageFromMemory(fileType: String, fileData: UByteVar, dataSize: Int) : Image { + return LoadImageFromMemory(fileType, fileData.ptr, dataSize).getPointer(MemScope()).pointed +} + +/** + * Load [Image] from GPU texture data + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImageFromTexture(texture: Texture2D) : Image { + return LoadImageFromTexture(texture.readValue()).getPointer(MemScope()).pointed +} + +/** + * Load image from screen buffer and (screenshot) + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImageFromScreen() : Image { + return LoadImageFromScreen().getPointer(MemScope()).pointed +} + +/** + * Unload [Image] from CPU memory (RAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadImage(image: Image) { + UnloadImage(image.readValue()) +} + +/** + * Export [Image] data to file, returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun exportImage(image: Image, fileName: String) : Boolean { + return ExportImage(image.readValue(), fileName) +} + +/** + * Export [Image] as code file defining an array of bytes, returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun exportImageAsCode(image: Image, fileName: String) : Boolean { + return ExportImageAsCode(image.readValue(), fileName) +} + +//=======================================================// +// IMAGE GENERATION FUNCTIONS +//=======================================================// + +/** + * Generate [Image]: plain [color] + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageColor(width: Int, height: Int, color: Color) : Image { + return GenImageColor(width, height, color.readValue()).getPointer(MemScope()).pointed +} + +/** + * Generate [Image]: vertical gradient + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageGradientV(width: Int, height: Int, top: Color, bottom: Color) : Image { + return GenImageGradientV(width, height, top.readValue(), bottom.readValue()).getPointer(MemScope()).pointed +} + +/** + * Generate [Image]: horizontal gradient + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageGradientH(width: Int, height: Int, left: Color, right: Color) : Image { + return GenImageGradientH(width, height, left.readValue(), right.readValue()).getPointer(MemScope()).pointed +} + +/** + * Generate [Image]: radial gradient + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageGradientRadial(width: Int, height: Int, density: Float, inner: Color, outer: Color) : Image { + return GenImageGradientRadial(width, height, density, inner.readValue(), outer.readValue()).getPointer(MemScope()).pointed +} + +/** + * Generate [Image]: checked + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageChecked(width: Int, height: Int, checksX: Int, checksY: Int, col1: Color, col2: Color) : Image { + return GenImageChecked(width, height, checksX, checksY, col1.readValue(), col2.readValue()).getPointer(MemScope()).pointed +} + +/** + * Generate [Image]: white noise + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageWhiteNoise(width: Int, height: Int, factor: Float) : Image { + return GenImageWhiteNoise(width, height, factor).getPointer(MemScope()).pointed +} + +/** + * Generate [Image]: cellular algorithm, bigger tileSize means bigger cells + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageCellular(width: Int, height: Int, tileSize: Int) : Image { + return GenImageCellular(width, height, tileSize).getPointer(MemScope()).pointed +} + +//=======================================================// +// IMAGE MANIPULATION FUNCTIONS +//=======================================================// + +/** + * Create an [Image] duplicate (useful for transformations) + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageCopy(image: Image) : Image { + return ImageCopy(image.readValue()).getPointer(MemScope()).pointed +} + +/** + * Create an [Image] from another [Image] piece + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageFromImage(image: Image, rec: Rectangle) : Image { + return ImageFromImage(image.readValue(), rec.readValue()).getPointer(MemScope()).pointed +} + +/** + * Create an [Image] from text (default font) + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageText(text: String, fontSize: Int, color: Color) : Image { + return ImageText(text, fontSize, color.readValue()).getPointer(MemScope()).pointed +} + +/** + * Create an [Image] from text (custom sprite font) + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageTextEx(font: Font, text: String, fontSize: Float, spacing: Float, tint: Color) : Image { + return ImageTextEx(font.readValue(), text, fontSize, spacing, tint.readValue()).getPointer(MemScope()).pointed +} + +/** + * Convert [Image] data to desired format + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageFormat(image: Image, newFormat: kaylibkit.kEnums.PixelFormat) { + ImageFormat(image.ptr, newFormat.value) +} + +/** + * Convert [Image] to POT (power-of-two) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageToPOT(image: Image, fill: Color) { + ImageToPOT(image.ptr, fill.readValue()) +} + +/** + * Crop an [Image] to a defined [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageCrop(image: Image, crop: Rectangle) { + ImageCrop(image.ptr, crop.readValue()) +} + +/** + * Crop [Image] depending on alpha value + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageAlphaCrop(image: Image, threshold: Float) { + ImageAlphaCrop(image.ptr, threshold) +} + +/** + * Clear alpha channel to desired [color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageAlphaClear(image: Image, color: Color, threshold: Float) { + ImageAlphaClear(image.ptr, color.readValue(), threshold) +} + +/** + * Apply alpha mask to [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageAlphaMask(image: Image, alphaMask: Image) { + ImageAlphaMask(image.ptr, alphaMask.readValue()) +} + +/** + * Premultiply alpha channel + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageAlphaPremultiply(image: Image) { + ImageAlphaPremultiply(image.ptr) +} + +/** + * Apply Gaussian blur using a box blur approximation + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageBlurGaussian(image: Image, blurSize: Int) { + ImageBlurGaussian(image.ptr, blurSize) +} + +/** + * Resize [Image] (Bicubic scaling algorithm) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageResize(image: Image, newWidth: Int, newHeight: Int) { + ImageResize(image.ptr, newWidth, newHeight) +} + +/** + * Resize [Image] (Nearest-Neighbor scaling algorithm) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageResizeNN(image: Image, newWidth: Int, newHeight: Int) { + ImageResizeNN(image.ptr, newWidth, newHeight) +} + +/** + * Resize canvas and fill with [color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageResizeCanvas(image: Image, newWidth: Int, newHeight: Int, offsetX: Int, offsetY: Int, fill: Color) { + ImageResizeCanvas(image.ptr, newWidth, newHeight, offsetX, offsetY, fill.readValue()) +} + +/** + * Compute all mipmap levels for a provided [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageMipmaps(image: Image) { + ImageMipmaps(image.ptr) +} + +/** + * Dither [Image] data to 16bpp or lower (Floyd-Steinberg dithering) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDither(image: Image, rBpp: Int, gBpp: Int, bBpp: Int, aBpp: Int) { + ImageDither(image.ptr, rBpp, gBpp, bBpp, aBpp) +} + +/** + * Flip [Image] vertically + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageFlipVertical(image: Image) { + ImageFlipVertical(image.ptr) +} + +/** + * Flip [Image] horizontally + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageFlipHorizontal(image: Image) { + ImageFlipHorizontal(image.ptr) +} + +/** + * Rotate [Image] clockwise 90deg + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageRotateCW(image: Image) { + ImageRotateCW(image.ptr) +} + +/** + * Rotate [Image] counter-clockwise 90deg + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageRotateCCW(image: Image) { + ImageRotateCCW(image.ptr) +} + +/** + * Modify [Image] [color]: tint + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageColorTint(image: Image, color: Color) { + ImageColorTint(image.ptr, color.readValue()) +} + +/** + * Modify [Image] [color]: invert + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageColorInvert(image: Image) { + ImageColorInvert(image.ptr) +} + +/** + * Modify [Image] [color]: grayscale + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageColorGrayscale(image: Image) { + ImageColorGrayscale(image.ptr) +} + +/** + * Modify [Image] [color]: contrast (-100 to 100) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageColorContrast(image: Image, contrast: Float) { + ImageColorContrast(image.ptr, contrast) +} + +/** + * Modify [Image] [color]: brightness (-255 to 255) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageColorBrightness(image: Image, brightness: Int) { + ImageColorBrightness(image.ptr, brightness) +} + +/** + * Modify [Image] [color]: replace [color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageColorReplace(image: Image, color: Color, replace: Color) { + ImageColorReplace(image.ptr, color.readValue(), replace.readValue()) +} + +/** + * Load color data from [Image] as a [Color] [Array] (RGBA - 32bit) Memory allocated should be freed + * @return [Array] of [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImageColors(image: Image): Array { + val colorPointer = LoadImageColors(image.readValue()) + val colorList = mutableListOf() + colorPointer?.pointed?.let { colorList.add(it) } + return colorList.toTypedArray() +} + +/** + * Load colors palette from [Image] as a [Color] [Array] (RGBA - 32bit) + * @return [Array] of [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadImagePalette(image: Image, maxPaletteSize: Int, colorCount: IntVar) : Array { + val result = LoadImagePalette(image.readValue(), maxPaletteSize, colorCount.ptr) + val colorList = mutableListOf() + result?.pointed?.let { colorList.add(it) } + return colorList.toTypedArray() +} + +/** + * Unload [Color] data loaded with LoadImageColors() Read Deprecated note + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadImageColors(colors: Color) { + UnloadImageColors(colors.ptr) +} + + +/** + * Unload [colors] palette loaded with LoadImagePalette() Read Deprecated note + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadImagePalette(colors: Color) { + UnloadImagePalette(colors.ptr) +} + +/** + * Get [Image] alpha border [Rectangle] + * @return [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getImageAlphaBorder(image: Image, threshold: Float) : Rectangle { + return GetImageAlphaBorder(image.readValue(), threshold).getPointer(MemScope()).pointed +} + +/** + * Get [Image] pixel [Color] at (x, y) position + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getImageColor(image: Image, x: Int, y: Int) : Color { + return GetImageColor(image.readValue(), x, y).getPointer(MemScope()).pointed +} + +//=======================================================// +// IMAGE DRAWING FUNCTIONS +//=======================================================// + +/** + * Clear [Image] background with given [color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageClearBackground(dst: Image, color: Color) { + ImageClearBackground(dst.ptr, color.readValue()) +} + +/** + * Draw pixel within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawPixel(dst: Image, posX: Int, posY: Int, color: Color) { + ImageDrawPixel(dst.ptr, posX, posY, color.readValue()) +} + +/** + * Draw pixel within an [Image] ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawPixelV(dst: Image, position: Vector2, color: Color) { + ImageDrawPixelV(dst.ptr, position.readValue(), color.readValue()) +} + +/** + * Draw line within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawLine(dst: Image, startPosX: Int, startPosY: Int, endPosX: Int, endPosY: Int, color: Color) { + ImageDrawLine(dst.ptr, startPosX, startPosY, endPosX, endPosY, color.readValue()) +} + +/** + * Draw line within an [Image] ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawLineV(dst: Image, start: Vector2, end: Vector2, color: Color) { + ImageDrawLineV(dst.ptr, start.readValue(), end.readValue(), color.readValue()) +} + +/** + * Draw circle within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawCircle(dst: Image, centerX: Int, centerY: Int, radius: Int, color: Color) { + ImageDrawCircle(dst.ptr, centerX, centerY, radius, color.readValue()) +} + +/** + * Draw circle within an [Image] ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawCircleV(dst: Image, center: Vector2, radius: Int, color: Color) { + ImageDrawCircleV(dst.ptr, center.readValue(), radius, color.readValue()) +} + +/** + * Draw [Rectangle] within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawRectangle(dst: Image, posX: Int, posY: Int, width: Int, height: Int, color: Color) { + ImageDrawRectangle(dst.ptr, posX, posY, width, height, color.readValue()) +} + +/** + * Draw [Rectangle] within an [Image] ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawRectangleV(dst: Image, position: Vector2, size: Vector2, color: Color) { + ImageDrawRectangleV(dst.ptr, position.readValue(), size.readValue(), color.readValue()) +} + +/** + * Draw [Rectangle] within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawRectangleRec(dst: Image, rec: Rectangle, color: Color) { + ImageDrawRectangleRec(dst.ptr, rec.readValue(), color.readValue()) +} + +/** + * Draw [Rectangle] lines within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawRectangleLines(dst: Image, rec: Rectangle, thick: Int, color: Color) { + ImageDrawRectangleLines(dst.ptr, rec.readValue(), thick, color.readValue()) +} + +/** + * Draw a source [Image] within a destination [Image] (tint applied to source) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDraw(dst: Image, src: Image, srcRec: Rectangle, dstRec: Rectangle, tint: Color) { + ImageDraw(dst.ptr, src.readValue(), srcRec.readValue(), dstRec.readValue(), tint.readValue()) +} + +/** + * Draw text (using default font) within an [Image] (destination) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawText(dst: Image, text: String, posX: Int, posY: Int, fontSize: Int, color: Color) { + return ImageDrawText(dst.ptr, text, posX, posY, fontSize, color.readValue()) +} + +/** + * Draw text (custom sprite font) within an [Image] (destination) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawText(dst: Image, font: Font, text: String, position: Vector2, fontSize: Float, spacing: Float, tint: Color) { + return ImageDrawTextEx(dst.ptr, font.readValue(), text, position.readValue(), fontSize, spacing, tint.readValue()) +} + +/** + * Draw circle outline within an [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawCircleLines(image: Image, centerX: Int, centerY: Int, radius: Int, color: Color) { + ImageDrawCircleLines(image.ptr, centerX, centerY, radius, color.readValue()) +} + +/** + * Draw circle outline within an [Image] using [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun imageDrawCircleLines(image: Image, center: Vector2, radius: Int, color: Color) { + ImageDrawCircleLines(image.ptr, center.x.toInt(), center.y.toInt(), radius, color.readValue()) +} + +/** + * Check if an [Image] is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isImageReady(image: Image) : Boolean { + return IsImageReady(image.readValue()) +} + +/** + * Set value of an [Image] with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Image.set(other: Image) { + this.format = other.format + this.height = other.height + this.data = other.data + this.mipmaps = other.mipmaps + this.width = other.width +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kMath/KMath.kt b/src/commonMain/kotlin/kaylibkit/kMath/KMath.kt new file mode 100644 index 0000000..081baa0 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kMath/KMath.kt @@ -0,0 +1,267 @@ +package kaylibkit.kMath + +import kaylibc.* +import kotlinx.cinterop.ExperimentalForeignApi +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +// -- Module: kMath + +//=======================================================// +// MATH FUNCTIONS +//=======================================================// + +/** + * Clamp float value + * @return [Float] + */ +inline fun clamp(value: Float, min: Float, max: Float) : Float = Clamp(value, min, max) + +/** + * Calculate linear interpolation between two floats + * @return [Float] + */ +inline fun lerp(value: Float, start: Float, end: Float) : Float = Lerp(value, start, end) + +/** + * Normalize input value within input range + * @return [Float] + */ +inline fun normalize(value: Float, start: Float, end: Float) : Float = Normalize(value, start, end) + +/** + * Remap input value within input range to output range + * @return [Float] + */ +inline fun remap(value: Float, inputStart: Float, inputEnd: Float, outputStart: Float, outputEnd: Float): Float { + + return (value - inputStart) / (inputEnd - inputStart) * (outputEnd - outputStart) + outputStart +} + +/** + * Calculate [Quaternion] based on the rotation from one vector to another + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun quaternionFromVector3toVector3(from: Vector3, to: Vector3): Quaternion { +// val result: Quaternion = kQuaternion() +// +// val cos2Theta = from.dot(to) +// +// val cross: Vector3 = from.crossProduct(to) +// +// +// result.x = cross.x +// result.y = cross.y +// result.z = cross.z +// result.w = 1.0f + cos2Theta +// +// var length: Quaternion = result.normalize() +// +// return result + + val result: Quaternion = kQuaternion() + + val cos2Theta = from.x * to.x + from.y * to.y + from.z * to.z // Vector3DotProduct(from, to) + + val cross: Vector3 = kVector3( + from.y * to.z - from.z * to.y, + from.z * to.x - from.x * to.z, + from.x * to.y - from.y * to.x + ) // Vector3CrossProduct(from, to) + + result.x = cross.x + result.y = cross.y + result.z = cross.z + result.w = 1.0f + cos2Theta + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + + var length: Float = sqrt(result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w) + if (length == 0.0f) length = 1.0f + val ilength = 1.0f / length + + result.x = result.x * ilength + result.y = result.y * ilength + result.z = result.z * ilength + result.w = result.w * ilength + + return result +} + +/** + * Get a [Quaternion] for a given rotation matrix + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun quaternionFromMatrix(mat: Matrix): Quaternion { + val result: Quaternion = kQuaternion() + + val fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10 + val fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10 + val fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10 + val fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5 + + var biggestIndex = 0 + var fourBiggestSquaredMinus1 = fourWSquaredMinus1 + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourXSquaredMinus1 + biggestIndex = 1 + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourYSquaredMinus1 + biggestIndex = 2 + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourZSquaredMinus1 + biggestIndex = 3 + } + + val biggestVal: Float = sqrt(fourBiggestSquaredMinus1 + 1.0f) * 0.5f + val mult = 0.25f / biggestVal + + when (biggestIndex) { + 0 -> { + result.w = biggestVal + result.x = (mat.m6 - mat.m9) * mult + result.y = (mat.m8 - mat.m2) * mult + result.z = (mat.m1 - mat.m4) * mult + } + + 1 -> { + result.x = biggestVal + result.w = (mat.m6 - mat.m9) * mult + result.y = (mat.m1 + mat.m4) * mult + result.z = (mat.m8 + mat.m2) * mult + } + + 2 -> { + result.y = biggestVal + result.w = (mat.m8 - mat.m2) * mult + result.x = (mat.m1 + mat.m4) * mult + result.z = (mat.m6 + mat.m9) * mult + } + + 3 -> { + result.z = biggestVal + result.w = (mat.m1 - mat.m4) * mult + result.x = (mat.m8 + mat.m2) * mult + result.y = (mat.m6 + mat.m9) * mult + } + } + + return result +} + +/** + * Get a [Matrix] for a given [Quaternion] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun quaternionToMatrix(q: Quaternion): Matrix { + val result: Matrix = kMatrix( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ) // MatrixIdentity() + + + val a2: Float = q.x * q.x + val b2: Float = q.y * q.y + val c2: Float = q.z * q.z + val ac: Float = q.x * q.z + val ab: Float = q.x * q.y + val bc: Float = q.y * q.z + val ad: Float = q.w * q.x + val bd: Float = q.w * q.y + val cd: Float = q.w * q.z + + result.m0 = 1 - 2 * (b2 + c2) + result.m1 = 2 * (ab + cd) + result.m2 = 2 * (ac - bd) + + result.m4 = 2 * (ab - cd) + result.m5 = 1 - 2 * (a2 + c2) + result.m6 = 2 * (bc + ad) + + result.m8 = 2 * (ac + bd) + result.m9 = 2 * (bc - ad) + result.m10 = 1 - 2 * (a2 + b2) + + return result +} + +/** + * Get rotation [Quaternion] for an [angle] and [axis] + * + * NOTE: [angle] must be provided in radians + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun quaternionFromAxisAngle(axis: Vector3, angle: Float): Quaternion { + var ang = angle + val result: Quaternion = kQuaternion(0.0f, 0.0f, 0.0f, 1.0f) + + val axisLength: Float = sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z) + + if (axisLength != 0.0f) { + ang *= 0.5f + var length: Float + var ilength: Float + + // Vector3Normalize(axis) + length = sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z) + if (length == 0.0f) length = 1.0f + ilength = 1.0f / length + axis.x *= ilength + axis.y *= ilength + axis.z *= ilength + val sinres: Float = sin(angle) + val cosres: Float = cos(angle) + result.x = axis.x * sinres + result.y = axis.y * sinres + result.z = axis.z * sinres + result.w = cosres + + // QuaternionNormalize(q); + length = sqrt(result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w) + if (length == 0.0f) length = 1.0f + ilength = 1.0f / length + result.x = result.x * ilength + result.y = result.y * ilength + result.z = result.z * ilength + result.w = result.w * ilength + } + + return result +} + +/** + * Get the [Quaternion] equivalent to Euler angles + * + * NOTE: Rotation order is ZYX + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun quaternionFromEuler(pitch: Float, yaw: Float, roll: Float): Quaternion { + val result: Quaternion = kQuaternion() + + val x0: Float = cos(pitch * 0.5f) + val x1: Float = sin(pitch * 0.5f) + val y0: Float = cos(yaw * 0.5f) + val y1: Float = sin(yaw * 0.5f) + val z0: Float = cos(roll * 0.5f) + val z1: Float = sin(roll * 0.5f) + + result.x = x1 * y0 * z0 - x0 * y1 * z1 + result.y = x0 * y1 * z0 + x1 * y0 * z1 + result.z = x0 * y0 * z1 - x1 * y1 * z0 + result.w = x0 * y0 * z0 + x1 * y1 * z1 + + return result +} diff --git a/src/commonMain/kotlin/kaylibkit/kMath/KMatrix.kt b/src/commonMain/kotlin/kaylibkit/kMath/KMatrix.kt new file mode 100644 index 0000000..44d39be --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kMath/KMatrix.kt @@ -0,0 +1,711 @@ +package kaylibkit.kMath + +import kaylibc.* +import kotlinx.cinterop.* +import platform.posix.tan +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +// -- Module: kMath + +//=======================================================// +// Matrix DATA TYPE +//=======================================================// + +/** + * Constructor function for [Matrix] + * @param [allocator] Uses `MemScope()` by default. + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kMatrix( + m0: Float = 0F, m4: Float = 0F, m8: Float = 0F, m12: Float = 0F, m1: Float = 0F, m5: Float = 0F, m9: Float = 0F, m13: Float = 0F, m2: Float = 0F, m6: Float = 0F, m10: Float = 0F, m14: Float = 0F, m3: Float = 0F, m7: Float = 0F, m11: Float = 0F, m15: Float = 0F, allocator: AutofreeScope = MemScope()): Matrix { + return allocator.alloc { + this.m0 = m0 + this.m4 = m4 + this.m8 = m8 + this.m12 = m12 + this.m1 = m1 + this.m5 = m5 + this.m9 = m9 + this.m13 = m13 + this.m2 = m2 + this.m6 = m6 + this.m10 = m10 + this.m14 = m14 + this.m3 = m3 + this.m7 = m7 + this.m11 = m11 + this.m15 = m15 + } +} + +/** + * Compute [Matrix] determinant + * @return [Float] + */ +inline fun Matrix.determinant(): Float { + var result: Float + + // Cache the matrix values (speed optimization) + val a00: Float = this.m0 + val a01: Float = this.m1 + val a02: Float = this.m2 + val a03: Float = this.m3 + val a10: Float = this.m4 + val a11: Float = this.m5 + val a12: Float = this.m6 + val a13: Float = this.m7 + val a20: Float = this.m8 + val a21: Float = this.m9 + val a22: Float = this.m10 + val a23: Float = this.m11 + val a30: Float = this.m12 + val a31: Float = this.m13 + val a32: Float = this.m14 + val a33: Float = this.m15 + + result = a30 * a21 * a12 * a03 - a20 * + a31 * a12 * a03 - a30 * a11 * + a22 * a03 + a10 * a31 * a22 * + a03 + a20 * a11 * a32 * + a03 - a10 * a21 * a32 * + a03 - a30 * a21 * a02 * + a13 + a20 * a31 * a02 * + a13 + a30 * a01 * a22 * + a13 - a00 * a31 * a22 * + a13 - a20 * a01 * a32 * + a13 + a00 * a21 * a32 * + a13 + a30 * a11 * a02 * + a23 - a10 * a31 * a02 * + a23 - a30 * a01 * a12 * + a23 + a00 * a31 * a12 * + a23 + a10 * a01 * a32 * + a23 - a00 * a11 * a32 * + a23 - a20 * a11 * a02 * + a33 + a10 * a21 * a02 * + a33 + a20 * a01 * a12 * + a33 - a00 * a21 * a12 * + a33 - a10 * a01 * a22 * + a33 + a00 * a11 * a22 * a33 + + return result +} + +/** + * Get the trace of the [Matrix] (sum of the values along the diagonal) + * @return [Float] + */ +inline fun Matrix.trace(): Float { + + return this.m0 + this.m5 + this.m10 + this.m15 +} + +/** + * Get the trace of the [Matrix] (sum of the values along the diagonal) + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.transpose(): Matrix { + val result: Matrix = kMatrix() + + result.m0 = this.m0 + result.m1 = this.m4 + result.m2 = this.m8 + result.m3 = this.m12 + result.m4 = this.m1 + result.m5 = this.m5 + result.m6 = this.m9 + result.m7 = this.m13 + result.m8 = this.m2 + result.m9 = this.m6 + result.m10 = this.m10 + result.m11 = this.m14 + result.m12 = this.m3 + result.m13 = this.m7 + result.m14 = this.m11 + result.m15 = this.m15 + + return result +} + +/** + * Invert provided [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.invert(): Matrix { + val result: Matrix = kMatrix() + + // Cache the matrix values (speed optimization) + val a00: Float = this.m0 + val a01: Float = this.m1 + val a02: Float = this.m2 + val a03: Float = this.m3 + val a10: Float = this.m4 + val a11: Float = this.m5 + val a12: Float = this.m6 + val a13: Float = this.m7 + val a20: Float = this.m8 + val a21: Float = this.m9 + val a22: Float = this.m10 + val a23: Float = this.m11 + val a30: Float = this.m12 + val a31: Float = this.m13 + val a32: Float = this.m14 + val a33: Float = this.m15 + + val b00 = a00 * a11 - a01 * a10 + val b01 = a00 * a12 - a02 * a10 + val b02 = a00 * a13 - a03 * a10 + val b03 = a01 * a12 - a02 * a11 + val b04 = a01 * a13 - a03 * a11 + val b05 = a02 * a13 - a03 * a12 + val b06 = a20 * a31 - a21 * a30 + val b07 = a20 * a32 - a22 * a30 + val b08 = a20 * a33 - a23 * a30 + val b09 = a21 * a32 - a22 * a31 + val b10 = a21 * a33 - a23 * a31 + val b11 = a22 * a33 - a23 * a32 + + // Calculate the invert determinant (inlined to avoid double-caching) + + // Calculate the invert determinant (inlined to avoid double-caching) + val invDet = 1.0f / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06) + + result.m0 = (a11 * b11 - a12 * b10 + a13 * b09) * invDet + result.m1 = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet + result.m2 = (a31 * b05 - a32 * b04 + a33 * b03) * invDet + result.m3 = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet + result.m4 = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet + result.m5 = (a00 * b11 - a02 * b08 + a03 * b07) * invDet + result.m6 = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet + result.m7 = (a20 * b05 - a22 * b02 + a23 * b01) * invDet + result.m8 = (a10 * b10 - a11 * b08 + a13 * b06) * invDet + result.m9 = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet + result.m10 = (a30 * b04 - a31 * b02 + a33 * b00) * invDet + result.m11 = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet + result.m12 = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet + result.m13 = (a00 * b09 - a01 * b07 + a02 * b06) * invDet + result.m14 = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet + result.m15 = (a20 * b03 - a21 * b01 + a22 * b00) * invDet + + return result +} + +/** + * Get identity [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.identity(): Matrix { + return kMatrix( + 1.0F, 0.0F, 0.0F, 0.0F, + 0.0F, 1.0F, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, + 0.0F, 0.0F, 0.0F, 1.0F + ) +} + +/** + * Add two matrices + */ +inline operator fun Matrix.plus(m: Matrix) { + this.m0 + m.m0 + this.m1 + m.m1 + this.m2 + m.m2 + this.m3 + m.m3 + this.m4 + m.m4 + this.m5 + m.m5 + this.m6 + m.m6 + this.m7 + m.m7 + this.m8 + m.m8 + this.m9 + m.m9 + this.m10 + m.m10 + this.m11 + m.m11 + this.m12 + m.m12 + this.m13 + m.m13 + this.m14 + m.m14 + this.m15 + m.m15 +} + +/** + * Subtract two matrices + */ +inline operator fun Matrix.minus(m: Matrix) { + this.m0 - m.m0 + this.m1 - m.m1 + this.m2 - m.m2 + this.m3 - m.m3 + this.m4 - m.m4 + this.m5 - m.m5 + this.m6 - m.m6 + this.m7 - m.m7 + this.m8 - m.m8 + this.m9 - m.m9 + this.m10 - m.m10 + this.m11 - m.m11 + this.m12 - m.m12 + this.m13 - m.m13 + this.m14 - m.m14 + this.m15 - m.m15 +} + +/** + * Multiply two matrices + */ +inline operator fun Matrix.times(m: Matrix) { + this.m0 * m.m0 + this.m1 * m.m1 + this.m2 * m.m2 + this.m3 * m.m3 + this.m4 * m.m4 + this.m5 * m.m5 + this.m6 * m.m6 + this.m7 * m.m7 + this.m8 * m.m8 + this.m9 * m.m9 + this.m10 * m.m10 + this.m11 * m.m11 + this.m12 * m.m12 + this.m13 * m.m13 + this.m14 * m.m14 + this.m15 * m.m15 +} + +/** + * Get translation [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.translate(x: Float, y: Float, z: Float): Matrix { + + return kMatrix( + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f + ) +} + +/** + * Create rotation [Matrix] from [axis] and [angle] + * NOTE: [angle] should be provided in radians + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.rotate(axis: Vector3, angle: Float): Matrix { + val result: Matrix = kMatrix() + + var x = axis.x + var y = axis.y + var z = axis.z + + val lengthSquared = x * x + y * y + z * z + + if (lengthSquared != 1.0f && lengthSquared != 0.0f) { + val ilength: Float = 1.0f / sqrt(lengthSquared) + x *= ilength + y *= ilength + z *= ilength + } + + val sinres: Float = sin(angle) + val cosres: Float = cos(angle) + val t = 1.0f - cosres + + result.m0 = x * x * t + cosres + result.m1 = y * x * t + z * sinres + result.m2 = z * x * t - y * sinres + result.m3 = 0.0f + + result.m4 = x * y * t - z * sinres + result.m5 = y * y * t + cosres + result.m6 = z * y * t + x * sinres + result.m7 = 0.0f + + result.m8 = x * z * t + y * sinres + result.m9 = y * z * t - x * sinres + result.m10 = z * z * t + cosres + result.m11 = 0.0f + + result.m12 = 0.0f + result.m13 = 0.0f + result.m14 = 0.0f + result.m15 = 1.0f + + return result +} + +/** + * Get x-rotation [Matrix] + * NOTE: [angle] must be provided in radians + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.rotateX(angle: Float): Matrix { + val result: Matrix = kMatrix( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ) // MatrixIdentity() + + + val cosres: Float = cos(angle) + val sinres: Float = sin(angle) + + result.m5 = cosres + result.m6 = sinres + result.m9 = -sinres + result.m10 = cosres + + return result +} + +/** + * Get y-rotation [Matrix] + * NOTE: [angle] must be provided in radians + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.rotateY(angle: Float): Matrix { + val result: Matrix = kMatrix( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ) // MatrixIdentity() + + + val cosres: Float = cos(angle) + val sinres: Float = sin(angle) + + result.m0 = cosres + result.m2 = -sinres + result.m8 = sinres + result.m10 = cosres + + return result +} + +/** + * Get z-rotation [Matrix] + * NOTE: [angle] must be provided in radians + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.rotateZ(angle: Float): Matrix { + val result: Matrix = kMatrix( + 1.0F, 0.0F, 0.0F, 0.0F, + 0.0F, 1.0F, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, + 0.0F, 0.0F, 0.0F, 1.0F + ) // MatrixIdentity() + + + val cosres: Float = cos(angle) + val sinres: Float = sin(angle) + + result.m0 = cosres + result.m1 = sinres + result.m4 = -sinres + result.m5 = cosres + + return result +} + +/** + * Get XYZ-rotation [Matrix] + * NOTE: [angle] must be provided in radians + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.rotateXYZ(angle: Vector3): Matrix { + val result: Matrix = kMatrix( + 1.0F, 0.0F, 0.0F, 0.0F, + 0.0F, 1.0F, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, + 0.0F, 0.0F, 0.0F, 1.0F + ) // MatrixIdentity() + + + val cosz: Float = cos(-angle.z) + val sinz: Float = sin(-angle.z) + val cosy: Float = cos(-angle.y) + val siny: Float = sin(-angle.y) + val cosx: Float = cos(-angle.x) + val sinx: Float = sin(-angle.x) + + result.m0 = cosz * cosy + result.m1 = cosz * siny * sinx - sinz * cosx + result.m2 = cosz * siny * cosx + sinz * sinx + + result.m4 = sinz * cosy + result.m5 = sinz * siny * sinx + cosz * cosx + result.m6 = sinz * siny * cosx - cosz * sinx + + result.m8 = -siny + result.m9 = cosy * sinx + result.m10 = cosy * cosx + + return result +} + +/** + * Get ZYX-rotation [Matrix] + * NOTE: [angle] must be provided in radians + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.rotateZYX(angle: Vector3): Matrix { + val result: Matrix = kMatrix() + + val cz: Float = cos(angle.z) + val sz: Float = sin(angle.z) + val cy: Float = cos(angle.y) + val sy: Float = sin(angle.y) + val cx: Float = cos(angle.x) + val sx: Float = sin(angle.x) + + result.m0 = cz * cy + result.m4 = cz * sy * sx - cx * sz + result.m8 = sz * sx + cz * cx * sy + result.m12 = 0F + + result.m1 = cy * sz + result.m5 = cz * cx + sz * sy * sx + result.m9 = cx * sz * sy - cz * sx + result.m13 = 0F + + result.m2 = -sy + result.m6 = cy * sx + result.m10 = cy * cx + result.m14 = 0F + + result.m3 = 0F + result.m7 = 0F + result.m11 = 0F + result.m15 = 1F + + return result +} + +/** + * Get scaling [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.scale(x: Float, y: Float, z: Float): Matrix { + + return kMatrix( + x, 0.0F, 0.0F, 0.0F, + 0.0F, y, 0.0F, 0.0F, + 0.0F, 0.0F, z, 0.0F, + 0.0F, 0.0F, 0.0F, 1.0F + ) +} + +/** + * Get perspective projection [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.frustum(left: Double, right: Double,bottom: Double, top: Double, near: Double, far: Double): Matrix { + val result: Matrix = kMatrix() + + val rl = (right - left).toFloat() + val tb = (top - bottom).toFloat() + val fn = (far - near).toFloat() + + result.m0 = near.toFloat() * 2.0f / rl + result.m1 = 0.0f + result.m2 = 0.0f + result.m3 = 0.0f + + result.m4 = 0.0f + result.m5 = near.toFloat() * 2.0f / tb + result.m6 = 0.0f + result.m7 = 0.0f + + result.m8 = (right.toFloat() + left.toFloat()) / rl + result.m9 = (top.toFloat() + bottom.toFloat()) / tb + result.m10 = -(far.toFloat() + near.toFloat()) / fn + result.m11 = -1.0f + + result.m12 = 0.0f + result.m13 = 0.0f + result.m14 = -(far.toFloat() * near.toFloat() * 2.0f) / fn + result.m15 = 0.0f + + return result +} + +/** + * Get perspective projection [Matrix] + * NOTE: [fovy] angle must be provided in radians + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.perspective(fovy: Double, aspect: Double, near: Double, far: Double): Matrix { + val result: Matrix = kMatrix() + + val top: Double = near * tan(fovy * 0.5) + val bottom = -top + val right = top * aspect + val left = -right + + // MatrixFrustum(-right, right, -top, top, near, far); + + // MatrixFrustum(-right, right, -top, top, near, far); + val rl = (right - left).toFloat() + val tb = (top - bottom).toFloat() + val fn = (far - near).toFloat() + + result.m0 = near.toFloat() * 2.0f / rl + result.m5 = near.toFloat() * 2.0f / tb + result.m8 = (right.toFloat() + left.toFloat()) / rl + result.m9 = (top.toFloat() + bottom.toFloat()) / tb + result.m10 = -(far.toFloat() + near.toFloat()) / fn + result.m11 = -1.0f + result.m14 = -(far.toFloat() * near.toFloat() * 2.0f) / fn + + return result +} + +/** + * Get orthographic projection [Matrix] + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.ortho(left: Double, right: Double,bottom: Double, top: Double, near: Double, far: Double): Matrix { + val result: Matrix = kMatrix() + + val rl = (right - left).toFloat() + val tb = (top - bottom).toFloat() + val fn = (far - near).toFloat() + + result.m0 = 2.0f / rl + result.m1 = 0.0f + result.m2 = 0.0f + result.m3 = 0.0f + result.m4 = 0.0f + result.m5 = 2.0f / tb + result.m6 = 0.0f + result.m7 = 0.0f + result.m8 = 0.0f + result.m9 = 0.0f + result.m10 = -2.0f / fn + result.m11 = 0.0f + result.m12 = -(left.toFloat() + right.toFloat()) / rl + result.m13 = -(top.toFloat() + bottom.toFloat()) / tb + result.m14 = -(far.toFloat() + near.toFloat()) / fn + result.m15 = 1.0f + + return result +} + +/** + * Get camera look-at [Matrix] (view matrix) + * @return [Matrix] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.lookAt(eye: Vector3, target: Vector3, up: Vector3): Matrix { + + val result: Matrix = kMatrix() + + var length: Float + var ilength: Float + + // Vector3Subtract(eye, target) + + // Vector3Subtract(eye, target) + val vz: Vector3 = kVector3(eye.x - target.x, eye.y - target.y, eye.z - target.z) + + // Vector3Normalize(vz) + + // Vector3Normalize(vz) + var v = vz + length = sqrt(v.x * v.x + v.y * v.y + v.z * v.z) + if (length == 0.0f) length = 1.0f + ilength = 1.0f / length + vz.x *= ilength + vz.y *= ilength + vz.z *= ilength + + // Vector3CrossProduct(up, vz) + + // Vector3CrossProduct(up, vz) + val vx: Vector3 = kVector3(up.y * vz.z - up.z * vz.y, up.z * vz.x - up.x * vz.z, up.x * vz.y - up.y * vz.x) + + // Vector3Normalize(x) + + // Vector3Normalize(x) + v = vx + length = sqrt(v.x * v.x + v.y * v.y + v.z * v.z) + if (length == 0.0f) length = 1.0f + ilength = 1.0f / length + vx.x *= ilength + vx.y *= ilength + vx.z *= ilength + + // Vector3CrossProduct(vz, vx) + + // Vector3CrossProduct(vz, vx) + val vy: Vector3 = kVector3(vz.y * vx.z - vz.z * vx.y, vz.z * vx.x - vz.x * vx.z, vz.x * vx.y - vz.y * vx.x) + + result.m0 = vx.x + result.m1 = vy.x + result.m2 = vz.x + result.m3 = 0.0f + result.m4 = vx.y + result.m5 = vy.y + result.m6 = vz.y + result.m7 = 0.0f + result.m8 = vx.z + result.m9 = vy.z + result.m10 = vz.z + result.m11 = 0.0f + result.m12 = -(vx.x * eye.x + vx.y * eye.y + vx.z * eye.z) // Vector3DotProduct(vx, eye) + + result.m13 = -(vy.x * eye.x + vy.y * eye.y + vy.z * eye.z) // Vector3DotProduct(vy, eye) + + result.m14 = -(vz.x * eye.x + vz.y * eye.y + vz.z * eye.z) // Vector3DotProduct(vz, eye) + + result.m15 = 1.0f + + return result +} + +/** + * Get float array of [Matrix] data + * @return [float16] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Matrix.toFloatV(): float16 { + return MatrixToFloatV(this.readValue()).getPointer(MemScope()).pointed +} + +/** + * Set value of a Matrix with another provided value. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Matrix.set(other: Matrix) { + this.m0 - other.m0 + this.m1 - other.m1 + this.m2 - other.m2 + this.m3 - other.m3 + this.m4 - other.m4 + this.m5 - other.m5 + this.m6 - other.m6 + this.m7 - other.m7 + this.m8 - other.m8 + this.m9 - other.m9 + this.m10 - other.m10 + this.m11 - other.m11 + this.m12 - other.m12 + this.m13 - other.m13 + this.m14 - other.m14 + this.m15 - other.m15 +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kMath/KQuaternion.kt b/src/commonMain/kotlin/kaylibkit/kMath/KQuaternion.kt new file mode 100644 index 0000000..18c7314 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kMath/KQuaternion.kt @@ -0,0 +1,157 @@ +package kaylibkit.kMath + +import kaylibc.* +import kotlinx.cinterop.* +import kotlin.math.* + +// -- Module: kMath + +//=======================================================// +// Quaternion DATA TYPE - Vector4 Alias +// NOTE: All functions are located in KVector4.kt +//=======================================================// + +/** + * Constructor function for [Quaternion] + * @param [allocator] Uses `MemScope()` by default. + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kQuaternion(x: Float = 0F, y: Float = 0F, z: Float = 0F, w: Float = 0F, allocator: AutofreeScope = MemScope()): Quaternion { + return allocator.alloc { + this.x = x + this.y = y + this.z = z + this.w = w + } +} + +/** + * Calculate slerp-optimized interpolation between two [Quaternion] + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Quaternion.nLerp(q2: Vector4, amount: Float): Quaternion { + val result: Quaternion = kQuaternion() + + // QuaternionLerp(q1, q2, amount) + + // QuaternionLerp(q1, q2, amount) + result.x = this.x + amount * (q2.x - this.x) + result.y = this.y + amount * (q2.y - this.y) + result.z = this.z + amount * (q2.z - this.z) + result.w = this.w + amount * (q2.w - this.w) + + // QuaternionNormalize(q); + + // QuaternionNormalize(q); + var length: Float = sqrt(result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w) + if (length == 0.0f) length = 1.0f + val ilength = 1.0f / length + + result.x *= ilength + result.y *= ilength + result.z *= ilength + result.w *= ilength + + return result +} + +/** + * Calculates spherical linear interpolation between two [Quaternion] + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Quaternion.sLerp(q2: Vector4, amount: Float): Quaternion { + var result: Quaternion = kQuaternion() + + var cosHalfTheta: Float = this.x * q2.x + this.y * q2.y + this.z * q2.z + this.w * q2.w + + if (cosHalfTheta < 0) { + q2.x = -q2.x + q2.y = -q2.y + q2.z = -q2.z + q2.w = -q2.w + cosHalfTheta = -cosHalfTheta + } + + if (abs(cosHalfTheta) >= 1.0f) result = this else if (cosHalfTheta > 0.95f) result = + this.nLerp(q2, amount) else { + val halfTheta: Float = acos(cosHalfTheta) + val sinHalfTheta: Float = sqrt(1.0f - cosHalfTheta * cosHalfTheta) + if (abs(sinHalfTheta) < 0.001f) { + result.x = this.x * 0.5f + q2.x * 0.5f + result.y = this.y * 0.5f + q2.y * 0.5f + result.z = this.z * 0.5f + q2.z * 0.5f + result.w = this.w * 0.5f + q2.w * 0.5f + } else { + val ratioA: Float = sin((1 - amount) * halfTheta) / sinHalfTheta + val ratioB: Float = sin(amount * halfTheta) / sinHalfTheta + result.x = this.x * ratioA + q2.x * ratioB + result.y = this.y * ratioA + q2.y * ratioB + result.z = this.z * ratioA + q2.z * ratioB + result.w = this.w * ratioA + q2.w * ratioB + } + } + + return result +} + +/** + * Get the rotation angle and axis for a given [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Quaternion.toAxisAngle(outAxis: CPointer, outAngle: CPointer) { + QuaternionToAxisAngle(this.readValue(), outAxis, outAngle) +} + +/** + * Get the Euler angles equivalent to [Quaternion] (roll, pitch, yaw) + * NOTE: Angles are returned in a Vector3 struct in radians + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Quaternion.toEuler(): Vector3 { + val result: Vector3 = kVector3() + + // Roll (x-axis rotation) + + // Roll (x-axis rotation) + val x0: Float = 2.0f * (this.w * this.x + this.y * this.z) + val x1: Float = 1.0f - 2.0f * (this.x * this.x + this.y * this.y) + result.x = atan2(x0, x1) + + // Pitch (y-axis rotation) + + // Pitch (y-axis rotation) + var y0: Float = 2.0f * (this.w * this.y - this.z * this.x) + y0 = if (y0 > 1.0f) 1.0f else y0 + y0 = if (y0 < -1.0f) -1.0f else y0 + result.y = asin(y0) + + // Yaw (z-axis rotation) + + // Yaw (z-axis rotation) + val z0: Float = 2.0f * (this.w * this.z + this.x * this.y) + val z1: Float = 1.0f - 2.0f * (this.y * this.y + this.z * this.z) + result.z = atan2(z0, z1) + + return result +} + +/** + * Get the Euler angles equivalent to [Quaternion] (roll, pitch, yaw) + * NOTE: Angles are returned in a [Vector3] struct in radians + * @return [Quaternion] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Quaternion.transform(mat: Matrix): Quaternion { + val result: Quaternion = kQuaternion() + + result.x = mat.m0 * this.x + mat.m4 * this.y + mat.m8 * this.z + mat.m12 * this.w + result.y = mat.m1 * this.x + mat.m5 * this.y + mat.m9 * this.z + mat.m13 * this.w + result.z = mat.m2 * this.x + mat.m6 * this.y + mat.m10 * this.z + mat.m14 * this.w + result.w = mat.m3 * this.x + mat.m7 * this.y + mat.m11 * this.z + mat.m15 * this.w + + return result +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kMath/KVector2.kt b/src/commonMain/kotlin/kaylibkit/kMath/KVector2.kt new file mode 100644 index 0000000..65f8c5a --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kMath/KVector2.kt @@ -0,0 +1,455 @@ +package kaylibkit.kMath + +import kaylibc.* +import kotlinx.cinterop.* +import kotlin.math.* + +// -- Module: kMath + +//=======================================================// +// VECTOR2 DATA TYPE +//=======================================================// + +/** + * Constructor function for [Vector2] + * @param [allocator] Uses `MemScope()` by default. + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kVector2(x: Float = 0F, y: Float = 0F, allocator: AutofreeScope = MemScope()): Vector2 { + return allocator.alloc { + this.x = x + this.y = y + } +} + +/** + * [Vector2] with components value 0 + * @return zero values Y and X of passed [Vector2] + */ +inline fun Vector2.setZero() : Vector2 = this.apply { this.x = 0F; this.y = 0F } + +/** + * [Vector2] with components value 1 + * @return values of 1 in Y and X of passed [Vector2] + */ +inline fun Vector2.setOne() : Vector2 = this.apply { this.x = 1F; this.y = 1F } + +/** + * Calculate [Vector2] length + * @return [Float] + */ +inline fun Vector2.length() : Float = sqrt((this.x*this.x) + (this.y*this.y)) + +/** + * Calculate [Vector2] square length + * @return [Float] + */ +inline fun Vector2.lengthSqr() = (this.x*this.x) + (this.y*this.y) + +/** + * Calculate two [Vector2] dot product + * @return [Float] + */ +inline fun Vector2.dot(v2: Vector2) : Float = (this.x*v2.x + this.y*v2.y) + +/** + * Calculate distance between two [Vector2] + * @return [Float] + */ +inline fun Vector2.distance(v2: Vector2) : Float = sqrt((this.x - v2.x)*(this.x - v2.x) + (this.y - v2.y)*(this.y - v2.y)) + +/** + * Calculate square distance between two [Vector2] + * @return [Float] + */ +inline fun Vector2.distanceSqr(v2: Vector2) : Float = ((this.x - v2.x)*(this.x - v2.x) + (this.y - v2.y)*(this.y - v2.y)) + +/** + * Calculate angle from two [Vector2] + * @return [Float] + */ +inline fun Vector2.angle(v2: Vector2) : Float = atan2(v2.y - this.y, v2.x - this.x) + +/** + * Scale [Vector2] (multiply by value) + */ +inline fun Vector2.times(scale: Float) { + this.x *= scale + this.y *= scale +} + +/** + * Add two [Vector2] + */ +inline fun Vector2.add(v2: Vector2) { + this.x += v2.x + this.y += v2.y +} + +/** + * Add two [Vector2] + */ +inline operator fun Vector2.plus(v2: Vector2) { + this.x += v2.x + this.y += v2.y +} + +/** + * Multiply [Vector2] by [Vector2] + */ +inline fun Vector2.multiply(v2: Vector2) { + this.x *= v2.x + this.y *= v2.y +} + +/** + * Add [Vector2] and float value + */ +inline fun Vector2.addValue(add: Float) { + this.x += add + this.y += add +} + +/** + * Subtract two [Vector2] + */ +inline fun Vector2.subtract(v2: Vector2) { + this.x -= v2.x + this.y -= v2.y +} + +/** + * Subtract [Vector2] by float value + */ +inline fun Vector2.subtractValue(sub: Float) { + this.x -= sub + this.y -= sub +} + +/** + * Negate [Vector2] + */ +inline fun Vector2.negate() { + this.x = this.x.unaryMinus() + this.y = this.y.unaryMinus() +} + +/** + * Normalize provided [Vector2] + * @return [Vector2] + */ +inline fun Vector2.normalize() : Vector2 { + val result = this + val length = sqrt((this.x*this.x) + (this.y*this.y)) + + if (length > 0) { + val iLength = 1F/length + result.x = this.x*iLength + result.y = this.y*iLength + } + return result +} + +/** + * Transforms a [Vector2] by a given [Matrix] + * @return [Vector2] + */ +inline fun Vector2.transform(mat: Matrix) : Vector2 { + val result = this + + val x = this.x + val y = this.y + val z = 0 + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12 + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13 + + return result +} + +/** + * Calculate linear interpolation between two [Vector2] + * @return [Vector2] + */ +inline fun Vector2.lerp(v2: Vector2, amount: Float) : Vector2 { + val result = this + + result.x = this.x + amount*(v2.x - this.x) + result.y = this.y + amount*(v2.y - this.y) + + return result +} + +/** + * Calculate reflected [Vector2] to normal + * * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector2.reflect(normal: Vector2) : Vector2 { + val result: Vector2 = kVector2() + + val dotProduct: Float = this.x * normal.x + this.y * normal.y // Dot product + + + result.x = this.x - 2.0f * normal.x * dotProduct + result.y = this.y - 2.0f * normal.y * dotProduct + + return result +} + +/** + * Rotate [Vector2] by [angle] + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector2.rotate(angle: Float) : Vector2 { + val result: Vector2 = kVector2() + + val cosres: Float = cos(angle) + val sinres: Float = sin(angle) + + result.x = this.x * cosres - this.y * sinres + result.y = this.x * sinres + this.y * cosres + + return result +} + +/** + * Move [Vector2] towards target + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector2.moveTowards(target: Vector2, maxDistance: Float) : Vector2 { + val result: Vector2 = kVector2() + + val dx: Float = target.x - this.x + val dy: Float = target.y - this.y + val value = dx * dx + dy * dy + + if (value == 0f || maxDistance >= 0 && value <= maxDistance * maxDistance) return target + + val dist: Float = sqrt(value) + + result.x = this.x + dx / dist * maxDistance + result.y = this.y + dy / dist * maxDistance + + return result +} + +/** + * Invert the given [Vector2] + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector2.invert(): Vector2 { return kVector2(1.0f / this.x, 1.0f / this.y) } + +/** + * Clamp the components of the [Vector2] between + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector2.clamp(min: Vector2, max: Vector2) : Vector2 { + val result: Vector2 = kVector2() + + result.x = min(max.x, max(min.x, this.x)) + result.y = min(max.y, max(min.y, this.y)) + + return result +} + +@Deprecated("Use Kotlin's internal coerceIn function", ReplaceWith("coerceIn()")) +/** + * Clamp the magnitude of the vector between two values + * @return [Unit] + */ +inline fun Vector2.clampValue() {} + +/** + * Calculate angle defined by a two vectors line + * NOTE: Parameters need to be normalized + * Current implementation should be aligned with glm::angle + * @return [Vector2] + */ +inline fun Vector2.lineAngle(start: Vector2, end: Vector2) : Float { + val result: Float + + val dot: Float = start.x * end.x + start.y * end.y // Dot product + var dotClamp = if (dot < -1.0f) -1.0f else dot // Clamp + if (dotClamp > 1.0f) dotClamp = 1.0f + result = acos(dotClamp) + + return result +} + +/** + * Deconstruct this [Vector2]. + */ +inline operator fun Vector2.component1(): Float = x + +/** + * Deconstruct this [Vector2]. + */ +inline operator fun Vector2.component2(): Float = y + +/** + * Divide vector by [Vector2] + */ +inline operator fun Vector2.divAssign(v2: Vector2) { + this.x /= v2.x + this.y /= v2.y +} + +/** + * Divide vector by [Vector2] + */ +inline operator fun Vector2.div(v2: Vector2) { + this.x /= v2.x + this.y /= v2.y +} + + +/** + * Divide both [Vector2] values by scalar. + */ +inline operator fun Vector2.divAssign(scalar: Float) { + this.x /= scalar + this.y /= scalar +} + +/** + * Divide both [Vector2] values by scalar. + */ +inline operator fun Vector2.divAssign(scalar: Int) { + this.x /= scalar + this.y /= scalar +} + +/** + * Multiply both [Vector2] values + */ +inline operator fun Vector2.timesAssign(scalar: Int) { + this.x *= scalar.toFloat() + this.y *= scalar.toFloat() +} + +/** + * Multiply both [Vector2] values + */ +inline operator fun Vector2.timesAssign(scalar: Float) { + this.x *= scalar + this.y *= scalar +} +/** + * Multiply both [Vector2] values + */ +inline operator fun Vector2.timesAssign(v2: Vector2) { + this.x *= v2.x + this.y *= v2.y +} + +/** + * Add two [Vector2] + */ +inline operator fun Vector2.plusAssign(v2: Vector2) { + this.add(v2) +} + +/** + * Add two [Vector2] to both x and y of the [Vector2]. + */ +inline operator fun Vector2.plusAssign(add: Float) { + this.x += add + this.y += add +} + +/** + * Add two [Vector2] to both x and y of the [Vector2]. + */ +inline operator fun Vector2.plusAssign(addend: Int) { + plusAssign(addend.toFloat()) +} + +/** + * Subtract a [Vector2] by another [Vector2] + */ +inline operator fun Vector2.minusAssign(v2: Vector2) { + this.x -= v2.x + this.y -= v2.y +} + +/** + * Value will be subtracted from both x and y of the [Vector2]. + */ +inline operator fun Vector2.minusAssign(value: Float) { + this.x -= value + this.y -= value +} + +/** + * Value will be subtracted from both x and y of the [Vector2]. + */ +inline operator fun Vector2.minusAssign(value: Int) { + minusAssign(value.toFloat()) +} + +/** + * Subtract a [Vector2] by another [Vector2] + */ +inline operator fun Vector2.minus(v2: Vector2) { + this.x -= v2.x + this.y -= v2.y +} + +/** + * Value will be multiplied by a [Vector2] + */ +inline operator fun Vector2.times(v2: Vector2) { + this.x *= v2.x + this.y *= v2.y +} + +/** + * Get a reminder of [Vector2] + */ +inline operator fun Vector2.rem(v2: Vector2) { + this.x %= v2.x + this.y %= v2.y +} + +/** + * Value will be compared with another [Vector2] + */ +inline operator fun Vector2.compareTo(other: Vector2): Int = when { + this.y != other.y -> (this.y - other.y).toInt() + else -> (this.x - other.x).toInt() +} + +/** + * Due to inability to override toString function, this is the current workaround printing the values of a [Vector2] + * @return [String] values of [Vector2] + */ +inline fun Vector2.asString(): String { + return "X: ${x.toString()}\nY: ${y.toString()}" +} + +/** + * Due to inability to override equals operator, this is the current workaround checking for equality of the values of a [Vector2] + * @return [Boolean] + */ +inline fun Vector2.equalsTo(v: Vector2): Boolean { + return (abs(this.x - v.x) <= EPSILON * max( + 1.0f, + max(abs(this.x), abs(v.x)) + ) && abs(this.y - v.y) <= EPSILON * max(1.0f, max(abs(this.y), abs(v.y)))) +} + +/** + * Set value of a [Vector2] with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Vector2.set(other: Vector2) { + this.x = other.x + this.y = other.y +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kMath/KVector3.kt b/src/commonMain/kotlin/kaylibkit/kMath/KVector3.kt new file mode 100644 index 0000000..148b556 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kMath/KVector3.kt @@ -0,0 +1,788 @@ +package kaylibkit.kMath + +import kaylibc.* +import kotlinx.cinterop.* +import kotlin.math.* + +// -- Module: kMath + +//=======================================================// +// VECTOR3 DATA TYPE +//=======================================================// + +/** + * Constructor function for [Vector3] + * @param [allocator] Uses `MemScope()` by default. + * @return [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kVector3(x: Float = 0F, y: Float = 0F, z: Float = 0F, allocator: AutofreeScope = MemScope()): Vector3 { + return allocator.alloc { + this.x = x + this.y = y + this.z = z + } +} + +/** + * [Vector3] with components value 0 + * @return zero values Y, X and Z of passed [Vector3] + */ +inline fun Vector3.setZero() : Vector3 = this.apply { this.x = 0F; this.y = 0F; this.z = 0F } + +/** + * [Vector3] with components value 1 + * @return values of 1 in Y and X of passed [Vector3] + */ +inline fun Vector3.setOne() : Vector3 = this.apply { this.x = 1F; this.y = 1F; this.z = 1F } + +/** + * Calculate [Vector3] length + */ +inline fun Vector3.length() : Float = sqrt((this.x*this.x + this.y*this.y + this.z*this.z)) + +/** + * Calculate [Vector3] square length + */ +inline fun Vector3.lengthSqr() = (this.x*this.x + this.y*this.y + this.z*this.z) + +/** + * Calculate two [Vector3] dot product + */ +inline fun Vector3.dot(v3: Vector3) : Float = (this.x*v3.x + this.y*v3.y + this.z*v3.z) + +/** + * Calculate distance between two [Vector3] + */ +inline fun Vector3.distance(v3: Vector3) : Float = sqrt((this.x - v3.x)*(this.x - v3.x) + (this.y - v3.y)*(this.y - v3.y) + (this.z - v3.z)*(this.z - v3.z)) + +/** + * Calculate square distance between two [Vector3] + */ +inline fun Vector3.distanceSqr(v3: Vector3) : Float = ((this.x - v3.x)*(this.x - v3.x) + (this.y - v3.y)*(this.y - v3.y) + (this.z - v3.z)*(this.z - v3.z)) + +/** + * Calculate angle from two [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.angle(v3: Vector3) : Float { + val result: Float + + val cross = kVector3(this.y*v3.z - this.z*v3.y, this.z*v3.x - this.x*v3.z, this.x*v3.y - this.y*v3.x ) + val len = sqrt(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z) + val dot = (this.x*v3.x + this.y*v3.y + this.z*v3.z) + + result = atan2(len, dot) + return result +} + +/** + * Calculate two [Vector3] cross product + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.crossProduct(v2: Vector3): Vector3 { + return kVector3(this.y * v2.z - this.z * v2.y, this.z * v2.x - this.x * v2.z, this.x * v2.y - this.y * v2.x) +} + +/** + * Scale [Vector3] (multiply by value) + */ +inline fun Vector3.times(scale: Float) { + this.x *= scale + this.y *= scale + this.z *= scale +} + +/** + * Add two [Vector3] + */ +inline fun Vector3.add(v3: Vector3) { + this.x += v3.x + this.y += v3.y + this.z += v3.z +} + +/** + * Multiply [Vector3] by [Vector3] + */ +inline fun Vector3.multiply(v3: Vector3) { + this.x *= v3.x + this.y *= v3.y + this.z *= v3.z +} + +/** + * Add [Vector3] and float value + */ +inline fun Vector3.addValue(add: Float) { + this.x += add + this.y += add + this.z += add +} + +/** + * Subtract two [Vector3] + */ +inline fun Vector3.subtract(v3: Vector3) { + this.x -= v3.x + this.y -= v3.y + this.z -= v3.z +} + +/** + * Subtract [Vector3] by float value + */ +inline fun Vector3.subtractValue(sub: Float) { + this.x -= sub + this.y -= sub + this.z -= sub +} + +/** + * Get reminder of [Vector3] + */ +inline fun Vector3.rem(v3: Vector3) { + this.x %= v3.x + this.y %= v3.y + this.z %= v3.z +} + +/** + * Negate [Vector3] + */ +inline fun Vector3.negate() { + this.x = this.x.unaryMinus() + this.y = this.y.unaryMinus() + this.z = this.z.unaryMinus() +} + +/** + * Normalize provided [Vector3] + */ +inline fun Vector3.normalize() : Vector3 { + val result = this + var length = sqrt(this.x*this.x + this.y*this.y + this.z*this.z) + + if (length == 0F) { length = 1F } + + val iLength = 1F/length + result.x = this.x*iLength + result.y = this.y*iLength + result.z = this.z*iLength + return result +} + +/** + * Transforms a [Vector3] by a given [Matrix] + */ +inline fun Vector3.transform(mat: Matrix) : Vector3 { + val result = this + + val x = this.x + val y = this.y + val z = this.z + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12 + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13 + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result +} + +/** + * Transform a [Vector3] by [Quaternion] rotation + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.rotateByQuaternion(q: Quaternion): Vector3 { + val result: Vector3 = kVector3() + + result.x = + this.x * (q.x * q.x + q.w * q.w - q.y * q.y - q.z * q.z) + this.y * (2 * q.x * q.y - 2 * q.w * q.z) + this.z * (2 * q.x * q.z + 2 * q.w * q.y) + result.y = + this.x * (2 * q.w * q.z + 2 * q.x * q.y) + this.y * (q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z) + this.z * (-2 * q.w * q.x + 2 * q.y * q.z) + result.z = + this.x * (-2 * q.w * q.y + 2 * q.x * q.z) + this.y * (2 * q.w * q.x + 2 * q.y * q.z) + this.z * (q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z) + + return result +} + +/** + * Rotates a [Vector3] around an [axis] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.rotateByAxisAngle(axis: Vector3, angle: Float): Vector3 { + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + val result: Vector3 = this + var ang = angle + + // Vector3Normalize(axis); + + // Vector3Normalize(axis); + var length: Float = sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z) + if (length == 0.0f) length = 1.0f + val ilength = 1.0f / length + axis.x *= ilength + axis.y *= ilength + axis.z *= ilength + + ang /= 2.0f + var a: Float = sin(angle) + val b = axis.x * a + val c = axis.y * a + val d = axis.z * a + a = cos(angle) + val w: Vector3 = kVector3(b, c, d) + + // Vector3CrossProduct(w, v) + + // Vector3CrossProduct(w, v) + val wv: Vector3 = kVector3(w.y * this.z - w.z * this.y, w.z * this.x - w.x * this.z, w.x * this.y - w.y * this.x) + + // Vector3CrossProduct(w, wv) + + // Vector3CrossProduct(w, wv) + val wwv: Vector3 = kVector3(w.y * wv.z - w.z * wv.y, w.z * wv.x - w.x * wv.z, w.x * wv.y - w.y * wv.x) + + // Vector3Scale(wv, 2 * a) + + // Vector3Scale(wv, 2 * a) + a *= 2f + wv.x *= a + wv.y *= a + wv.z *= a + + // Vector3Scale(wwv, 2) + + // Vector3Scale(wwv, 2) + wwv.x *= 2 + wwv.y *= 2 + wwv.z *= 2 + + result.x += wv.x + result.y += wv.y + result.z += wv.z + + result.x += wwv.x + result.y += wwv.y + result.z += wwv.z + + return result +} + +/** + * Calculate linear interpolation between two [Vector3] + */ +inline fun Vector3.lerp(v3: Vector3, amount: Float) : Vector3 { + val result = this + + result.x = this.x + amount*(v3.x - this.x) + result.y = this.y + amount*(v3.y - this.y) + result.z = this.z + amount*(v3.z - this.z) + + return result +} + +/** + * Orthonormalize provided [Vector3] + * Makes vectors normalized and orthogonal to each other + * Gram-Schmidt function implementation + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.orthoNormalize(v3: CPointer) { + return Vector3OrthoNormalize(this.ptr, v3) +} + + +/** + * Calculate reflected [Vector3] to normal + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.reflect(normal: Vector3) : Vector3 { + val result: Vector3 = kVector3() + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + val dotProduct: Float = this.x * normal.x + this.y * normal.y + this.z * normal.z + + result.x = this.x - 2.0f * normal.x * dotProduct + result.y = this.y - 2.0f * normal.y * dotProduct + result.z = this.z - 2.0f * normal.z * dotProduct + + return result +} + +/** + * Calculate one [Vector3] perpendicular vector + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.perpendicular() : Vector3 { + val result: Vector3 = kVector3() + + var min = abs(this.x) + var cardinalAxis: Vector3 = kVector3(1.0f, 0.0f, 0.0f) + + if (abs(this.y) < min) { + min = abs(this.y) + val tmp: Vector3 = kVector3(0.0f, 1.0f, 0.0f) + cardinalAxis = tmp + } + + if (abs(this.z) < min) { + val tmp: Vector3 = kVector3(0.0f, 0.0f, 1.0f) + cardinalAxis = tmp + } + + // Cross product between vectors + + // Cross product between vectors + result.x = this.y * cardinalAxis.z - this.z * cardinalAxis.y + result.y = this.z * cardinalAxis.x - this.x * cardinalAxis.z + result.z = this.x * cardinalAxis.y - this.y * cardinalAxis.x + + return result +} + +/** + * Invert the given [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.invert(): Vector3 { + + return kVector3(1.0f / this.x, 1.0f / this.y, 1.0f / this.z) +} + + +/** + * Get min value for each pair of components + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.min(v3: Vector3) : Vector3 { + val result: Vector3 = kVector3() + + result.x = min(this.x, v3.x) + result.y = min(this.y, v3.y) + result.z = min(this.z, v3.z) + + return result +} + +/** + * Get max value for each pair of components + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.max(v3: Vector3) : Vector3 { + val result: Vector3 = kVector3() + + result.x = max(this.x, v3.x) + result.y = max(this.y, v3.y) + result.z = max(this.z, v3.z) + + return result +} + +/** + * Get max value for each pair of components + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.barycenter(a: Vector3, b: Vector3, c: Vector3) : Vector3 { + val result: Vector3 = kVector3() + + val v0: Vector3 = kVector3(b.x - a.x, b.y - a.y, b.z - a.z) // Vector3Subtract(b, a) + + val v1: Vector3 = kVector3(c.x - a.x, c.y - a.y, c.z - a.z) // Vector3Subtract(c, a) + + val v2: Vector3 = kVector3(this.x - a.x, this.y - a.y, this.z - a.z) // Vector3Subtract(p, a) + + val d00 = v0.x * v0.x + v0.y * v0.y + v0.z * v0.z // Vector3DotProduct(v0, v0) + + val d01 = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z // Vector3DotProduct(v0, v1) + + val d11 = v1.x * v1.x + v1.y * v1.y + v1.z * v1.z // Vector3DotProduct(v1, v1) + + val d20 = v2.x * v0.x + v2.y * v0.y + v2.z * v0.z // Vector3DotProduct(v2, v0) + + val d21 = v2.x * v1.x + v2.y * v1.y + v2.z * v1.z // Vector3DotProduct(v2, v1) + + + val denom = d00 * d11 - d01 * d01 + + result.y = (d11 * d20 - d01 * d21) / denom + result.z = (d00 * d21 - d01 * d20) / denom + result.x = 1.0f - (result.z + result.y) + + return result +} + +/** + * Projects a [Vector3] from screen space into object space + * NOTE: We are avoiding calling other raymath functions despite available + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.unproject(projection: Matrix, view: Matrix) : Vector3 { + val result: Vector3 = kVector3() + + // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it + + // Calculate unproject matrix (multiply view patrix by projection matrix) and invert it + val matViewProj: Matrix = kMatrix( // MatrixMultiply(view, projection); + view.m0 * projection.m0 + view.m1 * projection.m4 + view.m2 * projection.m8 + view.m3 * projection.m12, + view.m0 * projection.m1 + view.m1 * projection.m5 + view.m2 * projection.m9 + view.m3 * projection.m13, + view.m0 * projection.m2 + view.m1 * projection.m6 + view.m2 * projection.m10 + view.m3 * projection.m14, + view.m0 * projection.m3 + view.m1 * projection.m7 + view.m2 * projection.m11 + view.m3 * projection.m15, + view.m4 * projection.m0 + view.m5 * projection.m4 + view.m6 * projection.m8 + view.m7 * projection.m12, + view.m4 * projection.m1 + view.m5 * projection.m5 + view.m6 * projection.m9 + view.m7 * projection.m13, + view.m4 * projection.m2 + view.m5 * projection.m6 + view.m6 * projection.m10 + view.m7 * projection.m14, + view.m4 * projection.m3 + view.m5 * projection.m7 + view.m6 * projection.m11 + view.m7 * projection.m15, + view.m8 * projection.m0 + view.m9 * projection.m4 + view.m10 * projection.m8 + view.m11 * projection.m12, + view.m8 * projection.m1 + view.m9 * projection.m5 + view.m10 * projection.m9 + view.m11 * projection.m13, + view.m8 * projection.m2 + view.m9 * projection.m6 + view.m10 * projection.m10 + view.m11 * projection.m14, + view.m8 * projection.m3 + view.m9 * projection.m7 + view.m10 * projection.m11 + view.m11 * projection.m15, + view.m12 * projection.m0 + view.m13 * projection.m4 + view.m14 * projection.m8 + view.m15 * projection.m12, + view.m12 * projection.m1 + view.m13 * projection.m5 + view.m14 * projection.m9 + view.m15 * projection.m13, + view.m12 * projection.m2 + view.m13 * projection.m6 + view.m14 * projection.m10 + view.m15 * projection.m14, + view.m12 * projection.m3 + view.m13 * projection.m7 + view.m14 * projection.m11 + view.m15 * projection.m15 + ) + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + val a00 = matViewProj.m0 + val a01 = matViewProj.m1 + val a02 = matViewProj.m2 + val a03 = matViewProj.m3 + val a10 = matViewProj.m4 + val a11 = matViewProj.m5 + val a12 = matViewProj.m6 + val a13 = matViewProj.m7 + val a20 = matViewProj.m8 + val a21 = matViewProj.m9 + val a22 = matViewProj.m10 + val a23 = matViewProj.m11 + val a30 = matViewProj.m12 + val a31 = matViewProj.m13 + val a32 = matViewProj.m14 + val a33 = matViewProj.m15 + + val b00 = a00 * a11 - a01 * a10 + val b01 = a00 * a12 - a02 * a10 + val b02 = a00 * a13 - a03 * a10 + val b03 = a01 * a12 - a02 * a11 + val b04 = a01 * a13 - a03 * a11 + val b05 = a02 * a13 - a03 * a12 + val b06 = a20 * a31 - a21 * a30 + val b07 = a20 * a32 - a22 * a30 + val b08 = a20 * a33 - a23 * a30 + val b09 = a21 * a32 - a22 * a31 + val b10 = a21 * a33 - a23 * a31 + val b11 = a22 * a33 - a23 * a32 + + // Calculate the invert determinant (inlined to avoid double-caching) + + // Calculate the invert determinant (inlined to avoid double-caching) + val invDet = 1.0f / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06) + + val matViewProjInv: Matrix = kMatrix( + (a11 * b11 - a12 * b10 + a13 * b09) * invDet, + (-a01 * b11 + a02 * b10 - a03 * b09) * invDet, + (a31 * b05 - a32 * b04 + a33 * b03) * invDet, + (-a21 * b05 + a22 * b04 - a23 * b03) * invDet, + (-a10 * b11 + a12 * b08 - a13 * b07) * invDet, + (a00 * b11 - a02 * b08 + a03 * b07) * invDet, + (-a30 * b05 + a32 * b02 - a33 * b01) * invDet, + (a20 * b05 - a22 * b02 + a23 * b01) * invDet, + (a10 * b10 - a11 * b08 + a13 * b06) * invDet, + (-a00 * b10 + a01 * b08 - a03 * b06) * invDet, + (a30 * b04 - a31 * b02 + a33 * b00) * invDet, + (-a20 * b04 + a21 * b02 - a23 * b00) * invDet, + (-a10 * b09 + a11 * b07 - a12 * b06) * invDet, + (a00 * b09 - a01 * b07 + a02 * b06) * invDet, + (-a30 * b03 + a31 * b01 - a32 * b00) * invDet, + (a20 * b03 - a21 * b01 + a22 * b00) * invDet + ) + + // Create quaternion from source point + + // Create quaternion from source point + val quat: Quaternion = kQuaternion(this.x, this.y, this.z, 1.0f) + + // Multiply quat point by unproject matrix + + // Multiply quat point by unproject matrix + val qtransformed: Quaternion = kQuaternion( // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0 * quat.x + matViewProjInv.m4 * quat.y + matViewProjInv.m8 * quat.z + matViewProjInv.m12 * quat.w, + matViewProjInv.m1 * quat.x + matViewProjInv.m5 * quat.y + matViewProjInv.m9 * quat.z + matViewProjInv.m13 * quat.w, + matViewProjInv.m2 * quat.x + matViewProjInv.m6 * quat.y + matViewProjInv.m10 * quat.z + matViewProjInv.m14 * quat.w, + matViewProjInv.m3 * quat.x + matViewProjInv.m7 * quat.y + matViewProjInv.m11 * quat.z + matViewProjInv.m15 * quat.w + ) + + // Normalized world points in vectors + + // Normalized world points in vectors + result.x = qtransformed.x / qtransformed.w + result.y = qtransformed.y / qtransformed.w + result.z = qtransformed.z / qtransformed.w + + return result +} + +/** + * Get [Vector3] as float array + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.toFloatV() : float3 { + return Vector3ToFloatV(this.readValue()).getPointer(MemScope()).pointed +} + +/** + * Clamp the components of the [Vector3] between + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.clamp(min: Vector3, max: Vector3) : Vector3 { + val result: Vector3 = kVector3() + + result.x = min(max.x, max(min.x, this.x)) + result.y = min(max.y, max(min.y, this.y)) + result.z = min(max.z, max(min.z, this.z)) + + return result +} + +@Deprecated("Use Kotlin's internal coerceIn function", ReplaceWith("coerceIn()")) +/** + * Clamp the magnitude of the vector between two values + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.clampValue(min: Float, max: Float) : Vector3 { + return Vector3ClampValue(this.readValue(), min, max).getPointer(MemScope()).pointed +} + +/** + * Due to inability to override equals operator, this is the current workaround checking for equality of the values of a [Vector3] + * @return String values of [Vector3] + */ +inline fun Vector3.equalsTo(v: Vector3): Boolean { + + return (abs(this.x - v.x) <= EPSILON * max( + 1.0f, + max(abs(this.x), abs(v.x)) + ) && abs(this.y - v.y) <= EPSILON * max( + 1.0f, + max(abs(this.y), abs(v.y)) + ) && abs(this.z - v.z) <= EPSILON * max(1.0f, max(abs(this.z), abs(v.z)))) +} + +/** + * Compute the direction of a refracted ray where v specifies the + * normalized direction of the incoming ray, n specifies the + * normalized normal vector of the interface of two optical media, + * and r specifies the ratio of the refractive index of the medium + * from where the ray comes to the refractive index of the medium + * on the other side of the surface + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector3.refract(n: Vector3, r: Float) : Vector3 { + var result: Vector3 = kVector3() + + val dot: Float = this.x * n.x + this.y * n.y + this.z * n.z + var d = 1.0f - r * r * (1.0f - dot * dot) + + if (d >= 0.0f) { + d = sqrt(d) + this.x = r * this.x - (r * dot + d) * n.x + this.y = r * this.y - (r * dot + d) * n.y + this.z = r * this.z - (r * dot + d) * n.z + result = this + } + + return result +} + +/** + * Divide vector by [Vector3] + */ +inline operator fun Vector3.div(v3: Vector3) { + this.x /= v3.x + this.y /= v3.y + this.z /= v3.z +} + +/** + * Deconstruct this [Vector3]. + */ +inline operator fun Vector3.component1(): Float = x + +/** + * Deconstruct this [Vector3]. + */ +inline operator fun Vector3.component2(): Float = y + +/** + * Deconstruct this [Vector3]. + */ +inline operator fun Vector3.component3(): Float = z + +/** + * Divide vector by [Vector3] + */ +inline operator fun Vector3.divAssign(v3: Vector3) { + this.x /= v3.x + this.y /= v3.y + this.z /= v3.z +} + +/** + * Divide both [Vector3] values by scalar. + */ +inline operator fun Vector3.divAssign(scalar: Float) { + this.x /= scalar + this.y /= scalar + this.z /= scalar +} + +/** + * Divide both [Vector3] values by scalar. + */ +inline operator fun Vector3.divAssign(scalar: Int) { + this.x /= scalar + this.y /= scalar + this.z /= scalar +} + +/** + * Multiply both [Vector3] values + */ +inline operator fun Vector3.timesAssign(scalar: Int) { + this.x *= scalar.toFloat() + this.y *= scalar.toFloat() + this.z *= scalar.toFloat() +} + +/** + * Multiply both [Vector3] values + */ +inline operator fun Vector3.timesAssign(scalar: Float) { + this.x *= scalar + this.y *= scalar + this.z *= scalar +} +/** + * Multiply both [Vector3] values + */ +inline operator fun Vector3.timesAssign(v3: Vector3) { + this.x *= v3.x + this.y *= v3.y + this.z *= v3.z +} + +/** + * Add two [Vector3] + */ +inline operator fun Vector3.plusAssign(v3: Vector3) { + this.add(v3) +} + +/** + * Add two [Vector3] + */ +inline operator fun Vector3.plus(v3: Vector3) { + this.add(v3) +} + +/** + * Add two [Vector3] to both x, y and z of the vector. + */ +inline operator fun Vector3.plusAssign(add: Float) { + this.x += add + this.y += add + this.z += add +} + +/** + * Add two [Vector3] to both x, y and z of the vector. + */ +inline operator fun Vector3.plusAssign(addend: Int) { + plusAssign(addend.toFloat()) +} + +/** + * Subtract a [Vector3] by another Vector3 + */ +inline operator fun Vector3.minusAssign(v3: Vector3) { + this.x -= v3.x + this.y -= v3.y + this.z -= v3.z +} + +/** + * Subtract a [Vector3] by another Vector3 + */ +inline operator fun Vector3.minus(v3: Vector3) { + this.x -= v3.x + this.y -= v3.y + this.z -= v3.z +} + +/** + * Value will be subtracted from both x, y and z of the [Vector3]. + */ +inline operator fun Vector3.minusAssign(value: Float) { + this.x -= value + this.y -= value + this.z -= value +} + +/** + * Value will be subtracted from both x, y and z of the [Vector3]. + */ +inline operator fun Vector3.minusAssign(value: Int) { + minusAssign(value.toFloat()) +} + +/** + * Value will be multiplied by a [Vector3] + */ +inline operator fun Vector3.times(v3: Vector3) { + this.x *= v3.x + this.y *= v3.y + this.z *= v3.z +} + +/** + * Value will be compared with another [Vector3] + */ +inline operator fun Vector3.compareTo(other: Vector3): Int = when { + this.y != other.y -> (this.y - other.y).toInt() + this.z != other.z -> (this.z - other.z).toInt() + else -> (this.x - other.x).toInt() +} + +/** + * Due to inability to override toString function, this is the current workaround printing the values of a [Vector3] + * @return [Vector3] + */ +inline fun Vector3.asString(): String { + return "X: ${x.toString()}\nY: ${y.toString()}\nZ: ${z.toString()}" +} + +/** + * Set value of a [Vector3] with another provided value. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Vector3.set(other: Vector3) { + this.x = other.x + this.y = other.y + this.z = other.z +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kMath/KVector4.kt b/src/commonMain/kotlin/kaylibkit/kMath/KVector4.kt new file mode 100644 index 0000000..05aeab2 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kMath/KVector4.kt @@ -0,0 +1,391 @@ +package kaylibkit.kMath + +import kaylibc.* +import kotlinx.cinterop.* +import kotlin.math.* + +// -- Module: kMath + +//=======================================================// +// Vector4 DATA TYPE +//=======================================================// + +/** + * Constructor function for [Vector4] + * @param [allocator] Uses `MemScope()` by default. + * @return [Vector4] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kVector4(x: Float = 0F, y: Float = 0F, z: Float = 0F, w: Float = 0F, allocator: AutofreeScope = MemScope()): Vector4 { + return allocator.alloc { + this.x = x + this.y = y + this.z = z + this.w = w + } +} + +/** + * Add [Vector4] and float value + */ +inline fun Vector4.addValue(add: Float) { + this.x += add + this.y += add + this.z += add + this.w += add +} + +/** + * Subtract two [Vector4] + */ +inline fun Vector4.subtract(v4: Vector4) { + this.x -= v4.x + this.y -= v4.y + this.z -= v4.z +} + +/** + * Subtract [Vector4] by float value + */ +inline fun Vector4.subtractValue(sub: Float) { + this.x -= sub + this.y -= sub + this.z -= sub + this.w -= sub +} + + +/** + * Negate [Vector4] + */ +inline fun Vector4.negate() { + this.x = this.x.unaryMinus() + this.y = this.y.unaryMinus() + this.z = this.z.unaryMinus() + this.w = this.w.unaryMinus() +} + +/** + * Compute the length of a [Vector4] + * @return [Vector4] + */ +inline fun Vector4.length(): Float { return sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w) } + +/** + * Normalize provided [Vector4] + * @return [Vector4] + */ +inline fun Vector4.normalize() : Vector4 { + val result = this + var length = sqrt(this.x*this.x + this.y*this.y + this.z*this.z + this.w*this.w) + + if (length == 0F) { length = 1F } + + val iLength = 1F/length + result.x = this.x*iLength + result.y = this.y*iLength + result.z = this.z*iLength + result.w = this.w*iLength + return result +} + +/** + * Invert provided [Quaternion]/[Vector4] + * @return [Vector4] + */ +inline fun Vector4.invert(): Vector4 { + val result: Vector4 = this + + val lengthSq: Float = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w + + if (lengthSq != 0.0F) { + val invLength = 1.0F / lengthSq + result.x *= -invLength + result.y *= -invLength + result.z *= -invLength + result.w *= invLength + } + + return result +} + +/** + * Deconstruct this [Vector4]. + */ +inline operator fun Vector4.component1(): Float = x + +/** + * Deconstruct this [Vector4]. + */ +inline operator fun Vector4.component2(): Float = y + +/** + * Deconstruct this [Vector4]. + */ +inline operator fun Vector4.component3(): Float = z + +/** + * Deconstruct this [Vector4]. + */ +inline operator fun Vector4.component4(): Float = w + +/** + * Divide vector by [Vector4] + */ +inline operator fun Vector4.divAssign(v4: Vector4) { + this.x /= v4.x + this.y /= v4.y + this.z /= v4.z + this.w /= v4.w +} + +/** + * Divide both [Vector4] values by scalar. + */ +inline operator fun Vector4.divAssign(scalar: Float) { + this.x /= scalar + this.y /= scalar + this.z /= scalar + this.w /= scalar +} + +/** + * Divide both [Vector4] values by scalar. + */ +inline operator fun Vector4.divAssign(scalar: Int) { + this.x /= scalar + this.y /= scalar + this.z /= scalar + this.w /= scalar +} + +/** + * Multiply both [Vector4] values + */ +inline operator fun Vector4.timesAssign(scalar: Int) { + this.x *= scalar.toFloat() + this.y *= scalar.toFloat() + this.z *= scalar.toFloat() + this.w *= scalar.toFloat() +} + +/** + * Multiply both [Vector4] values + */ +inline operator fun Vector4.timesAssign(scalar: Float) { + this.x *= scalar + this.y *= scalar + this.z *= scalar + this.w *= scalar +} +/** + * Multiply both [Vector4] values + */ +inline operator fun Vector4.timesAssign(v4: Vector4) { + this.x *= v4.x + this.y *= v4.y + this.z *= v4.z + this.w *= v4.w +} + +/** + * Add two [Vector4] + */ +inline fun Vector4.add(v4: Vector4) { + this.x += v4.x + this.y += v4.y + this.z += v4.z + this.w += v4.w +} + +/** + * Add two [Vector4] + */ +inline fun Vector4.plus(v4: Vector4) { + this.x += v4.x + this.y += v4.y + this.z += v4.z + this.w += v4.w +} + +/** + * Calculate linear interpolation between two [Vector4] + * @return [Vector4] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Vector4.lerp(v4: Vector4, amount: Float): Vector4 { + val result: Vector4 = kVector4() + + result.x = this.x + amount * (v4.x - this.x) + result.y = this.y + amount * (v4.y - this.y) + result.z = this.z + amount * (v4.z - this.z) + result.w = this.w + amount * (v4.w - this.w) + + return result +} + +/** + * Due to inability to override equals operator, this is the current workaround checking for equality of the values of a [Vector4] + * @return [Boolean] + */ +inline fun Vector4.equalsTo(v: Vector4): Boolean { + + return (abs(this.x - v.x) <= EPSILON * max( + 1.0f, + max(abs(this.x), abs(v.x)) + ) && abs(this.y - v.y) <= EPSILON * max( + 1.0f, + max(abs(this.y), abs(v.y)) + ) && abs(this.z - v.z) <= EPSILON * max( + 1.0f, + max(abs(this.z), abs(v.z)) + ) && abs(this.w - v.w) <= EPSILON * max( + 1.0f, + max(abs(this.w), abs(v.w)) + ) || abs(this.x + v.x) <= EPSILON * max( + 1.0f, + max(abs(this.x), abs(v.x)) + ) && abs(this.y + v.y) <= EPSILON * max( + 1.0f, + max(abs(this.y), abs(v.y)) + ) && abs(this.z + v.z) <= EPSILON * max( + 1.0f, + max(abs(this.z), abs(v.z)) + ) && abs(this.w + v.w) <= EPSILON * max(1.0f, max(abs(this.w), abs(v.w)))) +} + +/** + * Scale [Vector4] by float value + */ +inline fun Vector4.scale(mul: Float) { + this.x *= mul + this.y *= mul + this.z *= mul + this.w *= mul +} + +/** + * Add two [Vector4] + */ +inline operator fun Vector4.plusAssign(v4: Vector4) { + this.add(v4) +} + +/** + * Divide [Vector4] by [Vector4] + */ +inline operator fun Vector4.div(v4: Vector4) { + this.x /= v4.x + this.y /= v4.y + this.z /= v4.z + this.w /= v4.w +} + +/** + * Add two [Vector4] to both x, y, z and w of the [Vector4]. + */ +inline operator fun Vector4.plusAssign(add: Float) { + this.x += add + this.y += add + this.z += add + this.w += add +} + +/** + * Add two [Vector4] to both x, y, z and w of the [Vector4]. + */ +inline operator fun Vector4.plusAssign(addend: Int) { + plusAssign(addend.toFloat()) +} + +/** + * Subtract a [Vector4] by another [Vector4] + */ +inline operator fun Vector4.minusAssign(v4: Vector4) { + this.x -= v4.x + this.y -= v4.y + this.z -= v4.z + this.w -= v4.w +} + +/** + * Subtract a [Vector4] by another [Vector4] + */ +inline operator fun Vector4.minus(v4: Vector4) { + this.x -= v4.x + this.y -= v4.y + this.z -= v4.z + this.w -= v4.w +} + +/** + * Value will be subtracted from both x, y, z and w of the [Vector4]. + */ +inline operator fun Vector4.minusAssign(value: Float) { + this.x -= value + this.y -= value + this.z -= value + this.w -= value +} + +/** + * Value will be subtracted from both x, y, z and w of the [Vector4]. + */ +inline operator fun Vector4.minusAssign(value: Int) { + minusAssign(value.toFloat()) +} + +/** + * Calculate two [Vector4] multiplication + * @return [Vector4] + */ +@OptIn(ExperimentalForeignApi::class) +inline operator fun Vector4.times(v4: Vector4): Vector4 { + val result: Vector4 = kVector4() + + val qax: Float = this.x + val qay: Float = this.y + val qaz: Float = this.z + val qaw: Float = this.w + val qbx: Float = v4.x + val qby: Float = v4.y + val qbz: Float = v4.z + val qbw: Float = v4.w + + result.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby + result.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz + result.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx + result.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz + + return result +} + +/** + * Value will be compared with another [Vector4] + */ +inline operator fun Vector4.compareTo(other: Vector4): Int = when { + this.y != other.y -> (this.y - other.y).toInt() + this.z != other.z -> (this.z - other.z).toInt() + this.w != other.w -> (this.w - other.w).toInt() + else -> (this.x - other.x).toInt() +} + +/** + * Due to inability to override toString function, this is the current workaround printing the values of a [Vector4] + * @return [String] values of [Vector4] + */ +inline fun Vector4.asString(): String { + return "X: ${x.toString()}\nY: ${y.toString()}\nZ: ${z.toString()}\nW: ${w.toString()}" +} + +/** + * Set value of a [Quaternion]/[Vector4] with another provided value. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Vector4.set(other: Quaternion) { + this.x = other.x + this.y = other.y + this.z = other.z + this.w = other.w +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kModels/KModels.kt b/src/commonMain/kotlin/kaylibkit/kModels/KModels.kt new file mode 100644 index 0000000..0153bf6 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kModels/KModels.kt @@ -0,0 +1,634 @@ +package kaylibkit.kModels + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kModels + +//=======================================================// +// BASIC 3D GEOMETRIC DRAWING FUNCTIONS +//=======================================================// + +/** + * Draw a line in 3D world space + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLine3D(startPos: Vector3, endPos: Vector3, color: Color) { + DrawLine3D(startPos.readValue(), endPos.readValue(), color.readValue()) +} + +/** + * Draw a point in 3D space, actually a small line + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPoint3D(position: Vector3, color: Color) { + DrawPoint3D(position.readValue(), color.readValue()) +} + +/** + * Draw a circle in 3D world space + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircle3D(center: Vector3, radius: Float, rotationAxis: Vector3, rotationAngle: Float, color: Color) { + DrawCircle3D(center.readValue(), radius, rotationAxis.readValue(), rotationAngle, color.readValue()) +} + +/** + * Draw a capsule with the center of its sphere caps at startPos and endPos + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCapsule(startPos: Vector3, endPos: Vector3, radius: Float, slices: Int, rings: Int, color: Color) { + DrawCapsule(startPos.readValue(), endPos.readValue(), radius, slices, rings, color.readValue()) +} + +/** + * Draw capsule wires with the center of its sphere caps at [startPos] and [endPos] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCapsuleWires(startPos: Vector3, endPos: Vector3, radius: Float, slices: Int, rings: Int, color: Color) { + DrawCapsuleWires(startPos.readValue(), endPos.readValue(), radius, slices, rings, color.readValue()) +} + +/** + * Draw a [color]-filled triangle (vertex in counter-clockwise order!) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTriangle3D(v1: Vector3, v2: Vector3, v3: Vector3, color: Color) { + DrawTriangle3D(v1.readValue(), v2.readValue(), v3.readValue(), color.readValue()) +} + +/** + * Draw a triangle strip defined by [points] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTriangleStrip3D(points: Vector3, pointCount: Int, color: Color) { + DrawTriangleStrip3D(points.ptr, pointCount, color.readValue()) +} + +/** + * Draw cube + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCube(position: Vector3, width: Float, height: Float, length: Float, color: Color) { + DrawCube(position.readValue(), width, height, length, color.readValue()) +} + +/** + * Draw cube ([Vector3] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCubeV(position: Vector3, size: Vector3, color: Color) { + DrawCubeV(position.readValue(), size.readValue(), color.readValue()) +} + +/** + * Draw cube wires + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCubeWires(position: Vector3, width: Float, height: Float, length: Float, color: Color) { + DrawCubeWires(position.readValue(), width, height, length, color.readValue()) +} + +/** + * Draw cube wires ([Vector3] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCubeWiresV(position: Vector3, size: Vector3, color: Color) { + DrawCubeWiresV(position.readValue(), size.readValue(), color.readValue()) +} + +/** + * Draw sphere + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawSphere(centerPos: Vector3, radius: Float, color: Color) { + DrawSphere(centerPos.readValue(), radius, color.readValue()) +} + +/** + * Draw sphere with extended parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawSphereEx(centerPos: Vector3, radius: Float, rings: Int, slices: Int, color: Color) { + DrawSphereEx(centerPos.readValue(), radius, rings, slices, color.readValue()) +} + +/** + * Draw sphere wires + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawSphereWires(centerPos: Vector3, radius: Float, rings: Int, slices: Int, color: Color) { + DrawSphereWires(centerPos.readValue(), radius, rings, slices, color.readValue()) +} + +/** + * Draw a cylinder/cone + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCylinder(position: Vector3, radiusTop: Float, radiusBottom: Float, height: Float, slices: Int, color: Color) { + DrawCylinder(position.readValue(), radiusTop, radiusBottom, height, slices, color.readValue()) +} + +/** + * Draw a cylinder with base at [startPos] and top at [endPos] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCylinderEx(startPos: Vector3, endPos: Vector3, startRadius: Float, endRadius: Float, sides: Int, color: Color) { + DrawCylinderEx(startPos.readValue(), endPos.readValue(), startRadius, endRadius, sides, color.readValue()) +} + +/** + * Draw a cylinder/cone wires + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCylinderWires(position: Vector3, radiusTop: Float, radiusBottom: Float, height: Float, slices: Int, color: Color) { + DrawCylinderWires(position.readValue(), radiusTop, radiusBottom, height, slices, color.readValue()) +} + +/** + * Draw a cylinder wires with base at [startPos] and top at [endPos] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCylinderWiresEx(startPos: Vector3, endPos: Vector3, startRadius: Float, endRadius: Float, sides: Int, color: Color) { + DrawCylinderWiresEx(startPos.readValue(), endPos.readValue(), startRadius, endRadius, sides, color.readValue()) +} + +/** + * Draw a plane XZ + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPlane(centerPos: Vector3, size: Vector2, color: Color) { + DrawPlane(centerPos.readValue(), size.readValue(), color.readValue()) +} + +/** + * Draw a [ray] line + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRay(ray: Ray, color: Color) { + DrawRay(ray.readValue(), color.readValue()) +} + +/** + * Draw a grid (centered at (0, 0, 0)) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawGrid(slices: Int, spacing: Float) { + DrawGrid(slices, spacing) +} + +//=======================================================// +// 3D MODEL LOADING FUNCTIONS +//=======================================================// + +/** + * Load model from files (meshes and materials) + * @return [Model] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadModel(fileName: String) : Model { + return LoadModel(fileName).getPointer(MemScope()).pointed +} + +/** + * Load model from generated mesh (default material) + * @return [Model] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadModelFromMesh(mesh: Mesh) : Model { + return LoadModelFromMesh(mesh.readValue()).getPointer(MemScope()).pointed +} + +/** + * Unload [model] (including meshes) from memory (RAM and/or VRAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadModel(model: Model) { + UnloadModel(model.readValue()) +} + +/** + * Compute [model] bounding box limits (considers all meshes) + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getModelBoundingBox(model: Model) : BoundingBox { + return GetModelBoundingBox(model.readValue()).getPointer(MemScope()).pointed +} + +//=======================================================// +// 3D MODEL DRAWING FUNCTIONS +//=======================================================// + +/** + * Draw a [model] (with texture if set) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawModel(model: Model , position: Vector3, scale: Float, tint: Color) { + DrawModel(model.readValue(), position.readValue(), scale, tint.readValue()) +} + +/** + * Draw a [model] with extended parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawModelEx(model: Model, position: Vector3, rotationAxis: Vector3, rotationAngle: Float, scale: Vector3, tint: Color) { + DrawModelEx(model.readValue(), position.readValue(), rotationAxis.readValue(), rotationAngle, scale.readValue(), tint.readValue()) +} + +/** + * Draw a [model] wires (with texture if set) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawModelWires(model: Model, position: Vector3, scale: Float, tint: Color) { + DrawModelWires(model.readValue(), position.readValue(), scale, tint.readValue()) +} + +/** + * Draw a [model] wires (with texture if set) with extended parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawModelWiresEx(model: Model, position: Vector3, rotationAxis: Vector3, rotationAngle: Float, scale: Vector3, tint: Color) { + DrawModelWiresEx(model.readValue(), position.readValue(), rotationAxis.readValue(), rotationAngle, scale.readValue(), tint.readValue()) +} + +/** + * Draw bounding [box] (wires) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawBoundingBox(box: BoundingBox, color: Color) { + DrawBoundingBox(box.readValue(), color.readValue()) +} + +/** + * Draw a billboard [texture] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawBillboard(camera: Camera, texture: Texture2D, position: Vector3, size: Float, tint: Color) { + DrawBillboard(camera.readValue(), texture.readValue(), position.readValue(), size, tint.readValue()) +} + +/** + * Draw a billboard [texture] defined by [source] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawBillboardRec(camera: Camera, texture: Texture2D, source: Rectangle, position: Vector3, size: Vector2, tint: Color) { + DrawBillboardRec(camera.readValue(), texture.readValue(), source.readValue(), position.readValue(), size.readValue(), tint.readValue()) +} + +/** + * Draw a billboard [texture] defined by [source] and [rotation] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawBillboardPro(camera: Camera, texture: Texture2D, source: Rectangle, position: Vector3, up: Vector3, size: Vector2, origin: Vector2, rotation: Float, tint: Color) { + DrawBillboardPro(camera.readValue(), texture.readValue(), source.readValue(), position.readValue(), up.readValue(), size.readValue(), origin.readValue(), rotation, tint.readValue()) +} + +//=======================================================// +// MESH MANAGEMENT FUNCTIONS +//=======================================================// + +/** + * Upload [mesh] vertex data in GPU and provide VAO/VBO ids + */ +@OptIn(ExperimentalForeignApi::class) +inline fun uploadMesh(mesh: Mesh, dynamic: Boolean) { + UploadMesh(mesh.ptr, dynamic) +} + +/** + * Update [mesh] vertex data in GPU for a specific buffer index + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateMeshBuffer(mesh: Mesh, index: Int, data: COpaquePointer, dataSize: Int, offset: Int) { + UpdateMeshBuffer(mesh.readValue(), index, data, dataSize, offset) +} + +/** + * Unload [mesh] data from CPU and GPU + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadMesh(mesh: Mesh) { + UnloadMesh(mesh.readValue()) +} + +/** + * Draw a 3d [mesh] with [material] and [transform] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawMesh(mesh: Mesh, material: Material, transform: Matrix) { + DrawMesh(mesh.readValue(), material.readValue(), transform.readValue()) +} + +/** + * Draw multiple [mesh] instances with [material] and different [transforms] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawMeshInstanced(mesh: Mesh, material: Material, transforms: Matrix, instances: Int) { + DrawMeshInstanced(mesh.readValue(), material.readValue(), transforms.ptr, instances) +} + +/** + * Export mesh data to file, returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun exportMesh(mesh: Mesh, fileName: String) : Boolean { + return ExportMesh(mesh.readValue(), fileName) +} + +/** + * Compute mesh bounding box limits + * @return [BoundingBox] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getMeshBoundingBox(mesh: Mesh) : BoundingBox { + return GetMeshBoundingBox(mesh.readValue()).getPointer(MemScope()).pointed +} + +/** + * Compute mesh tangents + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshTangents(mesh: Mesh) { + GenMeshTangents(mesh.readValue()) +} + +//=======================================================// +// MESH GENERATION FUNCTIONS +//=======================================================// + +/** + * Generate polygonal mesh + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshPoly(sides: Int, radius: Float) : Mesh { + return GenMeshPoly(sides, radius).getPointer(MemScope()).pointed +} + +/** + * Generate plane mesh (with subdivisions) + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshPlane(width: Float, length: Float, resX: Int, resZ: Int) : Mesh { + return GenMeshPlane(width, length, resX, resZ).getPointer(MemScope()).pointed +} + +/** + * Generate cuboid mesh + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshCube(width: Float, height: Float, length: Float) : Mesh { + return GenMeshCube(width, height, length).getPointer(MemScope()).pointed +} + +/** + * Generate sphere mesh (standard sphere) + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshSphere(radius: Float, rings: Int, slices: Int) : Mesh { + return GenMeshSphere(radius, rings, slices).getPointer(MemScope()).pointed +} + +/** + * Generate half-sphere mesh (no bottom cap) + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshHemiSphere(radius: Float, rings: Int, slices: Int) : Mesh { + return GenMeshHemiSphere(radius, rings, slices).getPointer(MemScope()).pointed +} + +/** + * Generate cylinder mesh + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshCylinder(radius: Float, height: Float, slices: Int) : Mesh { + return GenMeshCylinder(radius, height, slices).getPointer(MemScope()).pointed +} + +/** + * Generate cone/pyramid mesh + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshCone(radius: Float, height: Float, slices: Int) : Mesh { + return GenMeshCone(radius, height, slices).getPointer(MemScope()).pointed +} + +/** + * Generate torus mesh + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshTorus(radius: Float, size: Float, radSeg: Int, sides: Int) : Mesh { + return GenMeshTorus(radius, size, radSeg, sides).getPointer(MemScope()).pointed +} + +/** + * Generate trefoil knot mesh + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshKnot(radius: Float, size: Float, radSeg: Int, sides: Int) : Mesh { + return GenMeshKnot(radius, size, radSeg, sides).getPointer(MemScope()).pointed +} + +/** + * Generate heightmap mesh from image data + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshHeightmap(heightmap: Image, size: Vector3) : Mesh { + return GenMeshHeightmap(heightmap.readValue(), size.readValue()).getPointer(MemScope()).pointed +} + +/** + * Generate cubes-based map mesh from image data + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genMeshCubicmap(cubicmap: Image, cubeSize: Vector3) : Mesh { + return GenMeshCubicmap(cubicmap.readValue(), cubeSize.readValue()).getPointer(MemScope()).pointed +} + +//=======================================================// +// MATERIAL LOADING/UNLOADING FUNCTIONS +//=======================================================// + +/** + * Load materials from model file + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadMaterials(fileName: String, materialCount: IntVar) : Material? { + return LoadMaterials(fileName, materialCount.ptr)?.getPointer(MemScope())?.pointed +} + +/** + * Load default [Material] (Supports: DIFFUSE, SPECULAR, NORMAL maps) + * @return [Material] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadMaterialDefault() : Material { + return LoadMaterialDefault().getPointer(MemScope()).pointed +} + +/** + * Unload [material] from GPU memory (VRAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadMaterial(material: Material) { + UnloadMaterial(material.readValue()) +} + +/** + * Set [texture] for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setMaterialTexture(material: Material, mapType: kaylibkit.kEnums.MaterialMapIndex, texture: Texture2D) { + SetMaterialTexture(material.ptr, mapType.value, texture.readValue()) +} + +/** + * Set [Material] for a [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setModelMeshMaterial(model: Model, meshId: Int, materialId: Int) { + SetModelMeshMaterial(model.ptr, meshId, materialId) +} + +//=======================================================// +// MODEL ANIMATION LOADING/UNLOADING FUNCTIONS +//=======================================================// + +/** + * Load model animations from file + * @return [ModelAnimation] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadModelAnimations(fileName: String, animCount: UIntVar) : ModelAnimation? { + return LoadModelAnimations(fileName, animCount.ptr)?.getPointer(MemScope())?.pointed +} + +/** + * Update model animation pose + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateModelAnimation(model: Model, anim: ModelAnimation, frame: Int) { + UpdateModelAnimation(model.readValue(), anim.readValue(), frame) +} + +/** + * Unload animation data + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadModelAnimation(anim: ModelAnimation) { + UnloadModelAnimation(anim.readValue()) +} + +/** + * Unload animation array data + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadModelAnimations(animations: ModelAnimation) { + UnloadModelAnimation(animations.readValue()) +} + +/** + * Check model animation skeleton match + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isModelAnimationValid(model: Model, anim: ModelAnimation) : Boolean { + return IsModelAnimationValid(model.readValue(), anim.readValue()) +} + +//=======================================================// +// COLLISION DETECTION FUNCTIONS +//=======================================================// + +/** + * Check collision between two spheres + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollisionSpheres(center1: Vector3, radius1: Float, center2: Vector3, radius2: Float) : Boolean { + return CheckCollisionSpheres(center1.readValue(), radius1, center2.readValue(), radius2) +} + +/** + * Check collision between two bounding boxes + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollisionBoxes(box1: BoundingBox, box2: BoundingBox) : Boolean { + return CheckCollisionBoxes(box1.readValue(), box2.readValue()) +} + +/** + * Check collision between [box] and sphere + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollisionBoxSphere(box: BoundingBox, center: Vector3, radius: Float) : Boolean { + return CheckCollisionBoxSphere(box.readValue(), center.readValue(), radius) +} + +/** + * Get collision info between [ray] and sphere + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getRayCollisionSphere(ray: Ray, center: Vector3, radius: Float) : RayCollision { + return GetRayCollisionSphere(ray.readValue(), center.readValue(), radius).getPointer(MemScope()).pointed +} + +/** + * Get collision info between [ray] and box ([BoundingBox]) + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getRayCollisionBox(ray: Ray, box: BoundingBox) : RayCollision { + return GetRayCollisionBox(ray.readValue(), box.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get collision info between [ray] and [mesh] + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getRayCollisionMesh(ray: Ray, mesh: Mesh, transform: Matrix) : RayCollision { + return GetRayCollisionMesh(ray.readValue(), mesh.readValue(), transform.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get collision info between [ray] and triangle + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getRayCollisionTriangle(ray: Ray, p1: Vector3, p2: Vector3, p3: Vector3) : RayCollision { + return GetRayCollisionTriangle(ray.readValue(), p1.readValue(), p2.readValue(), p3.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get collision info between [ray] and quad + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getRayCollisionQuad(ray: Ray, p1: Vector3, p2: Vector3, p3: Vector3, p4: Vector3) : RayCollision { + return GetRayCollisionQuad(ray.readValue(), p1.readValue(), p2.readValue(), p3.readValue(), p4.readValue()).getPointer(MemScope()).pointed +} + +/** + * Check if a model is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isModelReady(model: Model) : Boolean { + return IsModelReady(model.readValue()) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kShapes/KRectangle.kt b/src/commonMain/kotlin/kaylibkit/kShapes/KRectangle.kt new file mode 100644 index 0000000..d3d9d18 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kShapes/KRectangle.kt @@ -0,0 +1,51 @@ +package kaylibkit.kShapes + +import kaylibkit.kMath.kVector2 +import kaylibc.Rectangle +import kotlinx.cinterop.AutofreeScope +import kotlinx.cinterop.MemScope +import kotlinx.cinterop.alloc +import kaylibc.Vector2 +import kotlinx.cinterop.ExperimentalForeignApi + +/** + * Constructor function for Rectangle using X and Y floats for position. + * @param [allocator] Uses `MemScope()` by default. + * @return [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kRectangle(x: Float = 0F, y: Float = 0F, width: Float = 0F, height: Float = 0F, allocator: AutofreeScope = MemScope()) : Rectangle { + return allocator.alloc { + this.x = x + this.y = y + this.width = width + this.height = height + } +} + +/** + * Constructor function for Rectangle that take a kVector2 for position. + * @param [allocator] Uses `MemScope()` by default. + * @return [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kRectangle(vector2: Vector2 = kVector2(0F, 0F), width: Float = 0F, height: Float = 0F, allocator: AutofreeScope = MemScope()) : Rectangle { + return allocator.alloc { + this.x = vector2.x + this.y = vector2.y + this.width = width + this.height = height + } +} + +/** + * Set value of a Rectangle with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Rectangle.set(other: Rectangle) { + this.x = other.x + this.y = other.y + this.width = other.width + this.height = other.height +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kShapes/KShapes.kt b/src/commonMain/kotlin/kaylibkit/kShapes/KShapes.kt new file mode 100644 index 0000000..0bc0c33 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kShapes/KShapes.kt @@ -0,0 +1,418 @@ +package kaylibkit.kShapes + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kShapes + +//=======================================================// +// BASIC SHAPES DRAWING FUNCTIONS +//=======================================================// + +/** + * Set texture and [Rectangle] to be used on shapes drawing + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setShapesTexture(texture: Texture2D, source: Rectangle) { + SetShapesTexture(texture.readValue(), source.readValue()) +} + +/** + * Draw a pixel + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPixel(posX: Int, posY: Int, color: Color) { + DrawPixel(posX, posY, color.readValue()) +} + +/** + * Draw a pixel ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPixel(position: Vector2, color: Color) { + DrawPixelV(position.readValue(), color.readValue()) +} + +/** + * Draw a line + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLine(startPosX: Int, startPosY: Int, endPosX: Int, endPosY: Int, color: Color) { + DrawLine(startPosX, startPosY, endPosX, endPosY, color.readValue()) +} + +/** + * Draw a line ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLine(startPos: Vector2, endPos: Vector2, color: Color) { + DrawLineV(startPos.readValue(), endPos.readValue(), color.readValue()) +} + +/** + * Draw a line defining thickness + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLine(startPos: Vector2, endPos: Vector2, thick: Float, color: Color) { + DrawLineEx(startPos.readValue(), endPos.readValue(), thick, color.readValue()) +} + +/** + * Draw a line using cubic-bezier curves in-out + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLineBezier(startPos: Vector2, endPos: Vector2, thick: Float, color: Color) { + DrawLineBezier(startPos.readValue(), endPos.readValue(), thick, color.readValue()) +} + +/** + * Draw line using quadratic bezier curves with a control point + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLineBezierQuad(startPos: Vector2, endPos: Vector2, controlPos: Vector2, thick: Float, color: Color) { + DrawLineBezierQuad(startPos.readValue(), endPos.readValue(), controlPos.readValue(), thick, color.readValue()) +} + +/** + * Draw line using cubic bezier curves with 2 control points + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLineBezierCubic(startPos: Vector2, endPos: Vector2, startControlPos: Vector2, endControlPos: Vector2, thick: Float, color: Color) { + DrawLineBezierCubic(startPos.readValue(), endPos.readValue(), startControlPos.readValue(), endControlPos.readValue(), thick, color.readValue()) +} + +/** + * Draw lines sequence + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawLine(points: Vector2, pointsCount: Int, color: Color) { + DrawLineStrip(points.ptr, pointsCount, color.readValue()) +} + +/** + * Draw a [color]-filled circle + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircle(centerX: Int, centerY: Int, radius: Float, color: Color) { + DrawCircle(centerX, centerY, radius, color.readValue()) +} + +/** + * Draw a piece of a circle + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircle(center: Vector2, radius: Float, startAngle: Float, endAngle: Float, segments: Int, color: Color) { + DrawCircleSector(center.readValue(), radius, startAngle, endAngle, segments, color.readValue()) +} + +/** + * Draw circle sector outline + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircleSectorLines(center: Vector2, radius: Float, startAngle: Float, endAngle: Float, segments: Int, color: Color) { + DrawCircleSectorLines(center.readValue(), radius, startAngle, endAngle, segments, color.readValue()) +} + +/** + * Draw a gradient-filled circle + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircle(centerX: Int, centerY: Int, radius: Float, color1: Color, color2: Color) { + DrawCircleGradient(centerX, centerY, radius, color1.readValue(), color2.readValue()) +} + +/** + * Draw a [color]-filled circle ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircle(center: Vector2, radius: Float, color: Color) { + DrawCircleV(center.readValue(), radius, color.readValue()) +} + +/** + * Draw circle outline + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawCircleLines(centerX: Int, centerY: Int, radius: Float, color: Color) { + DrawCircleLines(centerX, centerY, radius, color.readValue()) +} + +/** + * Draw ellipse + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawEllipse(centerX: Int, centerY: Int, radiusH: Float, radiusV: Float, color: Color) { + DrawEllipse(centerX, centerY, radiusH, radiusV, color.readValue()) +} + +/** + * Draw ellipse outline + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawEllipseLines(centerX: Int, centerY: Int, radiusH: Float, radiusV: Float, color: Color) { + DrawEllipseLines(centerX, centerY, radiusH, radiusV, color.readValue()) +} + +/** + * Draw ring + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRing(center: Vector2, innerRadius: Float, outerRadius: Float, startAngle: Float, endAngle: Float, segments: Int, color: Color) { + DrawRing(center.readValue(), innerRadius, outerRadius, startAngle, endAngle, segments, color.readValue()) +} + +/** + * Draw ring outline + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRingLines(center: Vector2, innerRadius: Float, outerRadius: Float, startAngle: Float, endAngle: Float, segments: Int, color: Color) { + DrawRingLines(center.readValue(), innerRadius, outerRadius, startAngle, endAngle, segments, color.readValue()) +} + +/** + * Draw a [color]-filled [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(posX: Int, posY: Int, width: Int, height: Int, color: Color) { + DrawRectangle(posX, posY, width, height, color.readValue()) +} + +/** + * Draw a [color]-filled [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(rec: Rectangle, color: Color) { + DrawRectangleRec(rec.readValue(), color.readValue()) +} + +/** + * Draw a [color]-filled [Rectangle] with pro parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(rec: Rectangle, origin: Vector2, rotation: Float, color: Color) { + DrawRectanglePro(rec.readValue(), origin.readValue(), rotation, color.readValue()) +} + +/** + * Draw a [color]-filled [Rectangle] ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(position: Vector2, size: Vector2, color: Color) { + DrawRectangleV(position.readValue(), size.readValue(), color.readValue()) +} + +/** + * Draw a vertical-gradient-filled [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangleGradientV(posX: Int, posY: Int, width: Int, height: Int, color1: Color, color2: Color) { + DrawRectangleGradientV(posX, posY, width, height, color1.readValue(), color2.readValue()) +} + +/** + * Draw a horizontal-gradient-filled [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangleGradientH(posX: Int, posY: Int, width: Int, height: Int, color1: Color, color2: Color) { + DrawRectangleGradientH(posX, posY, width, height, color1.readValue(), color2.readValue()) +} + +/** + * Draw a gradient-filled rectangle with custom vertex colors + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(rec: Rectangle, col1: Color, col2: Color, col3: Color, col4: Color) { + DrawRectangleGradientEx(rec.readValue(), col1.readValue(), col2.readValue(), col3.readValue(), col4.readValue()) +} + +/** + * Draw [Rectangle] outline + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangleLines(posX: Int, posY: Int, width: Int, height: Int, color: Color) { + DrawRectangleLines(posX, posY, width, height, color.readValue()) +} + +/** + * Draw [Rectangle] outline with extended parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangleLinesEx(rec: Rectangle, lineThick: Float, color: Color) { + DrawRectangleLinesEx(rec.readValue(), lineThick, color.readValue()) +} + +/** + * Draw [Rectangle] with rounded edges + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(rec: Rectangle, roundness: Float, segments: Int, color: Color) { + DrawRectangleRounded(rec.readValue(), roundness, segments, color.readValue()) +} + +/** + * Draw [Rectangle] with rounded edges outline + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawRectangle(rec: Rectangle, roundness: Float, segments: Int, lineThick: Float, color: Color) { + DrawRectangleRoundedLines(rec.readValue(), roundness, segments, lineThick, color.readValue()) +} + +/** + * Draw a [color]-filled triangle (vertex in counter-clockwise order!) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTriangle(v1: Vector2, v2: Vector2, v3: Vector2, color: Color) { + DrawTriangle(v1.readValue(), v2.readValue(), v3.readValue(), color.readValue()) +} + +/** + * Draw triangle outline (vertex in counter-clockwise order!) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTriangleLines(v1: Vector2, v2: Vector2, v3: Vector2, color: Color) { + DrawTriangleLines(v1.readValue(), v2.readValue(), v3.readValue(), color.readValue()) +} + +/** + * Draw a triangle fan defined by [points] (first vertex is the center) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTriangleFan(points: Vector2, pointsCount: Int ,color: Color) { + DrawTriangleFan(points.ptr, pointsCount, color.readValue()) +} + +/** + * Draw a triangle strip defined by [points] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTriangleStrip(points: Vector2, pointCount: Int, color: Color) { + DrawTriangleStrip(points.ptr, pointCount, color.readValue()) +} + +/** + * Draw a regular polygon ([Vector2] version) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPoly(center: Vector2, sides: Int, radius: Float, rotation: Float, color: Color) { + DrawPoly(center.readValue(), sides, radius, rotation, color.readValue()) +} + +/** + * Draw a polygon outline of n [sides] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPolyLines(center: Vector2, sides: Int, radius: Float, rotation: Float, color: Color) { + DrawPolyLines(center.readValue(), sides, radius, rotation, color.readValue()) +} + +/** + * Draw a polygon outline of n [sides] with extended parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawPolyLinesEx(center: Vector2, sides: Int, radius: Float, rotation: Float, lineThick: Float, color: Color) { + DrawPolyLinesEx(center.readValue(), sides, radius, rotation, lineThick, color.readValue()) +} + +//=======================================================// +// BASIC SHAPES COLLISION DETECTION FUNCTIONS +//=======================================================// + +/** + * Check collision between two [Rectangle] + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(rec1: Rectangle, rec2: Rectangle) : Boolean { + return CheckCollisionRecs(rec1.readValue(), rec2.readValue()) +} + +/** + * Check collision between two circles + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(center1: Vector2, radius1: Float, center2: Vector2, radius2: Float) : Boolean { + return CheckCollisionCircles(center1.readValue(), radius1, center2.readValue(), radius2) +} + +/** + * Check collision between circle and [Rectangle] + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(center: Vector2, radius: Float, rec: Rectangle) : Boolean { + return CheckCollisionCircleRec(center.readValue(), radius, rec.readValue()) +} + +/** + * Check if [point] is inside [Rectangle] + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(point: Vector2, rec: Rectangle) : Boolean { + return CheckCollisionPointRec(point.readValue(), rec.readValue()) +} + +/** + * Check if [point] is inside circle + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(point: Vector2, center: Vector2, radius: Float) : Boolean { + return CheckCollisionPointCircle(point.readValue(), center.readValue(), radius) +} + +/** + * Check if [point] is inside a triangle + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(point: Vector2, p1: Vector2, p2: Vector2, p3: Vector2) : Boolean { + return CheckCollisionPointTriangle(point.readValue(), p1.readValue(), p2.readValue(), p3.readValue()) +} + +/** + * Check the collision between two lines defined by two points each, returns collision point by reference + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(startPos1: Vector2, endPos1: Vector2, startPos2: Vector2, endPos2: Vector2, collisionPoint: CValuesRef) : Boolean { + return CheckCollisionLines(startPos1.readValue(), endPos1.readValue(), startPos2.readValue(), endPos2.readValue(), collisionPoint) +} + +/** + * Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollision(point: Vector2, p1: Vector2, p2: Vector2, threshold: Int) : Boolean { + return CheckCollisionPointLine(point.readValue(), p1.readValue(), p2.readValue(), threshold) +} + +/** + * Get collision [Rectangle] for two rectangles collision + * @return [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCollision(rec1: Rectangle, rec2: Rectangle) : Rectangle { + return GetCollisionRec(rec1.readValue(), rec2.readValue()).getPointer(MemScope()).pointed +} + +/** + * Check if point is within a polygon described by array of vertices + * NOTE: Based on http://jeffreythompson.org/collision-detection/poly-point.php + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollisionPointPoly(point: Vector2, points: CValuesRef, pointCount: Int) : Boolean { + return CheckCollisionPointPoly(point.readValue(), points, pointCount) +} + +/** + * Check if point is inside rectangle + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun checkCollisionPointRec(point: Vector2, rec: Rectangle) : Boolean { + return CheckCollisionPointRec(point.readValue(), rec.readValue()) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kText/KText.kt b/src/commonMain/kotlin/kaylibkit/kText/KText.kt new file mode 100644 index 0000000..e753fd9 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kText/KText.kt @@ -0,0 +1,222 @@ +package kaylibkit.kText + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kText + +//=======================================================// +// FONT LOADING FUNCTIONS +//=======================================================// + +/** + * Get the default [Font] + * @return [Font] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getFontDefault() : Font { + return GetFontDefault().getPointer(MemScope()).pointed +} + +/** + * Load [Font] from file into GPU memory (VRAM) + * @return [Font] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFont(fileName: String) : Font { + return LoadFont(fileName).getPointer(MemScope()).pointed +} + +/** + * Load font from file with extended parameters + * @return [Font] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFont(fileName: String, fontSize: Int, fontChars: IntVar, glyphCount: Int) : Font { + return LoadFontEx(fileName, fontSize, fontChars.ptr, glyphCount).getPointer(MemScope()).pointed +} + +/** + * Load [Font] from Image (XNA style) + * @return [Font] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFontFromImage(image: Image, key: Color, firstChar: Int) : Font { + return LoadFontFromImage(image.readValue(), key.readValue(), firstChar).getPointer(MemScope()).pointed +} + +/** + * Load [Font] from memory buffer, fileType refers to extension: i.e. ".ttf" + * @return [Font] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFontFromMemory(fileType: String, fileData: UByteVar, dataSize: Int, fontSize: Int, fontChars: IntVar, glyphCount: Int) : Font { + return LoadFontFromMemory(fileType, fileData.ptr, dataSize, fontSize, fontChars.ptr, glyphCount).getPointer(MemScope()).pointed +} + +/** + * Load font data for further use + * @return [GlyphInfo] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadFontData(fileData: UByteVar, dataSize: Int, fontSize: Int, fontChars: IntVar, glyphCount: Int, type: kaylibkit.kEnums.FontType) : GlyphInfo? { + return LoadFontData(fileData.ptr, dataSize, fontSize, fontChars.ptr, glyphCount, type.value)?.getPointer(MemScope())?.pointed +} + +/** + * Generate [Image] font atlas using chars info + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageFontAtlas(chars: GlyphInfo, recs: CPointerVar, glyphCount: Int, fontSize: Int, padding: Int, packMethod: Int) : Image { + return GenImageFontAtlas(chars.ptr, recs.ptr, glyphCount, fontSize, padding, packMethod).getPointer(MemScope()).pointed +} + +/** + * Unload font chars info data (RAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadFontData(chars: GlyphInfo, glyphCount: Int) { + UnloadFontData(chars.ptr, glyphCount) +} + +/** + * Unload Font from GPU memory (VRAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadFont(font: Font) { + UnloadFont(font.readValue()) +} + +/** + * Export font as code file, returns true on success + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun exportFontAsCode(font: Font, fileName: String) : Boolean { + return ExportFontAsCode(font.readValue(), fileName) +} + +//=======================================================// +// TEXT DRAWING FUNCTIONS +//=======================================================// + +/** + * Draw current FPS + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawFPS(posX: Int, posY: Int) { + DrawFPS(posX, posY) +} + +/** + * Draw current FPS + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawText(text: String, posX: Int, posY: Int, fontSize: Int, color: Color) { + DrawText(text, posX, posY, fontSize, color.readValue()) +} + +/** + * Draw text using font and additional parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawText(font: Font, text: String, position: Vector2, fontSize: Float, spacing: Float, tint: Color) { + DrawTextEx(font.readValue(), text, position.readValue(), fontSize, spacing, tint.readValue()) +} + +/** + * Draw text using Font and pro parameters (rotation) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawText(font: Font, text: String, position: Vector2, origin: Vector2, rotation: Float, fontSize: Float, spacing: Float, tint: Color) { + DrawTextPro(font.readValue(), text, position.readValue(), origin.readValue(), rotation, fontSize, spacing, tint.readValue()) +} + +/** + * Draw one character (codepoint) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawText(font: Font, codepoint: Int, position: Vector2, fontSize: Float, tint: Color) { + DrawTextCodepoint(font.readValue(), codepoint, position.readValue(), fontSize, tint.readValue()) +} + +/** + * Draw one character (codepoint) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawText(font: Font, codepoint: IntVar, count: Int, position: Vector2, fontSize: Float, spacing: Float, tint: Color) { + DrawTextCodepoints(font.readValue(), codepoint.ptr, count, position.readValue(), fontSize, spacing, tint.readValue()) +} + +//=======================================================// +// TEXT FONT INFO FUNCTIONS +//=======================================================// + +/** + * Measure string width for default [Font] + * @return [Int] + */ +inline fun measureText(text: String, fontSize: Int) : Int { + return MeasureText(text, fontSize) +} + +/** + * Measure string size for [Font] + * @return [Vector2] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun measureText(font: Font, text: String, fontSize: Float, spacing: Float) : Vector2 { + return MeasureTextEx(font.readValue(), text, fontSize, spacing).getPointer(MemScope()).pointed +} + +/** + * Get index position for a unicode character on font + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getGlyphIndex(font: Font, codepoint: Int) : Int { + return GetGlyphIndex(font.readValue(), codepoint) +} + +/** + * Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found + * @return [GlyphInfo] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getGlyphInfo(font: Font, codepoint: Int) : GlyphInfo { + return GetGlyphInfo(font.readValue(), codepoint).getPointer(MemScope()).pointed +} + +/** + * Get glyph [Rectangle] in [font] atlas for a codepoint (unicode character), fallback to '?' if not found + * @return [Rectangle] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getGlyphAtlasRec(font: Font, codepoint: Int) : Rectangle { + return GetGlyphAtlasRec(font.readValue(), codepoint).getPointer(MemScope()).pointed +} + +/** + * Check if a font is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isFontReady(font: Font) : Boolean { + return IsFontReady(font.readValue()) +} + +//=======================================================// +// TEXT STRINGS MANAGEMENT FUNCTIONS +//=======================================================// + +// Internal Note: There is no need to bind other related text strings management functions. Use existing Kotlin API. + +/** + * Get Pascal case notation version of provided string + */ +@OptIn(ExperimentalForeignApi::class) +inline fun textToPascal(text: String) : String { + return TextToPascal(text)?.toKString() ?: "WARNING: Unable to return requested string." + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kTextures/KTextures.kt b/src/commonMain/kotlin/kaylibkit/kTextures/KTextures.kt new file mode 100644 index 0000000..363593e --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kTextures/KTextures.kt @@ -0,0 +1,235 @@ +package kaylibkit.kTextures + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kTextures + +//=======================================================// +// TEXTURE LOADING FUNCTIONS +//=======================================================// + +/** + * Load [Texture] from file into GPU memory (VRAM) + * @return [Texture2D] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadTexture(fileName: String) : Texture2D { + return LoadTexture(fileName).getPointer(MemScope()).pointed +} + +/** + * Load [Texture] from image data + * @return [Texture2D] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadTextureFromImage(image: Image) : Texture2D { + return LoadTextureFromImage(image.readValue()).getPointer(MemScope()).pointed +} + +/** + * Load cubemap from image, multiple image cubemap layouts supported + * @return [TextureCubemap] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadTextureCubemap(image: Image, layout: CubemapLayout) : TextureCubemap { + return LoadTextureCubemap(image.readValue(), layout.toInt()).getPointer(MemScope()).pointed +} + +/** + * Load [RenderTexture2D] for rendering (framebuffer) + * @return [RenderTexture2D] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadRenderTexture(width: Int, height: Int) : RenderTexture2D { + return LoadRenderTexture(width, height).getPointer(MemScope()).pointed +} + +/** + * Unload [Texture] from GPU memory (VRAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadTexture(texture: Texture2D) { + return UnloadTexture(texture.readValue()) +} + +/** + * Unload render [RenderTexture2D] from GPU memory (VRAM) + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadRenderTexture(target: RenderTexture2D) { + return UnloadRenderTexture(target.readValue()) +} + +/** + * Update GPU [Texture] with new data + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateTexture(texture: Texture2D, pixels: COpaquePointer) { + UpdateTexture(texture.readValue(), pixels) +} + +/** + * Update GPU [Texture] rectangle with new data + */ +@OptIn(ExperimentalForeignApi::class) +inline fun updateTextureRec(texture: Texture2D, rec: Rectangle, pixels: COpaquePointer) { + UpdateTextureRec(texture.readValue(), rec.readValue(), pixels) +} + +//=======================================================// +// TEXTURE CONFIGURATION FUNCTIONS +//=======================================================// + +/** + * Generate GPU mipmaps for a [Texture] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genTextureMipmaps(texture: Texture2D) { + GenTextureMipmaps(texture.ptr) +} + +/** + * Set [Texture] scaling filter mode + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setTextureFilter(texture: Texture2D, filter: kaylibkit.kEnums.TextureFilter) { + SetTextureFilter(texture.readValue(), filter.value) +} + +/** + * Set [Texture] wrapping mode + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setTextureWrap(texture: Texture2D, wrap: kaylibkit.kEnums.TextureWrap) { + SetTextureWrap(texture.readValue(), wrap.value) +} + +//=======================================================// +// TEXTURE DRAWING FUNCTIONS +//=======================================================// + +/** + * Draw a [Texture2D] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTexture(texture: Texture2D, posX: Int, posY: Int, tint: Color) { + return DrawTexture(texture.readValue(), posX, posY, tint.readValue()) +} + +/** + * Draw a [Texture2D] with position defined as Vector2 + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTextureV(texture: Texture2D, position: Vector2, tint: Color) { + DrawTextureV(texture.readValue(), position.readValue(), tint.readValue()) +} + +/** + * Draw a [Texture2D] with extended parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTextureEx(texture: Texture2D, position: Vector2, rotation: Float, scale: Float, tint: Color) { + DrawTextureEx(texture.readValue(), position.readValue(), rotation, scale, tint.readValue()) +} + +/** + * Draw a part of a [Texture] defined by a rectangle + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTextureRec(texture: Texture2D, source: Rectangle, position: Vector2, tint: Color) { + DrawTextureRec(texture.readValue(), source.readValue(), position.readValue(), tint.readValue()) +} + +/** + * Draw a part of a [Texture] defined by a rectangle with 'pro' parameters + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTexturePro(texture: Texture2D, source: Rectangle, dest: Rectangle, origin: Vector2, rotation: Float, tint: Color) { + DrawTexturePro(texture.readValue(), source.readValue(), dest.readValue(), origin.readValue(), rotation, tint.readValue()) +} + +/** + * Draws a [Texture] (or part of it) that stretches or shrinks nicely + */ +@OptIn(ExperimentalForeignApi::class) +inline fun drawTextureNPatch(texture: Texture2D, nPatchInfo: NPatchInfo, dest: Rectangle, origin: Vector2, rotation: Float, tint: Color) { + DrawTextureNPatch(texture.readValue(), nPatchInfo.readValue(), dest.readValue(), origin.readValue(), rotation, tint.readValue()) +} + +/** + * Get [Color] with brightness correction, brightness factor goes from -1.0f to 1.0f + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorBrightness(color: Color, factor: Float) : Color { + return ColorBrightness(color.readValue(), factor).getPointer(MemScope()).pointed +} + +/** + * Get color multiplied with another [Color] + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorTint(color: Color, tint: Color) : Color { + return ColorTint(color.readValue(), tint.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get [Color] with contrast correction + * NOTE: Contrast values between -1.0f and 1.0f + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorContrast(color: Color, contrast: Float) : Color { + return ColorContrast(color.readValue(), contrast).getPointer(MemScope()).pointed +} + +/** + * Generate image: perlin noise + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImagePerlinNoise(width: Int, height: Int, offsetX: Int, offsetY: Int, scale: Float) : Image { + return GenImagePerlinNoise(width, height, offsetX, offsetY, scale).getPointer(MemScope()).pointed +} + +/** + * Generate image: grayscale image from text data + * @return [Image] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun genImageText(width: Int, height: Int, text: String) : Image { + return GenImageText(width, height, text).getPointer(MemScope()).pointed +} + +/** + * Check if a [Texture] is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isTextureReady(texture: Texture2D) : Boolean { + return IsTextureReady(texture.readValue()) +} + +/** + * Check if a [RenderTexture] is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isRenderTextureReady(target: RenderTexture2D) : Boolean { + return IsRenderTextureReady(target.readValue()) +} + +/** + * Set value of a Texture with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Texture.set(other: Texture) { + this.format = other.format + this.height = other.height + this.id = other.id + this.mipmaps = other.mipmaps + this.width = other.width +} \ No newline at end of file diff --git a/src/commonMain/kotlin/kaylibkit/kTypes/KTypes.kt b/src/commonMain/kotlin/kaylibkit/kTypes/KTypes.kt new file mode 100644 index 0000000..714c681 --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kTypes/KTypes.kt @@ -0,0 +1,440 @@ +package kaylibkit.kTypes + +import kaylibkit.kMath.kVector2 +import kaylibkit.kMath.kVector3 +import kaylibkit.kMath.set +import kaylibkit.kShapes.set +import kaylibkit.kImage.set +import kaylibkit.kTextures.set +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kTypes + +//=======================================================// +// Type Constructors +//=======================================================// + + +/** + * Constructor function for [Color]. + * Important to note that this uses `MemScope()` by default. + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kColor(r: UByte = 0U, g: UByte = 0U, b: UByte = 0U, a: UByte = 0U, allocator: AutofreeScope = MemScope()) : Color { + return allocator.alloc { + this.r = r + this.g = g + this.b = b + this.a = a + } +} + +/** + * Set value of a [Color] with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +inline fun Color.set(other: Color) { + this.r = other.r + this.g = other.g + this.b = other.b + this.a = other.a +} + +/** + * Constructor function for [Texture]. + * Important to note that this uses `MemScope()` by default. + * @return [Texture] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kTexture(id: UInt, width:Int, height: Int, mipmaps: Int, format: Int, allocator: AutofreeScope = MemScope()) : Texture { + return allocator.alloc { + this.id = id + this.width = width + this.height = height + this.mipmaps = mipmaps + this.format = format + } +} + +/** + * Constructor function for RenderTexture]. + * Important to note that this uses `MemScope()` by default. + * @return [RenderTexture] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kRenderTexture(id: UInt, texture: Texture, depth: Texture, allocator: AutofreeScope = MemScope()) : RenderTexture { + return allocator.alloc { + this.id = id + this.texture.set(texture) + this.depth.set(depth) + } +} + +/** + * Constructor function for [NPatchInfo]. + * Important to note that this uses `MemScope()` by default. + * @return [NPatchInfo] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kNPatchInfo(source: Rectangle, left: Int, top: Int, right: Int, bottom: Int, layout: Int, allocator: AutofreeScope = MemScope()) : NPatchInfo { + return allocator.alloc { + this.source.set(source) + this.left = left + this.top = top + this.right = right + this.bottom = bottom + this.layout = layout + } +} + +/** + * Constructor function for [GlyphInfo]. + * Important to note that this uses `MemScope()` by default. + * @return [GlyphInfo] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kGlyphInfo(value: Int, offsetX: Int, offsetY: Int, advanceX: Int, image: Image, allocator: AutofreeScope = MemScope()) : GlyphInfo { + return allocator.alloc { + this.value = value + this.offsetX = offsetX + this.offsetY = offsetY + this.advanceX = advanceX + this.image.set(image) + + } +} + +/** + * Constructor function for [Font]. + * Important to note that this uses `MemScope()` by default. + * @return [Font] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kFont(baseSize: Int, glyphCount: Int, glyphPadding: Int, texture: Texture, recs: Rectangle, glyphs: GlyphInfo, allocator: AutofreeScope = MemScope()) : Font { + return allocator.alloc { + this.baseSize = baseSize + this.glyphCount = glyphCount + this.glyphPadding = glyphPadding + this.texture.set(texture) + this.recs = recs.ptr + this.glyphs = glyphs.ptr + } +} + +/** + * Constructor function for [Camera3D]. + * Important to note that this uses `MemScope()` by default. + * @return [Camera3D] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kCamera3D(position: Vector3 = kVector3(), target: Vector3 = kVector3(), up: Vector3 = kVector3(), fovy: Float = 0F, projection: kaylibkit.kEnums.CameraProjection, allocator: AutofreeScope = MemScope()) : Camera3D { + return allocator.alloc { + this.position.set(position) + this.target.set(target) + this.up.set(up) + this.fovy = fovy + this.projection = projection.value + } +} + +/** + * Constructor function for [Camera2D]. + * Important to note that this uses `MemScope()` by default. + * @return [Camera2D] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kCamera2D(offset: Vector2 = kVector2(), target: Vector2 = kVector2(), rotation: Float = 0F, zoom: Float = 0F, allocator: AutofreeScope = MemScope()) : Camera2D { + return allocator.alloc { + this.offset.set(offset) + this.target.set(target) + this.rotation = rotation + this.zoom = zoom + } +} + +/** + * Constructor function for [Mesh]. + * Important to note that this uses `MemScope()` by default. + * @return [Mesh] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kMesh(vertexCount: Int, triangleCount: Int, vertices: FloatVar, texcoords: FloatVar, texcoords2: FloatVar, normals: FloatVar, tangents: FloatVar, colors: UByteVar, indices: UShortVar, animVertices: FloatVar, animNormals: FloatVar, boneIds: UByteVar, boneWeights: FloatVar, vaoId: UInt, vboId: UIntVar , allocator: AutofreeScope = MemScope()) : Mesh { + return allocator.alloc { + this.vertexCount = vertexCount + this.triangleCount = triangleCount + this.vertices = vertices.ptr + this.texcoords = texcoords.ptr + this.texcoords2 = texcoords2.ptr + this.normals = normals.ptr + this.tangents = tangents.ptr + this.colors = colors.ptr + this.indices = indices.ptr + this.animVertices = animVertices.ptr + this.animNormals = animNormals.ptr + this.boneIds = boneIds.ptr + this.boneWeights = boneWeights.ptr + this.vaoId = vaoId + this.vboId = vboId.ptr + } +} + +/** + * Set value of a Color with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Mesh.set(other: Mesh) { + this.vertexCount = other.vertexCount + this.triangleCount = other.triangleCount + this.vertices = other.vertices + this.texcoords = other.texcoords + this.texcoords2 = other.texcoords2 + this.normals = other.normals + this.tangents = other.tangents + this.colors = other.colors + this.indices = other.indices + this.animNormals = other.animNormals + this.animVertices = other.animVertices + this.boneIds = other.boneIds + this.boneWeights = other.boneWeights + this.vaoId = other.vaoId + this.vboId = other.vboId +} + +/** + * Constructor function for [Shader]. + * Important to note that this uses `MemScope(`) by default. + * @return [Shader] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kShader(id: UInt, locs: IntVar, allocator: AutofreeScope = MemScope()) : Shader { + return allocator.alloc { + this.id = id + this.locs = locs.ptr + } +} + +/** + * Set value of a Shader with another provided value of same type. + * This is useful when dealing with cinterop CStruct that holds nested CStructs which are marked as immutable (val). + * NOTE: While the CStruct is immutable itself, the inner members of that CStruct are mutable. + */ +@OptIn(ExperimentalForeignApi::class) +inline fun Shader.set(other: Shader) { + this.id = other.id + this.locs = other.locs +} + +/** + * Constructor function for [MaterialMap]. + * Important to note that this uses `MemScope()` by default. + * @return [MaterialMap] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kMaterialMap(texture: Texture2D, color: Color, value: Float, allocator: AutofreeScope = MemScope()) : MaterialMap { + return allocator.alloc { + this.texture.set(texture) + this.color.set(color) + this.value = value + } +} + +/** + * Constructor function for [Material]. + * Important to note that this uses `MemScope()` by default. + * @return [Material] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kMaterial(shader: Shader, maps: MaterialMap, params: CArrayPointer, allocator: AutofreeScope = MemScope()) : Material { + return MaterialConstructor(shader.readValue(), maps.ptr, params).getPointer(allocator).pointed +} + +/** + * Constructor function for [Transform]. + * Important to note that this uses `MemScope()` by default. + * @return [Transform] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kTransform(translation: Vector3, rotation: Quaternion, scale: Vector3, allocator: AutofreeScope = MemScope()) : Transform { + return allocator.alloc { + this.translation.set(translation) + this.rotation.set(rotation) + this.scale.set(scale) + } +} + +/** + * Constructor function for [BoneInfo]. + * Important to note that this uses `MemScope()` by default. + * @return [BoneInfo] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kBoneInfo(name: CArrayPointer, parent: Int, allocator: AutofreeScope = MemScope()) : BoneInfo { + return BoneInfoConstructor(name, parent).getPointer(allocator).pointed +} + +/** + * Constructor function for [Model]. + * Important to note that this uses `MemScope()` by default. + * @return [Model] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kModel(transform: Matrix, meshCount: Int, materialCount: Int, meshes: Mesh, materials: Material, meshMaterial: IntVar, boneCount: Int, bones: BoneInfo, bindPose: Transform, allocator: AutofreeScope = MemScope()) : Model { + return allocator.alloc { + this.transform.set(transform) + this.meshCount = meshCount + this.materialCount = materialCount + this.meshes = meshes.ptr + this.materials = materials.ptr + this.meshMaterial = meshMaterial.ptr + this.boneCount = boneCount + this.bones = bones.ptr + this.bindPose = bindPose.ptr + } +} + +/** + * Constructor function for [ModelAnimation]. + * Important to note that this uses `MemScope()` by default. + * @return [ModelAnimation] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kModelAnimation(boneCount: Int, frameCount: Int, bones: BoneInfo, framePoses: CPointer>, allocator: AutofreeScope = MemScope()) : ModelAnimation { + return allocator.alloc { + this.boneCount = boneCount + this.frameCount = frameCount + this.bones = bones.ptr + this.framePoses = framePoses + } +} + +/** + * Constructor function for [Ray]. + * Important to note that this uses `MemScope()` by default. + * @return [Ray] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kRay(position: Vector3 = kVector3(), direction: Vector3 = kVector3(), allocator: AutofreeScope = MemScope()) : Ray { + return allocator.alloc { + this.position.set(position) + this.direction.set(direction) + } +} + +/** + * Constructor function for [RayCollision]. + * Important to note that this uses `MemScope()` by default. + * @return [RayCollision] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kRayCollision(hit: Boolean = false, distance: Float = 0F, point: Vector3 = kVector3(), normal: Vector3 = kVector3(), allocator: AutofreeScope = MemScope()) : RayCollision { + return allocator.alloc { + this.hit = hit + this.distance = distance + this.point.set(point) + this.normal.set(normal) + } +} + +/** + * Constructor function for [BoundingBox]. + * Important to note that this uses `MemScope()` by default. + * @return [BoundingBox] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kBoundingBox(min: Vector3 = kVector3(), max: Vector3 = kVector3(), allocator: AutofreeScope = MemScope()) : BoundingBox { + return allocator.alloc { + this.min.set(min) + this.max.set(max) + } +} + +/** + * Constructor function for [Wave]. + * Important to note that this uses `MemScope()` by default. + * @return [Wave] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kWave(frameCount: UInt, sampleRate: UInt, sampleSize: UInt, channels: UInt, data: COpaquePointer?, allocator: AutofreeScope = MemScope()) : Wave { + return allocator.alloc { + this.frameCount = frameCount + this.sampleRate = sampleRate + this.sampleSize = sampleSize + this.channels = channels + this.data = data + } +} + +/** + * Constructor function for [AudioStream]. + * Important to note that this uses `MemScope()` by default. + * @return [AudioStream] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kAudioStream(buffer: rAudioBuffer, processor: rAudioProcessor, sampleRate: UInt, sampleSize: UInt, channels: UInt, allocator: AutofreeScope = MemScope()) : AudioStream { + return allocator.alloc { + this.buffer = buffer.ptr + this.processor = processor.ptr + this.sampleRate = sampleRate + this.sampleSize = sampleSize + this.channels = channels + } +} + +/** + * Constructor function for [Sound]. + * Important to note that this uses `MemScope()` by default. + * @return [Sound] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kSound(stream: AudioStream, frameCount: UInt, allocator: AutofreeScope = MemScope()) : Sound { + return SoundConstructor(stream.readValue(), frameCount).getPointer(allocator).pointed +} + +/** + * Constructor function for [Music]. + * Important to note that this uses `MemScope()` by default. + * @return [Music] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kMusic(stream: AudioStream, frameCount: UInt, looping: Boolean, ctxType: Int, data: COpaquePointer?, allocator: AutofreeScope = MemScope()) : Music { + return MusicConstructor(stream.readValue(), frameCount, looping, ctxType, data).getPointer(allocator).pointed +} + +/** + * Constructor function for [VrDeviceInfo]. + * Important to note that this uses `MemScope()` by default. + * @return [VrDeviceInfo] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kVrDeviceInfo(hResolution: Int, vResolution: Int, hScreenSize: Float, vScreenSize: Float, vScreenCenter: Float, eyeToScreenDistance: Float, lensSeparationDistance: Float, interpupillaryDistance: Float, lensDistortionValues: CArrayPointer, chromaAbCorrection: CArrayPointer, allocator: AutofreeScope = MemScope()) : VrDeviceInfo { + return VrDeviceInfoConstructor(hResolution, vResolution, hScreenSize, vScreenSize, vScreenCenter, eyeToScreenDistance, lensSeparationDistance, interpupillaryDistance, lensDistortionValues, chromaAbCorrection).getPointer(allocator).pointed +} + +/** + * Constructor function for [VrStereoConfig]. + * Important to note that this uses `MemScope()` by default. + * @return [VrStereoConfig] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kVrStereoConfig(projection: CArrayPointer, viewOffset: CArrayPointer, leftLensCenter: CArrayPointer, rightLensCenter: CArrayPointer, leftScreenCenter: CArrayPointer, rightScreenCenter: CArrayPointer, scale: CArrayPointer, scaleIn: CArrayPointer, allocator: AutofreeScope = MemScope()) : VrStereoConfig { + return VrStereoConfigConstructor(projection, viewOffset, leftLensCenter, rightLensCenter, leftScreenCenter, rightScreenCenter, scale, scaleIn).getPointer(allocator).pointed +} + +/** + * Constructor function for [FilePathList]. + * Important to note that this uses `MemScope()` by default. + * @return [FilePathList] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun kFilePathList(capacity: UInt, count: UInt, paths: CPointer>, allocator: AutofreeScope = MemScope()) : FilePathList { + return allocator.alloc { + this.capacity = capacity + this.count = count + this.paths = paths + } +} diff --git a/src/commonMain/kotlin/kaylibkit/kUtils/KUtils.kt b/src/commonMain/kotlin/kaylibkit/kUtils/KUtils.kt new file mode 100644 index 0000000..394ce4c --- /dev/null +++ b/src/commonMain/kotlin/kaylibkit/kUtils/KUtils.kt @@ -0,0 +1,201 @@ +package kaylibkit.kUtils + +import kaylibc.* +import kotlinx.cinterop.* + +// -- Module: kUtils + +//=======================================================// +// Raylib Utility Functions - Don't belong anywhere else +//=======================================================// + +/** + * Encode text codepoint into UTF-8 text + * REQUIRES: memcpy() + * WARNING: Allocated memory must be manually freed + * @return [ByteVar] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun loadUTF8(codepoints: IntVar, length: Int): ByteVar? { + return LoadUTF8(codepoints.ptr, length)?.getPointer(MemScope())?.pointed +} + +/** + * Unload UTF-8 text encoded from codepoints array + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadUTF8(text: String) { + UnloadUTF8(text.cstr) +} + +/** + * Unload codepoints data from memory + */ +@OptIn(ExperimentalForeignApi::class) +inline fun unloadCodepoints(codepoints: IntVar) { + UnloadCodepoints(codepoints.ptr) +} + +/** + * Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCodepoint(text: String, codepointSize: IntVar) : Int { + return GetCodepoint(text, codepointSize.ptr) +} + +/** + * Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCodepointNext(text: String, codepointSize: IntVar) : Int { + return GetCodepointNext(text, codepointSize.ptr) +} + +/** + * Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getCodepointPrevious(text: String, codepointSize: IntVar) : Int { + return GetCodepointPrevious(text, codepointSize.ptr) +} + +/** + * Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure + */ +@OptIn(ExperimentalForeignApi::class) +inline fun codepointToUTF8(codepoint: Int, utf8Size: IntVar) : ByteVar? { + return CodepointToUTF8(codepoint, utf8Size.ptr)?.getPointer(MemScope())?.pointed +} + +//=======================================================// +// COLOR/PIXEL FUNCTIONS +//=======================================================// + +/** + * Get [Color] with alpha applied, alpha goes from 0.0f to 1.0f + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun fade(color: Color, alpha: Float) : Color { + return Fade(color.readValue(), alpha).getPointer(MemScope()).pointed +} + +/** + * Get hexadecimal value for a [Color] + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorToInt(color: Color) : Int { + return ColorToInt(color.readValue()) +} + +/** + * Get [Color] normalized as float [0..1] + * @return [Vector4] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorNormalize(color: Color) : Vector4 { + return ColorNormalize(color.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get [Color] from normalized values [0..1] + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorFromNormalized(normalized: Vector4) : Color { + return ColorFromNormalized(normalized.readValue()) .getPointer(MemScope()).pointed +} + +/** + * Get HSV values for a [Color], hue [0..360], saturation/value [0..1] + * @return [Vector3] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorToHSV(color: Color) : Vector3 { + return ColorToHSV(color.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get a [Color] from HSV values, hue [0..360], saturation/value [0..1] + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorFromHSV(hue: Float, saturation: Float, value: Float) : Color { + return ColorFromHSV(hue, saturation, value).getPointer(MemScope()).pointed +} + +/** + * Get [Color] with alpha applied, alpha goes from .0F to 1.0F + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorAlpha(color: Color, alpha: Float) : Color { + return ColorAlpha(color.readValue(), alpha).getPointer(MemScope()).pointed +} + +/** + * Get src alpha-blended into dst [Color] with tint + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun colorAlphaBlend(dst: Color, src: Color, tint: Color) : Color { + return ColorAlphaBlend(dst.readValue(), src.readValue(), tint.readValue()).getPointer(MemScope()).pointed +} + +/** + * Get [Color] structure from hexadecimal value + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getColor(hexValue: UInt) : Color { + return GetColor(hexValue).getPointer(MemScope()).pointed +} + +/** + * Get [Color] from a source pixel pointer of certain format + * @return [Color] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getPixelColor(srcPtr: COpaquePointer, format: kaylibkit.kEnums.PixelFormat) : Color { + return GetPixelColor(srcPtr, format.value).getPointer(MemScope()).pointed +} + +/** + * Set [Color] formatted into destination pixel pointer + */ +@OptIn(ExperimentalForeignApi::class) +inline fun setPixelColor(dstPtr: COpaquePointer, color: Color, format: kaylibkit.kEnums.PixelFormat) { + return SetPixelColor(dstPtr, color.readValue(), format.value) +} + +/** + * Get pixel data size in bytes for certain format + * @return [Int] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun getPixelDataSize(width: Int, height: Int, format: kaylibkit.kEnums.PixelFormat) : Int { + return GetPixelDataSize(width, height, format.value) +} + +/** + * Checks if shader is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isShaderReady(shader: Shader) : Boolean { + return IsShaderReady(shader.readValue()) +} + +/** + * Check if a material is ready + * @return [Boolean] + */ +@OptIn(ExperimentalForeignApi::class) +inline fun isMaterialReady(material: Material) : Boolean { + return IsMaterialReady(material.readValue()) +} \ No newline at end of file diff --git a/src/linuxMain/kotlin/kaylibkit/kCore/KCore.linux.kt b/src/linuxMain/kotlin/kaylibkit/kCore/KCore.linux.kt new file mode 100644 index 0000000..f204d01 --- /dev/null +++ b/src/linuxMain/kotlin/kaylibkit/kCore/KCore.linux.kt @@ -0,0 +1,9 @@ +package kaylibkit.kCore + +import kaylibc.TraceLogCallback +import kotlinx.cinterop.ExperimentalForeignApi + +@OptIn(ExperimentalForeignApi::class) +actual fun setTraceLogCallbackInternal(callback: TraceLogCallback) { + kaylibc.SetTraceLogCallback(callback) +} \ No newline at end of file diff --git a/src/macosMain/kotlin/kaylibkit/kCore/KCore.macos.kt b/src/macosMain/kotlin/kaylibkit/kCore/KCore.macos.kt new file mode 100644 index 0000000..921be6b --- /dev/null +++ b/src/macosMain/kotlin/kaylibkit/kCore/KCore.macos.kt @@ -0,0 +1,8 @@ +package kaylibkit.kCore + +import kotlinx.cinterop.ExperimentalForeignApi + +@OptIn(ExperimentalForeignApi::class) +actual fun setTraceLogCallbackInternal(callback: TraceLogCallback) { + kaylibc.SetTraceLogCallback(callback) +} \ No newline at end of file diff --git a/src/mingwMain/kotlin/kaylibkit/kCore/KCore.mingw.kt b/src/mingwMain/kotlin/kaylibkit/kCore/KCore.mingw.kt new file mode 100644 index 0000000..921be6b --- /dev/null +++ b/src/mingwMain/kotlin/kaylibkit/kCore/KCore.mingw.kt @@ -0,0 +1,8 @@ +package kaylibkit.kCore + +import kotlinx.cinterop.ExperimentalForeignApi + +@OptIn(ExperimentalForeignApi::class) +actual fun setTraceLogCallbackInternal(callback: TraceLogCallback) { + kaylibc.SetTraceLogCallback(callback) +} \ No newline at end of file diff --git a/src/nativeInterop/cinterop/Makefile b/src/nativeInterop/cinterop/Makefile new file mode 100644 index 0000000..26910f8 --- /dev/null +++ b/src/nativeInterop/cinterop/Makefile @@ -0,0 +1,66 @@ +LIB_DIR := $(CURDIR)/lib +INC_DIR := $(CURDIR)/include +LIB_LINUX := $(CURDIR)/lib/linux +LIB_OSX := $(CURDIR)/lib/osx +LIB_MINGW := $(CURDIR)/lib/mingw + +VERSION ?= 4.5.0 +URL := https://github.com/raysan5/raylib/releases/download/ +ARCHIVE_LINUX := raylib-$(VERSION)_linux_amd64.tar.gz +ARCHIVE_OSX := raylib-$(VERSION)_macos.tar.gz +ARCHIVE_MINGW := raylib-$(VERSION)_win64_mingw-w64.zip + +ARCHIVE_LINUX_EXT := raylib-$(VERSION)_linux_amd64 +ARCHIVE_OSX_EXT := raylib-$(VERSION)_macos +ARCHIVE_MINGW_EXT := raylib-$(VERSION)_win64_mingw-w64 + +HEADER_NAMES := raylib.h raymath.h rcamera.h + +.PHONY: all +all: download extract download_headers + +.PHONY: download +download: + @echo "Downloading Raylib files..." + @wget -q $(URL)$(VERSION)/$(ARCHIVE_LINUX) + @wget -q $(URL)$(VERSION)/$(ARCHIVE_OSX) + @wget -q $(URL)$(VERSION)/$(ARCHIVE_MINGW) + +.PHONY: extract +extract: + @echo "Extracting Raylib files..." + @tar -C $(LIB_LINUX) -xf $(CURDIR)/$(ARCHIVE_LINUX) --strip-components=2 $(ARCHIVE_LINUX_EXT)/lib/libraylib.a + @tar -C $(LIB_OSX) -xf $(CURDIR)/$(ARCHIVE_OSX) --strip-components=2 $(ARCHIVE_OSX_EXT)/lib/libraylib.a + @unzip -q -j -d $(LIB_MINGW) $(CURDIR)/$(ARCHIVE_MINGW) $(ARCHIVE_MINGW_EXT)/lib/libraylib.a + @unzip -q -j -d $(LIB_MINGW) $(CURDIR)/$(ARCHIVE_MINGW) $(ARCHIVE_MINGW_EXT)/lib/libraylibdll.a + @echo "Cleaning up archive files..." + @rm -rf $(CURDIR)/*.tar.gz $(CURDIR)/*.zip + +.PHONY: download_headers +download_headers: + @echo "Downloading header files..." + @for header in $(HEADER_NAMES); do \ + wget -q https://raw.githubusercontent.com/raysan5/raylib/18a36b3e066f5743757cfa9ecbe784bbe20d529e/src/$$header -P $(INC_DIR); \ + done + +.PHONY: clean +clean: + @echo "Cleaning up static libraries..." + @if [ "$(OS)" == "Windows_NT" ]; then \ + del /Q $(LIB_LINUX)\libraylib.a; \ + del /Q $(LIB_OSX)\libraylib.a; \ + del /Q $(LIB_MINGW)\libraylib.a; \ + del /Q $(LIB_MINGW)\libraylibdll.a; \ + del /Q $(INC_DIR)\raylib.h; \ + del /Q $(INC_DIR)\rcamera.h; \ + del /Q $(INC_DIR)\raymath.h; \ + else \ + rm -f $(LIB_LINUX)/libraylib.a; \ + rm -f $(LIB_OSX)/libraylib.a; \ + rm -f $(LIB_MINGW)/libraylib.a; \ + rm -f $(LIB_MINGW)/libraylibdll.a; \ + fi + @echo "Cleaning up header files..." + @for header in $(HEADER_NAMES); do \ + rm -f $(INC_DIR)/$$header; \ + done \ No newline at end of file diff --git a/src/nativeInterop/cinterop/include/.gitkeep b/src/nativeInterop/cinterop/include/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/nativeInterop/cinterop/kaylibc.def b/src/nativeInterop/cinterop/kaylibc.def new file mode 100644 index 0000000..7c44401 --- /dev/null +++ b/src/nativeInterop/cinterop/kaylibc.def @@ -0,0 +1,73 @@ +headers = raylib.h raymath.h rcamera.h +package = kaylibc +staticLibraries = libraylib.a +libraryPaths.osx = src/nativeInterop/cinterop/lib/osx +libraryPaths.linux = src/nativeInterop/cinterop/lib/linux +libraryPaths.mingw = src/nativeInterop/cinterop/lib/mingw + +compilerOpts = -Isrc/nativeInterop/cinterop/include +linkerOpts.mingw = -lwinmm -lgdi32 -lopengl32 -lkernel32 +linkerOpts.osx = -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL + + +--- +static Color lightGray = (Color){ 200, 200, 200, 255 }; +static Color gray = (Color){ 130, 130, 130, 255 }; +static Color darkGray = (Color){ 80, 80, 80, 255 }; +static Color yellow = (Color){ 253, 249, 0, 255 }; +static Color gold = (Color){ 255, 203, 0, 255 }; +static Color orange = (Color){ 255, 161, 0, 255 }; +static Color pink = (Color){ 255, 109, 194, 255 }; +static Color red = (Color){ 230, 41, 55, 255 }; +static Color maroon = (Color){ 190, 33, 55, 255 }; +static Color green = (Color){ 0, 228, 48, 255 }; +static Color lime = (Color){ 0, 158, 47, 255 }; +static Color darkGreen = (Color){ 0, 117, 44, 255 }; +static Color skyBlue = (Color){ 102, 191, 255, 255 }; +static Color blue = (Color){ 0, 121, 241, 255 }; +static Color darkBlue = (Color){ 0, 82, 172, 255 }; +static Color purple = (Color){ 200, 122, 255, 255 }; +static Color violet = (Color){ 135, 60, 190, 255 }; +static Color darkPurple = (Color){ 112, 31, 126, 255 }; +static Color beige = (Color){ 211, 176, 131, 255 }; +static Color brown = (Color){ 127, 106, 79, 255 }; +static Color darkBrown = (Color){ 76, 63, 47, 255 }; +static Color white = (Color){ 255, 255, 255, 255 }; +static Color black = (Color){ 0, 0, 0, 255 }; +static Color blank = (Color){ 0, 0, 0, 0 }; +static Color magenta = (Color){ 255, 0, 255, 255 }; +static Color rayWhite = (Color){ 245, 245, 245, 255 }; + + +// CONSTRUCTOR HELPERS + +static inline struct BoneInfo BoneInfoConstructor(char name[32], int parent) { + struct BoneInfo r = { name[32], parent }; + return r; +} + +static inline struct Material MaterialConstructor(Shader shader, MaterialMap *maps, float params[4]) { + struct Material r = { shader, maps, params[4] }; + return r; +} + +static inline struct Sound SoundConstructor(AudioStream stream, unsigned int frameCount) { + struct Sound r = { stream, frameCount }; + return r; +} + +static inline struct Music MusicConstructor(AudioStream stream, unsigned int frameCount, bool looping, int ctxType, void *ctxData) { + struct Music r = { stream, frameCount, looping, ctxType, ctxData }; + return r; +} + +static inline struct VrDeviceInfo VrDeviceInfoConstructor(int hResolution, int vResolution, float hScreenSize, float vScreenSize, float vScreenCenter, float eyeToScreenDistance, float lensSeparationDistance, float interpupillaryDistance, float lensDistortionValues[4], float chromaAbCorrection[4]) { + struct VrDeviceInfo r = { hResolution, vResolution, hScreenSize, vScreenSize, vScreenCenter, eyeToScreenDistance, lensSeparationDistance, interpupillaryDistance, lensDistortionValues[4], chromaAbCorrection[4] }; + return r; +} + + +static inline struct VrStereoConfig VrStereoConfigConstructor(Matrix projection[2], Matrix viewOffset[2], float leftLensCenter[2], float rightLensCenter[2], float leftScreenCenter[2], float rightScreenCenter[2], float scale[2], float scaleIn[2]) { + struct VrStereoConfig r = { projection[2], viewOffset[2], leftLensCenter[2], rightLensCenter[2], leftScreenCenter[2], rightScreenCenter[2], scale[2], scaleIn[2] }; + return r; +} \ No newline at end of file diff --git a/src/nativeInterop/cinterop/lib/.gitkeep b/src/nativeInterop/cinterop/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/nativeInterop/cinterop/lib/linux/.gitkeep b/src/nativeInterop/cinterop/lib/linux/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/nativeInterop/cinterop/lib/mingw/.gitkeep b/src/nativeInterop/cinterop/lib/mingw/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/nativeInterop/cinterop/lib/osx/.gitkeep b/src/nativeInterop/cinterop/lib/osx/.gitkeep new file mode 100644 index 0000000..e69de29