//import { OrbitControls } from '/uploads/cadogav2/OrbitControls.js';
//===============================================================================================================
// VARIABLES
//===============================================================================================================
// 3D MODELS PATHS
const PIN_OBJ_PATH = '/uploads/cadogav2/pin_s.obj';
const FLOOR_OBJ_PATH = '/uploads/cadogav2/floor_s.obj';
const GUTTER_OBJ_PATH = '/uploads/cadogav2/gutter_s.obj';
const BACK_GUTTER_OBJ_PATH = '/uploads/cadogav2/back_gutter_s.obj';
const BANNER_OBJ_PATH = '/uploads/cadogav2/banner_s.obj';
// TEXTURES PATHS
const PIN_TEXTURE_PATH = '/uploads/cadogav2/pin_tex.png';
const ALLEY_TEXTURE_PATH = '/uploads/cadogav2/alley_tex.jpg';
// LOAD TEXTURES
let textureLoader = new THREE.TextureLoader();
const pinTexture = textureLoader.load(PIN_TEXTURE_PATH);
pinTexture.minFilter = THREE.NearestFilter;
pinTexture.magFilter = THREE.NearestFilter;
const alleyTexture = textureLoader.load(ALLEY_TEXTURE_PATH);
alleyTexture.minFilter = THREE.NearestFilter;
alleyTexture.magFilter = THREE.NearestFilter;
// MATERIALS
let pinMat = new THREE.MeshToonMaterial({map: pinTexture});
let floorMat = new THREE.MeshToonMaterial({map: alleyTexture, side: THREE.DoubleSide});
let gutterMat = new THREE.MeshToonMaterial({color: 0x444444});
let backGutterMat = new THREE.MeshToonMaterial({color: 0x116482});
let ballMat = new THREE.MeshToonMaterial({color: 0x061429}); //010e21
let bannerMat = new THREE.MeshToonMaterial({map: alleyTexture});
// MEMBERS
const SKYCOLOR = 0x31408f;
let pinMesh, floorMesh, gutterMesh, bannerMesh;
let pins = [];
// all objects used in physics simulation
let dynamicObjects = [];
let physicsWorld;
let tmpTransform = undefined;
let mouseCoords = new THREE.Vector2();
let raycaster = new THREE.Raycaster();
let rayPos = new THREE.Vector3();
let ammoLoaded = false;
let gravity = -50;
let ballMass = 500;
let pinMass = 20;
let clock;
AB.clockTick = 25;
AB.maxSteps = Infinity;
// GAME LOGIC VARIABLES
let turn = false; // false = player 1, true = player 2
let shotsTaken = 0;
let ballMoving = false;
let p1Score = 0, p2Score = 0;
let strikeAnimPlaying = false;
let gameOver = false;
//===============================================================================================================
// LOAD DEPENDENCY SCRIPTS
//===============================================================================================================
// Load CSS
AB.loadCSS("/uploads/cadogav2/bowling_styles2.css");
$.getScript ("/uploads/cadogav2/ammo.wasm.js", function(){
console.log('script loaded')
Ammo().then(startAmmo);
});
//===============================================================================================================
// PHYSICS METHODS
//===============================================================================================================
function startAmmo(){
console.log('start ammo')
initPhysicsWorld();
addEventListeners();
createPhysicalPlane({w:10, h:0.5, d:80},{x:0, y:-0.25, z:38}, 0);
createPhyicalPins();
//let shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( sx, sy, sz ) );
//shape.setMargin( 0.05 );
}
function initPhysicsWorld(){
let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
let overlappingPairCache = new Ammo.btDbvtBroadphase();
let solver = new Ammo.btSequentialImpulseConstraintSolver();
physicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
physicsWorld.setGravity(new Ammo.btVector3(0, -50, 0));
tmpTransform = new Ammo.btTransform();
ammoLoaded = true;
}
function createPhysicalPlane(dimension, position, mass){
let planeGeometry = new THREE.BoxGeometry(dimension.w, dimension.h, dimension.d);
//let planeMaterial = new THREE.MeshPhongMaterial({ color: 0x111111 });
let plane = new THREE.Mesh(planeGeometry);
plane.position.set(position.x, position.y, position.z);
// Default Motion State - starting position and rotation
let transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3( position.x, position.y, position.z));
transform.setRotation(new Ammo.btQuaternion(0, 0, 0, 1));
let motionState = new Ammo.btDefaultMotionState(transform);
// Collision Geometry
let colGeometry = new Ammo.btBoxShape(new Ammo.btVector3(dimension.w * 0.5, dimension.h * 0.5, dimension.d * 0.5));
colGeometry.setMargin(0.05);
// Set Inertia
let localInertia = new Ammo.btVector3(0, 0, 0);
colGeometry.calculateLocalInertia(mass, localInertia);
// Create RigidBody
let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colGeometry, localInertia);
let body = new Ammo.btRigidBody(rbInfo);
//plane.visible = false;
physicsWorld.addRigidBody(body);
// make plane hold a reference to it's rigidbody
plane.userData.physicsBody = body;
dynamicObjects.push(plane);
//ABWorld.scene.add(plane);
}
function createPhyicalBall(radius, position, mass){
let ball = new THREE.Mesh(new THREE.SphereBufferGeometry(radius, 32, 16), ballMat);
ball.position.set(position.x, position.y, position.z);
ball.castShadow = true;
// add to the scene
ABWorld.scene.add(ball);
// Default Motion State - starting position and rotation
let transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
transform.setRotation(new Ammo.btQuaternion(0, 0, 0, 1));
let motionState = new Ammo.btDefaultMotionState(transform);
// Collision Geometry
let colGeometry = new Ammo.btSphereShape(radius);
colGeometry.setMargin(0.05);
// Set Inertia
let localInertia = new Ammo.btVector3(0, 0, 0);
colGeometry.calculateLocalInertia(mass, localInertia);
// Create RigidBody
let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, colGeometry, localInertia);
let body = new Ammo.btRigidBody(rbInfo);
body.setFriction(0.5);
physicsWorld.addRigidBody(body);
// place the ball with inital velocity
rayPos.copy(raycaster.ray.direction);
// the velocity
rayPos.multiplyScalar(100);
// fire ball
body.setLinearVelocity(new Ammo.btVector3(rayPos.x, rayPos.y, rayPos.z));
ball.userData.physicsBody = body;
dynamicObjects.push(ball);
}
function createPhyicalPins(){
if (!pins.length){
console.log("Couldn't create pins rigidbody");
return;
}
pins.forEach(pin => {
console.log("pin")
//console.log(pin)
createConvexHullNew(pin, pinMass);
})
}
function createConvexHullPhysicsShape(vertices) {
let shape = new Ammo.btConvexHullShape();
let tmpVec = new Ammo.btVector3(0, 0, 0)
for (var i = 0, il = vertices.length; i < il; i += 3) {
tmpVec.setValue(vertices[i], vertices[i + 1], vertices[i + 2]);
var lastOne = i >= il - 3;
shape.addPoint(tmpVec, lastOne);
}
return shape;
}
function createConvexHullNew(pin, mass){
let geometry = pin.children[0].geometry;
// console.log("geo")
// console.log(geometry)
let vertices = geometry.attributes.position.array;
//createConvexHullOld(pin)
let shape = createConvexHullPhysicsShape(vertices);
let transform = new Ammo.btTransform();
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pin.position.x, pin.position.y, pin.position.z));
let motionState = new Ammo.btDefaultMotionState(transform);
let localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);
let rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
let body = new Ammo.btRigidBody(rbInfo);
body.setRestitution(1);
body.setActivationState(4)
physicsWorld.addRigidBody(body);
pin.userData.physicsBody = body;
dynamicObjects.push(pin);
}
function resetPins(){
let spacing = 2;
let spread = spacing / 2;
let offset = spacing + spread;
let pinIndex = 0;
for (let row = 0; row <= 4; row++){
for (let i = 0; i < 4 - row; i++){
let pin = pins[pinIndex].userData.physicsBody;
pin.getWorldTransform().setIdentity();
pin.getWorldTransform().setRotation(new Ammo.btQuaternion(0, 0, 0, 1));
pin.setLinearVelocity(new Ammo.btVector3(0, 0, 0));
pin.setAngularVelocity(new Ammo.btVector3(0, 0, 0));
let origin = pin.getWorldTransform().getOrigin();
let x = (i * spacing) + (row*spread) - offset;
let z = row * spacing;
origin.setX(x);
origin.setY(0);
origin.setZ(z);
pin.activate();
pinIndex++;
}
}
}
//===============================================================================================================
// EVENTS
//===============================================================================================================
function addEventListeners(){
document.getElementById('ab-runcanvas').addEventListener('mousedown', onMouseDown);
}
function onMouseDown(event){
if (!ammoLoaded || gameOver) return;
console.log('clicked',ballMoving,!ballMoving)
// should also block player whos turn it's not
if (!ballMoving){
ballMoving = true;
shotsTaken++;
console.log(`player ${turn + 1} taken shot ${shotsTaken}`)
throwBall();
// ballMoving will be set to false after 6 sec in
setTimeout(function(){
ballMoving = false;
calculateScore();
if (shotsTaken >= 2){
// reset
shotsTaken = 0;
// switch turns for players
turn = !turn;
previousKnockedOver = 0;
$("#p1_details").toggleClass("active");
$("#p2_details").toggleClass("active");
// reset pins
resetPins();
// check who won, if any
checkForWinner();
}
}, 6000);
}
}
function checkForWinner(){
if (!gameOver && (p1Score >= 50 || p2Score >= 50)){
let w = p1Score >= 50 ? 1 : 2;
gameOver = true;
$('.anim_strike').text(`WINNER Player ${w}!`);
$('#player_info').text(`Congratulations!`);
$("#strike_msg").toggleClass("hide");
}
}
function throwBall(){
// normalise mouse coords between -1 to 1 with origin in center of screem
mouseCoords.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(mouseCoords, ABWorld.camera);
// where the ray intersects the camera screen from the mouse position
rayPos.copy(raycaster.ray.direction);
rayPos.add(raycaster.ray.origin)
console.log(rayPos)
// ball properties
let pos = {x: rayPos.x, y: rayPos.y, z: rayPos.z};
// create ball
// instead make single instance of ball and reset it position
console.log('create ball')
createPhyicalBall(1, pos, ballMass);
}
ABHandler.MouseDown = onMouseDown;
let previousKnockedOver = 0;
function calculateScore(){
let knockedOver = 0;
pins.forEach(pin => {
let posY = pin.position.y;
console.log("posY",posY)
if (posY <= -0.2 || posY >= 2) {
knockedOver++;
}
});
let score = 0;
// strike condition
if (shotsTaken === 1 && knockedOver === 10){
score = 14;
shotsTaken++;
addScore(score);
checkForWinner();
// show strike massage
if (!strikeAnimPlaying && !gameOver){
$('.anim_strike').text(`STRIKE!`);
$('#player_info').text(`for Player ${turn + 1}`);
$("#strike_msg").toggleClass("hide");
strikeAnimPlaying = true;
setTimeout(function(){
$("#strike_msg").toggleClass("hide");
strikeAnimPlaying = false;
}, 1200);
}
return;
} else if (shotsTaken === 1) {
// first shot
previousKnockedOver = knockedOver;
score = knockedOver;
} else {
// second shot
score = knockedOver - previousKnockedOver;
}
addScore(score);
}
function addScore(score){
if (!turn){
// player 1
p1Score += score;
} else {
// player 2
p2Score += score;
}
$('#p1_score').text(p1Score+"");
$('#p2_score').text(p2Score+"");
}
//===============================================================================================================
// LOAD RESOURCE METHODS
//===============================================================================================================
function asynchFinished()
{
// add more models and assets here!
if (pinMesh && floorMesh && gutterMesh && bannerMesh) {
return true;
} else {
return false;
}
}
function assignMaterial(obj, material, castShadow=true, receiveShadow=true){
obj.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.material = material;
child.castShadow = castShadow;
child.receiveShadow = receiveShadow;
}
});
}
function loadResources(){
// Load Pin Model
let objLoader = new THREE.OBJLoader(new THREE.LoadingManager());
objLoader.load(PIN_OBJ_PATH, function (obj)
{
assignMaterial(obj, pinMat, true, false);
pinMesh = obj;
let boundingBox = new THREE.Box3().setFromObject(pinMesh)
console.log( "measure" );
console.log( boundingBox.getSize() );
console.log( "obj" );
console.log( pinMesh );
//let offset = 12;
// spacing between rows
let spacing = 2;
// spread bewteen each pin for each row
let spread = spacing / 2;
let offset = spacing + spread;
for (let row = 0; row <= 4; row++){
for (let i = 0; i < 4 - row; i++){
let clone = pinMesh.clone();
let x = (i * spacing) + (row*spread) - offset;
let z = row * spacing;
clone.position.set(x, 0, z);
pins.push(clone);
ABWorld.scene.add(clone);
}
}
if (asynchFinished()) initScene();
});
// Load Floor Model
objLoader.load(FLOOR_OBJ_PATH, function (obj)
{
assignMaterial(obj, floorMat, true, true);
floorMesh = obj;
ABWorld.scene.add(floorMesh);
let clone = floorMesh.clone();
clone.position.x = -40;
clone.position.z = 83;
clone.rotation.y = Math.PI / 2;
ABWorld.scene.add(clone);
if (asynchFinished()) initScene();
});
// Load Gutter Model
objLoader.load(GUTTER_OBJ_PATH, function (obj)
{
assignMaterial(obj, gutterMat, true, true);
obj.position.x = 6;
obj.position.z = 68;
gutterMesh = obj;
ABWorld.scene.add(gutterMesh);
let clone = gutterMesh.clone();
clone.position.x = -6;
ABWorld.scene.add(clone);
if (asynchFinished()) initScene();
});
// Load Back Gutter Model
objLoader.load(BACK_GUTTER_OBJ_PATH, function (obj)
{
assignMaterial(obj, backGutterMat, false, true);
backGutterMesh = obj;
ABWorld.scene.add(backGutterMesh);
if (asynchFinished()) initScene();
});
// Load Banner Model
objLoader.load(BANNER_OBJ_PATH, function (obj)
{
assignMaterial(obj, bannerMat, true, true);
bannerMesh = obj;
ABWorld.scene.add(bannerMesh);
if (asynchFinished()) initScene();
});
}
//===============================================================================================================
// HELPER METHODS
//===============================================================================================================
function showAxesHelper(){
const axesHelper = new THREE.AxesHelper( 15 );
const color = new THREE.Color();
const array = axesHelper.geometry.attributes.color.array;
// x
color.set( new THREE.Color('red') );
color.toArray( array, 0 );
color.toArray( array, 3 );
// y
color.set( new THREE.Color('green') );
color.toArray( array, 6 );
color.toArray( array, 9 );
// z
color.set( new THREE.Color('blue') );
color.toArray( array, 12 );
color.toArray( array, 15 );
axesHelper.geometry.attributes.color.needsUpdate = true;
ABWorld.scene.add( axesHelper );
}
//===============================================================================================================
// INTIALISE METHODS
//===============================================================================================================
function initScene() {
console.log('init')
let ambient = new THREE.AmbientLight(0xddd1ff);
ambient.intensity = 0.5;
ABWorld.scene.add( ambient );
// 0xffeed4
// 0xfaddb1
let light = new THREE.DirectionalLight( 0xfaddb1, 0.5);
light.position.set(4,20,8);
light.castShadow = true;
light.shadowCameraLeft = -100;
light.shadowCameraRight = 100;
light.shadowCameraTop = 100;
light.shadowCameraBottom = -100;
light.shadow.mapSize.width = 2048;//3072;//2048;
light.shadow.mapSize.height = 2048;//3072;//2048;
light.shadow.camera.near = 1;
light.shadow.camera.far = 30;
light.shadow.bias = -0.005;
light.shadow.radius = 10;
ABWorld.scene.add(light);
//const clh = new THREE.CameraHelper(light.shadow.camera);
//ABWorld.scene.add(clh);
//const dlh = new THREE.DirectionalLightHelper(light, 2);
//ABWorld.scene.add(dlh);
//showAxesHelper();
console.log('finished init')
}
//===============================================================================================================
// RUN GAME METHODS
//===============================================================================================================
AB.world.newRun = function()
{
document.write(`
<div id="sidebar">
<div class="active details" id="p1_details">
<h1>Player 1</h1>
<div id="score_display">
<p class="bold">Score</p>
<p id="p1_score">0</p>
</div>
</div>
<div class="details" id="p2_details">
<h1>Player 2</h1>
<div id="score_display">
<p class="bold">Score</p>
<p id="p2_score">0</p>
</div>
</div>
</div>`);
document.write(`
<div id="strike_msg" class="hide">
<p class="anim_strike">STRIKE!</p>
<p id="player_info">for Player</p>
</div>`);
console.log('new run')
ABWorld.renderer = new THREE.WebGLRenderer ( { antialias: true } );
ABWorld.renderer.shadowMap.enabled = true;
ABWorld.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
ABWorld.init3d(1250, 3000, SKYCOLOR);
ABWorld.camera.position.x = 0;
ABWorld.camera.position.y = 5;
ABWorld.camera.position.z = 85;
loadResources();
clock = new THREE.Clock();
};
AB.world.nextStep = function()
{
// Code for Three.js re-drawing of objects.
// console.log('next')
if (ammoLoaded){
let delta = clock.getDelta();
updatePhysics(delta);
}
};
function updatePhysics(deltaTime){
// console.log('deltaTime')
// console.log(deltaTime)
physicsWorld.stepSimulation(deltaTime, 10);
for (let i = 0; i < dynamicObjects.length; i++){
let threejsObj = dynamicObjects[i];
let physicsObj = threejsObj.userData.physicsBody;
let motionState = physicsObj.getMotionState();
if (motionState){
motionState.getWorldTransform(tmpTransform);
let newPos = tmpTransform.getOrigin();
let newRot = tmpTransform.getRotation();
threejsObj.position.set(newPos.x(), newPos.y(), newPos.z());
threejsObj.quaternion.set(newRot.x(), newRot.y(), newRot.z(), newRot.w());
}
}
}
AB.world.endRun = function()
{
};