awesome7 / XamAR

Cross-platform Augmented Reality (AR) SDK for Xamarin

Home Page:https://xamar.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to create TransformableNode ?

windofny1 opened this issue · comments

After load 3D model to scene and when it touched there is exception rised
"TransformableNode must have an AnchorNode as a parent"
So, now I'am trying to create TransformableNode:
var TransformableNode = new TransformableNode(X);
Where X is "TransformationSystem" class, how I can get to it ?
In some source code (XamAR.Platform.Android.Sceneform.Factories.FactoryWrapper.cs) found commented code:
//var d = ((DisplayAndroid)ServicesManager.Display);
// Fragment is needed for TransformableNode, so user creates
// basic Node, and we will wrap it (to provide touch events).
Node wrapNode = node; // new TransformableNode(d.Fragment.TransformationSystem);

But I can't found ServicesManager. I think we need to get to fragment, that holds our model, but also can't get to it.

Hi, thank your for trying XamAR SDK.

Can you provide code segments that you are using? Or, if you are willing to, it would be the best to zip complete solution and share it here.

PS.
ServiceManager is not designed to be accessed by the user.
Basic idea is that XamAR tries to handle as many platform details as possible, leaving user to write minimum code and focus on development (but also to be able to have more control when needed). That is the reason why not many things are available from the SDK.

Hi, thank your for trying XamAR SDK.

Can you provide code segments that you are using? Or, if you are willing to, it would be the best to zip complete solution and share it here.

PS. ServiceManager is not designed to be accessed by the user. Basic idea is that XamAR tries to handle as many platform details as possible, leaving user to write minimum code and focus on development (but also to be able to have more control when needed). That is the reason why not many things are available from the SDK.

Thank You too for your library )
So, here it is (I'am trying to load 3D model and also didn't find method for load it from yours library):

            var nodeSphere = new Node()
            {
                LocalScale = new Google.AR.Sceneform.Math.Vector3(0.5f, 0.5f, 0.5f),
                LocalPosition = new Vector3(0, 0, 0).ToAR()              
            };
           //this code block call method for load 3D model file (TestModel.sdf)
            var builder = ModelRenderable.InvokeBuilder();
            var javaClass = Java.Lang.Class.FromType(builder.GetType());
            var methods = javaClass.GetMethods();
            var method = methods[11];
            method.Invoke(builder, Platform.CurrentActivity, RealTK.Mobile.Droid.Resource.Raw.TestModel);
            builder.Build((m) =>
            {
                var model = m;
                MaterialFactory.MakeOpaqueWithColor(Android.App.Application.Context,
                    new Google.AR.Sceneform.Rendering.Color(1.00f, 0.87f, 0.62f)).ThenAccept(
                    new DelegateConsumer<Material>((m) =>
                    { 
                        nodeSphere.Renderable = model;
                    })
                );
              
            });

Other part of code is the same as provided examples, so full class code for this model:

    public class TestModelItem : ModelFactory
    {
        public override ARModel CreateModel()
        { 
            var nodeSphere = new Node()
            {
                LocalScale = new Google.AR.Sceneform.Math.Vector3(0.5f, 0.5f, 0.5f),
                LocalPosition = new Vector3(0, 0, 0).ToAR()              
            };

         //this code block call method for load 3D model file (TestModel.sdf)
 
            var builder = ModelRenderable.InvokeBuilder();
            var javaClass = Java.Lang.Class.FromType(builder.GetType());
            var methods = javaClass.GetMethods();
            var method = methods[11]; 
            method.Invoke(builder, Platform.CurrentActivity, RealTK.Mobile.Droid.Resource.Raw.TestModel);
             
            builder.Build((m) =>
            {
                var model = m;
                MaterialFactory.MakeOpaqueWithColor(Android.App.Application.Context,
                    new Google.AR.Sceneform.Rendering.Color(1.00f, 0.87f, 0.62f)).ThenAccept(
                    new DelegateConsumer<Material>((m) =>
                    { 
                        nodeSphere.Renderable = model;
                    })
                );
              
            }); 
            return nodeSphere.AsARModel();
        } 
    }

Then this model loaded in Factory

  FactoryService.RegisterFactory<TestModelItem>("TestModel");

Then in Xamarin Forms project it call like at example:

   var world = XamAR.World.Instance;
   var modelAR = world.CreateModel("TestModel");

And so on ... And when it loaded in application on device - It's rendered perfectly, but when I'am hold 3D model and try to move - get error
"TransformableNode must have an AnchorNode as a parent"
After search examples in native Android code, get this:

  TransformableNode andy = new TransformableNode(arFragment.getTransformationSystem());
     andy.setLocalScale(new Vector3(0.1f, 0.1f, 0.1f));
     andy.setParent(anchorNode);
     andy.setRenderable(andyRenderable);       

That's why I'am trying to find Fragment with ArView and it's transformation TransformationSystem

Great, thank you for this feedback!
It's a little bit busy period, give me couple of days to check and test everything, and i will get back to you.

@windofny1
Hi. Ok, i have tested the problem, and this is current state.

tldr;

  • only dragging object makes problem - because our objects are not connected to anchors (this event should be disabled by XamAR)
  • loading of 3d models is in our todo list (.sfb and .glb formats will be supported. I am surprised that you have successfully loaded .sdf)

Detailed explanation:

XamAR at this moment works only with GPS-coordinates. This means that all positions are calculated per-frame, relative to camera position (and orientation), and anchors are not used. This further means that objects are not connected to fixed position in real world (which is done by using anchors).
Using coordinates to position object in AR world is bad idea (by both Google and Apple), because coordinate system can be changed between frames, but since in XamAR positions are calculated for each frame, this is not concern (problem would be if coordinates for object are calculated once, and not recalculated for following frames - this where anchor is used, among other things it keeps object in position in real world). Geo positioning that we do is kind of special case of AR, which deviates from "regular" AR.

Now comes the exception "TransformableNode must have an AnchorNode as a parent".
This is something which comes from Android. By dragging you try to change position of the object (it can be considered as if you are trying to add offset from object's current position, which is defined in real world by an anchor). But, since our object is not positioned by Anchor but directly by coordinates, Android complains because it doesn't have "reference" to which offset should be added.

In our todo list is also support for various events (not directly but through our wrapper), as well as adding support for anchors (and therefore positioning objects close to camera, with anchors - "regular" AR).

@windofny1
And, just in case, after creating and registering Factory, all you need to do is:
var world = World.Instance; var model = await world.CreateModel("TestModel"); // Object is always 1 meter in front of the camera. var position = new RelativeToDeviceDirection(new Vector3(0, 0, -1)); world.AddModel(position, model);
PS. You can't access TransformableNode (to use it in the app), this is something that we are adding behind the scenes.
PS2. But, before we have that working in XamAR, and if you really want to explore more, you can wrap your Node in TransformableNode (set TransformableNode as parent) and return it as result from the factory. It is platform specific solution (which we are trying to avoid, but you can test it). TransformableNode has some events that you can listen to.

Events are not yet supported, except for Tap event for objects and for planes, but this is more proof of concept at this moment.
You can check them with
World.Instance.GlobalPressed
and
World.Instance.PlaneTapped


Thanks again, if you have more questions, feel free to ask.

Thanks for detail answer! I appreciate it!
Generally, in our project we don't need to drag or rotate object, just need to display it at GPS location, so Yours library satisfies this purposes.
Like temporary solution now we add in Android project MainActivity.cs handler for all exceptions

        {
        ...
            AndroidEnvironment.UnhandledExceptionRaiser += (s, e) =>
            {
                 e.Handled = true;
            };
        }

In Your last answer you mentioned "TransformNode":

PS2. But, before we have that working in XamAR, and if you really want to explore more, you can wrap your Node in TransformNode (set TransformNode as parent) and return it as result from the factory. It is platform specific solution (which we are trying to avoid, but you can test it). TansformNode has some events that you can listen to.

Did you mean "TransformableNode" ? I didn't find any "TransformNode"

P.s.: We created tutorial video with using yours library, maybe it will be interesting to you
https://youtu.be/tnPFNXws0WE

Did you mean "TransformableNode" ? I didn't find any "TransformNode"

Sorry for that, you are right, this was a typo. I edited the answer.

Nice video :) Few explanations you may find useful.
Why reflection is used? Something is wrong with SetSource method in Xamarin.Android (wrong binding or something), so when you use it in regular way, it will throw exception at runtime that method is not found. But, reflection works well. 11-th method is SetSource with int as second parameter. You can also get this method with reflection by providing type parameters. You can see both approaches in link below.

Here you can check my answer on issue for SetSource (not that 13-th method is used) xamarin/XamarinComponents#833 (comment) .
Keep in mind that solution provided in the comment stores models in Assets folder, and is referenced by relative path (and not by id, as in Resources folder).
There you can also see how to load .glb file, which is much better solution because you can export it directly from 3d modeling software, and import without any conversions (like for .sfb, where you need Android Studio up to 3.5, and much time to wait for compilation).

Perfect! That's much better ) Thanks for provided solutions!!

@windofny1

Like temporary solution now we add in Android project MainActivity.cs handler for all exceptions

Great that this "hack" works. Although, you are also aware of it but for other readers, this is very poor practice for production environments. But until we handle that in XamAR library itself, this is best workaround.

Thank you for your feedback, it was very useful, and gave us some meaningful information! It will definitely be helpful for next versions (although we've been little recently bit slow on development).

PS. I missed somehow, can you explain for what you are using Xam.Plugin.Geolocator? XamAR has internal solution for getting GPS location of the device, so i'm little bit curious about your use case.

PS. I missed somehow, can you explain for what you are using Xam.Plugin.Geolocator? XamAR has internal solution for getting GPS location of the device, so i'm little bit curious about your use case.

In other our projects we prefered to use "Xam.Plugin.Geolocator", it's just architecture decision. We have one point, where we get location using dependency injection:

private ILocationService _LocationService;
 ....
    var loc = await _LocationService.GetCurrentLocation();
            if (loc.Result == Interfaces.DefaultEnums.Result.ok)
            {
                     // loc.Latitude,
                     // loc.Longitude,
                    // loc.Altitude
            }
 ...
 services.AddSingleton<ILocationService, GPSLocationService>();
 ...
 public class GPSLocationService:ILocationService
    {
        private IMappingService _MappingService;
        public GPSLocationService(IMappingService MappingService)
        {
            _MappingService = MappingService;
        }
        public async Task<IBaseLocation> GetCurrentLocation()
        {
             var locator = CrossGeolocator.Current;
            if (locator != null && locator.IsGeolocationAvailable && locator.IsGeolocationEnabled)
            {
                var pos = await locator.GetPositionAsync(TimeSpan.FromSeconds(10));
                return _MappingService.Map<BaseLocation, Position>(pos);
            }
            else
            {
                return BaseModelUtilities<BaseLocation>.Error(new Exception("No location info available"));
            }
        }
    }

Great that this "hack" works. Although, you are also aware of it but for other readers, this is very poor practice for production environments. But until we handle that in XamAR library itself, this is best workaround.

Yes, it's dangerous way, may product memory leaks and perfomance problems. It's just prototype for a quick hand, but inside this handler can add handling for current exception type and do some memory cleaning, or prouduce some response on error and so on..)
So, if I get something new for this case, will leave comments!

Great, thanks again!