I'm attempting to anchor a ViewRenderable to a ModelRenderable so that it appears on the top right corner of the Renderable & I use the following snippet to place it.
//Sets the button north east of the node that it is attached to
viewRenderable.localPosition = Vector3(
parent.right.x,
parent.up.y,
parent.right.z
)
However, the viewRenderable doesn't really show up as intended. It rather shows up a few floats away from the top right corner of the ModelRenderable. Is there a way I can get the actual bounds of the ModelRenderable so that I can place it better? I want the end result something similar to the below picture.
What I have right now is this image:
My code:
//Creation of ViewRenderable
val shareButton = ViewRenderable.builder()
.setVerticalAlignment(ViewRenderable.VerticalAlignment.TOP)
.setHorizontalAlignment(ViewRenderable.HorizontalAlignment.RIGHT)
.setView(view.getARFragment().context, R.layout.share)
.build()
//Creation of ModelRenderable
val video = ModelRenderable.builder()
.setSource(view.getARFragment().context,
Uri.parse("chroma_key_video.sfb")).build()
//Adding Model to the scene | Extension method called on ParentNode
private fun Node.attachVideoNode(videoRenderable: ModelRenderable?) {
//Show another anchor object
val videoNode = Node()
//Attach the new node with the node that this method was called on
videoNode.setParent(this#attachVideoNode)
//Set local position
videoNode.localPosition = this.right //This works fine. The video is rendered next to another ModelRenderable.
// Set the scale of the node so that the aspect ratio of the video is correct.
val videoWidth = mediaPlayer?.videoWidth?.toFloat()
val videoHeight = mediaPlayer?.videoHeight?.toFloat()
videoHeight?.let {
videoWidth?.apply {
videoNode.localScale = Vector3(
(this#apply / it), 1.0f, 1.0f)
}
}
// Start playing the video when the first node is placed.
mediaPlayer?.apply {
if (!isPlaying) {
start()
// Wait to set the renderable until the first frame of the video becomes available.
// This prevents the renderable from briefly appearing as a black quad before the video
// plays.
texture?.surfaceTexture
?.setOnFrameAvailableListener { surfaceTexture ->
videoNode.renderable = videoRenderable
surfaceTexture.setOnFrameAvailableListener(null)
//Attach a share button
videoNode.attachShareButton()
ARApplication.log("The local rotation of this videoRenderable is ${videoNode.localRotation}")
}
} else
videoNode.renderable = videoRenderable
}
}
//Attaches a share button to any node that it is called on. | Extension method called on VideoNode
private fun Node.attachShareButton() {
//Create a new node to display the close button
var closeButton = Node()
closeButton.setParent(this)
ARApplication.log("The close button has been added to the scene at world coordinates: ${closeButton.worldPosition}")
//Set the close button as a renderable
closeButton.renderable = view.getViewRenderable()
}
I don't know exactly what you already got, but this is a solution for put a text on the ViewRenderable on the top and on the right
x x V
x O x
x x x
V - ViewRenderable
O - ModelRenderable
ViewRenderable.builder()
.setView(context, R.layout.VIEW_LAYOUT_XXXXX)
.setHorizontalAlignment(ViewRenderable.HorizontalAlignment.RIGHT) //Here is where the magic happens
.build()
.thenAccept(
(renderable) -> {
YOUR_NODE_XXXXX.setRenderable(renderable);
TextView textView = (TextView) renderable.getView();
textView.setText(YOUR_TEXT_XXXXX);
})
.exceptionally(
(throwable) -> {
throw new AssertionError(R.string.ERROR_MSG_XXXXX, throwable);
});
I let a _XXXXXin everything that you should change with your code.
Result should be like this
EDIT. Ok looking your code now this is what you should do:
Firs you need to inicialize your view (I think your "close button" should be your share button)
private fun Node.attachShareButton() {
if(shareButton == null){ //Create a new node to display the share button
var shareButton = Node()
shareButton.setParent(this)
shareButton.renderable = view.getViewRenderable()
shareButton.setLocalPosition(new Vector3(SIZE_OF_BUTTON, SIZE_OF_BUTTON, SIZE_OF_BUTTON));
}
if (infoCard == null) {
infoCard = new Node();
infoCard.setParent(this);
infoCard.setEnabled(false);
infoCard.setLocalPosition(new Vector3(0.0f, SIZE_OF_BUTTON*0.55f, 0.0f));
ViewRenderable.builder()
.setView(context, R.layout.share)
.setHorizontalAlignment(ViewRenderable.HorizontalAlignment.RIGHT)
.build()
.thenAccept(
(renderable) -> {
infoCard.setRenderable(renderable);
})
.exceptionally(
(throwable) -> {
throw new AssertionError(R.string.ERROR_MSG_XXXXX, throwable);
});
}
shareButton.onTap(HitTestResult hitTestResult, MotionEvent motionEvent){
if (infoCard == null) {
return;
}
infoCard.setEnabled(!infoCard.isEnabled()); //This show your card when the button is click/tap
}
}
Related
Hello All I am working on a task where I want to show the TextView if camera frame does not have any 3D model in google ar core. I am able to place the 3D model into the back camera ar scene view I just want when I move my camera to check if the current frame has a 3D model or not.
here is my code for placing the 3d Model
private fun onTapPlane(hitResult: HitResult, plane: Plane, motionEvent: MotionEvent) {
if (model == null) {
Toast.makeText(context, "Loading...", Toast.LENGTH_SHORT).show()
return
}
clearAllModel()
anchorNode = AnchorNode(hitResult.createAnchor()).apply {
// Create the transformable model and add it to the anchor.
addChild(TransformableNode(arFragment.transformationSystem).apply {
model?.isShadowCaster = false
model?.isShadowReceiver = false
translationController.isEnabled = true
scaleController.isEnabled = true
rotationController.isEnabled = true
renderable = model
// Add the View
})
}
// add the Anchor node
scene.addChild(anchorNode)
}
Here I am check when its detect the plane surface
private fun onUpdateListener(frameTime: FrameTime?) {
arSceneView.arFrame?.let { frame ->
val trackable = frame.getUpdatedTrackables(Plane::class.java).iterator()
if (trackable.hasNext()) {
val plane = trackable.next() as Plane
if (plane.trackingState == TrackingState.TRACKING) {
binding.tvPlane.text = ""
binding.ivImage.setImageResource(R.drawable.ic_back_camera_tutorial_hint)
} else {
Glide.with(requireContext()).load(R.raw.loop_new).into(binding.ivImage)
binding.tvPlane.text = ""
}
}
}
}
Hello All I am working on a task where I want to show the TextView if the camera frame does not have any 3D model in google ar core. I am able to place the 3D model into the back camera ar scene view I just want when I move my camera to check if the current frame has a 3D model or not.
Show different text for viewrenderable view .(e.g) fl measurement have 10,12,15 but all views are showing 15,15,15. I need to show the text views are 10,12,15. Last values is replacing in all the row. Please help me to fix this issue.
//this is my viewrenderable
ViewRenderable.builder()
.setView(this, R.layout.measure_view)
.build()
.thenAccept(renderable -> measureViewRenderable = renderable).exceptionally(
throwable -> {
Toast toast =
Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
return null;
});
//I will show the text message above the line using viewrenderable view
private void addLineBetweenHits(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
if (myanchornode != null) {
anchorNode.setParent(arFragment.getArSceneView().getScene());
point1 = myanchornode.getWorldPosition();
point2 = anchorNode.getWorldPosition();
/*
First, find the vector extending between the two points and define a look rotation
in terms of this Vector.
*/
final Vector3 difference = Vector3.subtract(point1, point2);
final Vector3 directionFromTopToBottom = difference.normalized();
final Quaternion rotationFromAToB =
Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());
node = new Node();
MaterialFactory.makeOpaqueWithColor(getApplicationContext(), new Color(0, 255, 244))
.thenAccept(
material -> {
/* Then, create a rectangular prism, using ShapeFactory.makeCube() and use the difference vector
to extend to the necessary length. */
ModelRenderable model = ShapeFactory.makeCube(
new Vector3(.01f, .01f, difference.length()),
Vector3.zero(), material);
/* Last, set the world rotation of the node to the rotation calculated earlier and set the world position to
the midpoint between the given points . */
node.setParent(anchorNode);
node.setRenderable(model);
node.setWorldPosition(Vector3.add(point1, point2).scaled(.5f));
node.setWorldRotation(rotationFromAToB);
}
);
myanchornode = anchorNode;
TransformableNode andy = new TransformableNode(arFragment.getTransformationSystem());
andy.setParent(anchorNode);
andy.setRenderable(measureViewRenderable);
TextView view=(TextView) measureViewRenderable.getView().findViewById(R.id.txtMeasure);
view.setText("" + fl_measurement.get(fl_measurement.size() - 1));
andy.select();
andy.getScaleController().setEnabled(false);
}
}
I was investigating this same topic, right now it seems we need to create different ViewRenderables by re-using ViewRenderable.builder() in order to have different data in each.
I like to know how can we reset the scaling of a node on button click,
I have added a TransformableNode with functionality to rotate and scale on users gesture
what I need is after all the transactions if the user clicks a button I need to reset the model to its initial size and rotation,
The rotation is working fine but the scaling doesn't have any effect, how can we do that?
Following is the method I Used to reset the node
private fun resetModel(){
modelDragTransformableNode.localRotation = Quaternion.axisAngle(Vector3(0f, 0f, 0f), 00f)
modelDragTransformableNode.localScale = Vector3(1f, 1f, 1f)
}
The method used to add the node to sceneview
private fun addNodeToScene(model: ModelRenderable) {
if (sceneView != null) {
val transformationSystem = makeTransformationSystem()
modelDragTransformableNode = NewDragTransformableNode(transformationSystem)
modelDragTransformableNode.localPosition = Vector3(0f, -0.3f, -2.0f)
modelDragTransformableNode?.renderable = model
val collisionShape: Box = modelDragTransformableNode.collisionShape as Box
sceneView.getScene().addChild(modelDragTransformableNode)
val collisionShape2: Box = modelDragTransformableNode.collisionShape as Box
modelDragTransformableNode.select()
sceneView.getScene()
.addOnPeekTouchListener { hitTestResult: HitTestResult?, motionEvent: MotionEvent? ->
transformationSystem.onTouch(
hitTestResult,
motionEvent
)
}
}
}
The scale is not updating because it's controlled by the ScaleController's onUpdated method. Try disabling the scale controller before setting the scale and then enable it again:
modelDragTransformableNode.getScaleController().setEnabled(false)
If it doesn't work you can always use a custom scale controller with exposed setScale method (see example).
I am placing a 2D image on vertical wall like a painting hanging on wall. But the rendered image is rotating by random angle. Its not properly aligned. I have integrated following code:
#RequiresApi(api = Build.VERSION_CODES.N)
private void createViewRenderable(WeakReference<ARRoomViewActivity> weakActivity) {
imageViewRenderable = new ImageView(this);
Picasso.get().load(paintingImageUrl)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageViewRenderable);
ViewRenderable.builder()
.setView(this, imageViewRenderable)
.build()
.thenAccept(renderable -> {
ARRoomViewActivity activity = weakActivity.get();
if (activity != null) {
activity.renderable = renderable;
}
})
.exceptionally(
throwable -> {
return null;
});
}
private void addToScene(HitResult hitResult) {
if (renderable == null) {
return;
}
Anchor anchor = hitResult.createAnchor();
anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
node = new Node();
node.setRenderable(renderable);
transformableNode = new TransformableNode(arFragment.getTransformationSystem());
node.setLocalRotation(Quaternion.axisAngle(new Vector3(-1f, 0, 0), -90f));
node.setLookDirection(new Vector3(0, 10f, 0));
transformableNode.setParent(anchorNode);
node.setParent(transformableNode);
transformableNode.select();
}
I am using following Sceneform version and also tried latest 1.17.0 but could not succeed
implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.7.0'
The orientation of anchors by default is such that one of the axes points somewhat towards the camera. This results in models placed on the floor to be "looking at" the user. However, models on the wall end up rotating arbitrarily. This can be fixed by setting the look direction of the node.
In your addToScene method, you can introduce an intermediate node that fixes the orientation relative to the anchor node. Children of the intermediate node will then be oriented correctly.
// Create an anchor node that will stay attached to the wall
Anchor anchor = hitResult.createAnchor();
anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
// Create an intermediate node and orient it to be level by fixing look direction.
// This is needed specifically for nodes on walls.
Node intermediateNode = new Node();
intermediateNode.setParent(anchorNode);
Vector3 anchorUp = anchorNode.getUp();
intermediateNode.setLookDirection(Vector3.up(), anchorUp);
Note: the last two lines in the code sample above are the key to getting the orientation correct.
This has me totally puzzled. I put this node in the scene, but as soon as I add "localRotation", the node moves. It looks fine if I don't move it (positioned centered where I expect). Do I have to initialize the Quaternion so that it's perpendicular to the floor plane/pose?
scene?.apply {
boundingBoxNode?.let { scene.removeChild(it) }
boundingBoxNode = null
viewModel.boundingBox?.let { box ->
MaterialFactory.makeTransparentWithColor(this#MyActivity, boundingBoxColor)
.thenAccept { material ->
boundingBoxNode = Node().apply {
renderable = ShapeFactory.makeCube(box.size, box.center, material).apply {
collisionShape = null
isShadowCaster = false
}
localRotation = box.rotation
scene.addChild(this)
}
}
}
FYI, don't set the center on ShapeFactory.makeCube(box.size, box.center). Do this instead (ShapeFactory.makeCube(box.size, Vector3.zero()) so that it's centered on the origin.
You can then do:
worldPosition = box.center to move it.
I was rotating a box that wasn't centered :facepalm: