javascriptphphtmlcodeigniter360-panorama-viewer

Integrate Dynamic Images with HTML5 WebGL 360 degrees panorama viewer with Three.js in Codeigniter


The Panorama 360 Viewers Found HERE and HERE are easy to use if you just copy and paste the code available in their documentation and place the 360 images right next to the index file and open it in browser. However is there a way to Dynamically bring images from Database and render the 360 images in the view like this (link) enter image description here

The code given in panorama viewer file gets images in Panorama Array like this

var panoramasArray = ["01.jpg","02.jpg","03.jpg","04.jpg","05.jpg","06.jpg"];
var panoramaNumber = Math.floor(Math.random()*panoramasArray.length);

What if we have to display only one image? Has anyone tried to bring images dynamically from database and rendered a view with 360 viewer in it?. I have seen an unanswered thread here but no one replied that question.


Solution

  • For many of Codeigniter Developers and those who have been developing real-estate websites, either wanting to or have tried but failed to integrate 360 image viewer in their websites. Here is the exercise which I have done and learned so far.

    How it works?

    1. Upload 360 Image
    2. Get 360 Images from Database
    3. Display / Render the view

    What we need?

    This is my view to upload 360 Image This is my view to upload 360 images, which is just a form with file input field.

    public function upload_360_images()
    {
        if($this->session->userdata['id'] && $this->session->userdata['type']=='user')
        {
            if($_FILES)
            {
                if(isset($_FILES['files'])){
                    $data['errors']= array();
                    $extensions = array("jpeg","jpg","png");
    
                    foreach($_FILES['files']['tmp_name'] as $key => $tmp_name ){
    
                        $file_name = $key.$_FILES['files']['name'][$key];
                        $file_size =$_FILES['files']['size'][$key];
                        $file_tmp =$_FILES['files']['tmp_name'][$key];
                        $file_type=$_FILES['files']['type'][$key];
                        /*$file_ext=explode('.',$_FILES['image']['name'][$key]) ;
                        $file_ext=end($file_ext);*/
                        $i=1;
                        if($file_size > 7097152){
                            $data['errors'][$i]='File '.$i.' size must be less than 7 MB';
                            $i++;
                        }
    
                        $desired_dir="uploads";
                        if(empty($data['errors'])==true){
                            if(is_dir($desired_dir)==false){
                                mkdir("$desired_dir", 0700);        // Create directory if it does not exist
                            }
                            if(is_dir("$desired_dir/".$file_name)==false){
                                move_uploaded_file($file_tmp,"uploads/".$file_name);
                                $this->post_model->add360Image('property_360_images',$file_name,$this->uri->segment(3));
                            }else{                                  //rename the file if another one exist
                                $new_dir="uploads/".$file_name.time();
                                rename($file_tmp,$new_dir) ;
                            }
    
                        }else{
                            $data['contact']=$this->admin_model->getContactDetails();
                            $data['images']=$this->post_model->getProperty360Images($this->uri->segment(3));
                            $data['title']='My Profile Image';
                            $this->load->view('site/static/head',$data);
                            $this->load->view('site/static/header');
                            $this->load->view('site/content/upload_360_images');
                            $this->load->view('site/static/footer');
                            $this->load->view('site/static/footer_links');
                        }
                    }
                    if(empty($data['errors']))
                    {
                        redirect(base_url().'dashboard');
                    }
                    else
                    {
                        $data['contact']=$this->admin_model->getContactDetails();
                        $data['images']=$this->post_model->getProperty360Images($this->uri->segment(3));
                        $data['title']='My Profile Image';
                        $this->load->view('site/static/head',$data);
                        $this->load->view('site/static/header');
                        $this->load->view('site/content/upload_360_images');
                        $this->load->view('site/static/footer');
                        $this->load->view('site/static/footer_links');
                    }
                }
    
            }
            else
            {
                $data['contact']=$this->admin_model->getContactDetails();
                $data['images']=$this->post_model->getProperty360Images($this->uri->segment(3));
                $data['title']='My Profile Image';
                $this->load->view('site/static/head',$data);
                $this->load->view('site/static/header');
                $this->load->view('site/content/upload_360_images');
                $this->load->view('site/static/footer');
                $this->load->view('site/static/footer_links');
            }
    
        }
        else
        {
            redirect(base_url().'user/login');
        }
    
    }
    

    Above is my Controller function which uploads the 360 Image and saves the name in the database. Nothing Fancy, I am not using CI upload Library

    DB Schema

    This is my Database Table to store 360 Image names

    public function property_detail()
    {
    
        $id=$this->uri->segment(3);
        $this->property_model->incPageViewById($id);
        $data['contact']=$this->admin_model->getContactDetails();
        $data['section_fields']=$this->admin_model->getSectionFields('property_sections');
        $data['property']=$this->property_model->getPropertyById($id);
        // Get 360 Images list of this property based on ID
        $data['images360']=$this->post_model->getProperty360Images($id);
        $data['profile']=$this->property_model->getFieldsById($id);
        $data['types']=$this->admin_model->getAll('property_types');
    
        $data['similar']=$this->property_model->getSimilarPropertiesById($data['property'][0]['posted_by']);
        $data['popular']=$this->property_model->getAllProperties(0,0);
        if($this->isLoggedIn())
        {
            $data['favorites']=$this->property_model->getMyFavorites($this->session->userdata['id']);
            $data['is_favorite']=$this->property_model->isFavorite($id,$this->session->userdata['id']);
        }
    
        $data['posted_by']=$this->property_model->getPostedByDetails($id);
        $data['comments']=$this->property_model->getCommentsById($id);
        if($_POST)
        {
            $config=array(
                array(
                    'field'     => 'name',
                    'label'     => 'Name',
                    'rules'     => 'trim|required',
                ),
                array(
                    'field'     => 'email',
                    'label'     => 'Email',
                    'rules'     => 'trim|required',
                ),
                array(
                    'field'     => 'comment',
                    'label'     => 'Comment',
                    'rules'     => 'trim|required',
                )
            );
            $this->form_validation->set_rules($config);
            if($this->form_validation->run()==false)
            {
                $data['errors']=validation_errors();
                $data['title']=$data['property'][0]['title'];
                $this->load->view('site/static/head',$data);
                $this->load->view('site/static/header');
                $this->load->view('site/content/property_detail');
                $this->load->view('site/static/footer');
                $this->load->view('site/static/footer_links');
            }
            else
            {
                $this->property_model->addComment($_POST,$id);
                $data['success']='Comment posted successfully';
                $data['comments']=$this->property_model->getCommentsById($id);
                $data['title']=$data['property'][0]['title'];
                $this->load->view('site/static/head',$data);
                $this->load->view('site/static/header');
                $this->load->view('site/content/property_detail');
                $this->load->view('site/static/footer');
                $this->load->view('site/static/footer_links');
            }
        }
        else
        {
            $data['title']=$data['property'][0]['title'];
            $this->load->view('site/static/head',$data);
            $this->load->view('site/static/header');
            $this->load->view('site/content/property_detail');
            $this->load->view('site/static/footer');
            $this->load->view('site/static/footer_links');
        }
    
    }
    

    Above is the Controller Function which gets all the data from Models and Calls the view to render the page. You can see in the controller function the following lines

    // Get 360 Images list of this property based on ID
    $data['images360']=$this->post_model->getProperty360Images($id);
    

    get the list of 360 images from the model. Now in the property detail view I am again calling to the view which actually displays the 360 image in an < iframe > and sending the image name to the URL which displays the 360 image.

    <?php if(count($images360)>0){
     ?><h3>360 View</h3><?php
     for($i=0;$i<count($images360);$i++){?>
        <iframe src = "https://duperty.com/realestate/load360/showImage/<?php echo $images360[$i]['image']?>" width = "100%" height = "540" frameborder = "0" scrolling = "no"></iframe>
        <?php }
      }?>
    

    I have another controller load360 with a function showImage which receives Image name as parameter and calls the view which displays the 360 image

    class Load360 extends CI_Controller {
    function __construct()
    {
        parent::__construct();
    }
    
    public function showImage()
    {
        header("cache-Control: no-store, no-cache, must-revalidate");
        header("cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");
        header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
    
        $data['image']=$this->uri->segment(3);
        //echo $data['image'];exit;
        $this->load->view('site/content/show360',$data);
    }
    

    }

    In my show360 view which I am calling here I am simply echoing the image variable along with the image path.

       <html>
       <head>
         <style>
         body{
            margin: 0;
        }
    
           canvas {
            height: 100%;
            width: 100%;
            }
           </style>
         <script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>
    
          </head>
          <body>
    
         <script>
    
            var manualControl = false;
            var longitude = 0;
            var latitude = 0;
            var savedX;
            var savedY;
            var savedLongitude;
            var savedLatitude;
    
            // panoramas background
            var panoramasArray = ["<?php echo base_url().'uploads/'.$image;?>"];
            var panoramaNumber = Math.floor(Math.random()*panoramasArray.length);
    
            // setting up the renderer
            renderer = new THREE.WebGLRenderer();
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);
    
            // creating a new scene
            var scene = new THREE.Scene();
    
            // adding a camera
            //var camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.5, 500);
            var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
            camera.target = new THREE.Vector3(0, 0, 0);
    
            // creation of a big sphere geometry
            var sphere = new THREE.SphereGeometry(100, 100, 40);
            sphere.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));
    
            // creation of the sphere material
            var sphereMaterial = new THREE.MeshBasicMaterial();
            sphereMaterial.map = THREE.ImageUtils.loadTexture(panoramasArray[panoramaNumber])
    
            // geometry + material = mesh (actual object)
            var sphereMesh = new THREE.Mesh(sphere, sphereMaterial);
            scene.add(sphereMesh);
    
            // listeners
            document.addEventListener("mousedown", onDocumentMouseDown, false);
            document.addEventListener("mousemove", onDocumentMouseMove, false);
            document.addEventListener("mouseup", onDocumentMouseUp, false);
    
            render();
    
            function render(){
    
                requestAnimationFrame(render);
    
                if(!manualControl){
                    longitude += 0.1;
                }
    
                // limiting latitude from -85 to 85 (cannot point to the sky or under your feet)
                latitude = Math.max(-85, Math.min(85, latitude));
    
                // moving the camera according to current latitude (vertical movement) and longitude (horizontal movement)
                camera.target.x = 500 * Math.sin(THREE.Math.degToRad(90 - latitude)) * Math.cos(THREE.Math.degToRad(longitude));
                camera.target.y = 500 * Math.cos(THREE.Math.degToRad(90 - latitude));
                camera.target.z = 500 * Math.sin(THREE.Math.degToRad(90 - latitude)) * Math.sin(THREE.Math.degToRad(longitude));
                camera.lookAt(camera.target);
    
                // calling again render function
                renderer.render(scene, camera);
    
            }
    
            // when the mouse is pressed, we switch to manual control and save current coordinates
            function onDocumentMouseDown(event){
    
                event.preventDefault();
    
                manualControl = true;
    
                savedX = event.clientX;
                savedY = event.clientY;
    
                savedLongitude = longitude;
                savedLatitude = latitude;
    
            }
    
            // when the mouse moves, if in manual contro we adjust coordinates
            function onDocumentMouseMove(event){
    
                if(manualControl){
                    longitude = (savedX - event.clientX) * 0.1 + savedLongitude;
                    latitude = (event.clientY - savedY) * 0.1 + savedLatitude;
                }
    
            }
    
            // when the mouse is released, we turn manual control off
            function onDocumentMouseUp(event){
    
                manualControl = false;
    
            }
    
            // pressing a key (actually releasing it) changes the texture map
            document.onkeyup = function(event){
    
                panoramaNumber = (panoramaNumber + 1) % panoramasArray.length
                sphereMaterial.map = THREE.ImageUtils.loadTexture(panoramasArray[panoramaNumber])
    
            }
    
        </script>
    
     </body>
     </html>
    

    And Jobs Done. You can see how views are rendered on following links

    Double 360 Images

    Single 360 Image