// Billiards.js // // Created by Philip Rosedale on January 21, 2015 // Copyright 2014 High Fidelity, Inc. // // Creates a pool table in front of you. Hold and release space ball to shoot a ball. // Cue ball will return if falls off table. Delete and reset to restart. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var OVERTE_PUBLIC_CDN = networkingConstants.PUBLIC_BUCKET_CDN_URL; var tableParts = []; var balls = []; var cueBall; var LENGTH = 2.84; var WIDTH = 1.42; var HEIGHT = 0.80; var SCALE = 2.0; var BALL_SIZE = 0.05715; var BUMPER_WIDTH = 0.15; var BUMPER_HEIGHT = BALL_SIZE * 2.0; var HOLE_SIZE = BALL_SIZE; var DROP_HEIGHT = BALL_SIZE * 3.0; var GRAVITY = -9.8; var BALL_GAP = 0.001; var tableCenter; var cuePosition; var startStroke = 0; // Sounds to use var hitSound = OVERTE_PUBLIC_CDN + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav"; SoundCache.getSound(hitSound); var OVERTE_PUBLIC_CDN = networkingConstants.PUBLIC_BUCKET_CDN_URL; var screenSize = Controller.getViewportDimensions(); var reticle = Overlays.addOverlay("image", { x: screenSize.x / 2 - 16, y: screenSize.y / 2 - 16, width: 32, height: 32, imageURL: OVERTE_PUBLIC_CDN + "images/billiardsReticle.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 }); function makeTable(pos) { // Top tableParts.push(Entities.addEntity( { type: "Box", position: pos, dimensions: { x: LENGTH * SCALE, y: HEIGHT, z: WIDTH * SCALE }, color: { red: 0, green: 255, blue: 0 } })); // Long Bumpers tableParts.push(Entities.addEntity( { type: "Box", position: { x: pos.x - LENGTH / 2.0, y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( { type: "Box", position: { x: pos.x + LENGTH / 2.0, y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), z: pos.z - (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( { type: "Box", position: { x: pos.x - LENGTH / 2.0, y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( { type: "Box", position: { x: pos.x + LENGTH / 2.0, y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), z: pos.z + (WIDTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE }, dimensions: { x: (LENGTH - 3.0 * HOLE_SIZE) * SCALE / 2.0, y: BUMPER_HEIGHT, z: BUMPER_WIDTH * SCALE }, color: { red: 237, green: 201, blue: 175 } })); // End bumpers tableParts.push(Entities.addEntity( { type: "Box", position: { x: pos.x + (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), z: pos.z }, dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, color: { red: 237, green: 201, blue: 175 } })); tableParts.push(Entities.addEntity( { type: "Box", position: { x: pos.x - (LENGTH / 2.0 + BUMPER_WIDTH / 2.0) * SCALE, y: pos.y + (HEIGHT / 2.0 + BUMPER_HEIGHT / 2.0), z: pos.z }, dimensions: { z: (WIDTH - 2.0 * HOLE_SIZE) * SCALE, y: BUMPER_HEIGHT, x: BUMPER_WIDTH * SCALE }, color: { red: 237, green: 201, blue: 175 } })); } function makeBalls(pos) { // Object balls var whichBall = [ 1, 14, 15, 4, 8, 7, 12, 9, 3, 13, 10, 5, 6, 11, 2 ]; var ballNumber = 0; var ballPosition = { x: pos.x + (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; for (var row = 1; row <= 5; row++) { ballPosition.z = pos.z - ((row - 1.0) / 2.0 * (BALL_SIZE + BALL_GAP) * SCALE); for (var spot = 0; spot < row; spot++) { balls.push(Entities.addEntity( { type: "Model", modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/ball_" + whichBall[ballNumber].toString() + ".fbx", position: ballPosition, dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, rotation: Quat.fromPitchYawRollDegrees((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20), color: { red: 255, green: 255, blue: 255 }, gravity: { x: 0, y: GRAVITY, z: 0 }, velocity: {x: 0, y: -0.2, z: 0 }, ignoreCollisions: false, damping: 0.50, shapeType: "sphere", collisionSoundURL: hitSound, dynamic: true })); ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; ballNumber++; } ballPosition.x += (BALL_GAP + Math.sqrt(3.0) / 2.0 * BALL_SIZE) * SCALE; } // Cue Ball cuePosition = { x: pos.x - (LENGTH / 4.0) * SCALE, y: pos.y + HEIGHT / 2.0 + DROP_HEIGHT, z: pos.z }; cueBall = Entities.addEntity( { type: "Model", modelURL: "https://s3.amazonaws.com/hifi-public/models/props/Pool/cue_ball.fbx", position: cuePosition, dimensions: { x: BALL_SIZE * SCALE, y: BALL_SIZE * SCALE, z: BALL_SIZE * SCALE }, color: { red: 255, green: 255, blue: 255 }, gravity: { x: 0, y: GRAVITY, z: 0 }, angularVelocity: { x: 0, y: 0, z: 0 }, velocity: {x: 0, y: -0.2, z: 0 }, ignoreCollisions: false, damping: 0.50, shapeType: "sphere", dynamic: true }); } function isObjectBall(id) { for (var i; i < balls.length; i++) { if (balls[i].id == id) { return true; } } return false; } function shootCue(velocity) { var DISTANCE_FROM_CAMERA = BALL_SIZE * 5.0 * SCALE; var camera = Camera.getPosition(); var forwardVector = Quat.getFront(Camera.getOrientation()); var cuePosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); var velocity = Vec3.multiply(forwardVector, velocity); var BULLET_LIFETIME = 3.0; var BULLET_GRAVITY = 0.0; var SHOOTER_COLOR = { red: 255, green: 0, blue: 0 }; var SHOOTER_SIZE = BALL_SIZE / 1.5 * SCALE; bulletID = Entities.addEntity( { type: "Sphere", position: cuePosition, dimensions: { x: SHOOTER_SIZE, y: SHOOTER_SIZE, z: SHOOTER_SIZE }, color: SHOOTER_COLOR, velocity: velocity, lifetime: BULLET_LIFETIME, gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, damping: 0.10, density: 8000, ignoreCollisions: false, dynamic: true }); print("Shot, velocity = " + velocity); } function keyReleaseEvent(event) { if ((startStroke > 0) && event.text == "SPACE") { var endTime = new Date().getTime(); var delta = endTime - startStroke; shootCue(delta / 100.0); startStroke = 0; } } function keyPressEvent(event) { // Fire a cue ball if ((startStroke == 0) && (event.text == "SPACE")) { startStroke = new Date().getTime(); } } function cleanup() { for (var i = 0; i < tableParts.length; i++) { Entities.deleteEntity(tableParts[i]); } for (var i = 0; i < balls.length; i++) { Entities.deleteEntity(balls[i]); } Overlays.deleteOverlay(reticle); Entities.deleteEntity(cueBall); } function update(deltaTime) { // Check if cue ball has fallen off table, re-drop if so var cueProperties = Entities.getEntityProperties(cueBall); if (cueProperties.position.y < tableCenter.y) { // Replace the cueball Entities.editEntity(cueBall, { position: cuePosition } ); } } tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); makeTable(tableCenter); makeBalls(tableCenter); Script.scriptEnding.connect(cleanup); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update);