Mudassar Khani
Mudassar Khani

Reputation: 1479

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.

Upvotes: 0

Views: 5293

Answers (1)

Mudassar Khani
Mudassar Khani

Reputation: 1479

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?

  • A function in the Controller to Upload the 360 Images
  • A function in the model to save and get list of 360 Images
  • A function to call the views to display the images.
  • A markup page to display 360 image which has all the JavaScript in it.

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

Upvotes: 2

Related Questions