Created
September 11, 2022 13:47
-
-
Save ThomasGorisse/f4f7614d4b6f672c25718bc70346f706 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.Entity | |
import io.github.sceneview.animation.TransformAnimator | |
import io.github.sceneview.gesture.* | |
import io.github.sceneview.math.* | |
import io.github.sceneview.scene.modelMatrix | |
import io.github.sceneview.scene.projectionMatrix | |
import io.github.sceneview.scene.viewMatrix | |
const val defaultSmoothSpeed = 1.0f | |
interface HasTransformComponent : HasComponent { | |
val transformManager get() = engine.transformManager | |
val transformInstance: EntityInstance get() = transformManager.getInstance(entity) | |
/** | |
* Returns whether a particular [Entity] is associated with a component of the | |
* [TransformManager] | |
* @return true if this [Entity] has a component associated with this manager | |
*/ | |
val hasComponent get() = transformManager.hasComponent(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 * value | |
} | |
/** | |
* 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() = parentInstance?.let { | |
inverse(transformManager.getWorldTransform(it)) | |
} ?: Transform() | |
/** | |
* @see TransformManager.getParent | |
* @see TransformManager.setTransform | |
*/ | |
var parentEntity: Entity? | |
get() = transformManager.getParent(transformInstance).takeIf { it != 0 } | |
set(value) { | |
transformManager.setParent(transformInstance, value?.let { | |
transformManager.getInstance(value) | |
} ?: 0) | |
} | |
val parentInstance: EntityInstance? | |
get() = parentEntity?.let { transformManager.getInstance(it) } | |
/** | |
* Returns the number of children of an [EntityInstance]. | |
* | |
* @return The number of children of the queried component. | |
*/ | |
val childCount: Int | |
get() = transformManager.getChildCount(transformInstance) | |
/** | |
* Gets a list of children for a transform component. | |
* | |
* @param count The maximum number of children to retrieve. | |
* @return Array of retrieved children [Entity]. | |
*/ | |
val childEntities: IntArray | |
get() = transformManager.getChildren(transformInstance, childCount) | |
/** | |
* Creates a transform component and associates it with the given entity. The component is | |
* initialized with the identity transform. | |
* If this component already exists on the given entity, it is first | |
* destroyed as if [.destroy] was called. | |
* | |
* @see .destroy | |
*/ | |
fun createComponent(): EntityInstance = transformManager.create(entity) | |
/** | |
* Gets a list of children for a transform component. | |
* | |
* @param count The maximum number of children to retrieve. | |
* @return Array of retrieved children [Entity]. | |
*/ | |
fun getChildEntities(count: Int) = transformManager.getChildren(transformInstance, count) | |
/** | |
* ### 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) | |
// | |
// /** | |
// * ### Places the node at eye and rotates the node to face a point in world-space | |
// * | |
// * @param targetPosition The node position in local 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( | |
// eyePosition: Position, | |
// targetPosition: Position, | |
// upDirection: Direction = Direction(y = 1.0f), | |
// smooth: Boolean = false | |
// ) { | |
// position = eyePosition | |
// val newQuaternion = lookAt( | |
// targetPosition, | |
// worldPosition, | |
// upDirection | |
// ).toQuaternion() | |
// if (smooth) { | |
// smooth(quaternion = newQuaternion) | |
// } else { | |
// transform(quaternion = newQuaternion) | |
// } | |
// } | |
/** | |
* ### 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( | |
worldPosition, | |
targetPosition, | |
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: HasTransformComponent, | |
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) | |
} | |
} | |
} | |
/** | |
* @see clipSpaceToViewSpace | |
*/ | |
fun View.screenToClipSpace(screenPosition: Position) = | |
screenPosition / Float2(x = viewport.width.toFloat(), y = viewport.height.toFloat()) | |
/** | |
* @see viewSpaceToClipSpace | |
*/ | |
fun View.clipSpaceToScreen(clipSpacePosition: Position) = | |
clipSpacePosition * Float2(x = viewport.width.toFloat(), y = viewport.height.toFloat()) | |
/** | |
* @see View.screenToClipSpace | |
*/ | |
fun Camera.clipSpaceToViewSpace(clipSpacePosition: Position) = | |
inverse(projectionMatrix) * | |
(clipSpacePosition * 2.0f - 1.0f) | |
/** | |
* @see Camera.clipSpaceToViewSpace | |
*/ | |
fun Camera.viewSpaceToWorld(viewSpacePosition: Position) = | |
modelMatrix * (viewSpacePosition + 1.0f) / 2.0f | |
/** | |
* @see View.clipSpaceToScreen | |
*/ | |
fun Camera.viewSpaceToClipSpace(viewSpacePosition: Position) = | |
projectionMatrix * viewSpacePosition | |
/** | |
* @see viewSpaceToClipSpace | |
*/ | |
fun Camera.worldToViewSpace(worldPosition: Position) = | |
(projectionMatrix * viewMatrix) * worldPosition | |
/** | |
* Get a world space position from a screen space position | |
* | |
* @param x (0..View width) = (left..right) | |
* The X value is negative when the point is left of the viewport, between 0 and the width of | |
* the [SceneView] when the point is within the viewport, and greater than the width when | |
* the point is to the right of the viewport. | |
* | |
* @param y (0..View height) = (top..bottom) | |
* The Y value is negative when the point is below the viewport, between 0 and the height of | |
* the [SceneView] when the point is within the viewport, and greater than the height when | |
* the point is above the viewport. | |
* | |
* @param z (0..1) (far..near) | |
* The Z value is used for the depth between 1 and 0 (1=near, 0=infinity). | |
* | |
* @return a new Position that represents the point in screen-space. | |
* | |
* @see SceneView.worldToScreen | |
*/ | |
fun SceneView.screenToWorld(x: Float, y: Float, z: Float = 1.0f): Position { | |
// Invert Y because screen Y points down and Filament points up | |
val clipSpacePosition = view.screenToClipSpace(Position(x, -y, z)) | |
val viewSpacePosition = camera.clipSpaceToViewSpace(clipSpacePosition) | |
return camera.viewSpaceToWorld(viewSpacePosition) | |
} | |
/** | |
* Get a screen space position from a world space position | |
* | |
* @param worldPosition The world position to convert | |
* | |
* @return Screen space position in Android device screen coordinates within the SceneView: | |
* TopLeft = (0, 0), BottomRight = (SceneView Width, SceneView Height). | |
* - x (0..View width) = (left..right) | |
* - y (0..View height) = (top..bottom) | |
* - z (0..1) (far..near) | |
* | |
* The device coordinate space is unaffected by the orientation of the device | |
* | |
* @see SceneView.screenToWorld | |
*/ | |
fun SceneView.worldToScreen(worldPosition: Position): Position { | |
val viewSpacePosition = camera.worldToViewSpace(worldPosition) | |
val clipSpacePosition = camera.viewSpaceToClipSpace(viewSpacePosition) | |
return view.clipSpaceToScreen(clipSpacePosition).apply { | |
// Invert Y because Filament points up and screen Y points down. | |
y = -y | |
} | |
} |
I edited the answer, can you check if everything is ok now?
Yes, it is. I only think that it is not necessary to subtract 1 in the last two methods if we work with Float
numbers.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I edited the answer, can you check if everything is ok now? (I'd like to use it for the ViewNode MATCH_PARENT)
You are totally right