// // NewFleshWatch.js // // Created by David Back, Sam Gondelman, and Parker Richard on 10/26/18. // Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // (function() { // BEGIN LOCAL_SCOPE const ENTITY_NAME = "hackathon2018.newflesh"; const LEFT_FOREARM_JOINT = "LeftForeArm"; const LEFT_HAND_JOINT = "LeftHand"; const RIGHT_HAND_JOINT = "RightHand"; const WATCH_Z_OFFSET = -0.05; const WATCH_FOREARM_HAND_DISTANCE = 0.8; // Images by Parker Richard const RESET_URL = Script.resolvePath("textures/reset.png"); const SKULL_URL = Script.resolvePath("textures/skull-effect.png"); const GHOST_URL = Script.resolvePath("textures/ghost-effect.png"); const PARTICLE_URL = Script.resolvePath("textures/particle-effect.png"); const DARKNESS_URL = Script.resolvePath("textures/darkness-effect.png"); const PARTICLE_TEXTURE = Script.resolvePath("textures/glow-particle.png"); // https://poly.google.com/view/dqkji8vm0-s const WATCH_MODEL = Script.resolvePath("models/watchModel.fbx"); // https://www.cgbookcase.com/textures/rock-12 const MATERIAL_JSON = Script.resolvePath("materials/HackathonMaterial.json"); // https://poly.google.com/view/3SRO1k1Brxe const SKULL_MODEL = Script.resolvePath("models/skull.obj"); // Cleanup in case of crash before function cleanupFromBefore() { const ENTITY_SEARCH_DISTANCE = 1000; var toDelete = Entities.findEntitiesByName(ENTITY_NAME, MyAvatar.position, ENTITY_SEARCH_DISTANCE); for (var i = 0; i < toDelete.length; i++) { Entities.deleteEntity(toDelete[i]); } } cleanupFromBefore(); var entities = []; var appEntities = []; var overlays = []; var end = { type: "sphere", dimensions: { x: 0.05, y: 0.05, z: 0.05 }, solid: true, color: { red: 255, green: 0, blue: 0 }, alpha: 0.9, ignorePickIntersection: true, visible: true }; var renderStates = [ {name: "on", end: end} ]; var pointer = Pointers.createPointer(PickType.Ray, { joint: "Mouse", filter: Picks.PICK_OVERLAYS, renderStates: renderStates, triggers: [ {action: Controller.Hardware.Keyboard.LeftMouseButton, button: "Focus"}, {action: Controller.Hardware.Keyboard.LeftMouseButton, button: "Primary"} ], hover: true, enabled: true }); Pointers.setRenderState(pointer, "on"); var watchOpen = false; var debugWatchOpen = false; var currentSelectionIndex = -1; var t = 0; const NUM_BUTTONS = 5; const BUTTON_WIDTH = 0.1; const BUTTON_HEIGHT = 0.15; var buttons = [ { url: RESET_URL }, { url: SKULL_URL, open: function() { appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "Model", modelURL: SKULL_MODEL, parentID: MyAvatar.SELF_ID, dimensions: { x: 0.6, y: 0.6, z: 0.6 }, parentJointIndex: MyAvatar.getJointIndex("Head"), localPosition: { x: 0, y: 0.5 * Vec3.distance(MyAvatar.getJointPosition("HeadTop_End"), MyAvatar.getJointPosition("Head")), z: 0 }, localRotation: Quat.fromPitchYawRollDegrees(0, -20, 0) }, true)); appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("HeadTop_End"), textures: PARTICLE_TEXTURE, maxParticles: 100, emitRate: 100, lifespan: 1.0, emitSpeed: 0.3, speedSpread: 0, particleRadius: 0.03, radiusStart: 0, radiusFinish: 0.05, color: { red: 255, blue: 0, green: 0}, colorSpread: { red: 100, blue: 100, green: 100}, colorStart: { red: 200, blue: 200, green: 200}, colorFinish: { red: 255, blue: 0, green: 255}, emitAcceleration: {x: 0, y: 0.5, z: 0}, alpha: 1, alphaStart: 0, alphaFinish: 0, polarFinish: 3.141592653589793, emitterShouldTrail: false, isEmitting: true }, true)); } }, { url: GHOST_URL, open: function() { t = 0; var model = Graphics.getModel(MyAvatar.SELF_ID); if (model) { var partID = 0; for (var i = 0; i < model.meshes.length; i++) { var mesh = model.meshes[i]; for (var j = 0; j < mesh.parts.length; j++) { appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "Material", position: MyAvatar.position, materialURL: MATERIAL_JSON + "?ghost", priority: 1, parentID: MyAvatar.SELF_ID, parentMaterialName: partID }, true)); partID++; } } } }, update: function(dt) { for (var i = 0; i < appEntities.length; i++) { Entities.editEntity(appEntities[i], { materialMappingPos: { x: Math.cos(t * 0.03), y: Math.sin(t * 0.02) }, materialMappingScale: {x: 0.1 * Math.sin(t * 0.12) + 0.3, y: 0.1 * Math.sin(t * 0.09) + 0.3 } }); t = t + dt; } } }, { url: PARTICLE_URL, open: function() { // Hide my avatar var model = Graphics.getModel(MyAvatar.SELF_ID); if (model) { var partID = 0; for (var i = 0; i < model.meshes.length; i++) { var mesh = model.meshes[i]; for (var j = 0; j < mesh.parts.length; j++) { appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "Material", position: MyAvatar.position, materialURL: MATERIAL_JSON + "?invisible", priority: 1, parentID: MyAvatar.SELF_ID, parentMaterialName: partID }, true)); partID++; } } } const HALF_PARTICLE_EFFECT_COUNT = 15; var jointNames = MyAvatar.getJointNames(); for (var i = 0; i < HALF_PARTICLE_EFFECT_COUNT; i++) { if (jointNames.length == 0) { break; } var index = Math.floor(Math.random() * jointNames.length); var jointIndex = MyAvatar.getJointIndex(jointNames[index]); jointNames.splice(index, 1); appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: MyAvatar.SELF_ID, parentJointIndex: jointIndex, textures: PARTICLE_TEXTURE, maxParticles: 25, emitRate: 10, lifespan: 2.5, emitSpeed: 0.0, speedSpread: 0.0, particleRadius: 0.025, radiusStart: 0.0, radiusFinish: 0.0, alpha: 0.5, alphaStart: 0.0, alphaFinish: 0.0, color: { red: 255, green: 255, blue: 255 }, colorSpread: { red: 100, green: 100, blue: 100 }, emitAcceleration: { x: 0, y: 0, z: 0 }, emitDimensions: { x: 0.1, y: 0.1, z: 0.5 }, polarFinish: Math.PI / 2.0, emitterShouldTrail: true }, true)); index = Math.floor(Math.random() * jointNames.length); jointIndex = MyAvatar.getJointIndex(jointNames[index]); jointNames.splice(index, 1); appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: MyAvatar.SELF_ID, parentJointIndex: jointIndex, textures: PARTICLE_TEXTURE, maxParticles: 25, emitRate: 10, lifespan: 2.5, emitSpeed: 0.0, speedSpread: 0.0, particleRadius: 0.025, radiusStart: 0.0, radiusFinish: 0.0, alpha: 0.5, alphaStart: 0.0, alphaFinish: 0.0, color: { red: 255, green: 255, blue: 255 }, colorSpread: { red: 100, green: 100, blue: 100 }, emitAcceleration: { x: 0, y: 0, z: 0 }, emitDimensions: { x: 0.1, y: 0.1, z: 0.5 }, polarFinish: Math.PI / 2.0, emitterShouldTrail: false }, true)); } appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("LeftEye"), textures: PARTICLE_TEXTURE, maxParticles: 100, emitRate: 0, lifespan: 1.0, emitSpeed: 0.6, speedSpread: 0, emitDimensions: { x: 0.5, y: 0.5, z: 0.01 }, emitOrientation: Quat.fromPitchYawRollDegrees(10, 0, 90), particleRadius: 0.03, radiusStart: 0, radiusFinish: 0.05, color: { red: 255, blue: 0, green: 0}, colorSpread: { red: 100, blue: 100, green: 100}, colorStart: { red: 200, blue: 200, green: 200}, colorFinish: { red: 255, blue: 0, green: 255}, emitAcceleration: {x: 0, y: 0, z: 0}, alpha: 1, alphaStart: 0, alphaFinish: 0, polarFinish: 0.0872664600610733, emitterShouldTrail: false, isEmitting: false }, true)); appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("RightEye"), textures: PARTICLE_TEXTURE, maxParticles: 100, emitRate: 0, lifespan: 1.0, emitSpeed: 0.6, speedSpread: 0, emitDimensions: { x: 0.5, y: 0.5, z: 0.01 }, emitOrientation: Quat.fromPitchYawRollDegrees(-10, 0, 90), particleRadius: 0.03, radiusStart: 0, radiusFinish: 0.05, color: { red: 255, blue: 0, green: 0}, colorSpread: { red: 100, blue: 100, green: 100}, colorStart: { red: 200, blue: 200, green: 200}, colorFinish: { red: 255, blue: 0, green: 255}, emitAcceleration: {x: 0, y: 0, z: 0}, alpha: 1, alphaStart: 0, alphaFinish: 0, polarFinish: 0.0872664600610733, emitterShouldTrail: false, isEmitting: false }, true)); }, update: function() { const PARTICLE_AUDIO_THRESHOLD = 0.01; const MAX_PARTICLE_EMIT_RATE = 70; Entities.editEntity(appEntities[appEntities.length - 2], { isEmitting: Audio.inputLevel > PARTICLE_AUDIO_THRESHOLD, emitRate: MAX_PARTICLE_EMIT_RATE * Audio.inputLevel }); Entities.editEntity(appEntities[appEntities.length - 1], { isEmitting: Audio.inputLevel > PARTICLE_AUDIO_THRESHOLD, emitRate: MAX_PARTICLE_EMIT_RATE * Audio.inputLevel }); } }, { url: DARKNESS_URL, open: function() { t = 0; appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "Zone", parentID: MyAvatar.SELF_ID, dimensions: { x: 50.0, y: 50.0, z: 50.0 }, hazeMode: "enabled", haze: { hazeColor: { red: 0, green: 0, blue: 0 }, hazeRange: 1.0 } }, true)); appEntities.push(Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: MyAvatar.SELF_ID, textures: PARTICLE_TEXTURE, maxParticles: 100, emitRate: 10, lifespan: 10, emitSpeed: 0.0, speedSpread: 0.0, particleRadius: 5, radiusSpread: 2, alpha: 1.0, alphaStart: 0.0, alphaFinish: 0.0, color: { red: 255, green: 0, blue: 0 }, colorSpread: { red: 100, green: 0, blue: 0 }, spinSpread: 2.0 * Math.PI, emitAcceleration: { x: 0, y: 0, z: 0 }, accelerationSpread: { x: 0.5, y: 0.5, z: 0.5 }, emitDimensions: { x: 50.0, y: 25.0, z: 50.0 }, polarFinish: Math.PI, emitterShouldTrail: true }, true)); }, update: function(dt) { Entities.editEntity(appEntities[0], { haze: { hazeRange: 1.0 + 3.0 * Math.sin(t * 0.5) * Math.sin(t * 0.5) } }); t = t + dt; } } ]; function openApp(i) { if (buttons[i].open !== undefined) { buttons[i].open(); } } function updateApp(i, dt) { if (buttons[i].update !== undefined) { buttons[i].update(dt); } } function closeApp(i) { if (buttons[i].close !== undefined) { buttons[i].close(); } for (var i = 0; i < appEntities.length; i++) { Entities.deleteEntity(appEntities[i]); } appEntities = []; } var watchEntity = Entities.addEntity({ name: ENTITY_NAME, type: "Model", modelURL: WATCH_MODEL, dimensions: { x: 0.1, y: 0.1, z: 0.02 }, parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex(LEFT_FOREARM_JOINT), localPosition: { x: 0, y: WATCH_FOREARM_HAND_DISTANCE * Vec3.distance(MyAvatar.getJointPosition(LEFT_FOREARM_JOINT), MyAvatar.getJointPosition(LEFT_HAND_JOINT)), z: WATCH_Z_OFFSET } }, true); entities.push(watchEntity); var projectionEffect = Entities.addEntity({ name: ENTITY_NAME, type: "ParticleEffect", parentID: watchEntity, textures: PARTICLE_TEXTURE, maxParticles: 10, lifespan: 1.0, emitRate: 10, emitSpeed: 0.1, speedSpread: 0.1, alpha: 0.5, alphaStart: 0, alphaFinish: 0, particleRadius: 0.05, radiusFinish: 0.1, radiusStart: 0, color: { red: 18, green: 164, blue: 222 }, colorSpread: { red: 50, green: 50, blue: 100 }, emitAcceleration: { x: 0, y: -0.8, z: 0 }, emitOrientation: Quat.fromPitchYawRollDegrees(180, 0, 0), isEmitting: false }, true); entities.push(projectionEffect); var scroll = 0.0; var scrollRate = 0; var slowScrolling = true; function getButtonInfo(i) { var index = ((buttons.length - 1) + i) % buttons.length; var posOffset = BUTTON_WIDTH * (i + scroll); if (posOffset < 0.0) { posOffset = posOffset + NUM_BUTTONS * Math.abs(Math.floor(posOffset / BUTTON_WIDTH)); } posOffset = posOffset % (BUTTON_WIDTH * NUM_BUTTONS); var HALF_NAX_OFFSET = NUM_BUTTONS / 2 * BUTTON_WIDTH; var alpha = Math.abs(posOffset - HALF_NAX_OFFSET) / HALF_NAX_OFFSET; alpha = alpha * 1.2; alpha = Math.min(1.0, Math.max(0.0, alpha)); return { index: index, alpha: 1.0 - alpha, pos: { x: -NUM_BUTTONS * BUTTON_WIDTH / 2 + posOffset, y: 0, z: 0 } } } var background = Overlays.addOverlay("cube", { dimensions: { x: (NUM_BUTTONS - 1) * BUTTON_WIDTH, y: BUTTON_HEIGHT, z: 0.01 }, alpha: 0.0, visible: true, solid: true, localPosition: { x: 0, y: 0., z: -0.15 }, localOrientation: Quat.fromPitchYawRollDegrees(-100, 0, 90), parentID: watchEntity, ignorePickIntersection: true }); overlays.push(background); for (var i = 0; i < NUM_BUTTONS; i++) { var buttonInfo = getButtonInfo(i); overlays.push(Overlays.addOverlay("image3d", { url: buttons[buttonInfo.index].url, dimensions: { x: BUTTON_WIDTH, y: BUTTON_HEIGHT, z: 0.01 }, alpha: 0.0, visible: true, localPosition: buttonInfo.pos, localOrientation: Quat.fromPitchYawRollDegrees(0, 0, 0), parentID: background, ignorePickIntersection: true })); } function openWatch() { Overlays.editOverlay(background, { ignorePickIntersection: false }); Entities.editEntity(projectionEffect, { isEmitting: true }); watchOpen = true; } function closeWatch() { Overlays.editOverlay(background, { ignorePickIntersection: true }); for (var i = 0; i < NUM_BUTTONS; i++) { Overlays.editOverlay(overlays[i + 1], { alpha: 0.0 }); } Entities.editEntity(projectionEffect, { isEmitting: false }); watchOpen = false; } function checkActivation() { var watchPosition = Entities.getEntityProperties(watchEntity, ['position']).position; var rightHandPosition = MyAvatar.getJointPosition(RIGHT_HAND_JOINT); var distance = Vec3.distance(watchPosition, rightHandPosition); const ACTIVATION_DISTANCE = 0.25; const DEACTIVATION_DISTANCE = 0.3; if (watchOpen && distance > DEACTIVATION_DISTANCE && !debugWatchOpen) { closeWatch(); } else if (!watchOpen && distance < ACTIVATION_DISTANCE) { openWatch(); } } function update(dt) { checkActivation(); if (currentSelectionIndex != -1) { updateApp(currentSelectionIndex, dt); } scroll = scroll + scrollRate * dt; if (watchOpen) { for (var i = 0; i < NUM_BUTTONS; i++) { var buttonInfo = getButtonInfo(i); Overlays.editOverlay(overlays[i + 1], { //url: buttons[buttonInfo.index].url, alpha: buttonInfo.alpha, localPosition: buttonInfo.pos }); } } if (slowScrolling) { scrollRate = 0.9 * scrollRate; } } Script.update.connect(update); function keyPressEvent(event) { if (event.text === "8") { scrollRate = scrollRate - 1.0; slowScrolling = false; } else if (event.text === "9") { scrollRate = scrollRate + 1.0; slowScrolling = false; } else if (event.text === "0") { scrollRate = 0; slowScrolling = false; } else if (event.text === "u") { scroll = scroll - 1; slowScrolling = false; } else if (event.text === "i") { scroll = scroll + 1; slowScrolling = false; } else if (event.text === "o") { if (watchOpen) { debugWatchOpen = false; closeWatch(); } else { debugWatchOpen = true; openWatch(); } } } Controller.keyPressEvent.connect(keyPressEvent); var startPress; var prevPos2D = undefined; var avgScroll = 0; function mousePressOnOverlay(id, event) { if ((Pointers.isRightHand(event.id) || event.id === pointer) && id === background && event.isPrimaryButton) { startPress = new Date(); prevPos2D = event.pos2D; scrollRate = 0.0; slowScrolling = true; avgScroll = 0; } } Overlays.mousePressOnOverlay.connect(mousePressOnOverlay); function mouseReleaseOnOverlay(id, event) { if ((Pointers.isRightHand(event.id) || event.id === pointer) && id === background && event.isPrimaryButton) { var endPress = new Date(); if (endPress - startPress < 200 && avgScroll < 0.01) { var index = prevPos2D.x / BUTTON_WIDTH - scroll; if (index < 0.0) { index = index + NUM_BUTTONS * Math.abs(Math.floor(index / NUM_BUTTONS)); } index = Math.floor(index % NUM_BUTTONS); if (currentSelectionIndex != -1) { closeApp(currentSelectionIndex); } currentSelectionIndex = index; openApp(currentSelectionIndex); } scrollRate = 1000.0 * avgScroll; prevPos2D = undefined; avgScroll = 0; } } Overlays.mouseReleaseOnOverlay.connect(mouseReleaseOnOverlay); function mouseMoveOnOverlay(id, event) { if ((Pointers.isRightHand(event.id) || event.id === pointer) && id === background && prevPos2D !== undefined) { var diff = event.pos2D.x - prevPos2D.x; scroll = scroll + diff / BUTTON_WIDTH; if (avgScroll == 0) { avgScroll = diff; } else { avgScroll = 0.9 * avgScroll + 0.1 * diff; } prevPos2D = event.pos2D; } } Overlays.mouseMoveOnOverlay.connect(mouseMoveOnOverlay); function skeletonModelURLChanged() { if (currentSelectionIndex != -1) { closeApp(currentSelectionIndex); openApp(currentSelectionIndex); } } MyAvatar.skeletonModelURLChanged.connect(skeletonModelURLChanged); function shutdown() { closeWatch(); for (var i = 0; i < entities.length; i++) { Entities.deleteEntity(entities[i]); } Pointers.removePointer(pointer); entities = []; overlays = []; } Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE