jeromeetienne / AR.js

Efficient Augmented Reality for the Web - 60fps on mobile!
MIT License
15.8k stars 2.22k forks source link

AR.js should be loadable as a module (import / require) #428

Closed mpacary closed 4 years ago

mpacary commented 6 years ago

Do you want to request a feature or report a bug? feature

What is the current behavior? Using <script> is required to load AR.js.

What is the expected behavior? When using npm & webpack, be able to simply do npm i ar.js then add in your code

import ARjs from 'ar.js' // or const ARjs = require('ar.js') if using requireJS

// basic example adapted... (original example code uses name "THREEx" instead of "ARjs")
var arToolkitContext = new ARjs.ArToolkitContext({ ... })
var hiroMarker = new ARjs.ArMarkerControls(arToolkitContext, markerRoot, {
    type : 'pattern',
    patternUrl : someBaseURL + 'patt.hiro'
})

Currently, it does not load anything & triggers errors, e.g. with AR.js 1.6.2 & VueJS 2.5.16: can't resolve 'ar.js'

If this is a feature request, what is motivation or use case for changing the behavior? Because the vast majority of npm packages work like that nowadays (+ webpack relies on this behavior, most common frameworks rely on webpack...). AR.js is currently 928 KB minified and should be only loaded when necessary ; webpack helps for this. As well, other AR.js users are expecting the same behavior, see #251 (latest comments), and maybe #238 Check this article too.

Adding a "main" property to AR.js package.json file + a module.exports = ... instruction in main file three.js/build/AR.js might worth a try.

Useful resources:

mpacary commented 6 years ago

I see something very interesting in three.js/build/ar.js:

// UMD
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['WebAR'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.WebAR = factory();
  }
}(this, function() {
    return THREE.WebAR;
}));

Trying to make this work by tweaking package.json & source... Will keep track of my investigations here...

mpacary commented 6 years ago

Got it to work with main build file three.js/build/ar.js 🎉

Steps to follow:

  1. Edit package.json file
  1. Edit file three.js/build/ar.js

I hope those hints will help to address this issue.

Perhaps there should be two builds:

droid001 commented 6 years ago

Great work! Now the Module not found: Error: Can't resolve 'WebAR' error is gone, but for me the fs error in three.js still remains:

ERROR in ./node_modules/ar.js/three.js/build/ar.js Module not found: Error: Can't resolve 'fs' in '/Users/me/Development/webxr/node_modules/ar.js/three.js/build'

To give some context I'm building using webxr-webpack-boilerplate

mpacary commented 6 years ago

@droid001 which version of AR.js are you using? For my investigations I've used version 1.6.2

droid001 commented 6 years ago

@mpacary Same here. v1.6.2.

droid001 commented 6 years ago

I've got it working now without the fs warning. Not sure what could have been the issue but I have updated yarn to v1.10.1 and webxr-webpack-boilerplate to v0.9.16.

DarkAce65 commented 6 years ago

FYI I was having the same Error: Can't resolve 'fs' issue with a custom webpack config and I solved the issue by adding

node: {
    fs: 'empty'
}

to my config.

Got it from here: https://webpack.js.org/configuration/node/#other-node-core-libraries

droid001 commented 5 years ago

@mpacary : Did you manage to get a functioning project running with what you've got? I'm getting a blank screen with no errors and aframe and artoolkit loaded. Am I missing some initialisation which you reference in you first comment? How are you pulling in the libraries? I've got something like this (simplified code and vendor.js contains a compiled version aframe)

    <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
    <a-anchor hit-testing-enabled='true'>
      <a-box position='0 0.5 0' material='opacity: 0.5; side:double; color:red;'>
        <a-torus-knot radius='0.26' radius-tubular='0.05'>
          <a-animation attribute="rotation" to="360 0 0" dur="5000" easing='linear' repeat="indefinite"></a-animation>
        </a-torus-knot>
      </a-box>
    </a-anchor>

    <a-camera-static/>
  </a-scene>
  <script>
    import ARjs from 'ar.js';

    console.log("ARjs : " , ARjs);
  </script>
</body>

The console logs out ARjs containing two objects THREEx and WebAR. Global variables include artoolkit, ARCameraParam, ARController and AFRAME. My dev server is running on https so there shouldn't be a problem accessing the webcam.

mpacary commented 5 years ago

@droid001 sorry for the late reply.

I've managed to get AR.js loaded and running, in a project using Quasar Framework 0.16, which uses VueJS 2.5 and Webpack 3.11.

However, I did not need to combine A-Frame and AR.js at all for my project (<a-scene arjs>...)

Where did you take the example from? From the working example, list what you get as global variables when you include <script src=".../ar.js"> and try to "redefine" those globals after retrieving ARjs object using import.

For example, I see in ar.js file that maybe other global vars should be set (I may have missed some): AR CV POS SVD THREEx THREE (Three.js lib) ARjs

You may need to export all of those objects (=> change accordingly return { WebAR: THREE.WebAR, THREEx: THREEx }; near line 4390 to have return { AR: AR, CV: CV, ......, ARjs: ARjs };)

Then in your main example file, loop through all those properties and define them as globals Should be looking like:

import ARjs from 'ar.js';
for (obj in ARjs) {
  window[obj] = ARjs[obj];
}

Note: to avoid naming collision with ARjs global variable, maybe you should do import _ARjs from 'ar.js'; for (obj in _ARjs) { ... } since that loop should declare a global variable ARjs...

droid001 commented 5 years ago

@mpacary : How are you getting the ARjs object in your UMD declaration? The below code returns undefined for ARjs but works well for the other parameters:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root = factory();
  }
}(this, function() {
    // WARNING: this is a suggestion, AR.js team might decide to use different namings for exports
return {
    ARjs: ARjs,
    WebAR: THREE.WebAR,
    THREEx: THREEx,
    AR: AR,
    CV: CV,
    POS: POS,
    SVD: SVD
};
}));

Here's the import statement which I'm using :

import _ARjs from 'ar.js';

// Setting globals from ARjs
for (let obj in _ARjs) {
  window[obj] = _ARjs[obj];
}

The global window object looks like this then

AR: Object { Marker: Marker(), Detector: Detector()}
ARCameraParam: function ARCameraParam()
ARController: function ARController()
ARjs: undefined
.
.
.

This is a bit strange as var ARjs is defined several times and should be accessible in the UMD declaration stage.

As for the demo code I'm just using the first demo on the AR.js project page.

mpacary commented 5 years ago

@droid001 in the UMD section, what gives a console.log(ARjs) right before the return?

droid001 commented 5 years ago

@mpacary undefined. How does it look like in your end?

mpacary commented 5 years ago

Same behavior here.

After playing around a few more minutes, it looks like the position of the UMD section in the file matters (!) Move it to the end of ar.js built file, and suddenly when doing console.log(ARjs), the object is returned with all its properties & methods defined.

Downside: I get a few errors from ARjs at (hot) loading, related to a method buildSubMarkerControls().

buildSubMarkerControls center 0 0
Assertion failed: console.assert
buildSubMarkerControls topleft NaN NaN
Assertion failed: console.assert
buildSubMarkerControls topright NaN NaN
Assertion failed: console.assert
buildSubMarkerControls bottomleft NaN NaN
Assertion failed: console.assert
buildSubMarkerControls bottomright NaN NaN
Assertion failed: console.assert

Tracking the "assertion failed" errors is just awful, you have to dig in some minified webpack-generated code... I can't figure it out 😕 (found the console.assert() calls, but following the stack trace to see where this is called from is just impossible)

However everything else behaves "as normal".

droid001 commented 5 years ago

@mpacary Good work! I attempted the same with aframe-ar.js as the parameters are quite much the same if you look at the globals in a project which runs on aframe-ar.js. Unforunately there's something more behind the sceens as I get the following errors:

TypeError: AFRAME.registerSystem(...) is not a function
ReferenceError: signals is not defined

This is a bit surprising because aframe is loaded in before aframeAR.

import 'aframe';
import _aframeARjs from "../../node_modules/ar.js/aframe/build/aframe-ar.js";

for (let obj in _aframeARjs) {
  window[obj] = _aframeARjs[obj];
}
mpacary commented 5 years ago

Sorry I dont have a lot of time to investigate, but by looking at the code my guess is that aframe-ar.js requires the global AFRAME to be defined, and that happens only by loading the A-Frame lib using <script src=".../aframe.js">, instead of using import.

Maybe you will have to do something like import AFRAME from 'aframe' or import _aframe from 'aframe' and check what _aframe contains, the goal being to instantiate AFRAME object at global level.

Concerning the "signals" error, I see a minified version of that lib in aframe/build/aframe-ar.js (like in the ar.js built file), which is used for example line 7034, but maybe not "globally instanciated" in the context of a module. Can you provide the full stack trace of the error?

Note that like for ar.js built file, you will have to tweak aframe-ar.js before being able to load it using import


Another, more "modern" approach, would be to adapt aframe-ar.js to declare A-Frame as a dependency and use import ... from 'aframe', register its "aframe components", and export the modified aframe object, but thats looks quite a lot of work (modify /aframe/src/... files accordingly and create a PR) 😉

droid001 commented 5 years ago

I'm in the same boat, not having enough time to really dig into this problem.

Had a look how aframe places things into the global scope and rewrote the ARjs module export like this:

module.exports = Object.assign(window,
    {
    ARjs: ARjs,
    WebAR: THREE.WebAR,
    THREEx: THREEx,
    AR: AR,
    CV: CV,
    POS: POS,
    SVD: SVD
});

That way you don't have to pick out the _ARjs parameters into the global window object and you can import it easily import 'ar.js' along with import 'aframe'. I agree that creating a dependency to aframe and linking to it would be the real way of doing this. But yeah, lacking skills and time.

It worked similarly with aframe-ar.js just that I had to reference it directly like import '../../node_modules/ar.js/aframe/build/aframe-ar.js'; which isn't too portable but better than nothing. Still have problem with that minified signals library which doesn't expose itself globally in the aframe-ar.js. Don't really understand how ARjs.Session gets a hold of it on line 7023:

ARjs.Session = function(parameters){
    var _this = this
    // handle default parameters
    this.parameters = {
        renderer: null,
        camera: null,
        scene: null,
        sourceParameters: {},
        contextParameters: {},
    }

    this.signals = {
        sourceReady : new signals.Signal(),
        contextInitialized: new signals.Signal(),
    }
…

The stacktrace I get from the error is:

ReferenceError: signals is not defined[Read more]      aframe-ar.js:8849
./node_modules/ar.js/aframe/build/aframe-ar.js/        <aframe-ar.js:8849
./node_modules/ar.js/aframe/build/aframe-ar.js        https://localhost:9000/app/js/app.js:79412:29
__webpack_require__        bootstrap:19
./src/js/app.js        app.js:4
__webpack_require__        bootstrap:19
0        https://localhost:9000/app/js/app.js:159505:1
__webpack_require__        bootstrap:19
<anonymous>        bootstrap:83
<anonymous>        https://localhost:9000/app/js/app.js:1:11
jgipper commented 5 years ago

Great thread, landing on the same issues importing aframe and ar.js into Vue components.

mpacary commented 5 years ago

@droid001

That way you don't have to pick out the _ARjs parameters into the global window object and you can import it easily import 'ar.js' along with import 'aframe'.

Sounds Ok as a quick & dirty attempt 😉

It worked similarly with aframe-ar.js just that I had to reference it directly like import '../../node_modules/ar.js/aframe/build/aframe-ar.js';

FYI, to avoid issues when doing npm update (which modifies node_modules content), I've copied the modified ar.js file in a separate directory so I can do something like import * from 'mydir/ar'

Concerning the error about SignalsJS, after looking at unminified code of Signals JS, for a "quick fix", I think you should to replace the "exporting section" of SignalsJS line ~4398 (typeof define==="function"&&define.amd?define(e):typeof module!=="undefined"&&module.exports?module.exports=e:h.signals=e) by only the final instruction (h.signals=e), to force a global declaration of the "signals" object (unminified equivalent global['signals'] = signals;). If this fails, another attempt would be to replace the export section by window.signals = e.

I advise to add some console.log(signals) in aframe-ar.js file, right after the minified signals "declaration" (line ~4400) and just before the instruction this.signals = { ... sourceReady : new signals.Signal(), ... } (line ~7033) to check if things are going well.

Good luck 😉

leyyinad commented 5 years ago

This should really be fixed in this repo. What is the npm package currently good for?

mpacary commented 5 years ago

@leyyinad Currently the npm package doesn't looks very useful... Since there is an UMD section, it seems like an aborted attempt of making a "real" module.

saschagehlich commented 5 years ago

@jeromeetienne @nicolocarpignoli Are there any plans on making this a proper NPM module with dependencies and all?

nicolocarpignoli commented 5 years ago

Unfortunately, I have no time for this, I'm only here to provide help when I can with tips and other quick things. If you guys have time and will to make a PR for this, I will personally review it and eventually merge it

AnastasiiaaaaM commented 5 years ago

Had the same issue with

Can't resolve 'WebAR'

Now it says:

aframe-ar.js:1373 Uncaught TypeError: Cannot read property 'slice' of null

Any ideas how to fix this? Thanks!

I'm using: "three": "^0.101.1", "aframe": "^0.9.0", "ar.js": "^1.6.2"

Have also tried with "three": "^0.96.0", "aframe": "^0.8.2", "ar.js": "^1.6.2" -> the same issue

taime commented 4 years ago

Could somebody please share the example of using AR JS with Webpack or Browserfy ? I have to use require in my project (for three-js model loaders and also for filesaver.js) but got errors from ar.js file in webpack when trying to build project... So.. does anybody have working example?

nicolocarpignoli commented 4 years ago

we can continue here: https://github.com/AR-js-org/AR.js/issues/21