Sceneform-android-sdk: scaling a Renderable inside a TransformableNode

Created on 15 Jun 2018  路  5Comments  路  Source: google-ar/sceneform-android-sdk

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));
                });
question

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.

    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.

All 5 comments

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
   }
}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hcjung2000 picture hcjung2000  路  3Comments

PaulTVungle picture PaulTVungle  路  3Comments

dementia2029 picture dementia2029  路  3Comments

rohitagarwal3011 picture rohitagarwal3011  路  4Comments

yashvv picture yashvv  路  3Comments