I followed the solution here for rotating a TransformableNode
on the X axis based on the user's DragGesture
, using the Sceneform Android SDK. However, I would also like to rotate on the Y and Z axis as well, similar to how ARCore SceneViewer does it.
How can I achieve that?
What I have currently is on the left (rotates only on X axis), and what is desired is on the right (rotates on all axes, as in ARCore Scene Viewer).
class DragRotationController(transformableNode: BaseTransformableNode, gestureRecognizer: DragGestureRecognizer) :
BaseTransformationController<DragGesture>(transformableNode, gestureRecognizer) {
// Rate that the node rotates in degrees per degree of twisting.
var rotationRateDegrees = 0.5f
public override fun canStartTransformation(gesture: DragGesture): Boolean {
return transformableNode.isSelected
}
public override fun onContinueTransformation(gesture: DragGesture) {
var localRotation = transformableNode.localRotation
val rotationAmountX = gesture.delta.x * rotationRateDegrees
val rotationDeltaX = Quaternion(Vector3.up(), rotationAmountX)
localRotation = Quaternion.multiply(localRotation, rotationDeltaX)
// *** this only rotates on X axis. How do I rotate on all axes? ***
transformableNode.localRotation = localRotation
}
public override fun onEndTransformation(gesture: DragGesture) {}
}
I was able to find a working solution here: https://github.com/chnouman/SceneView
Here are the relevant snippets of code, with some of my adaptations to make it work for .glb
files.
I have forked this repo and am working on keyboard input support if anyone's interested in that.
Rendering the object:
private fun renderLocalObject() {
skuProgressBar.setVisibility(View.VISIBLE)
ModelRenderable.builder()
.setSource(this,
RenderableSource.builder().setSource(
this,
Uri.parse(localModel),
RenderableSource.SourceType.GLB)/*RenderableSource.SourceType.GLTF2)*/
.setScale(0.25f)
.setRecenterMode(RenderableSource.RecenterMode.ROOT)
.build())
.setRegistryId(localModel)
.build()
.thenAccept { modelRenderable: ModelRenderable ->
skuProgressBar.setVisibility(View.GONE)
addNodeToScene(modelRenderable)
}
Adding the object to the SceneView
:
private fun addNodeToScene(model: ModelRenderable) {
if (sceneView != null) {
val transformationSystem = makeTransformationSystem()
var dragTransformableNode = DragTransformableNode(1f, transformationSystem)
dragTransformableNode?.renderable = model
sceneView.getScene().addChild(dragTransformableNode)
dragTransformableNode?.select()
sceneView.getScene()
.addOnPeekTouchListener { hitTestResult: HitTestResult?, motionEvent: MotionEvent? ->
transformationSystem.onTouch(
hitTestResult,
motionEvent
)
}
}
}
Custom TransformableNode
:
class DragTransformableNode(val radius: Float, transformationSystem: TransformationSystem) :
TransformableNode(transformationSystem) {
val dragRotationController = DragRotationController(
this,
transformationSystem.dragRecognizer
)
}
Custom TransformationController
:
class DragRotationController(
private val transformableNode: DragTransformableNode,
gestureRecognizer: DragGestureRecognizer
) :
BaseTransformationController<DragGesture>(transformableNode, gestureRecognizer) {
companion object {
private const val initialLat = 26.15444376319647
private const val initialLong = 18.995950736105442
var lat: Double = initialLat
var long: Double = initialLong
}
// Rate that the node rotates in degrees per degree of twisting.
private var rotationRateDegrees = 0.5f
public override fun canStartTransformation(gesture: DragGesture): Boolean {
return transformableNode.isSelected
}
private fun getX(lat: Double, long: Double): Float {
return (transformableNode.radius * Math.cos(Math.toRadians(lat)) * Math.sin(Math.toRadians(long))).toFloat()
}
private fun getY(lat: Double, long: Double): Float {
return transformableNode.radius * Math.sin(Math.toRadians(lat)).toFloat()
}
private fun getZ(lat: Double, long: Double): Float {
return (transformableNode.radius * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(long))).toFloat()
}
override fun onActivated(node: Node?) {
super.onActivated(node)
Handler().postDelayed({
transformCamera(lat, long)
}, 0)
}
public override fun onContinueTransformation(gesture: DragGesture) {
val rotationAmountY = gesture.delta.y * rotationRateDegrees
val rotationAmountX = gesture.delta.x * rotationRateDegrees
val deltaAngleY = rotationAmountY.toDouble()
val deltaAngleX = rotationAmountX.toDouble()
long -= deltaAngleX
lat += deltaAngleY
//lat = Math.max(Math.min(lat, 90.0), 0.0)
transformCamera(lat, long)
}
private fun transformCamera(lat: Double, long: Double) {
val camera = transformableNode.scene?.camera
var rot = Quaternion.eulerAngles(Vector3(0F, 0F, 0F))
val pos = Vector3(getX(lat, long), getY(lat, long), getZ(lat, long))
rot = Quaternion.multiply(rot, Quaternion(Vector3.up(), (long).toFloat()))
rot = Quaternion.multiply(rot, Quaternion(Vector3.right(), (-lat).toFloat()))
camera?.localRotation = rot
camera?.localPosition = pos
}
fun resetInitialState() {
transformCamera(initialLat, initialLong)
}
public override fun onEndTransformation(gesture: DragGesture) {}
}