HTML element in three.js globe

0

I have a globe built using three.js

Refrene:http://callumprentice.github.io/apps/globe_manipulator/index.html

I need to show a HTML div at a specific lattitude/longitude, can you tell me how to position the div at some specific lat/long?

What have i tried: I am not even able to proceed with the conversion of lat lon to screen x and y cordinates.

My JS code:

            var camera, scene, renderer, controls, root_object = 0, mesh;
            var uniform_color = true, uniform_height = true, extrusion_amount = 5.0;
            var helper_1, helper_2;
            var radius;
            init();
            animate();

        function _convertLatLonToVec3 (lat,lon) {
            lat =  lat * Math.PI / 180.0;
            lon = -lon * Math.PI / 180.0;
            return new THREE.Vector3( 
                Math.cos(lat) * Math.cos(lon), //rechts links invert
                Math.sin(lat),  // up down invert
                Math.cos(lat) * Math.sin(lon));
        }

        function InfoBox( city, mesh, radius, domElement )
        {
            var position = _convertLatLonToVec3( city.lat, city.lng ).multiplyScalar( radius );
            this.mesh = mesh;
            this.mesh.position.copy( position );

            this._screenVector = new THREE.Vector3(0,0,0);

            this.box = document.createElement( 'div' );
            this.box.innerHTML = city.name;
            this.box.className = "hudLabel";

            this.domElement = domElement;
            this.domElement.appendChild( this.box );

        }

        InfoBox.prototype.update = function ()
        {

            this._screenVector.set( 0, 0, 0 );
            this.mesh.localToWorld( this._screenVector );
            this._screenVector.project( camera );

            var posx = Math.round(( this._screenVector.x + 1 ) * this.domElement.offsetWidth / 2 );
            var posy = Math.round(( 1 - this._screenVector.y ) * this.domElement.offsetHeight / 2 );

            var boundingRect = this.box.getBoundingClientRect();

            this.box.style.left = ( posx - boundingRect.width ) + 'px';
            this.box.style.top =  posy + 'px';

        };


        function init() {
            renderer = new THREE.WebGLRenderer({
                antialias: true
            });
            renderer.setClearColor(0x131C1D, 1.0);
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            scene = new THREE.Scene();

            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 2000);
            camera.position.z = 400;
            camera.position.y = 400;

            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(1, 0, 0);
            scene.add(light);
            var light2 = new THREE.DirectionalLight(0xffffff);
            light2.position.set(0, 0, 1);
            scene.add(light2);

            radius = 300;
            var segments = 128;
            var rings = 128;
            var geometry = new THREE.SphereGeometry(radius, segments, rings);
            material_map = THREE.ImageUtils.loadTexture('textures/sea_texture.jpg');
            material_map.wrapS = THREE.RepeatWrapping;
            material_map.wrapT = THREE.RepeatWrapping;
            material_map.repeat.set(8, 8);
            material = new THREE.MeshPhongMaterial({
                //map: material_map,
                color: 0xFFffffff,
                opacity: 0,
                transparent: true 
            });
            mesh = new THREE.Mesh(geometry, material);
            scene.add(mesh);

            var helper_geometry = new THREE.SphereGeometry( 2, 32, 32 );
            helper_1 = new THREE.Mesh( helper_geometry, new THREE.MeshNormalMaterial() );
            scene.add( helper_1 )
            helper_2 = new THREE.Mesh( helper_geometry, new THREE.MeshNormalMaterial() );
            scene.add( helper_2 )

            add_all_countries();

            controls = new THREE.TrackballControls(camera, renderer.domElement);

            globe_manipulator = new globe_manipulator({
                dom_object: renderer.domElement,
                camera: camera,
                radius: radius,
                on_clicked_callback: onClicked,
                auto_rotate: true,
                right_click_to_select: true,
                start_lat: 0.0,
                start_lng: 0.0,
                start_distance: 900.0,
                min_distance: 560.0,
                max_distance: 1200.0,
                mesh: mesh
            });
            gotoRandomCity();
            window.addEventListener('resize', onWindowResize, false);

        }


        function onClicked(event) {

            if ( event.mouse_event.button == 2 ) {
                if ( event.intersects ) {
                    actual_latlng = "Lat: " + event.lat.toFixed(2) + "  Lng: " + event.lng.toFixed(2);

                    var phi = (90.0 - event.lat) * Math.PI / 180.0;
                    var theta = (360.0 - event.lng) * Math.PI / 180.0;
                    var x = radius * Math.sin(phi) * Math.cos(theta);
                    var y = radius * Math.cos(phi);
                    var z = radius * Math.sin(phi) * Math.sin(theta);

                    helper_2.position.set( x, y, z );

                }
                else {
                    actual_latlng = "Missed";
                }
            }
        }


        function gotoRandomCity() {



            var name = "Delhi";
            var lat = 28.6100;
            var lng = 77.2300;

            var phi = (90.0 - lat) * Math.PI / 180.0;
            var theta = (360.0 - lng) * Math.PI / 180.0;
            var x = radius * Math.sin(phi) * Math.cos(theta);
            var y = radius * Math.cos(phi);
            var z = radius * Math.sin(phi) * Math.sin(theta);

            helper_1.position.set( x, y, z );


            city_name = name;
            target_latlng = "Lat: " + lat.toFixed(2) + "  Lng: " + lng.toFixed(2);
            console.log(lat+"   "+lng);
            globe_manipulator.set_lat_lng(lat, lng);

            var marker = new THREE.Mesh( new THREE.SphereGeometry( 5, 16, 16 ) );
            scene.add( marker );

            var city = { "name": "Hello", "lat": lat, "lng": lng };

            label = new InfoBox( city, marker, radius, document.body );

        }

        function add_country(shape_points) {
            var shape = new THREE.Shape(shape_points);
            var shape_geom;
            var inner_radius = 300.0;
            var outer_radius = 305.0;

            shape_geom = shape.extrude({
                amount: outer_radius - inner_radius,
                bevelEnabled: false
            });

            var offset = 0;
            if ( ! uniform_height )
                 offset = Math.random() * extrusion_amount;

            shape_geom.vertices.forEach(function (vert, index) {
                var radius = 0.0;
                if (index < shape_geom.vertices.length / 2) {
                    radius = inner_radius;
                } else {
                    radius = inner_radius + extrusion_amount + offset;
                }
                var phi = (90.0 - vert.y) * Math.PI / 180.0;
                var theta = (360.0 - vert.x) * Math.PI / 180.0;
                vert.x = radius * Math.sin(phi) * Math.cos(theta);;
                vert.y = radius * Math.cos(phi);;
                vert.z = radius * Math.sin(phi) * Math.sin(theta);;
            });
            var color = new THREE.Color(0x90BAC0);
            if (! uniform_color)
                color.setHSL(Math.random(),0.8,0.8 );

            var shape_material = new THREE.MeshPhongMaterial({
                color: color,
                side: THREE.DoubleSide
            });
            var shape_mesh = new THREE.Mesh(shape_geom, shape_material);
            root_object.add(shape_mesh);
        }

        function add_all_countries() {

            if ( root_object ) {
                scene.remove(root_object);
            }

            root_object = new THREE.Object3D();
            scene.add(root_object);

            countries.features.forEach(function (country) {
                if (country.geometry.coordinates.length === 1) {
                    var shape_points = [];
                    country.geometry.coordinates[0].forEach(function (points) {
                        shape_points.push(new THREE.Vector2(points[0], points[1]));
                    });
                    add_country(shape_points);
                } else {
                    country.geometry.coordinates.forEach(function (coord_set) {
                        if (coord_set.length == 1) {
                            var shape_points = [];
                            coord_set[0].forEach(function (points) {
                                shape_points.push(new THREE.Vector2(points[0], points[1]));
                            });
                            add_country(shape_points);
                        } else {
                            var shape_points = [];
                            coord_set.forEach(function (points) {
                                shape_points.push(new THREE.Vector2(points[0], points[1]));
                            });
                            add_country(shape_points);
                        }
                    });
                }
            });
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {



            requestAnimationFrame(animate);
            globe_manipulator.update();
            //controls.update();
            ///console.log(label_html);

            label.update();
            renderer.render(scene, camera);


            /*
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);*/
        }

And globe_manipulator is not creating the issue.

javascript
three.js
asked on Stack Overflow Oct 26, 2015 by void • edited Oct 26, 2015 by void

1 Answer

3

Im posting this as an answer now because its exceeding the comments intention.

First, convert your Lat/Long to a THREE.Vector3. You could use this function:

function convertLatLonToVec3(lat,lon) {
    lat =  lat * Math.PI / 180.0;
    lon = -lon * Math.PI / 180.0;
    return new THREE.Vector3( 
        Math.cos(lat) * Math.cos(lon),
        Math.sin(lat),
        Math.cos(lat) * Math.sin(lon));
};

You multiply this Vector3 by the radius of your sphere (globe) to get the position:

var radius = 100;
var position = convertLatLonToVec3( city.lat, city.lng ).multiplyScalar( radius );

This is your position in 3D-Space.

Next step get the screen space position from the 3D-Space position and update the HTML Overlay box-position. In short this is how it looks:

var screenVector = new THREE.Vector3( 0, 0, 0 );
screenVector.copy( position );
screenVector.project( camera );

var posx = Math.round(( screenVector.x + 1 ) * domElement.offsetWidth / 2 );
var posy = Math.round(( 1 - screenVector.y ) * domElement.offsetHeight / 2 );

// the html overlaybox
this.box.style.left = posx + 'px';
this.box.style.top =  posy + 'px';

This needs to be done on every update, makc is hijacking the Object3D for this.

This is a simple fiddle which may helps you understanding http://jsfiddle.net/qac19w5x/

enter image description here

Again only for reference, see this ready-made class from makc: three-js-and-2d-overlays

answered on Stack Overflow Oct 26, 2015 by Falk Thiele • edited Aug 27, 2019 by Falk Thiele

User contributions licensed under CC BY-SA 3.0