diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 7827ecc..82dba38 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -25,6 +25,7 @@
+
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index ce235ca..587f413 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -26,8 +26,21 @@ tasks.jar {
from(project(":networking").sourceSets.main.get().output)
}
+val projectGroup: String by project
+
subprojects {
apply(plugin = "maven-publish")
+ apply(plugin = "kotlin")
+
+ group = projectGroup
+
+ tasks.compileKotlin {
+ kotlinOptions {
+ jvmTarget = "17"
+ freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
+ freeCompilerArgs += "-Xjvm-default=all"
+ }
+ }
publishing {
repositories {
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Array2D.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Array2D.kt
new file mode 100644
index 0000000..7a6126e
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Array2D.kt
@@ -0,0 +1,129 @@
+package ru.dbotthepony.kommons.arrays
+
+/**
+ * Operations over 2D arrays are done in-place, unless states otherwise
+ */
+abstract class Array2D {
+ abstract val rows: Int
+ abstract val columns: Int
+
+ val size: Int get() = rows * columns
+ fun size() = size
+
+ val isEmpty: Boolean get() = size <= 0
+ val isNotEmpty: Boolean get() = size > 0
+
+ val isSquareShaped: Boolean get() = rows == columns
+
+ inline val columnIndices get() = 0 until columns
+ inline val rowIndices get() = 0 until rows
+ inline val dimensionsString get() = "$columns x $rows"
+
+ fun sizeEquals(other: Array2D): Boolean {
+ return rows == other.rows && columns == other.columns
+ }
+
+ fun requireSizeEquals(other: Array2D) {
+ if (!sizeEquals(other)) {
+ throw IllegalArgumentException("Other matrix (${other.columns}x${other.rows}) is not the same dimensions as this matrix (${columns}x${rows})")
+ }
+ }
+
+ inline fun requireSizeEquals(other: Array2D, lambda: () -> Any) {
+ if (!sizeEquals(other)) {
+ throw IllegalArgumentException(lambda.invoke().toString())
+ }
+ }
+
+ fun checkSizeEquals(other: Array2D) {
+ if (!sizeEquals(other)) {
+ throw IllegalStateException("Other matrix (${other.columns}x${other.rows}) is not the same dimensions as this matrix (${columns}x${rows})")
+ }
+ }
+
+ inline fun checkSizeEquals(other: Array2D, lambda: () -> Any) {
+ if (!sizeEquals(other)) {
+ throw IllegalStateException(lambda.invoke().toString())
+ }
+ }
+
+ fun rowsEquals(other: Array2D): Boolean {
+ return rows == other.rows
+ }
+
+ fun requireRowsEquals(other: Array2D) {
+ if (!rowsEquals(other)) {
+ throw IllegalArgumentException("Other matrix (${other.rows}) has different amount of rows compared to this matrix (${rows})")
+ }
+ }
+
+ inline fun requireRowsEquals(other: Array2D, lambda: () -> Any) {
+ if (!rowsEquals(other)) {
+ throw IllegalArgumentException(lambda.invoke().toString())
+ }
+ }
+
+ fun checkRowsEquals(other: Array2D) {
+ if (!rowsEquals(other)) {
+ throw IllegalStateException("Other matrix (${other.rows}) has different amount of rows compared to this matrix (${rows})")
+ }
+ }
+
+ inline fun checkRowsEquals(other: Array2D, lambda: () -> Any) {
+ if (!rowsEquals(other)) {
+ throw IllegalStateException(lambda.invoke().toString())
+ }
+ }
+
+ fun columnsEquals(other: Array2D): Boolean {
+ return columns == other.columns
+ }
+
+ fun requireColumnsEquals(other: Array2D) {
+ if (!rowsEquals(other)) {
+ throw IllegalArgumentException("Other matrix (${other.columns}) has different amount of columns compared to this matrix (${columns})")
+ }
+ }
+
+ inline fun requireColumnsEquals(other: Array2D, lambda: () -> Any) {
+ if (!rowsEquals(other)) {
+ throw IllegalArgumentException(lambda.invoke().toString())
+ }
+ }
+
+ fun checkColumnsEquals(other: Array2D) {
+ if (!rowsEquals(other)) {
+ throw IllegalStateException("Other matrix (${other.columns}) has different amount of columns compared to this matrix (${columns})")
+ }
+ }
+
+ inline fun checkColumnsEquals(other: Array2D, lambda: () -> Any) {
+ if (!rowsEquals(other)) {
+ throw IllegalStateException(lambda.invoke().toString())
+ }
+ }
+
+ inline fun requireIsSquare(lambda: () -> Any) {
+ if (!isSquareShaped) {
+ throw IllegalArgumentException(lambda.invoke().toString())
+ }
+ }
+
+ fun requireIsSquare() {
+ if (!isSquareShaped) {
+ throw IllegalArgumentException("Matrix is not square matrix: $columns x $rows")
+ }
+ }
+
+ inline fun checkIsSquare(lambda: () -> Any) {
+ if (!isSquareShaped) {
+ throw IllegalStateException(lambda.invoke().toString())
+ }
+ }
+
+ fun checkIsSquare() {
+ if (!isSquareShaped) {
+ throw IllegalStateException("Matrix is not square matrix: $columns x $rows")
+ }
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Boolean2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Boolean2DArray.kt
new file mode 100644
index 0000000..6b677c7
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Boolean2DArray.kt
@@ -0,0 +1,111 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.util.BitSet
+
+abstract class Boolean2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Boolean
+ abstract operator fun set(column: Int, row: Int, value: Boolean)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Boolean2DArray::get) { a, b -> a.put(if (b) 1 else 0) }
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Boolean2DArray::get) { a, b -> a.put(if (b) 1 else 0) }
+ fun store(target: Boolean2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Boolean2DArray::set) { it.get() > 0 }
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Boolean2DArray::set) { it.get() > 0 }
+ fun load(from: Boolean2DArray) { matrixLoad(from, this, Boolean2DArray::get, Boolean2DArray::set) }
+ fun storeTransposed(from: Boolean2DArray) { matrixLoadTransposed(from, this, Boolean2DArray::get, Boolean2DArray::set) }
+
+ open fun contentEquals(other: Boolean2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Boolean2DArray::get)
+ }
+
+ open val transposed: Boolean2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Boolean2DArray() {
+ private val mem = BitSet(columns * rows)
+
+ override fun get(column: Int, row: Int): Boolean {
+ if (column !in 0 until columns || row !in 0 until rows)
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+
+ return mem[column + row * columns]
+ }
+
+ override fun set(column: Int, row: Int, value: Boolean) {
+ if (column !in 0 until columns || row !in 0 until rows)
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+
+ mem[column + row * columns] = value
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return mem == other.mem
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Boolean2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Boolean2DArray) : Boolean2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Boolean {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Boolean) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Boolean2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Boolean2DArray): Boolean2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Boolean2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Byte2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Byte2DArray.kt
new file mode 100644
index 0000000..8216895
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Byte2DArray.kt
@@ -0,0 +1,112 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+
+abstract class Byte2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Byte
+ abstract operator fun set(column: Int, row: Int, value: Byte)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Byte2DArray::get, ByteBuffer::put)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Byte2DArray::get, ByteBuffer::put)
+ fun store(target: Byte2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Byte2DArray::set, ByteBuffer::get)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Byte2DArray::set, ByteBuffer::get)
+ fun load(from: Byte2DArray) { matrixLoad(from, this, Byte2DArray::get, Byte2DArray::set) }
+ fun storeTransposed(from: Byte2DArray) { matrixLoadTransposed(from, this, Byte2DArray::get, Byte2DArray::set) }
+
+ open fun contentEquals(other: Byte2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Byte2DArray::get)
+ }
+
+ val transposed: Byte2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Byte2DArray() {
+ private val mem = ByteArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Byte {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Byte) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Byte2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Byte2DArray) : Byte2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Byte {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Byte) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Byte2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Byte2DArray): Byte2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Byte2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Char2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Char2DArray.kt
new file mode 100644
index 0000000..cbdff2f
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Char2DArray.kt
@@ -0,0 +1,117 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.nio.CharBuffer
+
+abstract class Char2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Char
+ abstract operator fun set(column: Int, row: Int, value: Char)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Char2DArray::get, ByteBuffer::putChar)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Char2DArray::get, ByteBuffer::putChar)
+ fun storeColumnRow(target: CharBuffer) = matrixStoreColumnRow(this, target, Char2DArray::get, CharBuffer::put)
+ fun storeRowColumn(target: CharBuffer) = matrixStoreRowColumn(this, target, Char2DArray::get, CharBuffer::put)
+ fun store(target: Char2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Char2DArray::set, ByteBuffer::getChar)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Char2DArray::set, ByteBuffer::getChar)
+ fun loadColumnRow(target: CharBuffer) = matrixLoadColumnRow(this, target, Char2DArray::set, CharBuffer::get)
+ fun loadRowColumn(target: CharBuffer) = matrixLoadRowColumn(this, target, Char2DArray::set, CharBuffer::get)
+ fun load(from: Char2DArray) { matrixLoad(from, this, Char2DArray::get, Char2DArray::set) }
+ fun storeTransposed(from: Char2DArray) { matrixLoadTransposed(from, this, Char2DArray::get, Char2DArray::set) }
+
+ open fun contentEquals(other: Char2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Char2DArray::get)
+ }
+
+ open val transposed: Char2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Char2DArray() {
+ private val mem = CharArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Char {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Char) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Char2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Char2DArray) : Char2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Char {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Char) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Char2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Char2DArray): Char2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Char2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Double2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Double2DArray.kt
new file mode 100644
index 0000000..6416ff3
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Double2DArray.kt
@@ -0,0 +1,164 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.nio.DoubleBuffer
+
+abstract class Double2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Double
+ abstract operator fun set(column: Int, row: Int, value: Double)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Double2DArray::get, ByteBuffer::putDouble)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Double2DArray::get, ByteBuffer::putDouble)
+ fun storeColumnRow(target: DoubleBuffer) = matrixStoreColumnRow(this, target, Double2DArray::get, DoubleBuffer::put)
+ fun storeRowColumn(target: DoubleBuffer) = matrixStoreRowColumn(this, target, Double2DArray::get, DoubleBuffer::put)
+ fun store(target: Double2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Double2DArray::set, ByteBuffer::getDouble)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Double2DArray::set, ByteBuffer::getDouble)
+ fun loadColumnRow(target: DoubleBuffer) = matrixLoadColumnRow(this, target, Double2DArray::set, DoubleBuffer::get)
+ fun loadRowColumn(target: DoubleBuffer) = matrixLoadRowColumn(this, target, Double2DArray::set, DoubleBuffer::get)
+ fun load(from: Double2DArray) { matrixLoad(from, this, Double2DArray::get, Double2DArray::set) }
+ fun storeTransposed(from: Double2DArray) { matrixLoadTransposed(from, this, Double2DArray::get, Double2DArray::set) }
+
+ open fun contentEquals(other: Double2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ open fun add(other: Double2DArray): Double2DArray = matrixPlainOperator(this, other, Double2DArray::get, Double::plus, Double2DArray::set)
+ open fun sub(other: Double2DArray): Double2DArray = matrixPlainOperator(this, other, Double2DArray::get, Double::minus, Double2DArray::set)
+ open fun add(other: Double): Double2DArray = matrixPlainOperator(this, other, Double2DArray::get, Double::plus, Double2DArray::set)
+ open fun sub(other: Double): Double2DArray = matrixPlainOperator(this, other, Double2DArray::get, Double::minus, Double2DArray::set)
+ open fun mul(other: Double): Double2DArray = matrixPlainOperator(this, other, Double2DArray::get, Double::times, Double2DArray::set)
+ open fun div(other: Double): Double2DArray = matrixPlainOperator(this, other, Double2DArray::get, Double::div, Double2DArray::set)
+
+ /**
+ * Allocates new array
+ */
+ open fun mul(other: Double2DArray): Double2DArray {
+ return matrixTimes(this, other, Double2DArray::get, Double2DArray::set, Double::plus, Double::times, ::allocate)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Double2DArray::get)
+ }
+
+ open val determinant: Double? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+ else
+ return matrixDeterminant(this)
+ }
+
+ open val cofactorMatrix: Double2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, false)
+ return new
+ }
+
+ open val adjugateMatrix: Double2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, true)
+ return new
+ }
+
+ open val inversed: Double2DArray? get() {
+ val determinant = determinant ?: return null
+ if (determinant == 0.0) return null
+ val adj = adjugateMatrix!!
+ adj.mul(1f / determinant)
+ return adj
+ }
+
+ open val transposed: Double2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Double2DArray() {
+ private val mem = DoubleArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Double {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Double2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Double2DArray) : Double2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Double {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Double2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Double2DArray): Double2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Double2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Float2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Float2DArray.kt
new file mode 100644
index 0000000..6866965
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Float2DArray.kt
@@ -0,0 +1,165 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.nio.FloatBuffer
+
+abstract class Float2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Float
+ abstract operator fun set(column: Int, row: Int, value: Float)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Float2DArray::get, ByteBuffer::putFloat)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Float2DArray::get, ByteBuffer::putFloat)
+ fun storeColumnRow(target: FloatBuffer) = matrixStoreColumnRow(this, target, Float2DArray::get, FloatBuffer::put)
+ fun storeRowColumn(target: FloatBuffer) = matrixStoreRowColumn(this, target, Float2DArray::get, FloatBuffer::put)
+ fun store(target: Float2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Float2DArray::set, ByteBuffer::getFloat)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Float2DArray::set, ByteBuffer::getFloat)
+ fun loadColumnRow(target: FloatBuffer) = matrixLoadColumnRow(this, target, Float2DArray::set, FloatBuffer::get)
+ fun loadRowColumn(target: FloatBuffer) = matrixLoadRowColumn(this, target, Float2DArray::set, FloatBuffer::get)
+ fun load(from: Float2DArray) { matrixLoad(from, this, Float2DArray::get, Float2DArray::set) }
+ fun storeTransposed(from: Float2DArray) { matrixLoadTransposed(from, this, Float2DArray::get, Float2DArray::set) }
+
+ open fun contentEquals(other: Float2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ open fun add(other: Float2DArray): Float2DArray = matrixPlainOperator(this, other, Float2DArray::get, Float::plus, Float2DArray::set)
+ open fun sub(other: Float2DArray): Float2DArray = matrixPlainOperator(this, other, Float2DArray::get, Float::minus, Float2DArray::set)
+ open fun add(other: Float): Float2DArray = matrixPlainOperator(this, other, Float2DArray::get, Float::plus, Float2DArray::set)
+ open fun sub(other: Float): Float2DArray = matrixPlainOperator(this, other, Float2DArray::get, Float::minus, Float2DArray::set)
+ open fun mul(other: Float): Float2DArray = matrixPlainOperator(this, other, Float2DArray::get, Float::times, Float2DArray::set)
+ open fun div(other: Float): Float2DArray = matrixPlainOperator(this, other, Float2DArray::get, Float::div, Float2DArray::set)
+
+ /**
+ * Allocates new array, unlike other methods which modify object in place
+ */
+ open fun mul(other: Float2DArray): Float2DArray {
+ return matrixTimes(this, other, Float2DArray::get, Float2DArray::set, Float::plus, Float::times, ::allocate)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Float2DArray::get)
+ }
+
+ open val determinant: Float? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+ else
+ return matrixDeterminant(this)
+ }
+
+ open val cofactorMatrix: Float2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, false)
+ return new
+ }
+
+ open val adjugateMatrix: Float2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, true)
+ return new
+ }
+
+ open val inversed: Float2DArray? get() {
+ val determinant = determinant ?: return null
+ if (determinant == 0f) return null
+ val adj = adjugateMatrix!!
+ adj.mul(1f / determinant)
+ return adj
+ }
+
+ open val transposed: Float2DArray
+ get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Float2DArray() {
+ private val mem = FloatArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Float {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Float2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Float2DArray) : Float2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Float {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Float2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Float2DArray): Float2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Float2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Int2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Int2DArray.kt
new file mode 100644
index 0000000..a48331f
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Int2DArray.kt
@@ -0,0 +1,164 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.nio.IntBuffer
+
+abstract class Int2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Int
+ abstract operator fun set(column: Int, row: Int, value: Int)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Int2DArray::get, ByteBuffer::putInt)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Int2DArray::get, ByteBuffer::putInt)
+ fun storeColumnRow(target: IntBuffer) = matrixStoreColumnRow(this, target, Int2DArray::get, IntBuffer::put)
+ fun storeRowColumn(target: IntBuffer) = matrixStoreRowColumn(this, target, Int2DArray::get, IntBuffer::put)
+ fun store(target: Int2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Int2DArray::set, ByteBuffer::getInt)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Int2DArray::set, ByteBuffer::getInt)
+ fun loadColumnRow(target: IntBuffer) = matrixLoadColumnRow(this, target, Int2DArray::set, IntBuffer::get)
+ fun loadRowColumn(target: IntBuffer) = matrixLoadRowColumn(this, target, Int2DArray::set, IntBuffer::get)
+ fun load(from: Int2DArray) { matrixLoad(from, this, Int2DArray::get, Int2DArray::set) }
+ fun storeTransposed(from: Int2DArray) { matrixLoadTransposed(from, this, Int2DArray::get, Int2DArray::set) }
+
+ open fun contentEquals(other: Int2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ open fun add(other: Int2DArray): Int2DArray = matrixPlainOperator(this, other, Int2DArray::get, Int::plus, Int2DArray::set)
+ open fun sub(other: Int2DArray): Int2DArray = matrixPlainOperator(this, other, Int2DArray::get, Int::minus, Int2DArray::set)
+ open fun add(other: Int): Int2DArray = matrixPlainOperator(this, other, Int2DArray::get, Int::plus, Int2DArray::set)
+ open fun sub(other: Int): Int2DArray = matrixPlainOperator(this, other, Int2DArray::get, Int::minus, Int2DArray::set)
+ open fun mul(other: Int): Int2DArray = matrixPlainOperator(this, other, Int2DArray::get, Int::times, Int2DArray::set)
+ open fun div(other: Int): Int2DArray = matrixPlainOperator(this, other, Int2DArray::get, Int::div, Int2DArray::set)
+
+ /**
+ * Allocates new array, unlike other methods which modify object in place
+ */
+ open fun mul(other: Int2DArray): Int2DArray {
+ return matrixTimes(this, other, Int2DArray::get, Int2DArray::set, Int::plus, Int::times, ::allocate)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Int2DArray::get)
+ }
+
+ open val determinant: Int? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+ else
+ return matrixDeterminant(this)
+ }
+
+ open val cofactorMatrix: Int2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, false)
+ return new
+ }
+
+ open val adjugateMatrix: Int2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, true)
+ return new
+ }
+
+ open val inverted: Int2DArray? get() {
+ val determinant = determinant ?: return null
+ if (determinant == 0) return null
+ val adj = adjugateMatrix!!
+ adj.div(determinant)
+ return adj
+ }
+
+ open val transposed: Int2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Int2DArray() {
+ private val mem = IntArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Int {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Int) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Int2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Int2DArray) : Int2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Int {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Int) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Int2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Int2DArray): Int2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Int2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Long2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Long2DArray.kt
new file mode 100644
index 0000000..7beb175
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Long2DArray.kt
@@ -0,0 +1,164 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.nio.LongBuffer
+
+abstract class Long2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Long
+ abstract operator fun set(column: Int, row: Int, value: Long)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Long2DArray::get, ByteBuffer::putLong)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Long2DArray::get, ByteBuffer::putLong)
+ fun storeColumnRow(target: LongBuffer) = matrixStoreColumnRow(this, target, Long2DArray::get, LongBuffer::put)
+ fun storeRowColumn(target: LongBuffer) = matrixStoreRowColumn(this, target, Long2DArray::get, LongBuffer::put)
+ fun store(target: Long2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Long2DArray::set, ByteBuffer::getLong)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Long2DArray::set, ByteBuffer::getLong)
+ fun loadColumnRow(target: LongBuffer) = matrixLoadColumnRow(this, target, Long2DArray::set, LongBuffer::get)
+ fun loadRowColumn(target: LongBuffer) = matrixLoadRowColumn(this, target, Long2DArray::set, LongBuffer::get)
+ fun load(from: Long2DArray) { matrixLoad(from, this, Long2DArray::get, Long2DArray::set) }
+ fun storeTransposed(from: Long2DArray) { matrixLoadTransposed(from, this, Long2DArray::get, Long2DArray::set) }
+
+ open fun contentEquals(other: Long2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ open fun add(other: Long2DArray): Long2DArray = matrixPlainOperator(this, other, Long2DArray::get, Long::plus, Long2DArray::set)
+ open fun sub(other: Long2DArray): Long2DArray = matrixPlainOperator(this, other, Long2DArray::get, Long::minus, Long2DArray::set)
+ open fun add(other: Long): Long2DArray = matrixPlainOperator(this, other, Long2DArray::get, Long::plus, Long2DArray::set)
+ open fun sub(other: Long): Long2DArray = matrixPlainOperator(this, other, Long2DArray::get, Long::minus, Long2DArray::set)
+ open fun mul(other: Long): Long2DArray = matrixPlainOperator(this, other, Long2DArray::get, Long::times, Long2DArray::set)
+ open fun div(other: Long): Long2DArray = matrixPlainOperator(this, other, Long2DArray::get, Long::div, Long2DArray::set)
+
+ /**
+ * Allocates new array, unlike other methods which modify object in place
+ */
+ open fun mul(other: Long2DArray): Long2DArray {
+ return matrixTimes(this, other, Long2DArray::get, Long2DArray::set, Long::plus, Long::times, ::allocate)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Long2DArray::get)
+ }
+
+ open val determinant: Long? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+ else
+ return matrixDeterminant(this)
+ }
+
+ open val cofactorMatrix: Long2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, false)
+ return new
+ }
+
+ open val adjugateMatrix: Long2DArray? get() {
+ if (!isSquareShaped || columns <= 0)
+ return null
+
+ val new = allocate(columns, rows)
+ matrixCofactor(this, new, true)
+ return new
+ }
+
+ open val inverted: Long2DArray? get() {
+ val determinant = determinant ?: return null
+ if (determinant == 0L) return null
+ val adj = adjugateMatrix!!
+ adj.div(determinant)
+ return adj
+ }
+
+ open val transposed: Long2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Long2DArray() {
+ private val mem = LongArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Long {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Long) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Long2DArray" + super.toString()
+ }
+ }
+
+ private class View(private val parent: Long2DArray) : Long2DArray() {
+ override val rows: Int
+ get() = parent.rows
+ override val columns: Int
+ get() = parent.columns
+
+ override fun get(column: Int, row: Int): Long {
+ return parent[column, row]
+ }
+
+ override fun set(column: Int, row: Int, value: Long) {
+ throw UnsupportedOperationException("Read only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is View && parent == other.parent
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Long2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmStatic
+ fun unmodifiable(parent: Long2DArray): Long2DArray {
+ if (parent is View)
+ return parent
+ else
+ return View(parent)
+ }
+
+ @JvmField
+ val ZERO: Long2DArray = Impl(0, 0)
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/MatrixOps.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/MatrixOps.kt
new file mode 100644
index 0000000..1bfc820
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/MatrixOps.kt
@@ -0,0 +1,545 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.lang.StringBuilder
+import java.lang.ref.Reference
+import java.lang.ref.SoftReference
+import java.lang.ref.WeakReference
+import java.nio.Buffer
+
+internal class MatrixCache : ThreadLocal?>>() {
+ override fun initialValue(): ArrayList?> {
+ return ArrayList()
+ }
+}
+
+// template
+private inline fun ArrayList?>.load(index: Int, factory: (Int, Int) -> T): T {
+ while (size <= index) add(null)
+ var cached = this[index]?.get()
+
+ if (cached == null) {
+ cached = factory(index, index)
+ this[index] = if (index < 10) SoftReference(cached) else WeakReference(cached)
+ }
+
+ return cached
+}
+
+// template
+private inline fun loadMatrixMinor(
+ input: A,
+ output: B,
+ get: (A, Int, Int) -> N,
+ set: (B, Int, Int, N) -> Unit,
+ column: Int, row: Int
+) {
+ var tc = 0
+ var tr = 0
+
+ for (c in 0 until column) {
+ for (r in 0 until row) {
+ set.invoke(output, tc, tr++, get.invoke(input, c, r))
+ }
+
+ for (r in row + 1 until input.rows) {
+ set.invoke(output, tc, tr++, get.invoke(input, c, r))
+ }
+
+ tc++
+ }
+
+ for (c in column + 1 until input.columns) {
+ for (r in 0 until row) {
+ set.invoke(output, tc, tr++, get.invoke(input, c, r))
+ }
+
+ for (r in row + 1 until input.rows) {
+ set.invoke(output, tc, tr++, get.invoke(input, c, r))
+ }
+
+ tc++
+ }
+}
+
+// template
+private inline fun matrixCofactor(
+ input: Immutable,
+ output: Mutable,
+ adjugate: Boolean,
+ get: (Immutable, Int, Int) -> N,
+ set: (Mutable, Int, Int, N) -> Unit,
+ cache: ArrayList?>,
+ factory: (Int, Int) -> Mutable,
+ determinant: (Immutable, ArrayList?>) -> N,
+ neg: (N) -> N
+) {
+ input.requireIsSquare()
+ output.requireIsSquare()
+ input.requireSizeEquals(output)
+
+ if (input.columns == 0) {
+ return
+ } else if (input.columns == 1) {
+ set(output, 0, 0, get(input, 0, 0))
+ return
+ }
+
+ val target = output
+ val cached = cache.load(input.columns - 1, factory)
+
+ if (adjugate) {
+ for (column in input.columnIndices) {
+ for (row in input.rowIndices) {
+ loadMatrixMinor(input, cached, get, set, column, row)
+ val det = determinant(cached, cache)
+
+ if ((column + row) and 1 == 1) {
+ set(target, row, column, neg(det))
+ } else {
+ set(target, row, column, det)
+ }
+ }
+ }
+ } else {
+ for (column in input.columnIndices) {
+ for (row in input.rowIndices) {
+ loadMatrixMinor(input, cached, get, set, column, row)
+ val det = determinant(cached, cache)
+
+ if ((column + row) and 1 == 1) {
+ set(target, column, row, neg(det))
+ } else {
+ set(target, column, row, det)
+ }
+ }
+ }
+ }
+}
+
+// concrete
+private val floatCache = MatrixCache()
+private val doubleCache = MatrixCache()
+private val intCache = MatrixCache()
+private val longCache = MatrixCache()
+
+// templated
+internal fun matrixCofactor(
+ input: Float2DArray,
+ output: Float2DArray,
+ adjugate: Boolean,
+) = matrixCofactor(
+ input, output, adjugate,
+ Float2DArray::get, Float2DArray::set,
+ floatCache.get(), Float2DArray::allocate,
+ ::matrixDeterminant, Float::unaryMinus
+)
+
+// templated
+internal fun matrixCofactor(
+ input: Double2DArray,
+ output: Double2DArray,
+ adjugate: Boolean,
+) = matrixCofactor(
+ input, output, adjugate,
+ Double2DArray::get, Double2DArray::set,
+ doubleCache.get(), Double2DArray::allocate,
+ ::matrixDeterminant, Double::unaryMinus
+)
+
+internal fun matrixCofactor(
+ input: Int2DArray,
+ output: Int2DArray,
+ adjugate: Boolean,
+) = matrixCofactor(
+ input, output, adjugate,
+ Int2DArray::get, Int2DArray::set,
+ intCache.get(), Int2DArray::allocate,
+ ::matrixDeterminant, Int::unaryMinus
+)
+
+internal fun matrixCofactor(
+ input: Long2DArray,
+ output: Long2DArray,
+ adjugate: Boolean,
+) = matrixCofactor(
+ input, output, adjugate,
+ Long2DArray::get, Long2DArray::set,
+ longCache.get(), Long2DArray::allocate,
+ ::matrixDeterminant, Long::unaryMinus
+)
+
+internal fun matrixDeterminant(input: Float2DArray, cache: ArrayList?> = floatCache.get()): Float {
+ input.requireIsSquare()
+ require(input.columns >= 1) { "Can't compute determinant of matrix with no elements" }
+
+ if (input.columns == 1) {
+ return input[0, 0]
+ } else if (input.columns == 2) {
+ val a = input[0, 0]
+ val b = input[0, 1]
+ val c = input[1, 0]
+ val d = input[1, 0]
+ return a * d - b * c
+ } else {
+ val cached = cache.load(input.columns - 1, Float2DArray::allocate)
+ var result = 0f
+
+ for (i in 0 until input.columns) {
+ loadMatrixMinor(input, cached, Float2DArray::get, Float2DArray::set, i, 0)
+
+ if (i and 1 == 0) {
+ result += matrixDeterminant(cached, cache)
+ } else {
+ result -= matrixDeterminant(cached, cache)
+ }
+ }
+
+ return result
+ }
+}
+
+internal fun matrixDeterminant(input: Double2DArray, cache: ArrayList?> = doubleCache.get()): Double {
+ input.requireIsSquare()
+ require(input.columns >= 1) { "Can't compute determinant of matrix with no elements" }
+
+ if (input.columns == 1) {
+ return input[0, 0]
+ } else if (input.columns == 2) {
+ val a = input[0, 0]
+ val b = input[0, 1]
+ val c = input[1, 0]
+ val d = input[1, 0]
+ return a * d - b * c
+ } else {
+ val cached = cache.load(input.columns - 1, Double2DArray::allocate)
+ var result = 0.0
+
+ for (i in 0 until input.columns) {
+ loadMatrixMinor(input, cached, Double2DArray::get, Double2DArray::set, i, 0)
+
+ if (i and 1 == 0) {
+ result += matrixDeterminant(cached, cache)
+ } else {
+ result -= matrixDeterminant(cached, cache)
+ }
+ }
+
+ return result
+ }
+}
+
+internal fun matrixDeterminant(input: Int2DArray, cache: ArrayList?> = intCache.get()): Int {
+ input.requireIsSquare()
+ require(input.columns >= 1) { "Can't compute determinant of matrix with no elements" }
+
+ if (input.columns == 1) {
+ return input[0, 0]
+ } else if (input.columns == 2) {
+ val a = input[0, 0]
+ val b = input[0, 1]
+ val c = input[1, 0]
+ val d = input[1, 0]
+ return a * d - b * c
+ } else {
+ val cached = cache.load(input.columns - 1, Int2DArray::allocate)
+ var result = 0
+
+ for (i in 0 until input.columns) {
+ loadMatrixMinor(input, cached, Int2DArray::get, Int2DArray::set, i, 0)
+
+ if (i and 1 == 0) {
+ result += matrixDeterminant(cached, cache)
+ } else {
+ result -= matrixDeterminant(cached, cache)
+ }
+ }
+
+ return result
+ }
+}
+
+internal fun matrixDeterminant(input: Long2DArray, cache: ArrayList?> = longCache.get()): Long {
+ input.requireIsSquare()
+ require(input.columns >= 1) { "Can't compute determinant of matrix with no elements" }
+
+ if (input.columns == 1) {
+ return input[0, 0]
+ } else if (input.columns == 2) {
+ val a = input[0, 0]
+ val b = input[0, 1]
+ val c = input[1, 0]
+ val d = input[1, 0]
+ return a * d - b * c
+ } else {
+ val cached = cache.load(input.columns - 1, Long2DArray::allocate)
+ var result = 0L
+
+ for (i in 0 until input.columns) {
+ loadMatrixMinor(input, cached, Long2DArray::get, Long2DArray::set, i, 0)
+
+ if (i and 1 == 0) {
+ result += matrixDeterminant(cached, cache)
+ } else {
+ result -= matrixDeterminant(cached, cache)
+ }
+ }
+
+ return result
+ }
+}
+
+inline fun mulMatrixComponents(
+ a: M, b: M,
+ get: (M, Int, Int) -> N,
+ times: (N, N) -> N,
+ plus: (N, N) -> N,
+ column: Int,
+ row: Int
+): N {
+ require(a.columns == b.rows) { "Can't multiply incompatible matrices: A has ${a.columns} columns, B has ${b.rows} rows" }
+ var result = times(get(a, 0, row), get(b, column, 0))
+
+ for (i in 1 until a.columns) {
+ result = plus(result, times(get(a, i, row), get(b, column, i)))
+ }
+
+ return result
+}
+
+fun mulMatrixComponents(a: Float2DArray, b: Float2DArray, column: Int, row: Int): Float {
+ return mulMatrixComponents(a, b, Float2DArray::get, Float::times, Float::plus, column, row)
+}
+
+fun mulMatrixComponents(a: Double2DArray, b: Double2DArray, column: Int, row: Int): Double {
+ return mulMatrixComponents(a, b, Double2DArray::get, Double::times, Double::plus, column, row)
+}
+
+internal inline fun matrixToString(input: M, get: (M, Int, Int) -> Any?): String {
+ if (input.size == 0) {
+ return "[]"
+ }
+
+ val elements = ArrayList(input.size)
+ var maxWidth = 0
+
+ for (row in input.rowIndices) {
+ for (column in input.columnIndices) {
+ val elem = get(input, column, row).toString()
+ elements.add(elem)
+ maxWidth = maxWidth.coerceAtLeast(elem.length)
+ }
+ }
+
+ maxWidth++
+
+ val builder = StringBuilder()
+ builder.append("[\n")
+ val iterator = elements.iterator()
+
+ for (row in input.rowIndices) {
+ builder.append("\t")
+
+ for (column in input.columnIndices) {
+ val elem = iterator.next()
+ builder.append(" ".repeat(maxWidth - elem.length))
+ builder.append(elem)
+ builder.append(",")
+ }
+
+ builder.append("\n")
+ }
+
+ builder.append("]")
+
+ return builder.toString()
+}
+
+internal inline fun matrixEquals(a: M, b: Any?, equals: (M, M, Int, Int) -> Boolean): Boolean {
+ if (b == null || a.javaClass !== b.javaClass) return false
+ return matrixContentEquals(a, b as M, equals)
+}
+
+internal inline fun matrixContentEquals(a: M, b: M, equals: (M, M, Int, Int) -> Boolean): Boolean {
+ if (a.columns != b.columns || a.rows != b.rows) return false
+
+ for (column in a.columnIndices) {
+ for (row in a.rowIndices) {
+ if (!equals(a, b, column, row)) {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+internal inline fun matrixHashCode(input: M, get: (M, Int, Int) -> Int): Int {
+ var result = 0
+
+ for (column in input.columnIndices) {
+ for (row in input.rowIndices) {
+ result = result * 31 + get(input, column, row)
+ }
+ }
+
+ return result
+}
+
+internal inline fun matrixStoreColumnRow(input: M, target: B, get: (M, Int, Int) -> N, set: (B, N) -> Unit) {
+ for (column in 0 until input.columns) {
+ for (row in 0 until input.rows) {
+ set(target, get(input, column, row))
+ }
+ }
+}
+
+internal inline fun matrixStoreRowColumn(input: M, target: B, get: (M, Int, Int) -> N, set: (B, N) -> Unit) {
+ for (row in 0 until input.rows) {
+ for (column in 0 until input.columns) {
+ set(target, get(input, column, row))
+ }
+ }
+}
+
+internal inline fun matrixLoadColumnRow(input: M, target: B, set: (M, Int, Int, N) -> Unit, get: (B) -> N) {
+ for (column in 0 until input.columns) {
+ for (row in 0 until input.rows) {
+ set(input, column, row, get(target))
+ }
+ }
+}
+
+internal inline fun matrixLoadRowColumn(input: M, target: B, set: (M, Int, Int, N) -> Unit, get: (B) -> N) {
+ for (row in 0 until input.rows) {
+ for (column in 0 until input.columns) {
+ set(input, column, row, get(target))
+ }
+ }
+}
+
+internal inline fun matrixLoad(from: I, into: M, get: (I, Int, Int) -> N, set: (M, Int, Int, N) -> Unit): M {
+ from.requireSizeEquals(into)
+
+ for (column in 0 until from.columns) {
+ for (row in 0 until from.rows) {
+ set(into, column, row, get(from, column, row))
+ }
+ }
+
+ return into
+}
+
+internal inline fun matrixLoadTransposed(from: I, into: M, get: (I, Int, Int) -> N, set: (M, Int, Int, N) -> Unit): M {
+ require(from.columns == into.rows && from.rows == into.columns) { "Incompatible matrix for transposed load: from: ${from.columns} x ${from.rows}; into: ${into.columns} x ${into.rows}" }
+
+ for (column in 0 until from.columns) {
+ for (row in 0 until from.rows) {
+ set(into, row, column, get(from, column, row))
+ }
+ }
+
+ return into
+}
+
+internal inline fun matrixPlainOperator(
+ a: I, b: I,
+ get: (I, Int, Int) -> N,
+ operator: (N, N) -> N,
+ set: (M, Int, Int, N) -> Unit,
+ factory: (Int, Int) -> M
+): M {
+ a.requireSizeEquals(b)
+ val new = factory(a.columns, a.rows)
+
+ for (column in 0 until a.columns) {
+ for (row in 0 until a.rows) {
+ set(new, column, row, operator(get(a, column, row), get(b, column, row)))
+ }
+ }
+
+ return new
+}
+
+internal inline fun matrixPlainOperator(
+ a: I, b: N,
+ get: (I, Int, Int) -> N,
+ operator: (N, N) -> N,
+ set: (M, Int, Int, N) -> Unit,
+ factory: (Int, Int) -> M
+): M {
+ val new = factory(a.columns, a.rows)
+
+ for (column in 0 until a.columns) {
+ for (row in 0 until a.rows) {
+ set(new, column, row, operator(get(a, column, row), b))
+ }
+ }
+
+ return new
+}
+
+internal inline fun matrixPlainOperator(
+ a: M, b: I,
+ get: (I, Int, Int) -> N,
+ operator: (N, N) -> N,
+ set: (M, Int, Int, N) -> Unit,
+): M {
+ a.requireSizeEquals(b)
+
+ for (column in 0 until a.columns) {
+ for (row in 0 until a.rows) {
+ set(a, column, row, operator(get(a, column, row), get(b, column, row)))
+ }
+ }
+
+ return a
+}
+
+internal inline fun matrixPlainOperator(
+ a: M, b: N,
+ get: (M, Int, Int) -> N,
+ operator: (N, N) -> N,
+ set: (M, Int, Int, N) -> Unit
+): M {
+ for (column in 0 until a.columns) {
+ for (row in 0 until a.rows) {
+ set(a, column, row, operator(get(a, column, row), b))
+ }
+ }
+
+ return a
+}
+
+internal inline fun matrixTimes(
+ a: I, b: I,
+ get: (I, Int, Int) -> N,
+ set: (M, Int, Int, N) -> Unit,
+ plus: (N, N) -> N,
+ times: (N, N) -> N,
+ factory: (Int, Int) -> M
+): M {
+ require(a.columns == b.rows) { "Can't multiply incompatible matrices: A has ${a.columns} columns, B has ${b.rows} rows" }
+ val new = factory(b.columns, a.rows)
+
+ for (column in 0 until new.columns) {
+ for (row in 0 until new.rows) {
+ set(new, column, row, mulMatrixComponents(a, b, get, times, plus, column, row))
+ }
+ }
+
+ return new
+}
+
+private inline fun getOrElse(
+ get: (F, Int, Int) -> N,
+ input: F,
+ column: Int,
+ row: Int,
+ orElse: N
+): N {
+ if (column < input.columns && row < input.rows) {
+ return get(input, column, row)
+ } else {
+ return orElse
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Object2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Object2DArray.kt
new file mode 100644
index 0000000..15b5602
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Object2DArray.kt
@@ -0,0 +1,124 @@
+package ru.dbotthepony.kommons.arrays
+
+fun interface ObjectMatrixInitializer {
+ fun invoke(column: Int, row: Int): T
+}
+
+private fun initialize(columns: Int, rows: Int, initializer: ObjectMatrixInitializer): Array {
+ require(columns >= 0 && rows >= 0) { "Invalid dimensions: $columns x $rows" }
+
+ var c = 0
+ var r = 0
+
+ return Array(columns * rows) {
+ val value = initializer.invoke(c, r++)
+
+ if (r == rows) {
+ c++
+ r = 0
+ }
+
+ value
+ }
+}
+
+private fun initialize(columns: Int, rows: Int, value: Any?): Array {
+ require(columns >= 0 && rows >= 0) { "Invalid dimensions: $columns x $rows" }
+ return Array(columns * rows) { value }
+}
+
+class Object2DArray private constructor(
+ override val columns: Int,
+ override val rows: Int,
+ private val mem: Array
+) : Array2D() {
+ constructor(
+ columns: Int,
+ rows: Int,
+ initializer: ObjectMatrixInitializer
+ ) : this(columns, rows, initialize(columns, rows, initializer))
+
+ constructor(
+ columns: Int,
+ rows: Int,
+ value: T
+ ) : this(columns, rows, initialize(columns, rows, value))
+
+ constructor(
+ parent: Object2DArray
+ ) : this(parent.columns, parent.rows, Array(parent.size) { parent.mem[it] })
+
+ init {
+ require(columns >= 0 && rows >= 0) { "Invalid dimensions: $columns x $rows" }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this) { a, b, c -> a[b, c].toString() }
+ }
+
+ fun store(into: Object2DArray) {
+ into.load(this)
+ }
+
+ fun load(other: Object2DArray) {
+ requireSizeEquals(other)
+
+ for (column in columnIndices) {
+ for (row in rowIndices) {
+ this[column, row] = other[column, row]
+ }
+ }
+ }
+
+ operator fun get(column: Int, row: Int): T {
+ try {
+ return mem[column * rows + row] as T
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ operator fun set(column: Int, row: Int, value: T) {
+ try {
+ mem[column * rows + row] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ val transposed get(): Object2DArray {
+ val new = nulls(rows, columns)
+
+ for (column in columnIndices) {
+ for (row in rowIndices) {
+ new[column, row] = this[row, column]
+ }
+ }
+
+ return new as Object2DArray
+ }
+
+ companion object {
+ @JvmStatic
+ fun nulls(columns: Int, rows: Int): Object2DArray {
+ require(columns >= 0 && rows >= 0) { "Invalid dimensions: $columns x $rows" }
+ return Object2DArray(columns, rows, arrayOfNulls(columns * rows))
+ }
+
+ @JvmField
+ val EMPTY = nulls(0, 0)
+
+ @JvmStatic
+ fun empty(): Object2DArray {
+ return EMPTY as Object2DArray
+ }
+ }
+}
diff --git a/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Short2DArray.kt b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Short2DArray.kt
new file mode 100644
index 0000000..d2b57f7
--- /dev/null
+++ b/collect/src/main/kotlin/ru/dbotthepony/kommons/arrays/Short2DArray.kt
@@ -0,0 +1,82 @@
+package ru.dbotthepony.kommons.arrays
+
+import java.nio.ByteBuffer
+import java.nio.ShortBuffer
+
+abstract class Short2DArray : Array2D() {
+ abstract operator fun get(column: Int, row: Int): Short
+ abstract operator fun set(column: Int, row: Int, value: Short)
+
+ fun storeColumnRow(target: ByteBuffer) = matrixStoreColumnRow(this, target, Short2DArray::get, ByteBuffer::putShort)
+ fun storeRowColumn(target: ByteBuffer) = matrixStoreRowColumn(this, target, Short2DArray::get, ByteBuffer::putShort)
+ fun storeColumnRow(target: ShortBuffer) = matrixStoreColumnRow(this, target, Short2DArray::get, ShortBuffer::put)
+ fun storeRowColumn(target: ShortBuffer) = matrixStoreRowColumn(this, target, Short2DArray::get, ShortBuffer::put)
+ fun store(target: Short2DArray) { target.load(this) }
+
+ fun loadColumnRow(target: ByteBuffer) = matrixLoadColumnRow(this, target, Short2DArray::set, ByteBuffer::getShort)
+ fun loadRowColumn(target: ByteBuffer) = matrixLoadRowColumn(this, target, Short2DArray::set, ByteBuffer::getShort)
+ fun loadColumnRow(target: ShortBuffer) = matrixLoadColumnRow(this, target, Short2DArray::set, ShortBuffer::get)
+ fun loadRowColumn(target: ShortBuffer) = matrixLoadRowColumn(this, target, Short2DArray::set, ShortBuffer::get)
+ fun load(from: Short2DArray) { matrixLoad(from, this, Short2DArray::get, Short2DArray::set) }
+ fun storeTransposed(from: Short2DArray) { matrixLoadTransposed(from, this, Short2DArray::get, Short2DArray::set) }
+
+ open fun contentEquals(other: Short2DArray): Boolean = matrixContentEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+
+ override fun equals(other: Any?): Boolean {
+ return matrixEquals(this, other) { a, b, c, d -> a[c, d] == b[c, d] }
+ }
+
+ override fun hashCode(): Int {
+ return matrixHashCode(this) { a, b, c -> a[b, c].hashCode() }
+ }
+
+ override fun toString(): String {
+ return matrixToString(this, Short2DArray::get)
+ }
+
+ open val transposed: Short2DArray get() {
+ return allocate(rows, columns).also { it.load(this) }
+ }
+
+ private class Impl(
+ override val columns: Int,
+ override val rows: Int
+ ) : Short2DArray() {
+ private val mem = ShortArray(columns * rows)
+
+ override fun get(column: Int, row: Int): Short {
+ try {
+ return mem[column + row * columns]
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Short) {
+ try {
+ mem[column + row * columns] = value
+ } catch (err: IndexOutOfBoundsException) {
+ throw IndexOutOfBoundsException("Matrix index out of bounds: $column x $row, while this array has dimensions of $columns x $rows")
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Impl) return sizeEquals(other) && mem.contentEquals(other.mem)
+ return super.equals(other)
+ }
+
+ override fun toString(): String {
+ return "Short2DArray" + super.toString()
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun allocate(columns: Int, rows: Int): Short2DArray {
+ return Impl(columns, rows)
+ }
+
+ @JvmField
+ val ZERO: Short2DArray = Impl(0, 0)
+ }
+}
diff --git a/core/src/main/kotlin/ru/dbotthepony/kommons/core/Struct.kt b/core/src/main/kotlin/ru/dbotthepony/kommons/core/Struct.kt
new file mode 100644
index 0000000..1062cf2
--- /dev/null
+++ b/core/src/main/kotlin/ru/dbotthepony/kommons/core/Struct.kt
@@ -0,0 +1,95 @@
+
+@file:Suppress("unused")
+
+package ru.dbotthepony.kommons.core
+
+interface IStruct2f {
+ operator fun component1(): Float
+ operator fun component2(): Float
+}
+
+interface IStruct3f : IStruct2f {
+ operator fun component3(): Float
+}
+
+interface IStruct4f : IStruct3f {
+ operator fun component4(): Float
+}
+
+interface IStruct2d {
+ operator fun component1(): Double
+ operator fun component2(): Double
+}
+
+interface IStruct3d : IStruct2d {
+ operator fun component3(): Double
+}
+
+interface IStruct4d : IStruct3d {
+ operator fun component4(): Double
+}
+
+interface IStruct2i {
+ operator fun component1(): Int
+ operator fun component2(): Int
+}
+
+interface IStruct3i : IStruct2i {
+ operator fun component3(): Int
+}
+
+interface IStruct4i : IStruct3i {
+ operator fun component4(): Int
+}
+
+interface IStruct2l {
+ operator fun component1(): Long
+ operator fun component2(): Long
+}
+
+interface IStruct3l : IStruct2l {
+ operator fun component3(): Long
+}
+
+interface IStruct4l : IStruct3l {
+ operator fun component4(): Long
+}
+
+interface IStruct2s {
+ operator fun component1(): Short
+ operator fun component2(): Short
+}
+
+interface IStruct3s : IStruct2s {
+ operator fun component3(): Short
+}
+
+interface IStruct4s : IStruct3s {
+ operator fun component4(): Short
+}
+
+interface IStruct2b {
+ operator fun component1(): Byte
+ operator fun component2(): Byte
+}
+
+interface IStruct3b : IStruct2b {
+ operator fun component3(): Byte
+}
+
+interface IStruct4b : IStruct3b {
+ operator fun component4(): Byte
+}
+
+interface IStruct2z {
+ operator fun component1(): Boolean
+ operator fun component2(): Boolean
+}
+
+interface IStruct3z : IStruct2z {
+ operator fun component3(): Boolean
+}
+
+interface IStruct4z : IStruct3z {
+ operator fun component4(): Boolean
+}
diff --git a/gradle.properties b/gradle.properties
index a2884da..c6c4729 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,3 +14,4 @@ networkingVersion=1.0
mathVersion=1.0
collectVersion=1.0
guavaVersion=1.0
+linearAlgebraVersion=1.0
diff --git a/linear-algebra/build.gradle.kts b/linear-algebra/build.gradle.kts
new file mode 100644
index 0000000..e8bd09e
--- /dev/null
+++ b/linear-algebra/build.gradle.kts
@@ -0,0 +1,47 @@
+
+plugins {
+ kotlin("jvm")
+}
+
+val linearAlgebraVersion: String by project
+val specifyKotlinAsDependency: String by project
+
+version = linearAlgebraVersion
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+
+ implementation(project(":collect"))
+ implementation(project(":math"))
+ implementation(project(":core"))
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
+
+kotlin {
+ jvmToolchain(17)
+}
+
+publishing {
+ publications {
+ create("mavenJava") {
+ from(components["java"])
+
+ pom {
+ dependencies {
+ if (specifyKotlinAsDependency.toBoolean()) implementation(kotlin("stdlib"))
+
+ implementation(project(":collect"))
+ implementation(project(":math"))
+ implementation(project(":core"))
+ }
+ }
+ }
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/math/VectorLerp.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/math/VectorLerp.kt
new file mode 100644
index 0000000..f4672f5
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/math/VectorLerp.kt
@@ -0,0 +1,81 @@
+package ru.dbotthepony.kommons.math
+
+import ru.dbotthepony.kommons.vector.Vector2d
+import ru.dbotthepony.kommons.vector.Vector2f
+import ru.dbotthepony.kommons.vector.Vector3d
+import ru.dbotthepony.kommons.vector.Vector3f
+import ru.dbotthepony.kommons.vector.Vector4d
+import ru.dbotthepony.kommons.vector.Vector4f
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Float, a: Vector2f, b: Vector2f): Vector2f {
+ if (t <= 0f)
+ return a
+ else if (t >= 1f)
+ return b
+
+ return a + (b - a) * t
+}
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Float, a: Vector3f, b: Vector3f): Vector3f {
+ if (t <= 0f)
+ return a
+ else if (t >= 1f)
+ return b
+
+ return a + (b - a) * t
+}
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Float, a: Vector4f, b: Vector4f): Vector4f {
+ if (t <= 0f)
+ return a
+ else if (t >= 1f)
+ return b
+
+ return a + (b - a) * t
+}
+
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Double, a: Vector2d, b: Vector2d): Vector2d {
+ if (t <= 0.0)
+ return a
+ else if (t >= 1.0)
+ return b
+
+ return a + (b - a) * t
+}
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Double, a: Vector3d, b: Vector3d): Vector3d {
+ if (t <= 0.0)
+ return a
+ else if (t >= 1.0)
+ return b
+
+ return a + (b - a) * t
+}
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Double, a: Vector4d, b: Vector4d): Vector4d {
+ if (t <= 0.0)
+ return a
+ else if (t >= 1.0)
+ return b
+
+ return a + (b - a) * t
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix2d.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix2d.kt
new file mode 100644
index 0000000..7cd1fdb
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix2d.kt
@@ -0,0 +1,310 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Double2DArray
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.vector.Vector2d
+import java.nio.ByteBuffer
+import java.nio.DoubleBuffer
+
+sealed class Matrix2d : Double2DArray() {
+ abstract var c00: Double
+ abstract var c01: Double
+ abstract var c10: Double
+ abstract var c11: Double
+
+ fun c00() = c00
+ fun c00(value: Double): Matrix2d { c00 = value; return this }
+ fun c01() = c01
+ fun c01(value: Double): Matrix2d { c01 = value; return this }
+ fun c10() = c10
+ fun c10(value: Double): Matrix2d { c10 = value; return this }
+ fun c11() = c11
+ fun c11(value: Double): Matrix2d { c11 = value; return this }
+
+ var r00: Double get() = c00; set(value) { c00 = value } // row 0 column 0
+ var r01: Double get() = c10; set(value) { c10 = value } // row 1 column 0
+ var r10: Double get() = c01; set(value) { c01 = value } // row 0 column 1
+ var r11: Double get() = c11; set(value) { c11 = value } // row 1 column 1
+
+ fun r00() = r00
+ fun r00(value: Double): Matrix2d { r00 = value; return this }
+ fun r01() = r01
+ fun r01(value: Double): Matrix2d { r01 = value; return this }
+ fun r10() = r10
+ fun r10(value: Double): Matrix2d { r10 = value; return this }
+ fun r11() = r11
+ fun r11(value: Double): Matrix2d { r11 = value; return this }
+
+ final override val transposed get() =
+ columnMajor(
+ c00 = r00,
+ c01 = r01,
+ c10 = r10,
+ c11 = r11,
+ )
+
+ final override val rows: Int
+ get() = 2
+ final override val columns: Int
+ get() = 2
+
+ final override fun get(column: Int, row: Int): Double {
+ return when (row or (column shl 1)) {
+ C00 -> c00
+ C01 -> c01
+ C10 -> c10
+ C11 -> c11
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ when (column or (row shl 2)) {
+ C00 -> c00 = value
+ C01 -> c01 = value
+ C10 -> c10 = value
+ C11 -> c11 = value
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ final override val determinant: Double
+ get() = super.determinant!!
+ final override val cofactorMatrix
+ get() = from(super.cofactorMatrix!!)
+ final override val adjugateMatrix
+ get() = from(super.adjugateMatrix!!)
+ final override val inversed
+ get() = super.inversed?.let { from(it) }
+
+ fun mul(other: Matrix2d): Matrix2d {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ this.c00 = c00
+ this.c01 = c01
+ this.c10 = c10
+ this.c11 = c11
+ return this
+ }
+
+ fun mulIntoOther(other: Matrix2d): Matrix2d {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ other.c00 = c00
+ other.c01 = c01
+ other.c10 = c10
+ other.c11 = c11
+ return other
+ }
+
+ fun mul(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ ): Matrix2d {
+ val fc00 = this.c00 * c00 + this.c10 * c01
+ val fc01 = this.c01 * c00 + this.c11 * c01
+ val fc10 = this.c00 * c10 + this.c10 * c11
+ val fc11 = this.c01 * c10 + this.c11 * c11
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c10 = fc10
+ this.c11 = fc11
+ return this
+ }
+
+ fun mulIntoThis(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ ): Matrix2d {
+ val fc00 = c00 * this.c00 + c10 * this.c01
+ val fc01 = c01 * this.c00 + c11 * this.c01
+ val fc10 = c00 * this.c10 + c10 * this.c11
+ val fc11 = c01 * this.c10 + c11 * this.c11
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c10 = fc10
+ this.c11 = fc11
+ return this
+ }
+
+ final override fun add(other: Double): Matrix2d {
+ return super.add(other) as Matrix2d
+ }
+
+ final override fun sub(other: Double): Matrix2d {
+ return super.sub(other) as Matrix2d
+ }
+
+ final override fun mul(other: Double): Matrix2d {
+ return super.mul(other) as Matrix2d
+ }
+
+ final override fun div(other: Double): Matrix2d {
+ return super.div(other) as Matrix2d
+ }
+
+ final override fun add(other: Double2DArray): Matrix2d {
+ return super.add(other) as Matrix2d
+ }
+
+ final override fun sub(other: Double2DArray): Matrix2d {
+ return super.sub(other) as Matrix2d
+ }
+
+ fun to3d(filler: Double) = Matrix3d.from(this, filler)
+ fun to3d() = Matrix3d.from(this)
+ fun to4d(filler: Double) = Matrix4d.from(this, filler)
+ fun to4d() = Matrix4d.from(this)
+
+ fun copy(): Matrix2d {
+ return Impl(c00, c01, c10, c11)
+ }
+
+ @JvmOverloads
+ fun scale(x: Double = 1.0, y: Double = 1.0): Matrix2d {
+ return mul(c00 = x, c11 = y)
+ }
+
+ fun scale(value: IStruct2d) = scale(value.component1(), value.component2())
+ fun scale(value: Vector2d) = scale(value.component1(), value.component2())
+
+ @JvmOverloads
+ fun preScale(x: Double = 1.0, y: Double = 1.0): Matrix2d {
+ return mulIntoThis(c00 = x, c11 = y)
+ }
+
+ fun preScale(value: IStruct2d) = preScale(value.component1(), value.component2())
+ fun preScale(value: Vector2d) = preScale(value.component1(), value.component2())
+
+ fun translate(x: Double) = mul(c10 = x)
+ fun preTranslate(x: Double) = mulIntoThis(c10 = x)
+
+ private data class Impl(
+ override var c00: Double = 1.0,
+ override var c01: Double = 0.0,
+ override var c10: Double = 0.0,
+ override var c11: Double = 1.0,
+ ) : Matrix2d() {
+ override fun toString(): String {
+ return "Matrix2d" + super.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Matrix2d) return false
+
+ return this === other || c00 == other.c00 &&
+ c01 == other.c01 &&
+ c10 == other.c10 &&
+ c11 == other.c11
+ }
+ }
+
+ private class View(private val parent: Matrix2d) : Matrix2d() {
+ override var c00: Double get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c01: Double get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c10: Double get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c11: Double get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ throw UnsupportedOperationException("Read-Only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this || parent == other
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun all(value: Double) = construct2(value, ::columnMajor)
+
+ @JvmStatic
+ fun zero() = all(0.0)
+
+ @JvmStatic
+ fun identity() = columnMajor()
+
+ @JvmStatic
+ fun unmodifiable(value: Matrix2d): Matrix2d {
+ if (value is View)
+ return value
+ else
+ return View(value)
+ }
+
+ @JvmStatic
+ fun columnMajor(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ ): Matrix2d {
+ return Impl(c00, c01, c10, c11)
+ }
+
+ @JvmStatic
+ fun rowMajor(
+ r00: Double = 1.0, r01: Double = 0.0,
+ r10: Double = 0.0, r11: Double = 1.0,
+ ): Matrix2d {
+ return Impl(
+ r00, r10,
+ r01, r11,
+ )
+ }
+
+ @JvmStatic
+ fun from(matrix: Double2DArray, filler: Double) = construct2(matrix, Double2DArray::get, ::columnMajor, filler)
+ @JvmStatic
+ fun from(matrix: Double2DArray) = construct2(matrix, Double2DArray::get, ::columnMajor, 0.0)
+ @JvmStatic
+ fun from(matrix: Matrix2d) = construct2(matrix, Double2DArray::get, ::columnMajor)
+ @JvmStatic
+ fun from(matrix: Matrix3d) = construct2(matrix, Double2DArray::get, ::columnMajor)
+ @JvmStatic
+ fun from(matrix: Matrix4d) = construct2(matrix, Double2DArray::get, ::columnMajor)
+
+ @JvmStatic
+ fun fromTransposed(matrix: Double2DArray) = construct2(matrix, Double2DArray::get, Companion::rowMajor, 0.0)
+ @JvmStatic
+ fun fromTransposed(matrix: Double2DArray, filler: Double) = construct2(matrix, Double2DArray::get, Companion::rowMajor, filler)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix2d) = construct2(matrix, Double2DArray::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix3d) = construct2(matrix, Double2DArray::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix4d) = construct2(matrix, Double2DArray::get, Companion::rowMajor)
+
+ @JvmStatic
+ fun fromColumnMajor(buffer: DoubleBuffer) = construct2(buffer, DoubleBuffer::get, ::columnMajor)
+ @JvmStatic
+ fun fromColumnMajor(buffer: ByteBuffer) = construct2(buffer, ByteBuffer::getDouble, ::columnMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: DoubleBuffer) = construct2(buffer, DoubleBuffer::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: ByteBuffer) = construct2(buffer, ByteBuffer::getDouble, Companion::rowMajor)
+
+ // kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
+ const val C00 = (0 or (0 shl 1))
+ const val C01 = (0 or (1 shl 1))
+ const val C10 = (1 or (0 shl 1))
+ const val C11 = (1 or (1 shl 1))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix2f.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix2f.kt
new file mode 100644
index 0000000..1eac46b
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix2f.kt
@@ -0,0 +1,310 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Float2DArray
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.vector.Vector2f
+import java.nio.ByteBuffer
+import java.nio.FloatBuffer
+
+sealed class Matrix2f : Float2DArray() {
+ abstract var c00: Float
+ abstract var c01: Float
+ abstract var c10: Float
+ abstract var c11: Float
+
+ fun c00() = c00
+ fun c00(value: Float): Matrix2f { c00 = value; return this }
+ fun c01() = c01
+ fun c01(value: Float): Matrix2f { c01 = value; return this }
+ fun c10() = c10
+ fun c10(value: Float): Matrix2f { c10 = value; return this }
+ fun c11() = c11
+ fun c11(value: Float): Matrix2f { c11 = value; return this }
+
+ var r00: Float get() = c00; set(value) { c00 = value } // row 0 column 0
+ var r01: Float get() = c10; set(value) { c10 = value } // row 1 column 0
+ var r10: Float get() = c01; set(value) { c01 = value } // row 0 column 1
+ var r11: Float get() = c11; set(value) { c11 = value } // row 1 column 1
+
+ fun r00() = r00
+ fun r00(value: Float): Matrix2f { r00 = value; return this }
+ fun r01() = r01
+ fun r01(value: Float): Matrix2f { r01 = value; return this }
+ fun r10() = r10
+ fun r10(value: Float): Matrix2f { r10 = value; return this }
+ fun r11() = r11
+ fun r11(value: Float): Matrix2f { r11 = value; return this }
+
+ final override val transposed get() =
+ columnMajor(
+ c00 = r00,
+ c01 = r01,
+ c10 = r10,
+ c11 = r11,
+ )
+
+ final override val rows: Int
+ get() = 2
+ final override val columns: Int
+ get() = 2
+
+ final override fun get(column: Int, row: Int): Float {
+ return when (row or (column shl 1)) {
+ C00 -> c00
+ C01 -> c01
+ C10 -> c10
+ C11 -> c11
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ when (column or (row shl 2)) {
+ C00 -> c00 = value
+ C01 -> c01 = value
+ C10 -> c10 = value
+ C11 -> c11 = value
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ final override val determinant: Float
+ get() = super.determinant!!
+ final override val cofactorMatrix
+ get() = from(super.cofactorMatrix!!)
+ final override val adjugateMatrix
+ get() = from(super.adjugateMatrix!!)
+ final override val inversed
+ get() = super.inversed?.let { from(it) }
+
+ fun mul(other: Matrix2f): Matrix2f {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ this.c00 = c00
+ this.c01 = c01
+ this.c10 = c10
+ this.c11 = c11
+ return this
+ }
+
+ fun mulIntoOther(other: Matrix2f): Matrix2f {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ other.c00 = c00
+ other.c01 = c01
+ other.c10 = c10
+ other.c11 = c11
+ return other
+ }
+
+ fun mul(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ ): Matrix2f {
+ val fc00 = this.c00 * c00 + this.c10 * c01
+ val fc01 = this.c01 * c00 + this.c11 * c01
+ val fc10 = this.c00 * c10 + this.c10 * c11
+ val fc11 = this.c01 * c10 + this.c11 * c11
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c10 = fc10
+ this.c11 = fc11
+ return this
+ }
+
+ fun mulIntoThis(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ ): Matrix2f {
+ val fc00 = c00 * this.c00 + c10 * this.c01
+ val fc01 = c01 * this.c00 + c11 * this.c01
+ val fc10 = c00 * this.c10 + c10 * this.c11
+ val fc11 = c01 * this.c10 + c11 * this.c11
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c10 = fc10
+ this.c11 = fc11
+ return this
+ }
+
+ final override fun add(other: Float): Matrix2f {
+ return super.add(other) as Matrix2f
+ }
+
+ final override fun sub(other: Float): Matrix2f {
+ return super.sub(other) as Matrix2f
+ }
+
+ final override fun mul(other: Float): Matrix2f {
+ return super.mul(other) as Matrix2f
+ }
+
+ final override fun div(other: Float): Matrix2f {
+ return super.div(other) as Matrix2f
+ }
+
+ final override fun add(other: Float2DArray): Matrix2f {
+ return super.add(other) as Matrix2f
+ }
+
+ final override fun sub(other: Float2DArray): Matrix2f {
+ return super.sub(other) as Matrix2f
+ }
+
+ fun to3f(filler: Float) = Matrix3f.from(this, filler)
+ fun to3f() = Matrix3f.from(this)
+ fun to4f(filler: Float) = Matrix4f.from(this, filler)
+ fun to4f() = Matrix4f.from(this)
+
+ fun copy(): Matrix2f {
+ return Impl(c00, c01, c10, c11)
+ }
+
+ @JvmOverloads
+ fun scale(x: Float = 1f, y: Float = 1f): Matrix2f {
+ return mul(c00 = x, c11 = y)
+ }
+
+ fun scale(value: IStruct2f) = scale(value.component1(), value.component2())
+ fun scale(value: Vector2f) = scale(value.component1(), value.component2())
+
+ @JvmOverloads
+ fun preScale(x: Float = 1f, y: Float = 1f): Matrix2f {
+ return mulIntoThis(c00 = x, c11 = y)
+ }
+
+ fun preScale(value: IStruct2f) = preScale(value.component1(), value.component2())
+ fun preScale(value: Vector2f) = preScale(value.component1(), value.component2())
+
+ fun translate(x: Float) = mul(c10 = x)
+ fun preTranslate(x: Float) = mulIntoThis(c10 = x)
+
+ private data class Impl(
+ override var c00: Float = 1f,
+ override var c01: Float = 0f,
+ override var c10: Float = 0f,
+ override var c11: Float = 1f,
+ ) : Matrix2f() {
+ override fun toString(): String {
+ return "Matrix2f" + super.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Matrix2f) return false
+
+ return this === other || c00 == other.c00 &&
+ c01 == other.c01 &&
+ c10 == other.c10 &&
+ c11 == other.c11
+ }
+ }
+
+ private class View(private val parent: Matrix2f) : Matrix2f() {
+ override var c00: Float get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c01: Float get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c10: Float get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c11: Float get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ throw UnsupportedOperationException("Read-Only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this || parent == other
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun all(value: Float) = construct2(value, ::columnMajor)
+
+ @JvmStatic
+ fun zero() = all(0f)
+
+ @JvmStatic
+ fun identity() = columnMajor()
+
+ @JvmStatic
+ fun unmodifiable(value: Matrix2f): Matrix2f {
+ if (value is View)
+ return value
+ else
+ return View(value)
+ }
+
+ @JvmStatic
+ fun columnMajor(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ ): Matrix2f {
+ return Impl(c00, c01, c10, c11)
+ }
+
+ @JvmStatic
+ fun rowMajor(
+ r00: Float = 1f, r01: Float = 0f,
+ r10: Float = 0f, r11: Float = 1f,
+ ): Matrix2f {
+ return Impl(
+ r00, r10,
+ r01, r11,
+ )
+ }
+
+ @JvmStatic
+ fun from(matrix: Float2DArray, filler: Float) = construct2(matrix, Float2DArray::get, ::columnMajor, filler)
+ @JvmStatic
+ fun from(matrix: Float2DArray) = construct2(matrix, Float2DArray::get, ::columnMajor, 0f)
+ @JvmStatic
+ fun from(matrix: Matrix2f) = construct2(matrix, Float2DArray::get, ::columnMajor)
+ @JvmStatic
+ fun from(matrix: Matrix3f) = construct2(matrix, Float2DArray::get, ::columnMajor)
+ @JvmStatic
+ fun from(matrix: Matrix4f) = construct2(matrix, Float2DArray::get, ::columnMajor)
+
+ @JvmStatic
+ fun fromTransposed(matrix: Float2DArray) = construct2(matrix, Float2DArray::get, Companion::rowMajor, 0f)
+ @JvmStatic
+ fun fromTransposed(matrix: Float2DArray, filler: Float) = construct2(matrix, Float2DArray::get, Companion::rowMajor, filler)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix2f) = construct2(matrix, Float2DArray::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix3f) = construct2(matrix, Float2DArray::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix4f) = construct2(matrix, Float2DArray::get, Companion::rowMajor)
+
+ @JvmStatic
+ fun fromColumnMajor(buffer: FloatBuffer) = construct2(buffer, FloatBuffer::get, ::columnMajor)
+ @JvmStatic
+ fun fromColumnMajor(buffer: ByteBuffer) = construct2(buffer, ByteBuffer::getFloat, ::columnMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: FloatBuffer) = construct2(buffer, FloatBuffer::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: ByteBuffer) = construct2(buffer, ByteBuffer::getFloat, Companion::rowMajor)
+
+ // kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
+ const val C00 = (0 or (0 shl 1))
+ const val C01 = (0 or (1 shl 1))
+ const val C10 = (1 or (0 shl 1))
+ const val C11 = (1 or (1 shl 1))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3d.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3d.kt
new file mode 100644
index 0000000..00f933e
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3d.kt
@@ -0,0 +1,496 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Double2DArray
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.vector.Vector2d
+import ru.dbotthepony.kommons.vector.Vector3d
+import java.nio.ByteBuffer
+import java.nio.DoubleBuffer
+import kotlin.math.cos
+import kotlin.math.sin
+
+sealed class Matrix3d : Double2DArray() {
+ abstract var c00: Double
+ abstract var c01: Double
+ abstract var c02: Double
+ abstract var c10: Double
+ abstract var c11: Double
+ abstract var c12: Double
+ abstract var c20: Double
+ abstract var c21: Double
+ abstract var c22: Double
+
+ fun c00() = c00
+ fun c00(value: Double): Matrix3d { c00 = value; return this }
+ fun c01() = c01
+ fun c01(value: Double): Matrix3d { c01 = value; return this }
+ fun c02() = c02
+ fun c02(value: Double): Matrix3d { c02 = value; return this }
+ fun c10() = c10
+ fun c10(value: Double): Matrix3d { c10 = value; return this }
+ fun c11() = c11
+ fun c11(value: Double): Matrix3d { c11 = value; return this }
+ fun c12() = c12
+ fun c12(value: Double): Matrix3d { c12 = value; return this }
+ fun c20() = c20
+ fun c20(value: Double): Matrix3d { c20 = value; return this }
+ fun c21() = c21
+ fun c21(value: Double): Matrix3d { c21 = value; return this }
+ fun c22() = c22
+ fun c22(value: Double): Matrix3d { c22 = value; return this }
+
+ var r00: Double get() = c00; set(value) { c00 = value } // row 0 column 0
+ var r01: Double get() = c10; set(value) { c10 = value } // row 1 column 0
+ var r02: Double get() = c20; set(value) { c20 = value } // row 2 column 0
+ var r10: Double get() = c01; set(value) { c01 = value } // row 0 column 1
+ var r11: Double get() = c11; set(value) { c11 = value } // row 1 column 1
+ var r12: Double get() = c21; set(value) { c21 = value } // row 2 column 1
+ var r20: Double get() = c02; set(value) { c02 = value } // row 0 column 2
+ var r21: Double get() = c12; set(value) { c12 = value } // row 1 column 2
+ var r22: Double get() = c22; set(value) { c22 = value } // row 2 column 2
+
+ fun r00() = r00
+ fun r00(value: Double): Matrix3d { r00 = value; return this }
+ fun r01() = r01
+ fun r01(value: Double): Matrix3d { r01 = value; return this }
+ fun r02() = r02
+ fun r02(value: Double): Matrix3d { r02 = value; return this }
+ fun r10() = r10
+ fun r10(value: Double): Matrix3d { r10 = value; return this }
+ fun r11() = r11
+ fun r11(value: Double): Matrix3d { r11 = value; return this }
+ fun r12() = r12
+ fun r12(value: Double): Matrix3d { r12 = value; return this }
+ fun r20() = r20
+ fun r20(value: Double): Matrix3d { r20 = value; return this }
+ fun r21() = r21
+ fun r21(value: Double): Matrix3d { r21 = value; return this }
+ fun r22() = r22
+ fun r22(value: Double): Matrix3d { r22 = value; return this }
+
+ final override val rows: Int
+ get() = 3
+ final override val columns: Int
+ get() = 3
+
+ final override val determinant: Double
+ get() = super.determinant!!
+ final override val cofactorMatrix
+ get() = from(super.cofactorMatrix!!)
+ final override val adjugateMatrix
+ get() = from(super.adjugateMatrix!!)
+ final override val inversed
+ get() = super.inversed?.let { from(it) }
+
+ final override val transposed get() =
+ columnMajor(
+ c00 = r00,
+ c01 = r01,
+ c02 = r02,
+ c10 = r10,
+ c11 = r11,
+ c12 = r12,
+ c20 = r20,
+ c21 = r21,
+ c22 = r22,
+ )
+
+ final override fun get(column: Int, row: Int): Double {
+ return when (column or (row shl 2)) {
+ C00 -> c00
+ C01 -> c01
+ C02 -> c02
+ C10 -> c10
+ C11 -> c11
+ C12 -> c12
+ C20 -> c20
+ C21 -> c21
+ C22 -> c22
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ when (column or (row shl 2)) {
+ C00 -> c00 = value
+ C01 -> c01 = value
+ C02 -> c02 = value
+ C10 -> c10 = value
+ C11 -> c11 = value
+ C12 -> c12 = value
+ C20 -> c20 = value
+ C21 -> c21 = value
+ C22 -> c22 = value
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ fun mul(other: Matrix3d): Matrix3d {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ this.c00 = c00
+ this.c01 = c01
+ this.c02 = c02
+ this.c10 = c10
+ this.c11 = c11
+ this.c12 = c12
+ this.c20 = c20
+ this.c21 = c21
+ this.c22 = c22
+ return this
+ }
+
+ fun mulIntoOther(other: Matrix3d): Matrix3d {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ other.c00 = c00
+ other.c01 = c01
+ other.c02 = c02
+ other.c10 = c10
+ other.c11 = c11
+ other.c12 = c12
+ other.c20 = c20
+ other.c21 = c21
+ other.c22 = c22
+ return other
+ }
+
+ fun mul(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c02: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ c12: Double = 0.0,
+ c20: Double = 0.0,
+ c21: Double = 0.0,
+ c22: Double = 1.0,
+ ): Matrix3d {
+ val fc00 = this.c00 * c00 + this.c10 * c01 + this.c20 * c02
+ val fc01 = this.c01 * c00 + this.c11 * c01 + this.c21 * c02
+ val fc02 = this.c02 * c00 + this.c12 * c01 + this.c22 * c02
+ val fc10 = this.c00 * c10 + this.c10 * c11 + this.c20 * c12
+ val fc11 = this.c01 * c10 + this.c11 * c11 + this.c21 * c12
+ val fc12 = this.c02 * c10 + this.c12 * c11 + this.c22 * c12
+ val fc20 = this.c00 * c20 + this.c10 * c21 + this.c20 * c22
+ val fc21 = this.c01 * c20 + this.c11 * c21 + this.c21 * c22
+ val fc22 = this.c02 * c20 + this.c12 * c21 + this.c22 * c22
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ return this
+ }
+
+ fun mulIntoThis(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c02: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ c12: Double = 0.0,
+ c20: Double = 0.0,
+ c21: Double = 0.0,
+ c22: Double = 1.0,
+ ): Matrix3d {
+ val fc00 = c00 * this.c00 + c10 * this.c01 + c20 * this.c02
+ val fc01 = c01 * this.c00 + c11 * this.c01 + c21 * this.c02
+ val fc02 = c02 * this.c00 + c12 * this.c01 + c22 * this.c02
+ val fc10 = c00 * this.c10 + c10 * this.c11 + c20 * this.c12
+ val fc11 = c01 * this.c10 + c11 * this.c11 + c21 * this.c12
+ val fc12 = c02 * this.c10 + c12 * this.c11 + c22 * this.c12
+ val fc20 = c00 * this.c20 + c10 * this.c21 + c20 * this.c22
+ val fc21 = c01 * this.c20 + c11 * this.c21 + c21 * this.c22
+ val fc22 = c02 * this.c20 + c12 * this.c21 + c22 * this.c22
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ return this
+ }
+
+ final override fun add(other: Double): Matrix3d {
+ return super.add(other) as Matrix3d
+ }
+
+ final override fun sub(other: Double): Matrix3d {
+ return super.sub(other) as Matrix3d
+ }
+
+ final override fun mul(other: Double): Matrix3d {
+ return super.mul(other) as Matrix3d
+ }
+
+ final override fun div(other: Double): Matrix3d {
+ return super.div(other) as Matrix3d
+ }
+
+ final override fun add(other: Double2DArray): Matrix3d {
+ return super.add(other) as Matrix3d
+ }
+
+ final override fun sub(other: Double2DArray): Matrix3d {
+ return super.sub(other) as Matrix3d
+ }
+
+ fun to2d() = Matrix2d.from(this)
+ fun to4d(filler: Double) = Matrix4d.from(this, filler)
+ fun to4d() = Matrix4d.from(this)
+
+ fun copy(): Matrix3d {
+ return Impl(c00, c01, c02, c10, c11, c12, c20, c21, c22)
+ }
+
+ @JvmOverloads
+ fun translate(x: Double = 0.0, y: Double = 0.0): Matrix3d {
+ return mul(c20 = x, c21 = y)
+ }
+
+ fun translate(value: IStruct2d) = translate(value.component1(), value.component2())
+ fun translate(value: Vector2d) = translate(value.component1(), value.component2())
+
+ @JvmOverloads
+ fun preTranslate(x: Double = 0.0, y: Double = 0.0): Matrix3d {
+ return mulIntoThis(c20 = x, c21 = y)
+ }
+
+ fun preTranslate(value: IStruct2d) = preTranslate(value.component1(), value.component2())
+ fun preTranslate(value: Vector2d) = preTranslate(value.component1(), value.component2())
+
+ @JvmOverloads
+ fun scale(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0): Matrix3d {
+ return mul(c00 = x, c11 = y, c22 = z)
+ }
+
+ fun scale(value: IStruct2d) = scale(value.component1(), value.component2())
+ fun scale(value: IStruct3d) = scale(value.component1(), value.component2(), value.component3())
+
+ fun scale(value: Vector2d) = scale(value.component1(), value.component2())
+ fun scale(value: Vector3d) = scale(value.component1(), value.component2(), value.component3())
+
+ @JvmOverloads
+ fun preScale(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0): Matrix3d {
+ return mulIntoThis(c00 = x, c11 = y, c22 = z)
+ }
+
+ fun preScale(value: IStruct2d) = preScale(value.component1(), value.component2())
+ fun preScale(value: IStruct3d) = preScale(value.component1(), value.component2(), value.component3())
+
+ fun preScale(value: Vector2d) = preScale(value.component1(), value.component2())
+ fun preScale(value: Vector3d) = preScale(value.component1(), value.component2(), value.component3())
+
+ fun rotateAroundZ(angle: Double): Matrix3d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ fun preRotateAroundZ(angle: Double): Matrix3d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ private data class Impl(
+ override var c00: Double,
+ override var c01: Double,
+ override var c02: Double,
+ override var c10: Double,
+ override var c11: Double,
+ override var c12: Double,
+ override var c20: Double,
+ override var c21: Double,
+ override var c22: Double,
+ ) : Matrix3d() {
+ override fun toString(): String {
+ return "Matrix3d" + super.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Matrix3d) return false
+
+ return this === other || c00 == other.c00 &&
+ c01 == other.c01 &&
+ c02 == other.c02 &&
+ c10 == other.c10 &&
+ c11 == other.c11 &&
+ c12 == other.c12 &&
+ c20 == other.c20 &&
+ c21 == other.c21 &&
+ c22 == other.c22
+ }
+ }
+
+ private class View(private val parent: Matrix3d) : Matrix3d() {
+ override var c00: Double get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c01: Double get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c02: Double get() = parent.c02; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c10: Double get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c11: Double get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c12: Double get() = parent.c12; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c20: Double get() = parent.c20; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c21: Double get() = parent.c21; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c22: Double get() = parent.c22; set(_) { throw UnsupportedOperationException("Read-Only view") }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ throw UnsupportedOperationException("Read-Only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this || parent == other
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun all(value: Double) = construct3(value, ::columnMajor)
+
+ @JvmStatic
+ fun zero() = all(0.0)
+
+ @JvmStatic
+ fun identity() = columnMajor()
+
+ @JvmStatic
+ fun unmodifiable(value: Matrix3d): Matrix3d {
+ if (value is View)
+ return value
+ else
+ return View(value)
+ }
+
+ @JvmStatic
+ fun columnMajor(
+ c00: Double = 1.0, c01: Double = 0.0, c02: Double = 0.0,
+ c10: Double = 0.0, c11: Double = 1.0, c12: Double = 0.0,
+ c20: Double = 0.0, c21: Double = 0.0, c22: Double = 1.0,
+ ): Matrix3d {
+ return Impl(c00, c01, c02, c10, c11, c12, c20, c21, c22)
+ }
+
+ @JvmStatic
+ fun rowMajor(
+ r00: Double = 1.0, r01: Double = 0.0, r02: Double = 0.0,
+ r10: Double = 0.0, r11: Double = 1.0, r12: Double = 0.0,
+ r20: Double = 0.0, r21: Double = 0.0, r22: Double = 1.0,
+ ): Matrix3d {
+ return Impl(
+ r00, r10, r20,
+ r01, r11, r21,
+ r02, r12, r22,
+ )
+ }
+
+ @JvmStatic
+ fun from(matrix: Double2DArray, filler: Double) = construct3(matrix, Double2DArray::get, ::columnMajor, filler)
+ @JvmStatic
+ fun from(matrix: Double2DArray) = construct3(matrix, Double2DArray::get, ::columnMajor, 0.0)
+ @JvmStatic
+ fun from(matrix: Matrix3d) = construct3(matrix, Double2DArray::get, ::columnMajor)
+ @JvmStatic
+ fun from(matrix: Matrix4d) = construct3(matrix, Double2DArray::get, ::columnMajor)
+
+ @JvmStatic
+ fun fromTransposed(matrix: Double2DArray, filler: Double) = construct3(matrix, Double2DArray::get, Companion::rowMajor, filler)
+ @JvmStatic
+ fun fromTransposed(matrix: Double2DArray) = construct3(matrix, Double2DArray::get, Companion::rowMajor, 0.0)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix3d) = construct3(matrix, Double2DArray::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix4d) = construct3(matrix, Double2DArray::get, Companion::rowMajor)
+
+ @JvmStatic
+ fun fromColumnMajor(buffer: DoubleBuffer) = construct3(buffer, DoubleBuffer::get, ::columnMajor)
+ @JvmStatic
+ fun fromColumnMajor(buffer: ByteBuffer) = construct3(buffer, ByteBuffer::getDouble, ::columnMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: DoubleBuffer) = construct3(buffer, DoubleBuffer::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: ByteBuffer) = construct3(buffer, ByteBuffer::getDouble, Companion::rowMajor)
+
+ /**
+ * Constructs new ortho projection matrix, with Y coordinate flipped (useful for OpenGL GUI drawing).
+ *
+ * Inversion of Y means that X 0 Y 0 will be top left position on screen (in OpenGL), not bottom left.
+ */
+ @JvmStatic
+ fun ortho(left: Double, right: Double, bottom: Double, top: Double): Matrix3d {
+ return rowMajor(
+ r00 = 2.0 / (right - left),
+ r11 = -2.0 / (top - bottom),
+ r02 = -(right + left) / (right - left),
+ r12 = -(top + bottom) / (top - bottom) + 2f,
+ )
+ }
+
+ /**
+ * Constructs new ortho projection matrix
+ */
+ @JvmStatic
+ fun orthoDirect(left: Double, right: Double, bottom: Double, top: Double): Matrix3d {
+ return rowMajor(
+ r00 = 2.0 / (right - left),
+ r11 = 2.0 / (top - bottom),
+ r02 = -(right + left) / (right - left),
+ r12 = -(top + bottom) / (top - bottom),
+ )
+ }
+
+ // kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
+ const val C00 = (0 or (0 shl 2))
+ const val C01 = (0 or (1 shl 2))
+ const val C02 = (0 or (2 shl 2))
+ const val C10 = (1 or (0 shl 2))
+ const val C11 = (1 or (1 shl 2))
+ const val C12 = (1 or (2 shl 2))
+ const val C20 = (2 or (0 shl 2))
+ const val C21 = (2 or (1 shl 2))
+ const val C22 = (2 or (2 shl 2))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3dStack.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3dStack.kt
new file mode 100644
index 0000000..25fcd0d
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3dStack.kt
@@ -0,0 +1,55 @@
+package ru.dbotthepony.kommons.matrix
+
+class Matrix3dStack {
+ private val stack = ArrayDeque()
+
+ init {
+ stack.addLast(Matrix3d.identity())
+ }
+
+ fun clear(top: Matrix3d): Matrix3dStack {
+ stack.clear()
+ stack.addLast(top.copy())
+ return this
+ }
+
+ fun push(): Matrix3dStack {
+ stack.addLast(stack.last().copy())
+ return this
+ }
+
+ fun pop(): Matrix3d {
+ return stack.removeLast()
+ }
+
+ fun last(): Matrix3d {
+ return stack.last()
+ }
+
+ fun push(matrix: Matrix3d): Matrix3dStack {
+ stack.addLast(matrix.copy())
+ return this
+ }
+
+ fun identity() = push(Matrix3d.identity())
+ fun zero() = push(Matrix3d.all(0.0))
+ fun all(value: Double) = push(Matrix3d.all(value))
+
+ inline fun with(block: Matrix3d.() -> T): T {
+ return try {
+ push()
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+
+ inline fun with(matrix: Matrix3d, block: Matrix3d.() -> T): T {
+ return try {
+ push(matrix)
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3f.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3f.kt
new file mode 100644
index 0000000..da05b15
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3f.kt
@@ -0,0 +1,496 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Float2DArray
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.vector.Vector2f
+import ru.dbotthepony.kommons.vector.Vector3f
+import java.nio.ByteBuffer
+import java.nio.FloatBuffer
+import kotlin.math.cos
+import kotlin.math.sin
+
+sealed class Matrix3f : Float2DArray() {
+ abstract var c00: Float
+ abstract var c01: Float
+ abstract var c02: Float
+ abstract var c10: Float
+ abstract var c11: Float
+ abstract var c12: Float
+ abstract var c20: Float
+ abstract var c21: Float
+ abstract var c22: Float
+
+ fun c00() = c00
+ fun c00(value: Float): Matrix3f { c00 = value; return this }
+ fun c01() = c01
+ fun c01(value: Float): Matrix3f { c01 = value; return this }
+ fun c02() = c02
+ fun c02(value: Float): Matrix3f { c02 = value; return this }
+ fun c10() = c10
+ fun c10(value: Float): Matrix3f { c10 = value; return this }
+ fun c11() = c11
+ fun c11(value: Float): Matrix3f { c11 = value; return this }
+ fun c12() = c12
+ fun c12(value: Float): Matrix3f { c12 = value; return this }
+ fun c20() = c20
+ fun c20(value: Float): Matrix3f { c20 = value; return this }
+ fun c21() = c21
+ fun c21(value: Float): Matrix3f { c21 = value; return this }
+ fun c22() = c22
+ fun c22(value: Float): Matrix3f { c22 = value; return this }
+
+ var r00: Float get() = c00; set(value) { c00 = value } // row 0 column 0
+ var r01: Float get() = c10; set(value) { c10 = value } // row 1 column 0
+ var r02: Float get() = c20; set(value) { c20 = value } // row 2 column 0
+ var r10: Float get() = c01; set(value) { c01 = value } // row 0 column 1
+ var r11: Float get() = c11; set(value) { c11 = value } // row 1 column 1
+ var r12: Float get() = c21; set(value) { c21 = value } // row 2 column 1
+ var r20: Float get() = c02; set(value) { c02 = value } // row 0 column 2
+ var r21: Float get() = c12; set(value) { c12 = value } // row 1 column 2
+ var r22: Float get() = c22; set(value) { c22 = value } // row 2 column 2
+
+ fun r00() = r00
+ fun r00(value: Float): Matrix3f { r00 = value; return this }
+ fun r01() = r01
+ fun r01(value: Float): Matrix3f { r01 = value; return this }
+ fun r02() = r02
+ fun r02(value: Float): Matrix3f { r02 = value; return this }
+ fun r10() = r10
+ fun r10(value: Float): Matrix3f { r10 = value; return this }
+ fun r11() = r11
+ fun r11(value: Float): Matrix3f { r11 = value; return this }
+ fun r12() = r12
+ fun r12(value: Float): Matrix3f { r12 = value; return this }
+ fun r20() = r20
+ fun r20(value: Float): Matrix3f { r20 = value; return this }
+ fun r21() = r21
+ fun r21(value: Float): Matrix3f { r21 = value; return this }
+ fun r22() = r22
+ fun r22(value: Float): Matrix3f { r22 = value; return this }
+
+ final override val rows: Int
+ get() = 3
+ final override val columns: Int
+ get() = 3
+
+ final override val determinant: Float
+ get() = super.determinant!!
+ final override val cofactorMatrix
+ get() = from(super.cofactorMatrix!!)
+ final override val adjugateMatrix
+ get() = from(super.adjugateMatrix!!)
+ final override val inversed
+ get() = super.inversed?.let { from(it) }
+
+ final override val transposed get() =
+ columnMajor(
+ c00 = r00,
+ c01 = r01,
+ c02 = r02,
+ c10 = r10,
+ c11 = r11,
+ c12 = r12,
+ c20 = r20,
+ c21 = r21,
+ c22 = r22,
+ )
+
+ final override fun get(column: Int, row: Int): Float {
+ return when (column or (row shl 2)) {
+ C00 -> c00
+ C01 -> c01
+ C02 -> c02
+ C10 -> c10
+ C11 -> c11
+ C12 -> c12
+ C20 -> c20
+ C21 -> c21
+ C22 -> c22
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ when (column or (row shl 2)) {
+ C00 -> c00 = value
+ C01 -> c01 = value
+ C02 -> c02 = value
+ C10 -> c10 = value
+ C11 -> c11 = value
+ C12 -> c12 = value
+ C20 -> c20 = value
+ C21 -> c21 = value
+ C22 -> c22 = value
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ fun mul(other: Matrix3f): Matrix3f {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ this.c00 = c00
+ this.c01 = c01
+ this.c02 = c02
+ this.c10 = c10
+ this.c11 = c11
+ this.c12 = c12
+ this.c20 = c20
+ this.c21 = c21
+ this.c22 = c22
+ return this
+ }
+
+ fun mulIntoOther(other: Matrix3f): Matrix3f {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ other.c00 = c00
+ other.c01 = c01
+ other.c02 = c02
+ other.c10 = c10
+ other.c11 = c11
+ other.c12 = c12
+ other.c20 = c20
+ other.c21 = c21
+ other.c22 = c22
+ return other
+ }
+
+ fun mul(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c02: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ c12: Float = 0f,
+ c20: Float = 0f,
+ c21: Float = 0f,
+ c22: Float = 1f,
+ ): Matrix3f {
+ val fc00 = this.c00 * c00 + this.c10 * c01 + this.c20 * c02
+ val fc01 = this.c01 * c00 + this.c11 * c01 + this.c21 * c02
+ val fc02 = this.c02 * c00 + this.c12 * c01 + this.c22 * c02
+ val fc10 = this.c00 * c10 + this.c10 * c11 + this.c20 * c12
+ val fc11 = this.c01 * c10 + this.c11 * c11 + this.c21 * c12
+ val fc12 = this.c02 * c10 + this.c12 * c11 + this.c22 * c12
+ val fc20 = this.c00 * c20 + this.c10 * c21 + this.c20 * c22
+ val fc21 = this.c01 * c20 + this.c11 * c21 + this.c21 * c22
+ val fc22 = this.c02 * c20 + this.c12 * c21 + this.c22 * c22
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ return this
+ }
+
+ fun mulIntoThis(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c02: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ c12: Float = 0f,
+ c20: Float = 0f,
+ c21: Float = 0f,
+ c22: Float = 1f,
+ ): Matrix3f {
+ val fc00 = c00 * this.c00 + c10 * this.c01 + c20 * this.c02
+ val fc01 = c01 * this.c00 + c11 * this.c01 + c21 * this.c02
+ val fc02 = c02 * this.c00 + c12 * this.c01 + c22 * this.c02
+ val fc10 = c00 * this.c10 + c10 * this.c11 + c20 * this.c12
+ val fc11 = c01 * this.c10 + c11 * this.c11 + c21 * this.c12
+ val fc12 = c02 * this.c10 + c12 * this.c11 + c22 * this.c12
+ val fc20 = c00 * this.c20 + c10 * this.c21 + c20 * this.c22
+ val fc21 = c01 * this.c20 + c11 * this.c21 + c21 * this.c22
+ val fc22 = c02 * this.c20 + c12 * this.c21 + c22 * this.c22
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ return this
+ }
+
+ final override fun add(other: Float): Matrix3f {
+ return super.add(other) as Matrix3f
+ }
+
+ final override fun sub(other: Float): Matrix3f {
+ return super.sub(other) as Matrix3f
+ }
+
+ final override fun mul(other: Float): Matrix3f {
+ return super.mul(other) as Matrix3f
+ }
+
+ final override fun div(other: Float): Matrix3f {
+ return super.div(other) as Matrix3f
+ }
+
+ final override fun add(other: Float2DArray): Matrix3f {
+ return super.add(other) as Matrix3f
+ }
+
+ final override fun sub(other: Float2DArray): Matrix3f {
+ return super.sub(other) as Matrix3f
+ }
+
+ fun to2f() = Matrix2f.from(this)
+ fun to4f(filler: Float) = Matrix4f.from(this, filler)
+ fun to4f() = Matrix4f.from(this)
+
+ fun copy(): Matrix3f {
+ return Impl(c00, c01, c02, c10, c11, c12, c20, c21, c22)
+ }
+
+ @JvmOverloads
+ fun translate(x: Float = 0f, y: Float = 0f): Matrix3f {
+ return mul(c20 = x, c21 = y)
+ }
+
+ fun translate(value: IStruct2f) = translate(value.component1(), value.component2())
+ fun translate(value: Vector2f) = translate(value.component1(), value.component2())
+
+ @JvmOverloads
+ fun preTranslate(x: Float = 0f, y: Float = 0f): Matrix3f {
+ return mulIntoThis(c20 = x, c21 = y)
+ }
+
+ fun preTranslate(value: IStruct2f) = preTranslate(value.component1(), value.component2())
+ fun preTranslate(value: Vector2f) = preTranslate(value.component1(), value.component2())
+
+ @JvmOverloads
+ fun scale(x: Float = 1f, y: Float = 1f, z: Float = 1f): Matrix3f {
+ return mul(c00 = x, c11 = y, c22 = z)
+ }
+
+ fun scale(value: IStruct2f) = scale(value.component1(), value.component2())
+ fun scale(value: IStruct3f) = scale(value.component1(), value.component2(), value.component3())
+
+ fun scale(value: Vector2f) = scale(value.component1(), value.component2())
+ fun scale(value: Vector3f) = scale(value.component1(), value.component2(), value.component3())
+
+ @JvmOverloads
+ fun preScale(x: Float = 1f, y: Float = 1f, z: Float = 1f): Matrix3f {
+ return mulIntoThis(c00 = x, c11 = y, c22 = z)
+ }
+
+ fun preScale(value: IStruct2f) = preScale(value.component1(), value.component2())
+ fun preScale(value: IStruct3f) = preScale(value.component1(), value.component2(), value.component3())
+
+ fun preScale(value: Vector2f) = preScale(value.component1(), value.component2())
+ fun preScale(value: Vector3f) = preScale(value.component1(), value.component2(), value.component3())
+
+ fun rotateAroundZ(angle: Float): Matrix3f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ fun preRotateAroundZ(angle: Float): Matrix3f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ private data class Impl(
+ override var c00: Float,
+ override var c01: Float,
+ override var c02: Float,
+ override var c10: Float,
+ override var c11: Float,
+ override var c12: Float,
+ override var c20: Float,
+ override var c21: Float,
+ override var c22: Float,
+ ) : Matrix3f() {
+ override fun toString(): String {
+ return "Matrix3f" + super.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Matrix3f) return false
+
+ return c00 == other.c00 &&
+ c01 == other.c01 &&
+ c02 == other.c02 &&
+ c10 == other.c10 &&
+ c11 == other.c11 &&
+ c12 == other.c12 &&
+ c20 == other.c20 &&
+ c21 == other.c21 &&
+ c22 == other.c22
+ }
+ }
+
+ private class View(private val parent: Matrix3f) : Matrix3f() {
+ override var c00: Float get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c01: Float get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c02: Float get() = parent.c02; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c10: Float get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c11: Float get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c12: Float get() = parent.c12; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c20: Float get() = parent.c20; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c21: Float get() = parent.c21; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c22: Float get() = parent.c22; set(_) { throw UnsupportedOperationException("Read-Only view") }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ throw UnsupportedOperationException("Read-Only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this || parent == other
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun all(value: Float) = construct3(value, ::columnMajor)
+
+ @JvmStatic
+ fun zero() = all(0f)
+
+ @JvmStatic
+ fun identity() = columnMajor()
+
+ @JvmStatic
+ fun unmodifiable(value: Matrix3f): Matrix3f {
+ if (value is View)
+ return value
+ else
+ return View(value)
+ }
+
+ @JvmStatic
+ fun columnMajor(
+ c00: Float = 1f, c01: Float = 0f, c02: Float = 0f,
+ c10: Float = 0f, c11: Float = 1f, c12: Float = 0f,
+ c20: Float = 0f, c21: Float = 0f, c22: Float = 1f,
+ ): Matrix3f {
+ return Impl(c00, c01, c02, c10, c11, c12, c20, c21, c22)
+ }
+
+ @JvmStatic
+ fun rowMajor(
+ r00: Float = 1f, r01: Float = 0f, r02: Float = 0f,
+ r10: Float = 0f, r11: Float = 1f, r12: Float = 0f,
+ r20: Float = 0f, r21: Float = 0f, r22: Float = 1f,
+ ): Matrix3f {
+ return Impl(
+ r00, r10, r20,
+ r01, r11, r21,
+ r02, r12, r22,
+ )
+ }
+
+ @JvmStatic
+ fun from(matrix: Float2DArray, filler: Float) = construct3(matrix, Float2DArray::get, ::columnMajor, filler)
+ @JvmStatic
+ fun from(matrix: Float2DArray) = construct3(matrix, Float2DArray::get, ::columnMajor, 0f)
+ @JvmStatic
+ fun from(matrix: Matrix3f) = construct3(matrix, Float2DArray::get, ::columnMajor)
+ @JvmStatic
+ fun from(matrix: Matrix4f) = construct3(matrix, Float2DArray::get, ::columnMajor)
+
+ @JvmStatic
+ fun fromTransposed(matrix: Float2DArray, filler: Float) = construct3(matrix, Float2DArray::get, Companion::rowMajor, filler)
+ @JvmStatic
+ fun fromTransposed(matrix: Float2DArray) = construct3(matrix, Float2DArray::get, Companion::rowMajor, 0f)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix3f) = construct3(matrix, Float2DArray::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix4f) = construct3(matrix, Float2DArray::get, Companion::rowMajor)
+
+ @JvmStatic
+ fun fromColumnMajor(buffer: FloatBuffer) = construct3(buffer, FloatBuffer::get, ::columnMajor)
+ @JvmStatic
+ fun fromColumnMajor(buffer: ByteBuffer) = construct3(buffer, ByteBuffer::getFloat, ::columnMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: FloatBuffer) = construct3(buffer, FloatBuffer::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: ByteBuffer) = construct3(buffer, ByteBuffer::getFloat, Companion::rowMajor)
+
+ /**
+ * Constructs new ortho projection matrix, with Y coordinate flipped (useful for OpenGL GUI drawing).
+ *
+ * Inversion of Y means that X 0 Y 0 will be top left position on screen (in OpenGL), not bottom left.
+ */
+ @JvmStatic
+ fun ortho(left: Float, right: Float, bottom: Float, top: Float): Matrix3f {
+ return rowMajor(
+ r00 = 2f / (right - left),
+ r11 = -2f / (top - bottom),
+ r02 = -(right + left) / (right - left),
+ r12 = -(top + bottom) / (top - bottom) + 2f,
+ )
+ }
+
+ /**
+ * Constructs new ortho projection matrix
+ */
+ @JvmStatic
+ fun orthoDirect(left: Float, right: Float, bottom: Float, top: Float): Matrix3f {
+ return rowMajor(
+ r00 = 2f / (right - left),
+ r11 = 2f / (top - bottom),
+ r02 = -(right + left) / (right - left),
+ r12 = -(top + bottom) / (top - bottom),
+ )
+ }
+
+ // kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
+ const val C00 = (0 or (0 shl 2))
+ const val C01 = (0 or (1 shl 2))
+ const val C02 = (0 or (2 shl 2))
+ const val C10 = (1 or (0 shl 2))
+ const val C11 = (1 or (1 shl 2))
+ const val C12 = (1 or (2 shl 2))
+ const val C20 = (2 or (0 shl 2))
+ const val C21 = (2 or (1 shl 2))
+ const val C22 = (2 or (2 shl 2))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3fStack.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3fStack.kt
new file mode 100644
index 0000000..14ed9af
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix3fStack.kt
@@ -0,0 +1,55 @@
+package ru.dbotthepony.kommons.matrix
+
+class Matrix3fStack {
+ private val stack = ArrayDeque()
+
+ init {
+ stack.addLast(Matrix3f.identity())
+ }
+
+ fun clear(top: Matrix3f): Matrix3fStack {
+ stack.clear()
+ stack.addLast(top.copy())
+ return this
+ }
+
+ fun push(): Matrix3fStack {
+ stack.addLast(stack.last().copy())
+ return this
+ }
+
+ fun pop(): Matrix3f {
+ return stack.removeLast()
+ }
+
+ fun last(): Matrix3f {
+ return stack.last()
+ }
+
+ fun push(matrix: Matrix3f): Matrix3fStack {
+ stack.addLast(matrix.copy())
+ return this
+ }
+
+ fun identity() = push(Matrix3f.identity())
+ fun zero() = push(Matrix3f.all(0f))
+ fun all(value: Float) = push(Matrix3f.all(value))
+
+ inline fun with(block: Matrix3f.() -> T): T {
+ return try {
+ push()
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+
+ inline fun with(matrix: Matrix3f, block: Matrix3f.() -> T): T {
+ return try {
+ push(matrix)
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4d.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4d.kt
new file mode 100644
index 0000000..34ac787
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4d.kt
@@ -0,0 +1,734 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Double2DArray
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct4d
+import ru.dbotthepony.kommons.vector.Vector2d
+import ru.dbotthepony.kommons.vector.Vector3d
+import ru.dbotthepony.kommons.vector.Vector4d
+import java.nio.ByteBuffer
+import java.nio.DoubleBuffer
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.tan
+
+sealed class Matrix4d : Double2DArray() {
+ abstract var c00: Double
+ abstract var c01: Double
+ abstract var c02: Double
+ abstract var c03: Double
+ abstract var c10: Double
+ abstract var c11: Double
+ abstract var c12: Double
+ abstract var c13: Double
+ abstract var c20: Double
+ abstract var c21: Double
+ abstract var c22: Double
+ abstract var c23: Double
+ abstract var c30: Double
+ abstract var c31: Double
+ abstract var c32: Double
+ abstract var c33: Double
+
+ fun c00() = c00
+ fun c00(value: Double): Matrix4d { c00 = value; return this }
+ fun c01() = c01
+ fun c01(value: Double): Matrix4d { c01 = value; return this }
+ fun c02() = c02
+ fun c02(value: Double): Matrix4d { c02 = value; return this }
+ fun c03() = c03
+ fun c03(value: Double): Matrix4d { c03 = value; return this }
+ fun c10() = c10
+ fun c10(value: Double): Matrix4d { c10 = value; return this }
+ fun c11() = c11
+ fun c11(value: Double): Matrix4d { c11 = value; return this }
+ fun c12() = c12
+ fun c12(value: Double): Matrix4d { c12 = value; return this }
+ fun c13() = c13
+ fun c13(value: Double): Matrix4d { c13 = value; return this }
+ fun c20() = c20
+ fun c20(value: Double): Matrix4d { c20 = value; return this }
+ fun c21() = c21
+ fun c21(value: Double): Matrix4d { c21 = value; return this }
+ fun c22() = c22
+ fun c22(value: Double): Matrix4d { c22 = value; return this }
+ fun c23() = c23
+ fun c23(value: Double): Matrix4d { c23 = value; return this }
+ fun c30() = c30
+ fun c30(value: Double): Matrix4d { c30 = value; return this }
+ fun c31() = c31
+ fun c31(value: Double): Matrix4d { c31 = value; return this }
+ fun c32() = c32
+ fun c32(value: Double): Matrix4d { c32 = value; return this }
+ fun c33() = c33
+ fun c33(value: Double): Matrix4d { c33 = value; return this }
+
+ var r00: Double get() = c00; set(value) { c00 = value } // row 0 column 0
+ var r01: Double get() = c10; set(value) { c10 = value } // row 1 column 0
+ var r02: Double get() = c20; set(value) { c20 = value } // row 2 column 0
+ var r03: Double get() = c30; set(value) { c30 = value } // row 3 column 0
+ var r10: Double get() = c01; set(value) { c01 = value } // row 0 column 1
+ var r11: Double get() = c11; set(value) { c11 = value } // row 1 column 1
+ var r12: Double get() = c21; set(value) { c21 = value } // row 2 column 1
+ var r13: Double get() = c31; set(value) { c31 = value } // row 3 column 1
+ var r20: Double get() = c02; set(value) { c02 = value } // row 0 column 2
+ var r21: Double get() = c12; set(value) { c12 = value } // row 1 column 2
+ var r22: Double get() = c22; set(value) { c22 = value } // row 2 column 2
+ var r23: Double get() = c32; set(value) { c32 = value } // row 3 column 2
+ var r30: Double get() = c03; set(value) { c03 = value } // row 0 column 3
+ var r31: Double get() = c13; set(value) { c13 = value } // row 1 column 3
+ var r32: Double get() = c23; set(value) { c23 = value } // row 2 column 3
+ var r33: Double get() = c33; set(value) { c33 = value } // row 3 column 3
+
+ fun r00() = r00
+ fun r00(value: Double): Matrix4d { r00 = value; return this }
+ fun r01() = r01
+ fun r01(value: Double): Matrix4d { r01 = value; return this }
+ fun r02() = r02
+ fun r02(value: Double): Matrix4d { r02 = value; return this }
+ fun r03() = r03
+ fun r03(value: Double): Matrix4d { r03 = value; return this }
+ fun r10() = r10
+ fun r10(value: Double): Matrix4d { r10 = value; return this }
+ fun r11() = r11
+ fun r11(value: Double): Matrix4d { r11 = value; return this }
+ fun r12() = r12
+ fun r12(value: Double): Matrix4d { r12 = value; return this }
+ fun r13() = r13
+ fun r13(value: Double): Matrix4d { r13 = value; return this }
+ fun r20() = r20
+ fun r20(value: Double): Matrix4d { r20 = value; return this }
+ fun r21() = r21
+ fun r21(value: Double): Matrix4d { r21 = value; return this }
+ fun r22() = r22
+ fun r22(value: Double): Matrix4d { r22 = value; return this }
+ fun r23() = r23
+ fun r23(value: Double): Matrix4d { r23 = value; return this }
+ fun r30() = r30
+ fun r30(value: Double): Matrix4d { r30 = value; return this }
+ fun r31() = r31
+ fun r31(value: Double): Matrix4d { r31 = value; return this }
+ fun r32() = r32
+ fun r32(value: Double): Matrix4d { r32 = value; return this }
+ fun r33() = r33
+ fun r33(value: Double): Matrix4d { r33 = value; return this }
+
+ final override val rows: Int
+ get() = 4
+ final override val columns: Int
+ get() = 4
+
+ final override val transposed get() =
+ columnMajor(
+ c00 = r00,
+ c01 = r01,
+ c02 = r02,
+ c03 = r03,
+ c10 = r10,
+ c11 = r11,
+ c12 = r12,
+ c13 = r13,
+ c20 = r20,
+ c21 = r21,
+ c22 = r22,
+ c23 = r23,
+ c30 = r30,
+ c31 = r31,
+ c32 = r32,
+ c33 = r33,
+ )
+
+ final override fun get(column: Int, row: Int): Double {
+ return when (column or (row shl 2)) {
+ C00 -> c00
+ C01 -> c01
+ C02 -> c02
+ C03 -> c03
+ C10 -> c10
+ C11 -> c11
+ C12 -> c12
+ C13 -> c13
+ C20 -> c20
+ C21 -> c21
+ C22 -> c22
+ C23 -> c23
+ C30 -> c30
+ C31 -> c31
+ C32 -> c32
+ C33 -> c33
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ when (column or (row shl 2)) {
+ C00 -> c00 = value
+ C01 -> c01 = value
+ C02 -> c02 = value
+ C03 -> c03 = value
+ C10 -> c10 = value
+ C11 -> c11 = value
+ C12 -> c12 = value
+ C13 -> c13 = value
+ C20 -> c20 = value
+ C21 -> c21 = value
+ C22 -> c22 = value
+ C23 -> c23 = value
+ C30 -> c30 = value
+ C31 -> c31 = value
+ C32 -> c32 = value
+ C33 -> c33 = value
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ fun mul(other: Matrix4d): Matrix4d {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c03 = mulMatrixComponents(this, other, 0, 3)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c13 = mulMatrixComponents(this, other, 1, 3)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ val c23 = mulMatrixComponents(this, other, 2, 3)
+ val c30 = mulMatrixComponents(this, other, 3, 0)
+ val c31 = mulMatrixComponents(this, other, 3, 1)
+ val c32 = mulMatrixComponents(this, other, 3, 2)
+ val c33 = mulMatrixComponents(this, other, 3, 3)
+ this.c00 = c00
+ this.c01 = c01
+ this.c02 = c02
+ this.c03 = c03
+ this.c10 = c10
+ this.c11 = c11
+ this.c12 = c12
+ this.c13 = c13
+ this.c20 = c20
+ this.c21 = c21
+ this.c22 = c22
+ this.c23 = c23
+ this.c30 = c30
+ this.c31 = c31
+ this.c32 = c32
+ this.c33 = c33
+ return this
+ }
+
+ fun mulIntoOther(other: Matrix4d): Matrix4d {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c03 = mulMatrixComponents(this, other, 0, 3)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c13 = mulMatrixComponents(this, other, 1, 3)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ val c23 = mulMatrixComponents(this, other, 2, 3)
+ val c30 = mulMatrixComponents(this, other, 3, 0)
+ val c31 = mulMatrixComponents(this, other, 3, 1)
+ val c32 = mulMatrixComponents(this, other, 3, 2)
+ val c33 = mulMatrixComponents(this, other, 3, 3)
+ other.c00 = c00
+ other.c01 = c01
+ other.c02 = c02
+ other.c03 = c03
+ other.c10 = c10
+ other.c11 = c11
+ other.c12 = c12
+ other.c13 = c13
+ other.c20 = c20
+ other.c21 = c21
+ other.c22 = c22
+ other.c23 = c23
+ other.c30 = c30
+ other.c31 = c31
+ other.c32 = c32
+ other.c33 = c33
+ return other
+ }
+
+ /**
+ * this * other, writes result into this
+ */
+ fun mul(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c02: Double = 0.0,
+ c03: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ c12: Double = 0.0,
+ c13: Double = 0.0,
+ c20: Double = 0.0,
+ c21: Double = 0.0,
+ c22: Double = 1.0,
+ c23: Double = 0.0,
+ c30: Double = 0.0,
+ c31: Double = 0.0,
+ c32: Double = 0.0,
+ c33: Double = 1.0,
+ ): Matrix4d {
+ val fc00 = this.c00 * c00 + this.c10 * c01 + this.c20 * c02 + this.c30 * c03
+ val fc01 = this.c01 * c00 + this.c11 * c01 + this.c21 * c02 + this.c31 * c03
+ val fc02 = this.c02 * c00 + this.c12 * c01 + this.c22 * c02 + this.c32 * c03
+ val fc03 = this.c03 * c00 + this.c13 * c01 + this.c23 * c02 + this.c33 * c03
+ val fc10 = this.c00 * c10 + this.c10 * c11 + this.c20 * c12 + this.c30 * c13
+ val fc11 = this.c01 * c10 + this.c11 * c11 + this.c21 * c12 + this.c31 * c13
+ val fc12 = this.c02 * c10 + this.c12 * c11 + this.c22 * c12 + this.c32 * c13
+ val fc13 = this.c03 * c10 + this.c13 * c11 + this.c23 * c12 + this.c33 * c13
+ val fc20 = this.c00 * c20 + this.c10 * c21 + this.c20 * c22 + this.c30 * c23
+ val fc21 = this.c01 * c20 + this.c11 * c21 + this.c21 * c22 + this.c31 * c23
+ val fc22 = this.c02 * c20 + this.c12 * c21 + this.c22 * c22 + this.c32 * c23
+ val fc23 = this.c03 * c20 + this.c13 * c21 + this.c23 * c22 + this.c33 * c23
+ val fc30 = this.c00 * c30 + this.c10 * c31 + this.c20 * c32 + this.c30 * c33
+ val fc31 = this.c01 * c30 + this.c11 * c31 + this.c21 * c32 + this.c31 * c33
+ val fc32 = this.c02 * c30 + this.c12 * c31 + this.c22 * c32 + this.c32 * c33
+ val fc33 = this.c03 * c30 + this.c13 * c31 + this.c23 * c32 + this.c33 * c33
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c03 = fc03
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c13 = fc13
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ this.c23 = fc23
+ this.c30 = fc30
+ this.c31 = fc31
+ this.c32 = fc32
+ this.c33 = fc33
+ return this
+ }
+
+ /**
+ * other * this, writes result into this
+ */
+ fun mulIntoThis(
+ c00: Double = 1.0,
+ c01: Double = 0.0,
+ c02: Double = 0.0,
+ c03: Double = 0.0,
+ c10: Double = 0.0,
+ c11: Double = 1.0,
+ c12: Double = 0.0,
+ c13: Double = 0.0,
+ c20: Double = 0.0,
+ c21: Double = 0.0,
+ c22: Double = 1.0,
+ c23: Double = 0.0,
+ c30: Double = 0.0,
+ c31: Double = 0.0,
+ c32: Double = 0.0,
+ c33: Double = 1.0,
+ ): Matrix4d {
+ val fc00 = c00 * this.c00 + c10 * this.c01 + c20 * this.c02 + c30 * this.c03
+ val fc01 = c01 * this.c00 + c11 * this.c01 + c21 * this.c02 + c31 * this.c03
+ val fc02 = c02 * this.c00 + c12 * this.c01 + c22 * this.c02 + c32 * this.c03
+ val fc03 = c03 * this.c00 + c13 * this.c01 + c23 * this.c02 + c33 * this.c03
+ val fc10 = c00 * this.c10 + c10 * this.c11 + c20 * this.c12 + c30 * this.c13
+ val fc11 = c01 * this.c10 + c11 * this.c11 + c21 * this.c12 + c31 * this.c13
+ val fc12 = c02 * this.c10 + c12 * this.c11 + c22 * this.c12 + c32 * this.c13
+ val fc13 = c03 * this.c10 + c13 * this.c11 + c23 * this.c12 + c33 * this.c13
+ val fc20 = c00 * this.c20 + c10 * this.c21 + c20 * this.c22 + c30 * this.c23
+ val fc21 = c01 * this.c20 + c11 * this.c21 + c21 * this.c22 + c31 * this.c23
+ val fc22 = c02 * this.c20 + c12 * this.c21 + c22 * this.c22 + c32 * this.c23
+ val fc23 = c03 * this.c20 + c13 * this.c21 + c23 * this.c22 + c33 * this.c23
+ val fc30 = c00 * this.c30 + c10 * this.c31 + c20 * this.c32 + c30 * this.c33
+ val fc31 = c01 * this.c30 + c11 * this.c31 + c21 * this.c32 + c31 * this.c33
+ val fc32 = c02 * this.c30 + c12 * this.c31 + c22 * this.c32 + c32 * this.c33
+ val fc33 = c03 * this.c30 + c13 * this.c31 + c23 * this.c32 + c33 * this.c33
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c03 = fc03
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c13 = fc13
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ this.c23 = fc23
+ this.c30 = fc30
+ this.c31 = fc31
+ this.c32 = fc32
+ this.c33 = fc33
+ return this
+ }
+
+ final override fun add(other: Double): Matrix4d {
+ return super.add(other) as Matrix4d
+ }
+
+ final override fun sub(other: Double): Matrix4d {
+ return super.sub(other) as Matrix4d
+ }
+
+ final override fun mul(other: Double): Matrix4d {
+ return super.mul(other) as Matrix4d
+ }
+
+ final override fun div(other: Double): Matrix4d {
+ return super.div(other) as Matrix4d
+ }
+
+ final override fun add(other: Double2DArray): Matrix4d {
+ return super.add(other) as Matrix4d
+ }
+
+ final override fun sub(other: Double2DArray): Matrix4d {
+ return super.sub(other) as Matrix4d
+ }
+
+ fun to2d() = Matrix2d.from(this)
+ fun to3d() = Matrix3d.from(this)
+
+ fun copy(): Matrix4d {
+ return Impl(c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33)
+ }
+
+ @JvmOverloads
+ fun translate(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0): Matrix4d {
+ return mul(c30 = x, c31 = y, c32 = z)
+ }
+
+ fun translate(value: IStruct2d) = translate(value.component1(), value.component2())
+ fun translate(value: IStruct3d) = translate(value.component1(), value.component2(), value.component3())
+
+ fun translate(value: Vector2d) = translate(value.component1(), value.component2())
+ fun translate(value: Vector3d) = translate(value.component1(), value.component2(), value.component3())
+
+ @JvmOverloads
+ fun preTranslate(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0): Matrix4d {
+ return mulIntoThis(c30 = x, c31 = y, c32 = z)
+ }
+
+ fun preTranslate(value: IStruct2d) = preTranslate(value.component1(), value.component2())
+ fun preTranslate(value: IStruct3d) = preTranslate(value.component1(), value.component2(), value.component3())
+
+ fun preTranslate(value: Vector2d) = preTranslate(value.component1(), value.component2())
+ fun preTranslate(value: Vector3d) = preTranslate(value.component1(), value.component2(), value.component3())
+
+ @JvmOverloads
+ fun scale(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0): Matrix4d {
+ return mul(c00 = x, c11 = y, c22 = z, c33 = w)
+ }
+
+ fun scale(value: IStruct2d) = scale(value.component1(), value.component2())
+ fun scale(value: IStruct3d) = scale(value.component1(), value.component2(), value.component3())
+ fun scale(value: IStruct4d) = scale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ fun scale(value: Vector2d) = scale(value.component1(), value.component2())
+ fun scale(value: Vector3d) = scale(value.component1(), value.component2(), value.component3())
+ fun scale(value: Vector4d) = scale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ @JvmOverloads
+ fun preScale(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0): Matrix4d {
+ return mulIntoThis(c00 = x, c11 = y, c22 = z, c33 = w)
+ }
+
+ fun preScale(value: IStruct2d) = preScale(value.component1(), value.component2())
+ fun preScale(value: IStruct3d) = preScale(value.component1(), value.component2(), value.component3())
+ fun preScale(value: IStruct4d) = preScale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ fun preScale(value: Vector2d) = preScale(value.component1(), value.component2())
+ fun preScale(value: Vector3d) = preScale(value.component1(), value.component2(), value.component3())
+ fun preScale(value: Vector4d) = preScale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ fun rotateAroundX(angle: Double): Matrix4d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c11 = cos,
+ c21 = -sin,
+ c12 = sin,
+ c22 = cos,
+ )
+ }
+
+ fun rotateAroundY(angle: Double): Matrix4d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c00 = cos,
+ c20 = sin,
+ c02 = -sin,
+ c22 = cos,
+ )
+ }
+
+ fun rotateAroundZ(angle: Double): Matrix4d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ fun preRotateAroundX(angle: Double): Matrix4d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c11 = cos,
+ c21 = -sin,
+ c12 = sin,
+ c22 = cos,
+ )
+ }
+
+ fun preRotateAroundY(angle: Double): Matrix4d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c00 = cos,
+ c20 = sin,
+ c02 = -sin,
+ c22 = cos,
+ )
+ }
+
+ fun preRotateAroundZ(angle: Double): Matrix4d {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ private data class Impl(
+ override var c00: Double,
+ override var c01: Double,
+ override var c02: Double,
+ override var c03: Double,
+ override var c10: Double,
+ override var c11: Double,
+ override var c12: Double,
+ override var c13: Double,
+ override var c20: Double,
+ override var c21: Double,
+ override var c22: Double,
+ override var c23: Double,
+ override var c30: Double,
+ override var c31: Double,
+ override var c32: Double,
+ override var c33: Double,
+ ) : Matrix4d() {
+ override fun toString(): String {
+ return "Matrix4d" + super.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Matrix4d) return false
+
+ return this === other || c00 == other.c00 &&
+ c01 == other.c01 &&
+ c02 == other.c02 &&
+ c03 == other.c03 &&
+ c10 == other.c10 &&
+ c11 == other.c11 &&
+ c12 == other.c12 &&
+ c13 == other.c13 &&
+ c20 == other.c20 &&
+ c21 == other.c21 &&
+ c22 == other.c22 &&
+ c23 == other.c23 &&
+ c30 == other.c30 &&
+ c31 == other.c31 &&
+ c32 == other.c32 &&
+ c33 == other.c33
+ }
+ }
+
+ private class View(private val parent: Matrix4d) : Matrix4d() {
+ override var c00: Double get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c01: Double get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c02: Double get() = parent.c02; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c03: Double get() = parent.c03; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c10: Double get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c11: Double get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c12: Double get() = parent.c12; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c13: Double get() = parent.c13; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c20: Double get() = parent.c20; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c21: Double get() = parent.c21; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c22: Double get() = parent.c22; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c23: Double get() = parent.c23; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c30: Double get() = parent.c30; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c31: Double get() = parent.c31; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c32: Double get() = parent.c32; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c33: Double get() = parent.c33; set(_) { throw UnsupportedOperationException("Read-Only view") }
+
+ override fun set(column: Int, row: Int, value: Double) {
+ throw UnsupportedOperationException("Read-Only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this || parent == other
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun all(value: Double) = construct4(value, ::columnMajor)
+
+ @JvmStatic
+ fun zero() = all(0.0)
+
+ @JvmStatic
+ fun identity() = columnMajor()
+
+ @JvmStatic
+ fun unmodifiable(value: Matrix4d): Matrix4d {
+ if (value is View)
+ return value
+ else
+ return View(value)
+ }
+
+ @JvmStatic
+ fun columnMajor(
+ c00: Double = 1.0, c01: Double = 0.0, c02: Double = 0.0, c03: Double = 0.0,
+ c10: Double = 0.0, c11: Double = 1.0, c12: Double = 0.0, c13: Double = 0.0,
+ c20: Double = 0.0, c21: Double = 0.0, c22: Double = 1.0, c23: Double = 0.0,
+ c30: Double = 0.0, c31: Double = 0.0, c32: Double = 0.0, c33: Double = 1.0,
+ ): Matrix4d {
+ return Impl(c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33)
+ }
+
+ @JvmStatic
+ fun rowMajor(
+ r00: Double = 1.0, r01: Double = 0.0, r02: Double = 0.0, r03: Double = 0.0,
+ r10: Double = 0.0, r11: Double = 1.0, r12: Double = 0.0, r13: Double = 0.0,
+ r20: Double = 0.0, r21: Double = 0.0, r22: Double = 1.0, r23: Double = 0.0,
+ r30: Double = 0.0, r31: Double = 0.0, r32: Double = 0.0, r33: Double = 1.0
+ ): Matrix4d {
+ return Impl(
+ r00, r10, r20, r30,
+ r01, r11, r21, r31,
+ r02, r12, r22, r32,
+ r03, r13, r23, r33
+ )
+ }
+
+ @JvmStatic
+ fun from(matrix: Double2DArray, filler: Double) = construct4(matrix, Double2DArray::get, ::columnMajor, filler)
+ @JvmStatic
+ fun from(matrix: Double2DArray) = construct4(matrix, Double2DArray::get, ::columnMajor, 0.0)
+ @JvmStatic
+ fun from(matrix: Matrix4d) = construct4(matrix, Double2DArray::get, ::columnMajor)
+
+ @JvmStatic
+ fun fromTransposed(matrix: Double2DArray) = construct4(matrix, Double2DArray::get, Companion::rowMajor, 0.0)
+ @JvmStatic
+ fun fromTransposed(matrix: Double2DArray, filler: Double) = construct4(matrix, Double2DArray::get, Companion::rowMajor, filler)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix4d) = construct4(matrix, Double2DArray::get, Companion::rowMajor)
+
+ @JvmStatic
+ fun fromColumnMajor(buffer: DoubleBuffer) = construct4(buffer, DoubleBuffer::get, ::columnMajor)
+ @JvmStatic
+ fun fromColumnMajor(buffer: ByteBuffer) = construct4(buffer, ByteBuffer::getDouble, ::columnMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: DoubleBuffer) = construct4(buffer, DoubleBuffer::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: ByteBuffer) = construct4(buffer, ByteBuffer::getDouble, Companion::rowMajor)
+
+ /**
+ * Constructs new ortho projection matrix, with Y coordinate flipped (useful for OpenGL GUI drawing).
+ *
+ * Inversion of Y means that X 0 Y 0 will be top left position on screen (in OpenGL), not bottom left.
+ */
+ @JvmStatic
+ fun ortho(left: Double, right: Double, bottom: Double, top: Double, zNear: Double, zFar: Double): Matrix4d {
+ return rowMajor(
+ r00 = 2.0 / (right - left),
+ r11 = -2.0 / (top - bottom),
+ r22 = 2.0 / (zFar - zNear),
+ r03 = -(right + left) / (right - left),
+ r13 = -(top + bottom) / (top - bottom) + 2.0,
+ r23 = -(zFar + zNear) / (zFar - zNear)
+ )
+ }
+
+ /**
+ * Constructs new ortho projection matrix
+ */
+ @JvmStatic
+ fun orthoDirect(left: Double, right: Double, bottom: Double, top: Double, zNear: Double, zFar: Double): Matrix4d {
+ return rowMajor(
+ r00 = 2.0 / (right - left),
+ r11 = 2.0 / (top - bottom),
+ r22 = 2.0 / (zFar - zNear),
+ r03 = -(right + left) / (right - left),
+ r13 = -(top + bottom) / (top - bottom),
+ r23 = -(zFar + zNear) / (zFar - zNear)
+ )
+ }
+
+ /**
+ * Constructs new perspective matrix
+ */
+ @JvmStatic
+ fun perspective(fov: Double, zFar: Double, zNear: Double): Matrix4d {
+ val scale = (1.0 / (tan(Math.toRadians(fov.toDouble()) / 2.0))).toDouble()
+ val r = zFar - zNear
+
+ return rowMajor(
+ r00 = scale,
+ r11 = scale,
+ r22 = -zFar / r,
+ r23 = -1.0,
+ r32 = -zFar * zNear / r,
+ )
+ }
+
+ // kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
+ const val C00 = (0 or (0 shl 2))
+ const val C01 = (0 or (1 shl 2))
+ const val C02 = (0 or (2 shl 2))
+ const val C03 = (0 or (3 shl 2))
+ const val C10 = (1 or (0 shl 2))
+ const val C11 = (1 or (1 shl 2))
+ const val C12 = (1 or (2 shl 2))
+ const val C13 = (1 or (3 shl 2))
+ const val C20 = (2 or (0 shl 2))
+ const val C21 = (2 or (1 shl 2))
+ const val C22 = (2 or (2 shl 2))
+ const val C23 = (2 or (3 shl 2))
+ const val C30 = (3 or (0 shl 2))
+ const val C31 = (3 or (1 shl 2))
+ const val C32 = (3 or (2 shl 2))
+ const val C33 = (3 or (3 shl 2))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4dStack.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4dStack.kt
new file mode 100644
index 0000000..230e8b0
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4dStack.kt
@@ -0,0 +1,55 @@
+package ru.dbotthepony.kommons.matrix
+
+class Matrix4dStack {
+ private val stack = ArrayDeque()
+
+ init {
+ stack.addLast(Matrix4d.identity())
+ }
+
+ fun clear(top: Matrix4d): Matrix4dStack {
+ stack.clear()
+ stack.addLast(top.copy())
+ return this
+ }
+
+ fun push(): Matrix4dStack {
+ stack.addLast(stack.last().copy())
+ return this
+ }
+
+ fun pop(): Matrix4d {
+ return stack.removeLast()
+ }
+
+ fun last(): Matrix4d {
+ return stack.last()
+ }
+
+ fun push(matrix: Matrix4d): Matrix4dStack {
+ stack.addLast(matrix.copy())
+ return this
+ }
+
+ fun identity() = push(Matrix4d.identity())
+ fun zero() = push(Matrix4d.all(0.0))
+ fun all(value: Double) = push(Matrix4d.all(value))
+
+ inline fun with(block: Matrix4d.() -> T): T {
+ return try {
+ push()
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+
+ inline fun with(matrix: Matrix4d, block: Matrix4d.() -> T): T {
+ return try {
+ push(matrix)
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4f.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4f.kt
new file mode 100644
index 0000000..3f34f64
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4f.kt
@@ -0,0 +1,734 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Float2DArray
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct4f
+import ru.dbotthepony.kommons.vector.Vector2f
+import ru.dbotthepony.kommons.vector.Vector3f
+import ru.dbotthepony.kommons.vector.Vector4f
+import java.nio.ByteBuffer
+import java.nio.FloatBuffer
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.tan
+
+sealed class Matrix4f : Float2DArray() {
+ abstract var c00: Float
+ abstract var c01: Float
+ abstract var c02: Float
+ abstract var c03: Float
+ abstract var c10: Float
+ abstract var c11: Float
+ abstract var c12: Float
+ abstract var c13: Float
+ abstract var c20: Float
+ abstract var c21: Float
+ abstract var c22: Float
+ abstract var c23: Float
+ abstract var c30: Float
+ abstract var c31: Float
+ abstract var c32: Float
+ abstract var c33: Float
+
+ fun c00() = c00
+ fun c00(value: Float): Matrix4f { c00 = value; return this }
+ fun c01() = c01
+ fun c01(value: Float): Matrix4f { c01 = value; return this }
+ fun c02() = c02
+ fun c02(value: Float): Matrix4f { c02 = value; return this }
+ fun c03() = c03
+ fun c03(value: Float): Matrix4f { c03 = value; return this }
+ fun c10() = c10
+ fun c10(value: Float): Matrix4f { c10 = value; return this }
+ fun c11() = c11
+ fun c11(value: Float): Matrix4f { c11 = value; return this }
+ fun c12() = c12
+ fun c12(value: Float): Matrix4f { c12 = value; return this }
+ fun c13() = c13
+ fun c13(value: Float): Matrix4f { c13 = value; return this }
+ fun c20() = c20
+ fun c20(value: Float): Matrix4f { c20 = value; return this }
+ fun c21() = c21
+ fun c21(value: Float): Matrix4f { c21 = value; return this }
+ fun c22() = c22
+ fun c22(value: Float): Matrix4f { c22 = value; return this }
+ fun c23() = c23
+ fun c23(value: Float): Matrix4f { c23 = value; return this }
+ fun c30() = c30
+ fun c30(value: Float): Matrix4f { c30 = value; return this }
+ fun c31() = c31
+ fun c31(value: Float): Matrix4f { c31 = value; return this }
+ fun c32() = c32
+ fun c32(value: Float): Matrix4f { c32 = value; return this }
+ fun c33() = c33
+ fun c33(value: Float): Matrix4f { c33 = value; return this }
+
+ var r00: Float get() = c00; set(value) { c00 = value } // row 0 column 0
+ var r01: Float get() = c10; set(value) { c10 = value } // row 1 column 0
+ var r02: Float get() = c20; set(value) { c20 = value } // row 2 column 0
+ var r03: Float get() = c30; set(value) { c30 = value } // row 3 column 0
+ var r10: Float get() = c01; set(value) { c01 = value } // row 0 column 1
+ var r11: Float get() = c11; set(value) { c11 = value } // row 1 column 1
+ var r12: Float get() = c21; set(value) { c21 = value } // row 2 column 1
+ var r13: Float get() = c31; set(value) { c31 = value } // row 3 column 1
+ var r20: Float get() = c02; set(value) { c02 = value } // row 0 column 2
+ var r21: Float get() = c12; set(value) { c12 = value } // row 1 column 2
+ var r22: Float get() = c22; set(value) { c22 = value } // row 2 column 2
+ var r23: Float get() = c32; set(value) { c32 = value } // row 3 column 2
+ var r30: Float get() = c03; set(value) { c03 = value } // row 0 column 3
+ var r31: Float get() = c13; set(value) { c13 = value } // row 1 column 3
+ var r32: Float get() = c23; set(value) { c23 = value } // row 2 column 3
+ var r33: Float get() = c33; set(value) { c33 = value } // row 3 column 3
+
+ fun r00() = r00
+ fun r00(value: Float): Matrix4f { r00 = value; return this }
+ fun r01() = r01
+ fun r01(value: Float): Matrix4f { r01 = value; return this }
+ fun r02() = r02
+ fun r02(value: Float): Matrix4f { r02 = value; return this }
+ fun r03() = r03
+ fun r03(value: Float): Matrix4f { r03 = value; return this }
+ fun r10() = r10
+ fun r10(value: Float): Matrix4f { r10 = value; return this }
+ fun r11() = r11
+ fun r11(value: Float): Matrix4f { r11 = value; return this }
+ fun r12() = r12
+ fun r12(value: Float): Matrix4f { r12 = value; return this }
+ fun r13() = r13
+ fun r13(value: Float): Matrix4f { r13 = value; return this }
+ fun r20() = r20
+ fun r20(value: Float): Matrix4f { r20 = value; return this }
+ fun r21() = r21
+ fun r21(value: Float): Matrix4f { r21 = value; return this }
+ fun r22() = r22
+ fun r22(value: Float): Matrix4f { r22 = value; return this }
+ fun r23() = r23
+ fun r23(value: Float): Matrix4f { r23 = value; return this }
+ fun r30() = r30
+ fun r30(value: Float): Matrix4f { r30 = value; return this }
+ fun r31() = r31
+ fun r31(value: Float): Matrix4f { r31 = value; return this }
+ fun r32() = r32
+ fun r32(value: Float): Matrix4f { r32 = value; return this }
+ fun r33() = r33
+ fun r33(value: Float): Matrix4f { r33 = value; return this }
+
+ final override val rows: Int
+ get() = 4
+ final override val columns: Int
+ get() = 4
+
+ final override val transposed get() =
+ columnMajor(
+ c00 = r00,
+ c01 = r01,
+ c02 = r02,
+ c03 = r03,
+ c10 = r10,
+ c11 = r11,
+ c12 = r12,
+ c13 = r13,
+ c20 = r20,
+ c21 = r21,
+ c22 = r22,
+ c23 = r23,
+ c30 = r30,
+ c31 = r31,
+ c32 = r32,
+ c33 = r33,
+ )
+
+ final override fun get(column: Int, row: Int): Float {
+ return when (column or (row shl 2)) {
+ C00 -> c00
+ C01 -> c01
+ C02 -> c02
+ C03 -> c03
+ C10 -> c10
+ C11 -> c11
+ C12 -> c12
+ C13 -> c13
+ C20 -> c20
+ C21 -> c21
+ C22 -> c22
+ C23 -> c23
+ C30 -> c30
+ C31 -> c31
+ C32 -> c32
+ C33 -> c33
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ when (column or (row shl 2)) {
+ C00 -> c00 = value
+ C01 -> c01 = value
+ C02 -> c02 = value
+ C03 -> c03 = value
+ C10 -> c10 = value
+ C11 -> c11 = value
+ C12 -> c12 = value
+ C13 -> c13 = value
+ C20 -> c20 = value
+ C21 -> c21 = value
+ C22 -> c22 = value
+ C23 -> c23 = value
+ C30 -> c30 = value
+ C31 -> c31 = value
+ C32 -> c32 = value
+ C33 -> c33 = value
+ else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
+ }
+ }
+
+ fun mul(other: Matrix4f): Matrix4f {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c03 = mulMatrixComponents(this, other, 0, 3)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c13 = mulMatrixComponents(this, other, 1, 3)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ val c23 = mulMatrixComponents(this, other, 2, 3)
+ val c30 = mulMatrixComponents(this, other, 3, 0)
+ val c31 = mulMatrixComponents(this, other, 3, 1)
+ val c32 = mulMatrixComponents(this, other, 3, 2)
+ val c33 = mulMatrixComponents(this, other, 3, 3)
+ this.c00 = c00
+ this.c01 = c01
+ this.c02 = c02
+ this.c03 = c03
+ this.c10 = c10
+ this.c11 = c11
+ this.c12 = c12
+ this.c13 = c13
+ this.c20 = c20
+ this.c21 = c21
+ this.c22 = c22
+ this.c23 = c23
+ this.c30 = c30
+ this.c31 = c31
+ this.c32 = c32
+ this.c33 = c33
+ return this
+ }
+
+ fun mulIntoOther(other: Matrix4f): Matrix4f {
+ val c00 = mulMatrixComponents(this, other, 0, 0)
+ val c01 = mulMatrixComponents(this, other, 0, 1)
+ val c02 = mulMatrixComponents(this, other, 0, 2)
+ val c03 = mulMatrixComponents(this, other, 0, 3)
+ val c10 = mulMatrixComponents(this, other, 1, 0)
+ val c11 = mulMatrixComponents(this, other, 1, 1)
+ val c12 = mulMatrixComponents(this, other, 1, 2)
+ val c13 = mulMatrixComponents(this, other, 1, 3)
+ val c20 = mulMatrixComponents(this, other, 2, 0)
+ val c21 = mulMatrixComponents(this, other, 2, 1)
+ val c22 = mulMatrixComponents(this, other, 2, 2)
+ val c23 = mulMatrixComponents(this, other, 2, 3)
+ val c30 = mulMatrixComponents(this, other, 3, 0)
+ val c31 = mulMatrixComponents(this, other, 3, 1)
+ val c32 = mulMatrixComponents(this, other, 3, 2)
+ val c33 = mulMatrixComponents(this, other, 3, 3)
+ other.c00 = c00
+ other.c01 = c01
+ other.c02 = c02
+ other.c03 = c03
+ other.c10 = c10
+ other.c11 = c11
+ other.c12 = c12
+ other.c13 = c13
+ other.c20 = c20
+ other.c21 = c21
+ other.c22 = c22
+ other.c23 = c23
+ other.c30 = c30
+ other.c31 = c31
+ other.c32 = c32
+ other.c33 = c33
+ return other
+ }
+
+ /**
+ * this * other, writes result into this
+ */
+ fun mul(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c02: Float = 0f,
+ c03: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ c12: Float = 0f,
+ c13: Float = 0f,
+ c20: Float = 0f,
+ c21: Float = 0f,
+ c22: Float = 1f,
+ c23: Float = 0f,
+ c30: Float = 0f,
+ c31: Float = 0f,
+ c32: Float = 0f,
+ c33: Float = 1f,
+ ): Matrix4f {
+ val fc00 = this.c00 * c00 + this.c10 * c01 + this.c20 * c02 + this.c30 * c03
+ val fc01 = this.c01 * c00 + this.c11 * c01 + this.c21 * c02 + this.c31 * c03
+ val fc02 = this.c02 * c00 + this.c12 * c01 + this.c22 * c02 + this.c32 * c03
+ val fc03 = this.c03 * c00 + this.c13 * c01 + this.c23 * c02 + this.c33 * c03
+ val fc10 = this.c00 * c10 + this.c10 * c11 + this.c20 * c12 + this.c30 * c13
+ val fc11 = this.c01 * c10 + this.c11 * c11 + this.c21 * c12 + this.c31 * c13
+ val fc12 = this.c02 * c10 + this.c12 * c11 + this.c22 * c12 + this.c32 * c13
+ val fc13 = this.c03 * c10 + this.c13 * c11 + this.c23 * c12 + this.c33 * c13
+ val fc20 = this.c00 * c20 + this.c10 * c21 + this.c20 * c22 + this.c30 * c23
+ val fc21 = this.c01 * c20 + this.c11 * c21 + this.c21 * c22 + this.c31 * c23
+ val fc22 = this.c02 * c20 + this.c12 * c21 + this.c22 * c22 + this.c32 * c23
+ val fc23 = this.c03 * c20 + this.c13 * c21 + this.c23 * c22 + this.c33 * c23
+ val fc30 = this.c00 * c30 + this.c10 * c31 + this.c20 * c32 + this.c30 * c33
+ val fc31 = this.c01 * c30 + this.c11 * c31 + this.c21 * c32 + this.c31 * c33
+ val fc32 = this.c02 * c30 + this.c12 * c31 + this.c22 * c32 + this.c32 * c33
+ val fc33 = this.c03 * c30 + this.c13 * c31 + this.c23 * c32 + this.c33 * c33
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c03 = fc03
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c13 = fc13
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ this.c23 = fc23
+ this.c30 = fc30
+ this.c31 = fc31
+ this.c32 = fc32
+ this.c33 = fc33
+ return this
+ }
+
+ /**
+ * other * this, writes result into this
+ */
+ fun mulIntoThis(
+ c00: Float = 1f,
+ c01: Float = 0f,
+ c02: Float = 0f,
+ c03: Float = 0f,
+ c10: Float = 0f,
+ c11: Float = 1f,
+ c12: Float = 0f,
+ c13: Float = 0f,
+ c20: Float = 0f,
+ c21: Float = 0f,
+ c22: Float = 1f,
+ c23: Float = 0f,
+ c30: Float = 0f,
+ c31: Float = 0f,
+ c32: Float = 0f,
+ c33: Float = 1f,
+ ): Matrix4f {
+ val fc00 = c00 * this.c00 + c10 * this.c01 + c20 * this.c02 + c30 * this.c03
+ val fc01 = c01 * this.c00 + c11 * this.c01 + c21 * this.c02 + c31 * this.c03
+ val fc02 = c02 * this.c00 + c12 * this.c01 + c22 * this.c02 + c32 * this.c03
+ val fc03 = c03 * this.c00 + c13 * this.c01 + c23 * this.c02 + c33 * this.c03
+ val fc10 = c00 * this.c10 + c10 * this.c11 + c20 * this.c12 + c30 * this.c13
+ val fc11 = c01 * this.c10 + c11 * this.c11 + c21 * this.c12 + c31 * this.c13
+ val fc12 = c02 * this.c10 + c12 * this.c11 + c22 * this.c12 + c32 * this.c13
+ val fc13 = c03 * this.c10 + c13 * this.c11 + c23 * this.c12 + c33 * this.c13
+ val fc20 = c00 * this.c20 + c10 * this.c21 + c20 * this.c22 + c30 * this.c23
+ val fc21 = c01 * this.c20 + c11 * this.c21 + c21 * this.c22 + c31 * this.c23
+ val fc22 = c02 * this.c20 + c12 * this.c21 + c22 * this.c22 + c32 * this.c23
+ val fc23 = c03 * this.c20 + c13 * this.c21 + c23 * this.c22 + c33 * this.c23
+ val fc30 = c00 * this.c30 + c10 * this.c31 + c20 * this.c32 + c30 * this.c33
+ val fc31 = c01 * this.c30 + c11 * this.c31 + c21 * this.c32 + c31 * this.c33
+ val fc32 = c02 * this.c30 + c12 * this.c31 + c22 * this.c32 + c32 * this.c33
+ val fc33 = c03 * this.c30 + c13 * this.c31 + c23 * this.c32 + c33 * this.c33
+ this.c00 = fc00
+ this.c01 = fc01
+ this.c02 = fc02
+ this.c03 = fc03
+ this.c10 = fc10
+ this.c11 = fc11
+ this.c12 = fc12
+ this.c13 = fc13
+ this.c20 = fc20
+ this.c21 = fc21
+ this.c22 = fc22
+ this.c23 = fc23
+ this.c30 = fc30
+ this.c31 = fc31
+ this.c32 = fc32
+ this.c33 = fc33
+ return this
+ }
+
+ final override fun add(other: Float): Matrix4f {
+ return super.add(other) as Matrix4f
+ }
+
+ final override fun sub(other: Float): Matrix4f {
+ return super.sub(other) as Matrix4f
+ }
+
+ final override fun mul(other: Float): Matrix4f {
+ return super.mul(other) as Matrix4f
+ }
+
+ final override fun div(other: Float): Matrix4f {
+ return super.div(other) as Matrix4f
+ }
+
+ final override fun add(other: Float2DArray): Matrix4f {
+ return super.add(other) as Matrix4f
+ }
+
+ final override fun sub(other: Float2DArray): Matrix4f {
+ return super.sub(other) as Matrix4f
+ }
+
+ fun to2f() = Matrix2f.from(this)
+ fun to3f() = Matrix3f.from(this)
+
+ fun copy(): Matrix4f {
+ return Impl(c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33)
+ }
+
+ @JvmOverloads
+ fun translate(x: Float = 0f, y: Float = 0f, z: Float = 0f): Matrix4f {
+ return mul(c30 = x, c31 = y, c32 = z)
+ }
+
+ fun translate(value: IStruct2f) = translate(value.component1(), value.component2())
+ fun translate(value: IStruct3f) = translate(value.component1(), value.component2(), value.component3())
+
+ fun translate(value: Vector2f) = translate(value.component1(), value.component2())
+ fun translate(value: Vector3f) = translate(value.component1(), value.component2(), value.component3())
+
+ @JvmOverloads
+ fun preTranslate(x: Float = 0f, y: Float = 0f, z: Float = 0f): Matrix4f {
+ return mulIntoThis(c30 = x, c31 = y, c32 = z)
+ }
+
+ fun preTranslate(value: IStruct2f) = preTranslate(value.component1(), value.component2())
+ fun preTranslate(value: IStruct3f) = preTranslate(value.component1(), value.component2(), value.component3())
+
+ fun preTranslate(value: Vector2f) = preTranslate(value.component1(), value.component2())
+ fun preTranslate(value: Vector3f) = preTranslate(value.component1(), value.component2(), value.component3())
+
+ @JvmOverloads
+ fun scale(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f): Matrix4f {
+ return mul(c00 = x, c11 = y, c22 = z, c33 = w)
+ }
+
+ fun scale(value: IStruct2f) = scale(value.component1(), value.component2())
+ fun scale(value: IStruct3f) = scale(value.component1(), value.component2(), value.component3())
+ fun scale(value: IStruct4f) = scale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ fun scale(value: Vector2f) = scale(value.component1(), value.component2())
+ fun scale(value: Vector3f) = scale(value.component1(), value.component2(), value.component3())
+ fun scale(value: Vector4f) = scale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ @JvmOverloads
+ fun preScale(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f): Matrix4f {
+ return mulIntoThis(c00 = x, c11 = y, c22 = z, c33 = w)
+ }
+
+ fun preScale(value: IStruct2f) = preScale(value.component1(), value.component2())
+ fun preScale(value: IStruct3f) = preScale(value.component1(), value.component2(), value.component3())
+ fun preScale(value: IStruct4f) = preScale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ fun preScale(value: Vector2f) = preScale(value.component1(), value.component2())
+ fun preScale(value: Vector3f) = preScale(value.component1(), value.component2(), value.component3())
+ fun preScale(value: Vector4f) = preScale(value.component1(), value.component2(), value.component3(), value.component3())
+
+ fun rotateAroundX(angle: Float): Matrix4f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c11 = cos,
+ c21 = -sin,
+ c12 = sin,
+ c22 = cos,
+ )
+ }
+
+ fun rotateAroundY(angle: Float): Matrix4f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c00 = cos,
+ c20 = sin,
+ c02 = -sin,
+ c22 = cos,
+ )
+ }
+
+ fun rotateAroundZ(angle: Float): Matrix4f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mul(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ fun preRotateAroundX(angle: Float): Matrix4f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c11 = cos,
+ c21 = -sin,
+ c12 = sin,
+ c22 = cos,
+ )
+ }
+
+ fun preRotateAroundY(angle: Float): Matrix4f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c00 = cos,
+ c20 = sin,
+ c02 = -sin,
+ c22 = cos,
+ )
+ }
+
+ fun preRotateAroundZ(angle: Float): Matrix4f {
+ val cos = cos(angle)
+ val sin = sin(angle)
+
+ return mulIntoThis(
+ c00 = cos,
+ c10 = -sin,
+ c01 = sin,
+ c11 = cos,
+ )
+ }
+
+ private data class Impl(
+ override var c00: Float,
+ override var c01: Float,
+ override var c02: Float,
+ override var c03: Float,
+ override var c10: Float,
+ override var c11: Float,
+ override var c12: Float,
+ override var c13: Float,
+ override var c20: Float,
+ override var c21: Float,
+ override var c22: Float,
+ override var c23: Float,
+ override var c30: Float,
+ override var c31: Float,
+ override var c32: Float,
+ override var c33: Float,
+ ) : Matrix4f() {
+ override fun toString(): String {
+ return "Matrix4f" + super.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Matrix4f) return false
+
+ return this === other || c00 == other.c00 &&
+ c01 == other.c01 &&
+ c02 == other.c02 &&
+ c03 == other.c03 &&
+ c10 == other.c10 &&
+ c11 == other.c11 &&
+ c12 == other.c12 &&
+ c13 == other.c13 &&
+ c20 == other.c20 &&
+ c21 == other.c21 &&
+ c22 == other.c22 &&
+ c23 == other.c23 &&
+ c30 == other.c30 &&
+ c31 == other.c31 &&
+ c32 == other.c32 &&
+ c33 == other.c33
+ }
+ }
+
+ private class View(private val parent: Matrix4f) : Matrix4f() {
+ override var c00: Float get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c01: Float get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c02: Float get() = parent.c02; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c03: Float get() = parent.c03; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c10: Float get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c11: Float get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c12: Float get() = parent.c12; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c13: Float get() = parent.c13; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c20: Float get() = parent.c20; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c21: Float get() = parent.c21; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c22: Float get() = parent.c22; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c23: Float get() = parent.c23; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c30: Float get() = parent.c30; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c31: Float get() = parent.c31; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c32: Float get() = parent.c32; set(_) { throw UnsupportedOperationException("Read-Only view") }
+ override var c33: Float get() = parent.c33; set(_) { throw UnsupportedOperationException("Read-Only view") }
+
+ override fun set(column: Int, row: Int, value: Float) {
+ throw UnsupportedOperationException("Read-Only view")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this || parent == other
+ }
+
+ override fun hashCode(): Int {
+ return parent.hashCode()
+ }
+
+ override fun toString(): String {
+ return "View = $parent"
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun all(value: Float) = construct4(value, ::columnMajor)
+
+ @JvmStatic
+ fun zero() = all(0f)
+
+ @JvmStatic
+ fun identity() = columnMajor()
+
+ @JvmStatic
+ fun unmodifiable(value: Matrix4f): Matrix4f {
+ if (value is View)
+ return value
+ else
+ return View(value)
+ }
+
+ @JvmStatic
+ fun columnMajor(
+ c00: Float = 1f, c01: Float = 0f, c02: Float = 0f, c03: Float = 0f,
+ c10: Float = 0f, c11: Float = 1f, c12: Float = 0f, c13: Float = 0f,
+ c20: Float = 0f, c21: Float = 0f, c22: Float = 1f, c23: Float = 0f,
+ c30: Float = 0f, c31: Float = 0f, c32: Float = 0f, c33: Float = 1f,
+ ): Matrix4f {
+ return Impl(c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33)
+ }
+
+ @JvmStatic
+ fun rowMajor(
+ r00: Float = 1f, r01: Float = 0f, r02: Float = 0f, r03: Float = 0f,
+ r10: Float = 0f, r11: Float = 1f, r12: Float = 0f, r13: Float = 0f,
+ r20: Float = 0f, r21: Float = 0f, r22: Float = 1f, r23: Float = 0f,
+ r30: Float = 0f, r31: Float = 0f, r32: Float = 0f, r33: Float = 1f
+ ): Matrix4f {
+ return Impl(
+ r00, r10, r20, r30,
+ r01, r11, r21, r31,
+ r02, r12, r22, r32,
+ r03, r13, r23, r33
+ )
+ }
+
+ @JvmStatic
+ fun from(matrix: Float2DArray, filler: Float) = construct4(matrix, Float2DArray::get, ::columnMajor, filler)
+ @JvmStatic
+ fun from(matrix: Float2DArray) = construct4(matrix, Float2DArray::get, ::columnMajor, 0f)
+ @JvmStatic
+ fun from(matrix: Matrix4f) = construct4(matrix, Float2DArray::get, ::columnMajor)
+
+ @JvmStatic
+ fun fromTransposed(matrix: Float2DArray) = construct4(matrix, Float2DArray::get, Companion::rowMajor, 0f)
+ @JvmStatic
+ fun fromTransposed(matrix: Float2DArray, filler: Float) = construct4(matrix, Float2DArray::get, Companion::rowMajor, filler)
+ @JvmStatic
+ fun fromTransposed(matrix: Matrix4f) = construct4(matrix, Float2DArray::get, Companion::rowMajor)
+
+ @JvmStatic
+ fun fromColumnMajor(buffer: FloatBuffer) = construct4(buffer, FloatBuffer::get, ::columnMajor)
+ @JvmStatic
+ fun fromColumnMajor(buffer: ByteBuffer) = construct4(buffer, ByteBuffer::getFloat, ::columnMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: FloatBuffer) = construct4(buffer, FloatBuffer::get, Companion::rowMajor)
+ @JvmStatic
+ fun fromRowMajor(buffer: ByteBuffer) = construct4(buffer, ByteBuffer::getFloat, Companion::rowMajor)
+
+ /**
+ * Constructs new ortho projection matrix, with Y coordinate flipped (useful for OpenGL GUI drawing).
+ *
+ * Inversion of Y means that X 0 Y 0 will be top left position on screen (in OpenGL), not bottom left.
+ */
+ @JvmStatic
+ fun ortho(left: Float, right: Float, bottom: Float, top: Float, zNear: Float, zFar: Float): Matrix4f {
+ return rowMajor(
+ r00 = 2f / (right - left),
+ r11 = -2f / (top - bottom),
+ r22 = 2f / (zFar - zNear),
+ r03 = -(right + left) / (right - left),
+ r13 = -(top + bottom) / (top - bottom) + 2f,
+ r23 = -(zFar + zNear) / (zFar - zNear)
+ )
+ }
+
+ /**
+ * Constructs new ortho projection matrix
+ */
+ @JvmStatic
+ fun orthoDirect(left: Float, right: Float, bottom: Float, top: Float, zNear: Float, zFar: Float): Matrix4f {
+ return rowMajor(
+ r00 = 2f / (right - left),
+ r11 = 2f / (top - bottom),
+ r22 = 2f / (zFar - zNear),
+ r03 = -(right + left) / (right - left),
+ r13 = -(top + bottom) / (top - bottom),
+ r23 = -(zFar + zNear) / (zFar - zNear)
+ )
+ }
+
+ /**
+ * Constructs new perspective matrix
+ */
+ @JvmStatic
+ fun perspective(fov: Float, zFar: Float, zNear: Float): Matrix4f {
+ val scale = (1.0 / (tan(Math.toRadians(fov.toDouble()) / 2.0))).toFloat()
+ val r = zFar - zNear
+
+ return rowMajor(
+ r00 = scale,
+ r11 = scale,
+ r22 = -zFar / r,
+ r23 = -1f,
+ r32 = -zFar * zNear / r,
+ )
+ }
+
+ // kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
+ const val C00 = (0 or (0 shl 2))
+ const val C01 = (0 or (1 shl 2))
+ const val C02 = (0 or (2 shl 2))
+ const val C03 = (0 or (3 shl 2))
+ const val C10 = (1 or (0 shl 2))
+ const val C11 = (1 or (1 shl 2))
+ const val C12 = (1 or (2 shl 2))
+ const val C13 = (1 or (3 shl 2))
+ const val C20 = (2 or (0 shl 2))
+ const val C21 = (2 or (1 shl 2))
+ const val C22 = (2 or (2 shl 2))
+ const val C23 = (2 or (3 shl 2))
+ const val C30 = (3 or (0 shl 2))
+ const val C31 = (3 or (1 shl 2))
+ const val C32 = (3 or (2 shl 2))
+ const val C33 = (3 or (3 shl 2))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4fStack.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4fStack.kt
new file mode 100644
index 0000000..60cbd12
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/Matrix4fStack.kt
@@ -0,0 +1,55 @@
+package ru.dbotthepony.kommons.matrix
+
+class Matrix4fStack {
+ private val stack = ArrayDeque()
+
+ init {
+ stack.addLast(Matrix4f.identity())
+ }
+
+ fun clear(top: Matrix4f): Matrix4fStack {
+ stack.clear()
+ stack.addLast(top.copy())
+ return this
+ }
+
+ fun push(): Matrix4fStack {
+ stack.addLast(stack.last().copy())
+ return this
+ }
+
+ fun pop(): Matrix4f {
+ return stack.removeLast()
+ }
+
+ fun last(): Matrix4f {
+ return stack.last()
+ }
+
+ fun push(matrix: Matrix4f): Matrix4fStack {
+ stack.addLast(matrix.copy())
+ return this
+ }
+
+ fun identity() = push(Matrix4f.identity())
+ fun zero() = push(Matrix4f.all(0f))
+ fun all(value: Float) = push(Matrix4f.all(value))
+
+ inline fun with(block: Matrix4f.() -> T): T {
+ return try {
+ push()
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+
+ inline fun with(matrix: Matrix4f, block: Matrix4f.() -> T): T {
+ return try {
+ push(matrix)
+ block(last())
+ } finally {
+ pop()
+ }
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/MatrixOps.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/MatrixOps.kt
new file mode 100644
index 0000000..56dc2dd
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/matrix/MatrixOps.kt
@@ -0,0 +1,518 @@
+package ru.dbotthepony.kommons.matrix
+
+import ru.dbotthepony.kommons.arrays.Array2D
+import ru.dbotthepony.kommons.arrays.mulMatrixComponents
+import java.nio.Buffer
+
+private inline fun getOrElse(
+ get: (F, Int, Int) -> N,
+ input: F,
+ column: Int,
+ row: Int,
+ orElse: N
+): N {
+ if (column < input.columns && row < input.rows) {
+ return get(input, column, row)
+ } else {
+ return orElse
+ }
+}
+
+internal inline fun construct4(
+ from: F,
+ get: (F, Int, Int) -> N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R,
+ filler: N
+): R {
+ return factory(
+ getOrElse(get, from, 0, 0, filler),
+ getOrElse(get, from, 0, 1, filler),
+ getOrElse(get, from, 0, 2, filler),
+ getOrElse(get, from, 0, 3, filler),
+ getOrElse(get, from, 1, 0, filler),
+ getOrElse(get, from, 1, 1, filler),
+ getOrElse(get, from, 1, 2, filler),
+ getOrElse(get, from, 1, 3, filler),
+ getOrElse(get, from, 2, 0, filler),
+ getOrElse(get, from, 2, 1, filler),
+ getOrElse(get, from, 2, 2, filler),
+ getOrElse(get, from, 2, 3, filler),
+ getOrElse(get, from, 3, 0, filler),
+ getOrElse(get, from, 3, 1, filler),
+ getOrElse(get, from, 3, 2, filler),
+ getOrElse(get, from, 3, 3, filler),
+ )
+}
+
+internal inline fun construct3(
+ from: F,
+ get: (F, Int, Int) -> N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R,
+ filler: N
+): R {
+ return factory(
+ getOrElse(get, from, 0, 0, filler), getOrElse(get, from, 0, 1, filler), getOrElse(get, from, 0, 2, filler),
+ getOrElse(get, from, 1, 0, filler), getOrElse(get, from, 1, 1, filler), getOrElse(get, from, 1, 2, filler),
+ getOrElse(get, from, 2, 0, filler), getOrElse(get, from, 2, 1, filler), getOrElse(get, from, 2, 2, filler),
+ )
+}
+
+internal inline fun construct2(
+ from: F,
+ get: (F, Int, Int) -> N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R,
+ filler: N
+): R {
+ return factory(
+ getOrElse(get, from, 0, 0, filler), getOrElse(get, from, 0, 1, filler),
+ getOrElse(get, from, 1, 0, filler), getOrElse(get, from, 1, 1, filler),
+ )
+}
+
+internal inline fun construct4(
+ from: F,
+ get: (F, Int, Int) -> N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R
+): R {
+ require(from.columns >= 4 && from.rows >= 4) { "Can't use ${from.dimensionsString} matrix to construct 4x4 matrix" }
+
+ return factory(
+ get(from, 0, 0), get(from, 0, 1), get(from, 0, 2), get(from, 0, 3),
+ get(from, 1, 0), get(from, 1, 1), get(from, 1, 2), get(from, 1, 3),
+ get(from, 2, 0), get(from, 2, 1), get(from, 2, 2), get(from, 2, 3),
+ get(from, 3, 0), get(from, 3, 1), get(from, 3, 2), get(from, 3, 3),
+ )
+}
+
+internal inline fun construct4(
+ value: N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R
+): R {
+ return factory(
+ value, value, value, value,
+ value, value, value, value,
+ value, value, value, value,
+ value, value, value, value,
+ )
+}
+
+internal inline fun construct3(
+ from: F,
+ get: (F, Int, Int) -> N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R
+): R {
+ require(from.columns >= 3 && from.rows >= 3) { "Can't use ${from.dimensionsString} matrix to construct 3x3 matrix" }
+
+ return factory(
+ get(from, 0, 0), get(from, 0, 1), get(from, 0, 2),
+ get(from, 1, 0), get(from, 1, 1), get(from, 1, 2),
+ get(from, 2, 0), get(from, 2, 1), get(from, 2, 2),
+ )
+}
+
+internal inline fun construct3(
+ value: N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R
+): R {
+ return factory(
+ value, value, value,
+ value, value, value,
+ value, value, value,
+ )
+}
+
+internal inline fun construct2(
+ from: F,
+ get: (F, Int, Int) -> N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R
+): R {
+ require(from.columns >= 2 && from.rows >= 2) { "Can't use ${from.dimensionsString} matrix to construct 2x2 matrix" }
+
+ return factory(
+ get(from, 0, 0), get(from, 0, 1),
+ get(from, 1, 0), get(from, 1, 1),
+ )
+}
+
+internal inline fun construct2(
+ value: N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R
+): R {
+ return factory(
+ value, value,
+ value, value,
+ )
+}
+
+internal inline fun construct4(
+ from: F,
+ get: (F) -> N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R
+): R {
+ return factory(
+ get(from), get(from), get(from), get(from),
+ get(from), get(from), get(from), get(from),
+ get(from), get(from), get(from), get(from),
+ get(from), get(from), get(from), get(from),
+ )
+}
+
+internal inline fun construct3(
+ from: F,
+ get: (F) -> N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R
+): R {
+ return factory(
+ get(from), get(from), get(from),
+ get(from), get(from), get(from),
+ get(from), get(from), get(from),
+ )
+}
+
+internal inline fun construct2(
+ from: F,
+ get: (F) -> N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R
+): R {
+ return factory(
+ get(from), get(from),
+ get(from), get(from),
+ )
+}
+
+internal inline fun operator4(
+ a: F, b: F,
+ get: (F, Int, Int) -> N,
+ operator: (N, N) -> N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R
+): R {
+ return factory(
+ operator(get(a, 0, 0), get(b, 0, 0)), operator(get(a, 0, 1), get(b, 0, 1)), operator(get(a, 0, 2), get(b, 0, 2)), operator(get(a, 0, 3), get(b, 0, 3)),
+ operator(get(a, 1, 0), get(b, 1, 0)), operator(get(a, 1, 1), get(b, 1, 1)), operator(get(a, 1, 2), get(b, 1, 2)), operator(get(a, 1, 3), get(b, 1, 3)),
+ operator(get(a, 2, 0), get(b, 2, 0)), operator(get(a, 2, 1), get(b, 2, 1)), operator(get(a, 2, 2), get(b, 2, 2)), operator(get(a, 2, 3), get(b, 2, 3)),
+ operator(get(a, 3, 0), get(b, 3, 0)), operator(get(a, 3, 1), get(b, 3, 1)), operator(get(a, 3, 2), get(b, 3, 2)), operator(get(a, 3, 3), get(b, 3, 3)),
+ )
+}
+
+internal inline fun operator4(
+ a: F, b: N,
+ get: (F, Int, Int) -> N,
+ operator: (N, N) -> N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R
+): R {
+ return factory(
+ operator(get(a, 0, 0), b), operator(get(a, 0, 1), b), operator(get(a, 0, 2), b), operator(get(a, 0, 3), b),
+ operator(get(a, 1, 0), b), operator(get(a, 1, 1), b), operator(get(a, 1, 2), b), operator(get(a, 1, 3), b),
+ operator(get(a, 2, 0), b), operator(get(a, 2, 1), b), operator(get(a, 2, 2), b), operator(get(a, 2, 3), b),
+ operator(get(a, 3, 0), b), operator(get(a, 3, 1), b), operator(get(a, 3, 2), b), operator(get(a, 3, 3), b),
+ )
+}
+
+internal inline fun operator3(
+ a: F, b: F,
+ get: (F, Int, Int) -> N,
+ operator: (N, N) -> N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R
+): R {
+ return factory(
+ operator(get(a, 0, 0), get(b, 0, 0)), operator(get(a, 0, 1), get(b, 0, 1)), operator(get(a, 0, 2), get(b, 0, 2)),
+ operator(get(a, 1, 0), get(b, 1, 0)), operator(get(a, 1, 1), get(b, 1, 1)), operator(get(a, 1, 2), get(b, 1, 2)),
+ operator(get(a, 2, 0), get(b, 2, 0)), operator(get(a, 2, 1), get(b, 2, 1)), operator(get(a, 2, 2), get(b, 2, 2)),
+ )
+}
+
+internal inline fun operator3(
+ a: F, b: N,
+ get: (F, Int, Int) -> N,
+ operator: (N, N) -> N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R
+): R {
+ return factory(
+ operator(get(a, 0, 0), b), operator(get(a, 0, 1), b), operator(get(a, 0, 2), b),
+ operator(get(a, 1, 0), b), operator(get(a, 1, 1), b), operator(get(a, 1, 2), b),
+ operator(get(a, 2, 0), b), operator(get(a, 2, 1), b), operator(get(a, 2, 2), b),
+ )
+}
+
+internal inline fun operator2(
+ a: F, b: F,
+ get: (F, Int, Int) -> N,
+ operator: (N, N) -> N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R
+): R {
+ return factory(
+ operator(get(a, 0, 0), get(b, 0, 0)), operator(get(a, 0, 1), get(b, 0, 1)),
+ operator(get(a, 1, 0), get(b, 1, 0)), operator(get(a, 1, 1), get(b, 1, 1)),
+ )
+}
+
+internal inline fun operator2(
+ a: F, b: N,
+ get: (F, Int, Int) -> N,
+ operator: (N, N) -> N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R
+): R {
+ return factory(
+ operator(get(a, 0, 0), b), operator(get(a, 0, 1), b),
+ operator(get(a, 1, 0), b), operator(get(a, 1, 1), b),
+ )
+}
+
+internal inline fun times4(
+ a: F, b: F,
+ get: (F, Int, Int) -> N,
+ times: (N, N) -> N,
+ plus: (N, N) -> N,
+ factory: (
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ N, N, N, N,
+ ) -> R
+): R {
+ return factory(
+ mulMatrixComponents(a, b, get, times, plus, 0, 0),
+ mulMatrixComponents(a, b, get, times, plus, 0, 1),
+ mulMatrixComponents(a, b, get, times, plus, 0, 2),
+ mulMatrixComponents(a, b, get, times, plus, 0, 3),
+ mulMatrixComponents(a, b, get, times, plus, 1, 0),
+ mulMatrixComponents(a, b, get, times, plus, 1, 1),
+ mulMatrixComponents(a, b, get, times, plus, 1, 2),
+ mulMatrixComponents(a, b, get, times, plus, 1, 3),
+ mulMatrixComponents(a, b, get, times, plus, 2, 0),
+ mulMatrixComponents(a, b, get, times, plus, 2, 1),
+ mulMatrixComponents(a, b, get, times, plus, 2, 2),
+ mulMatrixComponents(a, b, get, times, plus, 2, 3),
+ mulMatrixComponents(a, b, get, times, plus, 3, 0),
+ mulMatrixComponents(a, b, get, times, plus, 3, 1),
+ mulMatrixComponents(a, b, get, times, plus, 3, 2),
+ mulMatrixComponents(a, b, get, times, plus, 3, 3),
+ )
+}
+
+internal inline fun times3(
+ a: F, b: F,
+ get: (F, Int, Int) -> N,
+ times: (N, N) -> N,
+ plus: (N, N) -> N,
+ factory: (
+ N, N, N,
+ N, N, N,
+ N, N, N,
+ ) -> R
+): R {
+ return factory(
+ mulMatrixComponents(a, b, get, times, plus, 0, 0),
+ mulMatrixComponents(a, b, get, times, plus, 0, 1),
+ mulMatrixComponents(a, b, get, times, plus, 0, 2),
+ mulMatrixComponents(a, b, get, times, plus, 1, 0),
+ mulMatrixComponents(a, b, get, times, plus, 1, 1),
+ mulMatrixComponents(a, b, get, times, plus, 1, 2),
+ mulMatrixComponents(a, b, get, times, plus, 2, 0),
+ mulMatrixComponents(a, b, get, times, plus, 2, 1),
+ mulMatrixComponents(a, b, get, times, plus, 2, 2),
+ )
+}
+
+internal inline fun times2(
+ a: F, b: F,
+ get: (F, Int, Int) -> N,
+ times: (N, N) -> N,
+ plus: (N, N) -> N,
+ factory: (
+ N, N,
+ N, N,
+ ) -> R
+): R {
+ return factory(
+ mulMatrixComponents(a, b, get, times, plus, 0, 0), mulMatrixComponents(a, b, get, times, plus, 0, 1),
+ mulMatrixComponents(a, b, get, times, plus, 1, 0), mulMatrixComponents(a, b, get, times, plus, 1, 1),
+ )
+}
+
+internal inline fun operator4(
+ a: R, b: F,
+ get: (F, Int, Int) -> N,
+ set: (R, Int, Int, N) -> Unit,
+ operator: (N, N) -> N,
+): R {
+ a.requireSizeEquals(b)
+ set(a, 0, 0, operator(get(a, 0, 0), get(b, 0, 0)))
+ set(a, 0, 1, operator(get(a, 0, 1), get(b, 0, 1)))
+ set(a, 0, 2, operator(get(a, 0, 2), get(b, 0, 2)))
+ set(a, 0, 3, operator(get(a, 0, 3), get(b, 0, 3)))
+ set(a, 1, 0, operator(get(a, 1, 0), get(b, 1, 0)))
+ set(a, 1, 1, operator(get(a, 1, 1), get(b, 1, 1)))
+ set(a, 1, 2, operator(get(a, 1, 2), get(b, 1, 2)))
+ set(a, 1, 3, operator(get(a, 1, 3), get(b, 1, 3)))
+ set(a, 2, 0, operator(get(a, 2, 0), get(b, 2, 0)))
+ set(a, 2, 1, operator(get(a, 2, 1), get(b, 2, 1)))
+ set(a, 2, 2, operator(get(a, 2, 2), get(b, 2, 2)))
+ set(a, 2, 3, operator(get(a, 2, 3), get(b, 2, 3)))
+ set(a, 3, 0, operator(get(a, 3, 0), get(b, 3, 0)))
+ set(a, 3, 1, operator(get(a, 3, 1), get(b, 3, 1)))
+ set(a, 3, 2, operator(get(a, 3, 2), get(b, 3, 2)))
+ set(a, 3, 3, operator(get(a, 3, 3), get(b, 3, 3)))
+ return a
+}
+
+internal inline fun operator4(
+ a: R, b: N,
+ get: (F, Int, Int) -> N,
+ set: (R, Int, Int, N) -> Unit,
+ operator: (N, N) -> N,
+): R {
+ set(a, 0, 0, operator(get(a, 0, 0), b))
+ set(a, 0, 1, operator(get(a, 0, 1), b))
+ set(a, 0, 2, operator(get(a, 0, 2), b))
+ set(a, 0, 3, operator(get(a, 0, 3), b))
+ set(a, 1, 0, operator(get(a, 1, 0), b))
+ set(a, 1, 1, operator(get(a, 1, 1), b))
+ set(a, 1, 2, operator(get(a, 1, 2), b))
+ set(a, 1, 3, operator(get(a, 1, 3), b))
+ set(a, 2, 0, operator(get(a, 2, 0), b))
+ set(a, 2, 1, operator(get(a, 2, 1), b))
+ set(a, 2, 2, operator(get(a, 2, 2), b))
+ set(a, 2, 3, operator(get(a, 2, 3), b))
+ set(a, 3, 0, operator(get(a, 3, 0), b))
+ set(a, 3, 1, operator(get(a, 3, 1), b))
+ set(a, 3, 2, operator(get(a, 3, 2), b))
+ set(a, 3, 3, operator(get(a, 3, 3), b))
+ return a
+}
+
+internal inline fun operator3(
+ a: R, b: F,
+ get: (F, Int, Int) -> N,
+ set: (R, Int, Int, N) -> Unit,
+ operator: (N, N) -> N,
+): R {
+ a.requireSizeEquals(b)
+ set(a, 0, 0, operator(get(a, 0, 0), get(b, 0, 0)))
+ set(a, 0, 1, operator(get(a, 0, 1), get(b, 0, 1)))
+ set(a, 0, 2, operator(get(a, 0, 2), get(b, 0, 2)))
+ set(a, 1, 0, operator(get(a, 1, 0), get(b, 1, 0)))
+ set(a, 1, 1, operator(get(a, 1, 1), get(b, 1, 1)))
+ set(a, 1, 2, operator(get(a, 1, 2), get(b, 1, 2)))
+ set(a, 2, 0, operator(get(a, 2, 0), get(b, 2, 0)))
+ set(a, 2, 1, operator(get(a, 2, 1), get(b, 2, 1)))
+ set(a, 2, 2, operator(get(a, 2, 2), get(b, 2, 2)))
+ return a
+}
+
+internal inline fun operator3(
+ a: R, b: N,
+ get: (F, Int, Int) -> N,
+ set: (R, Int, Int, N) -> Unit,
+ operator: (N, N) -> N,
+): R {
+ set(a, 0, 0, operator(get(a, 0, 0), b))
+ set(a, 0, 1, operator(get(a, 0, 1), b))
+ set(a, 0, 2, operator(get(a, 0, 2), b))
+ set(a, 1, 0, operator(get(a, 1, 0), b))
+ set(a, 1, 1, operator(get(a, 1, 1), b))
+ set(a, 1, 2, operator(get(a, 1, 2), b))
+ set(a, 2, 0, operator(get(a, 2, 0), b))
+ set(a, 2, 1, operator(get(a, 2, 1), b))
+ set(a, 2, 2, operator(get(a, 2, 2), b))
+ return a
+}
+
+internal inline fun operator2(
+ a: R, b: F,
+ get: (F, Int, Int) -> N,
+ set: (R, Int, Int, N) -> Unit,
+ operator: (N, N) -> N,
+): R {
+ a.requireSizeEquals(b)
+ set(a, 0, 0, operator(get(a, 0, 0), get(b, 0, 0)))
+ set(a, 0, 1, operator(get(a, 0, 1), get(b, 0, 1)))
+ set(a, 1, 0, operator(get(a, 1, 0), get(b, 1, 0)))
+ set(a, 1, 1, operator(get(a, 1, 1), get(b, 1, 1)))
+ return a
+}
+
+internal inline fun operator2(
+ a: R, b: N,
+ get: (F, Int, Int) -> N,
+ set: (R, Int, Int, N) -> Unit,
+ operator: (N, N) -> N,
+): R {
+ set(a, 0, 0, operator(get(a, 0, 0), b))
+ set(a, 0, 1, operator(get(a, 0, 1), b))
+ set(a, 1, 0, operator(get(a, 1, 0), b))
+ set(a, 1, 1, operator(get(a, 1, 1), b))
+ return a
+}
\ No newline at end of file
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/util/AABB.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/util/AABB.kt
new file mode 100644
index 0000000..fd171ac
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/util/AABB.kt
@@ -0,0 +1,214 @@
+
+@file:Suppress("unused", "LiftReturnOrAssignment", "MemberVisibilityCanBePrivate")
+
+package ru.dbotthepony.kvector.util2d
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.math.intersectRectangles
+import ru.dbotthepony.kommons.math.rectangleContainsRectangle
+import ru.dbotthepony.kommons.vector.Vector2d
+import kotlin.math.absoluteValue
+
+/**
+ * Axis Aligned Bounding Box, represented by two points, [mins] as lowermost corner of BB,
+ * and [maxs] as uppermost corner of BB
+ */
+data class AABB(val mins: Vector2d, val maxs: Vector2d) {
+ init {
+ require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
+ require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
+ }
+
+ operator fun plus(other: AABB) = AABB(mins + other.mins, maxs + other.maxs)
+ operator fun minus(other: AABB) = AABB(mins - other.mins, maxs - other.maxs)
+ operator fun times(other: AABB) = AABB(mins * other.mins, maxs * other.maxs)
+ operator fun div(other: AABB) = AABB(mins / other.mins, maxs / other.maxs)
+
+ operator fun plus(other: Vector2d) = AABB(mins + other, maxs + other)
+ operator fun minus(other: Vector2d) = AABB(mins - other, maxs - other)
+ operator fun times(other: Vector2d) = AABB(mins * other, maxs * other)
+ operator fun div(other: Vector2d) = AABB(mins / other, maxs / other)
+
+ operator fun times(other: Double) = AABB(mins * other, maxs * other)
+ operator fun div(other: Double) = AABB(mins / other, maxs / other)
+
+ val xSpan get() = maxs.x - mins.x
+ val ySpan get() = maxs.y - mins.y
+ val centre get() = (mins + maxs) * 0.5
+
+ val A get() = mins
+ val B get() = Vector2d(mins.x, maxs.y)
+ val C get() = maxs
+ val D get() = Vector2d(maxs.x, mins.y)
+
+ val bottomLeft get() = A
+ val topLeft get() = B
+ val topRight get() = C
+ val bottomRight get() = D
+
+ val width get() = maxs.x - mins.x
+ val height get() = maxs.y - mins.y
+
+ val extents get() = Vector2d(width * 0.5, height * 0.5)
+
+ val diameter get() = mins.distance(maxs)
+ val radius get() = diameter / 2.0
+
+ val perimeter get() = (xSpan + ySpan) * 2.0
+
+ fun isInside(point: IStruct2d): Boolean {
+ return point.component1() in mins.x .. maxs.x && point.component2() in mins.y .. maxs.y
+ }
+
+ fun isInside(x: Double, y: Double): Boolean {
+ return x in mins.x .. maxs.x && y in mins.y .. maxs.y
+ }
+
+ /**
+ * Checks whenever is this AABB intersect with [other]
+ *
+ * This method consider they intersect even if only one of axis are equal
+ */
+ fun intersect(other: AABB): Boolean {
+ return intersectRectangles(mins, maxs, other.mins, other.maxs)
+ }
+
+ /**
+ * Returns whenever [other] is contained (encased) inside this AABB
+ */
+ operator fun contains(other: AABB): Boolean {
+ return rectangleContainsRectangle(mins, maxs, other.mins, other.maxs)
+ }
+
+ /**
+ * Checks whenever is this AABB intersect with [other]
+ *
+ * This method DOES NOT consider they intersect if only one of axis are equal
+ */
+ fun intersectWeak(other: AABB): Boolean {
+ if (maxs.x == other.mins.x || mins.x == other.maxs.x || maxs.y == other.mins.y || mins.y == other.maxs.y)
+ return false
+
+ return intersectRectangles(mins, maxs, other.mins, other.maxs)
+ }
+
+ fun intersectionDepth(other: AABB): Vector2d {
+ val xDepth: Double
+ val yDepth: Double
+
+ val thisCentre = centre
+ val otherCentre = other.centre
+
+ if (thisCentre.x > otherCentre.x) {
+ // считаем, что мы вошли справа
+ xDepth = mins.x - other.maxs.x
+ } else {
+ // считаем, что мы вошли слева
+ xDepth = maxs.x - other.mins.x
+ }
+
+ if (thisCentre.y > otherCentre.y) {
+ // считаем, что мы вошли сверху
+ yDepth = mins.y - other.maxs.y
+ } else {
+ // считаем, что мы вошли снизу
+ yDepth = maxs.x - other.mins.x
+ }
+
+ return Vector2d(xDepth, yDepth)
+ }
+
+ fun distance(other: AABB): Double {
+ val intersectX: Boolean
+ val intersectY: Boolean
+
+ if (ySpan <= other.ySpan)
+ intersectY = mins.y in other.mins.y .. other.maxs.y || maxs.y in other.mins.y .. other.maxs.y
+ else
+ intersectY = other.mins.y in mins.y .. maxs.y || other.maxs.y in mins.y .. maxs.y
+
+ if (xSpan <= other.xSpan)
+ intersectX = mins.x in other.mins.x .. other.maxs.x || maxs.x in other.mins.x .. other.maxs.x
+ else
+ intersectX = other.mins.x in mins.x .. maxs.x || other.maxs.x in mins.x .. maxs.x
+
+ if (intersectY && intersectX) {
+ return 0.0
+ }
+
+ if (intersectX) {
+ return (mins.y - other.maxs.y).absoluteValue.coerceAtMost((maxs.y - other.mins.y).absoluteValue)
+ } else {
+ return (mins.x - other.maxs.x).absoluteValue.coerceAtMost((maxs.x - other.mins.x).absoluteValue)
+ }
+ }
+
+ fun pushOutFrom(other: AABB): Vector2d {
+ if (!intersect(other))
+ return Vector2d.ZERO
+
+ val depth = intersectionDepth(other)
+
+ if (depth.x.absoluteValue < depth.y.absoluteValue) {
+ return Vector2d(x = depth.x)
+ } else {
+ return Vector2d(y = depth.y)
+ }
+ }
+
+ /**
+ * Returns AABB which contains both AABBs
+ */
+ fun combine(other: AABB): AABB {
+ if (contains(other)) return this
+
+ val minX = mins.x.coerceAtMost(other.mins.x)
+ val minY = mins.y.coerceAtMost(other.mins.y)
+ val maxX = maxs.x.coerceAtLeast(other.maxs.x)
+ val maxY = maxs.y.coerceAtLeast(other.maxs.y)
+
+ return AABB(Vector2d(minX, minY), Vector2d(maxX, maxY))
+ }
+
+ /**
+ * Returns AABB which contains this AABB and specified point
+ */
+ fun expand(x: Double, y: Double): AABB {
+ if (isInside(x, y))
+ return this
+
+ return AABB(
+ mins.coerceAtMost(x, y),
+ maxs.coerceAtLeast(x, y)
+ )
+ }
+
+ fun expand(value: IStruct2d) = expand(value.component1(), value.component2())
+
+ /**
+ * Returns AABB which edges are expanded by [x] and [y] along their normals
+ */
+ fun enlarge(x: Double, y: Double): AABB {
+ if (x == 0.0 && y == 0.0) return this
+
+ return AABB(
+ Vector2d(mins.x - x, mins.y - y),
+ Vector2d(maxs.x + x, maxs.y + y),
+ )
+ }
+
+ companion object {
+ fun rectangle(pos: IStruct2d, width: Double, height: Double = width): AABB {
+ val (x, y) = pos
+
+ return AABB(
+ Vector2d(x - width / 2.0, y - height / 2.0),
+ Vector2d(x + width / 2.0, y + height / 2.0),
+ )
+ }
+
+ @JvmField val ZERO = AABB(Vector2d.ZERO, Vector2d.ZERO)
+ }
+}
+
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/util/AABBi.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/util/AABBi.kt
new file mode 100644
index 0000000..19da290
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/util/AABBi.kt
@@ -0,0 +1,116 @@
+package ru.dbotthepony.kvector.util2d
+
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.math.intersectRectangles
+import ru.dbotthepony.kommons.math.rectangleContainsRectangle
+import ru.dbotthepony.kommons.vector.Vector2d
+import ru.dbotthepony.kommons.vector.Vector2i
+
+data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
+ init {
+ require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
+ require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
+ }
+
+ operator fun plus(other: AABBi) = AABBi(mins + other.mins, maxs + other.maxs)
+ operator fun minus(other: AABBi) = AABBi(mins - other.mins, maxs - other.maxs)
+ operator fun times(other: AABBi) = AABBi(mins * other.mins, maxs * other.maxs)
+ operator fun div(other: AABBi) = AABBi(mins / other.mins, maxs / other.maxs)
+
+ operator fun plus(other: Vector2i) = AABBi(mins + other, maxs + other)
+ operator fun minus(other: Vector2i) = AABBi(mins - other, maxs - other)
+ operator fun times(other: Vector2i) = AABBi(mins * other, maxs * other)
+ operator fun div(other: Vector2i) = AABBi(mins / other, maxs / other)
+
+ operator fun times(other: Int) = AABBi(mins * other, maxs * other)
+ operator fun div(other: Int) = AABBi(mins / other, maxs / other)
+
+ val xSpan get() = maxs.x - mins.x
+ val ySpan get() = maxs.y - mins.y
+ val centre get() = (mins.toDoubleVector() + maxs.toDoubleVector()) * 0.5
+
+ val A get() = mins
+ val B get() = Vector2i(mins.x, maxs.y)
+ val C get() = maxs
+ val D get() = Vector2i(maxs.x, mins.y)
+
+ val bottomLeft get() = A
+ val topLeft get() = B
+ val topRight get() = C
+ val bottomRight get() = D
+
+ val width get() = (maxs.x - mins.x) / 2
+ val height get() = (maxs.y - mins.y) / 2
+
+ val diameter get() = mins.distance(maxs)
+ val radius get() = diameter / 2.0
+
+ val perimeter get() = (xSpan + ySpan) * 2
+
+ fun isInside(point: IStruct2i): Boolean {
+ return point.component1() in mins.x .. maxs.x && point.component2() in mins.y .. maxs.y
+ }
+
+ fun isInside(x: Int, y: Int): Boolean {
+ return x in mins.x .. maxs.x && y in mins.y .. maxs.y
+ }
+
+ /**
+ * Checks whenever is this AABBi intersect with [other]
+ *
+ * This method consider they intersect even if only one of axis are equal
+ */
+ fun intersect(other: AABBi): Boolean {
+ return intersectRectangles(mins, maxs, other.mins, other.maxs)
+ }
+
+ /**
+ * Returns whenever [other] is contained (encased) inside this AABBi
+ */
+ operator fun contains(other: AABBi): Boolean {
+ return rectangleContainsRectangle(mins, maxs, other.mins, other.maxs)
+ }
+
+ /**
+ * Checks whenever is this AABB intersect with [other]
+ *
+ * This method DOES NOT consider they intersect if only one of axis are equal
+ */
+ fun intersectWeak(other: AABBi): Boolean {
+ if (maxs.x == other.mins.x || mins.x == other.maxs.x || maxs.y == other.mins.y || mins.y == other.maxs.y)
+ return false
+
+ return intersectRectangles(mins, maxs, other.mins, other.maxs)
+ }
+
+ fun toDoubleAABB() = AABB(mins.toDoubleVector(), maxs.toDoubleVector())
+
+ /**
+ * Returns AABB which contains this AABB and specified point
+ */
+ fun expand(x: Int, y: Int): AABBi {
+ if (isInside(x, y))
+ return this
+
+ return AABBi(
+ mins.coerceAtMost(x, y),
+ maxs.coerceAtLeast(x, y)
+ )
+ }
+
+ fun expand(value: IStruct2i) = expand(value.component1(), value.component2())
+
+ /**
+ * Returns AABB which contains both AABBs
+ */
+ fun combine(other: AABBi): AABBi {
+ if (contains(other)) return this
+
+ val minX = mins.x.coerceAtMost(other.mins.x)
+ val minY = mins.y.coerceAtMost(other.mins.y)
+ val maxX = maxs.x.coerceAtLeast(other.maxs.x)
+ val maxY = maxs.y.coerceAtLeast(other.maxs.y)
+
+ return AABBi(Vector2i(minX, minY), Vector2i(maxX, maxY))
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector.kt
new file mode 100644
index 0000000..0ad759e
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector.kt
@@ -0,0 +1,119 @@
+package ru.dbotthepony.kommons.vector
+
+import kotlin.math.sqrt
+
+/**
+ * Vectors are exclusively immutable
+ */
+sealed class Vector {
+ val length: Double get() = sqrt(lengthSquared)
+ val isValid: Boolean get() = isFinite && !isNaN
+
+ abstract val isFinite: Boolean
+ abstract val isNaN: Boolean
+ abstract val lengthSquared: Double
+
+ /**
+ * @throws IllegalArgumentException if matrix is not finite
+ */
+ inline fun requireIsFinite(lambda: () -> Any) {
+ if (!isFinite) {
+ throw IllegalArgumentException(lambda.invoke().toString())
+ }
+ }
+
+ /**
+ * @throws IllegalArgumentException if matrix is not finite
+ */
+ fun requireIsFinite() {
+ if (!isFinite) {
+ throw IllegalArgumentException("Matrix is not finite")
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if matrix is not finite
+ */
+ inline fun checkIsFinite(lambda: () -> Any) {
+ if (!isFinite) {
+ throw IllegalStateException(lambda.invoke().toString())
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if matrix is not finite
+ */
+ fun checkIsFinite() {
+ if (!isFinite) {
+ throw IllegalStateException("Matrix is not finite")
+ }
+ }
+
+ /**
+ * @throws IllegalArgumentException if vector has NaN component
+ */
+ inline fun requireNotNaN(lambda: () -> Any) {
+ if (isNaN) {
+ throw IllegalArgumentException(lambda.invoke().toString())
+ }
+ }
+
+ /**
+ * @throws IllegalArgumentException if matrix has NaN component
+ */
+ fun requireNotNaN() {
+ if (isNaN) {
+ throw IllegalArgumentException("Matrix has NaN component")
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if matrix has NaN component
+ */
+ inline fun checkNotNaN(lambda: () -> Any) {
+ if (!isFinite) {
+ throw IllegalStateException(lambda.invoke().toString())
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if matrix has NaN component
+ */
+ fun checkNotNaN() {
+ if (!isFinite) {
+ throw IllegalStateException("Matrix has NaN component")
+ }
+ }
+
+ /**
+ * @throws IllegalArgumentException if matrix has NaN or infinite component
+ */
+ fun requireIsValid() {
+ requireIsFinite()
+ requireNotNaN()
+ }
+
+ /**
+ * @throws IllegalStateException if matrix has NaN or infinite component
+ */
+ fun checkIsValid() {
+ requireIsFinite()
+ requireNotNaN()
+ }
+
+ /**
+ * @throws IllegalArgumentException if matrix has NaN or infinite component
+ */
+ inline fun requireIsValid(lambda: () -> Any) {
+ requireIsFinite(lambda)
+ requireNotNaN(lambda)
+ }
+
+ /**
+ * @throws IllegalStateException if matrix has NaN or infinite component
+ */
+ inline fun checkIsValid(lambda: () -> Any) {
+ requireIsFinite(lambda)
+ requireNotNaN(lambda)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2d.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2d.kt
new file mode 100644
index 0000000..b388f71
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2d.kt
@@ -0,0 +1,204 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.matrix.Matrix2d
+import ru.dbotthepony.kommons.matrix.Matrix3d
+import ru.dbotthepony.kommons.matrix.Matrix4d
+import ru.dbotthepony.kommons.matrix.Matrix4dStack
+import kotlin.math.absoluteValue
+import kotlin.math.acos
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.sqrt
+
+data class Vector2d(
+ val x: Double = 0.0,
+ val y: Double = 0.0,
+) : IStruct2d, Vector() {
+ constructor(value: IStruct2d) : this(value.component1(), value.component2())
+ constructor(value: Vector2d) : this(value.x, value.y)
+
+ override val lengthSquared: Double
+ get() = x * x + y * y
+
+ override val isFinite: Boolean
+ get() = x.isFinite() && y.isFinite()
+ override val isNaN: Boolean
+ get() = x.isNaN() || y.isNaN()
+
+ val unitVector: Vector2d get() {
+ var length = lengthSquared
+ if (length == 0.0 || length == 1.0) return this
+ length = sqrt(length)
+ return Vector2d(x / length, y / length)
+ }
+
+ val absoluteValue: Vector2d get() {
+ val x = x.absoluteValue
+ val y = y.absoluteValue
+ if (x != this.x || y != this.y) return Vector2d(x, y)
+ return this
+ }
+
+ val r get() = x
+ val g get() = y
+
+ val s get() = x
+ val t get() = y
+
+ fun plus(x: Double = 0.0, y: Double = 0.0) = Vector2d(this.x + x, this.y + y)
+ fun minus(x: Double = 0.0, y: Double = 0.0) = Vector2d(this.x - x, this.y - y)
+ fun times(x: Double = 1.0, y: Double = 1.0) = Vector2d(this.x * x, this.y * y)
+ fun div(x: Double = 1.0, y: Double = 1.0) = Vector2d(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2d): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2d): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value)
+ operator fun minus(value: Double) = minus(value, value)
+ operator fun times(value: Double) = times(value, value)
+ operator fun div(value: Double) = div(value, value)
+
+ fun plus(x: Int = 0, y: Int = 0) = Vector2d(this.x + x, this.y + y)
+ fun minus(x: Int = 0, y: Int = 0) = Vector2d(this.x - x, this.y - y)
+ fun times(x: Int = 1, y: Int = 1) = Vector2d(this.x * x, this.y * y)
+ fun div(x: Int = 1, y: Int = 1) = Vector2d(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2i): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2i): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value)
+ operator fun minus(value: Int) = minus(value, value)
+ operator fun times(value: Int) = times(value, value)
+ operator fun div(value: Int) = div(value, value)
+
+ fun plus(x: Float = 0f, y: Float = 0f) = Vector2d(this.x + x, this.y + y)
+ fun minus(x: Float = 0f, y: Float = 0f) = Vector2d(this.x - x, this.y - y)
+ fun times(x: Float = 1f, y: Float = 1f) = Vector2d(this.x * x, this.y * y)
+ fun div(x: Float = 1f, y: Float = 1f) = Vector2d(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2f): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2f): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value)
+ operator fun minus(value: Float) = minus(value, value)
+ operator fun times(value: Float) = times(value, value)
+ operator fun div(value: Float) = div(value, value)
+
+ operator fun unaryMinus(): Vector2d = Vector2d(-x, -y)
+
+ fun dot(x: Double, y: Double) = this.x * x + this.y * y
+ fun dot(value: IStruct2d): Double { val (x, y) = value; return dot(x, y) }
+ fun dot(value: Vector2d): Double { val (x, y) = value; return dot(x, y) }
+
+ fun distanceSquared(x: Double, y: Double): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ return dx * dx + dy * dy
+ }
+
+ fun distanceSquared(value: IStruct2d): Double { val (x, y) = value; return distanceSquared(x, y) }
+ fun distanceSquared(value: Vector2d): Double { val (x, y) = value; return distanceSquared(x, y) }
+ fun distance(x: Double, y: Double) = sqrt(distanceSquared(x, y))
+ fun distance(value: IStruct2d): Double { val (x, y) = value; return sqrt(distanceSquared(x, y)) }
+ fun distance(value: Vector2d): Double { val (x, y) = value; return sqrt(distanceSquared(x, y)) }
+
+ fun coerceAtLeast(x: Double, y: Double) = Vector2d(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y))
+ fun coerceAtMost(x: Double, y: Double) = Vector2d(this.x.coerceAtMost(x), this.y.coerceAtMost(y))
+
+ fun coerceIn(minimum: Vector2d, maximum: Vector2d) = Vector2d(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y))
+
+ fun coerceIn(minimum: IStruct2d, maximum: IStruct2d) = Vector2d(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()))
+
+ fun coerceAtLeast(value: IStruct2d): Vector2d { val (x, y) = value; return coerceAtLeast(x, y) }
+ fun coerceAtMost(value: IStruct2d): Vector2d { val (x, y) = value; return coerceAtMost(x, y) }
+ fun coerceAtLeast(value: Vector2d): Vector2d { val (x, y) = value; return coerceAtLeast(x, y) }
+ fun coerceAtMost(value: Vector2d): Vector2d { val (x, y) = value; return coerceAtMost(x, y) }
+
+ fun rotate(angle: Double): Vector2d {
+ val s = sin(angle)
+ val c = cos(angle)
+
+ return Vector2d(x * c - s * y, s * x + c * y)
+ }
+
+ fun toAngle(zeroAngle: Vector2d): Double {
+ val dot = unitVector.dot(zeroAngle.unitVector)
+
+ return if (y > 0.0) {
+ acos(dot)
+ } else {
+ -acos(dot)
+ }
+ }
+
+ fun toAngle(zeroAngle: IStruct2d): Double {
+ return toAngle(Vector2d(zeroAngle))
+ }
+
+ fun toAngle(): Double {
+ return atan2(y, x)
+ }
+
+ operator fun times(value: Matrix4d): Vector2d {
+ return Vector2d(
+ x * value.c00 + y * value.c10 + value.c30,
+ x * value.c01 + y * value.c11 + value.c31,
+ )
+ }
+
+ operator fun times(value: Matrix3d): Vector2d {
+ return Vector2d(
+ x * value.c00 + y * value.c10 + value.c20,
+ x * value.c01 + y * value.c11 + value.c21,
+ )
+ }
+
+ operator fun times(value: Matrix2d): Vector2d {
+ return Vector2d(
+ x * value.c00 + y * value.c10,
+ x * value.c01 + y * value.c11,
+ )
+ }
+
+ operator fun times(value: Matrix4dStack) = times(value.last())
+
+ companion object {
+ @JvmField val ZERO = Vector2d()
+ @JvmField val POSITIVE_X = Vector2d(x = 1.0)
+ @JvmField val NEGATIVE_X = Vector2d(x = -1.0)
+ @JvmField val POSITIVE_Y = Vector2d(y = 1.0)
+ @JvmField val NEGATIVE_Y = Vector2d(y = -1.0)
+
+ @JvmField val POSITIVE_XY = Vector2d(1.0, 1.0)
+ @JvmField val NEGATIVE_XY = Vector2d(-1.0, -1.0)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2f.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2f.kt
new file mode 100644
index 0000000..86490bb
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2f.kt
@@ -0,0 +1,206 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.matrix.Matrix2f
+import ru.dbotthepony.kommons.matrix.Matrix3f
+import ru.dbotthepony.kommons.matrix.Matrix4f
+import ru.dbotthepony.kommons.matrix.Matrix4fStack
+import kotlin.math.absoluteValue
+import kotlin.math.acos
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.sqrt
+
+data class Vector2f(
+ val x: Float = 0f,
+ val y: Float = 0f,
+) : IStruct2f, Vector() {
+ constructor(value: IStruct2f) : this(value.component1(), value.component2())
+ constructor(value: Vector2f) : this(value.x, value.y)
+
+ override val lengthSquared: Double
+ get() = x.toDouble() * x + y.toDouble() * y
+
+ override val isFinite: Boolean
+ get() = x.isFinite() && y.isFinite()
+ override val isNaN: Boolean
+ get() = x.isNaN() || y.isNaN()
+
+ val unitVector: Vector2f get() {
+ var length = lengthSquared.toFloat()
+ if (length == 0f || length == 1f) return this
+ length = sqrt(length)
+ return Vector2f(x / length, y / length)
+ }
+
+ val absoluteValue: Vector2f get() {
+ val x = x.absoluteValue
+ val y = y.absoluteValue
+ if (x != this.x || y != this.y) return Vector2f(x, y)
+ return this
+ }
+
+ val r get() = x
+ val g get() = y
+
+ val s get() = x
+ val t get() = y
+
+ fun plus(x: Float = 0f, y: Float = 0f) = Vector2f(this.x + x, this.y + y)
+ fun minus(x: Float = 0f, y: Float = 0f) = Vector2f(this.x - x, this.y - y)
+ fun times(x: Float = 1f, y: Float = 1f) = Vector2f(this.x * x, this.y * y)
+ fun div(x: Float = 1f, y: Float = 1f) = Vector2f(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2f): Vector2f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector2f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector2f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector2f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2f): Vector2f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector2f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector2f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector2f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value)
+ operator fun minus(value: Float) = minus(value, value)
+ operator fun times(value: Float) = times(value, value)
+ operator fun div(value: Float) = div(value, value)
+
+ fun plus(x: Int = 0, y: Int = 0) = Vector2f(this.x + x, this.y + y)
+ fun minus(x: Int = 0, y: Int = 0) = Vector2f(this.x - x, this.y - y)
+ fun times(x: Int = 1, y: Int = 1) = Vector2f(this.x * x, this.y * y)
+ fun div(x: Int = 1, y: Int = 1) = Vector2f(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2i): Vector2f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector2f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector2f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector2f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2i): Vector2f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector2f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector2f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector2f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value)
+ operator fun minus(value: Int) = minus(value, value)
+ operator fun times(value: Int) = times(value, value)
+ operator fun div(value: Int) = div(value, value)
+
+ fun plus(x: Double = 0.0, y: Double = 0.0) = Vector2d(this.x + x, this.y + y)
+ fun minus(x: Double = 0.0, y: Double = 0.0) = Vector2d(this.x - x, this.y - y)
+ fun times(x: Double = 1.0, y: Double = 1.0) = Vector2d(this.x * x, this.y * y)
+ fun div(x: Double = 1.0, y: Double = 1.0) = Vector2d(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2d): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2d): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value)
+ operator fun minus(value: Double) = minus(value, value)
+ operator fun times(value: Double) = times(value, value)
+ operator fun div(value: Double) = div(value, value)
+
+ operator fun unaryMinus(): Vector2f = Vector2f(-x, -y)
+
+ fun dot(x: Float, y: Float) = this.x * x + this.y * y
+ fun dot(value: IStruct2f): Float { val (x, y) = value; return dot(x, y) }
+ fun dot(value: Vector2f): Float { val (x, y) = value; return dot(x, y) }
+
+ fun distanceSquared(x: Float, y: Float): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ return dx * dx + dy * dy
+ }
+
+ fun distanceSquared(value: IStruct2f): Double { val (x, y) = value; return distanceSquared(x, y) }
+ fun distanceSquared(value: Vector2f): Double { val (x, y) = value; return distanceSquared(x, y) }
+ fun distance(x: Float, y: Float) = sqrt(distanceSquared(x, y))
+ fun distance(value: IStruct2f): Double { val (x, y) = value; return sqrt(distanceSquared(x, y)) }
+ fun distance(value: Vector2f): Double { val (x, y) = value; return sqrt(distanceSquared(x, y)) }
+
+ fun coerceAtLeast(x: Float, y: Float) = Vector2f(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y))
+ fun coerceAtMost(x: Float, y: Float) = Vector2f(this.x.coerceAtMost(x), this.y.coerceAtMost(y))
+
+ fun coerceIn(minimum: Vector2f, maximum: Vector2f) = Vector2f(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y))
+
+ fun coerceIn(minimum: IStruct2f, maximum: IStruct2f) = Vector2f(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()))
+
+ fun coerceAtLeast(value: IStruct2f): Vector2f { val (x, y) = value; return coerceAtLeast(x, y) }
+ fun coerceAtMost(value: IStruct2f): Vector2f { val (x, y) = value; return coerceAtMost(x, y) }
+ fun coerceAtLeast(value: Vector2f): Vector2f { val (x, y) = value; return coerceAtLeast(x, y) }
+ fun coerceAtMost(value: Vector2f): Vector2f { val (x, y) = value; return coerceAtMost(x, y) }
+
+ fun rotate(angle: Float): Vector2f {
+ val s = sin(angle)
+ val c = cos(angle)
+
+ return Vector2f(x * c - s * y, s * x + c * y)
+ }
+
+ fun toAngle(zeroAngle: Vector2f): Float {
+ val dot = unitVector.dot(zeroAngle.unitVector)
+
+ return if (y > 0.0) {
+ acos(dot)
+ } else {
+ -acos(dot)
+ }
+ }
+
+ fun toAngle(zeroAngle: IStruct2f): Float {
+ return toAngle(Vector2f(zeroAngle))
+ }
+
+ fun toAngle(): Float {
+ return atan2(y, x)
+ }
+
+ operator fun times(value: Matrix4f): Vector2f {
+ return Vector2f(
+ x * value.c00 + y * value.c10 + value.c30,
+ x * value.c01 + y * value.c11 + value.c31,
+ )
+ }
+
+ operator fun times(value: Matrix3f): Vector2f {
+ return Vector2f(
+ x * value.c00 + y * value.c10 + value.c20,
+ x * value.c01 + y * value.c11 + value.c21,
+ )
+ }
+
+ operator fun times(value: Matrix2f): Vector2f {
+ return Vector2f(
+ x * value.c00 + y * value.c10,
+ x * value.c01 + y * value.c11,
+ )
+ }
+
+ operator fun times(value: Matrix4fStack) = times(value.last())
+
+ fun toDoubleVector() = Vector2d(x.toDouble(), y.toDouble())
+
+ companion object {
+ @JvmField val ZERO = Vector2f()
+ @JvmField val POSITIVE_X = Vector2f(x = 1.0f)
+ @JvmField val NEGATIVE_X = Vector2f(x = -1.0f)
+ @JvmField val POSITIVE_Y = Vector2f(y = 1.0f)
+ @JvmField val NEGATIVE_Y = Vector2f(y = -1.0f)
+
+ @JvmField val POSITIVE_XY = Vector2f(1.0f, 1.0f)
+ @JvmField val NEGATIVE_XY = Vector2f(-1.0f, -1.0f)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2i.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2i.kt
new file mode 100644
index 0000000..2236ae0
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector2i.kt
@@ -0,0 +1,136 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import kotlin.math.sqrt
+
+data class Vector2i(
+ val x: Int = 0,
+ val y: Int = 0,
+) : IStruct2i, Vector() {
+ constructor(value: IStruct2i) : this(value.component1(), value.component2())
+ constructor(value: Vector2i) : this(value.x, value.y)
+
+ override val lengthSquared: Double
+ get() = x.toDouble() * x + y.toDouble() * y
+
+ override val isFinite: Boolean
+ get() = true
+ override val isNaN: Boolean
+ get() = false
+
+ val r get() = x
+ val g get() = y
+
+ val s get() = x
+ val t get() = y
+
+ fun plus(x: Int = 0, y: Int = 0) = Vector2i(this.x + x, this.y + y)
+ fun minus(x: Int = 0, y: Int = 0) = Vector2i(this.x - x, this.y - y)
+ fun times(x: Int = 1, y: Int = 1) = Vector2i(this.x * x, this.y * y)
+ fun div(x: Int = 1, y: Int = 1) = Vector2i(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2i): Vector2i { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector2i { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector2i { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector2i { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2i): Vector2i { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector2i { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector2i { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector2i { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value)
+ operator fun minus(value: Int) = minus(value, value)
+ operator fun times(value: Int) = times(value, value)
+ operator fun div(value: Int) = div(value, value)
+
+ fun plus(x: Double = 0.0, y: Double = 0.0) = Vector2d(this.x + x, this.y + y)
+ fun minus(x: Double = 0.0, y: Double = 0.0) = Vector2d(this.x - x, this.y - y)
+ fun times(x: Double = 1.0, y: Double = 1.0) = Vector2d(this.x * x, this.y * y)
+ fun div(x: Double = 1.0, y: Double = 1.0) = Vector2d(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2d): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2d): Vector2d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector2d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector2d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector2d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value)
+ operator fun minus(value: Double) = minus(value, value)
+ operator fun times(value: Double) = times(value, value)
+ operator fun div(value: Double) = div(value, value)
+
+ fun plus(x: Float = 0f, y: Float = 0f) = Vector2f(this.x + x, this.y + y)
+ fun minus(x: Float = 0f, y: Float = 0f) = Vector2f(this.x - x, this.y - y)
+ fun times(x: Float = 1f, y: Float = 1f) = Vector2f(this.x * x, this.y * y)
+ fun div(x: Float = 1f, y: Float = 1f) = Vector2f(this.x / x, this.y / y)
+
+ operator fun plus(value: IStruct2f): Vector2f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector2f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector2f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector2f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector2f): Vector2f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector2f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector2f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector2f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value)
+ operator fun minus(value: Float) = minus(value, value)
+ operator fun times(value: Float) = times(value, value)
+ operator fun div(value: Float) = div(value, value)
+
+ operator fun unaryMinus(): Vector2i = Vector2i(-x, -y)
+
+ fun dot(x: Int, y: Int) = this.x * x + this.y * y
+ fun dot(value: IStruct2i): Int { val (x, y) = value; return dot(x, y) }
+ fun dot(value: Vector2i): Int { val (x, y) = value; return dot(x, y) }
+
+ fun distanceSquared(x: Int, y: Int): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ return dx * dx + dy * dy
+ }
+
+ fun distanceSquared(value: IStruct2i): Double { val (x, y) = value; return distanceSquared(x, y) }
+ fun distanceSquared(value: Vector2i): Double { val (x, y) = value; return distanceSquared(x, y) }
+ fun distance(x: Int, y: Int) = sqrt(distanceSquared(x, y))
+ fun distance(value: IStruct2i): Double { val (x, y) = value; return sqrt(distanceSquared(x, y)) }
+ fun distance(value: Vector2i): Double { val (x, y) = value; return sqrt(distanceSquared(x, y)) }
+
+ fun coerceAtLeast(x: Int, y: Int) = Vector2i(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y))
+ fun coerceAtMost(x: Int, y: Int) = Vector2i(this.x.coerceAtMost(x), this.y.coerceAtMost(y))
+
+ fun coerceIn(minimum: Vector2i, maximum: Vector2i) = Vector2i(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y))
+
+ fun coerceIn(minimum: IStruct2i, maximum: IStruct2i) = Vector2i(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()))
+
+ fun coerceAtLeast(value: IStruct2i): Vector2i { val (x, y) = value; return coerceAtLeast(x, y) }
+ fun coerceAtMost(value: IStruct2i): Vector2i { val (x, y) = value; return coerceAtMost(x, y) }
+ fun coerceAtLeast(value: Vector2i): Vector2i { val (x, y) = value; return coerceAtLeast(x, y) }
+ fun coerceAtMost(value: Vector2i): Vector2i { val (x, y) = value; return coerceAtMost(x, y) }
+
+ fun toDoubleVector() = Vector2d(x.toDouble(), y.toDouble())
+ fun toFloatVector() = Vector2f(x.toFloat(), y.toFloat())
+
+ companion object {
+ @JvmField val ZERO = Vector2i()
+ @JvmField val POSITIVE_X = Vector2i(x = 1)
+ @JvmField val NEGATIVE_X = Vector2i(x = -1)
+ @JvmField val POSITIVE_Y = Vector2i(y = 1)
+ @JvmField val NEGATIVE_Y = Vector2i(y = -1)
+
+ @JvmField val POSITIVE_XY = Vector2i(1, 1)
+ @JvmField val NEGATIVE_XY = Vector2i(-1, -1)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3d.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3d.kt
new file mode 100644
index 0000000..271c0aa
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3d.kt
@@ -0,0 +1,220 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct3i
+import ru.dbotthepony.kommons.matrix.Matrix3d
+import ru.dbotthepony.kommons.matrix.Matrix4d
+import ru.dbotthepony.kommons.matrix.Matrix4dStack
+import kotlin.math.absoluteValue
+import kotlin.math.sqrt
+
+data class Vector3d(
+ val x: Double = 0.0,
+ val y: Double = 0.0,
+ val z: Double = 0.0,
+) : IStruct3d, Vector() {
+ constructor(value: IStruct2d) : this(value.component1(), value.component2())
+ constructor(value: Vector2d) : this(value.x, value.y)
+ constructor(value: IStruct2d, z: Double) : this(value.component1(), value.component2(), z)
+ constructor(value: Vector2d, z: Double) : this(value.x, value.y, z)
+ constructor(value: IStruct3d) : this(value.component1(), value.component2(), value.component3())
+ constructor(value: Vector3d) : this(value.x, value.y, value.z)
+
+ override val lengthSquared: Double
+ get() = x * x + y * y + z * z
+
+ override val isFinite: Boolean
+ get() = x.isFinite() && y.isFinite() && z.isFinite()
+ override val isNaN: Boolean
+ get() = x.isNaN() || y.isNaN() || z.isNaN()
+
+ val unitVector: Vector3d get() {
+ var length = lengthSquared
+ if (length == 0.0 || length == 1.0) return this
+ length = sqrt(length)
+ return Vector3d(x / length, y / length, z / length)
+ }
+
+ val absoluteValue: Vector3d get() {
+ val x = x.absoluteValue
+ val y = y.absoluteValue
+ val z = z.absoluteValue
+ if (x != this.x || y != this.y || z != this.z) return Vector3d(x, y, z)
+ return this
+ }
+
+ val r get() = x
+ val g get() = y
+ val b get() = z
+
+ val s get() = x
+ val t get() = y
+ val p get() = z
+
+ fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = Vector3d(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = Vector3d(this.x - x, this.y - y, this.z - z)
+ fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0) = Vector3d(this.x * x, this.y * y, this.z * z)
+ fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0) = Vector3d(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3d): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3d): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3d): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3d): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2d): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3d): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3d): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3d): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3d): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2d): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value, value)
+ operator fun minus(value: Double) = minus(value, value, value)
+ operator fun times(value: Double) = times(value, value, value)
+ operator fun div(value: Double) = div(value, value, value)
+
+ fun plus(x: Int = 0, y: Int = 0, z: Int = 0) = Vector3d(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Int = 0, y: Int = 0, z: Int = 0) = Vector3d(this.x - x, this.y - y, this.z - z)
+ fun times(x: Int = 1, y: Int = 1, z: Int = 1) = Vector3d(this.x * x, this.y * y, this.z * z)
+ fun div(x: Int = 1, y: Int = 1, z: Int = 1) = Vector3d(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3i): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3i): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3i): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3i): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2i): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3i): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3i): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3i): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3i): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2i): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value, value)
+ operator fun minus(value: Int) = minus(value, value, value)
+ operator fun times(value: Int) = times(value, value, value)
+ operator fun div(value: Int) = div(value, value, value)
+
+ fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f) = Vector3d(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f) = Vector3d(this.x - x, this.y - y, this.z - z)
+ fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f) = Vector3d(this.x * x, this.y * y, this.z * z)
+ fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f) = Vector3d(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3f): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3f): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3f): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3f): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2f): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3f): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3f): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3f): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3f): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2f): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value, value)
+ operator fun minus(value: Float) = minus(value, value, value)
+ operator fun times(value: Float) = times(value, value, value)
+ operator fun div(value: Float) = div(value, value, value)
+
+ operator fun unaryMinus(): Vector3d = Vector3d(-x, -y, -z)
+
+ fun dot(x: Double, y: Double, z: Double) = this.x * x + this.y * y + this.z * z
+ fun dot(value: IStruct3d): Double { val (x, y, z) = value; return dot(x, y, z) }
+ fun dot(value: Vector3d): Double { val (x, y, z) = value; return dot(x, y, z) }
+
+ fun distanceSquared(x: Double, y: Double, z: Double): Double {
+ val dx = (this.x - x)
+ val dy = (this.y - y)
+ val dz = (this.z - z)
+ return dx * dx + dy * dy + dz * dz
+ }
+
+ fun distanceSquared(value: IStruct3d): Double { val (x, y, z) = value; return distanceSquared(x, y, z) }
+ fun distanceSquared(value: Vector3d): Double { val (x, y, z) = value; return distanceSquared(x, y, z) }
+ fun distance(x: Double, y: Double, z: Double) = sqrt(distanceSquared(x, y, z))
+ fun distance(value: IStruct3d): Double { val (x, y, z) = value; return sqrt(distanceSquared(x, y, z)) }
+ fun distance(value: Vector3d): Double { val (x, y, z) = value; return sqrt(distanceSquared(x, y, z)) }
+
+ fun coerceAtLeast(x: Double, y: Double, z: Double) = Vector3d(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z))
+ fun coerceAtMost(x: Double, y: Double, z: Double) = Vector3d(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z))
+
+ fun coerceIn(minimum: Vector3d, maximum: Vector3d) = Vector3d(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y),
+ this.z.coerceIn(minimum.z, maximum.z))
+
+ fun coerceIn(minimum: IStruct3d, maximum: IStruct3d) = Vector3d(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()),
+ this.z.coerceIn(minimum.component3(), maximum.component3()))
+
+ fun coerceAtLeast(value: IStruct3d): Vector3d { val (x, y, z) = value; return coerceAtLeast(x, y, z) }
+ fun coerceAtMost(value: IStruct3d): Vector3d { val (x, y, z) = value; return coerceAtMost(x, y, z) }
+ fun coerceAtLeast(value: Vector3d): Vector3d { val (x, y, z) = value; return coerceAtLeast(x, y, z) }
+ fun coerceAtMost(value: Vector3d): Vector3d { val (x, y, z) = value; return coerceAtMost(x, y, z) }
+
+ fun cross(x: Double, y: Double, z: Double): Vector3d {
+ return Vector3d(
+ this.y * z - this.z * y,
+ this.z * x - this.x * z,
+ this.x * y - this.y * x,
+ )
+ }
+
+ fun cross(value: IStruct3d): Vector3d { val (x, y, z) = value; return cross(x, y, z) }
+ fun cross(value: Vector3d): Vector3d { val (x, y, z) = value; return cross(x, y, z) }
+
+ operator fun times(value: Matrix4d): Vector3d {
+ return Vector3d(
+ x * value.c00 + y * value.c10 + z * value.c20 + value.c30,
+ x * value.c01 + y * value.c11 + z * value.c21 + value.c31,
+ x * value.c02 + y * value.c12 + z * value.c22 + value.c32,
+ )
+ }
+
+ operator fun times(value: Matrix3d): Vector3d {
+ return Vector3d(
+ x * value.c00 + y * value.c10 + z * value.c20,
+ x * value.c01 + y * value.c11 + z * value.c21,
+ x * value.c02 + y * value.c12 + z * value.c22,
+ )
+ }
+
+ operator fun times(value: Matrix4dStack) = times(value.last())
+
+ companion object {
+ @JvmField val ZERO = Vector3d()
+ @JvmField val POSITIVE_X = Vector3d(x = 1.0)
+ @JvmField val NEGATIVE_X = Vector3d(x = -1.0)
+ @JvmField val POSITIVE_Y = Vector3d(y = 1.0)
+ @JvmField val NEGATIVE_Y = Vector3d(y = -1.0)
+ @JvmField val POSITIVE_Z = Vector3d(z = 1.0)
+ @JvmField val NEGATIVE_Z = Vector3d(z = -1.0)
+
+ @JvmField val POSITIVE_XYZ = Vector3d(1.0, 1.0, 1.0)
+ @JvmField val NEGATIVE_XYZ = Vector3d(-1.0, -1.0, -1.0)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3f.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3f.kt
new file mode 100644
index 0000000..121b1a1
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3f.kt
@@ -0,0 +1,222 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct3i
+import ru.dbotthepony.kommons.matrix.Matrix3f
+import ru.dbotthepony.kommons.matrix.Matrix4f
+import ru.dbotthepony.kommons.matrix.Matrix4fStack
+import kotlin.math.absoluteValue
+import kotlin.math.sqrt
+
+data class Vector3f(
+ val x: Float = 0f,
+ val y: Float = 0f,
+ val z: Float = 0f,
+) : IStruct3f, Vector() {
+ constructor(value: IStruct2f) : this(value.component1(), value.component2())
+ constructor(value: Vector2f) : this(value.x, value.y)
+ constructor(value: IStruct2f, z: Float) : this(value.component1(), value.component2(), z)
+ constructor(value: Vector2f, z: Float) : this(value.x, value.y, z)
+ constructor(value: IStruct3f) : this(value.component1(), value.component2(), value.component3())
+ constructor(value: Vector3f) : this(value.x, value.y, value.z)
+
+ override val lengthSquared: Double
+ get() = x.toDouble() * x + y.toDouble() * y + z.toDouble() * z
+
+ override val isFinite: Boolean
+ get() = x.isFinite() && y.isFinite() && z.isFinite()
+ override val isNaN: Boolean
+ get() = x.isNaN() || y.isNaN() || z.isNaN()
+
+ val unitVector: Vector3f get() {
+ var length = lengthSquared.toFloat()
+ if (length == 0f || length == 1f) return this
+ length = sqrt(length)
+ return Vector3f(x / length, y / length, z / length)
+ }
+
+ val absoluteValue: Vector3f get() {
+ val x = x.absoluteValue
+ val y = y.absoluteValue
+ val z = z.absoluteValue
+ if (x != this.x || y != this.y || z != this.z) return Vector3f(x, y, z)
+ return this
+ }
+
+ val r get() = x
+ val g get() = y
+ val b get() = z
+
+ val s get() = x
+ val t get() = y
+ val p get() = z
+
+ fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f) = Vector3f(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f) = Vector3f(this.x - x, this.y - y, this.z - z)
+ fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f) = Vector3f(this.x * x, this.y * y, this.z * z)
+ fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f) = Vector3f(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3f): Vector3f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3f): Vector3f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3f): Vector3f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3f): Vector3f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2f): Vector3f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector3f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector3f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector3f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3f): Vector3f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3f): Vector3f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3f): Vector3f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3f): Vector3f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2f): Vector3f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector3f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector3f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector3f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value, value)
+ operator fun minus(value: Float) = minus(value, value, value)
+ operator fun times(value: Float) = times(value, value, value)
+ operator fun div(value: Float) = div(value, value, value)
+
+ fun plus(x: Int = 0, y: Int = 0, z: Int = 0) = Vector3f(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Int = 0, y: Int = 0, z: Int = 0) = Vector3f(this.x - x, this.y - y, this.z - z)
+ fun times(x: Int = 1, y: Int = 1, z: Int = 1) = Vector3f(this.x * x, this.y * y, this.z * z)
+ fun div(x: Int = 1, y: Int = 1, z: Int = 1) = Vector3f(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3i): Vector3f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3i): Vector3f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3i): Vector3f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3i): Vector3f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2i): Vector3f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector3f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector3f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector3f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3i): Vector3f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3i): Vector3f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3i): Vector3f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3i): Vector3f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2i): Vector3f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector3f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector3f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector3f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value, value)
+ operator fun minus(value: Int) = minus(value, value, value)
+ operator fun times(value: Int) = times(value, value, value)
+ operator fun div(value: Int) = div(value, value, value)
+
+ fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = Vector3d(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = Vector3d(this.x - x, this.y - y, this.z - z)
+ fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0) = Vector3d(this.x * x, this.y * y, this.z * z)
+ fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0) = Vector3d(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3d): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3d): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3d): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3d): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2d): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3d): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3d): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3d): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3d): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2d): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value, value)
+ operator fun minus(value: Double) = minus(value, value, value)
+ operator fun times(value: Double) = times(value, value, value)
+ operator fun div(value: Double) = div(value, value, value)
+
+ operator fun unaryMinus(): Vector3f = Vector3f(-x, -y, -z)
+
+ fun dot(x: Float, y: Float, z: Float) = this.x * x + this.y * y + this.z * z
+ fun dot(value: IStruct3f): Float { val (x, y, z) = value; return dot(x, y, z) }
+ fun dot(value: Vector3f): Float { val (x, y, z) = value; return dot(x, y, z) }
+
+ fun distanceSquared(x: Float, y: Float, z: Float): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ val dz = (this.z - z).toDouble()
+ return dx * dx + dy * dy + dz * dz
+ }
+
+ fun distanceSquared(value: IStruct3f): Double { val (x, y, z) = value; return distanceSquared(x, y, z) }
+ fun distanceSquared(value: Vector3f): Double { val (x, y, z) = value; return distanceSquared(x, y, z) }
+ fun distance(x: Float, y: Float, z: Float) = sqrt(distanceSquared(x, y, z))
+ fun distance(value: IStruct3f): Double { val (x, y, z) = value; return sqrt(distanceSquared(x, y, z)) }
+ fun distance(value: Vector3f): Double { val (x, y, z) = value; return sqrt(distanceSquared(x, y, z)) }
+
+ fun coerceAtLeast(x: Float, y: Float, z: Float) = Vector3f(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z))
+ fun coerceAtMost(x: Float, y: Float, z: Float) = Vector3f(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z))
+
+ fun coerceIn(minimum: Vector3f, maximum: Vector3f) = Vector3f(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y),
+ this.z.coerceIn(minimum.z, maximum.z))
+
+ fun coerceIn(minimum: IStruct3f, maximum: IStruct3f) = Vector3f(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()),
+ this.z.coerceIn(minimum.component3(), maximum.component3()))
+
+ fun coerceAtLeast(value: IStruct3f): Vector3f { val (x, y, z) = value; return coerceAtLeast(x, y, z) }
+ fun coerceAtMost(value: IStruct3f): Vector3f { val (x, y, z) = value; return coerceAtMost(x, y, z) }
+ fun coerceAtLeast(value: Vector3f): Vector3f { val (x, y, z) = value; return coerceAtLeast(x, y, z) }
+ fun coerceAtMost(value: Vector3f): Vector3f { val (x, y, z) = value; return coerceAtMost(x, y, z) }
+
+ fun cross(x: Float, y: Float, z: Float): Vector3f {
+ return Vector3f(
+ this.y * z - this.z * y,
+ this.z * x - this.x * z,
+ this.x * y - this.y * x,
+ )
+ }
+
+ fun cross(value: IStruct3f): Vector3f { val (x, y, z) = value; return cross(x, y, z) }
+ fun cross(value: Vector3f): Vector3f { val (x, y, z) = value; return cross(x, y, z) }
+
+ operator fun times(value: Matrix4f): Vector3f {
+ return Vector3f(
+ x * value.c00 + y * value.c10 + z * value.c20 + value.c30,
+ x * value.c01 + y * value.c11 + z * value.c21 + value.c31,
+ x * value.c02 + y * value.c12 + z * value.c22 + value.c32,
+ )
+ }
+
+ operator fun times(value: Matrix3f): Vector3f {
+ return Vector3f(
+ x * value.c00 + y * value.c10 + z * value.c20,
+ x * value.c01 + y * value.c11 + z * value.c21,
+ x * value.c02 + y * value.c12 + z * value.c22,
+ )
+ }
+
+ operator fun times(value: Matrix4fStack) = times(value.last())
+
+ fun toDoubleVector() = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
+
+ companion object {
+ @JvmField val ZERO = Vector3f()
+ @JvmField val POSITIVE_X = Vector3f(x = 1.0f)
+ @JvmField val NEGATIVE_X = Vector3f(x = -1.0f)
+ @JvmField val POSITIVE_Y = Vector3f(y = 1.0f)
+ @JvmField val NEGATIVE_Y = Vector3f(y = -1.0f)
+ @JvmField val POSITIVE_Z = Vector3f(z = 1.0f)
+ @JvmField val NEGATIVE_Z = Vector3f(z = -1.0f)
+
+ @JvmField val POSITIVE_XYZ = Vector3f(1.0f, 1.0f, 1f)
+ @JvmField val NEGATIVE_XYZ = Vector3f(-1.0f, -1.0f, -1f)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3i.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3i.kt
new file mode 100644
index 0000000..beb71ee
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector3i.kt
@@ -0,0 +1,186 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct3i
+import kotlin.math.sqrt
+
+data class Vector3i(
+ val x: Int = 0,
+ val y: Int = 0,
+ val z: Int = 0,
+) : IStruct3i, Vector() {
+ constructor(value: IStruct2i) : this(value.component1(), value.component2())
+ constructor(value: Vector2i) : this(value.x, value.y)
+ constructor(value: IStruct2i, z: Int) : this(value.component1(), value.component2(), z)
+ constructor(value: Vector2i, z: Int) : this(value.x, value.y, z)
+ constructor(value: IStruct3i) : this(value.component1(), value.component2(), value.component3())
+ constructor(value: Vector3i) : this(value.x, value.y, value.z)
+
+ override val lengthSquared: Double
+ get() = x.toDouble() * x + y.toDouble() * y + z.toDouble() * z
+
+ override val isFinite: Boolean
+ get() = true
+ override val isNaN: Boolean
+ get() = false
+
+ val r get() = x
+ val g get() = y
+ val b get() = z
+
+ val s get() = x
+ val t get() = y
+ val p get() = z
+
+ fun plus(x: Int = 0, y: Int = 0, z: Int = 0) = Vector3i(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Int = 0, y: Int = 0, z: Int = 0) = Vector3i(this.x - x, this.y - y, this.z - z)
+ fun times(x: Int = 1, y: Int = 1, z: Int = 1) = Vector3i(this.x * x, this.y * y, this.z * z)
+ fun div(x: Int = 1, y: Int = 1, z: Int = 1) = Vector3i(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3i): Vector3i { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3i): Vector3i { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3i): Vector3i { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3i): Vector3i { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2i): Vector3i { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector3i { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector3i { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector3i { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3i): Vector3i { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3i): Vector3i { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3i): Vector3i { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3i): Vector3i { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2i): Vector3i { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector3i { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector3i { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector3i { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value, value)
+ operator fun minus(value: Int) = minus(value, value, value)
+ operator fun times(value: Int) = times(value, value, value)
+ operator fun div(value: Int) = div(value, value, value)
+
+ fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f) = Vector3f(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f) = Vector3f(this.x - x, this.y - y, this.z - z)
+ fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f) = Vector3f(this.x * x, this.y * y, this.z * z)
+ fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f) = Vector3f(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3f): Vector3f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3f): Vector3f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3f): Vector3f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3f): Vector3f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2f): Vector3f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector3f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector3f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector3f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3f): Vector3f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3f): Vector3f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3f): Vector3f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3f): Vector3f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2f): Vector3f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector3f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector3f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector3f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value, value)
+ operator fun minus(value: Float) = minus(value, value, value)
+ operator fun times(value: Float) = times(value, value, value)
+ operator fun div(value: Float) = div(value, value, value)
+
+ fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = Vector3d(this.x + x, this.y + y, this.z + z)
+ fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0) = Vector3d(this.x - x, this.y - y, this.z - z)
+ fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0) = Vector3d(this.x * x, this.y * y, this.z * z)
+ fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0) = Vector3d(this.x / x, this.y / y, this.z / z)
+
+ operator fun plus(value: IStruct3d): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3d): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3d): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3d): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2d): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector3d): Vector3d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3d): Vector3d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3d): Vector3d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3d): Vector3d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2d): Vector3d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector3d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector3d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector3d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value, value)
+ operator fun minus(value: Double) = minus(value, value, value)
+ operator fun times(value: Double) = times(value, value, value)
+ operator fun div(value: Double) = div(value, value, value)
+
+ operator fun unaryMinus(): Vector3i = Vector3i(-x, -y, -z)
+
+ fun dot(x: Int, y: Int, z: Int) = this.x * x + this.y * y + this.z * z
+ fun dot(value: IStruct3i): Int { val (x, y, z) = value; return dot(x, y, z) }
+ fun dot(value: Vector3i): Int { val (x, y, z) = value; return dot(x, y, z) }
+
+ fun distanceSquared(x: Int, y: Int, z: Int): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ val dz = (this.z - z).toDouble()
+ return dx * dx + dy * dy + dz * dz
+ }
+
+ fun distanceSquared(value: IStruct3i): Double { val (x, y, z) = value; return distanceSquared(x, y, z) }
+ fun distanceSquared(value: Vector3i): Double { val (x, y, z) = value; return distanceSquared(x, y, z) }
+ fun distance(x: Int, y: Int, z: Int) = sqrt(distanceSquared(x, y, z))
+ fun distance(value: IStruct3i): Double { val (x, y, z) = value; return sqrt(distanceSquared(x, y, z)) }
+ fun distance(value: Vector3i): Double { val (x, y, z) = value; return sqrt(distanceSquared(x, y, z)) }
+
+ fun coerceAtLeast(x: Int, y: Int, z: Int) = Vector3i(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z))
+ fun coerceAtMost(x: Int, y: Int, z: Int) = Vector3i(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z))
+
+ fun coerceIn(minimum: Vector3i, maximum: Vector3i) = Vector3i(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y),
+ this.z.coerceIn(minimum.z, maximum.z))
+
+ fun coerceIn(minimum: IStruct3i, maximum: IStruct3i) = Vector3i(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()),
+ this.z.coerceIn(minimum.component3(), maximum.component3()))
+
+ fun coerceAtLeast(value: IStruct3i): Vector3i { val (x, y, z) = value; return coerceAtLeast(x, y, z) }
+ fun coerceAtMost(value: IStruct3i): Vector3i { val (x, y, z) = value; return coerceAtMost(x, y, z) }
+ fun coerceAtLeast(value: Vector3i): Vector3i { val (x, y, z) = value; return coerceAtLeast(x, y, z) }
+ fun coerceAtMost(value: Vector3i): Vector3i { val (x, y, z) = value; return coerceAtMost(x, y, z) }
+
+ fun cross(x: Int, y: Int, z: Int): Vector3i {
+ return Vector3i(
+ this.y * z - this.z * y,
+ this.z * x - this.x * z,
+ this.x * y - this.y * x,
+ )
+ }
+
+ fun cross(value: IStruct3i): Vector3i { val (x, y, z) = value; return cross(x, y, z) }
+ fun cross(value: Vector3i): Vector3i { val (x, y, z) = value; return cross(x, y, z) }
+
+ fun toDoubleVector() = Vector3d(x.toDouble(), y.toDouble(), z.toDouble())
+ fun toFloatVector() = Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
+
+ companion object {
+ @JvmField val ZERO = Vector3i()
+ @JvmField val POSITIVE_X = Vector3i(x = 1)
+ @JvmField val NEGATIVE_X = Vector3i(x = -1)
+ @JvmField val POSITIVE_Y = Vector3i(y = 1)
+ @JvmField val NEGATIVE_Y = Vector3i(y = -1)
+ @JvmField val POSITIVE_Z = Vector3i(z = 1)
+ @JvmField val NEGATIVE_Z = Vector3i(z = -1)
+
+ @JvmField val POSITIVE_XYZ = Vector3i(1, 1, 1)
+ @JvmField val NEGATIVE_XYZ = Vector3i(-1, -1, -1)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4d.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4d.kt
new file mode 100644
index 0000000..0527877
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4d.kt
@@ -0,0 +1,241 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.vector.Vector4f
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct3i
+import ru.dbotthepony.kommons.core.IStruct4d
+import ru.dbotthepony.kommons.core.IStruct4f
+import ru.dbotthepony.kommons.core.IStruct4i
+import ru.dbotthepony.kommons.matrix.Matrix4d
+import ru.dbotthepony.kommons.matrix.Matrix4dStack
+import kotlin.math.absoluteValue
+import kotlin.math.sqrt
+
+data class Vector4d(
+ val x: Double = 0.0,
+ val y: Double = 0.0,
+ val z: Double = 0.0,
+ val w: Double = 0.0,
+) : IStruct4d, Vector() {
+ constructor(value: IStruct2d) : this(value.component1(), value.component2())
+ constructor(value: Vector2d) : this(value.x, value.y)
+ constructor(value: IStruct2d, z: Double) : this(value.component1(), value.component2(), z)
+ constructor(value: Vector2d, z: Double) : this(value.x, value.y, z)
+ constructor(value: IStruct2d, z: Double, w: Double) : this(value.component1(), value.component2(), z, w)
+ constructor(value: Vector2d, z: Double, w: Double) : this(value.x, value.y, z, w)
+ constructor(value: IStruct3d) : this(value.component1(), value.component2(), value.component3())
+ constructor(value: Vector3d) : this(value.x, value.y, value.z)
+ constructor(value: IStruct3d, w: Double) : this(value.component1(), value.component2(), value.component3(), w)
+ constructor(value: Vector3d, w: Double) : this(value.x, value.y, value.z, w)
+ constructor(value: IStruct4d) : this(value.component1(), value.component2(), value.component3(), value.component4())
+ constructor(value: Vector4d) : this(value.x, value.y, value.z, value.w)
+
+ override val lengthSquared: Double
+ get() = x * x + y * y + z * z + w * w
+
+ override val isFinite: Boolean
+ get() = x.isFinite() && y.isFinite() && z.isFinite() && w.isFinite()
+ override val isNaN: Boolean
+ get() = x.isNaN() || y.isNaN() || z.isNaN() || w.isNaN()
+
+ val unitVector: Vector4d get() {
+ var length = lengthSquared
+ if (length == 0.0 || length == 1.0) return this
+ length = sqrt(length)
+ return Vector4d(x / length, y / length, z / length, w / length)
+ }
+
+ val absoluteValue: Vector4d get() {
+ val x = x.absoluteValue
+ val y = y.absoluteValue
+ val z = z.absoluteValue
+ val w = w.absoluteValue
+ if (x != this.x || y != this.y || z != this.z || w != this.w) return Vector4d(x, y, z, w)
+ return this
+ }
+
+ val r get() = x
+ val g get() = y
+ val b get() = z
+ val a get() = w
+
+ val s get() = x
+ val t get() = y
+ val p get() = z
+ val q get() = w
+
+ fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2d): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2d): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value, value, value)
+ operator fun minus(value: Double) = minus(value, value, value, value)
+ operator fun times(value: Double) = times(value, value, value, value)
+ operator fun div(value: Double) = div(value, value, value, value)
+
+ fun plus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3i): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3i): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3i): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3i): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2i): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4i): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4i): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4i): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4i): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3i): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3i): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3i): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3i): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2i): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value, value, value)
+ operator fun minus(value: Int) = minus(value, value, value, value)
+ operator fun times(value: Int) = times(value, value, value, value)
+ operator fun div(value: Int) = div(value, value, value, value)
+
+ fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3f): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3f): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3f): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3f): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2f): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4f): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4f): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4f): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4f): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3f): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3f): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3f): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3f): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2f): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value, value, value)
+ operator fun minus(value: Float) = minus(value, value, value, value)
+ operator fun times(value: Float) = times(value, value, value, value)
+ operator fun div(value: Float) = div(value, value, value, value)
+
+ operator fun unaryMinus(): Vector4d = Vector4d(-x, -y, -z, -w)
+
+ fun dot(x: Double, y: Double, z: Double, w: Double) = this.x * x + this.y * y + this.z * z + this.w * w
+ fun dot(value: IStruct4d): Double { val (x, y, z, w) = value; return dot(x, y, z, w) }
+ fun dot(value: Vector4d): Double { val (x, y, z, w) = value; return dot(x, y, z, w) }
+
+ fun distanceSquared(x: Double, y: Double, z: Double, w: Double): Double {
+ val dx = (this.x - x)
+ val dy = (this.y - y)
+ val dz = (this.z - z)
+ val dw = (this.w - w)
+ return dx * dx + dy * dy + dz * dz + dw * dw
+ }
+
+ fun distanceSquared(value: IStruct4d): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
+ fun distanceSquared(value: Vector4d): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
+ fun distance(x: Double, y: Double, z: Double, w: Double) = sqrt(distanceSquared(x, y, z, w))
+ fun distance(value: IStruct4d): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
+ fun distance(value: Vector4d): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
+
+ fun coerceAtLeast(x: Double, y: Double, z: Double, w: Double) = Vector4d(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z), this.w.coerceAtLeast(w))
+ fun coerceAtMost(x: Double, y: Double, z: Double, w: Double) = Vector4d(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z), this.w.coerceAtMost(w))
+
+ fun coerceIn(minimum: Vector4d, maximum: Vector4d) = Vector4d(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y),
+ this.z.coerceIn(minimum.z, maximum.z),
+ this.w.coerceIn(minimum.w, maximum.w))
+
+ fun coerceIn(minimum: IStruct4d, maximum: IStruct4d) = Vector4d(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()),
+ this.z.coerceIn(minimum.component3(), maximum.component3()),
+ this.w.coerceIn(minimum.component4(), maximum.component4()))
+
+ fun coerceAtLeast(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
+ fun coerceAtMost(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
+ fun coerceAtLeast(value: Vector4d): Vector4d { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
+ fun coerceAtMost(value: Vector4d): Vector4d { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
+
+ operator fun times(value: Matrix4d): Vector4d {
+ return Vector4d(
+ x * value.c00 + y * value.c10 + z * value.c20 + w * value.c30,
+ x * value.c01 + y * value.c11 + z * value.c21 + w * value.c31,
+ x * value.c02 + y * value.c12 + z * value.c22 + w * value.c32,
+ x * value.c03 + y * value.c13 + z * value.c23 + w * value.c33,
+ )
+ }
+
+ operator fun times(value: Matrix4dStack) = times(value.last())
+
+ companion object {
+ @JvmField val ZERO = Vector4d()
+ @JvmField val POSITIVE_X = Vector4d(x = 1.0)
+ @JvmField val NEGATIVE_X = Vector4d(x = -1.0)
+ @JvmField val POSITIVE_Y = Vector4d(y = 1.0)
+ @JvmField val NEGATIVE_Y = Vector4d(y = -1.0)
+ @JvmField val POSITIVE_Z = Vector4d(z = 1.0)
+ @JvmField val NEGATIVE_Z = Vector4d(z = -1.0)
+ @JvmField val POSITIVE_W = Vector4d(w = 1.0)
+ @JvmField val NEGATIVE_W = Vector4d(w = -1.0)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4f.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4f.kt
new file mode 100644
index 0000000..d074f1b
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4f.kt
@@ -0,0 +1,244 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct3i
+import ru.dbotthepony.kommons.core.IStruct4d
+import ru.dbotthepony.kommons.core.IStruct4f
+import ru.dbotthepony.kommons.core.IStruct4i
+import ru.dbotthepony.kommons.matrix.Matrix4f
+import ru.dbotthepony.kommons.matrix.Matrix4fStack
+import kotlin.math.absoluteValue
+import kotlin.math.sqrt
+
+data class Vector4f(
+ val x: Float = 0f,
+ val y: Float = 0f,
+ val z: Float = 0f,
+ val w: Float = 0f,
+) : IStruct4f, Vector() {
+ constructor(value: IStruct2f) : this(value.component1(), value.component2())
+ constructor(value: Vector2f) : this(value.x, value.y)
+ constructor(value: IStruct2f, z: Float) : this(value.component1(), value.component2(), z)
+ constructor(value: Vector2f, z: Float) : this(value.x, value.y, z)
+ constructor(value: IStruct2f, z: Float, w: Float) : this(value.component1(), value.component2(), z, w)
+ constructor(value: Vector2f, z: Float, w: Float) : this(value.x, value.y, z, w)
+ constructor(value: IStruct3f) : this(value.component1(), value.component2(), value.component3())
+ constructor(value: Vector3f) : this(value.x, value.y, value.z)
+ constructor(value: IStruct3f, w: Float) : this(value.component1(), value.component2(), value.component3(), w)
+ constructor(value: Vector3f, w: Float) : this(value.x, value.y, value.z, w)
+ constructor(value: IStruct4f) : this(value.component1(), value.component2(), value.component3(), value.component4())
+ constructor(value: Vector4f) : this(value.x, value.y, value.z, value.w)
+
+ override val lengthSquared: Double
+ get() = x.toDouble() * x + y.toDouble() * y + z.toDouble() * z + w.toDouble() * w
+
+ override val isFinite: Boolean
+ get() = x.isFinite() && y.isFinite() && z.isFinite() && w.isFinite()
+ override val isNaN: Boolean
+ get() = x.isNaN() || y.isNaN() || z.isNaN() || w.isNaN()
+
+ val unitVector: Vector4f
+ get() {
+ var length = lengthSquared.toFloat()
+ if (length == 0f || length == 1f) return this
+ length = sqrt(length)
+ return Vector4f(x / length, y / length, z / length, w / length)
+ }
+
+ val absoluteValue: Vector4f
+ get() {
+ val x = x.absoluteValue
+ val y = y.absoluteValue
+ val z = z.absoluteValue
+ val w = w.absoluteValue
+ if (x != this.x || y != this.y || z != this.z || w != this.w) return Vector4f(x, y, z, w)
+ return this
+ }
+
+ val r get() = x
+ val g get() = y
+ val b get() = z
+ val a get() = w
+
+ val s get() = x
+ val t get() = y
+ val p get() = z
+ val q get() = w
+
+ fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4f(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4f(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4f(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4f(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3f): Vector4f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3f): Vector4f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3f): Vector4f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3f): Vector4f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2f): Vector4f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector4f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector4f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector4f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4f): Vector4f { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4f): Vector4f { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4f): Vector4f { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4f): Vector4f { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3f): Vector4f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3f): Vector4f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3f): Vector4f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3f): Vector4f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2f): Vector4f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector4f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector4f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector4f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value, value, value)
+ operator fun minus(value: Float) = minus(value, value, value, value)
+ operator fun times(value: Float) = times(value, value, value, value)
+ operator fun div(value: Float) = div(value, value, value, value)
+
+ fun plus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4f(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4f(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4f(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4f(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4i): Vector4f { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4i): Vector4f { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4i): Vector4f { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4i): Vector4f { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3i): Vector4f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3i): Vector4f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3i): Vector4f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3i): Vector4f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2i): Vector4f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector4f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector4f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector4f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4i): Vector4f { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4i): Vector4f { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4i): Vector4f { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4i): Vector4f { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3i): Vector4f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3i): Vector4f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3i): Vector4f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3i): Vector4f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2i): Vector4f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector4f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector4f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector4f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value, value, value)
+ operator fun minus(value: Int) = minus(value, value, value, value)
+ operator fun times(value: Int) = times(value, value, value, value)
+ operator fun div(value: Int) = div(value, value, value, value)
+
+ fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2d): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2d): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value, value, value)
+ operator fun minus(value: Double) = minus(value, value, value, value)
+ operator fun times(value: Double) = times(value, value, value, value)
+ operator fun div(value: Double) = div(value, value, value, value)
+
+ operator fun unaryMinus(): Vector4f = Vector4f(-x, -y, -z, -w)
+
+ fun dot(x: Float, y: Float, z: Float, w: Float) = this.x * x + this.y * y + this.z * z + this.w * w
+ fun dot(value: IStruct4f): Float { val (x, y, z, w) = value; return dot(x, y, z, w) }
+ fun dot(value: Vector4f): Float { val (x, y, z, w) = value; return dot(x, y, z, w) }
+
+ fun distanceSquared(x: Float, y: Float, z: Float, w: Float): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ val dz = (this.z - z).toDouble()
+ val dw = (this.w - w).toDouble()
+ return dx * dx + dy * dy + dz * dz + dw * dw
+ }
+
+ fun distanceSquared(value: IStruct4f): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
+ fun distanceSquared(value: Vector4f): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
+ fun distance(x: Float, y: Float, z: Float, w: Float) = sqrt(distanceSquared(x, y, z, w))
+ fun distance(value: IStruct4f): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
+ fun distance(value: Vector4f): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
+
+ fun coerceAtLeast(x: Float, y: Float, z: Float, w: Float) = Vector4f(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z), this.w.coerceAtLeast(w))
+ fun coerceAtMost(x: Float, y: Float, z: Float, w: Float) = Vector4f(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z), this.w.coerceAtMost(w))
+
+ fun coerceIn(minimum: Vector4f, maximum: Vector4f) = Vector4f(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y),
+ this.z.coerceIn(minimum.z, maximum.z),
+ this.w.coerceIn(minimum.w, maximum.w))
+
+ fun coerceIn(minimum: IStruct4f, maximum: IStruct4f) = Vector4f(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()),
+ this.z.coerceIn(minimum.component3(), maximum.component3()),
+ this.w.coerceIn(minimum.component4(), maximum.component4()))
+
+ fun coerceAtLeast(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
+ fun coerceAtMost(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
+ fun coerceAtLeast(value: Vector4f): Vector4f { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
+ fun coerceAtMost(value: Vector4f): Vector4f { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
+
+ operator fun times(value: Matrix4f): Vector4f {
+ return Vector4f(
+ x * value.c00 + y * value.c10 + z * value.c20 + w * value.c30,
+ x * value.c01 + y * value.c11 + z * value.c21 + w * value.c31,
+ x * value.c02 + y * value.c12 + z * value.c22 + w * value.c32,
+ x * value.c03 + y * value.c13 + z * value.c23 + w * value.c33,
+ )
+ }
+
+ operator fun times(value: Matrix4fStack) = times(value.last())
+
+ fun toDoubleVector() = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+
+ companion object {
+ @JvmField val ZERO = Vector4f()
+ @JvmField val POSITIVE_X = Vector4f(x = 1.0f)
+ @JvmField val NEGATIVE_X = Vector4f(x = -1.0f)
+ @JvmField val POSITIVE_Y = Vector4f(y = 1.0f)
+ @JvmField val NEGATIVE_Y = Vector4f(y = -1.0f)
+ @JvmField val POSITIVE_Z = Vector4f(z = 1.0f)
+ @JvmField val NEGATIVE_Z = Vector4f(z = -1.0f)
+ @JvmField val POSITIVE_W = Vector4f(w = 1.0f)
+ @JvmField val NEGATIVE_W = Vector4f(w = -1.0f)
+ }
+}
diff --git a/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4i.kt b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4i.kt
new file mode 100644
index 0000000..27fdfe6
--- /dev/null
+++ b/linear-algebra/src/main/kotlin/ru/dbotthepony/kommons/vector/Vector4i.kt
@@ -0,0 +1,214 @@
+package ru.dbotthepony.kommons.vector
+
+import ru.dbotthepony.kommons.vector.Vector4f
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct3d
+import ru.dbotthepony.kommons.core.IStruct3f
+import ru.dbotthepony.kommons.core.IStruct3i
+import ru.dbotthepony.kommons.core.IStruct4d
+import ru.dbotthepony.kommons.core.IStruct4f
+import ru.dbotthepony.kommons.core.IStruct4i
+import kotlin.math.sqrt
+
+data class Vector4i(
+ val x: Int = 0,
+ val y: Int = 0,
+ val z: Int = 0,
+ val w: Int = 0,
+) : IStruct4i, Vector() {
+ constructor(value: IStruct2i) : this(value.component1(), value.component2())
+ constructor(value: Vector2i) : this(value.x, value.y)
+ constructor(value: IStruct2i, z: Int) : this(value.component1(), value.component2(), z)
+ constructor(value: Vector2i, z: Int) : this(value.x, value.y, z)
+ constructor(value: IStruct2i, z: Int, w: Int) : this(value.component1(), value.component2(), z, w)
+ constructor(value: Vector2i, z: Int, w: Int) : this(value.x, value.y, z, w)
+ constructor(value: IStruct3i) : this(value.component1(), value.component2(), value.component3())
+ constructor(value: Vector3i) : this(value.x, value.y, value.z)
+ constructor(value: IStruct3i, w: Int) : this(value.component1(), value.component2(), value.component3(), w)
+ constructor(value: Vector3i, w: Int) : this(value.x, value.y, value.z, w)
+ constructor(value: IStruct4i) : this(value.component1(), value.component2(), value.component3(), value.component4())
+ constructor(value: Vector4i) : this(value.x, value.y, value.z, value.w)
+
+ override val lengthSquared: Double
+ get() = x.toDouble() * x + y.toDouble() * y + z.toDouble() * z + w.toDouble() * w
+
+ override val isFinite: Boolean
+ get() = true
+ override val isNaN: Boolean
+ get() = false
+
+ val r get() = x
+ val g get() = y
+ val b get() = z
+ val a get() = w
+
+ val s get() = x
+ val t get() = y
+ val p get() = z
+ val q get() = w
+
+ fun plus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4i(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4i(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4i(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4i(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4i): Vector4i { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4i): Vector4i { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4i): Vector4i { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4i): Vector4i { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3i): Vector4i { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3i): Vector4i { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3i): Vector4i { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3i): Vector4i { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2i): Vector4i { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2i): Vector4i { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2i): Vector4i { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2i): Vector4i { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4i): Vector4i { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4i): Vector4i { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4i): Vector4i { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4i): Vector4i { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3i): Vector4i { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3i): Vector4i { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3i): Vector4i { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3i): Vector4i { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2i): Vector4i { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2i): Vector4i { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2i): Vector4i { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2i): Vector4i { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Int) = plus(value, value, value, value)
+ operator fun minus(value: Int) = minus(value, value, value, value)
+ operator fun times(value: Int) = times(value, value, value, value)
+ operator fun div(value: Int) = div(value, value, value, value)
+
+ fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2d): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2d): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2d): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2d): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2d): Vector4d { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2d): Vector4d { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2d): Vector4d { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2d): Vector4d { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Double) = plus(value, value, value, value)
+ operator fun minus(value: Double) = minus(value, value, value, value)
+ operator fun times(value: Double) = times(value, value, value, value)
+ operator fun div(value: Double) = div(value, value, value, value)
+
+ fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4f(this.x + x, this.y + y, this.z + z, this.w + w)
+ fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4f(this.x - x, this.y - y, this.z - z, this.w - w)
+ fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4f(this.x * x, this.y * y, this.z * z, this.w * w)
+ fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4f(this.x / x, this.y / y, this.z / z, this.w / w)
+
+ operator fun plus(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: IStruct4f): Vector4f { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: IStruct3f): Vector4f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: IStruct3f): Vector4f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: IStruct3f): Vector4f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: IStruct3f): Vector4f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: IStruct2f): Vector4f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: IStruct2f): Vector4f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: IStruct2f): Vector4f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: IStruct2f): Vector4f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Vector4f): Vector4f { val (x, y, z, w) = value; return plus(x, y, z, w) }
+ operator fun minus(value: Vector4f): Vector4f { val (x, y, z, w) = value; return minus(x, y, z, w) }
+ operator fun times(value: Vector4f): Vector4f { val (x, y, z, w) = value; return times(x, y, z, w) }
+ operator fun div(value: Vector4f): Vector4f { val (x, y, z, w) = value; return div(x, y, z, w) }
+ operator fun plus(value: Vector3f): Vector4f { val (x, y, z) = value; return plus(x, y, z) }
+ operator fun minus(value: Vector3f): Vector4f { val (x, y, z) = value; return minus(x, y, z) }
+ operator fun times(value: Vector3f): Vector4f { val (x, y, z) = value; return times(x, y, z) }
+ operator fun div(value: Vector3f): Vector4f { val (x, y, z) = value; return div(x, y, z) }
+ operator fun plus(value: Vector2f): Vector4f { val (x, y) = value; return plus(x, y) }
+ operator fun minus(value: Vector2f): Vector4f { val (x, y) = value; return minus(x, y) }
+ operator fun times(value: Vector2f): Vector4f { val (x, y) = value; return times(x, y) }
+ operator fun div(value: Vector2f): Vector4f { val (x, y) = value; return div(x, y) }
+
+ operator fun plus(value: Float) = plus(value, value, value, value)
+ operator fun minus(value: Float) = minus(value, value, value, value)
+ operator fun times(value: Float) = times(value, value, value, value)
+ operator fun div(value: Float) = div(value, value, value, value)
+
+ operator fun unaryMinus(): Vector4i = Vector4i(-x, -y, -z, -w)
+
+ fun dot(x: Int, y: Int, z: Int, w: Int) = this.x * x + this.y * y + this.z * z + this.w * w
+ fun dot(value: IStruct4i): Int { val (x, y, z, w) = value; return dot(x, y, z, w) }
+ fun dot(value: Vector4i): Int { val (x, y, z, w) = value; return dot(x, y, z, w) }
+
+ fun distanceSquared(x: Int, y: Int, z: Int, w: Int): Double {
+ val dx = (this.x - x).toDouble()
+ val dy = (this.y - y).toDouble()
+ val dz = (this.z - z).toDouble()
+ val dw = (this.w - w).toDouble()
+ return dx * dx + dy * dy + dz * dz + dw * dw
+ }
+
+ fun distanceSquared(value: IStruct4i): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
+ fun distanceSquared(value: Vector4i): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
+ fun distance(x: Int, y: Int, z: Int, w: Int) = sqrt(distanceSquared(x, y, z, w))
+ fun distance(value: IStruct4i): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
+ fun distance(value: Vector4i): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
+
+ fun coerceAtLeast(x: Int, y: Int, z: Int, w: Int) = Vector4i(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z), this.w.coerceAtLeast(w))
+ fun coerceAtMost(x: Int, y: Int, z: Int, w: Int) = Vector4i(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z), this.w.coerceAtMost(w))
+
+ fun coerceIn(minimum: Vector4i, maximum: Vector4i) = Vector4i(
+ this.x.coerceIn(minimum.x, maximum.x),
+ this.y.coerceIn(minimum.y, maximum.y),
+ this.z.coerceIn(minimum.z, maximum.z),
+ this.w.coerceIn(minimum.w, maximum.w))
+
+ fun coerceIn(minimum: IStruct4i, maximum: IStruct4i) = Vector4i(
+ this.x.coerceIn(minimum.component1(), maximum.component1()),
+ this.y.coerceIn(minimum.component2(), maximum.component2()),
+ this.z.coerceIn(minimum.component3(), maximum.component3()),
+ this.w.coerceIn(minimum.component4(), maximum.component4()))
+
+ fun coerceAtLeast(value: IStruct4i): Vector4i { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
+ fun coerceAtMost(value: IStruct4i): Vector4i { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
+ fun coerceAtLeast(value: Vector4i): Vector4i { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
+ fun coerceAtMost(value: Vector4i): Vector4i { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
+
+ fun toDoubleVector() = Vector4d(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
+ fun toFloatVector() = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())
+
+ companion object {
+ @JvmField val ZERO = Vector4i()
+ @JvmField val POSITIVE_X = Vector4i(x = 1)
+ @JvmField val NEGATIVE_X = Vector4i(x = -1)
+ @JvmField val POSITIVE_Y = Vector4i(y = 1)
+ @JvmField val NEGATIVE_Y = Vector4i(y = -1)
+ @JvmField val POSITIVE_Z = Vector4i(z = 1)
+ @JvmField val NEGATIVE_Z = Vector4i(z = -1)
+ @JvmField val POSITIVE_W = Vector4i(w = 1)
+ @JvmField val NEGATIVE_W = Vector4i(w = -1)
+ }
+}
diff --git a/math/build.gradle.kts b/math/build.gradle.kts
index 52c5a2f..6364429 100644
--- a/math/build.gradle.kts
+++ b/math/build.gradle.kts
@@ -16,6 +16,8 @@ repositories {
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
+
+ implementation(project(":core"))
}
tasks.test {
diff --git a/math/src/main/kotlin/ru/dbotthepony/kommons/math/Bezier.kt b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Bezier.kt
new file mode 100644
index 0000000..3fca25d
--- /dev/null
+++ b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Bezier.kt
@@ -0,0 +1,381 @@
+
+@file:Suppress("unused")
+
+package ru.dbotthepony.kommons.math
+
+import kotlin.math.pow
+
+/**
+ * Bézier curve of two components, denoted by [t].
+ *
+ * Mathematically it is equal to linear interpolation.
+ */
+fun bezierCurve(t: Float, a0: Float, a1: Float): Float {
+ return linearInterpolation(t, a0, a1)
+}
+
+/**
+ * Bézier curve of two components, denoted by [t].
+ *
+ * Mathematically it is equal to linear interpolation.
+ */
+fun bezierCurve(t: Double, a0: Double, a1: Double): Double {
+ return linearInterpolation(t, a0, a1)
+}
+
+/**
+ * Bézier curve of three components, denoted by [t].
+ *
+ * Mathematically, it is computed using binomial of third order.
+ *
+ * If numerical stability is a priority, use [bezierCurveStrict].
+ */
+fun bezierCurve(t: Float, a0: Float, a1: Float, a2: Float): Float {
+ if (t <= 0f)
+ return a0
+ else if (t >= 1f)
+ return a2
+
+ val inv = 1f - t
+
+ return inv * inv * a0 + 2f * t * inv * a1 + t * t * a2
+}
+
+/**
+ * Bézier curve of three components, denoted by [t].
+ *
+ * Mathematically, it is computed using binomial of third order.
+ *
+ * If numerical stability is a priority, use [bezierCurveStrict].
+ */
+fun bezierCurve(t: Double, a0: Double, a1: Double, a2: Double): Double {
+ if (t <= 0.0)
+ return a0
+ else if (t >= 1.0)
+ return a2
+
+ val inv = 1.0 - t
+
+ return inv * inv * a0 + 2.0 * t * inv * a1 + t * t * a2
+}
+
+/**
+ * Bézier curve of four components, denoted by [t].
+ *
+ * Mathematically, it is computed using binomial of fourth order.
+ *
+ * If numerical stability is a priority, use [bezierCurveStrict].
+ */
+fun bezierCurve(t: Float, a0: Float, a1: Float, a2: Float, a3: Float): Float {
+ if (t <= 0f)
+ return a0
+ else if (t >= 1f)
+ return a3
+
+ val inv = 1f - t
+
+ return inv * inv * inv * a0 + 3f * t * inv * inv * a1 + 3f * t * t * inv * a2 + t * t * t * a3
+}
+
+/**
+ * Bézier curve of four components, denoted by [t].
+ *
+ * Mathematically, it is computed using binomial of fourth order.
+ *
+ * If numerical stability is a priority, use [bezierCurveStrict].
+ */
+fun bezierCurve(t: Double, a0: Double, a1: Double, a2: Double, a3: Double): Double {
+ if (t <= 0.0)
+ return a0
+ else if (t >= 1.0)
+ return a3
+
+ val inv = 1.0 - t
+
+ return inv * inv * inv * a0 + 3.0 * t * inv * inv * a1 + 3.0 * t * t * inv * a2 + t * t * t * a3
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm recursively compute curves of lower order, until order of 2 is reached,
+ * in which case [linearInterpolation] is used.
+ */
+fun bezierCurveStrict(t: Float, vararg values: Float): Float {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ else -> {
+ @Suppress("name_shadowing")
+ val values = values.clone()
+
+ // construct prime - 1
+ for (length in values.size - 2 downTo 0) {
+ // search prime
+ for (j in 0 .. length) {
+ val point1 = values[j]
+ val point2 = values[j + 1]
+
+ values[j] = linearInterpolation(t, point1, point2)
+ }
+ }
+
+ return values[0]
+ }
+ }
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm recursively compute curves of lower order, until order of 2 is reached,
+ * in which case [linearInterpolation] is used.
+ *
+ * This function does not copy [values] and modify it directly.
+ */
+fun bezierCurveStrictDirect(t: Float, values: FloatArray): Float {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ else -> {
+ // construct prime - 1
+ for (length in values.size - 2 downTo 0) {
+ // search prime
+ for (j in 0 .. length) {
+ val point1 = values[j]
+ val point2 = values[j + 1]
+
+ values[j] = linearInterpolation(t, point1, point2)
+ }
+ }
+
+ return values[0]
+ }
+ }
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm recursively compute curves of lower order, until order of 2 is reached,
+ * in which case [linearInterpolation] is used.
+ */
+fun bezierCurveStrict(t: Double, vararg values: Double): Double {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ else -> {
+ @Suppress("name_shadowing")
+ val values = values.clone()
+
+ // construct prime - 1
+ for (length in values.size - 2 downTo 0) {
+ // search prime
+ for (j in 0 .. length) {
+ val point1 = values[j]
+ val point2 = values[j + 1]
+
+ values[j] = linearInterpolation(t, point1, point2)
+ }
+ }
+
+ return values[0]
+ }
+ }
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm recursively compute curves of lower order, until order of 2 is reached,
+ * in which case [linearInterpolation] is used.
+ *
+ * This function does not copy [values] and modify it directly.
+ */
+fun bezierCurveStrictDirect(t: Double, values: DoubleArray): Double {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ else -> {
+ // construct prime - 1
+ for (length in values.size - 2 downTo 0) {
+ // search prime
+ for (j in 0 .. length) {
+ val point1 = values[j]
+ val point2 = values[j + 1]
+
+ values[j] = linearInterpolation(t, point1, point2)
+ }
+ }
+
+ return values[0]
+ }
+ }
+}
+
+private val polynomialCacheF = arrayOfNulls(60)
+private val polynomialCacheD = arrayOfNulls(160)
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm compute pascal triangle' row using [pascalTriangleRowFloat], and then compute
+ * Bézier curve using computed coefficients.
+ *
+ * Generally, this method is faster than [bezierCurveStrict], but lack numeric stability the latter offers.
+ *
+ * This function use cache for reasonable order of pascal triangle.
+ */
+fun bezierCurveBinomial(t: Float, vararg values: Float): Float {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ 2 -> return bezierCurve(t, values[0], values[1])
+ 3 -> return bezierCurve(t, values[0], values[1], values[2])
+ 4 -> return bezierCurve(t, values[0], values[1], values[2], values[3])
+ }
+
+ val poly: FloatArray
+ val trigRow = values.size - 1
+
+ if (values.size >= 60) {
+ poly = pascalTriangleRowFloat(trigRow)
+ } else {
+ if (polynomialCacheF[trigRow] == null) {
+ polynomialCacheF[trigRow] = pascalTriangleRowFloat(trigRow)
+ }
+
+ poly = polynomialCacheF[trigRow]!!
+ }
+
+ var sum = 0f
+ val inv = 1f - t
+
+ for (i in values.indices) {
+ sum += inv.pow(trigRow - i) * t.pow(i) * values[i] * poly[i]
+ }
+
+ return sum
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm compute pascal triangle' row using [pascalTriangleRowFloat], and then compute
+ * Bézier curve using computed coefficients.
+ *
+ * Generally, this method is faster than [bezierCurveStrict], but lack numeric stability the latter offers.
+ *
+ * This function use cache for reasonable order of pascal triangle.
+ *
+ * This function does not modify [values], but also avoids copying.
+ */
+fun bezierCurveBinomialDirect(t: Float, values: FloatArray): Float {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ 2 -> return bezierCurve(t, values[0], values[1])
+ 3 -> return bezierCurve(t, values[0], values[1], values[2])
+ 4 -> return bezierCurve(t, values[0], values[1], values[2], values[3])
+ }
+
+ val poly: FloatArray
+ val trigRow = values.size - 1
+
+ if (values.size >= 60) {
+ poly = pascalTriangleRowFloat(trigRow)
+ } else {
+ if (polynomialCacheF[trigRow] == null) {
+ polynomialCacheF[trigRow] = pascalTriangleRowFloat(trigRow)
+ }
+
+ poly = polynomialCacheF[trigRow]!!
+ }
+
+ var sum = 0f
+ val inv = 1f - t
+
+ for (i in values.indices) {
+ sum += inv.pow(trigRow - i) * t.pow(i) * values[i] * poly[i]
+ }
+
+ return sum
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm compute pascal triangle' row using [pascalTriangleRowDouble], and then compute
+ * Bézier curve using computed coefficients.
+ *
+ * Generally, this method is faster than [bezierCurveStrict], but lack numeric stability the latter offers.
+ *
+ * This function use cache for reasonable order of pascal triangle.
+ */
+fun bezierCurveBinomial(t: Double, vararg values: Double): Double {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ 2 -> return bezierCurve(t, values[0], values[1])
+ 3 -> return bezierCurve(t, values[0], values[1], values[2])
+ 4 -> return bezierCurve(t, values[0], values[1], values[2], values[3])
+ }
+
+ val poly: DoubleArray
+ val trigRow = values.size - 1
+
+ if (values.size >= 160) {
+ poly = pascalTriangleRowDouble(trigRow)
+ } else {
+ if (polynomialCacheD[trigRow] == null) {
+ polynomialCacheD[trigRow] = pascalTriangleRowDouble(trigRow)
+ }
+
+ poly = polynomialCacheD[trigRow]!!
+ }
+
+ var sum = 0.0
+ val inv = 1.0 - t
+
+ for (i in values.indices) {
+ sum += inv.pow(trigRow - i) * t.pow(i) * values[i] * poly[i]
+ }
+
+ return sum
+}
+
+/**
+ * Bézier curve of any order, denoted by [t].
+ *
+ * This algorithm compute pascal triangle' row using [pascalTriangleRowDouble], and then compute
+ * Bézier curve using computed coefficients.
+ *
+ * Generally, this method is faster than [bezierCurveStrict], but lack numeric stability the latter offers.
+ *
+ * This function use cache for reasonable order of pascal triangle.
+ *
+ * This function does not modify [values], but also avoids copying.
+ */
+fun bezierCurveBinomialDirect(t: Double, values: DoubleArray): Double {
+ when (values.size) {
+ 0, 1 -> throw IllegalArgumentException("Provided array has only ${values.size} entries in it")
+ 2 -> return bezierCurve(t, values[0], values[1])
+ 3 -> return bezierCurve(t, values[0], values[1], values[2])
+ 4 -> return bezierCurve(t, values[0], values[1], values[2], values[3])
+ }
+
+ val poly: DoubleArray
+ val trigRow = values.size - 1
+
+ if (values.size >= 160) {
+ poly = pascalTriangleRowDouble(trigRow)
+ } else {
+ if (polynomialCacheD[trigRow] == null) {
+ polynomialCacheD[trigRow] = pascalTriangleRowDouble(trigRow)
+ }
+
+ poly = polynomialCacheD[trigRow]!!
+ }
+
+ var sum = 0.0
+ val inv = 1.0 - t
+
+ for (i in values.indices) {
+ sum += inv.pow(trigRow - i) * t.pow(i) * values[i] * poly[i]
+ }
+
+ return sum
+}
diff --git a/math/src/main/kotlin/ru/dbotthepony/kommons/math/Binomial.kt b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Binomial.kt
new file mode 100644
index 0000000..0daacc4
--- /dev/null
+++ b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Binomial.kt
@@ -0,0 +1,244 @@
+package ru.dbotthepony.kommons.math
+
+/**
+ * Calculates n!
+ */
+fun factorial(n: Int): Int {
+ require(n >= 0) { "Tried to compute ($n)!" }
+
+ when (n) {
+ 0, 1 -> return 1
+ 2 -> return 2
+ 3 -> return 6
+ }
+
+ var value = 6
+
+ for (j in 4..n) {
+ value *= j
+ }
+
+ return value
+}
+
+/**
+ * Calculates n!
+ */
+fun factorial(n: Long): Long {
+ require(n >= 0L) { "Tried to compute ($n)!" }
+
+ when (n) {
+ 0L, 1L -> return 1L
+ 2L -> return 2L
+ 3L -> return 6L
+ }
+
+ var value = 6L
+
+ for (j in 4L..n) {
+ value *= j
+ }
+
+ return value
+}
+
+/**
+ * Calculates n!
+ *
+ * For higher order of [n] this is prone to rounding errors.
+ */
+fun factorialFloat(n: Long): Float {
+ require(n >= 0L) { "Tried to compute ($n)!" }
+
+ when (n) {
+ 0L, 1L -> return 1f
+ 2L -> return 2f
+ 3L -> return 6f
+ }
+
+ var value = 6f
+
+ for (j in 4L..n) {
+ value *= j
+ }
+
+ return value
+}
+
+/**
+ * Calculates n!
+ *
+ * For higher order of [n] this is prone to rounding errors.
+ */
+fun factorialDouble(n: Long): Double {
+ require(n >= 0L) { "Tried to compute ($n)!" }
+
+ when (n) {
+ 0L, 1L -> return 1.0
+ 2L -> return 2.0
+ 3L -> return 6.0
+ }
+
+ var value = 6.0
+
+ for (j in 4L..n) {
+ value *= j
+ }
+
+ return value
+}
+
+/**
+ * Calculates n!
+ *
+ * For higher order of [n] this is prone to rounding errors.
+ */
+fun factorialFloat(n: Int): Float {
+ require(n >= 0L) { "Tried to compute ($n)!" }
+
+ when (n) {
+ 0, 1 -> return 1f
+ 2 -> return 2f
+ 3 -> return 6f
+ }
+
+ var value = 6f
+
+ for (j in 4..n) {
+ value *= j
+ }
+
+ return value
+}
+
+/**
+ * Calculates n!
+ *
+ * For higher order of [n] this is prone to rounding errors.
+ */
+fun factorialDouble(n: Int): Double {
+ require(n >= 0L) { "Tried to compute ($n)!" }
+
+ when (n) {
+ 0, 1 -> return 1.0
+ 2 -> return 2.0
+ 3 -> return 6.0
+ }
+
+ var value = 6.0
+
+ for (j in 4..n) {
+ value *= j
+ }
+
+ return value
+}
+
+/**
+ * Calculates binomial coefficient of n by k
+ */
+fun binomial(n: Int, k: Int): Int {
+ return factorial(n) / (factorial(k) * factorial(n - k))
+}
+
+/**
+ * Calculates binomial coefficient of n by k
+ */
+fun binomial(n: Long, k: Long): Long {
+ return factorial(n) / (factorial(k) * factorial(n - k))
+}
+
+/**
+ * Calculates binomial coefficient of n by k.
+ *
+ * For higher order of [n] or [k] this is prone to rounding errors.
+ */
+fun binomialFloat(n: Int, k: Int): Float {
+ return factorialFloat(n) / (factorialFloat(k) * factorialFloat(n - k))
+}
+
+/**
+ * Calculates binomial coefficient of n by k.
+ *
+ * For higher order of [n] or [k] this is prone to rounding errors.
+ */
+fun binomialFloat(n: Long, k: Long): Float {
+ return factorialFloat(n) / (factorialFloat(k) * factorialFloat(n - k))
+}
+
+/**
+ * Calculates binomial coefficient of n by k.
+ *
+ * For higher order of [n] or [k] this is prone to rounding errors.
+ */
+fun binomialDouble(n: Int, k: Int): Double {
+ return factorialDouble(n) / (factorialDouble(k) * factorialDouble(n - k))
+}
+
+/**
+ * Calculates binomial coefficient of n by k
+ *
+ * For higher order of [n] or [k] this is prone to rounding errors.
+ */
+fun binomialDouble(n: Long, k: Long): Double {
+ return factorialDouble(n) / (factorialDouble(k) * factorialDouble(n - k))
+}
+
+/**
+ * Calculates a row in pascal triangle, returning result as [IntArray].
+ */
+fun pascalTriangleRow(row: Int): IntArray {
+ val built = IntArray(row + 1)
+
+ for (k in 0 .. row) {
+ built[k] = binomial(row, k)
+ }
+
+ return built
+}
+
+/**
+ * Calculates a row in pascal triangle, returning result as [LongArray].
+ */
+fun pascalTriangleRowLong(row: Int): LongArray {
+ val built = LongArray(row + 1)
+ val rowL = row.toLong()
+
+ for (k in 0 .. row) {
+ built[k] = binomial(rowL, k.toLong())
+ }
+
+ return built
+}
+
+/**
+ * Calculates a row in pascal triangle, returning result as [FloatArray].
+ *
+ * High orders of polynomials are numerically unstable and prone to increasing rounding errors.
+ */
+fun pascalTriangleRowFloat(row: Int): FloatArray {
+ val built = FloatArray(row + 1)
+ val rowL = row.toLong()
+
+ for (k in 0 .. row) {
+ built[k] = binomialFloat(rowL, k.toLong())
+ }
+
+ return built
+}
+
+/**
+ * Calculates a row in pascal triangle, returning result as [DoubleArray].
+ *
+ * High orders of polynomials are numerically unstable and prone to increasing rounding errors.
+ */
+fun pascalTriangleRowDouble(row: Int): DoubleArray {
+ val built = DoubleArray(row + 1)
+ val rowL = row.toLong()
+
+ for (k in 0 .. row) {
+ built[k] = binomialDouble(rowL, k.toLong())
+ }
+
+ return built
+}
diff --git a/math/src/main/kotlin/ru/dbotthepony/kommons/math/Intersect.kt b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Intersect.kt
new file mode 100644
index 0000000..2efa0fb
--- /dev/null
+++ b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Intersect.kt
@@ -0,0 +1,495 @@
+
+@file:Suppress("nothing_to_inline", "unused")
+
+package ru.dbotthepony.kommons.math
+
+import ru.dbotthepony.kommons.core.IStruct2d
+import ru.dbotthepony.kommons.core.IStruct2f
+import ru.dbotthepony.kommons.core.IStruct2i
+import ru.dbotthepony.kommons.core.IStruct2l
+
+private inline fun span(x0: Float, x1: Float): Float {
+ return x1 - x0
+}
+
+private inline fun span(x0: Double, x1: Double): Double {
+ return x1 - x0
+}
+
+/**
+ * Whenever two segments on one axis intersect
+ */
+fun intersectSegments(a0: Float, a1: Float, b0: Float, b1: Float): Boolean {
+ return a0 in b0 .. b1 || a1 in b0 .. b1 || b0 in a0 .. a1 || b1 in a0 .. a1
+}
+
+/**
+ * Whenever two segments on one axis intersect
+ */
+fun intersectSegments(a0: Double, a1: Double, b0: Double, b1: Double): Boolean {
+ return a0 in b0 .. b1 || a1 in b0 .. b1 || b0 in a0 .. a1 || b1 in a0 .. a1
+}
+
+/**
+ * Whenever two segments on one axis intersect
+ */
+fun intersectSegments(a0: Int, a1: Int, b0: Int, b1: Int): Boolean {
+ return a0 in b0 .. b1 || a1 in b0 .. b1 || b0 in a0 .. a1 || b1 in a0 .. a1
+}
+
+/**
+ * Whenever two segments on one axis intersect
+ */
+fun intersectSegments(a0: Long, a1: Long, b0: Long, b1: Long): Boolean {
+ return a0 in b0 .. b1 || a1 in b0 .. b1 || b0 in a0 .. a1 || b1 in a0 .. a1
+}
+
+fun intersectRectangles(
+ thisMinsX: Float,
+ thisMinsY: Float,
+ thisMaxsX: Float,
+ thisMaxsY: Float,
+
+ otherMinsX: Float,
+ otherMinsY: Float,
+ otherMaxsX: Float,
+ otherMaxsY: Float,
+): Boolean {
+ return intersectSegments(thisMinsX, thisMaxsX, otherMinsX, otherMaxsX) && intersectSegments(thisMinsY, thisMaxsY, otherMinsY, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMinsX: Float,
+ thisMinsY: Float,
+ thisMaxsX: Float,
+ thisMaxsY: Float,
+
+ otherMinsX: Float,
+ otherMinsY: Float,
+ otherMaxsX: Float,
+ otherMaxsY: Float,
+): Boolean {
+ if (span(thisMinsX, thisMaxsX) < span(otherMinsX, otherMaxsX) || span(thisMinsY, thisMaxsY) < span(otherMinsY, otherMaxsY))
+ return false
+
+ return otherMinsX in thisMinsX .. thisMaxsX &&
+ otherMaxsX in thisMinsX .. thisMaxsX &&
+ otherMinsY in thisMinsY .. thisMaxsY &&
+ otherMaxsY in thisMinsY .. thisMaxsY
+}
+
+fun intersectRectangles(
+ thisMins: IStruct2f,
+ thisMaxs: IStruct2f,
+
+ otherMins: IStruct2f,
+ otherMaxs: IStruct2f
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return intersectRectangles(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMins: IStruct2f,
+ thisMaxs: IStruct2f,
+
+ otherMins: IStruct2f,
+ otherMaxs: IStruct2f
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return rectangleContainsRectangle(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+fun intersectRectangles(
+ thisMinsX: Double,
+ thisMinsY: Double,
+ thisMaxsX: Double,
+ thisMaxsY: Double,
+
+ otherMinsX: Double,
+ otherMinsY: Double,
+ otherMaxsX: Double,
+ otherMaxsY: Double,
+): Boolean {
+ return intersectSegments(thisMinsX, thisMaxsX, otherMinsX, otherMaxsX) && intersectSegments(thisMinsY, thisMaxsY, otherMinsY, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMinsX: Double,
+ thisMinsY: Double,
+ thisMaxsX: Double,
+ thisMaxsY: Double,
+
+ otherMinsX: Double,
+ otherMinsY: Double,
+ otherMaxsX: Double,
+ otherMaxsY: Double,
+): Boolean {
+ if (span(thisMinsX, thisMaxsX) < span(otherMinsX, otherMaxsX) || span(thisMinsY, thisMaxsY) < span(otherMinsY, otherMaxsY))
+ return false
+
+ return otherMinsX in thisMinsX .. thisMaxsX &&
+ otherMaxsX in thisMinsX .. thisMaxsX &&
+ otherMinsY in thisMinsY .. thisMaxsY &&
+ otherMaxsY in thisMinsY .. thisMaxsY
+}
+
+fun intersectRectangles(
+ thisMins: IStruct2d,
+ thisMaxs: IStruct2d,
+
+ otherMins: IStruct2d,
+ otherMaxs: IStruct2d
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return intersectRectangles(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMins: IStruct2d,
+ thisMaxs: IStruct2d,
+
+ otherMins: IStruct2d,
+ otherMaxs: IStruct2d
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return rectangleContainsRectangle(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+private inline fun span(x0: Int, x1: Int): Int {
+ return x1 - x0
+}
+
+fun intersectRectangles(
+ thisMinsX: Int,
+ thisMinsY: Int,
+ thisMaxsX: Int,
+ thisMaxsY: Int,
+
+ otherMinsX: Int,
+ otherMinsY: Int,
+ otherMaxsX: Int,
+ otherMaxsY: Int,
+): Boolean {
+ return intersectSegments(thisMinsX, thisMaxsX, otherMinsX, otherMaxsX) && intersectSegments(thisMinsY, thisMaxsY, otherMinsY, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMinsX: Int,
+ thisMinsY: Int,
+ thisMaxsX: Int,
+ thisMaxsY: Int,
+
+ otherMinsX: Int,
+ otherMinsY: Int,
+ otherMaxsX: Int,
+ otherMaxsY: Int,
+): Boolean {
+ if (span(thisMinsX, thisMaxsX) < span(otherMinsX, otherMaxsX) || span(thisMinsY, thisMaxsY) < span(otherMinsY, otherMaxsY))
+ return false
+
+ return otherMinsX in thisMinsX .. thisMaxsX &&
+ otherMaxsX in thisMinsX .. thisMaxsX &&
+ otherMinsY in thisMinsY .. thisMaxsY &&
+ otherMaxsY in thisMinsY .. thisMaxsY
+}
+
+fun intersectRectangles(
+ thisMins: IStruct2i,
+ thisMaxs: IStruct2i,
+
+ otherMins: IStruct2i,
+ otherMaxs: IStruct2i
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return intersectRectangles(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMins: IStruct2i,
+ thisMaxs: IStruct2i,
+
+ otherMins: IStruct2i,
+ otherMaxs: IStruct2i
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return rectangleContainsRectangle(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+private inline fun span(x0: Long, x1: Long): Long {
+ return x1 - x0
+}
+
+fun intersectRectangles(
+ thisMinsX: Long,
+ thisMinsY: Long,
+ thisMaxsX: Long,
+ thisMaxsY: Long,
+
+ otherMinsX: Long,
+ otherMinsY: Long,
+ otherMaxsX: Long,
+ otherMaxsY: Long,
+): Boolean {
+ return intersectSegments(thisMinsX, thisMaxsX, otherMinsX, otherMaxsX) && intersectSegments(thisMinsY, thisMaxsY, otherMinsY, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMinsX: Long,
+ thisMinsY: Long,
+ thisMaxsX: Long,
+ thisMaxsY: Long,
+
+ otherMinsX: Long,
+ otherMinsY: Long,
+ otherMaxsX: Long,
+ otherMaxsY: Long,
+): Boolean {
+ if (span(thisMinsX, thisMaxsX) < span(otherMinsX, otherMaxsX) || span(thisMinsY, thisMaxsY) < span(otherMinsY, otherMaxsY))
+ return false
+
+ return otherMinsX in thisMinsX .. thisMaxsX &&
+ otherMaxsX in thisMinsX .. thisMaxsX &&
+ otherMinsY in thisMinsY .. thisMaxsY &&
+ otherMaxsY in thisMinsY .. thisMaxsY
+}
+
+fun intersectRectangles(
+ thisMins: IStruct2l,
+ thisMaxs: IStruct2l,
+
+ otherMins: IStruct2l,
+ otherMaxs: IStruct2l
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return intersectRectangles(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+fun rectangleContainsRectangle(
+ thisMins: IStruct2l,
+ thisMaxs: IStruct2l,
+
+ otherMins: IStruct2l,
+ otherMaxs: IStruct2l
+): Boolean {
+ val (thisMinsX, thisMinsY) = thisMins
+ val (thisMaxsX, thisMaxsY) = thisMaxs
+
+ val (otherMinsX, otherMinsY) = otherMins
+ val (otherMaxsX, otherMaxsY) = otherMaxs
+
+ return rectangleContainsRectangle(thisMinsX, thisMinsY, thisMaxsX, thisMaxsY, otherMinsX, otherMinsY, otherMaxsX, otherMaxsY)
+}
+
+fun intersectCirclePoint(
+ circleX: Float,
+ circleY: Float,
+ circleRadius: Float,
+
+ x: Float,
+ y: Float
+): Boolean {
+ return ((circleX - x) * (circleX - x) + (circleY - y) * (circleY - y)) <= circleRadius * circleRadius
+}
+
+fun intersectCirclePoint(
+ circleX: Double,
+ circleY: Double,
+ circleRadius: Double,
+
+ x: Double,
+ y: Double
+): Boolean {
+ return ((circleX - x) * (circleX - x) + (circleY - y) * (circleY - y)) <= circleRadius * circleRadius
+}
+
+fun intersectCircleRectangle(
+ circleX: Float,
+ circleY: Float,
+ circleRadius: Float,
+
+ rectMinsX: Float,
+ rectMinsY: Float,
+ rectMaxsX: Float,
+ rectMaxsY: Float,
+): Boolean {
+ // test if circle's center is inside rectangle
+ if (circleX in rectMinsX .. rectMaxsX && circleY in rectMinsY .. rectMaxsY) {
+ return true
+ }
+
+ // test intersection between rectangle and circle's outer square
+ // if they don't intersect there is no way circle can be intersecting with this rect
+ if (!intersectRectangles(
+ circleX - circleRadius,
+ circleY - circleRadius,
+ circleX + circleRadius,
+ circleY + circleRadius,
+ rectMinsX,
+ rectMinsY,
+ rectMaxsX,
+ rectMaxsY,
+ )) {
+ return false
+ }
+
+ return intersectCirclePoint(circleX, circleY, circleRadius, rectMinsX, rectMinsY) ||
+ intersectCirclePoint(circleX, circleY, circleRadius, rectMinsX, rectMaxsY) ||
+ intersectCirclePoint(circleX, circleY, circleRadius, rectMaxsX, rectMaxsY) ||
+ intersectCirclePoint(circleX, circleY, circleRadius, rectMaxsX, rectMinsY)
+}
+
+fun intersectCircleRectangle(
+ circleX: Float,
+ circleY: Float,
+ circleRadius: Float,
+
+ mins: IStruct2f,
+ maxs: IStruct2f,
+): Boolean {
+ val (rectMinsX, rectMinsY) = mins
+ val (rectMaxsX, rectMaxsY) = maxs
+
+ return intersectCircleRectangle(circleX, circleY, circleRadius, rectMinsX, rectMinsY, rectMaxsX, rectMaxsY)
+}
+
+fun intersectCircleRectangle(
+ circlePos: IStruct2f,
+ circleRadius: Float,
+
+ mins: IStruct2f,
+ maxs: IStruct2f,
+): Boolean {
+ val (circleX, circleY) = circlePos
+ val (rectMinsX, rectMinsY) = mins
+ val (rectMaxsX, rectMaxsY) = maxs
+
+ return intersectCircleRectangle(circleX, circleY, circleRadius, rectMinsX, rectMinsY, rectMaxsX, rectMaxsY)
+}
+
+fun intersectCircleRectangle(
+ circlePos: IStruct2f,
+ circleRadius: Float,
+
+ rectMinsX: Float,
+ rectMinsY: Float,
+ rectMaxsX: Float,
+ rectMaxsY: Float,
+): Boolean {
+ val (circleX, circleY) = circlePos
+
+ return intersectCircleRectangle(circleX, circleY, circleRadius, rectMinsX, rectMinsY, rectMaxsX, rectMaxsY)
+}
+
+fun intersectCircleRectangle(
+ circleX: Double,
+ circleY: Double,
+ circleRadius: Double,
+
+ rectMinsX: Double,
+ rectMinsY: Double,
+ rectMaxsX: Double,
+ rectMaxsY: Double,
+): Boolean {
+ // test if circle's center is inside rectangle
+ if (circleX in rectMinsX .. rectMaxsX && circleY in rectMinsY .. rectMaxsY) {
+ return true
+ }
+
+ // test intersection between rectangle and circle's outer square
+ // if they don't intersect there is no way circle can be intersecting with this rect
+ if (!intersectRectangles(
+ circleX - circleRadius,
+ circleY - circleRadius,
+ circleX + circleRadius,
+ circleY + circleRadius,
+ rectMinsX,
+ rectMinsY,
+ rectMaxsX,
+ rectMaxsY,
+ )) {
+ return false
+ }
+
+ return intersectCirclePoint(circleX, circleY, circleRadius, rectMinsX, rectMinsY) ||
+ intersectCirclePoint(circleX, circleY, circleRadius, rectMinsX, rectMaxsY) ||
+ intersectCirclePoint(circleX, circleY, circleRadius, rectMaxsX, rectMaxsY) ||
+ intersectCirclePoint(circleX, circleY, circleRadius, rectMaxsX, rectMinsY)
+}
+
+fun intersectCircleRectangle(
+ circleX: Double,
+ circleY: Double,
+ circleRadius: Double,
+
+ mins: IStruct2d,
+ maxs: IStruct2d,
+): Boolean {
+ val (rectMinsX, rectMinsY) = mins
+ val (rectMaxsX, rectMaxsY) = maxs
+
+ return intersectCircleRectangle(circleX, circleY, circleRadius, rectMinsX, rectMinsY, rectMaxsX, rectMaxsY)
+}
+
+fun intersectCircleRectangle(
+ circlePos: IStruct2d,
+ circleRadius: Double,
+
+ mins: IStruct2d,
+ maxs: IStruct2d,
+): Boolean {
+ val (circleX, circleY) = circlePos
+ val (rectMinsX, rectMinsY) = mins
+ val (rectMaxsX, rectMaxsY) = maxs
+
+ return intersectCircleRectangle(circleX, circleY, circleRadius, rectMinsX, rectMinsY, rectMaxsX, rectMaxsY)
+}
+
+fun intersectCircleRectangle(
+ circlePos: IStruct2d,
+ circleRadius: Double,
+
+ rectMinsX: Double,
+ rectMinsY: Double,
+ rectMaxsX: Double,
+ rectMaxsY: Double,
+): Boolean {
+ val (circleX, circleY) = circlePos
+
+ return intersectCircleRectangle(circleX, circleY, circleRadius, rectMinsX, rectMinsY, rectMaxsX, rectMaxsY)
+}
diff --git a/math/src/main/kotlin/ru/dbotthepony/kommons/math/Lerp.kt b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Lerp.kt
new file mode 100644
index 0000000..b22e550
--- /dev/null
+++ b/math/src/main/kotlin/ru/dbotthepony/kommons/math/Lerp.kt
@@ -0,0 +1,25 @@
+package ru.dbotthepony.kommons.math
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Float, a: Float, b: Float): Float {
+ if (t <= 0f)
+ return a
+ else if (t >= 1f)
+ return b
+
+ return a + (b - a) * t
+}
+
+/**
+ * Linear interpolation between [a] and [b] by [t]
+ */
+fun linearInterpolation(t: Double, a: Double, b: Double): Double {
+ if (t <= 0.0)
+ return a
+ else if (t >= 1.0)
+ return b
+
+ return a + (b - a) * t
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index e18e89d..53eabb5 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -12,3 +12,4 @@ include("math")
include("io-math")
include("collect")
include("guava")
+include("linear-algebra")