iFrame + rotation camera problem


Hi all, I am having a problem and I have been trying things for a week with no results. This bug happens to me when I am dragging the mouse to rotate the camera and I enter the area where I contain the iFrame video (still pressing the mouse). When I stop pressing, it is as if I do not detect or ignore and the camera stays stuck following the mouse all the time.
Any type of information or idea on how to solve this is appreciated, I already tried a lot of things and I can’t understand if the problem is in iframe or in lookcamera or where I can fix it.
Attached a video:

The issue is that the mouse has entered into the iframe and has exited where you were dragging before.

You can remove all pointer events from the iframe using CSS but that would make the iframe not interactable.

Or you can add/remove the event from the iframe on mouse down/up in the main window.

So exactly where I can remove it? I make a button to play/stop the vidrio but using the videos inside playCanvas, but it’s to heavy so decide to put them on Vimeo but this stat happening. Can I make that button work on this iFrame too??

Thanks a lot for the answer, I’m not a full programer jaja i’m doing my best

I’m afraid I’m too busy to give an example unfortunately.

It sounds like you want this:

So when you mouse down outside the iframe, remove the pointer event detection on the iframe element.

And on mouse up, add them back again so you can click on the play button in the iframe.

1 Like

this is the iFrame script i got:

var IframePlane = pc.createScript('iframePlane');
IframePlane.attributes.add('iframeUrl', {type: 'string'});
IframePlane.attributes.add('pixelsPerUnit', {
    type: 'number', 
    default: 640, 
    description: 'Number of canvas pixels per unit of world space. The larger the number, the higher the resolution of the iframe.'
});


// initialize code called once per entity
IframePlane.prototype.initialize = function() {
    // WARNING: IframePlane does not work with touch events
    
    var element;
    
    if (this.iframeUrl) {
        element = document.createElement("iframe");
        element.src = this.iframeUrl;
        element.style.border = '0px';
    } else {
        element = null;
    }

    this._css3Plane = new pc.Css3Plane(element, this.entity, this.pixelsPerUnit);
    
    var material = new pc.StandardMaterial();
    material.depthWrite = true;
    material.redWrite = false;
    material.greenWrite = false;
    material.blueWrite = false;
    material.alphaWrite = false;
    material.blendType = pc.BLEND_NONE;
    material.opacity = 0;
    material.update();

    this.entity.model.material = material;
    
    this.on('destroy', function() {
        this._css3Plane.disable();
    }, this);
    
    this.on('enable', function() {
        this._css3Plane.enable();
    }, this);
    
    this.on('disable', function() {
        this._css3Plane.disable();
    }, this);
};

And this other the CSS script:

pc.extend(pc, function () {
    'use strict';

    var epsilon = function (value) {
        return Math.abs(value) < 1e-10 ?0 :value;
    };
    
    var app = pc.Application.getApplication();

    // the Y axis of html is the opposite of gl,so multiply by minus one
    var getCameraCSSMatrix = function (matrix) {
        var elements = matrix.data;

        return 'matrix3d(' +
            epsilon( elements[ 0 ] ) + ',' +
            epsilon( - elements[ 1 ] ) + ',' +
            epsilon( elements[ 2 ] ) + ',' +
            epsilon( elements[ 3 ] ) + ',' +
            epsilon( elements[ 4 ] ) + ',' +
            epsilon( - elements[ 5 ] ) + ',' +
            epsilon( elements[ 6 ] ) + ',' +
            epsilon( elements[ 7 ] ) + ',' +
            epsilon( elements[ 8 ] ) + ',' +
            epsilon( - elements[ 9 ] ) + ',' +
            epsilon( elements[ 10 ] ) + ',' +
            epsilon( elements[ 11 ] ) + ',' +
            epsilon( elements[ 12 ] ) + ',' +
            epsilon( - elements[ 13 ] ) + ',' +
            epsilon( elements[ 14 ] ) + ',' +
            epsilon( elements[ 15 ] ) +
            ')';
    };

    // scaleX and scaleY represent how many pixels of div equlas to one unit of model.
    var getObjectCSSMatrix = function(matrix,scaleX,scaleY) {        
        var elements = matrix.data;

        var matrix3d = 'matrix3d(' +
            epsilon( elements[ 0 ] / scaleX) + ',' +
            epsilon( elements[ 1 ] / scaleX) + ',' +
            epsilon( elements[ 2 ] / scaleX) + ',' +
            epsilon( elements[ 3 ] / scaleX) + ',' +
            epsilon( elements[ 8 ] / scaleY) + ',' +
            epsilon( elements[ 9 ] / scaleY) + ',' +
            epsilon( elements[ 10 ] / scaleY) + ',' +
            epsilon( elements[ 11 ] / scaleY) + ',' +
            epsilon( elements[ 4 ] ) + ',' +
            epsilon( elements[ 5 ] ) + ',' +
            epsilon( elements[ 6 ] ) + ',' +
            epsilon( elements[ 7 ] ) + ',' +
            epsilon( elements[ 12 ] ) + ',' +
            epsilon( elements[ 13 ] ) + ',' +
            epsilon( elements[ 14 ] ) + ',' +
            epsilon( elements[ 15 ] ) +
            ')';

        // Make sure the origin is in the center
        return 'translate(-50%,-50%)' + matrix3d;
    };

    var randomCssColor = function() {
        var r = Math.round(Math.random() * 255);
        var g = Math.round(Math.random() * 255);
        var b = Math.round(Math.random() * 255);

        return "rgb(" + r + "," + g + "," + b + ")";
    };

    // construct a css3 renderer
    var Css3Renderer = function() {
        // make sure we only have one css3 renderer
        if(app.css3Renderer)
            return app.css3Renderer;
        app.css3Renderer = this;

        // init data
        this._stageElement = null;
        this._cameras = [];
        this._cameraElements = [];
        this._defaultCameraElement = null;
        this._css3Targets = [];
        // cache
        this._cameraInvertMat = new pc.Mat4();
        this._cameraHalfSize = new pc.Vec2();

        // create a div to contain all elements
        var stageElement = document.createElement("div");
        stageElement.style.overflow = "hidden";
        stageElement.style.pointerEvents = 'auto';
        document.body.appendChild(stageElement);
        
        // have the touch device detect touches from the stage element instead
        var touchDevice = app.touch;
        if (touchDevice) {
            touchDevice.attach(stageElement);
        }
        
        if (app.elementInput) {
            app.elementInput.attach(stageElement);
        }

        // make sure the div created is behind the canvas since we want to show the div when canvas is transparent
        var canvas = document.getElementById("application-canvas");
        canvas.style.pointerEvents = "none";
        document.body.insertBefore(stageElement,canvas);

        this._stageElement = stageElement;

        // add default camera
        this._defaultCameraElement = this.addCamera(app.root.findComponent("camera"));

        var self = this;
        function onWindowResize(){
            self._width = window.innerWidth;
            self._height = window.innerHeight;
            self._widthHalf = self._width / 2;
            self._heightHalf = self._height / 2;
            self._stageElement.style.width = self._width + 'px';
            self._stageElement.style.height = self._height + 'px';
            for(var i = 0;i < self._cameraElements.length;i++) {
                self._cameraElements[i].style.width = self._width + 'px';
                self._cameraElements[i].style.height = self._height + 'px';
            }
        }
        onWindowResize();

        window.addEventListener("resize", onWindowResize, false);
    };

    Css3Renderer.prototype = {
        // start render every frame
        render: function () {
            if(this._isRendering)
                return;

            this._isRendering = true;
            app.on("update",this._renderElements,this);  
        },

        cancelRender: function() {
            app.off("update",this._renderElements,this);
            this._isRendering = false;

        },

        // add new camera
        addCamera: function (camera) {
            // make sure the camera we added is not duplicate
            if(!camera)
                return this._defaultCameraElement;
            if(this._cameras.indexOf(camera) > -1) {
                return this._cameraElements[this._cameras.indexOf(camera)];   
            }
            // create a div for this camera
            var cameraElement = document.createElement("div");
            cameraElement.style.WebkitTransformStyle = 'preserve-3d';
            cameraElement.style.transformStyle = 'preserve-3d';
            cameraElement.style.pointerEvents = 'none';
            // add to html
            this._stageElement.appendChild(cameraElement);
            // camera cache
            this._cameras.push(camera);
            this._cameraElements.push(cameraElement);

            return cameraElement;
        },

        // add new render element
        addTarget: function (target) {
            if(this._css3Targets.indexOf(target) <= -1) 
                this._css3Targets.push(target);
        },
        
        // remove render element
        removeTarget: function (target) {
            var index = this._css3Targets.indexOf(target);
            if(index !== -1) 
                this._css3Targets.splice(index, 1);
        },

        // set whether the div can be interacted
        blockEvents: function (state) {
            for(var i = 0;i < this._css3Targets.length;this._css3Targets[i++].blockEvents(state));
        },

        // render elements every frame
        _renderElements: function () {
            for(var i = 0;i < this._cameras.length;i++) {
                var tx,ty,cameraCSSMatrix,style;
                var camera = this._cameras[i];
                var cameraElement = this._cameraElements[i];
                var fov = camera.projectionMatrix.data[5] * this._heightHalf;

                // update stage div
                if(camera.projection == pc.PROJECTION_PERSPECTIVE) {
                    this._stageElement.style.WebkitPerspective = fov + 'px';
                    this._stageElement.style.perspective = fov + 'px';
                }
                else {
                    this._stageElement.style.WebkitPerspective = '';
                    this._stageElement.style.perspective = '';
                }

                // update camera
                if(camera.projection == pc.PROJECTION_ORTHOGRAPHIC) {
                    pc.Mat4._getPerspectiveHalfSize(this._cameraHalfSize, camera.fov, camera.aspectRatio, camera.nearClip, camera.horizontalFov);
                    tx = - (this._cameraHalfSize.x - this._cameraHalfSize.x) / 2;
                    ty = (this._cameraHalfSize.y - this._cameraHalfSize.y) / 2;
                }
                this._cameraInvertMat.copy(camera.entity.getWorldTransform()).invert();
                cameraCSSMatrix = camera.projection == pc.PROJECTION_ORTHOGRAPHIC ?'scale(' + fov + ')' + 'translate(' + epsilon(tx) + 'px,' + epsilon(ty) + 'px)' + getCameraCSSMatrix(this._cameraInvertMat) 
                :'translateZ(' + fov +'px)' + getCameraCSSMatrix(this._cameraInvertMat);
                style = cameraCSSMatrix + 'translate(' + this._widthHalf + 'px,' + this._heightHalf + 'px)';
                cameraElement.style.WebkitTransform = style;
                cameraElement.style.transform = style;
            }

            // update all div
            for(var j = 0;j < this._css3Targets.length;this._css3Targets[j++].updateTransform());
        }
    };

    /**
    * @class
    * @name pc.Css3Plane
    * @classdesc merge the div and entity together.
    * @description merge the div and entity together,update the domElement transform according to the entity every frame so they look the same to the viewer.
    * @param {object} dom - the dom element to show,create a new one if not exist.
    * @param {pc.Entity} entity - the entity to attach,create a new one if not exist.
    * @param {number} pixelsPerWorldUnit - represent how many pixels of div equlas to one unit of model.
    * @param {pc.CameraComponent} camera - the camera.
    */
    var Css3Plane = function (dom, entity, pixelsPerWorldUnit, camera) {        
        // create a css3 renderer if we don't have one
        if(!app.css3Renderer) {
            app.css3Renderer = new pc.Css3Renderer();
        }
        // set the renderer
        this._renderer = app.css3Renderer;

        // create a div if we don't have one
        if(!dom) {
            dom = document.createElement('div');
            dom.innerHTML = "CSS3 Plane"; 
            dom.style.backgroundColor = randomCssColor();  
            dom.style.textAlign = "center"; // 文字居中
        }
        dom.style.position = 'absolute';
        dom.style.pointerEvents = 'auto';
        this.dom = dom;

        // create an entity and add it to scene if we don't have one
        if(!entity) {
            entity = new pc.Entity();
            app.root.addChild(entity);
        }
        this.entity = entity;

        // add camera to renderer
        this.cameraElement = this._renderer.addCamera(camera);  
        // add div to cameraElement
        this.cameraElement.appendChild(dom);

        // set the max width and height
        this._maxWidth = 1920;
        this._maxHeight = 1080;
        // scaleFactor represent how many pixels of div equals to one unit of model.
        pixelsPerWorldUnit = pixelsPerWorldUnit || this._maxWidth;
        this.pixelsPerWorldUnit = new pc.Vec2(pixelsPerWorldUnit,pixelsPerWorldUnit);

        // start render
        this._renderer.addTarget(this); 
        this._renderer.render();
    };

    Css3Plane.prototype = {
        // update model materix
        updateTransform: function () {
            var modelTransform = this.entity.getWorldTransform();
            var scale = modelTransform.getScale();
            var width = Math.min(scale.x * this.pixelsPerWorldUnit.x,this._maxWidth);
            var height = Math.min(scale.z * this.pixelsPerWorldUnit.y,this._maxHeight);
            var style = getObjectCSSMatrix(modelTransform,width,height);
            
            // Safari doesn't like multi decimal numbers for width and height
            // and misaligns the iframe in the plane
            
            this.dom.style.width = Math.round(width) + "px";
            this.dom.style.height = Math.round(height) + "px";
            this.dom.style.lineHeight = this.dom.style.height;
            this.dom.style.WebkitTransform = style;
            this.dom.style.transform = style;
        },

        // set whether the div can be interacted
        blockEvents: function (state) {
            this.dom.style.pointerEvents = state ? "none" : "auto";  
        },

        // attach other plane in scene
        attachPlane: function (entity) {
            this.entity = entity;
        },
        
        disable: function () {
            this.cameraElement.removeChild(this.dom);
            this._renderer.removeTarget(this); 
        },
        
        enable: function () {
            this.cameraElement.appendChild(this.dom);
            this._renderer.addTarget(this); 
        }
    };

    return {
        Css3Renderer: Css3Renderer,
        Css3Plane: Css3Plane
    };
}());

cant find the mouse event T_T or is the same of TouchEvent on the css file?

You will have to add the logic yourself via JS. It’s not part of the original script.

1 Like

That said, I can’t reproduce the same issue in the original tutorial: https://developer.playcanvas.com/en/tutorials/youtube-in-3d-scenes/

1 Like

I do not understand why the error does not happen in the example the truth, unfortunately the displacement mode is not useful for my project, I am seeing if I can modify it but it is quite complex for my level of code X_X

this is my lookCamera script:

var LookCamera4Floor = pc.createScript('lookCamera4Floor');

LookCamera4Floor.attributes.add("mouseLookSensitivity", {type: "number", default: 0.2, title: "Mouse Look Sensitivity", description: ""});
LookCamera4Floor.attributes.add("touchLookSensitivity", {type: "number", default: 0.2, title: "Touch Look Sensitivity", description: ""});

LookCamera4Floor.attributes.add("sphere", {type: "entity", title: "Sphere"});
var inciar = true;

LookCamera4Floor.prototype.initialize = function () {
   // console.log("inicializa");
    this.gira = true;
   // this.script.enabled = false;
    // Camera euler angle rotation around x and y axes
    var quat = this.entity.getLocalRotation();

    this.ex = this.getPitch(quat) * pc.math.RAD_TO_DEG;
    this.ey = this.getYaw(quat) * pc.math.RAD_TO_DEG;
  
    this.targetEx = this.ex;
    this.targetEy = this.ey; 
    this.moved = false;
    this.lmbDown = false;
    if(inciar){
    // Disabling the context menu stops the browser displaying a menu when
    // you right-click the page
    this.app.mouse.disableContextMenu();

    this.lastTouchPosition = new pc.Vec2();

    this.addEventCallbacks();
                }     
    this.on("destroy", function () {
        this.removeEventCallbacks();
    });
    
    this.app.xr.on('start', function() {
        //this.entity.setLocalEulerAngles(0,0,0);
    }, this);
    
   
};

LookCamera4Floor.prototype.addEventCallbacks = function() {
    if (this.app.mouse) {
        this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
        this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
        this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
    }
    
    if (this.app.touch) {
        this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStart, this);
        this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchEnd, this);
        this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
        this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchCancel, this);   
    }
};

LookCamera4Floor.prototype.removeEventCallbacks = function() {
    if (this.app.mouse) {
        this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
        this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
        this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
    }
    
    if (this.app.touch) {
        this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStart, this);
        this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchEnd, this);
        this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
        this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchCancel, this);   
    }
};

// Taken from http://stackoverflow.com/questions/23310299/quaternion-from-tait-bryan-angles
// Not completely accurate, usually a couple of degrees out
LookCamera4Floor.prototype.getYaw = function (quaternion) {
    var q = quaternion;
    var x = q.x * q.x;
    var y = q.y * q.y;
    
    return Math.atan2(2 * q.y * q.w - 2 * q.z * q.x, 1 - 2 * y - 2 * x);    
};

LookCamera4Floor.prototype.getPitch = function(quaternion) {
    var q = quaternion;
    return -Math.asin(2 * q.z * q.y + 2 * q.x * q.w);
};

LookCamera4Floor.prototype.update = function (dt) {
          //console.log(inciar); 
    // Update the camera's orientation
    this.ex = pc.math.lerp(this.ex, this.targetEx, dt / 0.5);
    this.ey = pc.math.lerp(this.ey, this.targetEy, dt / 0.5);
        
    if (! this.app.xr.active) {
        this.entity.setLocalEulerAngles(this.ex, this.ey, 0);
    }   
    
    // Make sure that the user is always in the center of the sphere
    this.sphere.setPosition(this.entity.getPosition());
};

LookCamera4Floor.prototype.moveCamera = function(dx, dy, lookSensitivity) {
    if (!this.moved) {
        // first move event can be very large
        this.moved = true;
        return;
    }
        
    this.targetEx += dy * lookSensitivity;
    this.targetEx = pc.math.clamp(this.targetEx, -90, 90);
    this.targetEy += dx * lookSensitivity;  
};

LookCamera4Floor.prototype.onMouseMove = function (event) {  
    if (!this.lmbDown)
        return;
    
    this.moveCamera(event.dx, event.dy, 0.4);
};

LookCamera4Floor.prototype.onMouseDown = function (event) {

     
                     this.app.tween({}).to({}, 0.01, pc.Linear).delay(0)
            .on('complete', function() {
                      if (event.button === 0 && this.gira) {
        this.lmbDown = true;
   }  
        }, this).start();
    
    
    
    
/*   if (event.button === 0 && this.gira) {
        this.lmbDown = true;
   }      */


};

LookCamera4Floor.prototype.onMouseUp = function (event) {
    this.gira = true;
    if (event.button === 0) {
        this.lmbDown = false;
    }
};

LookCamera4Floor.prototype.onTouchStart = function(event) {
    if (event.touches.length == 1) {
        this.lmbDown = true;
        var touch = event.touches[0];
        this.lastTouchPosition.set(touch.x, touch.y);
    }
    event.event.preventDefault();
};

LookCamera4Floor.prototype.onTouchEnd = function(event) {
    if (event.touches.length === 0) {
        this.lmbDown = false;
    } else if (event.touches.length == 1) {
        var touch = event.touches[0];
        this.lastTouchPosition.set(touch.x, touch.y);  
    }
};

LookCamera4Floor.prototype.onTouchMove = function(event) {
    var touch = event.touches[0];
    if (event.touches.length == 1) {
        this.moveCamera((touch.x - this.lastTouchPosition.x), (touch.y - this.lastTouchPosition.y), this.touchLookSensitivity);
    }
    this.lastTouchPosition.set(touch.x, touch.y);
};

LookCamera4Floor.prototype.onTouchCancel = function(event) {
    this.lmbDown = false;
};

LookCamera4Floor.prototype.saveRot = function (){
   // console.log("entra");
    inciar = false;
     // var quat = this.entity.getLocalRotation();  
};

I add the codeline you suggest me and its working, this is the final code:

var IframePlane = pc.createScript('iframePlane');

IframePlane.attributes.add('iframeUrl', {type: 'string'});
IframePlane.attributes.add('pixelsPerUnit', {
    type: 'number', 
    default: 640, 
    description: 'Number of canvas pixels per unit of world space. The larger the number, the higher the resolution of the iframe.'
});


// initialize code called once per entity
IframePlane.prototype.initialize = function() {
    // WARNING: IframePlane does not work with touch events
    var element;
  
    if (this.iframeUrl) {
        element = document.createElement("iframe");
        element.src = this.iframeUrl;
        element.style.border = '0px';

        this.app.mouse.on(pc.EVENT_MOUSEDOWN, ()=> { element.style.pointerEvents = "none";});
        this.app.mouse.on(pc.EVENT_MOUSEUP, ()=>  { element.style.pointerEvents = "auto";});


    } else {
        element = null;
    }

    this._css3Plane = new pc.Css3Plane(element, this.entity, this.pixelsPerUnit);
    
    var material = new pc.StandardMaterial();
    material.depthWrite = true;
    material.redWrite = false;
    material.greenWrite = false;
    material.blueWrite = false;
    material.alphaWrite = false;
    material.blendType = pc.BLEND_NONE;
    material.opacity = 0;
    material.update();

    this.entity.model.material = material;
    
    this.on('destroy', function() {
        this._css3Plane.disable();
    }, this);
    
    this.on('enable', function() {
        this._css3Plane.enable();
    }, this);
    
    this.on('disable', function() {
        this._css3Plane.disable();
    }, this);
                                                         
};
1 Like