Bardelman
Bardelman

Reputation: 2298

Can't understand a javascript .call() usage

I'm trying to learn the three.js library by reading the "WebGL Up And Running" book and the problem for me is that the author made his own javascript framework called 'sim.js' which is a "higher-level set of reusable objects build upon three.js that wraps the more repetitive Three.js tasks" as he said but for a beginner like me i'd prefer more experiencing the raw three.js first.. So now i have to understand what his framwork does to understand what happens under the hood.

this is the sim.js

// Sim.js - A Simple Simulator for WebGL (based on Three.js)

Sim = {};

// Sim.Publisher - base class for event publishers
Sim.Publisher = function() {
    this.messageTypes = {};
}

Sim.Publisher.prototype.subscribe = function(message, subscriber, callback) {
    var subscribers = this.messageTypes[message];
    if (subscribers)
    {
        if (this.findSubscriber(subscribers, subscriber) != -1)
        {
            return;
        }
    }
    else
    {
        subscribers = [];
        this.messageTypes[message] = subscribers;
    }

    subscribers.push({ subscriber : subscriber, callback : callback });
}

Sim.Publisher.prototype.unsubscribe =  function(message, subscriber, callback) {
    if (subscriber)
    {
        var subscribers = this.messageTypes[message];

        if (subscribers)
        {
            var i = this.findSubscriber(subscribers, subscriber, callback);
            if (i != -1)
            {
                this.messageTypes[message].splice(i, 1);
            }
        }
    }
    else
    {
        delete this.messageTypes[message];
    }
}

Sim.Publisher.prototype.publish = function(message) {
    var subscribers = this.messageTypes[message];

    if (subscribers)
    {
        for (var i = 0; i < subscribers.length; i++)
        {
            var args = [];
            for (var j = 0; j < arguments.length - 1; j++)
            {
                args.push(arguments[j + 1]);
            }
            subscribers[i].callback.apply(subscribers[i].subscriber, args);
        }
    }
}

Sim.Publisher.prototype.findSubscriber = function (subscribers, subscriber) {
    for (var i = 0; i < subscribers.length; i++)
    {
        if (subscribers[i] == subscriber)
        {
            return i;
        }
    }

    return -1;
}

// Sim.App - application class (singleton)
Sim.App = function()
{
    Sim.Publisher.call(this);

    this.renderer = null;
    this.scene = null;
    this.camera = null;
    this.objects = [];
}

Sim.App.prototype = new Sim.Publisher;

Sim.App.prototype.init = function(param)
{
    param = param || {};    
    var container = param.container;
    var canvas = param.canvas;

    // Create the Three.js renderer, add it to our div
    var renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas } );
    renderer.setSize(container.offsetWidth, container.offsetHeight);
    container.appendChild( renderer.domElement );

    // Create a new Three.js scene
    var scene = new THREE.Scene();
    scene.add( new THREE.AmbientLight( 0x505050 ) );
    scene.data = this;

    // Put in a camera at a good default location
    camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 10000 );
    camera.position.set( 0, 0, 3.3333 );

    scene.add(camera);

    // Create a root object to contain all other scene objects
    var root = new THREE.Object3D();
    scene.add(root);

    // Create a projector to handle picking
    var projector = new THREE.Projector();

    // Save away a few things
    this.container = container;
    this.renderer = renderer;
    this.scene = scene;
    this.camera = camera;
    this.projector = projector;
    this.root = root;

    // Set up event handlers
    this.initMouse();
    this.initKeyboard();
    this.addDomHandlers();
}

//Core run loop
Sim.App.prototype.run = function()
{
    this.update();
    this.renderer.render( this.scene, this.camera );
    var that = this;
    requestAnimationFrame(function() { that.run(); });  
}

// Update method - called once per tick
Sim.App.prototype.update = function()
{
    var i, len;
    len = this.objects.length;
    for (i = 0; i < len; i++)
    {
        this.objects[i].update();
    }
}

// Add/remove objects
Sim.App.prototype.addObject = function(obj)
{
    this.objects.push(obj);

    // If this is a renderable object, add it to the root scene
    if (obj.object3D)
    {
        this.root.add(obj.object3D);
    }
}

Sim.App.prototype.removeObject = function(obj)
{
    var index = this.objects.indexOf(obj);
    if (index != -1)
    {
        this.objects.splice(index, 1);
        // If this is a renderable object, remove it from the root scene
        if (obj.object3D)
        {
            this.root.remove(obj.object3D);
        }
    }
}

// Event handling
Sim.App.prototype.initMouse = function()
{
    var dom = this.renderer.domElement;

    var that = this;
    dom.addEventListener( 'mousemove', 
            function(e) { that.onDocumentMouseMove(e); }, false );
    dom.addEventListener( 'mousedown', 
            function(e) { that.onDocumentMouseDown(e); }, false );
    dom.addEventListener( 'mouseup', 
            function(e) { that.onDocumentMouseUp(e); }, false );

    $(dom).mousewheel(
            function(e, delta) {
                that.onDocumentMouseScroll(e, delta);
            }
        );

    this.overObject = null;
    this.clickedObject = null;
}

Sim.App.prototype.initKeyboard = function()
{
    var dom = this.renderer.domElement;

    var that = this;
    dom.addEventListener( 'keydown', 
            function(e) { that.onKeyDown(e); }, false );
    dom.addEventListener( 'keyup', 
            function(e) { that.onKeyUp(e); }, false );
    dom.addEventListener( 'keypress', 
            function(e) { that.onKeyPress(e); }, false );

    // so it can take focus
    dom.setAttribute("tabindex", 1);
    dom.style.outline='none';
}

Sim.App.prototype.addDomHandlers = function()
{
    var that = this;
    window.addEventListener( 'resize', function(event) { that.onWindowResize(event); }, false );
}

Sim.App.prototype.onDocumentMouseMove = function(event)
{
    event.preventDefault();

    if (this.clickedObject && this.clickedObject.handleMouseMove)
    {
        var hitpoint = null, hitnormal = null;
        var intersected = this.objectFromMouse(event.pageX, event.pageY);
        if (intersected.object == this.clickedObject)
        {
            hitpoint = intersected.point;
            hitnormal = intersected.normal;
        }
        this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
    }
    else
    {
        var handled = false;

        var oldObj = this.overObject;
        var intersected = this.objectFromMouse(event.pageX, event.pageY);
        this.overObject = intersected.object;

        if (this.overObject != oldObj)
        {
            if (oldObj)
            {
                this.container.style.cursor = 'auto';

                if (oldObj.handleMouseOut)
                {
                    oldObj.handleMouseOut(event.pageX, event.pageY);
                }
            }

            if (this.overObject)
            {
                if (this.overObject.overCursor)
                {
                    this.container.style.cursor = this.overObject.overCursor;
                }

                if (this.overObject.handleMouseOver)
                {
                    this.overObject.handleMouseOver(event.pageX, event.pageY);
                }
            }

            handled = true;
        }

        if (!handled && this.handleMouseMove)
        {
            this.handleMouseMove(event.pageX, event.pageY);
        }
    }
}

Sim.App.prototype.onDocumentMouseDown = function(event)
{
    event.preventDefault();

    var handled = false;

    var intersected = this.objectFromMouse(event.pageX, event.pageY);
    if (intersected.object)
    {
        if (intersected.object.handleMouseDown)
        {
            intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal);
            this.clickedObject = intersected.object;
            handled = true;
        }
    }

    if (!handled && this.handleMouseDown)
    {
        this.handleMouseDown(event.pageX, event.pageY);
    }
}

Sim.App.prototype.onDocumentMouseUp = function(event)
{
    event.preventDefault();

    var handled = false;

    var intersected = this.objectFromMouse(event.pageX, event.pageY);
    if (intersected.object)
    {
        if (intersected.object.handleMouseUp)
        {
            intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal);
            handled = true;
        }
    }

    if (!handled && this.handleMouseUp)
    {
        this.handleMouseUp(event.pageX, event.pageY);
    }

    this.clickedObject = null;
}

Sim.App.prototype.onDocumentMouseScroll = function(event, delta)
{
    event.preventDefault();

    if (this.handleMouseScroll)
    {
        this.handleMouseScroll(delta);
    }
}

Sim.App.prototype.objectFromMouse = function(pagex, pagey)
{
    // Translate page coords to element coords
    var offset = $(this.renderer.domElement).offset();  
    var eltx = pagex - offset.left;
    var elty = pagey - offset.top;

    // Translate client coords into viewport x,y
    var vpx = ( eltx / this.container.offsetWidth ) * 2 - 1;
    var vpy = - ( elty / this.container.offsetHeight ) * 2 + 1;

    var vector = new THREE.Vector3( vpx, vpy, 0.5 );

    this.projector.unprojectVector( vector, this.camera );

    var ray = new THREE.Ray( this.camera.position, vector.subSelf( this.camera.position ).normalize() );

    var intersects = ray.intersectScene( this.scene );

    if ( intersects.length > 0 ) {      

        var i = 0;
        while(!intersects[i].object.visible)
        {
            i++;
        }

        var intersected = intersects[i];
        var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld);
        var point = mat.multiplyVector3(intersected.point);

        return (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal));                                                 
    }
    else
    {
        return { object : null, point : null, normal : null };
    }
}

Sim.App.prototype.findObjectFromIntersected = function(object, point, normal)
{
    if (object.data)
    {
        return { object: object.data, point: point, normal: normal };
    }
    else if (object.parent)
    {
        return this.findObjectFromIntersected(object.parent, point, normal);
    }
    else
    {
        return { object : null, point : null, normal : null };
    }
}


Sim.App.prototype.onKeyDown = function(event)
{
    // N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
    event.preventDefault();

    if (this.handleKeyDown)
    {
        this.handleKeyDown(event.keyCode, event.charCode);
    }
}

Sim.App.prototype.onKeyUp = function(event)
{
    // N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
    event.preventDefault();

    if (this.handleKeyUp)
    {
        this.handleKeyUp(event.keyCode, event.charCode);
    }
}

Sim.App.prototype.onKeyPress = function(event)
{
    // N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
    event.preventDefault();

    if (this.handleKeyPress)
    {
        this.handleKeyPress(event.keyCode, event.charCode);
    }
}

Sim.App.prototype.onWindowResize = function(event) {

    this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);

    this.camera.aspect = this.container.offsetWidth / this.container.offsetHeight;
    this.camera.updateProjectionMatrix();

}

Sim.App.prototype.focus = function()
{
    if (this.renderer && this.renderer.domElement)
    {
        this.renderer.domElement.focus();
    }
}


// Sim.Object - base class for all objects in our simulation
Sim.Object = function()
{
    Sim.Publisher.call(this);

    this.object3D = null;
    this.children = [];
}

Sim.Object.prototype = new Sim.Publisher;

Sim.Object.prototype.init = function()
{
}

Sim.Object.prototype.update = function()
{
    this.updateChildren();
}

// setPosition - move the object to a new position
Sim.Object.prototype.setPosition = function(x, y, z)
{
    if (this.object3D)
    {
        this.object3D.position.set(x, y, z);
    }
}

//setScale - scale the object
Sim.Object.prototype.setScale = function(x, y, z)
{
    if (this.object3D)
    {
        this.object3D.scale.set(x, y, z);
    }
}

//setScale - scale the object
Sim.Object.prototype.setVisible = function(visible)
{
    function setVisible(obj, visible)
    {
        obj.visible = visible;
        var i, len = obj.children.length;
        for (i = 0; i < len; i++)
        {
            setVisible(obj.children[i], visible);
        }
    }

    if (this.object3D)
    {
        setVisible(this.object3D, visible);
    }
}

// updateChildren - update all child objects
Sim.Object.prototype.update = function()
{
    var i, len;
    len = this.children.length;
    for (i = 0; i < len; i++)
    {
        this.children[i].update();
    }
}

Sim.Object.prototype.setObject3D = function(object3D)
{
    object3D.data = this;
    this.object3D = object3D;
}

//Add/remove children
Sim.Object.prototype.addChild = function(child)
{
    this.children.push(child);

    // If this is a renderable object, add its object3D as a child of mine
    if (child.object3D)
    {
        this.object3D.add(child.object3D);
    }
}

Sim.Object.prototype.removeChild = function(child)
{
    var index = this.children.indexOf(child);
    if (index != -1)
    {
        this.children.splice(index, 1);
        // If this is a renderable object, remove its object3D as a child of mine
        if (child.object3D)
        {
            this.object3D.remove(child.object3D);
        }
    }
}

// Some utility methods
Sim.Object.prototype.getScene = function()
{
    var scene = null;
    if (this.object3D)
    {
        var obj = this.object3D;
        while (obj.parent)
        {
            obj = obj.parent;
        }

        scene = obj;
    }

    return scene;
}

Sim.Object.prototype.getApp = function()
{
    var scene = this.getScene();
    return scene ? scene.data : null;
}

// Some constants

/* key codes
37: left
38: up
39: right
40: down
*/
Sim.KeyCodes = {};
Sim.KeyCodes.KEY_LEFT  = 37;
Sim.KeyCodes.KEY_UP  = 38;
Sim.KeyCodes.KEY_RIGHT  = 39;
Sim.KeyCodes.KEY_DOWN  = 40;

in another script he wrote a new class called earth-basic based on the sim class so, the begining of the earth-basic.js script looks as the following :

// Constructor
EarthApp = function()
{
    Sim.App.call(this);
}

// Subclass Sim.App
EarthApp.prototype = new Sim.App();

// Our custom initializer
EarthApp.prototype.init = function(param)
{
    // Call superclass init code to set up scene, renderer, default camera
    Sim.App.prototype.init.call(this, param);

    // Create the Earth and add it to our sim
    var earth = new Earth();
    earth.init();
    this.addObject(earth);
}

// Custom Earth class
Earth = function()
{
    Sim.Object.call(this);
}

Earth.prototype = new Sim.Object();

1) what does the function call in the line "Sim.App.call(this);" written in the constructor (by passing "this" as parameter which i suppose is referring to the EarthApp variable)? all what i can guess is that EarthApp will inherit the Sim.App properties (the renderer, the camera ...). The same "technique" was used inside the "Sim.App" function itself by calling "Sim.Publisher.call(this);"

2) in 1) i supposed he just used the sim.App class as a super-Class but then all of a sudden, i found he added a new instance of sim.App() to the EarthApp's prototype by writting "EarthApp.prototype = new Sim.App();" PLease guys tell me what the heck is going on in there.

Upvotes: 2

Views: 138

Answers (1)

Jaboc83
Jaboc83

Reputation: 302

The call function in Sim.App.call(this) is calling the App function like you would normally (by doing Sim.App()), but the difference is that by using call you may pass in a specific context for the the function. The context being the value of 'this' inside the Sim.App function. As you mentioned this happens again inside the Sim.App function which means that the object you create when you call the EarthApp constructor will end up having the properties and methods defined on all three types EarthApp, SimApp, and Publisher.

Setting the EarthApp prototype to an instance of SimApp is how inheritance works in Javascript. I encourage you to read up on it as it's very different from the classical inheritance you are probably familiar with from other languages. This link may be of some help.

JavaScript Prototype in Plain Language

Upvotes: 1

Related Questions