// ==== Starter World =============================================================================================== // (c) Ancient Brain Ltd. All rights reserved. // This code is only for use on the Ancient Brain site. // This code may be freely copied and edited by anyone on the Ancient Brain site. // This code may not be copied, re-published or used on any other website. // To include a run of this code on another website, see the "Embed code" links provided on the Ancient Brain site. // ================================================================================================================== // Physijs based World // Bouncy balls // =================================================================================================================== // === Start of tweaker's box ======================================================================================== // =================================================================================================================== // The easiest things to modify are in this box. // You should be able to change things in this box without being a JavaScript programmer. // Go ahead and change some of these. What's the worst that could happen? AB.screenshotStep = 200; // Take screenshot on this step. (All resources should have finished loading.) Default 50. const BALLSIZE = 5 ; const BALLPOS = BALLSIZE * 2 ; // x,z start position of balls is random in this interval const BALLHEIGHT = BALLSIZE * 10 ; // y start position of balls const HEAVYIMPACT = 60 ; // heavy impacts have y velocity greater than this const GROUNDSIZE = BALLSIZE * 50; const startRadius = BALLSIZE * 30 ; const maxRadius = BALLSIZE * 250 ; const TEXTURE_GROUND = '/uploads/starter/rocks.jpg' ; // const TEXTURE_GROUND = '/uploads/starter/latin.jpg' ; const SOUND_COLLISION = '/uploads/starter/bounce.mp3' ; // credit: // http://soundbible.com/1343-Jump.html const SKYCOLOR = 0xffffcc; // friction and restitution between 0 and 1: const GROUND_FRICTION = 0.1; const GROUND_RESTITUTION = 0.95; const BALL_FRICTION = 0.1; const BALL_RESTITUTION = 0.95; // define gravity along x,y,z dimensions: var gravity = new THREE.Vector3 ( 0, -50, 0 ); const BALL_MASS = 1; function nextballin() // Speed of ball creation // Create next ball in some random (m to n) milliseconds time. { // return ( AB.randomIntAtoB ( 2000, 4000 ) ); return ( AB.randomIntAtoB ( 200, 1000 ) ); } const FILE_ARRAY = [ "/uploads/starter/earth.1.jpg", "/uploads/starter/earth.2.jpg", "/uploads/starter/earth.3.jpg", "/uploads/starter/earth.4.jpg", "/uploads/starter/earth.5.jpg" ]; ABWorld.drawCameraControls = false; // =================================================================================================================== // === End of tweaker's box ========================================================================================== // =================================================================================================================== // You will need to be some sort of JavaScript programmer to change things below the tweaker's box. var resourcesLoaded = false; var splashClicked = false; function World() { var textureArray = new Array ( FILE_ARRAY.length ); var ground_texture ; var self = this; function loadResources() // asynchronous file loads - call initScene() when all finished { var loader = new THREE.TextureLoader(); loader.load ( TEXTURE_GROUND, function ( thetexture ) // asynchronous file load { thetexture.minFilter = THREE.LinearFilter; ground_texture = thetexture; if ( asynchFinished() ) initScene(); }); for ( var i = 0; i < FILE_ARRAY.length; i++ ) startFileLoad ( i ); // start n more asynchronous file loads } function startFileLoad ( n ) // asynchronous file load of texture n { var loader = new THREE.TextureLoader(); loader.load ( FILE_ARRAY[n], function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; textureArray[n] = thetexture; if ( asynchFinished() ) initScene(); }); } function asynchFinished() // all file loads returned { if ( ! ground_texture ) return false; for ( var i = 0; i < FILE_ARRAY.length; i++ ) if ( ! textureArray[i] ) return false; return true; } function initScene() // all file loads have returned { // --- Light ------------------------------------------------------------------ var light = new THREE.DirectionalLight( 0xFFFFFF, 1.3 ); // close to origin, high up, works best for shadows light.position.set ( BALLHEIGHT, (BALLHEIGHT * 2), 0 ); light.target.position.copy( ABWorld.scene.position ); light.castShadow = true; // how far away to draw shadows: light.shadow.camera.left = -GROUNDSIZE; light.shadow.camera.right = GROUNDSIZE; light.shadow.camera.bottom = -GROUNDSIZE; light.shadow.camera.top = GROUNDSIZE; // higher quality shadows at expense of computation time: light.shadow.mapSize.width = 2048; light.shadow.mapSize.height = 2048; light.shadow.bias = -0.0001; ABWorld.scene.add( light ); // --- Ground ------------------------------------------------------------------ var ground_material = Physijs.createMaterial( // new THREE.MeshBasicMaterial( { color: 0xdddddd } ), new THREE.MeshLambertMaterial({ map: ground_texture }), GROUND_FRICTION, GROUND_RESTITUTION ); ground_material.map.wrapS = THREE.RepeatWrapping; ground_material.map.wrapT = THREE.RepeatWrapping; ground_material.map.repeat.set( 3, 3 ); // ground as plane allows bounces but seems to be infinite plane var ground = new Physijs.PlaneMesh ( new THREE.PlaneGeometry ( GROUNDSIZE, GROUNDSIZE ), ground_material ); ground.rotation.x = (Math.PI / 2) * 3; /* // ground as box is finite (beyond it, things fall into the void) // but it does not seem to allow bounces ground = new Physijs.BoxMesh ( new THREE.BoxGeometry ( GROUNDSIZE, 1, GROUNDSIZE ), ground_material, 0 ); */ ground.receiveShadow = true; ABWorld.scene.add( ground ); // start infinite loop - but maybe neutralised until splashClicked is true ABWorld.render(); console.log ( "Resources loaded." ); resourcesLoaded = true; createBall(); } function createBall() { if ( resourcesLoaded && splashClicked ) { // (subtle bug) apparently each ball needs its own material if it is to bounce separately var ball_material = Physijs.createMaterial ( new THREE.MeshLambertMaterial({ map: AB.randomElementOfArray(textureArray) }), BALL_FRICTION, BALL_RESTITUTION ); var ball_geometry = new THREE.SphereGeometry ( BALLSIZE, 20, 20 ); var ball = new Physijs.SphereMesh ( ball_geometry, ball_material, BALL_MASS ); ball.collisions = 0; ball.castShadow = true; ball.position.set( AB.randomIntAtoB(-BALLPOS,BALLPOS), BALLHEIGHT, AB.randomIntAtoB(-BALLPOS,BALLPOS) ); ball.addEventListener( 'collision', function( other_object, relative_velocity, relative_rotation, contact_normal ) { var mainImpact = relative_velocity.y; // impact in direction of gravity // console.log ( Math.abs(mainImpact) ); if ( Math.abs(mainImpact) > HEAVYIMPACT ) // main impact, not lesser ones as it settles { soundCollision(); } }); ABWorld.scene.add( ball ); } // --- call createBall again ----------------------------------------------------- setTimeout ( createBall, nextballin() ); // set to create next ball in some random time } // --- public interface ---------------------------------------------------------------------- this.newRun = function() { ABWorld.init3d ( startRadius, maxRadius, SKYCOLOR ); // sets up renderer, scene, camera // can adjust renderer: ABWorld.renderer.shadowMap.enabled = true; // scene is Physijs.Scene, not THREE.Scene // can adjust scene - change gravity from default: ABWorld.scene.setGravity ( gravity ); loadResources(); // asynchronous file loads // calls initScene() when all done ABWorld.lookat.copy ( ABWorld.scene.position ); ABWorld.scene.simulate(); // Physics simulate - runs on independent timer to AB nextStep }; this.nextStep = function() // not used { }; } // --- audio -------------------------------------------------------------- // This is a more sophisticated solution to get audio autoplay on mobile/Chrome. // Splash screen forces user interaction, which marks *multiple* audio global variables as "approved" for later play. // Then when we need to play audio, find one of these global variables that is not already playing, and play it. // global variable audio objects: var theaudio1 = new Audio( SOUND_COLLISION ); var theaudio2 = new Audio( SOUND_COLLISION ); var theaudio3 = new Audio( SOUND_COLLISION ); var theaudio4 = new Audio( SOUND_COLLISION ); var theaudio5 = new Audio( SOUND_COLLISION ); function soundCollision() // triggered by JS, not by user interaction { if ( ! AB.audioIsPlaying ( theaudio1 ) ) { theaudio1.play(); return; } if ( ! AB.audioIsPlaying ( theaudio2 ) ) { theaudio2.play(); return; } if ( ! AB.audioIsPlaying ( theaudio3 ) ) { theaudio3.play(); return; } if ( ! AB.audioIsPlaying ( theaudio4 ) ) { theaudio4.play(); return; } if ( ! AB.audioIsPlaying ( theaudio5 ) ) { theaudio5.play(); return; } // else try to make a new one, local variable, without user interaction // but this will not play on some browsers: var a = new Audio( SOUND_COLLISION ); a.play(); } // --- Splash screen -------------------------------------------------------------- AB.newSplash ( "Demo of Physics API" ); // when user clicks/touches button on splash screen, audio starts and run starts: $("#splashbutton").click ( function () { // mark multiple audio objects as good now for JS to call play() without further user interaction: theaudio1.play(); theaudio1.pause(); theaudio2.play(); theaudio2.pause(); theaudio3.play(); theaudio3.pause(); theaudio4.play(); theaudio4.pause(); theaudio5.play(); theaudio5.pause(); AB.removeSplash(); // remove splash screen splashClicked = true; });