I am attempting to create a ModelRenderable which can be moved around until it is set on the right position, then confirm with tap to 'place'.
The first issue I am running into is the scaling of the Renderable. I am using a TransformableNode to be able to move the Renderable around, but .setLocalScale has no effect on TransformableNode (on a Node object it works just fine).
What would be the right way to go about this? It should be pretty straightforward, but I am unable to solve this puzzle.
arFragment.setOnTapArPlaneListener(
(HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
if (modelRenderable == null) {
return;
}
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
node = new TransformableNode(arFragment.getTransformationSystem());
node.setParent(anchorNode);
node.setRenderable(this.modelRenderable);
node.setLocalScale(new Vector3(0.55f, 0.55f, 0.55f));
});
The local scale of the TransformableNode must be set before calling node.setParent. This is because the TransformableNode's ScaleController caches the initial local scale when the node is activated. Also, you likely want to set the min and max scale of the ScaleController.
arFragment.setOnTapArPlaneListener(
(HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
if (modelRenderable == null) {
return;
}
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
TransformableNode node = new TransformableNode(arFragment.getTransformationSystem());
node.setRenderable(modelRenderable);
// Set the min and max scales of the ScaleController.
// Default min is 0.75, default max is 1.75.
node.getScaleController().setMinScale(0.4f);
node.getScaleController().setMaxScale(2.0f);
// Set the local scale of the node BEFORE setting its parent
node.setLocalScale(new Vector3(0.55f, 0.55f, 0.55f));
node.setParent(anchorNode);
});
Alternatively, you can set a scale for the model when you import it in the .sfa file.
@dsternfeld7 thanx for your excellent answer, much appreciated. I had already implemented the min & max scale, but had no clue that the order was important. If you don't mind me asking, do you have a good resource to read up on Sceneform besides the pages on developers.google.com? I am having a hard time getting my questions answered online.
I'm glad that helped you out!
If you can't find the information you need on developers.google.com, positing issues here on GitHub is the best way to get your questions answered and tell us about problems your are running into. There is also a good getting started post here, and a post about augmented images here.
How to add default round circle for Node in Android AR. It is coming for Transformable Node by default. But we need for Node which is not drag-gable.
You can create your own ScaleController and add methods to can access and change the currentScaleRatio.
import com.google.ar.sceneform.FrameTime
import com.google.ar.sceneform.Node
import com.google.ar.sceneform.math.MathHelper
import com.google.ar.sceneform.math.Vector3
import com.google.ar.sceneform.ux.BaseTransformableNode
import com.google.ar.sceneform.ux.BaseTransformationController
import com.google.ar.sceneform.ux.PinchGesture
import com.google.ar.sceneform.ux.PinchGestureRecognizer
class ScaleController(
transformableNode: BaseTransformableNode,
gestureRecognizer: PinchGestureRecognizer
) :
BaseTransformationController<PinchGesture>(transformableNode, gestureRecognizer) {
var minScale = DEFAULT_MIN_SCALE
var maxScale = DEFAULT_MAX_SCALE
var sensitivity = DEFAULT_SENSITIVITY
var elasticity = DEFAULT_ELASTICITY
private var currentScaleRatio: Float = 0.toFloat()
private val scaleDelta: Float
get() {
val scaleDelta = maxScale - minScale
if (scaleDelta <= 0.0f) {
throw IllegalStateException("maxScale must be greater than minScale.")
}
return scaleDelta
}
private val clampedScaleRatio: Float
get() = Math.min(1.0f, Math.max(0.0f, currentScaleRatio))
private val finalScale: Float
get() {
val elasticScaleRatio = clampedScaleRatio + elasticDelta
return minScale + elasticScaleRatio * scaleDelta
}
private val elasticDelta: Float
get() {
val overRatio: Float
if (currentScaleRatio > 1.0f) {
overRatio = currentScaleRatio - 1.0f
} else if (currentScaleRatio < 0.0f) {
overRatio = currentScaleRatio
} else {
return 0.0f
}
return (1.0f - 1.0f / (Math.abs(overRatio) * elasticity + 1.0f)) * Math.signum(overRatio)
}
override fun onActivated(node: Node?) {
super.onActivated(node)
val scale = transformableNode.localScale
currentScaleRatio = (scale.x - minScale) / scaleDelta
}
override fun onUpdated(node: Node?, frameTime: FrameTime?) {
if (isTransforming) {
return
}
val t = MathHelper.clamp(frameTime!!.deltaSeconds * LERP_SPEED, 0f, 1f)
currentScaleRatio = MathHelper.lerp(currentScaleRatio, clampedScaleRatio, t)
val finalScaleValue = finalScale
val finalScale = Vector3(finalScaleValue, finalScaleValue, finalScaleValue)
transformableNode.localScale = finalScale
}
public override fun canStartTransformation(gesture: PinchGesture): Boolean {
return transformableNode.isSelected
}
fun getScaleRatio(): Float {
return currentScaleRatio
}
fun updateScaleRatio(ratio: Float) {
currentScaleRatio = ratio
}
public override fun onContinueTransformation(gesture: PinchGesture) {
currentScaleRatio += gesture.gapDeltaInches() * sensitivity
val finalScaleValue = finalScale
val finalScale = Vector3(finalScaleValue, finalScaleValue, finalScaleValue)
transformableNode.localScale = finalScale
if (currentScaleRatio < -ELASTIC_RATIO_LIMIT || currentScaleRatio > 1.0f + ELASTIC_RATIO_LIMIT) {
gesture.wasCancelled()
}
}
public override fun onEndTransformation(gesture: PinchGesture) {}
companion object {
const val DEFAULT_MIN_SCALE = 0.75f
const val DEFAULT_MAX_SCALE = 1.75f
const val DEFAULT_SENSITIVITY = 0.75f
const val DEFAULT_ELASTICITY = 0.15f
private const val ELASTIC_RATIO_LIMIT = 0.8f
private const val LERP_SPEED = 8.0f
}
}
Most helpful comment
The local scale of the TransformableNode must be set before calling node.setParent. This is because the TransformableNode's ScaleController caches the initial local scale when the node is activated. Also, you likely want to set the min and max scale of the ScaleController.
Alternatively, you can set a scale for the model when you import it in the .sfa file.