Code viewer for World: Landmark Museum

// Cloned by Chris Dobey on 1 Dec 2023 from World "First Person Controls" by Enhanced 
// Please leave this clone trail here.
 


// Cloned by Enhanced on 21 Jun 2018 from World "First Person Controls" by Mathias Bazin 
// Please leave this clone trail here.

// OpenAi API key (required for project to function and left blank by default):
var apikey = "";

const openaiURL = "https://api.openai.com/v1/chat/completions";           // can POST to this 3rd party URL
const ai_model = "gpt-3.5-turbo";       // the OpenAI model we are going to talk to

//Image Search API URL
var cors_proxy = 'https://corsproxy.io/?'
var search_url = 'https://www.googleapis.com/customsearch/v1?key=AIzaSyDCtyOoxLZw2GiG-FChBbziU7zHwyAxdH8&cx=a4e0f132c11a1483a&searchType=image&q='


const maxTokens = 300;  // Adjust as needed

const conversation = [
  { role: 'user', content: 'User message 1' },
  { role: 'assistant', content: 'Assistant message 1' },
];

// Customise AB run parameters (optional).
// The following parameters can be customised. (They have default values.)

AB.clockTick       = 20;    

	// Speed of run: Step every n milliseconds. Default 100.
	
AB.maxSteps        = 65545;    

	// Length of run: Maximum length of run in steps. Default 1000.

AB.screenshotStep  = 50;   
  
	// For automatic generation of World images.
	// Take screenshot on this step. (All resources should have finished loading.) Default 50.


AB.drawRunControls = false;
threeworld.drawCameraControls = false;

	
const floorTextureFile = "/uploads/dobeyc3/wooden_floor.png"
const wallTextureFile = "/uploads/dobeyc3/brick_wall.jpg"

const MOVESPEED = 3;


//==============================================================================
//  Defines the THREE.PointerLockControls class, source at https://threejs.org/
//==============================================================================

THREE.PointerLockControls = function ( camera ) {

	var scope = this;

	camera.rotation.set( 0, 0, 0 );

	var pitchObject = new THREE.Object3D();
	pitchObject.add( camera );

	var yawObject = new THREE.Object3D();
	yawObject.position.y = 10;
	yawObject.add( pitchObject );

	var PI_2 = Math.PI / 2;

	var onMouseMove = function ( event ) {

		if ( scope.enabled === false ) return;

		var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
		var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

		yawObject.rotation.y -= movementX * 0.002;
		pitchObject.rotation.x -= movementY * 0.002;

		pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );

	};

	this.dispose = function () {

		document.removeEventListener( 'mousemove', onMouseMove, false );

	};

	document.addEventListener( 'mousemove', onMouseMove, false );

	this.enabled = false;

	this.getObject = function () {

		return yawObject;

	};

	this.getDirection = function () {

		// assumes the camera itself is not rotated

		var direction = new THREE.Vector3( 0, 0, - 1 );
		var rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );

		return function ( v ) {

			rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 );

			v.copy( direction ).applyEuler( rotation );

			return v;

		};

	}();

};
//==============================================================================



function World() { 

    var camera, controls;

	var objects = [];

	var raycaster;
	
    var moveForward = false;
	var moveBackward = false;
	var moveLeft = false;
	var moveRight = false;
	var canJump = false;

	var prevTime = performance.now();
	var velocity = new THREE.Vector3();
	var direction = new THREE.Vector3();
	var vertex = new THREE.Vector3();
	var color = new THREE.Color();
	
	var countryInput;
	var image_url1 = ""
	var image_url2 = ""
	var image_url3 = ""
	var image_url4 = ""
	var parts = []
	var name_description1 = []
	var name_description2 = []
	var name_description3 = []
	var name_description4 = []
	
	var previousMesh1 = [];
	var previousMesh2 = [];
	var previousMesh3 = [];
	var previousMesh4 = [];
	
	this.newRun = function()
	{
	    
	    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
        threeworld.camera = camera;
	    threeworld.init3d ( 0,0, 0x7ec0ee  ); 
	    
	    var light = new THREE.HemisphereLight( 0xeeeeff, 0x777788, 0.75 );
		light.position.set( 0.5, 1, 0.75 );
		threeworld.scene.add( light );
		
		threeworld.scene.fog = new THREE.Fog( 0xffffff, 0, 1500 );

        controls = new THREE.PointerLockControls( camera );
        threeworld.scene.add( controls.getObject() );
        
        
        
        
    //This will handle key presses
    
        var onKeyDown = function ( event ) {

	    	switch ( event.keyCode ) {

				case 38: // up
				case 87: // w
					moveForward = true;
					break;

				case 37: // left
				case 65: // a
					moveLeft = true; break;

				case 40: // down
				case 83: // s
					moveBackward = true;
					break;

				case 39: // right
				case 68: // d
					moveRight = true;
					break;

				case 32: // space
					if ( canJump === true ) velocity.y += 350;
					canJump = false;
					break;

			}

		};

		var onKeyUp = function ( event ) {

			switch( event.keyCode ) {

				case 38: // up
				case 87: // w
					moveForward = false;
					break;

				case 37: // left
				case 65: // a
					moveLeft = false;
					break;

				case 40: // down
				case 83: // s
					moveBackward = false;
                    break;

				case 39: // right
				case 68: // d
					moveRight = false;
					break;

			}

		};

		document.addEventListener( 'keydown', onKeyDown, false );
		document.addEventListener( 'keyup', onKeyUp, false );
		
		raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 );

        
    
        
	    $("#user_span1").html(" Use WASD or Arrows to move, mouse to look around and space to jump.");
	    
	    
	    
	    
	    var blocker = $("#user_span2");
	    blocker.html("<p><b>Click screen to enable mouse controls</b></p>");

        // Assuming you have a variable named 'paragraph' defined elsewhere
        var description1 = ""; // Replace this line with your actual definition
        var description2 = "";
        var description3 = "";
        var description4 = "";
        var landmark1 = ""; // Replace this line with your actual definition
        var landmark2 = "";
        var landmark3 = "";
        var landmark4 = "";
        // Initial HTML string
        var thehtml = "";
        var htmlbutton = "";
        var generatedText = "";
        
        // Create a text box and append it to the HTML string
        var textBox = document.createElement('input');
        textBox.type = 'text';
        textBox.id = 'myTextBox';
        htmlbutton = "<button id='countrySubmit' class='normbutton'>Submit</button> <br> ";
        thehtml += "<label for='myTextBox'>Enter a Country: </label>" + textBox.outerHTML + htmlbutton;
        AB.msg(thehtml, 1);
        
        // Access the text box using its ID
        var myTextBoxElement = document.getElementById('myTextBox');
        

		// Define Button function
		function logText() {
		    
		    generatedText = ""
		    parts = []
		    
		    const x = myTextBoxElement.value; // Replace this with dynamic value

            fetch(openaiURL, {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${apikey}`,
              },
              body: JSON.stringify({
                model: ai_model,
                messages: [{
                  role: "user",
                  content: `For the country of ${x}, I want you to return a brief description of each of this country's 4 most famous landmarks. Each description should be aproximately 45 words in length. Give your response in the following format: "Insert name of landmark 1 here: Insert Description 1 here (delimit with "/" symbol) Insert name of landmark 2 here: Insert Description 2 here (delimit with "/" symbol) Insert name of landmark 3 here: Insert Description 3 here (delimit with "/" symbol) Insert name of landmark 4 here: Insert Description 4 here"." It is vitally important that you delimit between each landmark with a / symbol`,
                }],
                max_tokens: maxTokens,
              }),
            })
              .then(response => response.json())
              .then(data => {
                if (data.choices && data.choices.length > 0) {
                  generatedText = data.choices[0].message.content;
                  console.log(generatedText);
                  try {
                    // Split the string based on the slash symbol
                    parts = generatedText.split('/');
                    
                    // Extract the part before the colon for landmark 1
                    name_description1 = parts[0].split(':');
                    landmark1 = name_description1[0].trim(); // Use trim to remove leading and trailing whitespaces
                    description1 = name_description1[1].trim(); // Use trim to remove leading and trailing whitespaces
                
                    // Extract the part before the colon for landmark 2
                    name_description2 = parts[1].split(':');
                    landmark2 = name_description2[0].trim(); // Use trim to remove leading and trailing whitespaces
                    description2 = name_description2[1].trim(); // Use trim to remove leading and trailing whitespaces
                
                    // Extract the part before the colon for landmark 3
                    name_description3 = parts[2].split(':');
                    landmark3 = name_description3[0].trim(); // Use trim to remove leading and trailing whitespaces
                    description3 = name_description3[1].trim(); // Use trim to remove leading and trailing whitespaces
                
                    // Extract the part before the colon for landmark 4
                    name_description4 = parts[3].split(':');
                    landmark4 = name_description4[0].trim(); // Use trim to remove leading and trailing whitespaces
                    description4 = name_description4[1].trim(); // Use trim to remove leading and trailing whitespaces
                
                    placeText();
                } catch (error) {
                    console.log("Failed to delimit on /, Trying for a new Response...");
                    logText();
                }
                } else {
                  console.error('Error: Empty or undefined data.choices array');
                }
              })
              .catch(error => console.error('Error:', error));

		}
		    
		function logImage() {
            // Image 1
            const fetchImage = (searchUrl, landmark, imageNumber) => {
              const imageSearch = searchUrl + landmark;

              return fetch(imageSearch, {
                method: 'GET',
                headers: {
                  'Content-Type': 'application/json',
                },
              })
                .then(response => response.json())
                .then(data => {
                  if (data.items && data.items.length > 0) {
                    return cors_proxy + data.items[0].link;
                  } else {
                    console.error(`Error: Empty or undefined data.items array for image ${imageNumber}`);
                    return null;
                  }
                })
                .catch(error => {
                  console.error(`Error fetching image ${imageNumber}:`, error);
                  return null;
                });
            };

            const imagePromises = [
              fetchImage(search_url, landmark1, 1),
              fetchImage(search_url, landmark2, 2),
              fetchImage(search_url, landmark3, 3),
              fetchImage(search_url, landmark4, 4),
            ];

            // MH edit
            console.log ( landmark1 );

            Promise.all(imagePromises)
              .then(imageURLs => {
                // imageURLs is an array containing the results of all the fetch requests
                [image_url1, image_url2, image_url3, image_url4] = imageURLs;
                
                // MH edit
                console.log ( imageURLs );

                placeImage();
              })
              .catch(error => console.error('Error:', error));

		}
		
            function placeText() {
                
                if (previousMesh1.length > 0) {
                    for (var i = 0; i < previousMesh1.length; i++) {
                        threeworld.scene.remove(previousMesh1[i]);
                    }
                    for (var i = 0; i < previousMesh2.length; i++) {
                        threeworld.scene.remove(previousMesh2[i]);
                    }
                    for (var i = 0; i < previousMesh3.length; i++) {
                        threeworld.scene.remove(previousMesh3[i]);
                    }
                    for (var i = 0; i < previousMesh4.length; i++) {
                        threeworld.scene.remove(previousMesh4[i]);
                    }
                    // Clear the arrays
                    previousMesh1 = [];
                    previousMesh2 = [];
                    previousMesh3 = [];
                    previousMesh4 = [];
                }
                    
                // Load the font
                var loader = new THREE.FontLoader();
                loader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', function(font) {
                
    
                    // Define the maximum number of characters per line
                    var maxCharCount = 28;
                
                    // Split the paragraph into lines based on character count
                    var lines1 = (landmark1 + '\n' + description1).match(new RegExp('.{1,' + maxCharCount + '}', 'g'));
                    var lines2 = (landmark2 + '\n' + description2).match(new RegExp('.{1,' + maxCharCount + '}', 'g'));
                    var lines3 = (landmark3 + '\n' + description3).match(new RegExp('.{1,' + maxCharCount + '}', 'g'));
                    var lines4 = (landmark4 + '\n' + description4).match(new RegExp('.{1,' + maxCharCount + '}', 'g'));
                    
                    // Define the text material
                    var textMaterial = new THREE.MeshBasicMaterial({color: 0xFFFFFF}); // White color
                
                    // For Paragraph 1
                    for (var i = 0; i < lines1.length; i++) {
                
                        // Define the text geometry
                        var textGeometry = new THREE.TextGeometry(lines1[i], {
                            font: font,
                            size: 15,
                            height: 0.1,
                            curveSegments: 12,
                            bevelEnabled: true,
                            bevelThickness: 0.01,
                            bevelSize: 0.01,
                            bevelOffset: 0,
                            bevelSegments: 5
                        });
                
                        // Create the text mesh
                        var textMesh1 = new THREE.Mesh(textGeometry, textMaterial);
                
                        // Set the position of the text
                        textMesh1.position.set(-225, 210 - i * 20, -249); // Front wall, adjust the y position based on the line number
                
                        
                        // Add the text to the scene
                        threeworld.scene.add(textMesh1);
                        
                        previousMesh1.push(textMesh1);
                    }
                    
                    // Paragraph2
                    for (var i = 0; i < lines2.length; i++) {
                
                        // Define the text geometry
                        var textGeometry = new THREE.TextGeometry(lines2[i], {
                            font: font,
                            size: 15,
                            height: 0.1,
                            curveSegments: 12,
                            bevelEnabled: true,
                            bevelThickness: 0.01,
                            bevelSize: 0.01,
                            bevelOffset: 0,
                            bevelSegments: 5
                        });
                
                        // Create the text mesh
                        var textMesh2 = new THREE.Mesh(textGeometry, textMaterial);
                        
                        // Set the position of the text
                        textMesh2.position.set(249, 210 - i * 20, -225); // Front wall, adjust the y position based on the line number
                        textMesh2.rotation.set(0, -Math.PI / 2, 0);
                        
                        // Add the text to the scene
                        threeworld.scene.add(textMesh2);
                        
                        previousMesh2.push(textMesh2);
                    }
                    
                    // Paragraph 3
                    for (var i = 0; i < lines3.length; i++) {
                
                        // Define the text geometry
                        var textGeometry = new THREE.TextGeometry(lines3[i], {
                            font: font,
                            size: 15,
                            height: 0.1,
                            curveSegments: 12,
                            bevelEnabled: true,
                            bevelThickness: 0.01,
                            bevelSize: 0.01,
                            bevelOffset: 0,
                            bevelSegments: 5
                        });
                
                        // Create the text mesh
                        var textMesh3 = new THREE.Mesh(textGeometry, textMaterial);
                
                        // Set the position of the text
                        textMesh3.position.set(225, 210 - i * 20, 249); // Front wall, adjust the y position based on the line number
                        // Rotate the text to face the wall behind the camera
                        textMesh3.rotation.set(0, Math.PI, 0);
                        
                        // Add the text to the scene
                        threeworld.scene.add(textMesh3);
                        
                        previousMesh3.push(textMesh3);
                    }
                    
                    // Paragraph 4
                    for (var i = 0; i < lines4.length; i++) {
                
                        // Define the text geometry
                        var textGeometry = new THREE.TextGeometry(lines4[i], {
                            font: font,
                            size: 15,
                            height: 0.1,
                            curveSegments: 12,
                            bevelEnabled: true,
                            bevelThickness: 0.01,
                            bevelSize: 0.01,
                            bevelOffset: 0,
                            bevelSegments: 5
                        });
                
                        // Create the text mesh
                        var textMesh4 = new THREE.Mesh(textGeometry, textMaterial);
                
                        // Set the position of the text
                        textMesh4.position.set(-249, 210 - i * 20, 225); // Front wall, adjust the y position based on the line number
                        textMesh4.rotation.set(0, Math.PI / 2, 0);
                        
                        // Add the text to the scene
                        threeworld.scene.add(textMesh4);
                        
                        previousMesh4.push(textMesh4);
                    }
                });
                logImage();
    		}
    		
    		function placeImage() {
        		var image_loader = new THREE.TextureLoader();
                // Create a geometry for the wall
                var geometry = new THREE.PlaneGeometry(150, 200);
            
                // Create a material using the texture (placeholder texture for now)
                var material = new THREE.MeshBasicMaterial({ color: 0xffffff });
            
                // Load the image 1
                image_loader.load(image_url1, function (texture) {
                    // Create a new material using the loaded texture
                    var material1 = new THREE.MeshBasicMaterial({ map: texture });
            
                    // Create a mesh using the geometry and new material
                    var mesh1 = new THREE.Mesh(geometry, material1);
            
                    // Set the position of the mesh
                    mesh1.position.set(150, 120, -249); // Front wall
            
                    // Add the mesh to the scene
                    threeworld.scene.add(mesh1);
                });
            
                // Load the image 2
                image_loader.load(image_url2, function (texture) {
                    // Create a new material using the loaded texture
                    var material2 = new THREE.MeshBasicMaterial({ map: texture });
            
                    // Create a mesh using the geometry and new material
                    var mesh2 = new THREE.Mesh(geometry, material2);
            
                    // Set the position and rotation of the mesh
                    mesh2.position.set(249, 120, 150);
                    mesh2.rotation.set(0, -Math.PI / 2, 0);
            
                    // Add the mesh to the scene
                    threeworld.scene.add(mesh2);
                });
                
                // Load the image 3
                image_loader.load(image_url3, function (texture) {
                    // Create a new material using the loaded texture
                    var material3 = new THREE.MeshBasicMaterial({ map: texture });
            
                    // Create a mesh using the geometry and new material
                    var mesh3 = new THREE.Mesh(geometry, material3);
            
                    // Set the position and rotation of the mesh
                    mesh3.position.set(-150, 120, 249);
                    mesh3.rotation.set(0, Math.PI, 0);
            
                    // Add the mesh to the scene
                    threeworld.scene.add(mesh3);
                });
                
                // Load the image 4
                image_loader.load(image_url4, function (texture) {
                    // Create a new material using the loaded texture
                    var material4 = new THREE.MeshBasicMaterial({ map: texture });
            
                    // Create a mesh using the geometry and new material
                    var mesh4 = new THREE.Mesh(geometry, material4);
            
                    // Set the position and rotation of the mesh
                    mesh4.position.set(-249, 120, -150);
                    mesh4.rotation.set(0, Math.PI / 2, 0);
            
                    // Add the mesh to the scene
                    threeworld.scene.add(mesh4);
                });
    		}
    		
    		

		// Attach event listener using JavaScript
		document.getElementById('countrySubmit').addEventListener('click', logText);




        
    
    //The following handles pointer locking when clicking the window
	    
	    var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
        console.log(havePointerLock);
        if ( havePointerLock ) {

			var element = document.body;

			var pointerlockchange = function ( event ) {

				if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) {

					controls.enabled = true;
					blocker.html(" <p><b>Use WASD or Arrows to move, mouse to look around and space to jump.</b></p> ");

				} else
				{
					controls.enabled = false;
					blocker.html("<p><b>Click screen to enable mouse controls</b></p>");

				}

			};

			var pointerlockerror = function ( event ) {

				console.error("pointerlockerror");

			};

			// Hook pointer lock state change events
			document.addEventListener( 'pointerlockchange', pointerlockchange, false );
			document.addEventListener( 'mozpointerlockchange', pointerlockchange, false );
			document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false );

			document.addEventListener( 'pointerlockerror', pointerlockerror, false );
			document.addEventListener( 'mozpointerlockerror', pointerlockerror, false );
			document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false );

			document.addEventListener( 'click', function ( event ) {
                
                // Check if the clicked element has either 'myTextBox' or 'button' ID
                if (event.target.id === 'myTextBox' || event.target.id === 'countrySubmit') {
                    return; // Do nothing for the excluded elements
                }
                
				// Ask the browser to lock the pointer
				element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
				element.requestPointerLock();

			}, false );

		} else {

			$("#user_span1").html('<p>Your browser doesn\'t seem to support Pointer Lock API</p>');

		}
		
		
	//The following draws a simple scene
	
		//floor
	    
	    var floorGeometry = new THREE.PlaneBufferGeometry( 2000, 2000, 100, 100 );
        floorGeometry.rotateX( - Math.PI / 2 );
        var floorTexture = new THREE.ImageUtils.loadTexture ( floorTextureFile );
        floorTexture.minFilter = THREE.LinearFilter;
        floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
        floorTexture.offset.set( 0, 0 );
        floorTexture.repeat.set( 40, 40 );
        var floor = new THREE.Mesh(floorGeometry, new THREE.MeshBasicMaterial({map : floorTexture}));
        floor.position.set(0,-20,0);
        threeworld.scene.add(floor);
        
        // Define the geometry of the wall
        var wallGeometry = new THREE.BoxGeometry(500, 500, 1);
        
        // Define the material of the wall
        var textureLoader = new THREE.TextureLoader();
        var wallTexture = new THREE.ImageUtils.loadTexture ( wallTextureFile );
        
        // Create a material with the loaded texture
        var wallMaterial = new THREE.MeshBasicMaterial({ map: wallTexture });

        
        // Create four walls
        var wall1 = new THREE.Mesh(wallGeometry, wallMaterial);
        var wall2 = new THREE.Mesh(wallGeometry, wallMaterial);
        var wall3 = new THREE.Mesh(wallGeometry, wallMaterial);
        var wall4 = new THREE.Mesh(wallGeometry, wallMaterial);
        
        // Set the position of each wall
        wall1.position.set(0, 5, -250); // Front wall
        wall2.position.set(0, 5, 250); // Back wall
        wall3.position.set(-250, 5, 0); // Left wall
        wall4.position.set(250, 5, 0); // Right wall
        
        // Rotate the left and right walls
        wall3.rotation.y = Math.PI / 2;
        wall4.rotation.y = Math.PI / 2;
        
        // Add the walls to the scene
        threeworld.scene.add(wall1);
        threeworld.scene.add(wall2);
        threeworld.scene.add(wall3);
        threeworld.scene.add(wall4);
        
        

	};


	this.nextStep = function()		 
	{

        
        //======================================================================
        //This will handle moving the player and the camera
        //======================================================================
        
        
			raycaster.ray.origin.copy( controls.getObject().position );
			raycaster.ray.origin.y -= 10;

			var intersections = raycaster.intersectObjects( objects );

			var onObject = intersections.length > 0;

			var time = performance.now();
			var delta = ( time - prevTime ) / 1000;

			velocity.x -= velocity.x * 10.0 * delta;
	    	velocity.z -= velocity.z * 10.0 * delta;

			velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass

			direction.z = Number( moveForward ) - Number( moveBackward );
			direction.x = Number( moveLeft ) - Number( moveRight );
			direction.normalize(); // this ensures consistent movements in all directions

			if ( moveForward || moveBackward ) velocity.z -= direction.z * 400.0 * MOVESPEED * delta;
			if ( moveLeft || moveRight ) velocity.x -= direction.x * 400.0 * MOVESPEED * delta;

			if ( onObject === true ) {

				velocity.y = Math.max( 0, velocity.y );
				canJump = true;

			}

			controls.getObject().translateX( velocity.x * delta );
			controls.getObject().translateY( velocity.y * delta );
			controls.getObject().translateZ( velocity.z * delta );

			if ( controls.getObject().position.y < 10 ) {

				velocity.y = 0;
				controls.getObject().position.y = 10;

				canJump = true;

			}

			prevTime = time;
        
        //======================================================================
        
        
        
	
	};

}