diff --git a/examples/example/games/airHockey.js b/examples/example/games/airHockey.js new file mode 100644 index 0000000000..036d6ca5ac --- /dev/null +++ b/examples/example/games/airHockey.js @@ -0,0 +1,252 @@ +// +// AirHockey.js +// +// Created by Philip Rosedale on January 26, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// AirHockey table and pucks +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var debugVisible = false; + +var FIELD_WIDTH = 1.21; +var FIELD_LENGTH = 1.92; +var FLOOR_THICKNESS = 0.20; +var EDGE_THICKESS = 0.10; +var EDGE_HEIGHT = 0.10; +var DROP_HEIGHT = 0.3; +var PUCK_SIZE = 0.15; +var PUCK_THICKNESS = 0.03; +var PADDLE_SIZE = 0.12; +var PADDLE_THICKNESS = 0.03; + +var GOAL_WIDTH = 0.35; + +var GRAVITY = -9.8; +var LIFETIME = 6000; +var PUCK_DAMPING = 0.03; +var PADDLE_DAMPING = 0.35; +var ANGULAR_DAMPING = 0.10; +var PADDLE_ANGULAR_DAMPING = 0.35; +var MODEL_SCALE = 1.52; +var MODEL_OFFSET = { x: 0, y: -0.18, z: 0 }; + +var scoreSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_score.wav"); + +var polyTable = "https://hifi-public.s3.amazonaws.com/ozan/props/airHockeyTable/airHockeyTableForPolyworld.fbx" +var normalTable = "https://hifi-public.s3.amazonaws.com/ozan/props/airHockeyTable/airHockeyTable.fbx" +var hitSound1 = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav" +var hitSound2 = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit2.wav" +var hitSideSound = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit3.wav" + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply((FIELD_WIDTH + FIELD_LENGTH) * 0.60, Quat.getFront(Camera.getOrientation()))); + +var floor = Entities.addEntity( + { type: "Box", + position: Vec3.subtract(center, { x: 0, y: 0, z: 0 }), + dimensions: { x: FIELD_WIDTH, y: FLOOR_THICKNESS, z: FIELD_LENGTH }, + color: { red: 128, green: 128, blue: 128 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + locked: true, + visible: debugVisible, + lifetime: LIFETIME }); + +var edge1 = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: FIELD_WIDTH / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), + dimensions: { x: EDGE_THICKESS, y: EDGE_HEIGHT, z: FIELD_LENGTH + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge2 = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: -FIELD_WIDTH / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), + dimensions: { x: EDGE_THICKESS, y: EDGE_HEIGHT, z: FIELD_LENGTH + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge3a = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: FIELD_WIDTH / 4.0 + (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: -FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge3b = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: -FIELD_WIDTH / 4.0 - (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: -FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge4a = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: FIELD_WIDTH / 4.0 + (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var edge4b = Entities.addEntity( + { type: "Box", + collisionSoundURL: hitSideSound, + position: Vec3.sum(center, { x: -FIELD_WIDTH / 4.0 - (GOAL_WIDTH / 4.0), y: FLOOR_THICKNESS / 2.0, z: FIELD_LENGTH / 2.0 }), + dimensions: { x: FIELD_WIDTH / 2.0 - GOAL_WIDTH / 2.0, y: EDGE_HEIGHT, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, + gravity: { x: 0, y: 0, z: 0 }, + ignoreCollisions: false, + visible: debugVisible, + locked: true, + lifetime: LIFETIME }); + +var table = Entities.addEntity( + { type: "Model", + modelURL: polyTable, + dimensions: Vec3.multiply({ x: 0.8, y: 0.45, z: 1.31 }, MODEL_SCALE), + position: Vec3.sum(center, MODEL_OFFSET), + ignoreCollisions: false, + visible: true, + locked: true, + lifetime: LIFETIME }); + +var puck; +var paddle1, paddle2; + +// Create pucks + +function makeNewProp(which) { + if (which == "puck") { + return Entities.addEntity( + { type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + collisionSoundURL: hitSound1, + position: Vec3.sum(center, { x: 0, y: DROP_HEIGHT, z: 0 }), + dimensions: { x: PUCK_SIZE, y: PUCK_THICKNESS, z: PUCK_SIZE }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: PUCK_DAMPING, + angularDamping: ANGULAR_DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true }); + } + else if (which == "paddle1") { + return Entities.addEntity( + { type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + collisionSoundURL: hitSound2, + position: Vec3.sum(center, { x: 0, y: DROP_HEIGHT, z: FIELD_LENGTH * 0.35 }), + dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: PADDLE_DAMPING, + angularDamping: PADDLE_ANGULAR_DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true }); + } + else if (which == "paddle2") { + return Entities.addEntity( + { type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + compoundShapeURL: "http://headache.hungry.com/~seth/hifi/puck.obj", + collisionSoundURL: hitSound2, + position: Vec3.sum(center, { x: 0, y: DROP_HEIGHT, z: -FIELD_LENGTH * 0.35 }), + dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, + ignoreCollisions: false, + damping: PADDLE_DAMPING, + angularDamping: PADDLE_ANGULAR_DAMPING, + lifetime: LIFETIME, + collisionsWillMove: true }); + } +} + + +puck = makeNewProp("puck"); +paddle1 = makeNewProp("paddle1"); +paddle2 = makeNewProp("paddle2"); + +function update(deltaTime) { + if (Math.random() < 0.1) { + puckProps = Entities.getEntityProperties(puck); + paddle1Props = Entities.getEntityProperties(paddle1); + paddle2Props = Entities.getEntityProperties(paddle2); + if (puckProps.position.y < (center.y - DROP_HEIGHT)) { + Audio.playSound(scoreSound, { + position: center, + volume: 1.0 + }); + Entities.deleteEntity(puck); + puck = makeNewProp("puck"); + } + + if (paddle1Props.position.y < (center.y - DROP_HEIGHT)) { + Entities.deleteEntity(paddle1); + paddle1 = makeNewProp("paddle1"); + } + if (paddle2Props.position.y < (center.y - DROP_HEIGHT)) { + Entities.deleteEntity(paddle2); + paddle2 = makeNewProp("paddle2"); + } + } +} + +function scriptEnding() { + + Entities.editEntity(edge1, { locked: false }); + Entities.editEntity(edge2, { locked: false }); + Entities.editEntity(edge3a, { locked: false }); + Entities.editEntity(edge3b, { locked: false }); + Entities.editEntity(edge4a, { locked: false }); + Entities.editEntity(edge4b, { locked: false }); + Entities.editEntity(floor, { locked: false }); + Entities.editEntity(table, { locked: false }); + + + Entities.deleteEntity(edge1); + Entities.deleteEntity(edge2); + Entities.deleteEntity(edge3a); + Entities.deleteEntity(edge3b); + Entities.deleteEntity(edge4a); + Entities.deleteEntity(edge4b); + Entities.deleteEntity(floor); + Entities.deleteEntity(puck); + Entities.deleteEntity(paddle1); + Entities.deleteEntity(paddle2); + Entities.deleteEntity(table); +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js new file mode 100644 index 0000000000..31597ba32d --- /dev/null +++ b/examples/example/games/grabHockey.js @@ -0,0 +1,278 @@ + +// grab.js +// examples +// +// Created by Eric Levin on May 1, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Grab's physically moveable entities with the mouse, by applying a spring force. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var isGrabbing = false; +var grabbedEntity = null; +var lineEntityID = null; +var prevMouse = {}; +var deltaMouse = { + z: 0 +} +var entityProps; +var moveUpDown = false; +var CLOSE_ENOUGH = 0.001; +var FULL_STRENGTH = 1.0; +var SPRING_RATE = 1.5; +var DAMPING_RATE = 0.80; +var ANGULAR_DAMPING_RATE = 0.40; +var SCREEN_TO_METERS = 0.001; +var currentPosition, currentVelocity, cameraEntityDistance, currentRotation; +var velocityTowardTarget, desiredVelocity, addedVelocity, newVelocity, dPosition, camYaw, distanceToTarget, targetPosition; +var originalGravity = {x: 0, y: 0, z: 0}; +var shouldRotate = false; +var dQ, theta, axisAngle, dT; +var angularVelocity = { + x: 0, + y: 0, + z: 0 +}; + +var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); +var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); +var VOLUME = 0.10; + +var DROP_DISTANCE = 5.0; +var DROP_COLOR = { + red: 200, + green: 200, + blue: 200 +}; +var DROP_WIDTH = 2; + + +var dropLine = Overlays.addOverlay("line3d", { + color: DROP_COLOR, + alpha: 1, + visible: false, + lineWidth: DROP_WIDTH +}); + + +function vectorIsZero(v) { + return v.x == 0 && v.y == 0 && v.z == 0; +} + +function nearLinePoint(targetPosition) { + // var handPosition = Vec3.sum(MyAvatar.position, {x:0, y:0.2, z:0}); + var handPosition = MyAvatar.getRightPalmPosition(); + var along = Vec3.subtract(targetPosition, handPosition); + along = Vec3.normalize(along); + along = Vec3.multiply(along, 0.4); + return Vec3.sum(handPosition, along); +} + + +function mousePressEvent(event) { + if (!event.isLeftButton) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking + if (intersection.intersects && intersection.properties.collisionsWillMove) { + grabbedEntity = intersection.entityID; + var props = Entities.getEntityProperties(grabbedEntity) + isGrabbing = true; + originalGravity = props.gravity; + targetPosition = props.position; + currentPosition = props.position; + currentVelocity = props.velocity; + updateDropLine(targetPosition); + + Entities.editEntity(grabbedEntity, { + gravity: {x: 0, y: 0, z: 0} + }); + + lineEntityID = Entities.addEntity({ + type: "Line", + position: nearLinePoint(targetPosition), + dimensions: Vec3.subtract(targetPosition, nearLinePoint(targetPosition)), + color: { red: 255, green: 255, blue: 255 }, + lifetime: 300 // if someone crashes while moving something, don't leave the line there forever. + }); + + Audio.playSound(grabSound, { + position: props.position, + volume: VOLUME + }); + } +} + +function updateDropLine(position) { + Overlays.editOverlay(dropLine, { + visible: true, + start: { + x: position.x, + y: position.y + DROP_DISTANCE, + z: position.z + }, + end: { + x: position.x, + y: position.y - DROP_DISTANCE, + z: position.z + } + }) +} + + +function mouseReleaseEvent() { + if (isGrabbing) { + isGrabbing = false; + + // only restore the original gravity if it's not zero. This is to avoid... + // 1. interface A grabs an entity and locally saves off its gravity + // 2. interface A sets the entity's gravity to zero + // 3. interface B grabs the entity and saves off its gravity (which is zero) + // 4. interface A releases the entity and puts the original gravity back + // 5. interface B releases the entity and puts the original gravity back (to zero) + if (!vectorIsZero(originalGravity)) { + Entities.editEntity(grabbedEntity, { + gravity: originalGravity + }); + } + + Overlays.editOverlay(dropLine, { + visible: false + }); + targetPosition = null; + + Entities.deleteEntity(lineEntityID); + + Audio.playSound(releaseSound, { + position: entityProps.position, + volume: VOLUME + }); + + } +} + +function mouseMoveEvent(event) { + if (isGrabbing) { + // see if something added/restored gravity + var props = Entities.getEntityProperties(grabbedEntity); + if (!vectorIsZero(props.gravity)) { + originalGravity = props.gravity; + } + + deltaMouse.x = event.x - prevMouse.x; + if (!moveUpDown) { + deltaMouse.z = event.y - prevMouse.y; + deltaMouse.y = 0; + } else { + deltaMouse.y = (event.y - prevMouse.y) * -1; + deltaMouse.z = 0; + } + // Update the target position by the amount the mouse moved + camYaw = Quat.safeEulerAngles(Camera.getOrientation()).y; + dPosition = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), deltaMouse); + if (!shouldRotate) { + // Adjust target position for the object by the mouse move + cameraEntityDistance = Vec3.distance(Camera.getPosition(), currentPosition); + // Scale distance we want to move by the distance from the camera to the grabbed object + // TODO: Correct SCREEN_TO_METERS to be correct for the actual FOV, resolution + targetPosition = Vec3.sum(targetPosition, Vec3.multiply(dPosition, cameraEntityDistance * SCREEN_TO_METERS)); + } else if (shouldRotate) { + var transformedDeltaMouse = { + x: deltaMouse.z, + y: deltaMouse.x, + z: deltaMouse.y + }; + transformedDeltaMouse = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), transformedDeltaMouse); + dQ = Quat.fromVec3Degrees(transformedDeltaMouse); + theta = 2 * Math.acos(dQ.w); + axisAngle = Quat.axis(dQ); + angularVelocity = Vec3.multiply((theta / dT), axisAngle); + } + + Entities.editEntity(lineEntityID, { + position: nearLinePoint(targetPosition), + dimensions: Vec3.subtract(targetPosition, nearLinePoint(targetPosition)) + }); + } + prevMouse.x = event.x; + prevMouse.y = event.y; + +} + + +function keyReleaseEvent(event) { + if (event.text === "SHIFT") { + moveUpDown = false; + } + if (event.text === "SPACE") { + shouldRotate = false; + } +} + +function keyPressEvent(event) { + if (event.text === "SHIFT") { + moveUpDown = true; + } + if (event.text === "SPACE") { + shouldRotate = true; + } +} + +function update(deltaTime) { + dT = deltaTime; + if (isGrabbing) { + + entityProps = Entities.getEntityProperties(grabbedEntity); + currentPosition = entityProps.position; + currentVelocity = entityProps.velocity; + currentRotation = entityProps.rotation; + + var dPosition = Vec3.subtract(targetPosition, currentPosition); + + distanceToTarget = Vec3.length(dPosition); + if (distanceToTarget > CLOSE_ENOUGH) { + // compute current velocity in the direction we want to move + velocityTowardTarget = Vec3.dot(currentVelocity, Vec3.normalize(dPosition)); + velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), velocityTowardTarget); + // compute the speed we would like to be going toward the target position + + desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); + // compute how much we want to add to the existing velocity + addedVelocity = Vec3.subtract(desiredVelocity, velocityTowardTarget); + // If target is too far, roll off the force as inverse square of distance + if (distanceToTarget / cameraEntityDistance > FULL_STRENGTH) { + addedVelocity = Vec3.multiply(addedVelocity, Math.pow(FULL_STRENGTH / distanceToTarget, 2.0)); + } + newVelocity = Vec3.sum(currentVelocity, addedVelocity); + // Add Damping + newVelocity = Vec3.subtract(newVelocity, Vec3.multiply(newVelocity, DAMPING_RATE)); + // Update entity + } else { + newVelocity = {x: 0, y: 0, z: 0}; + } + if (shouldRotate) { + angularVelocity = Vec3.subtract(angularVelocity, Vec3.multiply(angularVelocity, ANGULAR_DAMPING_RATE)); + } else { + angularVelocity = entityProps.angularVelocity; + } + + Entities.editEntity(grabbedEntity, { + position: currentPosition, + rotation: currentRotation, + velocity: newVelocity, + angularVelocity: angularVelocity + }); + updateDropLine(targetPosition); + } +} + +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.update.connect(update);