Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AR autogenerate USDZ #2374

Merged
merged 9 commits into from
May 11, 2021
Merged

AR autogenerate USDZ #2374

merged 9 commits into from
May 11, 2021

Conversation

kolodi
Copy link
Contributor

@kolodi kolodi commented May 11, 2021

Reference Issue #2373

Adding USDZ generation on the fly as an alternative to static ios-src (maintain both ways)

@elalish
Copy link
Collaborator

elalish commented May 11, 2021

Thanks, I notice you reference my own PR on this topic; can you give a bit of detail of how yours is different or what it adds?

@kolodi
Copy link
Contributor Author

kolodi commented May 11, 2021

Thanks, I notice you reference my own PR on this topic; can you give a bit of detail of how yours is different or what it adds?

Hello, yes.
I was preparing my PR and then realized that you have already the similar update ready.

I checked your implementation, the key differences in mine are:

  1. using of a custom property to enable/disable autogeneration on the fly
  2. releasing the generated URL after src is being changed otherwise continue using a previously generated one
  3. disabling the AR button (in case user clicks many times on it during the export phase)
  4. showing loading bar (for older devices there can be a noticeable time for export or the model can be heavy)
  5. using a slightly different way for Blob creation
  6. disabling the shadow in the scene before exporting, similarly to how it is during glTF export in scene graph.

@@ -327,7 +331,12 @@ configuration or device capabilities');
* Takes a URL to a USDZ file and sets the appropriate fields so that Safari
* iOS can intent to their AR Quick Look.
*/
[$openIOSARQuickLook]() {
async[$openIOSARQuickLook]() {
if (this.arAutogenerateUsdz && !this.iosSrc) {
Copy link
Contributor Author

@kolodi kolodi May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generate USDZ on the fly only if autogenerate property is set and there is not ios-src.
Also disable temporally the AR button so the user cannot press it multiple times during the export.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the documentation updates in my PR and let me know what you think of my alternative solution to this.

Copy link
Contributor Author

@kolodi kolodi May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the export of USDZ has a tons of limitations, I'm very aware of them and I had a really hard time creating dozens of workarounds about them. That was actually one of the main reasons why I wanted to leave a possibility to disable the on the fly generation, sometimes you still want your static prepared ahead USDZ file or set it manually from a pool of static variants or something like this, in any case we need a mechanism to turn it on/off

@elalish
Copy link
Collaborator

elalish commented May 11, 2021

Fantastic! 2-6 are great; I simply hadn't gotten around to those yet. I think I prefer my method of keeping this from being a breaking change without introducing a new attribute. Do you agree? If so, I think merging our PRs would be ideal. Would you like to pull mine into yours and I can review?

@@ -72,6 +73,8 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
ModelViewerElement: T): Constructor<ARInterface>&T => {
class ARModelViewerElement extends ModelViewerElement {
@property({type: Boolean, attribute: 'ar'}) ar: boolean = false;
@property({type: Boolean, attribute: 'ar-autogenerate-usdz'})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set this property to true if you want to enable on the fly USDZ generation

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you see my comment about doing this without introducing a new attribute? If you look at my PR, I changed the logic a bit so that the presence of ios-src automatically enables Quick Look, while explicitly adding the quick-look ar-mode opts in to USDZ generation. I think I prefer that approach; what do you think? Would you mind merging in that part of my PR here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, quick-look presence can be enough. I also added blob clean up before creating a new one (see the commit)

@@ -340,9 +349,50 @@ configuration or device capabilities');
const img = document.createElement('img');
anchor.appendChild(img);
anchor.setAttribute('href', modelUrl.toString());
if (this.arAutogenerateUsdz) {
anchor.setAttribute('download', 'model.usdz');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this tricks Safari to open the Quick Look. the file name has been chosen the same as it is in USDZExporter.js, although it could be anything.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my testing it wasn't necessary on my iPhone SE. But it also probably doesn't hurt anything.

packages/model-viewer/src/features/ar.ts Outdated Show resolved Hide resolved
@@ -340,9 +349,50 @@ configuration or device capabilities');
const img = document.createElement('img');
anchor.appendChild(img);
anchor.setAttribute('href', modelUrl.toString());
if (this.arAutogenerateUsdz) {
anchor.setAttribute('download', 'model.usdz');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do? Is it necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I prepare the Blob, I use simply "application/octet-stream"for the type. So Safari need a "hint" to treat the link as a valid source to open Quick Look, that's why there is a download attribute. As a side effect you can also test the export in desktop browser as it will download the usdz file for you.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, interesting. I think it worked for me because I used a more specific mime type; you can see in my PR. The button still doesn't appear on desktop Safari though, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you have to manually override IS_AR_QUICKLOOK_CANDIDATE constant :)

type: 'application/octet-stream',
});

this.iosSrc = URL.createObjectURL(blob);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like this internal detail being publicly visible; maybe better to create a new private symbol instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean showing the loading bar? I was initially thinking about showing some custom poster but then decided to just show some simulated (0.2) progress. On latest iPhone it is quite immediate to export a simple scene, but on older devices or when the scene is quite complex, it might require more time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the loading bar is great. It's just that this.iosSrc is part of our public API. If someone replaced it without revoking it, bad things would happen. I just mean we should store our blob as a private member instead.

Copy link
Contributor Author

@kolodi kolodi May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok. Infact, ios-src continue to store only the url string not the actual blob. I need to check if when we call URL.revoke... if it actually dispose the blob and to not create a memory leak here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the Blob is garbage collected after URL.revokeObjectURL is called?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is probably still a good thing to store blob url in separate member. Let me make those changes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

packages/model-viewer/src/features/ar.ts Show resolved Hide resolved
Copy link
Contributor Author

@kolodi kolodi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more details using comments in code

@mrdoob
Copy link
Collaborator

mrdoob commented May 11, 2021

  1. showing loading bar (for older devices there can be a noticeable time for export or the model can be heavy)

You may want to try with the dev version of USDZExporter in Three.js. I think it should run faster now.

@elalish
Copy link
Collaborator

elalish commented May 11, 2021

@mrdoob Honestly it already feels faster than downloading the separate USDZ, even on my old SE. I'd rather stick to released versions :). We'll update soon enough anyway.

const modelUrl = new URL(this.iosSrc!, self.location.toString());
const modelUrl = this.arAutogenerateUsdz ?
new URL(this[$generatedIosUrl]!, self.location.toString()) :
new URL(this.iosSrc!, self.location.toString());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put the ternary op inside of the new URL() to simplify this a bit.

@@ -72,6 +73,8 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
ModelViewerElement: T): Constructor<ARInterface>&T => {
class ARModelViewerElement extends ModelViewerElement {
@property({type: Boolean, attribute: 'ar'}) ar: boolean = false;
@property({type: Boolean, attribute: 'ar-autogenerate-usdz'})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you see my comment about doing this without introducing a new attribute? If you look at my PR, I changed the logic a bit so that the presence of ios-src automatically enables Quick Look, while explicitly adding the quick-look ar-mode opts in to USDZ generation. I think I prefer that approach; what do you think? Would you mind merging in that part of my PR here?

[$openIOSARQuickLook]() {
const modelUrl = new URL(this.iosSrc!, self.location.toString());
async[$openIOSARQuickLook]() {
if (this.arAutogenerateUsdz && !this[$generatedIosUrl]) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I just noticed a problematic aspect of your feature 2: if the file changes due to the scene-graph API (switched variant, swapped texture/color etc) and you jump into Quick Look again, you won't see the changes due to caching the USDZ. We could either skip the cache entirely (I don't think the perf change is a huge deal, especially since going into Quick Look twice with the same model is probably pretty rare), or else we'd have to add some kind of isDirty for the scene-graph functions that would tell us to regenerate the USDZ. I think simpler is better, especially for the first version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the scene-graph I can see that this is used when changing something:

[$needsRender]() {
  this[$scene].isDirty = true;
}

We can override this method in AR mixin to revoke our URL

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but the chance of breaking things is high. Again, I don't think the perf gain here is enough to justify the complexity. Let's just skip the USDZ caching and call it a win on memory footprint. 😁

Copy link
Contributor Author

@kolodi kolodi May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, for now, let's make those bionic chips do their job :). I really hope they will finally let us use WebXR on iOS directly so we can avoid using USDZ for good :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here here! Someday...

Copy link
Collaborator

@elalish elalish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking just about ready to merge, thanks for the quick turn around! I'll remove the code changes from my PR and merge it afterwards just for the docs/examples updates. Can you please pull master and resolve whatever small conflict has arisen?

@@ -53,6 +54,7 @@ const $arMode = Symbol('arMode');
const $arModes = Symbol('arModes');
const $arAnchor = Symbol('arAnchor');
const $preload = Symbol('preload');
const $generatedIosUrl = Symbol('generatedIosUrl');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is vestigial now.

} else if (
value === 'quick-look' && !!this.iosSrc &&
IS_AR_QUICKLOOK_CANDIDATE) {
} else if (value === 'quick-look' && IS_AR_QUICKLOOK_CANDIDATE) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra logic block we'll need below this from my PR. You can either pull it in, or I can add it in mine with the docs update.

if (this.arScale === 'fixed') {
if (modelUrl.hash) {
modelUrl.hash += '&';
}
modelUrl.hash += 'allowsContentScaling=0';
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read somewhere that Safari doesn't like hashes on blob URLs. Have you tried arScale="fixed" to see if it actually works in QuickLook? I'm not very concerned if it doesn't, so long as it doesn't crash, but I'd be pretty stoked if I'm wrong.

@kolodi
Copy link
Contributor Author

kolodi commented May 11, 2021

This is looking just about ready to merge, thanks for the quick turn around! I'll remove the code changes from my PR and merge it afterwards just for the docs/examples updates. Can you please pull master and resolve whatever small conflict has arisen?

Ok, I'll try now

@kolodi
Copy link
Contributor Author

kolodi commented May 11, 2021

This is looking just about ready to merge, thanks for the quick turn around! I'll remove the code changes from my PR and merge it afterwards just for the docs/examples updates. Can you please pull master and resolve whatever small conflict has arisen?

Ok, I'll try now

I have merged your usdz branch and (hopefully) resolved all conflicts correctly :)

Copy link
Collaborator

@elalish elalish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This is a really important feature.

@hybridherbst
Copy link
Contributor

A question @elalish since this has been merged already: how would one now set the URL parameters on the USDZ file for configuring checkoutTitle etc.? There's similar URL parameters for Quick Look as for Scene Viewer, and we're using those a lot.

One idea here would be: could the ios-src still be set but contain a "magic" string for "insert autogenerated USDZ here"? This way the URL parameters would "just work".

@elalish
Copy link
Collaborator

elalish commented May 17, 2021

Actually, could you give that a test for me first? I read that Safari doesn't handle query params on blob URLs, so I figured this wasn't going to work. If it does, I'll be happy to find a way to enable it.

@kolodi
Copy link
Contributor Author

kolodi commented May 18, 2021

how would one now set the URL parameters on the USDZ file for configuring checkoutTitle etc.? There's similar URL parameters for Quick Look as for Scene Viewer, and we're using those a lot.

You mean parameter after a hashtag (docs), hmm, I never used them. Do they really work?

One can set ios-src with a string starting with "#", then we can check that this is a "hashtag part" only and not a valid URL therefore generate the USDZ on the fly and add the "hashtag part" to the link.

There is also a problem with the link being destroyed immediately after the click event, so your listener for "message" would not work... but I think we can delay destroying the link to the point when user turns back to the web page.

@milesgreen
Copy link
Contributor

Actually, could you give that a test for me first? I read that Safari doesn't handle query params on blob URLs, so I figured this wasn't going to work. If it does, I'll be happy to find a way to enable it.

Ah. Maybe this is the cause of #2405 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants