// Cloned by Daniel Pelech on 23 Nov 2023 from World "Collision World" by Starter user
// Please leave this clone trail here.
// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================
// Demo of Physijs based World
// From here, with many changes:
// http://chandlerprall.github.io/Physijs/examples/collisions.html
// ===================================================================================================================
// === 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.clockTick = 100;
// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 2000;
// Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 200;
// Take screenshot on this step. (All resources should have finished loading.) Default 50.
const BOXSIZE = 1 ;
const BOXPOS = BOXSIZE * 2 ; // range of box x,z start positions
const BOXHEIGHT = BOXSIZE * 10 ; // y start position of boxes
const HEAVYIMPACT = 30 ; // heavy impacts have y velocity greater than this
const GROUNDSIZE = BOXSIZE * 40;
const startRadius = BOXSIZE * 15 ;
const maxRadius = BOXSIZE * 250 ;
const TEXTURE_FILE_0 = '/uploads/starter/smiley.png' ;
const TEXTURE_FILE_1 = '/uploads/starter/ghost.3.png' ;
// https://commons.wikimedia.org/wiki/Smiley
const TEXTURE_GROUND = '/uploads/starter/rocks.jpg' ;
const SOUND_COLLISION = '/uploads/starter/wooden.mp3' ;
// credit:
// http://soundbible.com/715-Dropped-Wooden-Floor.html
const SOUND_BEEP = '/uploads/starter/beep.mp3' ;
const SKYCOLOR = 0xffffcc;
const GROUND_FRICTION = 0.8;
const GROUND_RESTITUTION = 0.3;
const BOX_FRICTION = 0.6;
const BOX_RESTITUTION = 0.3;
// define gravity along x,y,z dimensions:
var gravity = new THREE.Vector3 ( 0, -30, 0 );
// var gravity = new THREE.Vector3 ( -30, -30, 0 );
function nextboxin()
// Speed of box creation
// Create next box in some random (m to n) milliseconds time.
{
return ( AB.randomIntAtoB ( 2000, 4000 ) );
// return ( AB.randomIntAtoB ( 200, 1000 ) );
}
// ===================================================================================================================
// === 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;
var box_texture_0, box_texture_1 ;
var ground_texture ;
var boxno = 0;
var currentbox ;
function loadResources() // asynchronous file loads - call initScene() when all finished
{
var loader1 = new THREE.TextureLoader();
var loader2 = new THREE.TextureLoader();
var loader3 = new THREE.TextureLoader();
loader1.load ( TEXTURE_GROUND, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
ground_texture = thetexture;
if ( asynchFinished() ) initScene(); // if all file loads have returned
});
loader2.load ( TEXTURE_FILE_0, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
box_texture_0 = thetexture;
if ( asynchFinished() ) initScene();
});
loader3.load ( TEXTURE_FILE_1, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
box_texture_1 = thetexture;
if ( asynchFinished() ) initScene();
});
}
function asynchFinished() // all file loads returned
{
if ( ground_texture && box_texture_0 && box_texture_1 ) return true;
else return false;
}
function initScene() // all file loads have returned
{
// --- Light ------------------------------------------------------------------
var light = new THREE.DirectionalLight( 0xFFFFFF, 1.3 );
light.position.set ( BOXHEIGHT, (BOXHEIGHT * 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;
light.shadow.bias = -0.0001;
// higher quality shadows at expense of computation time
// slower PCs can struggle if this is too high
// World author could make it an option at run-time - high or low graphics option - or just shadows on/off
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
ABWorld.scene.add( light );
// --- Ground ------------------------------------------------------------------
var ground_material = Physijs.createMaterial(
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 );
var ground = new Physijs.BoxMesh(
new THREE.BoxGeometry( GROUNDSIZE, 1, GROUNDSIZE ),
ground_material,
0 ); // mass
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;
createBox();
}
function createBox()
{
// has to be started by resourcesLoaded code
// cannot be started by splashClicked code since that is outside World
// needs both to be true
if ( resourcesLoaded && splashClicked )
{
// think each box needs its own material
var box_material_0 = Physijs.createMaterial(
new THREE.MeshLambertMaterial({ map: box_texture_0 }),
BOX_FRICTION,
BOX_RESTITUTION );
var box_geometry = new THREE.BoxGeometry( BOXSIZE, BOXSIZE, BOXSIZE );
var box = new Physijs.BoxMesh ( box_geometry, box_material_0 );
box.collisions = 0;
box.castShadow = true;
box.position.set( AB.randomIntAtoB(-BOXPOS,BOXPOS), BOXHEIGHT, AB.randomIntAtoB(-BOXPOS,BOXPOS) );
box.rotation.set( Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI );
box.addEventListener( 'collision', function( other_object, relative_velocity, relative_rotation, contact_normal )
// box has collided with other_object with an impact speed of relative_velocity and a rotational force of relative_rotation and at normal contact_normal
{
// console.log ( relative_velocity );
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();
var box_material_1 = Physijs.createMaterial(
new THREE.MeshLambertMaterial({ map: box_texture_1 }),
BOX_FRICTION,
BOX_RESTITUTION );
this.material = box_material_1;
}
});
ABWorld.scene.add( box );
currentbox = box;
boxno++;
}
// --- call createBox again -----------------------------------------------------
setTimeout ( createBox, nextboxin() );
// Create next box in some random time.
// Put this in createBox, not nextStep - runs on independent timer.
}
AB.world.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 );
// multiply mouse scrolls by some factor to speed up/slow down movement:
// ABHandler.SCROLLFACTOR = 0.1;
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
};
AB.world.nextStep = function()
{
// not much to do each step
// physics simulate runs on independent timer
// create box runs on another independent timer
// for camera control, follow current falling box, whose position changes constantly:
if ( currentbox )
ABWorld.follow.copy ( currentbox.position );
if ( boxno > 0 )
{
// AB.msg ( "Box " + boxno + " position: <tt> " + AB.vector3toString ( currentbox.position ) + "</tt>" );
AB.msg ( "Box " + boxno + " height: <tt> " + Math.floor(currentbox.position.y) + "</tt>" );
}
};
// --- audio --------------------------------------------------------------
function soundCollision()
{
var audio = new Audio( SOUND_COLLISION );
audio.play();
}
function soundBeep()
{
var audio = new Audio( SOUND_BEEP );
audio.play();
}
// --- Splash screen --------------------------------------------------------------
// Splash screen is to try to get audio events to autoplay on mobile/Chrome.
// This is a simple version. It just tries one audio event (beep) after user interaction, and hopes other audio will now work.
// This simple approach works on some mobile browsers - touch splash screen allows all subsequent audios.
// But does not work on other mobile browsers - touch splash screen plays first audio, but subsequent audios are still silent.
// display splash screen
AB.newSplash ( "Demo of Physics API" );
// when user clicks/touches button on splash screen, audio starts and run starts:
AB.splashClick ( function ()
{
soundBeep(); // audio linked to user interaction
AB.removeSplash(); // remove splash screen
splashClicked = true; // will cause createBox to work (if resourcesLoaded is true)
});