如何在 Android SceneForm 中使用拖动手势旋转 3D 模型
How to rotate an 3D model using DragGesture in Android SceneForm
我遵循了解决方案 here for rotating a TransformableNode
on the X axis based on the user's DragGesture
, using the Sceneform Android SDK。但是,我也想在 Y 轴和 Z 轴上旋转,类似于 ARCore SceneViewer 的做法。
我怎样才能做到这一点?
我目前拥有的在左侧(仅在 X 轴上旋转),而所需的在右侧(在所有轴上旋转,如在 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) {}
}
我在这里找到了可行的解决方案:https://github.com/chnouman/SceneView
这里是相关的代码片段,经过我的一些改编使其适用于 .glb
个文件。
我已经分叉了这个 repo,如果有人对此感兴趣,我正在致力于键盘输入支持。
渲染对象:
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)
}
将对象添加到 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
)
}
}
}
自定义 TransformableNode
:
class DragTransformableNode(val radius: Float, transformationSystem: TransformationSystem) :
TransformableNode(transformationSystem) {
val dragRotationController = DragRotationController(
this,
transformationSystem.dragRecognizer
)
}
自定义 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) {}
}
我遵循了解决方案 here for rotating a TransformableNode
on the X axis based on the user's DragGesture
, using the Sceneform Android SDK。但是,我也想在 Y 轴和 Z 轴上旋转,类似于 ARCore SceneViewer 的做法。
我怎样才能做到这一点?
我目前拥有的在左侧(仅在 X 轴上旋转),而所需的在右侧(在所有轴上旋转,如在 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) {}
}
我在这里找到了可行的解决方案:https://github.com/chnouman/SceneView
这里是相关的代码片段,经过我的一些改编使其适用于 .glb
个文件。
我已经分叉了这个 repo,如果有人对此感兴趣,我正在致力于键盘输入支持。
渲染对象:
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)
}
将对象添加到 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
)
}
}
}
自定义 TransformableNode
:
class DragTransformableNode(val radius: Float, transformationSystem: TransformationSystem) :
TransformableNode(transformationSystem) {
val dragRotationController = DragRotationController(
this,
transformationSystem.dragRecognizer
)
}
自定义 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) {}
}