aframevr / aframe

:a: Web framework for building virtual reality experiences.
https://aframe.io/
MIT License
16.68k stars 3.98k forks source link

scene manipulations during event callbacks #1902

Open blairmacintyre opened 8 years ago

blairmacintyre commented 8 years ago

In my ar-scene setup, I want to replace the default camera entity with my own "ar-camera" entity, if the user did not create one in the scene.

So, in the scene's "loaded" callback (play method, actually), I grab a list of cameras, and look for an "ar-camera" entity. This is all good.

BUT, I also want to disable any camera that is already there by setting active=false.

The problem is that the camera system ALSO has a "loaded" callback, in which it creates a default camera (if there isn't one) and attaches it to the scene.

Unfortunately, the a-node attachedCallback does not seem to run for these new nodes until after all of the various "loaded" handlers are run. Which means that this.sceneEl in these nodes is not set, so when I call cameraEl.setAttribute('camera', 'active', false); on the default camera that the camera system created, it throws an error inside the camera's init method (since setAttribute calls updateComponent which calls init on the uninitialized component (in a-entity.js).

It seems that there needs to be something in there to check (during init, I guess?) it's it's attached BUT the attached callback hasn't been called? If so, do the same work (like setting the sceneEl)?

ngokevin commented 8 years ago

Maybe because the default camera is created after the scene is loaded...to check for existing cameras.

There is an event: camera-set-active. Does that help?

blairmacintyre commented 8 years ago

The problem is that I'm running my code in the "loaded" callback; I have set up an event on "camera-set-active" but it wasn't running for the default camera because the event is fired in its "loaded" callback, before my loaded callback is executed ... at least that's what I think is happening, since my handler isn't running for the default camera. To solve this, I also ran the same callback once in my loaded callback, which ran into the problem above.

ngokevin commented 8 years ago

A bit hard to follow all these callbacks (our fault!). So you can't move code outside the loaded callback and into the camera-set-active callback?

blairmacintyre commented 8 years ago

I know, it's fun (if confusing)...

I was setting the camera-set-active callback up in loaded ... I only want to check for cameras after the scene is loaded, in case the programmer has created an "ar-camera". I guess I could try a different tack, setting the callback up at init, and watching for an ar-camera, and then creating the ar-camera in loaded.

Although, while this may work, it side steps the larger problem; I don't think the setAttribute command should be raising an error. Perhaps "init" on the node should check if the node is attached to the DOM and check for the existence of a scene (if sceneEl isn't set) and set it (as is done in in AttachedCallback) if it's not set.

ngokevin commented 8 years ago

You can check for nodeready which is emitted after attach...but ideally we shouldn't have to do that. If you write your code within components/systems, you should be assured that everything has been set up. I think I'd need full code to give more helpful guidance though.

blairmacintyre commented 8 years ago

I really don't want to set up a nodeready callback on each of these cameras, although I guess I could do that (check if sceneEl is null, if so set up a callback to do the "active = false" when it's ready). Yuck. :)

The code is my argon-aframe stuff (github.com/argonjs/argon-aframe). The offending code is in the "play" method of ar-scene.js. Right now, the code in the repo is only using a callback on camera-set-active, which isn't getting called for the default camera ... all the time. The weird thing is, it was getting called sometimes, but I discovered it not getting called in a more complex app I was building which starts out with an empty ar-scene and adds to it programmatically.

I replaced the addEventListener in that play method with a function that I set on the listener, and call:

          var fixCamera = function () {
            var arCameraEl = null;
            var cameraEls = self.querySelectorAll('[camera]');
            for (i = 0; i < cameraEls.length; i++) {
                cameraEl = cameraEls[i];
                if (cameraEl.tagName === "AR-CAMERA") { 
                  arCameraEl = cameraEl;
                  continue; 
                }
                cameraEl.setAttribute('camera', 'active', false);
                cameraEl.pause();
            }

            if (arCameraEl == null) {
                var defaultCameraEl = document.createElement('ar-camera');
                defaultCameraEl.setAttribute(AR_CAMERA_ATTR, '');
                defaultCameraEl.setAttribute(constants.AFRAME_INJECTED, '');
                self.appendChild(defaultCameraEl);
            }
          }
          // if there are any cameras aside from the AR-CAMERA loaded, 
          // make them inactive.
          this.addEventListener('camera-set-active', fixCamera);
          fixCamera();

The cameraEl.setAttribute('camera', 'active', false);ends up raising an exception because the cameraEl is set to the default camera, which hasn't had it's attachCallback method called yet, so it's not ready.

donmccurdy commented 8 years ago

Is it feasible to remove invocations of init() from inside setAttribute()? This also bit me in #1900.

Another option, specific to this issue, might be some scene-level config to prevent injection of default cameras.

ngokevin commented 8 years ago

Maybe we can also let it look for data-aframe-camera...?

blairmacintyre commented 8 years ago

Ok, I leveraged the nodeready event to work around this issue. It's kinda ugly though.

7dir commented 7 years ago

@blairmacintyre working code? please

trusktr commented 6 years ago

Seems like there should be a simpler way to allow a-frame users to specify what the "default camera" for a scene should be, rather than them overriding the default camera after it has already been added to the scene.

f.e. <a-scene default-camera="other-type-of-camera"> and if no camera is specified in the HTML, then the first and only camera will be other-type-of-camera without having overriden some other camera.