Cannot read property 'render' of undefined after already rendering once

My earth object renders fine, but I get about 6 different console errors saying “Cannot read property ‘render’ of undefined”. I have researched and it looks like it is somehow losing the context of the renderer object. I have declared the variable at the top of my JS file, so not sure how the context could be lost in my animate function… Here is my entire JS file:

    //Define namespace
var EarthView3D = EarthView3D || {};

//THREE JS setup variables
var camera, scene, renderer, labelRenderer, earth;

$(document).ready(function () {
    EarthView3D.GetSiteCoordList(); // This must run before initializing EarthView, Init() is called in success property or ajax call.
    EarthView3D.Animate(); // This renders the initialized 3D Earth.

}); /* END DOCUMENT READY */

EarthView3D.GetSiteCoordList = function () {
    $.ajax({
        url: basePath + 'EarthView3D/GetSiteCoords',
        type: 'GET',
        dataType: 'html',
        cache: false,
        success: function (data) {
            // Set the siteObjects, defined at the top of this file.
            var siteObjects = JSON.parse(data);
            EarthView3D.Init(siteObjects);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            alert('Error retrieving site coordinates for earth view.');
        },
        complete: function (jqXHR, textStatus) {

        }
    });
}

EarthView3D.Init = function (siteObjects) {
    const EARTH_RADIUS = 225;
    const EARTH_SEGMENTS = 75;
    const EARTH_RINGS = 75;

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000); //-87 pixels for header and dev environment bar.
    camera.position.set(-70, 375, 500);
    scene = new THREE.Scene();

    var light = new THREE.PointLight(0xffffff, 1, Infinity);
    camera.add(light);

    scene.add(camera);

    // Starfield
    var starGeometry = new THREE.SphereGeometry(1000, 50, 50);
    var starMaterial = new THREE.MeshPhongMaterial({
        map: new THREE.ImageUtils.loadTexture(basePath + 'Content/images/galaxy_starfield.png'),
        side: THREE.DoubleSide,
        shininess: 0
    });
    var starField = new THREE.Mesh(starGeometry, starMaterial);
    scene.add(starField);

    // Create group to hold sphere and texture, makes up the earth.
    earth = new THREE.Group();
    scene.add(earth);

    // Create sphere and texture, and mesh together using texture loader
    var loader = new THREE.TextureLoader();

    loader.load(basePath + 'Content/images/earth_clouds_16k.jpg', function (texture) {

        // Create the sphere
        var sphere = new THREE.SphereGeometry(EARTH_RADIUS, EARTH_SEGMENTS, EARTH_RINGS);

        // Map the texture to the material. 
        var material = new THREE.MeshBasicMaterial({
            map: texture
        });
        // Create a new mesh with sphere geometry.
        var mesh = new THREE.Mesh(sphere, material);

        // Add mesh to globe
        earth.add(mesh);
    });

    scene.add(earth);

    // Add siteObjects to Earth
    for (var i = 0; i < siteObjects.length; i++) {

        var siteID = siteObjects[i].ID;
        var name = siteObjects[i].Name;
        var lat = siteObjects[i].LAT;
        var lon = siteObjects[i].LON;
        var status = siteObjects[i].Status;

        // Create dot for each site
        var siteDiv = document.createElement('div');
        siteDiv.className = 'dot';
        siteDiv.setAttribute("id", name);
        siteDiv.setAttribute("title", name);
        siteDiv.style.backgroundColor =
            status == "FMC" ? "green" :
                status == "PMC" ? "yellow" : "red";

        //Create label to append to each site's dot
        var siteDivLabel = document.createElement('label');
        siteDivLabel.setAttribute("id", name + '_label');
        siteDivLabel.setAttribute("title", name + ' label');
        siteDivLabel.textContent = name;
        //Setting the label's class (Some labels need to display on different sides of the dot so that all can be seen in initial view.)
        if (siteID == 6 || siteID == 9) { // 6 is BUckley, 9 is COD
            siteDivLabel.className = 'label_top_right';
        } else if (siteID == 12 || siteID == 20 || siteID == 19) { // 12 is FGA, 20 is PAFB, 19 is MDIOC
            siteDivLabel.className = 'label_bottom_right';
        } else if (siteID == 8) { // 8 is CMD
            siteDivLabel.className = 'label_bottom_left';
        } else {
            siteDivLabel.className = 'label_top_left';
        }
        siteDiv.appendChild(siteDivLabel);

        var siteDot = new THREE.CSS2DObject(siteDiv);

        siteDiv.parent = siteDot;

        siteDot.element.style.cursor = "pointer";

        // Add click event for each dot/label that takes them to the site section for the dashboard homepage.
        siteDot.element.onclick = function (e) {
            var id = $(this).attr('id');
            window.location = basePath + 'Dashboard?id=' + id;
        };

        var sitePosition = EarthView3D.CalcPosFromLatLonRad(lat, lon, EARTH_RADIUS);

        // Some code to slightly move certain site markers around from their original position so that they don't overlap each other too much.
        if (siteID == 5) { // 5 is boulder
            siteDot.position.set(sitePosition[0] - 1, sitePosition[1] + 1, Math.abs(sitePosition[2]));
        } else if (siteID == 8) { // 8 is CMD
            siteDot.position.set(sitePosition[0] - 1, sitePosition[1] - 1, Math.abs(sitePosition[2]));
        } else if (siteID == 20) { // 20 is PAFB
            siteDot.position.set(sitePosition[0] + 1, sitePosition[1] - 6, Math.abs(sitePosition[2]));
        } else if (siteID == 19) { // 19 is MDIOC
            siteDot.position.set(sitePosition[0] + 5, sitePosition[1] + 4, Math.abs(sitePosition[2]));
        } else if (siteID == 6) { // 6 is Buckley
            siteDot.position.set(sitePosition[0] + 2, sitePosition[1] + 3, Math.abs(sitePosition[2]));
        } else {
            siteDot.position.set(sitePosition[0], sitePosition[1], Math.abs(sitePosition[2]));
        }
        earth.add(siteDot);
    }

    renderer = new THREE.WebGLRenderer({ alpha: true });
    renderer.setClearColor(0xffffff, 0);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    var earthContainer = document.getElementById('earth_container');
    earthContainer.appendChild(renderer.domElement);

    labelRenderer = new THREE.CSS2DRenderer();
    labelRenderer.setSize(window.innerWidth, window.innerHeight);
    labelRenderer.domElement.style.position = 'absolute';
    labelRenderer.domElement.style.top = 0;
    document.body.appendChild(labelRenderer.domElement);

    // For rotation and mouse controls
    var controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
    controls.minDistance = 300;
    controls.maxDistance = 800;
};

/* Referenced from answer found here:
https://stackoverflow.com/questions/28365948/javascript-latitude-longitude-to-xyz-position-on-earth-threejs
*/
EarthView3D.CalcPosFromLatLonRad = function (lat, lon, radius) {

    var phi = (90 - lat) * (Math.PI / 180)
    var theta = (lon + 180) * (Math.PI / 180)

    x = -((radius) * Math.sin(phi) * Math.cos(theta))
    z = ((radius) * Math.sin(phi) * Math.sin(theta))
    y = ((radius) * Math.cos(phi))

    return [x, y, z];
};

EarthView3D.Animate = function () {
    //earth.rotation.y -= .0004; - Rotation was causing shaking of markers/labels, so commented out.
    requestAnimationFrame(EarthView3D.Animate);
    renderer.render(scene, camera);
    labelRenderer.render(scene, camera);
};

The GetSiteCoordList() function is asynchronous — it will complete immediately but will execute its callbacks only after the coord list is fetched, and Animate() will be called before Init(), when no renderers exist. To solve this, you probably want to call Animate() immediately after you call Init(), instead.

1 Like

Thank you sir! That was it :smiley:

Update, here is code that worked and fixed issues:

//Define namespace
var EarthView3D = EarthView3D || {};

//THREE JS setup variables
var camera, scene, renderer, labelRenderer, earth;

$(document).ready(function () {
    EarthView3D.GetSiteCoordList(); // This must run before initializing EarthView, Init() is called in success property or ajax call.

}); /* END DOCUMENT READY */

EarthView3D.GetSiteCoordList = function () {
    $.ajax({
        url: basePath + 'EarthView3D/GetSiteCoords',
        type: 'GET',
        dataType: 'html',
        cache: false,
        success: function (data) {
            // Set the siteObjects, defined at the top of this file.
            var siteObjects = JSON.parse(data);
            EarthView3D.Init(siteObjects, EarthView3D.Animate);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            alert('Error retrieving site coordinates for earth view.');
        },
        complete: function (jqXHR, textStatus) {

        }
    });
}

EarthView3D.Init = function (siteObjects, callback) {
    console.log('init started');
    const EARTH_RADIUS = 225;
    const EARTH_SEGMENTS = 75;
    const EARTH_RINGS = 75;

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000); //-87 pixels for header and dev environment bar.
    camera.position.set(-70, 375, 500);
    scene = new THREE.Scene();

    var light = new THREE.PointLight(0xffffff, 1, Infinity);
    camera.add(light);

    scene.add(camera);

    // Starfield
    var starGeometry = new THREE.SphereGeometry(1000, 50, 50);
    var starMaterial = new THREE.MeshPhongMaterial({
        map: new THREE.ImageUtils.loadTexture(basePath + 'Content/images/galaxy_starfield.png'),
        side: THREE.DoubleSide,
        shininess: 0
    });
    var starField = new THREE.Mesh(starGeometry, starMaterial);
    scene.add(starField);

    // Create group to hold sphere and texture, makes up the earth.
    earth = new THREE.Group();
    scene.add(earth);

    // Create sphere and texture, and mesh together using texture loader
    var loader = new THREE.TextureLoader();

    loader.load(basePath + 'Content/images/earth_clouds_16k.jpg', function (texture) {

        // Create the sphere
        var sphere = new THREE.SphereGeometry(EARTH_RADIUS, EARTH_SEGMENTS, EARTH_RINGS);

        // Map the texture to the material. 
        var material = new THREE.MeshBasicMaterial({
            map: texture
        });
        // Create a new mesh with sphere geometry.
        var mesh = new THREE.Mesh(sphere, material);

        // Add mesh to globe
        earth.add(mesh);
    });

    scene.add(earth);

    // Add siteObjects to Earth
    for (var i = 0; i < siteObjects.length; i++) {

        var siteID = siteObjects[i].ID;
        var name = siteObjects[i].Name;
        var lat = siteObjects[i].LAT;
        var lon = siteObjects[i].LON;
        var status = siteObjects[i].Status;

        // Create dot for each site
        var siteDiv = document.createElement('div');
        siteDiv.setAttribute("id", name);
        siteDiv.setAttribute("data-siteid", siteID);
        siteDiv.setAttribute("title", name);
        siteDiv.classList = status.toLowerCase() == "fmc" ? "dot fmc_earth_dot" :
                            status.toLowerCase() == "pmc" ? "dot pmc_earth_dot" :
                                                            "dot nmc_earth_dot";
        console.log('Rendering dot color.');

        //Create label to append to each site's dot
        var siteDivLabel = document.createElement('label');
        siteDivLabel.setAttribute("id", name + '_label');
        siteDivLabel.setAttribute("title", name + ' label');
        siteDivLabel.textContent = name;
        //Setting the label's class (Some labels need to display on different sides of the dot so that all can be seen in initial view.)
        if (siteID == 6 || siteID == 9) { // 6 is BUckley, 9 is COD
            siteDivLabel.className = 'label_top_right';
        } else if (siteID == 12 || siteID == 20 || siteID == 19) { // 12 is FGA, 20 is PAFB, 19 is MDIOC
            siteDivLabel.className = 'label_bottom_right';
        } else if (siteID == 8) { // 8 is CMD
            siteDivLabel.className = 'label_bottom_left';
        } else {
            siteDivLabel.className = 'label_top_left';
        }
        siteDiv.appendChild(siteDivLabel);

        var siteDot = new THREE.CSS2DObject(siteDiv);

        siteDiv.parent = siteDot;

        siteDot.element.style.cursor = "pointer";

        // Add click event for each dot/label that takes them to the site section for the dashboard homepage.
        siteDot.element.onclick = function (e) {
            var id = $(this).attr('id');
            window.location = basePath + 'Dashboard?id=' + id;
        };

        var sitePosition = EarthView3D.CalcPosFromLatLonRad(lat, lon, EARTH_RADIUS);

        // Some code to slightly move certain site markers around from their original position so that they don't overlap each other too much.
        if (siteID == 5) { // 5 is boulder
            siteDot.position.set(sitePosition[0] - 1, sitePosition[1] + 1, Math.abs(sitePosition[2]));
        } else if (siteID == 8) { // 8 is CMD
            siteDot.position.set(sitePosition[0] - 1, sitePosition[1] - 1, Math.abs(sitePosition[2]));
        } else if (siteID == 20) { // 20 is PAFB
            siteDot.position.set(sitePosition[0] + 1, sitePosition[1] - 6, Math.abs(sitePosition[2]));
        } else if (siteID == 19) { // 19 is MDIOC
            siteDot.position.set(sitePosition[0] + 5, sitePosition[1] + 4, Math.abs(sitePosition[2]));
        } else if (siteID == 6) { // 6 is Buckley
            siteDot.position.set(sitePosition[0] + 2, sitePosition[1] + 3, Math.abs(sitePosition[2]));
        } else {
            siteDot.position.set(sitePosition[0], sitePosition[1], Math.abs(sitePosition[2]));
        }
        earth.add(siteDot);
    }

    renderer = new THREE.WebGLRenderer({ alpha: true });
    renderer.setClearColor(0xffffff, 0);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    var earthContainer = document.getElementById('earth_container');
    earthContainer.appendChild(renderer.domElement);

    labelRenderer = new THREE.CSS2DRenderer();
    labelRenderer.setSize(window.innerWidth, window.innerHeight);
    labelRenderer.domElement.style.position = 'absolute';
    labelRenderer.domElement.style.top = 0;
    document.body.appendChild(labelRenderer.domElement);

    // For rotation and mouse controls
    var controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
    controls.minDistance = 300;
    controls.maxDistance = 800;
    callback(siteObjects); //Running Animate right after Init.
};

/* Referenced from answer found here:
https://stackoverflow.com/questions/28365948/javascript-latitude-longitude-to-xyz-position-on-earth-threejs
*/
EarthView3D.CalcPosFromLatLonRad = function (lat, lon, radius) {

    var phi = (90 - lat) * (Math.PI / 180)
    var theta = (lon + 180) * (Math.PI / 180)

    x = -((radius) * Math.sin(phi) * Math.cos(theta))
    z = ((radius) * Math.sin(phi) * Math.sin(theta))
    y = ((radius) * Math.cos(phi))

    return [x, y, z];
};

EarthView3D.Animate = function () {
    //earth.rotation.y -= .0004; - Rotation was causing shaking of markers/labels, so commented out.
    requestAnimationFrame(EarthView3D.Animate);
    renderer.render(scene, camera);
    labelRenderer.render(scene, camera);
};

var myfunc = function () {
    alert('myfunc ran');
}