diff --git a/harness/tests/scripts/godot/tests/Invocation.gdj b/harness/tests/scripts/godot/tests/Invocation.gdj index 48818aea41..8d9d67c741 100644 --- a/harness/tests/scripts/godot/tests/Invocation.gdj +++ b/harness/tests/scripts/godot/tests/Invocation.gdj @@ -4,20 +4,20 @@ registeredName = Invocation fqName = godot.tests.Invocation baseType = Node3D supertypes = [ - godot.Node3D, + godot.Node3D, godot.Node, godot.Object, godot.core.KtObject, kotlin.Any ] signals = [ - no_param, + no_param, one_param, two_param, signal_with_multiple_targets ] properties = [ - button, + button, enum_list, vector_list, enum_list_mutable, @@ -76,7 +76,7 @@ properties = [ array ] functions = [ - target_function_one, + target_function_one, target_function_two, int_value, long_value, @@ -169,4 +169,4 @@ functions = [ nullable_string_is_null, nullable_return_type, create_variant_array_of_user_type -] +] \ No newline at end of file diff --git a/harness/tests/src/main/kotlin/godot/tests/coretypes/BasisTest.kt b/harness/tests/src/main/kotlin/godot/tests/coretypes/BasisTest.kt index b557aca763..bf3395df34 100644 --- a/harness/tests/src/main/kotlin/godot/tests/coretypes/BasisTest.kt +++ b/harness/tests/src/main/kotlin/godot/tests/coretypes/BasisTest.kt @@ -21,7 +21,7 @@ class BasisTest : Node() { @RegisterFunction fun getRotationQuat(basis: Basis): Quaternion { - return basis.getRotationQuat() + return basis.getRotationQuaternion() } @RegisterFunction diff --git a/kt/godot-library/src/main/kotlin/godot/core/AABB.kt b/kt/godot-library/src/main/kotlin/godot/core/AABB.kt index 758feec150..668f757a83 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/AABB.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/AABB.kt @@ -3,6 +3,8 @@ package godot.core import godot.annotation.CoreTypeHelper import godot.util.CMP_EPSILON import godot.util.RealT +import kotlin.math.min + class AABB( p_position: Vector3, @@ -73,6 +75,19 @@ class AABB( this(other._position, other._size) //API + /** + * Returns an AABB with equivalent position and size, modified so that the most-negative corner is the origin and + * the size is positive. + */ + fun abs() = AABB( + Vector3( + position.x + min(size.x, 0.0), + position.y + min(size.y, 0.0), + position.z + min(size.z, 0.0) + ), + size.abs() + ) + /** * Returns true if this AABB completely encloses another one. */ @@ -127,11 +142,9 @@ class AABB( } /** - * Returns the volume of the AABB. + * Returns the center of the [AABB], which is equal to [position] + ([size] / 2). */ - fun getArea(): RealT { - return _size.x * _size.y * _size.z - } + fun getCenter() = position + (size * 0.5) /** * Gets the position of the 8 endpoints of the AABB in space. @@ -271,6 +284,13 @@ class AABB( ) + ofs } + /** + * Returns the volume of the AABB. + */ + fun getVolume(): RealT { + return _size.x * _size.y * _size.z + } + /** * Returns a copy of the AABB grown a given amount of units towards all the sides. */ @@ -289,20 +309,6 @@ class AABB( _size.z += 2.0 * amount } - /** - * Returns true if the AABB is flat or empty. - */ - fun hasNoArea(): Boolean { - return (_size.x <= CMP_EPSILON || _size.y <= CMP_EPSILON || _size.z <= CMP_EPSILON) - } - - /** - * Returns true if the AABB is empty. - */ - fun hasNoSurface(): Boolean { - return (_size.x <= CMP_EPSILON && _size.y <= CMP_EPSILON && _size.z <= CMP_EPSILON) - } - /** * Returns true if the AABB contains a point. */ @@ -318,6 +324,21 @@ class AABB( } } + /** + * Returns `true` if the [AABB] has a surface or a length, and `false` if the [AABB] is empty (all components of + * [size] are zero or negative). + */ + fun hasSurface(): Boolean { + return (_size.x > CMP_EPSILON && _size.y > CMP_EPSILON && _size.z > CMP_EPSILON) + } + + /** + * Returns `true` if the [AABB] has a volume, and `false` if the [AABB] is flat, empty, or has a negative [size]. + */ + fun hasVolume(): Boolean { + return (_size.x > CMP_EPSILON || _size.y > CMP_EPSILON || _size.z > CMP_EPSILON) + } + /** * Returns the intersection between two AABB. An empty AABB (size 0,0,0) is returned on failure. */ @@ -396,6 +417,49 @@ class AABB( return under && over } + /** + * Returns `true` if the given ray intersects with this [AABB]. Ray length is infinite. + */ + fun intersectsRay(from: Vector3, dir: Vector3): Boolean { + require(size.x >= 0 && size.y >= 0 && size.z >= 0) { + "AABB size is negative, this is not supported. Use AABB.abs() to get an AABB with a positive size." + } + + var c1 = Vector3() + var c2 = Vector3() + val end = position + size + var near = -1e20 + var far = 1e20 + var axis = 0 + + for (i in 0..2) { + if (dir[i] == 0.0) { + if (from[i] < position[i] || from[i] > end[i]) { + return false + } + } else { // ray not parallel to planes in this direction + c1[i] = (position[i] - from[i]) / dir[i] + c2[i] = (end[i] - from[i]) / dir[i] + if (c1[i] > c2[i]) { + val aux = c1 + c1 = c2 + c2 = aux + } + if (c1[i] > near) { + near = c1[i] + } + if (c2[i] < far) { + far = c2[i] + } + if (near > far || far < 0) { + return false + } + } + } + + return true + } + /** * Returns true if the AABB intersects the line segment between from and to. */ @@ -445,6 +509,11 @@ class AABB( return this._position.isEqualApprox(other._position) && this._size.isEqualApprox(other._size) } + /** + * Returns `true` if this [AABB] is finite, by calling [Vector3.isFinite] on each component. + */ + fun isFinite() = position.isFinite() && size.isFinite() + /** * Returns a larger AABB that contains both this AABB and with. */ diff --git a/kt/godot-library/src/main/kotlin/godot/core/Basis.kt b/kt/godot-library/src/main/kotlin/godot/core/Basis.kt index 588d098dd7..dcba051eca 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Basis.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Basis.kt @@ -1,5 +1,6 @@ package godot.core +import godot.EulerOrder import godot.annotation.CoreTypeHelper import godot.util.CMP_EPSILON import godot.util.RealT @@ -68,6 +69,42 @@ class Basis() : CoreType { Basis(0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0), Basis(0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0) ) + + fun fromEuler(euler: Vector3, order: EulerOrder = EulerOrder.EULER_ORDER_YXZ) = Basis().also { + it.setEuler(euler, order) + } + fun fromScale(scale: Vector3) = Basis(scale.x, 0, 0, 0, scale.y, 0, 0, 0, scale.z) + + /** + * Creates a Basis with a rotation such that the forward axis (-Z) points towards the [target] position. + * + * The up axis (+Y) points as close to the [up] vector as possible while staying perpendicular to the forward + * axis. + * The resulting Basis is orthonormalized. The [target] and [up] vectors cannot be zero, and cannot be parallel + * to each other. + */ + fun lookingAt(target: Vector3, up: Vector3 = Vector3(0, 1, 0)): Basis { + require(!target.isZeroApprox()) { + "The target vector can't be zero." + } + require(!up.isZeroApprox()) { + "The up vector can't be zero." + } + val vZ: Vector3 = -target.normalized() + val vX: Vector3 = up.cross(vZ) + + require(!vX.isZeroApprox()) { + "The target vector and up vector can't be parallel to each other." + } + + vX.normalize() + val vY = vZ.cross(vX) + + val basis = Basis() + + basis.setColumns(vX, vY, vZ) + return basis + } } @@ -166,10 +203,20 @@ class Basis() : CoreType { } /** + * Returns the basis's rotation in the form of Euler angles. The Euler order depends on the [order] parameter, by + * default it uses the YXZ convention: when decomposing, first Z, then X, and Y last. The returned vector contains + * the rotation angles in the format (X angle, Y angle, Z angle). * + * Consider using the [getRotationQuaternion] method instead, which returns a [Quaternion] quaternion instead of + * Euler angles. */ - fun getEuler(): Vector3 { - return getEulerYxz() + fun getEuler(order: EulerOrder = EulerOrder.EULER_ORDER_YXZ) = when(order) { + EulerOrder.EULER_ORDER_XYZ -> getEulerXyz() + EulerOrder.EULER_ORDER_XZY -> getEulerXzy() + EulerOrder.EULER_ORDER_YXZ -> getEulerYxz() + EulerOrder.EULER_ORDER_YZX -> getEulerYzx() + EulerOrder.EULER_ORDER_ZXY -> getEulerZxy() + EulerOrder.EULER_ORDER_ZYX -> getEulerZyx() } /** @@ -225,6 +272,36 @@ class Basis() : CoreType { return euler } + /** + * Euler angles in XZY convention. + * See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + * rot = cz*cy -sz cz*sy + * sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx + * cy*sx*sz cz*sx cx*cy+sx*sz*sy + */ + internal fun getEulerXzy(): Vector3 { + val euler = Vector3() + val sz = _x[1] + if (sz < (1.0f - CMP_EPSILON)) { + if (sz > -(1.0f - CMP_EPSILON)) { + euler.x = atan2(_z[1], _y[1]) + euler.y = atan2(_x[2], _x[0]) + euler.z = asin(-sz) + } else { + // It's -1 + euler.x = -atan2(_y[2], _z[2]) + euler.y = 0.0 + euler.z = Math.PI / 2.0f + } + } else { + // It's 1 + euler.x = -atan2(y[2], z[2]); + euler.y = 0.0 + euler.z = -Math.PI / 2.0 + } + return euler + } + /** * getEulerYxz returns a vector containing the Euler angles in the YXZ convention, * as in first-Z, then-X, last-Y. The angles for X, Y, and Z rotations are returned @@ -274,6 +351,93 @@ class Basis() : CoreType { return euler } + /** + * Euler angles in YZX convention. + * See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + * rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx + * sz cz*cx -cz*sx + * -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx + */ + internal fun getEulerYzx(): Vector3 { + val euler = Vector3() + val sz = _y[0] + if (sz < (1.0f - CMP_EPSILON)) { + if (sz > -(1.0f - CMP_EPSILON)) { + euler.x = atan2(-_y[2], _y[1]) + euler.y = atan2(-_z[0], _x[0]) + euler.z = asin(sz) + } else { + // It's -1 + euler.x = atan2(_z[1], _z[2]) + euler.y = 0.0 + euler.z = -Math.PI / 2.0 + } + } else { + // It's 1 + euler.x = atan2(_z[1], _z[2]) + euler.y = 0.0 + euler.z = Math.PI / 2.0 + } + + return euler + } + + /** + * Euler angles in ZXY convention. + * See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + * rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx + * cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx + * -cx*sy sx cx*cy + */ + internal fun getEulerZxy(): Vector3 { + val euler = Vector3() + val sx = _z[1] + + if (sx < 1.0f - CMP_EPSILON) { + if (sx > -(1.0f - CMP_EPSILON)) { + euler.x = asin(sx) + euler.y = atan2(-_z[0], _z[2]) + euler.z = atan2(-_x[1], _y[1]) + } else { + // It's -1 + euler.x = -Math.PI / 2.0f + euler.y = atan2(_x[2], _x[0]) + euler.z = 0.0 + } + } else { + // It's 1 + euler.x = Math.PI / 2.0f + euler.y = atan2(_x[2], _x[0]) + euler.z = 0.0 + } + + return euler + } + + internal fun getEulerZyx(): Vector3 { + val euler = Vector3() + val sy = _z[0]; + + if (sy < 1.0f - CMP_EPSILON) { + if (sy > -(1.0f - CMP_EPSILON)) { + euler.x = atan2(_z[1], _z[2]) + euler.y = asin(-sy) + euler.z = atan2(_y[0], _x[0]) + } else { + // It's -1 + euler.x = 0.0 + euler.y = Math.PI / 2.0f + euler.z = -atan2(_x[1], _y[1]) + } + } else { + // It's 1 + euler.x = 0.0; + euler.y = -Math.PI / 2.0f; + euler.z = -atan2(_x[1], _y[1]); + } + + return euler + } private fun isOrthogonal(): Boolean { val id = Basis() @@ -315,7 +479,7 @@ class Basis() : CoreType { /** * */ - fun getRotationQuat(): Quaternion { + fun getRotationQuaternion(): Quaternion { // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). // See the comment in get_scale() for further information. @@ -378,7 +542,7 @@ class Basis() : CoreType { ) } - fun getQuat(): Quaternion { + fun getQuaternion(): Quaternion { require(isRotation()) { "Basis must be normalized in order to be casted to a Quaternion. Use get_rotation_quat() or call orthonormalized() instead." } val trace = this._x.x + this._y.y + this._z.z val temp: Array @@ -433,6 +597,11 @@ class Basis() : CoreType { return true } + /** + * Returns `true` if this basis is finite, by calling [Vector3.isFinite] on each component. + */ + fun isFinite() = _x.isFinite() && _y.isFinite() && _z.isFinite() + /** * Returns the orthonormalized version of the matrix (useful to call from time to time to avoid rounding error for orthogonal matrices). * This performs a Gram-Schmidt orthonormalization on the basis of the matrix. @@ -507,48 +676,13 @@ class Basis() : CoreType { this._z.z *= scale.z } - /** - * - */ - fun setEuler(p_euler: Vector3) { - setEulerYxz(p_euler) - } - - /** - * setEulerXyz expects a vector containing the Euler angles in the format - * (ax,ay,az), where ax is the angle of rotation around x axis, - * and similar for other axes. - * The current implementation uses XYZ convention (Z is the first rotation). - */ - internal fun setEulerXyz(euler: Vector3) { - - var c: RealT = cos(euler.x) - var s: RealT = sin(euler.x) - - val xmat = Basis(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c) - - c = cos(euler.y) - s = sin(euler.y) - val ymat = Basis(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c) - - c = cos(euler.z) - s = sin(euler.z) - val zmat = Basis(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0) - - //optimizer will optimize away all this anyway - val ret = xmat * (ymat * zmat) - this._x = ret._x - this._y = ret._y - this._z = ret._z + private fun set(basis: Basis) { + this._x = basis._x + this._y = basis._y + this._z = basis._z } - /** - * setEulerYxz expects a vector containing the Euler angles in the format - * (ax,ay,az), where ax is the angle of rotation around x axis, - * and similar for other axes. - * The current implementation uses YXZ convention (Z is the first rotation). - */ - internal fun setEulerYxz(euler: Vector3) { + internal fun setEuler(euler: Vector3, order: EulerOrder = EulerOrder.EULER_ORDER_YXZ) { var c: RealT = cos(euler.x) var s: RealT = sin(euler.x) @@ -562,11 +696,14 @@ class Basis() : CoreType { s = sin(euler.z) val zmat = Basis(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0) - val ret = ymat * xmat * zmat - - this._x = ret._x - this._y = ret._y - this._z = ret._z + when(order) { + EulerOrder.EULER_ORDER_XYZ -> set(xmat * (ymat * zmat)) + EulerOrder.EULER_ORDER_XZY -> set(xmat * zmat * ymat) + EulerOrder.EULER_ORDER_YXZ -> set(ymat * xmat * zmat) + EulerOrder.EULER_ORDER_YZX -> set(ymat * zmat * xmat) + EulerOrder.EULER_ORDER_ZXY -> set(zmat * xmat * ymat) + EulerOrder.EULER_ORDER_ZYX -> set(zmat * ymat * xmat) + } } /** @@ -673,6 +810,18 @@ class Basis() : CoreType { } } + internal fun setColumns(x: Vector3, y: Vector3, z: Vector3) { + setColumn(0, x) + setColumn(1, y) + setColumn(2, z) + } + + internal fun setColumn(index: Int, value: Vector3) { + _x[index] = value.x + _y[index] = value.y + _z[index] = value.z + } + fun set( xx: RealT, xy: RealT, diff --git a/kt/godot-library/src/main/kotlin/godot/core/Callable.kt b/kt/godot-library/src/main/kotlin/godot/core/Callable.kt index 78e0765c3e..397d02606f 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Callable.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Callable.kt @@ -48,6 +48,18 @@ class Callable internal constructor( GarbageCollector.registerNativeCoreType(this, VariantType.CALLABLE) } + fun bind(vararg args: Any?): Callable { + TransferContext.writeArguments(*args.map { VariantType.ANY to it }.toTypedArray()) + Bridge.engine_call_bind(_handle) + return TransferContext.readReturnValue(VariantType.CALLABLE, false) as Callable + } + + fun bindv(args: VariantArray): Callable { + TransferContext.writeArguments(VariantType.ARRAY to args) + Bridge.engine_call_bindv(_handle) + return TransferContext.readReturnValue(VariantType.CALLABLE, false) as Callable + } + fun call(vararg args: Any?): Any? { TransferContext.writeArguments(*args.map { VariantType.ANY to it }.toTypedArray()) Bridge.engine_call_call(_handle) @@ -59,6 +71,78 @@ class Callable internal constructor( Bridge.engine_call_call_deferred(_handle) } + fun callv(args: VariantArray): Any? { + TransferContext.writeArguments(VariantType.ARRAY to args) + Bridge.engine_call_callv(_handle) + return TransferContext.readReturnValue(VariantType.ANY, true) + } + + fun getBoundArguments(): VariantArray { + Bridge.engine_call_get_bound_arguments(_handle) + return TransferContext.readReturnValue(VariantType.ARRAY, false) as VariantArray + } + + fun getBoundArgumentCount(): Int { + Bridge.engine_call_get_bound_arguments_count(_handle) + return TransferContext.readReturnValue(VariantType.JVM_INT, false) as Int + } + + fun getMethod(): StringName { + Bridge.engine_call_get_method(_handle) + return TransferContext.readReturnValue(VariantType.STRING_NAME, false) as StringName + } + + fun getObject(): Object { + Bridge.engine_call_get_object(_handle) + return TransferContext.readReturnValue(VariantType.OBJECT, false) as Object + } + + fun getObjectId(): ObjectID { + Bridge.engine_call_get_object_id(_handle) + return ObjectID(TransferContext.readReturnValue(VariantType.LONG) as Long) + } + + override fun hashCode(): Int { + Bridge.engine_call_hash(_handle) + return TransferContext.readReturnValue(VariantType.JVM_INT) as Int + } + + fun isCustom(): Boolean { + Bridge.engine_call_is_custom(_handle) + return TransferContext.readReturnValue(VariantType.BOOL) as Boolean + } + + fun isNull(): Boolean { + Bridge.engine_call_is_null(_handle) + return TransferContext.readReturnValue(VariantType.BOOL) as Boolean + } + + fun isStandard(): Boolean { + Bridge.engine_call_is_standard(_handle) + return TransferContext.readReturnValue(VariantType.BOOL) as Boolean + } + + fun isValid(): Boolean { + Bridge.engine_call_is_valid(_handle) + return TransferContext.readReturnValue(VariantType.BOOL) as Boolean + } + + fun rpc(vararg args: Any?) { + TransferContext.writeArguments(*args.map { VariantType.ANY to it }.toTypedArray()) + Bridge.engine_call_rpc(_handle) + } + + fun rpcId(peerId: Long, vararg args: Any?) { + TransferContext.writeArguments(VariantType.LONG to peerId, *args.map { VariantType.ANY to it }.toTypedArray()) + Bridge.engine_call_rpc_id(_handle) + } + + fun unbind(argCount: Int): Callable { + TransferContext.writeArguments(VariantType.JVM_INT to argCount) + Bridge.engine_call_unbind(_handle) + return TransferContext.readReturnValue(VariantType.CALLABLE, false) as Callable + } + @Suppress("FunctionName") object Bridge { external fun engine_call_constructor(): VoidPtr @@ -66,8 +150,24 @@ class Callable internal constructor( external fun engine_call_constructor_kt_custom_callable(callable: KtCustomCallable): VoidPtr external fun engine_call_copy_constructor(): VoidPtr + external fun engine_call_bind(_handle: VoidPtr) + external fun engine_call_bindv(_handle: VoidPtr) external fun engine_call_call(handle: VoidPtr) external fun engine_call_call_deferred(handle: VoidPtr) + external fun engine_call_callv(_handle: VoidPtr) + external fun engine_call_get_bound_arguments(_handle: VoidPtr) + external fun engine_call_get_bound_arguments_count(_handle: VoidPtr) + external fun engine_call_get_method(_handle: VoidPtr) + external fun engine_call_get_object(_handle: VoidPtr) + external fun engine_call_get_object_id(_handle: VoidPtr) + external fun engine_call_hash(_handle: VoidPtr) + external fun engine_call_is_custom(_handle: VoidPtr) + external fun engine_call_is_null(_handle: VoidPtr) + external fun engine_call_is_standard(_handle: VoidPtr) + external fun engine_call_is_valid(_handle: VoidPtr) + external fun engine_call_rpc(_handle: VoidPtr) + external fun engine_call_rpc_id(_handle: VoidPtr) + external fun engine_call_unbind(_handle: VoidPtr) } } diff --git a/kt/godot-library/src/main/kotlin/godot/core/Color.kt b/kt/godot-library/src/main/kotlin/godot/core/Color.kt index a4b824ef71..bdb22c7287 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Color.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Color.kt @@ -155,9 +155,9 @@ class Color( } companion object { - inline val aliceblue: Color + inline val aliceBlue: Color get() = Color(0.94, 0.97, 1.00) - inline val antiquewhite: Color + inline val antiqueWhite: Color get() = Color(0.98, 0.92, 0.84) inline val aqua: Color get() = Color(0.00, 1.00, 1.00) @@ -171,17 +171,17 @@ class Color( get() = Color(1.00, 0.89, 0.77) inline val black: Color get() = Color(0.00, 0.00, 0.00) - inline val blanchedalmond: Color + inline val blanchedAlmond: Color get() = Color(1.00, 0.92, 0.80) inline val blue: Color get() = Color(0.00, 0.00, 1.00) - inline val blueviolet: Color + inline val blueViolet: Color get() = Color(0.54, 0.17, 0.89) inline val brown: Color get() = Color(0.65, 0.16, 0.16) inline val burlywood: Color get() = Color(0.87, 0.72, 0.53) - inline val cadetblue: Color + inline val cadetBlue: Color get() = Color(0.37, 0.62, 0.63) inline val chartreuse: Color get() = Color(0.50, 1.00, 0.00) @@ -189,7 +189,7 @@ class Color( get() = Color(0.82, 0.41, 0.12) inline val coral: Color get() = Color(1.00, 0.50, 0.31) - inline val cornflower: Color + inline val cornflowerBlue: Color get() = Color(0.39, 0.58, 0.93) inline val cornsilk: Color get() = Color(1.00, 0.97, 0.86) @@ -197,59 +197,59 @@ class Color( get() = Color(0.86, 0.08, 0.24) inline val cyan: Color get() = Color(0.00, 1.00, 1.00) - inline val darkblue: Color + inline val darkBlue: Color get() = Color(0.00, 0.00, 0.55) - inline val darkcyan: Color + inline val darkCyan: Color get() = Color(0.00, 0.55, 0.55) - inline val darkgoldenrod: Color + inline val darkGoldenrod: Color get() = Color(0.72, 0.53, 0.04) - inline val darkgray: Color + inline val darkGray: Color get() = Color(0.66, 0.66, 0.66) - inline val darkgreen: Color + inline val darkGreen: Color get() = Color(0.00, 0.39, 0.00) - inline val darkkhaki: Color + inline val darkKhaki: Color get() = Color(0.74, 0.72, 0.42) - inline val darkmagenta: Color + inline val darkMagenta: Color get() = Color(0.55, 0.00, 0.55) - inline val darkolivegreen: Color + inline val darkOliveGreen: Color get() = Color(0.33, 0.42, 0.18) inline val darkorange: Color get() = Color(1.00, 0.55, 0.00) - inline val darkorchid: Color + inline val darkOrchid: Color get() = Color(0.60, 0.20, 0.80) - inline val darkred: Color + inline val darkRed: Color get() = Color(0.55, 0.00, 0.00) - inline val darksalmon: Color + inline val darkSalmon: Color get() = Color(0.91, 0.59, 0.48) - inline val darkseagreen: Color + inline val darkSeaGreen: Color get() = Color(0.56, 0.74, 0.56) - inline val darkslateblue: Color + inline val darkSlateBlue: Color get() = Color(0.28, 0.24, 0.55) - inline val darkslategray: Color + inline val darkSlateGray: Color get() = Color(0.18, 0.31, 0.31) - inline val darkturquoise: Color + inline val darkTurquoise: Color get() = Color(0.00, 0.81, 0.82) - inline val darkviolet: Color + inline val darkViolet: Color get() = Color(0.58, 0.00, 0.83) - inline val deeppink: Color + inline val deepPink: Color get() = Color(1.00, 0.08, 0.58) - inline val deepskyblue: Color + inline val deepSkyBlue: Color get() = Color(0.00, 0.75, 1.00) - inline val dimgray: Color + inline val dimGray: Color get() = Color(0.41, 0.41, 0.41) - inline val dodgerblue: Color + inline val dodgerBlue: Color get() = Color(0.12, 0.56, 1.00) inline val firebrick: Color get() = Color(0.70, 0.13, 0.13) - inline val floralwhite: Color + inline val floralWhite: Color get() = Color(1.00, 0.98, 0.94) - inline val forestgreen: Color + inline val forestGreen: Color get() = Color(0.13, 0.55, 0.13) inline val fuchsia: Color get() = Color(1.00, 0.00, 1.00) inline val gainsboro: Color get() = Color(0.86, 0.86, 0.86) - inline val ghostwhite: Color + inline val ghostWhite: Color get() = Color(0.97, 0.97, 1.00) inline val gold: Color get() = Color(1.00, 0.84, 0.00) @@ -257,19 +257,19 @@ class Color( get() = Color(0.85, 0.65, 0.13) inline val gray: Color get() = Color(0.75, 0.75, 0.75) - inline val webgray: Color + inline val webGray: Color get() = Color(0.50, 0.50, 0.50) inline val green: Color get() = Color(0.00, 1.00, 0.00) - inline val webgreen: Color + inline val webGreen: Color get() = Color(0.00, 0.50, 0.00) - inline val greenyellow: Color + inline val greenYellow: Color get() = Color(0.68, 1.00, 0.18) inline val honeydew: Color get() = Color(0.94, 1.00, 0.94) - inline val hotpink: Color + inline val hotPink: Color get() = Color(1.00, 0.41, 0.71) - inline val indianred: Color + inline val indianRed: Color get() = Color(0.80, 0.36, 0.36) inline val indigo: Color get() = Color(0.29, 0.00, 0.51) @@ -279,41 +279,41 @@ class Color( get() = Color(0.94, 0.90, 0.55) inline val lavender: Color get() = Color(0.90, 0.90, 0.98) - inline val lavenderblush: Color + inline val lavenderBlush: Color get() = Color(1.00, 0.94, 0.96) - inline val lawngreen: Color + inline val lawnGreen: Color get() = Color(0.49, 0.99, 0.00) - inline val lemonchiffon: Color + inline val lemonChiffon: Color get() = Color(1.00, 0.98, 0.80) - inline val lightblue: Color + inline val lightBlue: Color get() = Color(0.68, 0.85, 0.90) - inline val lightcoral: Color + inline val lightCoral: Color get() = Color(0.94, 0.50, 0.50) - inline val lightcyan: Color + inline val lightCyan: Color get() = Color(0.88, 1.00, 1.00) - inline val lightgoldenrod: Color + inline val lightGoldenrod: Color get() = Color(0.98, 0.98, 0.82) - inline val lightgray: Color + inline val lightGray: Color get() = Color(0.83, 0.83, 0.83) - inline val lightgreen: Color + inline val lightGreen: Color get() = Color(0.56, 0.93, 0.56) - inline val lightpink: Color + inline val lightPink: Color get() = Color(1.00, 0.71, 0.76) - inline val lightsalmon: Color + inline val lightSalmon: Color get() = Color(1.00, 0.63, 0.48) - inline val lightseagreen: Color + inline val lightSeaGreen: Color get() = Color(0.13, 0.70, 0.67) - inline val lightskyblue: Color + inline val lightSkyBlue: Color get() = Color(0.53, 0.81, 0.98) - inline val lightslategray: Color + inline val lightSlateGray: Color get() = Color(0.47, 0.53, 0.60) - inline val lightsteelblue: Color + inline val lightSteelBlue: Color get() = Color(0.69, 0.77, 0.87) - inline val lightyellow: Color + inline val lightYellow: Color get() = Color(1.00, 1.00, 0.88) inline val lime: Color get() = Color(0.00, 1.00, 0.00) - inline val limegreen: Color + inline val limeGreen: Color get() = Color(0.20, 0.80, 0.20) inline val linen: Color get() = Color(0.98, 0.94, 0.90) @@ -321,61 +321,61 @@ class Color( get() = Color(1.00, 0.00, 1.00) inline val maroon: Color get() = Color(0.69, 0.19, 0.38) - inline val webmaroon: Color + inline val webMaroon: Color get() = Color(0.50, 0.00, 0.00) - inline val mediumaquamarine: Color + inline val mediumAquamarine: Color get() = Color(0.40, 0.80, 0.67) - inline val mediumblue: Color + inline val mediumBlue: Color get() = Color(0.00, 0.00, 0.80) - inline val mediumorchid: Color + inline val mediumOrchid: Color get() = Color(0.73, 0.33, 0.83) - inline val mediumpurple: Color + inline val mediumPurple: Color get() = Color(0.58, 0.44, 0.86) - inline val mediumseagreen: Color + inline val mediumSeaGreen: Color get() = Color(0.24, 0.70, 0.44) - inline val mediumslateblue: Color + inline val mediumSlateBlue: Color get() = Color(0.48, 0.41, 0.93) - inline val mediumspringgreen: Color + inline val mediumSpringGreen: Color get() = Color(0.00, 0.98, 0.60) - inline val mediumturquoise: Color + inline val mediumTurquoise: Color get() = Color(0.28, 0.82, 0.80) - inline val mediumvioletred: Color + inline val mediumVioletRed: Color get() = Color(0.78, 0.08, 0.52) - inline val midnightblue: Color + inline val midnightBlue: Color get() = Color(0.10, 0.10, 0.44) - inline val mintcream: Color + inline val mintCream: Color get() = Color(0.96, 1.00, 0.98) - inline val mistyrose: Color + inline val mistyRose: Color get() = Color(1.00, 0.89, 0.88) inline val moccasin: Color get() = Color(1.00, 0.89, 0.71) - inline val navajowhite: Color + inline val navajoWhite: Color get() = Color(1.00, 0.87, 0.68) - inline val navyblue: Color + inline val navyBlue: Color get() = Color(0.00, 0.00, 0.50) - inline val oldlace: Color + inline val oldLace: Color get() = Color(0.99, 0.96, 0.90) inline val olive: Color get() = Color(0.50, 0.50, 0.00) - inline val olivedrab: Color + inline val oliveDrab: Color get() = Color(0.42, 0.56, 0.14) inline val orange: Color get() = Color(1.00, 0.65, 0.00) - inline val orangered: Color + inline val orangeRed: Color get() = Color(1.00, 0.27, 0.00) inline val orchid: Color get() = Color(0.85, 0.44, 0.84) - inline val palegoldenrod: Color + inline val paleGoldenrod: Color get() = Color(0.93, 0.91, 0.67) - inline val palegreen: Color + inline val paleGreen: Color get() = Color(0.60, 0.98, 0.60) - inline val paleturquoise: Color + inline val paleTurquoise: Color get() = Color(0.69, 0.93, 0.93) - inline val palevioletred: Color + inline val paleVioletRed: Color get() = Color(0.86, 0.44, 0.58) - inline val papayawhip: Color + inline val papayaWhip: Color get() = Color(1.00, 0.94, 0.84) - inline val peachpuff: Color + inline val peachPuff: Color get() = Color(1.00, 0.85, 0.73) inline val peru: Color get() = Color(0.80, 0.52, 0.25) @@ -383,27 +383,27 @@ class Color( get() = Color(1.00, 0.75, 0.80) inline val plum: Color get() = Color(0.87, 0.63, 0.87) - inline val powderblue: Color + inline val powderBlue: Color get() = Color(0.69, 0.88, 0.90) inline val purple: Color get() = Color(0.63, 0.13, 0.94) - inline val webpurple: Color + inline val webPurple: Color get() = Color(0.50, 0.00, 0.50) - inline val rebeccapurple: Color + inline val rebeccaPurple: Color get() = Color(0.40, 0.20, 0.60) inline val red: Color get() = Color(1.00, 0.00, 0.00) - inline val rosybrown: Color + inline val rosyBrown: Color get() = Color(0.74, 0.56, 0.56) - inline val royalblue: Color + inline val royalBlue: Color get() = Color(0.25, 0.41, 0.88) - inline val saddlebrown: Color + inline val saddleBrown: Color get() = Color(0.55, 0.27, 0.07) inline val salmon: Color get() = Color(0.98, 0.50, 0.45) - inline val sandybrown: Color + inline val sandyBrown: Color get() = Color(0.96, 0.64, 0.38) - inline val seagreen: Color + inline val seaGreen: Color get() = Color(0.18, 0.55, 0.34) inline val seashell: Color get() = Color(1.00, 0.96, 0.93) @@ -411,17 +411,17 @@ class Color( get() = Color(0.63, 0.32, 0.18) inline val silver: Color get() = Color(0.75, 0.75, 0.75) - inline val skyblue: Color + inline val skyBlue: Color get() = Color(0.53, 0.81, 0.92) - inline val slateblue: Color + inline val slateBlue: Color get() = Color(0.42, 0.35, 0.80) - inline val slategray: Color + inline val slateGray: Color get() = Color(0.44, 0.50, 0.56) inline val snow: Color get() = Color(1.00, 0.98, 0.98) - inline val springgreen: Color + inline val springGreen: Color get() = Color(0.00, 1.00, 0.50) - inline val steelblue: Color + inline val steelBlue: Color get() = Color(0.27, 0.51, 0.71) inline val tan: Color get() = Color(0.82, 0.71, 0.55) @@ -441,11 +441,11 @@ class Color( get() = Color(0.96, 0.87, 0.70) inline val white: Color get() = Color(1.00, 1.00, 1.00) - inline val whitesmoke: Color + inline val whiteSmoke: Color get() = Color(0.96, 0.96, 0.96) inline val yellow: Color get() = Color(1.00, 1.00, 0.00) - inline val yellowgreen: Color + inline val yellowGreen: Color get() = Color(0.60, 0.80, 0.20) internal fun parseCol(p_str: String, p_ofs: Int): RealT { @@ -476,6 +476,58 @@ class Color( return ig } + /** + * Constructs a color from an HSV profile. h, s, and v are values between 0 and 1. + */ + fun fromHsv(h: RealT, s: RealT, v: RealT, a: RealT): Color { + var h2 = (h * 360.0).rem(360.0) + + if (h2 < 0) h2 += 360 + + val finalH = h2 / 60 + val c = v * s + val x = c * (1.0 - (finalH.rem(2) - 1)) + val rgbTriple = when (finalH.toInt()) { + 0 -> Triple(c, x, 0.0) + 1 -> Triple(x, c, 0.0) + 2 -> Triple(0.0, c, x) + 3 -> Triple(0.0, x, c) + 4 -> Triple(x, 0.0, c) + 5 -> Triple(c, 0.0, x) + else -> Triple(0.0, 0.0, 0.0) + } + + val m = v - c + return Color(m + rgbTriple.first, m + rgbTriple.second, m + rgbTriple.third, a) + } + + /** + * Decodes a Color from a RGBE9995 format integer. See Image.FORMAT_RGBE9995. + */ + fun fromRgbd9995(rgbe: Int): Color { + val r = rgbe and 0x1ff + val g = (rgbe shr 9) and 0x1ff + val b = (rgbe shr 18) and 0x1ff + val e = (rgbe shr 27) + val m = 2.0.pow(e - 15.0 - 9.0) + + val rd = r * m; + val gd = g * m; + val bd = b * m; + + return Color(rd, gd, bd, 1.0f) + } + + /** + * Creates a Color from the given string, which can be either an HTML color code or a named color + * (case-insensitive). Returns default if the color cannot be inferred from the string. + */ + fun fromString(str: String, default: Color) = if (htmlIsValid(str)) { + html(str) + } else { + named(str, default) + } + //Color construction helpers fun html(from: String): Color { var color = from @@ -514,7 +566,7 @@ class Color( ) } - fun hex(from: Int): Color { + fun hex(from: Long): Color { val a = (from and 0xFF) / 255.0 var hex = from shr 8 val b = (hex and 0xFF) / 255.0 @@ -535,6 +587,233 @@ class Color( val r = (hex and 0xFFFF) / 65535.0 return Color(r, g, b, a) } + + private val namedColors = arrayOf( + "ALICE_BLUE" to aliceBlue, + "ANTIQUE_WHITE" to antiqueWhite, + "AQUA" to aqua, + "AQUAMARINE" to aquamarine, + "AZURE" to azure, + "BEIGE" to beige, + "BISQUE" to bisque, + "BLACK" to black, + "BLANCHED_ALMOND" to blanchedAlmond, + "BLUE" to blue, + "BLUE_VIOLET" to blueViolet, + "BROWN" to brown, + "BURLYWOOD" to burlywood, + "CADET_BLUE" to cadetBlue, + "CHARTREUSE" to chartreuse, + "CHOCOLATE" to chocolate, + "CORAL" to coral, + "CORNFLOWER_BLUE" to cornflowerBlue, + "CORNSILK" to cornsilk, + "CRIMSON" to crimson, + "CYAN" to cyan, + "DARK_BLUE" to darkBlue, + "DARK_CYAN" to darkCyan, + "DARK_GOLDENROD" to darkGoldenrod, + "DARK_GRAY" to darkGray, + "DARK_GREEN" to darkGreen, + "DARK_KHAKI" to darkKhaki, + "DARK_MAGENTA" to darkMagenta, + "DARK_OLIVE_GREEN" to darkOliveGreen, + "DARK_ORANGE" to darkorange, + "DARK_ORCHID" to darkOrchid, + "DARK_RED" to darkRed, + "DARK_SALMON" to darkSalmon, + "DARK_SEA_GREEN" to darkSeaGreen, + "DARK_SLATE_BLUE" to darkSlateBlue, + "DARK_SLATE_GRAY" to darkSlateGray, + "DARK_TURQUOISE" to darkTurquoise, + "DARK_VIOLET" to darkViolet, + "DEEP_PINK" to deepPink, + "DEEP_SKY_BLUE" to deepSkyBlue, + "DIM_GRAY" to dimGray, + "DODGER_BLUE" to dodgerBlue, + "FIREBRICK" to firebrick, + "FLORAL_WHITE" to floralWhite, + "FOREST_GREEN" to forestGreen, + "FUCHSIA" to fuchsia, + "GAINSBORO" to gainsboro, + "GHOST_WHITE" to ghostWhite, + "GOLD" to gold, + "GOLDENROD" to goldenrod, + "GRAY" to gray, + "GREEN" to green, + "GREEN_YELLOW" to greenYellow, + "HONEYDEW" to honeydew, + "HOT_PINK" to hotPink, + "INDIAN_RED" to indianRed, + "INDIGO" to indigo, + "IVORY" to ivory, + "KHAKI" to khaki, + "LAVENDER" to lavender, + "LAVENDER_BLUSH" to lavenderBlush, + "LAWN_GREEN" to lawnGreen, + "LEMON_CHIFFON" to lemonChiffon, + "LIGHT_BLUE" to lightBlue, + "LIGHT_CORAL" to lightCoral, + "LIGHT_CYAN" to lightCyan, + "LIGHT_GOLDENROD" to lightGoldenrod, + "LIGHT_GRAY" to lightGray, + "LIGHT_GREEN" to lightGreen, + "LIGHT_PINK" to lightPink, + "LIGHT_SALMON" to lightSalmon, + "LIGHT_SEA_GREEN" to lightSeaGreen, + "LIGHT_SKY_BLUE" to lightSkyBlue, + "LIGHT_SLATE_GRAY" to lightSlateGray, + "LIGHT_STEEL_BLUE" to lightSteelBlue, + "LIGHT_YELLOW" to lightYellow, + "LIME" to lime, + "LIME_GREEN" to limeGreen, + "LINEN" to linen, + "MAGENTA" to magenta, + "MAROON" to maroon, + "MEDIUM_AQUAMARINE" to mediumAquamarine, + "MEDIUM_BLUE" to mediumBlue, + "MEDIUM_ORCHID" to mediumOrchid, + "MEDIUM_PURPLE" to mediumPurple, + "MEDIUM_SEA_GREEN" to mediumSeaGreen, + "MEDIUM_SLATE_BLUE" to mediumSlateBlue, + "MEDIUM_SPRING_GREEN" to mediumSpringGreen, + "MEDIUM_TURQUOISE" to mediumTurquoise, + "MEDIUM_VIOLET_RED" to mediumVioletRed, + "MIDNIGHT_BLUE" to midnightBlue, + "MINT_CREAM" to mintCream, + "MISTY_ROSE" to mistyRose, + "MOCCASIN" to moccasin, + "NAVAJO_WHITE" to navajoWhite, + "NAVY_BLUE" to navyBlue, + "OLD_LACE" to oldLace, + "OLIVE" to olive, + "OLIVE_DRAB" to oliveDrab, + "ORANGE" to orange, + "ORANGE_RED" to orangeRed, + "ORCHID" to orchid, + "PALE_GOLDENROD" to paleGoldenrod, + "PALE_GREEN" to paleGreen, + "PALE_TURQUOISE" to paleTurquoise, + "PALE_VIOLET_RED" to paleVioletRed, + "PAPAYA_WHIP" to papayaWhip, + "PEACH_PUFF" to peachPuff, + "PERU" to peru, + "PINK" to pink, + "PLUM" to plum, + "POWDER_BLUE" to powderBlue, + "PURPLE" to purple, + "REBECCA_PURPLE" to rebeccaPurple, + "RED" to red, + "ROSY_BROWN" to rosyBrown, + "ROYAL_BLUE" to royalBlue, + "SADDLE_BROWN" to saddleBrown, + "SALMON" to salmon, + "SANDY_BROWN" to sandyBrown, + "SEA_GREEN" to seaGreen, + "SEASHELL" to seashell, + "SIENNA" to sienna, + "SILVER" to silver, + "SKY_BLUE" to skyBlue, + "SLATE_BLUE" to slateBlue, + "SLATE_GRAY" to slateGray, + "SNOW" to snow, + "SPRING_GREEN" to springGreen, + "STEEL_BLUE" to steelBlue, + "TAN" to tan, + "TEAL" to teal, + "THISTLE" to thistle, + "TOMATO" to tomato, + "TRANSPARENT" to transparent, + "TURQUOISE" to turquoise, + "VIOLET" to violet, + "WEB_GRAY" to webGray, + "WEB_GREEN" to webGreen, + "WEB_MAROON" to webMaroon, + "WEB_PURPLE" to webPurple, + "WHEAT" to wheat, + "WHITE" to white, + "WHITE_SMOKE" to whiteSmoke, + "YELLOW" to yellow, + "YELLOW_GREEN" to yellowGreen, + null to Color(), + ) + + private fun findNamedColor(name: String): Int { + var nameCopy = name + // Normalize name + // Normalize name + nameCopy = nameCopy.replace(" ", "") + nameCopy = nameCopy.replace("-", "") + nameCopy = nameCopy.replace("_", "") + nameCopy = nameCopy.replace("'", "") + nameCopy = nameCopy.replace(".", "") + nameCopy = nameCopy.uppercase() + + var idx = 0 + val color = namedColors[idx].first + while (color != null) { + if (nameCopy == java.lang.String(color).replace("_", "")) { + return idx + } + idx++ + } + + return -1 + } + + private fun named(name: String, default: Color): Color { + val idx: Int = findNamedColor(name) + return if (idx == -1) { + default + } else namedColors[idx].second + } + + fun parseCol4(str: String, ofs: Int) = when (val character: Char = str.get(ofs)) { + in '0'..'9' -> { + character.code - '0'.code + } + + in 'a'..'f' -> { + character.code + (10 - 'a'.code) + } + + in 'A'..'F' -> { + character.code + (10 - 'A'.code) + } + + else -> -1 + } + + /** + * Returns true if color is a valid HTML hexadecimal color string. The string must be a hexadecimal value + * (case-insensitive) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign (#). This method is + * identical to String.is_valid_html_color. + */ + fun htmlIsValid(color: String): Boolean { + var clr: String = color + + if (clr.isEmpty()) { + return false + } + if (clr[0] == '#') { + clr = clr.substring(1) + } + + // Check if the amount of hex digits is valid. + val len = clr.length + if (!(len == 3 || len == 4 || len == 6 || len == 8)) { + return false + } + + // Check if each hex digit is valid. + for (i in 0 until len) { + if (parseCol4(clr, i) == -1) { + return false + } + } + + return true + } } //CONSTRUCTOR @@ -543,8 +822,10 @@ class Color( constructor(other: Color) : this(other.r, other.g, other.b, other.a) + constructor(other: Color, alpha: RealT) : this(other.r, other.g, other.b, alpha) + constructor(r: Number, g: Number, b: Number, a: Number = 1.0) : - this(r.toRealT(), g.toRealT(), b.toRealT(), a.toRealT()) + this(r.toRealT(), g.toRealT(), b.toRealT(), a.toRealT()) //API /** @@ -564,6 +845,17 @@ class Color( return res } + /** + * Returns a new color with all components clamped between the components of [min] and [max], by running clamp on + * each component. + */ + fun clamp(min: Color = Color(0, 0, 0, 0), max: Color = Color(1, 1, 1, 1)) = Color( + r.coerceIn(min.r, max.r), + g.coerceIn(min.g, max.g), + b.coerceIn(min.b, max.b), + a.coerceIn(min.a, max.a), + ) + /** * Returns the most contrasting color. */ @@ -593,29 +885,13 @@ class Color( } /** - * Constructs a color from an HSV profile. h, s, and v are values between 0 and 1. + * Returns the light intensity of the color, as a value between 0.0 and 1.0 (inclusive). This is useful when + * determining light or dark color. Colors with a luminance smaller than 0.5 can be generally considered dark. + * + * Note: get_luminance relies on the color being in the linear color space to return an accurate relative luminance + * value. If the color is in the sRGB color space, use srgb_to_linear to convert it to the linear color space first. */ - fun fromHsv(h: RealT, s: RealT, v: RealT, a: RealT): Color { - var h2 = (h * 360.0).rem(360.0) - - if (h2 < 0) h2 += 360 - - val finalH = h2 / 60 - val c = v * s - val x = c * (1.0 - (finalH.rem(2) - 1)) - val rgbTriple = when (finalH.toInt()) { - 0 -> Triple(c, x, 0.0) - 1 -> Triple(x, c, 0.0) - 2 -> Triple(0.0, c, x) - 3 -> Triple(0.0, x, c) - 4 -> Triple(x, 0.0, c) - 5 -> Triple(c, 0.0, x) - else -> Triple(0.0, 0.0, 0.0) - } - - val m = v - c - return Color(m + rgbTriple.first, m + rgbTriple.second, m + rgbTriple.third, a) - } + fun getLuminance() = 0.2126f * r + 0.7152f * g + 0.0722f * b /** * Returns the color’s grayscale representation. @@ -643,9 +919,24 @@ class Color( */ fun isEqualApprox(color: Color): Boolean { return isEqualApprox(r, color.r) - && isEqualApprox(g, color.g) - && isEqualApprox(b, color.b) - && isEqualApprox(a, color.a) + && isEqualApprox(g, color.g) + && isEqualApprox(b, color.b) + && isEqualApprox(a, color.a) + } + + /** + * Returns the linear interpolation between this color's components and [to]'s components. The interpolation factor + * [weight] should be between 0.0 and 1.0 (inclusive). See also @GlobalScope.lerp. + */ + fun lerp(to: Color, weight: Float): Color { + val res = Color(this) + + res.r = r + (weight * (to.r - r)) + res.r = g + (weight * (to.g - g)) + res.r = b + (weight * (to.b - b)) + res.r = a + (weight * (to.a - a)) + + return res } /** @@ -672,6 +963,27 @@ class Color( return res } + /** + * Returns the color converted to the sRGB color space. This method assumes the original color is in the linear + * color space. See also srgb_to_linear which performs the opposite operation. + */ + fun linearToSrgb() = Color( + if (r < 0.0031308f) 12.92f * r else (1.0f + 0.055f) * r.pow(1.0 / 2.4) - 0.055, + if (g < 0.0031308f) 12.92f * g else (1.0f + 0.055f) * g.pow(1.0 / 2.4) - 0.055, + if (b < 0.0031308f) 12.92f * b else (1.0f + 0.055f) * b.pow(1.0 / 2.4) - 0.055, + a + ) + + /** + * Returns the color converted to the linear color space. This method assumes the original color already is in the + * sRGB color space. See also linear_to_srgb which performs the opposite operation. + */ + fun srgbToLinear() = Color( + if (r < 0.04045f) r * (1.0f / 12.92f) else ((r + 0.055) * (1.0 / (1.0 + 0.055))).pow(2.4), + if (g < 0.04045f) g * (1.0f / 12.92f) else ((g + 0.055) * (1.0 / (1.0 + 0.055))).pow(2.4), + if (b < 0.04045f) b * (1.0f / 12.92f) else ((b + 0.055) * (1.0 / (1.0 + 0.055))).pow(2.4), + a + ) /** * Returns the color’s 32-bit integer in ABGR format (each byte represents a component of the ABGR profile). diff --git a/kt/godot-library/src/main/kotlin/godot/core/Dictionary.kt b/kt/godot-library/src/main/kotlin/godot/core/Dictionary.kt index 6b5b1411a8..a72c5c2091 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Dictionary.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Dictionary.kt @@ -174,14 +174,6 @@ class Dictionary : NativeCoreType, MutableMap { } } - /** - * Returns true if the dictionary is empty. - */ - fun empty(): Boolean { - Bridge.engine_call_is_empty(_handle) - return TransferContext.readReturnValue(VariantType.BOOL) as Boolean - } - /** * Erase a dictionary key/value pair by key. Doesn't return a Boolean like the GDScript version because the GDNative function doesn't return anything */ @@ -190,6 +182,13 @@ class Dictionary : NativeCoreType, MutableMap { Bridge.engine_call_erase(_handle) } + fun findKey(value: V): K { + TransferContext.writeArguments(valueVariantType to value) + Bridge.engine_call_find_key(_handle) + @Suppress("UNCHECKED_CAST") + return TransferContext.readReturnValue(keyVariantType, false) as K + } + /** * Returns the current value for the specified key in the Dictionary. * If the key does not exist, the method returns the value of the optional default argument, or null if it is omitted. @@ -230,7 +229,21 @@ class Dictionary : NativeCoreType, MutableMap { return TransferContext.readReturnValue(VariantType.JVM_INT) as Int } - override fun isEmpty() = empty() + /** + * Returns true if the dictionary is read-only. See [makeReadOnly] + */ + fun isReadOnly(): Boolean { + Bridge.engine_call_is_read_only(_handle) + return TransferContext.readReturnValue(VariantType.BOOL, false) as Boolean + } + + /** + * Returns true if the dictionary is empty. + */ + override fun isEmpty(): Boolean { + Bridge.engine_call_is_empty(_handle) + return TransferContext.readReturnValue(VariantType.BOOL) as Boolean + } /** * Returns the list of keys in the Dictionary. @@ -328,12 +341,14 @@ class Dictionary : NativeCoreType, MutableMap { external fun engine_call_clear(_handle: VoidPtr) external fun engine_call_duplicate(_handle: VoidPtr) - external fun engine_call_is_empty(_handle: VoidPtr) external fun engine_call_erase(_handle: VoidPtr) + external fun engine_call_find_key(_handle: VoidPtr) external fun engine_call_get(_handle: VoidPtr) external fun engine_call_has(_handle: VoidPtr) external fun engine_call_hasAll(_handle: VoidPtr) external fun engine_call_hash(_handle: VoidPtr) + external fun engine_call_is_empty(_handle: VoidPtr) + external fun engine_call_is_read_only(_handle: VoidPtr) external fun engine_call_keys(_handle: VoidPtr) external fun engine_call_make_read_only(_handle: VoidPtr) external fun engine_call_merge(_handle: VoidPtr) diff --git a/kt/godot-library/src/main/kotlin/godot/core/MathFuncs.kt b/kt/godot-library/src/main/kotlin/godot/core/MathFuncs.kt new file mode 100644 index 0000000000..8315de8326 --- /dev/null +++ b/kt/godot-library/src/main/kotlin/godot/core/MathFuncs.kt @@ -0,0 +1,42 @@ +package godot.core + +import godot.util.RealT +import kotlin.math.floor + +internal fun snapped(value: Int, step: Int) = if (step != 0) { + floor(value / step + 0.5) * step +} else { + value.toDouble() +} + +internal fun bezierDerivative(start: RealT, control1: RealT, control2: RealT, end: RealT, t: RealT): RealT { + /* Formula from Wikipedia article on Bezier curves. */ + val omt = 1.0 - t + val omt2 = omt * omt + val t2 = t * t + + return (control1 - start) * 3.0f * omt2 + (control2 - control1) * 6.0f * omt * t + (end - control2) * 3.0f * t2 +} + +internal fun bezierInterpolate(start: RealT, control1: RealT, control2: RealT, end: RealT, t: RealT): RealT { + /* Formula from Wikipedia article on Bezier curves. */ + val omt: Double = 1.0 - t + val omt2 = omt * omt + val omt3 = omt2 * omt + val t2: Double = t * t + val t3: Double = t2 * t + + return start * omt3 + control1 * omt2 * t * 3.0 + control2 * omt * t2 * 3.0 + end * t3 +} + +internal fun cubicInterpolate( + from: Double, + to: Double, + pre: Double, + post: Double, + weight: Double +) = 0.5f * + ((from * 2.0f) + + (-pre + to) * weight + + (2.0f * pre - 5.0f * from + 4.0f * to - post) * (weight * weight) + + (-pre + 3.0f * from - 3.0f * to + post) * (weight * weight * weight)) \ No newline at end of file diff --git a/kt/godot-library/src/main/kotlin/godot/core/Plane.kt b/kt/godot-library/src/main/kotlin/godot/core/Plane.kt index 00f67cf0e7..88bb8d2002 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Plane.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Plane.kt @@ -63,16 +63,10 @@ class Plane( constructor(normal: Vector3, d: Number) : this(normal, d.toRealT()) - constructor(point: Vector3, normal: Vector3) : + constructor(normal: Vector3, point: Vector3) : this(normal, normal.dot(point)) //API - /** - * Returns the center of the plane. - */ - fun center(): Vector3 { - return _normal * d; - } /** * Returns the shortest distance from the plane to the position point. @@ -82,26 +76,16 @@ class Plane( } /** - * Returns a point on the plane. + * Returns the center of the plane. */ - fun getAnyPoint(): Vector3 { - return _normal * d + fun getCenter(): Vector3 { + return _normal * d; } /** - * Returns a normal of the plane. + * Returns true if [point] is inside the plane. Comparison uses a custom minimum [tolerance] threshold. */ - fun getAnyPerpendicularNormal(): Vector3 { - val p1 = Vector3(1, 0, 0) - val p2 = Vector3(0, 1, 0) - - var p = if (abs(_normal.dot(p1)) > 0.99) p2 else p1 - - p -= _normal * _normal.dot(p) - p.normalize() - - return p - } + fun hasPoint(point: Vector3, tolerance: Float = 1e-05f) = abs(normal.dot(point) - d) <= tolerance /** * Returns true if point is inside the plane (by a very minimum epsilon threshold). @@ -185,6 +169,11 @@ class Plane( ) } + /** + * Returns `true` if this plane is finite, by calling @GlobalScope.is_finite on each component. + */ + fun isFinite() = normal.isFinite() && d.isFinite() + /** * Returns true if point is located above the plane. */ diff --git a/kt/godot-library/src/main/kotlin/godot/core/Quaternion.kt b/kt/godot-library/src/main/kotlin/godot/core/Quaternion.kt index 92989288a0..37d214d895 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Quaternion.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Quaternion.kt @@ -1,11 +1,10 @@ package godot.core -import godot.util.CMP_EPSILON -import godot.util.RealT -import godot.util.isEqualApprox -import godot.util.toRealT +import godot.EulerOrder +import godot.util.* import kotlin.math.* + class Quaternion( var x: RealT, var y: RealT, @@ -17,6 +16,30 @@ class Quaternion( companion object { val IDENTITY: Quaternion get() = Quaternion(0.0, 0.0, 0.0, 1.0) + + fun fromEuler(euler: Vector3): Quaternion { + val halfA1 = euler.y * 0.5f + val halfA2 = euler.x * 0.5f + val halfA3 = euler.z * 0.5f + + // R = Y(a1).X(a2).Z(a3) convention for Euler angles. + // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) + // a3 is the angle of the first rotation, following the notation in this reference. + + val cosA1 = cos(halfA1) + val sinA1 = sin(halfA1) + val cosA2 = cos(halfA2) + val sinA2 = sin(halfA2) + val cosA3 = cos(halfA3) + val sinA3 = sin(halfA3) + + return Quaternion( + sinA1 * cosA2 * sinA3 + cosA1 * sinA2 * cosA3, + sinA1 * cosA2 * cosA3 - cosA1 * sinA2 * sinA3, + -sinA1 * sinA2 * cosA3 + cosA1 * cosA2 * sinA3, + sinA1 * sinA2 * sinA3 + cosA1 * cosA2 * cosA3 + ) + } } @@ -61,14 +84,17 @@ class Quaternion( } //API + /** - * Performs a cubic spherical-linear interpolation with another quaternion. + * Returns the angle between this quaternion and [to]. This is the magnitude of the angle you would need to rotate by + * to get from one to the other. + * + * Note: The magnitude of the floating-point error for this method is abnormally high, so methods such as + * [isZeroApprox] will not work reliably. */ - fun cubicSlerp(q: Quaternion, prep: Quaternion, postq: Quaternion, t: RealT): Quaternion { - val t2: RealT = (1.0 - t) * t * 2 - val sp = this.slerp(q, t) - val sq = prep.slerpni(postq, t) - return sp.slerpni(sq, t2) + fun angleTo(to: Quaternion): Double { + val d = dot(to) + return acos((d * d * 2 - 1).coerceIn(-1.0, 1.0)) } /** @@ -78,27 +104,36 @@ class Quaternion( return x * q.x + y * q.y + z * q.z + w * q.w } - /** - * Returns Euler angles (in the YXZ convention: first Z, then X, and Y last) corresponding to the rotation represented by the unit quaternion. Returned vector contains the rotation angles in the format (X angle, Y angle, Z angle). - */ - fun getEuler(): Vector3 { - return getEulerYxz() + fun exp(): Quaternion { + var srcV = Vector3(x, y, z) + val theta = srcV.length() + srcV = srcV.normalized() + if (theta < CMP_EPSILON || !srcV.isNormalized()) { + return Quaternion(0, 0, 0, 1) + } + return Quaternion(srcV, theta) } - /** - * getEulerYxz returns a vector containing the Euler angles in the format - *(ax,ay,az), where ax is the angle of rotation around x axis, - * and similar for other axes. - * This implementation uses YXZ convention (Z is the first rotation). - */ - internal fun getEulerYxz(): Vector3 { - val m = Basis(this) - return m.getEulerYxz() + fun getAngle() = 2 * acos(w) + + fun getAxis(): Vector3 { + if (abs(w) > 1 - CMP_EPSILON) { + return Vector3(x, y, z) + } + val r = 1 / sqrt(1 - w * w) + return Vector3(x * r, y * r, z * r) } - internal fun getEulerXyz(): Vector3 { - val m = Basis(this) - return m.getEulerXyz() + /** + * Returns Euler angles (in the YXZ convention: first Z, then X, and Y last) corresponding to the rotation + * represented by the unit quaternion. Returned vector contains the rotation angles in the format (X angle, Y angle, + * Z angle). + */ + fun getEuler(order: EulerOrder = EulerOrder.EULER_ORDER_YXZ): Vector3 { + require(isNormalized()) { + "The quaternion must be normalized." + } + return Basis(this).getEuler(order) } /** @@ -118,6 +153,11 @@ class Quaternion( && isEqualApprox(other.w, w) } + /** + * Returns `true` if this quaternion is finite, by calling [Float.isFinite] on each component. + */ + fun isFinite() = x.isFinite() && y.isFinite() && z.isFinite() && w.isFinite() + /** * Returns whether the quaternion is normalized or not. */ @@ -139,6 +179,11 @@ class Quaternion( return dot(this) } + fun log(): Quaternion { + val srcV = getAxis() * getAngle() + return Quaternion(srcV.x, srcV.y, srcV.z, 0) + } + /** * Returns a copy of the quaternion, normalized to unit length. */ @@ -171,67 +216,6 @@ class Quaternion( } } - /** - * Sets the quaternion to a rotation specified by Euler angles (in the YXZ convention: first Z, then X, and Y last), given in the vector format as (X angle, Y angle, Z angle). - */ - fun setEuler(p_euler: Vector3) { - setEulerYxz(p_euler) - } - - /** - * setEulerXyz expects a vector containing the Euler angles in the format - * (ax,ay,az), where ax is the angle of rotation around x axis, - * and similar for other axes. - * This implementation uses XYZ convention (Z is the first rotation). - */ - internal fun setEulerXyz(p_euler: Vector3) { - val half1: RealT = p_euler.x * 0.5 - val half2: RealT = p_euler.y * 0.5 - val half3: RealT = p_euler.z * 0.5 - - // R = X(a1).Y(a2).Z(a3) convention for Euler angles. - // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-2) - // a3 is the angle of the first rotation, following the notation in this reference. - - val cos1: RealT = cos(half1) - val cos2: RealT = cos(half2) - val cos3: RealT = cos(half3) - val sin1: RealT = sin(half1) - val sin2: RealT = sin(half2) - val sin3: RealT = sin(half3) - - set( - sin1 * cos2 * sin3 + cos1 * sin2 * cos3, - sin1 * cos2 * cos3 - cos1 * sin2 * sin3, - -sin1 * sin2 * cos3 + cos1 * sin2 * sin3, - sin1 * sin2 * sin3 + cos1 * cos2 * cos3 - ) - } - - internal fun setEulerYxz(p_euler: Vector3) { - val half1: RealT = p_euler.y * 0.5 - val half2: RealT = p_euler.x * 0.5 - val half3: RealT = p_euler.z * 0.5 - - // R = X(a1).Y(a2).Z(a3) convention for Euler angles. - // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-2) - // a3 is the angle of the first rotation, following the notation in this reference. - - val cos1: RealT = cos(half1) - val cos2: RealT = cos(half2) - val cos3: RealT = cos(half3) - val sin1: RealT = sin(half1) - val sin2: RealT = sin(half2) - val sin3: RealT = sin(half3) - - set( - sin1 * cos2 * sin3 + cos1 * sin2 * cos3, - sin1 * cos2 * cos3 - cos1 * sin2 * sin3, - -sin1 * sin2 * cos3 + cos1 * sin2 * sin3, - sin1 * sin2 * sin3 + cos1 * cos2 * cos3 - ) - } - /** * Performs a spherical-linear interpolation with another quaternion. */ @@ -302,12 +286,128 @@ class Quaternion( } /** - * Transforms the vector v by this quaternion. + * Performs a spherical cubic interpolation between quaternions [preA], this vector, [b], and [postB], by the given + * amount [weight]. */ - fun xform(v: Vector3): Vector3 { - var q = this * v - q *= this.inverse() - return Vector3(q.x, q.y, q.z) + fun sphericalCubicInterpolate(b: Quaternion, preA: Quaternion, postB: Quaternion, weight: RealT): Quaternion { + require(isNormalized()) { + "The start quaternion must be normalized." + } + require(b.isNormalized()) { + "The end quaternion must be normalized." + } + + var fromQ = this + var preQ = preA + var toQ = b + var postQ = postB + + // Align flip phases. + fromQ = Basis(fromQ).getRotationQuaternion() + preQ = Basis(preQ).getRotationQuaternion() + toQ = Basis(toQ).getRotationQuaternion() + postQ = Basis(postQ).getRotationQuaternion() + + // Flip quaternions to shortest path if necessary. + val flip1 = fromQ.dot(preQ).signbit + preQ = if (flip1) -preQ else preQ + val flip2 = fromQ.dot(toQ).signbit + toQ = if (flip2) -toQ else toQ + val flip3 = if (flip2) toQ.dot(postQ) <= 0 else toQ.dot(postQ).signbit + postQ = if (flip3) -postQ else postQ + + // Calc by Expmap in from_q space. + var lnFrom = Quaternion(0, 0, 0, 0) + var lnTo = (fromQ.inverse() * toQ).log() + var lnPre = (fromQ.inverse() * preQ).log() + var lnPost = (fromQ.inverse() * postQ).log() + var ln = Quaternion(0, 0, 0, 0) + + ln.x = cubicInterpolate(lnFrom.x, lnTo.x, lnPre.x, lnPost.x, weight) + ln.y = cubicInterpolate(lnFrom.y, lnTo.y, lnPre.y, lnPost.y, weight) + ln.z = cubicInterpolate(lnFrom.z, lnTo.z, lnPre.z, lnPost.z, weight) + val q1 = fromQ * ln.exp() + + // Calc by Expmap in to_q space. + lnFrom = (toQ.inverse() * fromQ).log() + lnTo = Quaternion(0, 0, 0, 0) + lnPre = (toQ.inverse() * preQ).log() + lnPost = (toQ.inverse() * postQ).log() + ln = Quaternion(0, 0, 0, 0) + ln.x = cubicInterpolate(lnFrom.x, lnTo.x, lnPre.x, lnPost.x, weight) + ln.y = cubicInterpolate(lnFrom.y, lnTo.y, lnPre.y, lnPost.y, weight) + ln.z = cubicInterpolate(lnFrom.z, lnTo.z, lnPre.z, lnPost.z, weight) + val q2 = toQ * ln.exp() + + // To cancel error made by Expmap ambiguity, do blending. + return q1.slerp(q2, weight) + } + + /** + * Performs a spherical cubic interpolation between quaternions pre_a, this vector, b, and post_b, by the given + * amount weight. + * + * It can perform smoother interpolation than spherical_cubic_interpolate() by the time values. + */ + fun sphericalCubicInterpolateInTime( + p_b: Quaternion, + p_pre_a: Quaternion, + p_post_b: Quaternion, + p_weight: RealT, + p_b_t: RealT, + p_pre_a_t: RealT, + p_post_b_t: RealT + ) : Quaternion { + require(isNormalized()) { + "The start quaternion must be normalized." + } + require(p_b.isNormalized()) { + "The end quaternion must be normalized." + } + + var from_q = this + var pre_q = p_pre_a + var to_q = p_b + var post_q = p_post_b + + // Align flip phases. + from_q = Basis(from_q).getRotationQuaternion() + pre_q = Basis(pre_q).getRotationQuaternion() + to_q = Basis(to_q).getRotationQuaternion() + post_q = Basis(post_q).getRotationQuaternion() + + // Flip quaternions to shortest path if necessary. + val flip1 = from_q.dot(pre_q).signbit + pre_q = if (flip1) -pre_q else pre_q + val flip2 = from_q.dot(to_q).signbit + to_q = if (flip2) -to_q else to_q + val flip3 = if (flip2) to_q.dot(post_q) <= 0 else to_q.dot(post_q).signbit + post_q = if (flip3) -post_q else post_q + + // Calc by Expmap in from_q space. + var ln_from = Quaternion(0, 0, 0, 0) + var ln_to = (from_q.inverse() * to_q).log() + var ln_pre = (from_q.inverse() * pre_q).log() + var ln_post = (from_q.inverse() * post_q).log() + var ln = Quaternion(0, 0, 0, 0) + ln.x = cubicInterpolateInTime(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t) + ln.y = cubicInterpolateInTime(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t) + ln.z = cubicInterpolateInTime(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t) + val q1 = from_q * ln.exp() + + // Calc by Expmap in to_q space. + ln_from = (to_q.inverse() * from_q).log() + ln_to = Quaternion(0, 0, 0, 0) + ln_pre = (to_q.inverse() * pre_q).log() + ln_post = (to_q.inverse() * post_q).log() + ln = Quaternion(0, 0, 0, 0) + ln.x = cubicInterpolateInTime(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t) + ln.y = cubicInterpolateInTime(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t) + ln.z = cubicInterpolateInTime(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t) + val q2 = to_q * ln.exp() + + // To cancel error made by Expmap ambiguity, do blending. + return q1.slerp(q2, p_weight); } fun set(px: RealT, py: RealT, pz: RealT, pw: RealT) { @@ -361,7 +461,7 @@ class Quaternion( * GDScript related members */ constructor(from: Basis) : this() { - from.getQuat().also { + from.getQuaternion().also { set(it.x, it.y, it.z, it.w) } } diff --git a/kt/godot-library/src/main/kotlin/godot/core/Rect2.kt b/kt/godot-library/src/main/kotlin/godot/core/Rect2.kt index 74e17777d0..09adee437d 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Rect2.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Rect2.kt @@ -1,7 +1,9 @@ package godot.core +import godot.Side import godot.annotation.CoreTypeHelper import godot.util.RealT +import org.w3c.dom.css.Rect import kotlin.math.max import kotlin.math.min @@ -106,6 +108,8 @@ class Rect2( constructor(other: Rect2) : this(other._position, other._size) + constructor(other: Rect2i) : this(other._position.toVector2(), other._size.toVector2()) + constructor(x: RealT, y: RealT, width: RealT, height: RealT) : this(Vector2(x, y), Vector2(width, height)) @@ -123,31 +127,13 @@ class Rect2( ) } - /** - * Returns the intersection of this Rect2 and b. - */ - fun intersection(b: Rect2): Rect2 { - if (!intersects(b)) return Rect2() - - b._position.x = max(b._position.x, _position.x) - b._position.y = max(b._position.y, _position.y) - - val rectEnd = b._position + b._size - val end = _position + _size - - b._size.x = min(rectEnd.x, end.x) - b._position.x - b._size.y = min(rectEnd.y, end.y) - b._position.y - - return b - } - /** * Returns true if this Rect2 completely encloses another one. */ fun encloses(b: Rect2): Boolean { return (b._position.x >= _position.x) && (b._position.y >= _position.y) && - ((b._position.x + b._size.x) < (_position.x + _size.x)) && - ((b._position.y + b._size.y) < (_position.y + _size.y)) + ((b._position.x + b._size.x) < (_position.x + _size.x)) && + ((b._position.y + b._size.y) < (_position.y + _size.y)) } /** @@ -188,6 +174,11 @@ class Rect2( return _size.x * _size.y } + /** + * Returns the center of the Rect2, which is equal to [position] + ([size] / 2). + */ + fun getCenter() = position + (size * 0.5f) + /** * Returns a copy of the Rect2 grown a given amount of units towards all the sides. */ @@ -213,38 +204,24 @@ class Rect2( } /** - * Returns a copy of the Rect2 grown a given amount of units towards all the sides. + * Returns a copy of the Rect2 grown a given [amount] of units towards all the sides. */ - fun growMargin(margin: Margin, by: RealT): Rect2 { - val g = Rect2(this._position, this._size) - when (margin) { - Margin.LEFT -> { - g._position.x -= by - g._size.x += by - } - - Margin.RIGHT -> { - g._size.x += by - } - - Margin.TOP -> { - g._position.y -= by - g._size.y += by - } - - Margin.BOTTOM -> { - g._size.y += by - } - } + fun growSide(side: Side, amount: RealT): Rect2 { + var g = Rect2(this) + g = g.growIndividual( + if (Side.SIDE_LEFT == side) amount else 0.0, + if (Side.SIDE_TOP == side) amount else 0.0, + if (Side.SIDE_RIGHT == side) amount else 0.0, + if (Side.SIDE_BOTTOM == side) amount else 0.0, + ) return g } /** - * Returns true if the Rect2 is flat or empty. + * Returns true if the [Rect2] has area, and false if the Rect2 is linear, empty, or has a negative size. See also + * [getArea]. */ - fun hasNoArea(): Boolean { - return _size.x <= 0 || _size.y <= 0 - } + fun hasArea() = size.x > 0.0f && size.y > 0.0f /** * Returns true if the Rect2 contains a point. @@ -259,6 +236,30 @@ class Rect2( } } + /** + * Returns the intersection of this [Rect2] and [b]. + * + * If the rectangles do not intersect, an empty [Rect2] is returned. + */ + fun intersection(p_rect: Rect2): Rect2 { + val newRect = Rect2(p_rect) + + if (!intersects(newRect)) { + return Rect2() + } + + newRect.position.x = max(p_rect.position.x, position.x) + newRect.position.y = max(p_rect.position.y, position.y) + + val p_rect_end = p_rect.position + p_rect.size + val end = position + size + + newRect.size.x = min(p_rect_end.x, end.x) - newRect.position.x + newRect.size.y = min(p_rect_end.y, end.y) - newRect.position.y + + return newRect; + } + /** * Returns true if the Rect2 overlaps with b (i.e. they have at least one point in common). * If include_borders is true, they will also be considered overlapping if their borders touch, even without intersection. @@ -307,6 +308,8 @@ class Rect2( return ret } + fun toRect2i() = Rect2i(this) + override fun equals(other: Any?): Boolean { return when (other) { is Rect2 -> _position == other._position && _size == other._size diff --git a/kt/godot-library/src/main/kotlin/godot/core/Rect2i.kt b/kt/godot-library/src/main/kotlin/godot/core/Rect2i.kt index f3bf4b6895..e6dc4d00f8 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Rect2i.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Rect2i.kt @@ -110,95 +110,109 @@ class Rect2i( */ constructor(rect: Rect2i) : this(rect._position, rect._size) + // API + /** - * Returns the area of the [Rect2i]. See also [hasArea]. + * Returns a [Rect2i] with equivalent position and area, modified so that the top-left corner is the origin and + * `width` and `height` are positive. */ - val area: Int - get() = _size.width * _size.height + fun abs() = Rect2i( + Point2i( + _position.x + min(_size.x, 0), + _position.y + min(_size.y, 0) + ), + _size.abs() + ) /** - * Returns the center of the [Rect2i], which is equal to [position] + ([size] / 2). - * If [size] is an odd number, the returned center value will be rounded towards [position]. + * Returns `true` if this [Rect2i] completely encloses another one. */ - val center: Vector2i - get() = _position + (_size / 2) + @Suppress("unused") + fun encloses(rect: Rect2i) = (rect._position.x >= _position.x) && (rect._position.y >= _position.y) && + ((rect._position.x + rect._size.x) <= (_position.x + _size.x)) && + ((rect._position.y + rect._size.y) <= (_position.y + _size.y)) /** - * Returns `true` if the [Rect2i] overlaps with `b` (i.e. they have at least one point in common). + * Returns a copy of this [Rect2i] expanded so that the borders align with the given point. */ - fun intersects(rect: Rect2i): Boolean { - if (_position.x >= (rect._position.x + rect._size.width)) { - return false + fun expand(vector: Point2i) = Rect2i(this).also { + it.expandTo(vector) + } + + private fun expandTo(p_vector: Point2i) { + val begin = Point2i(_position) + val end = _position + _size + + if (p_vector.x < begin.x) { + begin.x = p_vector.x } - if ((_position.x + _size.width) <= rect._position.x) { - return false + if (p_vector.y < begin.y) { + begin.y = p_vector.y } - if (_position.y >= (rect._position.y + rect._size.height)) { - return false + + if (p_vector.x > end.x) { + end.x = p_vector.x } - if ((_position.y + _size.height) <= rect._position.y) { - return false + if (p_vector.y > end.y) { + end.y = p_vector.y } - return true + _position = begin + _size = end - begin } /** - * Returns `true` if this [Rect2i] completely encloses another one. + * Returns the area of the [Rect2i]. See also [hasArea]. */ - @Suppress("unused") - fun encloses(rect: Rect2i) = (rect._position.x >= _position.x) && (rect._position.y >= _position.y) && - ((rect._position.x + rect._size.x) <= (_position.x + _size.x)) && - ((rect._position.y + rect._size.y) <= (_position.y + _size.y)) + fun getArea(): Int = size.width * size.height /** - * Returns `true` if the [Rect2i] has area, and `false` if the [Rect2i] is linear, empty, or has a negative [size]. - * See also [area]. + * Returns the center of the [Rect2i], which is equal to [position] + ([size] / 2). + * + * If [size] is an odd number, the returned center value will be rounded towards [position]. */ - @Suppress("unused") - fun hasArea() = _size.x > 0 && _size.y > 0 + fun getCenter(): Vector2i = position + (size / 2) /** - * Returns the intersection of this [Rect2i] and `b`. - * If the rectangles do not intersect, an empty [Rect2i] is returned. + * Returns a copy of the [Rect2i] grown by the specified `amount` on all sides. */ - fun intersection(rect: Rect2i): Rect2i { - val newRect = Rect2i(rect) - - if (!intersects(newRect)) { - return Rect2i() - } - - newRect._position.x = max(rect._position.x, _position.x) - newRect._position.y = max(rect._position.y, _position.y) - - val p_rect_end = rect._position + rect._size - val end = _position + _size - - newRect._size.x = min(p_rect_end.x, end.x) - newRect._position.x - newRect._size.y = min(p_rect_end.y, end.y) - newRect._position.y + fun grow(amount: Int) = Rect2i(this).also { + it._position.x -= amount + it._position.y -= amount + it._size.width += amount * 2 + it._size.height += amount * 2 + } - return rect + /** + * Returns a copy of the [Rect2i] grown by the specified amount on each side individually. + */ + fun growIndividual(left: Int, top: Int, right: Int, bottom: Int) = Rect2i(this).also { + it._position.x -= left + it._position.y -= top + it._size.width += left + right + it._size.height += top + bottom } /** - * Returns a larger [Rect2i] that contains this [Rect2i] and `b`. + * Returns a copy of the [Rect2i] grown by the specified [amount] on the specified [Side]. */ @Suppress("unused") - fun merge(rect: Rect2i): Rect2i { - val newRect = Rect2i() - - newRect._position.x = min(rect._position.x, _position.x) - newRect._position.y = min(rect._position.y, _position.y) - - newRect._size.x = max(rect._position.x + rect._size.x, _position.x + _size.x) - newRect._size.y = max(rect._position.y + rect._size.y, _position.y + _size.y) - - newRect._size = newRect._size - newRect._position // Make relative again. - - return newRect + fun growSide(side: Side, amount: Int) = Rect2i(this).also { + it.growIndividual( + if (Side.SIDE_LEFT == side) amount else 0, + if (Side.SIDE_TOP == side) amount else 0, + if (Side.SIDE_RIGHT == side) amount else 0, + if (Side.SIDE_BOTTOM == side) amount else 0 + ) } + /** + * Returns `true` if the [Rect2i] has area, and `false` if the [Rect2i] is linear, empty, or has a negative [size]. + * See also [getArea]. + */ + @Suppress("unused") + fun hasArea() = _size.x > 0 && _size.y > 0 + /** * Returns `true` if the [Rect2i] contains a point. By convention, the right and bottom edges of the [Rect2i] are * considered exclusive, so points on these edges are not included. @@ -226,78 +240,65 @@ class Rect2i( } /** - * Returns a copy of the [Rect2i] grown by the specified `amount` on all sides. + * Returns the intersection of this [Rect2i] and `b`. + * If the rectangles do not intersect, an empty [Rect2i] is returned. */ - fun grow(amount: Int) = Rect2i(this).also { - it._position.x -= amount - it._position.y -= amount - it._size.width += amount * 2 - it._size.height += amount * 2 - } + fun intersection(rect: Rect2i): Rect2i { + val newRect = Rect2i(rect) - /** - * Returns a copy of the [Rect2i] grown by the specified [amount] on the specified [Side]. - */ - @Suppress("unused") - fun growSide(side: Side, amount: Int) = Rect2i(this).also { - it.growIndividual( - if (Side.SIDE_LEFT == side) amount else 0, - if (Side.SIDE_TOP == side) amount else 0, - if (Side.SIDE_RIGHT == side) amount else 0, - if (Side.SIDE_BOTTOM == side) amount else 0 - ) - } + if (!intersects(newRect)) { + return Rect2i() + } - /** - * Returns a copy of the [Rect2i] grown by the specified amount on each side individually. - */ - fun growIndividual(left: Int, top: Int, right: Int, bottom: Int) = Rect2i(this).also { - it._position.x -= left - it._position.y -= top - it._size.width += left + right - it._size.height += top + bottom + newRect._position.x = max(rect._position.x, _position.x) + newRect._position.y = max(rect._position.y, _position.y) + + val p_rect_end = rect._position + rect._size + val end = _position + _size + + newRect._size.x = min(p_rect_end.x, end.x) - newRect._position.x + newRect._size.y = min(p_rect_end.y, end.y) - newRect._position.y + + return rect } /** - * Returns a copy of this [Rect2i] expanded so that the borders align with the given point. + * Returns `true` if the [Rect2i] overlaps with `b` (i.e. they have at least one point in common). */ - fun expand(vector: Point2i) = Rect2i(this).also { - it.expandTo(vector) - } - - private fun expandTo(p_vector: Point2i) { - val begin = Point2i(_position) - val end = _position + _size - - if (p_vector.x < begin.x) { - begin.x = p_vector.x + fun intersects(rect: Rect2i): Boolean { + if (_position.x >= (rect._position.x + rect._size.width)) { + return false } - if (p_vector.y < begin.y) { - begin.y = p_vector.y + if ((_position.x + _size.width) <= rect._position.x) { + return false } - - if (p_vector.x > end.x) { - end.x = p_vector.x + if (_position.y >= (rect._position.y + rect._size.height)) { + return false } - if (p_vector.y > end.y) { - end.y = p_vector.y + if ((_position.y + _size.height) <= rect._position.y) { + return false } - _position = begin - _size = end - begin + return true } /** - * Returns a [Rect2i] with equivalent position and area, modified so that the top-left corner is the origin and - * `width` and `height` are positive. + * Returns a larger [Rect2i] that contains this [Rect2i] and `b`. */ - fun abs() = Rect2i( - Point2i( - _position.x + min(_size.x, 0), - _position.y + min(_size.y, 0) - ), - _size.abs() - ) + @Suppress("unused") + fun merge(rect: Rect2i): Rect2i { + val newRect = Rect2i() + + newRect._position.x = min(rect._position.x, _position.x) + newRect._position.y = min(rect._position.y, _position.y) + + newRect._size.x = max(rect._position.x + rect._size.x, _position.x + _size.x) + newRect._size.y = max(rect._position.y + rect._size.y, _position.y + _size.y) + + newRect._size = newRect._size - newRect._position // Make relative again. + + return newRect + } @Suppress("unused") fun toRect2() = Rect2( diff --git a/kt/godot-library/src/main/kotlin/godot/core/StringName.kt b/kt/godot-library/src/main/kotlin/godot/core/StringName.kt index 5f4f8d1eca..1a2567670d 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/StringName.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/StringName.kt @@ -36,7 +36,20 @@ class StringName : NativeCoreType { return TransferContext.readReturnValue(VariantType.STRING, false) as String } - //TODO/4.0: Implement + /** + * Method to use JVM string methods on [StringName]. This [StringName] is first converted to [String] and then code + * block is called on the converted [String]. + * + * Example: + * ```kotlin + * val stringName = "path/to/my/file".asStringName() + * val splitResult = stringName.invoke { split('/') } + * ``` + * + * This should be used to reproduce behaviour of methods described in + * [StringName Godot's documentation](https://docs.godotengine.org/en/stable/classes/class_stringname.html). + */ + operator fun invoke(stringOperation: String.() -> T): T = toString().stringOperation() @Suppress("FunctionName") private object Bridge { diff --git a/kt/godot-library/src/main/kotlin/godot/core/Transform2D.kt b/kt/godot-library/src/main/kotlin/godot/core/Transform2D.kt index 9a1bf7fc1c..2b20882308 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Transform2D.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Transform2D.kt @@ -221,6 +221,11 @@ class Transform2D( && transform._origin.isEqualApprox(this._origin) } + /** + * Returns true if this transform is finite, by calling @GlobalScope.is_finite on each component. + */ + fun isFinite() = x.isFinite() && y.isFinite() && origin.isFinite() + /** * Returns the transform with the basis orthogonal (90 degrees), and normalized axis vectors. */ diff --git a/kt/godot-library/src/main/kotlin/godot/core/Transform3D.kt b/kt/godot-library/src/main/kotlin/godot/core/Transform3D.kt index 191f6f5f57..4b8bf763dd 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Transform3D.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Transform3D.kt @@ -142,6 +142,11 @@ class Transform3D( return transform3D._basis.isEqualApprox(this._basis) && transform3D._origin.isEqualApprox(this._origin) } + /** + * Returns true if this transform is finite, by calling @GlobalScope.is_finite on each component. + */ + fun isFinite() = basis.isFinite() && origin.isFinite() + /** * Returns a copy of the transform rotated such that its -Z axis points towards the target position. * The transform will first be rotated around the given up vector, and then fully aligned to the target by a further rotation around an axis perpendicular to both the target and up vectors. diff --git a/kt/godot-library/src/main/kotlin/godot/core/Vector2.kt b/kt/godot-library/src/main/kotlin/godot/core/Vector2.kt index f782abd02f..3cb6d0cb5a 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Vector2.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Vector2.kt @@ -50,6 +50,8 @@ class Vector2( constructor(vec: Vector2) : this(vec.x, vec.y) + constructor(other: Vector2i) : this(other.x, other.y) + constructor(x: Number, y: Number) : this(x.toRealT(), y.toRealT()) @@ -91,6 +93,34 @@ class Vector2( return this.x / this.y } + /** + * Returns the derivative at the given [t] on the Bézier curve defined by this vector and the given [control1], + * [control2], and [end] points. + */ + fun bezierDerivative( + control1: Vector2, + control2: Vector2, + end: Vector2, + t: RealT + ) = Vector2( + bezierDerivative(this.x, control1.x, control2.x, end.x, t), + bezierDerivative(this.y, control1.y, control2.y, end.y, t) + ) + + /** + * Returns the point at the given [t] on the Bézier curve defined by this vector and the given [control1], + * [control2], and [end] points. + */ + fun bezierInterpolate( + control1: Vector2, + control2: Vector2, + end: Vector2, + t: RealT + ) = Vector2( + bezierInterpolate(this.x, control1.x, control2.x, end.x, t), + bezierInterpolate(this.y, control1.y, control2.y, end.y, t) + ) + /** * Returns the vector “bounced off” from a plane defined by the given normal. */ @@ -226,11 +256,6 @@ class Vector2( ) && isEqualApprox(other.y, y) } - /** - * Returns true if this vector's values are approximately zero - */ - fun isZeroApprox() = isEqualApprox(Vector2.ZERO) - /** * Returns true if this vector is finite, by calling @GlobalScope.is_finite on each component. */ @@ -243,6 +268,11 @@ class Vector2( return isEqualApprox(this.length(), 1.0) } + /** + * Returns true if this vector's values are approximately zero + */ + fun isZeroApprox() = isEqualApprox(Vector2.ZERO) + /** * Returns the vector’s length. */ diff --git a/kt/godot-library/src/main/kotlin/godot/core/Vector2i.kt b/kt/godot-library/src/main/kotlin/godot/core/Vector2i.kt index 0c8a99c869..ca045a4ec3 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Vector2i.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Vector2i.kt @@ -67,11 +67,6 @@ class Vector2i( */ fun aspect() = this.x / this.y - /** - * Returns the vector with each component set to one or negative one, depending on the signs of the components. - */ - fun sign() = Vector2i(kotlin.math.sign(x.toFloat()), kotlin.math.sign(y.toFloat())) - /** * Returns a new vector with all components clamped between the components of min and max, by running * @GlobalScope.clamp on each component. @@ -79,15 +74,15 @@ class Vector2i( fun clamp(min: Vector2i, max: Vector2i) = Vector2(x.coerceIn(min.x, max.x), y.coerceIn(min.y, max.y)) /** - * Returns the vector’s length squared. - * Prefer this method over length if you need to sort vectors or need the squared length for some formula. + * Returns the vector’s length. */ - fun lengthSquared() = x * x + y * y + fun length() = sqrt(lengthSquared().toDouble()) /** - * Returns the vector’s length. + * Returns the vector’s length squared. + * Prefer this method over length if you need to sort vectors or need the squared length for some formula. */ - fun length() = sqrt(lengthSquared().toDouble()) + fun lengthSquared() = x * x + y * y /** * Returns the axis of the vector's highest value. See AXIS_* constants. If all components are equal, @@ -101,6 +96,29 @@ class Vector2i( */ fun minAxisIndex() = if (x < y) Vector2.AXIS_X else Vector2.AXIS_Y + /** + * Returns the vector with each component set to one or negative one, depending on the signs of the components. + */ + fun sign() = Vector2i(kotlin.math.sign(x.toFloat()), kotlin.math.sign(y.toFloat())) + + /** + * Returns a new vector with each component snapped to the closest multiple of the corresponding component in [step]. + */ + fun snapped(step: Vector2i) = Vector2i( + snapped(x, step.x), + snapped(y, step.y) + ) + + fun mod(other: Vector2i) = Vector2i( + x.mod(other.x), + y.mod(other.y) + ) + + fun mod(scalar: Int) = Vector2i( + x.mod(scalar), + y.mod(scalar) + ) + operator fun get(idx: Int): Int = when (idx) { 0 -> x diff --git a/kt/godot-library/src/main/kotlin/godot/core/Vector3.kt b/kt/godot-library/src/main/kotlin/godot/core/Vector3.kt index 4d7e166049..7cb063e1ca 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Vector3.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Vector3.kt @@ -68,6 +68,8 @@ class Vector3( constructor(vec: Vector3) : this(vec.x, vec.y, vec.z) + constructor(other: Vector3i) : this(other.x, other.y, other.z) + constructor(x: Number, y: Number, z: Number) : this(x.toRealT(), y.toRealT(), z.toRealT()) @@ -87,6 +89,36 @@ class Vector3( return atan2(cross(to).length(), dot(to)) } + /** + * Returns the derivative at the given [t] on the Bézier curve defined by this vector and the given [control1], + * [control2], and [end] points. + */ + fun bezierDerivative( + control1: Vector3, + control2: Vector3, + end: Vector3, + t: RealT + ) = Vector3( + bezierDerivative(this.x, control1.x, control2.x, end.x, t), + bezierDerivative(this.y, control1.y, control2.y, end.y, t), + bezierDerivative(this.z, control1.z, control2.z, end.z, t) + ) + + /** + * Returns the point at the given [t] on the Bézier curve defined by this vector and the given [control1], + * [control2], and [end] points. + */ + fun bezierInterpolate( + control1: Vector3, + control2: Vector3, + end: Vector3, + t: RealT + ) = Vector3( + bezierInterpolate(this.x, control1.x, control2.x, end.x, t), + bezierInterpolate(this.y, control1.y, control2.y, end.y, t), + bezierInterpolate(this.z, control1.z, control2.z, end.z, t) + ) + /** * Returns the vector “bounced off” from a plane defined by the given normal. */ @@ -366,6 +398,18 @@ class Vector3( } } + /** + * Returns the Vector3 from an octahedral-compressed form created using [octahedronEncode] (stored as a Vector2). + */ + fun octahedronDecode(uv: Vector2): Vector3 { + val f = Vector2(uv.x * 2.0f - 1.0f, uv.y * 2.0f - 1.0f) + val n = Vector3(f.x, f.y, 1.0f - abs(f.x) - abs(f.y)) + val t = -n.z.coerceIn(0.0, 1.0) + n.x += (if (n.x >= 0) -t else t).toDouble() + n.y += (if (n.y >= 0) -t else t).toDouble() + return n.normalized() + } + fun octahedronEncode(): Vector2 { var n = Vector3(this) n /= abs(n.x) + abs(n.y) + abs(n.z) @@ -499,13 +543,7 @@ class Vector3( } } - /** - * Returns a diagonal matrix with the vector as main diagonal. - */ -// TODO: fix me -// fun toDiagonalMatrix(): Basis { -// return Basis() -// } + fun toVector3i() = Vector3i(this) operator fun get(n: Int): RealT = diff --git a/kt/godot-library/src/main/kotlin/godot/core/Vector3i.kt b/kt/godot-library/src/main/kotlin/godot/core/Vector3i.kt index 3f98031b3a..f5a255f79f 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Vector3i.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Vector3i.kt @@ -132,6 +132,27 @@ class Vector3i( return Vector3i(kotlin.math.sign(x.toDouble()), kotlin.math.sign(y.toDouble()), kotlin.math.sign(z.toDouble())) } + /** + * Returns a new vector with each component snapped to the closest multiple of the corresponding component in [step]. + */ + fun snapped(step: Vector3i) = Vector3i( + snapped(x, step.x), + snapped(y, step.y), + snapped(z, step.z) + ) + + fun mod(other: Vector3i) = Vector3i( + x.mod(other.x), + y.mod(other.y), + z.mod(other.z) + ) + + fun mod(scalar: Int) = Vector3i( + x.mod(scalar), + y.mod(scalar), + z.mod(scalar) + ) + operator fun get(n: Int): Int = when (n) { 0 -> x diff --git a/kt/godot-library/src/main/kotlin/godot/core/Vector4.kt b/kt/godot-library/src/main/kotlin/godot/core/Vector4.kt index d790d62427..58433484be 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Vector4.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Vector4.kt @@ -50,6 +50,8 @@ class Vector4( constructor(vec: Vector4) : this(vec.x, vec.y, vec.z, vec.w) + constructor(other: Vector4i) : this(other.x, other.y, other.z, other.w) + constructor(x: Number, y: Number, z: Number, w: Number) : this(x.toRealT(), y.toRealT(), z.toRealT(), w.toRealT()) @@ -62,13 +64,6 @@ class Vector4( return Vector4(abs(x), abs(y), abs(z), abs(w)) } - /** - * Returns the vector “bounced off” from a plane defined by the given normal. - */ - fun bounce(n: Vector4): Vector4 { - return -reflect(n) - } - /** * Returns a new vector with all components rounded up. */ @@ -219,9 +214,9 @@ class Vector4( } /** - * Returns true if this vector's values are approximately zero + * Returns true if this vector is finite, by calling [Float.isFinite] on each component. */ - fun isZeroApprox() = isEqualApprox(ZERO) + fun isFinite() = x.isFinite() && y.isFinite() && z.isFinite() && w.isFinite() /** * Returns true if the vector is normalized. @@ -230,6 +225,11 @@ class Vector4( return isEqualApprox(this.length(), 1.0) } + /** + * Returns true if this vector's values are approximately zero + */ + fun isZeroApprox() = isEqualApprox(ZERO) + /** * Returns the vector’s length. */ @@ -254,20 +254,6 @@ class Vector4( w + (weight * (to.w - w)) ) - /** - * Returns the vector with a maximum length by limiting its length to length. - */ - fun limitLength(length: RealT = 1.0): Vector4 { - val l = length() - var v = Vector4(this) - if (l > 0 && length < l) { - v /= l - v *= length - } - - return v - } - /** * Returns the axis of the vector's highest value. See AXIS_* constants. * If all components are equal, this method returns AXIS_X. @@ -303,19 +289,6 @@ class Vector4( return minIndex.toNaturalT() } - /** - * Moves the vector toward to by the fixed delta amount. - */ - fun moveToward(to: Vector4, delta: RealT): Vector4 { - val vd = to - this - val len = vd.length() - return if (len <= delta || len < CMP_EPSILON) { - to - } else { - this + vd / len * delta - } - } - /** * Returns the vector scaled to unit length. Equivalent to v / v.length(). */ @@ -340,15 +313,6 @@ class Vector4( } } - /** - * Returns the outer product with b. - */ - fun outer(b: Vector4) = Basis( - Vector3(x * b.x, x * b.y, x * b.z), - Vector3(y * b.x, y * b.y, y * b.z), - Vector3(z * b.x, z * b.y, z * b.z) - ) - /** * Returns a vector composed of the fposmod of this vector’s components and mod. */ @@ -359,13 +323,6 @@ class Vector4( */ fun posmodv(modv: Vector4) = Vector4(x.rem(modv.x), y.rem(modv.y), z.rem(modv.z), w.rem(modv.w)) - /** - * Returns the vector reflected from a plane defined by the given normal. - */ - fun reflect(by: Vector4): Vector4 { - return by - this * this.dot(by) * 2.0 - } - /** * Returns the vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. */ @@ -380,13 +337,6 @@ class Vector4( return Vector4(sign(x), sign(y), sign(z), sign(w)) } - /** - * Returns the component of the vector along a plane defined by the given normal. - */ - fun slide(vec: Vector4): Vector4 { - return vec - this * this.dot(vec) - } - /** * Returns a copy of the vector snapped to the lowest neared multiple. */ @@ -403,13 +353,7 @@ class Vector4( } } - /** - * Returns a diagonal matrix with the vector as main diagonal. - */ -// TODO: fix me -// fun toDiagonalMatrix(): Basis { -// return Basis() -// } + fun toVector4i() = Vector4i(this) operator fun get(n: Int): RealT = diff --git a/kt/godot-library/src/main/kotlin/godot/core/Vector4i.kt b/kt/godot-library/src/main/kotlin/godot/core/Vector4i.kt index 4ebaf4b914..88122667c7 100644 --- a/kt/godot-library/src/main/kotlin/godot/core/Vector4i.kt +++ b/kt/godot-library/src/main/kotlin/godot/core/Vector4i.kt @@ -129,13 +129,30 @@ class Vector4i( } /** - * Returns a diagonal matrix with the vector as main diagonal. + * Returns a new vector with each component snapped to the closest multiple of the corresponding component in [step]. */ -// TODO: fix me -// fun toDiagonalMatrix(): Basis { -// return Basis() -// } + fun snapped(step: Vector4i) = Vector4i( + snapped(x, step.x), + snapped(y, step.y), + snapped(z, step.z), + snapped(w, step.w) + ) + + fun toVector4() = Vector4(this) + fun mod(other: Vector4i) = Vector4i( + x.mod(other.x), + y.mod(other.y), + z.mod(other.z), + w.mod(other.w), + ) + + fun mod(scalar: Int) = Vector4i( + x.mod(scalar), + y.mod(scalar), + z.mod(scalar), + w.mod(scalar), + ) operator fun get(n: Int): Int = when (n) { diff --git a/kt/godot-library/src/main/kotlin/godot/global/GDCore.kt b/kt/godot-library/src/main/kotlin/godot/global/GDCore.kt index 07e1348d65..6ef345bb0d 100644 --- a/kt/godot-library/src/main/kotlin/godot/global/GDCore.kt +++ b/kt/godot-library/src/main/kotlin/godot/global/GDCore.kt @@ -25,139 +25,139 @@ internal interface GDCore { /** Returns a color according to the standardized name with alpha ranging from 0 to 1.*/ fun ColorN(name: String, alpha: Float = 1.0f): Color { return when (name) { - "aliceblue" -> Color.aliceblue + "aliceblue" -> Color.aliceBlue "aqua" -> Color.aqua "aquamarine" -> Color.aquamarine "azure" -> Color.azure "beige" -> Color.beige "bisque" -> Color.bisque "black" -> Color.black - "blanchedalmond" -> Color.blanchedalmond + "blanchedalmond" -> Color.blanchedAlmond "blue" -> Color.blue - "blueviolet" -> Color.blueviolet + "blueviolet" -> Color.blueViolet "brown" -> Color.brown "burlywood" -> Color.burlywood - "cadetblue" -> Color.cadetblue + "cadetblue" -> Color.cadetBlue "chartreuse" -> Color.chartreuse "chocolate" -> Color.chocolate "coral" -> Color.coral - "cornflower" -> Color.cornflower + "cornflower" -> Color.cornflowerBlue "cornsilk" -> Color.cornsilk "crimson" -> Color.crimson "cyan" -> Color.cyan - "darkblue" -> Color.darkblue - "darkcyan" -> Color.darkcyan - "darkgoldenrod" -> Color.darkgoldenrod - "darkgray" -> Color.darkgray - "darkgreen" -> Color.darkgreen - "darkkhaki" -> Color.darkkhaki - "darkmagenta" -> Color.darkmagenta - "darkolivegreen" -> Color.darkolivegreen + "darkblue" -> Color.darkBlue + "darkcyan" -> Color.darkCyan + "darkgoldenrod" -> Color.darkGoldenrod + "darkgray" -> Color.darkGray + "darkgreen" -> Color.darkGreen + "darkkhaki" -> Color.darkKhaki + "darkmagenta" -> Color.darkMagenta + "darkolivegreen" -> Color.darkOliveGreen "darkorange" -> Color.darkorange - "darkorchid" -> Color.darkorchid - "darkred" -> Color.darkred - "darksalmon" -> Color.darksalmon - "darkseagreen" -> Color.darkseagreen - "darkslateblue" -> Color.darkslateblue - "darkslategray" -> Color.darkslategray - "darkturquoise" -> Color.darkturquoise - "darkviolet" -> Color.darkviolet - "deeppink" -> Color.deeppink - "deepskyblue" -> Color.deepskyblue - "dimgray" -> Color.dimgray - "dodgerblue" -> Color.dodgerblue + "darkorchid" -> Color.darkOrchid + "darkred" -> Color.darkRed + "darksalmon" -> Color.darkSalmon + "darkseagreen" -> Color.darkSeaGreen + "darkslateblue" -> Color.darkSlateBlue + "darkslategray" -> Color.darkSlateGray + "darkturquoise" -> Color.darkTurquoise + "darkviolet" -> Color.darkViolet + "deeppink" -> Color.deepPink + "deepskyblue" -> Color.deepSkyBlue + "dimgray" -> Color.dimGray + "dodgerblue" -> Color.dodgerBlue "firebrick" -> Color.firebrick - "floralwhite" -> Color.floralwhite - "forestgreen" -> Color.forestgreen + "floralwhite" -> Color.floralWhite + "forestgreen" -> Color.forestGreen "fuchsia" -> Color.fuchsia "gainsboro" -> Color.gainsboro - "ghostwhite" -> Color.ghostwhite + "ghostwhite" -> Color.ghostWhite "gold" -> Color.gold "goldenrod" -> Color.goldenrod "gray" -> Color.gray - "webgray" -> Color.webgray + "webgray" -> Color.webGray "green" -> Color.green - "webgreen" -> Color.webgreen - "greenyellow" -> Color.greenyellow + "webgreen" -> Color.webGreen + "greenyellow" -> Color.greenYellow "honeydew" -> Color.honeydew - "hotpink" -> Color.hotpink - "indianred" -> Color.indianred + "hotpink" -> Color.hotPink + "indianred" -> Color.indianRed "indigo" -> Color.indigo "ivory" -> Color.ivory "khaki" -> Color.khaki "lavender" -> Color.lavender - "lavenderblush" -> Color.lavenderblush - "lawngreen" -> Color.lawngreen - "lemonchiffon" -> Color.lemonchiffon - "lightblue" -> Color.lightblue - "lightcoral" -> Color.lightcoral - "lightcyan" -> Color.lightcyan - "lightgoldenrod" -> Color.lightgoldenrod - "lightgray" -> Color.lightgray - "lightgreen" -> Color.lightgreen - "lightpink" -> Color.lightpink - "lightsalmon" -> Color.lightsalmon - "lightseagreen" -> Color.lightseagreen - "lightskyblue" -> Color.lightskyblue - "lightslategray" -> Color.lightslategray - "lightsteelblue" -> Color.lightsteelblue - "lightyellow" -> Color.lightyellow + "lavenderblush" -> Color.lavenderBlush + "lawngreen" -> Color.lawnGreen + "lemonchiffon" -> Color.lemonChiffon + "lightblue" -> Color.lightBlue + "lightcoral" -> Color.lightCoral + "lightcyan" -> Color.lightCyan + "lightgoldenrod" -> Color.lightGoldenrod + "lightgray" -> Color.lightGray + "lightgreen" -> Color.lightGreen + "lightpink" -> Color.lightPink + "lightsalmon" -> Color.lightSalmon + "lightseagreen" -> Color.lightSeaGreen + "lightskyblue" -> Color.lightSkyBlue + "lightslategray" -> Color.lightSlateGray + "lightsteelblue" -> Color.lightSteelBlue + "lightyellow" -> Color.lightYellow "lime" -> Color.lime - "limegreen" -> Color.limegreen + "limegreen" -> Color.limeGreen "linen" -> Color.linen "magenta" -> Color.magenta "maroon" -> Color.maroon - "webmaroon" -> Color.webmaroon - "mediumaquamarine" -> Color.mediumaquamarine - "mediumblue" -> Color.mediumblue - "mediumorchid" -> Color.mediumorchid - "mediumpurple" -> Color.mediumpurple - "mediumseagreen" -> Color.mediumseagreen - "mediumslateblue" -> Color.mediumslateblue - "mediumspringgreen" -> Color.mediumspringgreen - "mediumturquoise" -> Color.mediumturquoise - "mediumvioletred" -> Color.mediumvioletred - "midnightblue" -> Color.midnightblue - "mintcream" -> Color.mintcream - "mistyrose" -> Color.mistyrose + "webmaroon" -> Color.webMaroon + "mediumaquamarine" -> Color.mediumAquamarine + "mediumblue" -> Color.mediumBlue + "mediumorchid" -> Color.mediumOrchid + "mediumpurple" -> Color.mediumPurple + "mediumseagreen" -> Color.mediumSeaGreen + "mediumslateblue" -> Color.mediumSlateBlue + "mediumspringgreen" -> Color.mediumSpringGreen + "mediumturquoise" -> Color.mediumTurquoise + "mediumvioletred" -> Color.mediumVioletRed + "midnightblue" -> Color.midnightBlue + "mintcream" -> Color.mintCream + "mistyrose" -> Color.mistyRose "moccasin" -> Color.moccasin - "navajowhite" -> Color.navajowhite - "navyblue" -> Color.navyblue - "oldlace" -> Color.oldlace + "navajowhite" -> Color.navajoWhite + "navyblue" -> Color.navyBlue + "oldlace" -> Color.oldLace "olive" -> Color.olive - "olivedrab" -> Color.olivedrab + "olivedrab" -> Color.oliveDrab "orange" -> Color.orange - "orangered" -> Color.orangered + "orangered" -> Color.orangeRed "orchid" -> Color.orchid - "palegoldenrod" -> Color.palegoldenrod - "palegreen" -> Color.palegreen - "paleturquoise" -> Color.paleturquoise - "palevioletred" -> Color.palevioletred - "papayawhip" -> Color.papayawhip - "peachpuff" -> Color.peachpuff + "palegoldenrod" -> Color.paleGoldenrod + "palegreen" -> Color.paleGreen + "paleturquoise" -> Color.paleTurquoise + "palevioletred" -> Color.paleVioletRed + "papayawhip" -> Color.papayaWhip + "peachpuff" -> Color.peachPuff "peru" -> Color.peru "pink" -> Color.pink "plum" -> Color.plum - "powderblue" -> Color.powderblue + "powderblue" -> Color.powderBlue "purple" -> Color.purple - "webpurple" -> Color.webpurple - "rebeccapurple" -> Color.rebeccapurple + "webpurple" -> Color.webPurple + "rebeccapurple" -> Color.rebeccaPurple "red" -> Color.red - "rosybrown" -> Color.rosybrown - "royalblue" -> Color.royalblue - "saddlebrown" -> Color.saddlebrown + "rosybrown" -> Color.rosyBrown + "royalblue" -> Color.royalBlue + "saddlebrown" -> Color.saddleBrown "salmon" -> Color.salmon - "sandybrown" -> Color.sandybrown - "seagreen" -> Color.seagreen + "sandybrown" -> Color.sandyBrown + "seagreen" -> Color.seaGreen "seashell" -> Color.seashell "sienna" -> Color.sienna "silver" -> Color.silver - "skyblue" -> Color.skyblue - "slateblue" -> Color.slateblue - "slategray" -> Color.slategray + "skyblue" -> Color.skyBlue + "slateblue" -> Color.slateBlue + "slategray" -> Color.slateGray "snow" -> Color.snow - "springgreen" -> Color.springgreen - "steelblue" -> Color.steelblue + "springgreen" -> Color.springGreen + "steelblue" -> Color.steelBlue "tan" -> Color.tan "teal" -> Color.teal "thistle" -> Color.thistle @@ -167,9 +167,9 @@ internal interface GDCore { "violet" -> Color.violet "wheat" -> Color.wheat "white" -> Color.white - "whitesmoke" -> Color.whitesmoke + "whitesmoke" -> Color.whiteSmoke "yellow" -> Color.yellow - "yellowgreen" -> Color.yellowgreen + "yellowgreen" -> Color.yellowGreen else -> throw NoSuchElementException("$name is not a valid color name.") }.also { it.a = alpha.toRealT() diff --git a/kt/godot-library/src/main/kotlin/godot/util/Utils.kt b/kt/godot-library/src/main/kotlin/godot/util/Utils.kt index 8bb3fdbe73..2649f49856 100644 --- a/kt/godot-library/src/main/kotlin/godot/util/Utils.kt +++ b/kt/godot-library/src/main/kotlin/godot/util/Utils.kt @@ -4,13 +4,16 @@ import godot.core.ObjectID import godot.core.VariantType import java.util.* import kotlin.math.abs +import kotlin.math.sign typealias RealT = Double internal typealias NaturalT = Long + internal val realTVariantType = VariantType.DOUBLE internal val NaturalTVariantType = VariantType.LONG typealias VoidPtr = Long + const val nullptr: VoidPtr = -1L val nullObjectID = ObjectID(-1) @@ -40,6 +43,18 @@ internal inline fun Float.toGodotReal(): Float = this @Suppress("NOTHING_TO_INLINE") inline fun Int.toNaturalT(): NaturalT = this.toLong() +val Int.signbit: Boolean + get() = sign < 0 + +val Long.signbit: Boolean + get() = sign < 0 + +val Float.signbit: Boolean + get() = sign < 0 + +val Double.signbit: Boolean + get() = sign < 0 + /** * Because of float precision, it's hard to obtain two perfectly equal real numbers. * They are considered equal if the difference is smaller than CMP_EPSILON @@ -72,7 +87,7 @@ fun cubicInterpolateInTime( toT: RealT, preT: RealT, postT: RealT -) : RealT { +): RealT { val t = lerp(0.0, toT, weight) val a1 = lerp(pre, from, if (preT == 0.0) 0.0 else (t - preT) / -preT) val a2 = lerp(from, to, if (toT == 0.0) 0.5 else t / toT) diff --git a/kt/plugins/godot-gradle-plugin/src/main/resources/godot/gradle/godot-kotlin-graal-jni-config.json b/kt/plugins/godot-gradle-plugin/src/main/resources/godot/gradle/godot-kotlin-graal-jni-config.json index 600c236f69..943dffd68b 100644 --- a/kt/plugins/godot-gradle-plugin/src/main/resources/godot/gradle/godot-kotlin-graal-jni-config.json +++ b/kt/plugins/godot-gradle-plugin/src/main/resources/godot/gradle/godot-kotlin-graal-jni-config.json @@ -169,8 +169,24 @@ { "name" : "engine_call_constructor_kt_custom_callable", "parameterTypes" : ["godot.core.KtCustomCallable"] }, { "name" : "engine_call_copy_constructor", "parameterTypes" : [] }, + { "name" : "engine_call_bind", "parameterTypes" : ["long"] }, + { "name" : "engine_call_bindv", "parameterTypes" : ["long"] }, { "name" : "engine_call_call", "parameterTypes" : ["long"] }, - { "name" : "engine_call_call_deferred", "parameterTypes" : ["long"] } + { "name" : "engine_call_call_deferred", "parameterTypes" : ["long"] }, + { "name" : "engine_call_callv", "parameterTypes" : ["long"] }, + { "name" : "engine_call_get_bound_arguments", "parameterTypes" : ["long"] }, + { "name" : "engine_call_get_bound_arguments_count", "parameterTypes" : ["long"] }, + { "name" : "engine_call_get_method", "parameterTypes" : ["long"] }, + { "name" : "engine_call_get_object", "parameterTypes" : ["long"] }, + { "name" : "engine_call_get_object_id", "parameterTypes" : ["long"] }, + { "name" : "engine_call_hash", "parameterTypes" : ["long"] }, + { "name" : "engine_call_is_custom", "parameterTypes" : ["long"] }, + { "name" : "engine_call_is_null", "parameterTypes" : ["long"] }, + { "name" : "engine_call_is_standard", "parameterTypes" : ["long"] }, + { "name" : "engine_call_is_valid", "parameterTypes" : ["long"] }, + { "name" : "engine_call_rpc", "parameterTypes" : ["long"] }, + { "name" : "engine_call_rpc_id", "parameterTypes" : ["long"] }, + { "name" : "engine_call_unbind", "parameterTypes" : ["long"] } ] }, { @@ -185,10 +201,12 @@ { "name" : "engine_call_duplicate", "parameterTypes" : ["long"] }, { "name" : "engine_call_is_empty", "parameterTypes" : ["long"] }, { "name" : "engine_call_erase", "parameterTypes" : ["long"] }, + { "name" : "engine_call_find_key", "parameterTypes" : ["long"] }, { "name" : "engine_call_get", "parameterTypes" : ["long"] }, { "name" : "engine_call_has", "parameterTypes" : ["long"] }, { "name" : "engine_call_hasAll", "parameterTypes" : ["long"] }, { "name" : "engine_call_hash", "parameterTypes" : ["long"] }, + { "name" : "engine_call_is_read_only", "parameterTypes" : ["long"] }, { "name" : "engine_call_keys", "parameterTypes" : ["long"] }, { "name" : "engine_call_make_read_only", "parameterTypes" : ["long"] }, { "name" : "engine_call_merge", "parameterTypes" : ["long"] }, diff --git a/src/memory/bridges/callable_bridge.cpp b/src/memory/bridges/callable_bridge.cpp index aeb4ea414c..a9a17b8d88 100644 --- a/src/memory/bridges/callable_bridge.cpp +++ b/src/memory/bridges/callable_bridge.cpp @@ -9,6 +9,8 @@ using namespace bridges; JNI_INIT_STATICS_FOR_CLASS(CallableBridge) +CallableBridge::StringNames CallableBridge::string_names; + uintptr_t CallableBridge::engine_call_constructor(JNIEnv* p_raw_env, jobject p_instance) { return reinterpret_cast(memnew(Callable)); } @@ -33,6 +35,34 @@ uintptr_t CallableBridge::engine_call_copy_constructor(JNIEnv* p_raw_env, jobjec return reinterpret_cast(memnew(Callable(args[0].operator Callable()))); } +void CallableBridge::engine_call_bind(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + TransferContext* transfer_context {GDKotlin::get_instance().transfer_context}; + uint32_t args_size {transfer_context->read_args_size(env)}; + Variant arg_store[5]; + const Variant* arg_pointers[5]; + for (uint32_t i = 0; i < args_size; ++i) { + arg_store[i] = transfer_context->read_single_arg(env); + arg_pointers[i] = &arg_store[i]; + } + Variant result { + from_uint_to_ptr(p_raw_ptr)->bindp(arg_pointers, args_size) + }; + transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_bindv(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + Variant args[1] = {}; + TransferContext* transfer_context {GDKotlin::get_instance().transfer_context}; + transfer_context->read_args(env, args); + + Variant result { + from_uint_to_ptr(p_raw_ptr)->bindv(args[0]) + }; + transfer_context->write_return_value(env, result); +} + void CallableBridge::engine_call_call(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr) { jni::Env env {p_raw_env}; uint32_t args_size {GDKotlin::get_instance().transfer_context->read_args_size(env)}; @@ -62,6 +92,148 @@ void CallableBridge::engine_call_call_deferred(JNIEnv* p_raw_env, jobject p_inst from_uint_to_ptr(p_raw_ptr)->call_deferredp(arg_pointers, args_size); } +void CallableBridge::engine_call_callv(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + Variant args[1] = {}; + TransferContext* transfer_context {GDKotlin::get_instance().transfer_context}; + transfer_context->read_args(env, args); + + Variant result { + from_uint_to_ptr(p_raw_ptr)->callv(args[0]) + }; + transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_get_bound_arguments(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->get_bound_arguments() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_get_bound_arguments_count(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->get_bound_arguments_count() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_get_method(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->get_method() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_get_object(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->get_object() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_get_object_id(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->get_object_id() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_hash(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->hash() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_is_custom(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->is_custom() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_is_null(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->is_null() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_is_standard(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->is_standard() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_is_valid(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + + Variant result { + from_uint_to_ptr(p_raw_ptr)->is_valid() + }; + GDKotlin::get_instance().transfer_context->write_return_value(env, result); +} + +void CallableBridge::engine_call_rpc(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + uint32_t args_size {GDKotlin::get_instance().transfer_context->read_args_size(env)}; + + Variant arg_store[5]; + const Variant* arg_pointers[5]; + for (uint32_t i = 0; i < args_size; ++i) { + arg_store[i] = GDKotlin::get_instance().transfer_context->read_single_arg(env); + arg_pointers[i] = &arg_store[i]; + } + Variant instance { *from_uint_to_ptr(p_raw_ptr) }; + instance.call(string_names.func_rpc, arg_pointers); +} + +void CallableBridge::engine_call_rpc_id(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + uint32_t args_size {GDKotlin::get_instance().transfer_context->read_args_size(env)}; + + Variant arg_store[5]; + const Variant* arg_pointers[5]; + for (uint32_t i = 0; i < args_size; ++i) { + arg_store[i] = GDKotlin::get_instance().transfer_context->read_single_arg(env); + arg_pointers[i] = &arg_store[i]; + } + Variant instance { *from_uint_to_ptr(p_raw_ptr) }; + instance.call(string_names.func_rpc_id, arg_pointers); +} + +void CallableBridge::engine_call_unbind(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + Variant args[1] = {}; + TransferContext* transfer_context {GDKotlin::get_instance().transfer_context}; + transfer_context->read_args(env, args); + + Variant result { + from_uint_to_ptr(p_raw_ptr)->unbind(args[0]) + }; + transfer_context->write_return_value(env, result); +} + CallableBridge::CallableBridge(jni::JObject p_wrapped, jni::JObject p_class_loader) : JavaInstanceWrapper(CALLABLE_BRIDGE_CLASS_NAME, p_wrapped, p_class_loader) { jni::JNativeMethod engine_call_constructor_method { @@ -84,12 +256,113 @@ CallableBridge::CallableBridge(jni::JObject p_wrapped, jni::JObject p_class_load const_cast("()J"), (void*) CallableBridge::engine_call_copy_constructor}; - jni::JNativeMethod engine_call_call_method {const_cast("engine_call_call"), const_cast("(J)V"), (void*) CallableBridge::engine_call_call}; + jni::JNativeMethod engine_call_bind_method { + const_cast("engine_call_bind"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_bind + }; + + jni::JNativeMethod engine_call_bindv_method { + const_cast("engine_call_bindv"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_bindv + }; + + jni::JNativeMethod engine_call_call_method { + const_cast("engine_call_call"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_call + }; jni::JNativeMethod engine_call_call_deferred_method { const_cast("engine_call_call_deferred"), const_cast("(J)V"), - (void*) CallableBridge::engine_call_call_deferred}; + (void*) CallableBridge::engine_call_call_deferred + }; + + jni::JNativeMethod engine_call_callv_method { + const_cast("engine_call_callv"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_callv + }; + + jni::JNativeMethod engine_call_get_bound_arguments_method { + const_cast("engine_call_get_bound_arguments"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_get_bound_arguments + }; + + jni::JNativeMethod engine_call_get_bound_arguments_count_method { + const_cast("engine_call_get_bound_arguments_count"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_get_bound_arguments_count + }; + + jni::JNativeMethod engine_call_get_method_method { + const_cast("engine_call_get_method"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_get_method + }; + + jni::JNativeMethod engine_call_get_object_method { + const_cast("engine_call_get_object"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_get_object + }; + + jni::JNativeMethod engine_call_get_object_id_method { + const_cast("engine_call_get_object_id"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_get_object_id + }; + + jni::JNativeMethod engine_call_hash_method { + const_cast("engine_call_hash"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_hash + }; + + jni::JNativeMethod engine_call_is_custom_method { + const_cast("engine_call_is_custom"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_is_custom + }; + + jni::JNativeMethod engine_call_is_null_method { + const_cast("engine_call_is_null"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_is_null + }; + + jni::JNativeMethod engine_call_is_standard_method { + const_cast("engine_call_is_standard"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_is_standard + }; + + jni::JNativeMethod engine_call_is_valid_method { + const_cast("engine_call_is_valid"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_is_valid + }; + + jni::JNativeMethod engine_call_rpc_method { + const_cast("engine_call_rpc"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_rpc + }; + + jni::JNativeMethod engine_call_rpc_id_method { + const_cast("engine_call_rpc_id"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_rpc_id + }; + + jni::JNativeMethod engine_call_unbind_method { + const_cast("engine_call_unbind"), + const_cast("(J)V"), + (void*) CallableBridge::engine_call_unbind + }; Vector methods; methods.push_back(engine_call_constructor_method); @@ -97,8 +370,27 @@ CallableBridge::CallableBridge(jni::JObject p_wrapped, jni::JObject p_class_load methods.push_back(engine_call_constructor_kt_custom_callable_method); methods.push_back(engine_call_copy_constructor_method); + methods.push_back(engine_call_bind_method); + methods.push_back(engine_call_bindv_method); methods.push_back(engine_call_call_method); methods.push_back(engine_call_call_deferred_method); + methods.push_back(engine_call_callv_method); + methods.push_back(engine_call_get_bound_arguments_method); + methods.push_back(engine_call_get_bound_arguments_count_method); + methods.push_back(engine_call_get_method_method); + methods.push_back(engine_call_get_object_method); + methods.push_back(engine_call_get_object_id_method); + methods.push_back(engine_call_hash_method); + methods.push_back(engine_call_is_custom_method); + methods.push_back(engine_call_is_null_method); + methods.push_back(engine_call_is_standard_method); + methods.push_back(engine_call_is_valid_method); + methods.push_back(engine_call_rpc_method); + methods.push_back(engine_call_rpc_id_method); + methods.push_back(engine_call_unbind_method); + + string_names.func_rpc = _scs_create("rpc"); + string_names.func_rpc_id = _scs_create("rpc_id"); jni::Env env {jni::Jvm::current_env()}; j_class.register_natives(env, methods); diff --git a/src/memory/bridges/callable_bridge.h b/src/memory/bridges/callable_bridge.h index 5cbb9a62e0..891aab38f4 100644 --- a/src/memory/bridges/callable_bridge.h +++ b/src/memory/bridges/callable_bridge.h @@ -5,6 +5,14 @@ namespace bridges { class CallableBridge : public JavaInstanceWrapper { + private: + struct StringNames { + StringName func_rpc; + StringName func_rpc_id; + }; + + static StringNames string_names; + public: static uintptr_t engine_call_constructor(JNIEnv* p_raw_env, jobject p_instance); @@ -14,8 +22,24 @@ namespace bridges { static uintptr_t engine_call_copy_constructor(JNIEnv* p_raw_env, jobject p_instance); + static void engine_call_bind(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_bindv(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_call(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_call_deferred(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_callv(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_get_bound_arguments(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_get_bound_arguments_count(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_get_method(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_get_object(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_get_object_id(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_hash(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_is_custom(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_is_null(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_is_standard(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_is_valid(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_rpc(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_rpc_id(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_unbind(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); CallableBridge(jni::JObject p_wrapped, jni::JObject p_class_loader); ~CallableBridge() = default; diff --git a/src/memory/bridges/dictionary_bridge.cpp b/src/memory/bridges/dictionary_bridge.cpp index a66984707b..e7ff13d709 100644 --- a/src/memory/bridges/dictionary_bridge.cpp +++ b/src/memory/bridges/dictionary_bridge.cpp @@ -31,13 +31,24 @@ DictionaryBridge::DictionaryBridge(jni::JObject p_wrapped, jni::JObject p_class_ const_cast("engine_call_erase"), const_cast("(J)V"), (void*) DictionaryBridge::engine_call_erase}; + jni::JNativeMethod engine_call_find_key_method { + const_cast("engine_call_find_key"), + const_cast("(J)V"), + (void*) DictionaryBridge::engine_call_find_key}; jni::JNativeMethod engine_call_get_method {const_cast("engine_call_get"), const_cast("(J)V"), (void*) DictionaryBridge::engine_call_get}; jni::JNativeMethod engine_call_has_method {const_cast("engine_call_has"), const_cast("(J)V"), (void*) DictionaryBridge::engine_call_has}; jni::JNativeMethod engine_call_hasAll_method { const_cast("engine_call_hasAll"), const_cast("(J)V"), (void*) DictionaryBridge::engine_call_hasAll}; - jni::JNativeMethod engine_call_hash_method {const_cast("engine_call_hash"), const_cast("(J)V"), (void*) DictionaryBridge::engine_call_hash}; + jni::JNativeMethod engine_call_hash_method { + const_cast("engine_call_hash"), + const_cast("(J)V"), + (void*) DictionaryBridge::engine_call_hash}; + jni::JNativeMethod engine_call_is_read_only_method { + const_cast("engine_call_is_read_only"), + const_cast("(J)V"), + (void*) DictionaryBridge::engine_call_is_read_only}; jni::JNativeMethod engine_call_keys_method {const_cast("engine_call_keys"), const_cast("(J)V"), (void*) DictionaryBridge::engine_call_keys}; jni::JNativeMethod engine_call_make_read_only_method { const_cast("engine_call_make_read_only"), @@ -73,10 +84,12 @@ DictionaryBridge::DictionaryBridge(jni::JObject p_wrapped, jni::JObject p_class_ methods.push_back(engine_call_duplicate_method); methods.push_back(engine_call_is_empty_method); methods.push_back(engine_call_erase_method); + methods.push_back(engine_call_find_key_method); methods.push_back(engine_call_get_method); methods.push_back(engine_call_has_method); methods.push_back(engine_call_hasAll_method); methods.push_back(engine_call_hash_method); + methods.push_back(engine_call_is_read_only_method); methods.push_back(engine_call_keys_method); methods.push_back(engine_call_make_read_only_method); methods.push_back(engine_call_merge_method); @@ -122,6 +135,15 @@ void DictionaryBridge::engine_call_erase(JNIEnv* p_raw_env, jobject p_instance, from_uint_to_ptr(p_raw_ptr)->erase(args[0]); } +void DictionaryBridge::engine_call_find_key(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + Variant args[1] = {}; + TransferContext* transfer_context {GDKotlin::get_instance().transfer_context}; + transfer_context->read_args(env, args); + Variant ret { from_uint_to_ptr(p_raw_ptr)->find_key(args[0]) }; + transfer_context->write_return_value(env, ret); +} + void DictionaryBridge::engine_call_get(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr) { jni::Env env {p_raw_env}; TransferContext* transfer_context {GDKotlin::get_instance().transfer_context}; @@ -155,6 +177,12 @@ void DictionaryBridge::engine_call_hash(JNIEnv* p_raw_env, jobject p_instance, j GDKotlin::get_instance().transfer_context->write_return_value(env, variant); } +void DictionaryBridge::engine_call_is_read_only(JNIEnv *p_raw_env, jobject p_instance, jlong p_raw_ptr) { + jni::Env env {p_raw_env}; + Variant ret {from_uint_to_ptr(p_raw_ptr)->is_read_only() }; + GDKotlin::get_instance().transfer_context->write_return_value(env, ret); +} + void DictionaryBridge::engine_call_keys(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr) { jni::Env env {p_raw_env}; Variant variant {from_uint_to_ptr(p_raw_ptr)->keys()}; diff --git a/src/memory/bridges/dictionary_bridge.h b/src/memory/bridges/dictionary_bridge.h index 0461771668..72ba73f6be 100644 --- a/src/memory/bridges/dictionary_bridge.h +++ b/src/memory/bridges/dictionary_bridge.h @@ -15,10 +15,12 @@ namespace bridges { static void engine_call_duplicate(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_is_empty(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_erase(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_find_key(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_get(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_has(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_hasAll(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_hash(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); + static void engine_call_is_read_only(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_keys(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_make_read_only(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr); static void engine_call_merge(JNIEnv* p_raw_env, jobject p_instance, jlong p_raw_ptr);