Skip to content

Instantly share code, notes, and snippets.

@ThomasGorisse
Last active August 19, 2022 17:15
Show Gist options
  • Select an option

  • Save ThomasGorisse/7436e553dbe3daef302798f9c5cb0406 to your computer and use it in GitHub Desktop.

Select an option

Save ThomasGorisse/7436e553dbe3daef302798f9c5cb0406 to your computer and use it in GitHub Desktop.
package io.github.sceneview.components
import android.animation.ObjectAnimator
import com.google.android.filament.*
import com.google.ar.sceneform.rendering.*
import dev.romainguy.kotlin.math.*
import io.github.sceneview.*
import io.github.sceneview.animation.TransformAnimator
import io.github.sceneview.gesture.*
import io.github.sceneview.math.*
const val defaultSmoothSpeed = 1.0f
interface TransformComponent : Component {
val transformManager get() = engine.transformManager
val transformInstance: Int @EntityInstance get() = transformManager.getInstance(entity)
/**
* ### The node position to locate it within the coordinate system of its parent
*
* Default is `Position(x = 0.0f, y = 0.0f, z = 0.0f)`, indicating that the node is placed at
* the origin of the parent node's coordinate system.
*
* **Horizontal (X):**
* - left: x < 0.0f
* - center horizontal: x = 0.0f
* - right: x > 0.0f
*
* **Vertical (Y):**
* - top: y > 0.0f
* - center vertical : y = 0.0f
* - bottom: y < 0.0f
*
* **Depth (Z):**
* - forward: z < 0.0f
* - origin/camera position: z = 0.0f
* - backward: z > 0.0f
*
* ------- +y ----- -z
*
* ---------|----/----
*
* ---------|--/------
*
* -x - - - 0 - - - +x
*
* ------/--|---------
*
* ----/----|---------
*
* +z ---- -y --------
*/
var position: Position
get() = transform.position
set(value) {
transform = Transform(value, quaternion, scale)
}
/**
* ### The node world-space position
*
* The world position of this node (i.e. relative to the [SceneView]).
* This is the composition of this component's local position with its parent's world
* position.
*
* @see worldTransform
*/
var worldPosition: Position
get() = worldTransform.position
set(value) {
position = (worldToParent * Float4(value, 1f)).xyz
}
/**
* TODO: Doc
*/
var quaternion: Quaternion
get() = transform.quaternion
set(value) {
transform = Transform(position, value, scale)
}
/**
* ### The node world-space quaternion
*
* The world quaternion of this node (i.e. relative to the [SceneView]).
* This is the composition of this component's local quaternion with its parent's world
* quaternion.
*
* @see worldTransform
*/
var worldQuaternion: Quaternion
get() = worldTransform.toQuaternion()
set(value) {
quaternion = worldToParent.toQuaternion() * value
}
/**
* ### The node orientation in Euler Angles Degrees per axis from `0.0f` to `360.0f`
*
* The three-component rotation vector specifies the direction of the rotation axis in degrees.
* Rotation is applied relative to the node's origin property.
*
* Default is `Rotation(x = 0.0f, y = 0.0f, z = 0.0f)`, specifying no rotation.
*
* Note that modifying the individual components of the returned rotation doesn't have any effect.
*
* ------- +y ----- -z
*
* ---------|----/----
*
* ---------|--/------
*
* -x - - - 0 - - - +x
*
* ------/--|---------
*
* ----/----|---------
*
* +z ---- -y --------
*/
var rotation: Rotation
get() = quaternion.toEulerAngles()
set(value) {
quaternion = Quaternion.fromEuler(value)
}
/**
* ### The node world-space rotation
*
* The world rotation of this node (i.e. relative to the [SceneView]).
* This is the composition of this component's local rotation with its parent's world
* rotation.
*
* @see worldTransform
*/
var worldRotation: Rotation
get() = worldTransform.rotation
set(value) {
quaternion = worldToParent.toQuaternion() * Quaternion.fromEuler(value)
}
/**
* ### The node scale on each axis.
*
* Reduce (`scale < 1.0f`) / Increase (`scale > 1.0f`)
*/
var scale: Scale
get() = transform.scale
set(value) {
transform = Transform(position, quaternion, value)
}
/**
* ### The node world-space scale
*
* The world scale of this node (i.e. relative to the [SceneView]).
* This is the composition of this component's local scale with its parent's world
* scale.
*
* @see worldTransform
*/
var worldScale: Scale
get() = worldTransform.scale
set(value) {
scale = (worldToParent * scale(value)).scale
}
/**
* @see TransformManager.getTransform
* @see TransformManager.setTransform
*/
var transform: Transform
get() = transformManager.getTransform(transformInstance)
set(value) {
transformManager.setTransform(transformInstance, value)
}
/**
* @see TransformManager.getWorldTransform
*/
val worldTransform: Transform
get() = transformManager.getWorldTransform(transformInstance)
/**
* ### The transform from the world coordinate system to the coordinate system of the parent node
*/
val worldToParent: Transform
get() = parentTransformable?.let { inverse(it.worldTransform) } ?: Transform()
/**
* @see TransformManager.getParent
* @see TransformManager.setTransform
*/
var parentTransformable: TransformComponent?
get() = transformManager.getParent(transformInstance).takeIf { it != 0 }?.let {
object : TransformComponent {
override val engine = this@TransformComponent.engine
override val entity = it
}
}
set(value) {
transformManager.setParent(transformInstance, value?.transformInstance ?: 0)
}
/**
* ### The node scale
*
* - reduce size: scale < 1.0f
* - same size: scale = 1.0f
* - increase size: scale > 1.0f
*/
fun setScale(scale: Float) {
this.scale.xyz = Scale(scale)
}
/**
* ## Change the node transform
*
* @see position
* @see quaternion
* @see scale
*/
fun transform(
position: Position = this.position,
quaternion: Quaternion = this.quaternion,
scale: Scale = this.scale,
smooth: Boolean = false,
smoothSpeed: Float = defaultSmoothSpeed
) {
if (smooth) {
smooth(position, quaternion, scale, smoothSpeed)
} else {
this.position = position
this.quaternion = quaternion
this.scale = scale
}
}
/**
* ## Change the node transform
*
* @see position
* @see rotation
* @see scale
*/
fun transform(
position: Position = this.position,
rotation: Rotation = this.rotation,
scale: Scale = this.scale,
smooth: Boolean = false,
smoothSpeed: Float = defaultSmoothSpeed
) = transform(position, rotation.toQuaternion(), scale, smooth, smoothSpeed)
/**
* ## Smooth move, rotate and scale at a specified speed
*
* @see position
* @see quaternion
* @see scale
* @see speed
*/
fun smooth(
position: Position = this.position,
quaternion: Quaternion = this.quaternion,
scale: Scale = this.scale,
speed: Float = defaultSmoothSpeed
) = smooth(Transform(position, quaternion, scale), speed)
/**
* ## Smooth move, rotate and scale at a specified speed
*
* @see position
* @see quaternion
* @see scale
* @see speed
*/
fun smooth(
position: Position = this.position,
rotation: Rotation = this.rotation,
scale: Scale = this.scale,
speed: Float = defaultSmoothSpeed
) = smooth(Transform(position, rotation, scale), speed)
/**
* ## Smooth move, rotate and scale at a specified speed
*
* @see transform
*/
fun smooth(transform: Transform, speed: Float = defaultSmoothSpeed) {
val maxLength = floatArrayOf(
distance(this.position, transform.position),
length(this.quaternion - transform.quaternion),
distance(this.scale, transform.scale)
).maxOrNull()!!
TransformAnimator.ofTransform(this, transform).apply {
setAutoCancel(true)
duration = (maxLength * speed).toLong()
}.start()
}
fun animatePositions(vararg positions: Position): ObjectAnimator =
TransformAnimator.ofPosition(this, *positions)
fun animateQuaternions(vararg quaternions: Quaternion): ObjectAnimator =
TransformAnimator.ofQuaternion(this, *quaternions)
fun animateRotations(vararg rotations: Rotation): ObjectAnimator =
TransformAnimator.ofRotation(this, *rotations)
fun animateScales(vararg scales: Scale): ObjectAnimator =
TransformAnimator.ofScale(this, *scales)
fun animateTransforms(vararg transforms: Transform): ObjectAnimator =
TransformAnimator.ofTransform(this, *transforms)
/**
* ### Rotates the node to face a point in world-space
*
* @param targetPosition The target position to look at in world space
* @param upDirection The up direction will determine the orientation of the node around the direction
* @param smooth Whether the rotation should happen smoothly
*/
fun lookAt(
targetPosition: Position,
upDirection: Direction = Direction(y = 1.0f),
smooth: Boolean = false
) {
val newQuaternion = lookAt(
targetPosition,
worldPosition,
upDirection
).toQuaternion()
if (smooth) {
smooth(quaternion = newQuaternion)
} else {
transform(quaternion = newQuaternion)
}
}
/**
* ### Rotates the node to face another node
*
* @param targetNode The target node to look at
* @param upDirection The up direction will determine the orientation of the node around the direction
* @param smooth Whether the rotation should happen smoothly
*/
fun lookAt(
targetTransformable: TransformComponent,
upDirection: Direction = Direction(y = 1.0f),
smooth: Boolean = false
) = lookAt(targetTransformable.worldPosition, upDirection, smooth)
/**
* ### Rotates the node to face a direction in world-space
*
* The look direction and up direction cannot be coincident (parallel) or the orientation will
* be invalid.
*
* @param lookDirection The desired look direction in world-space
* @param upDirection The up direction will determine the orientation of the node around the look direction
* @param smooth Whether the rotation should happen smoothly
*/
fun lookTowards(
lookDirection: Direction,
upDirection: Direction = Direction(y = 1.0f),
smooth: Boolean = false
) {
val newQuaternion = lookTowards(worldPosition, -lookDirection, upDirection).toQuaternion()
if (smooth) {
smooth(quaternion = newQuaternion)
} else {
transform(quaternion = newQuaternion)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment