// // watchScript.js // // Created by David Back 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 LEFT_FOREARM_JOINT = "LeftForeArm"; const LEFT_HAND_JOINT = "LeftHand"; const RIGHT_HAND_JOINT = "RightHand"; const ACTIVATION_DISTANCE = 0.25; const DEACTIVATION_DISTANCE = 0.6; const SWIPE_THRESHOLD = 0.125; const ACTIVATION_INTERVAL = 50; const WATCH_Z_OFFSET = -0.05; const WATCH_FOREARM_HAND_DISTANCE = 0.8; const LEFT_HAND = 0; const RIGHT_HAND = 1; const WATCH_MODEL = Script.resolvePath("watchModel.fbx"); const GHOST_EFFECT = Script.resolvePath("ghost-effect.png"); const PARTICLE_EFFECT = Script.resolvePath("particle-effect.png"); const RESET = Script.resolvePath("reset.png"); const SELECTION_ORDER = [ GHOST_EFFECT, PARTICLE_EFFECT, RESET ]; const DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; const DEBUG_ACTIVATION_KEY = 70; var materialEntities = []; var watchOpen = false; var debugWatchOpen = false; var activationInterval = null; var watchEntity = null; var projectionEffect = null; var selectionOverlay = null; var selectionIndex = 0; var pointer = null; var mousePressPickPos2D = null; var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); function cleanupFromBefore() { var toDelete = Entities.findEntitiesByName("Hackathon_GhostMaterial", MyAvatar.position, 1000); for (var i = 0; i < toDelete.length; i++) { Entities.deleteEntity(toDelete[i]); } var toDelete = Entities.findEntitiesByName("Hackathon_Particle", MyAvatar.position, 1000); for (var i = 0; i < toDelete.length; i++) { Entities.deleteEntity(toDelete[i]); } } function openWatch() { print("WATCH - OPEN"); projectionEffect = Entities.addEntity({ alpha: 0, alphaFinish: 0, alphaStart: 0.5, color: { "blue": 222, "green": 164, "red": 18 }, colorSpread: { "blue": 100, "green": 50, "red": 50 }, emitAcceleration: { "x": 0, "y": 0.8, "z": 0 }, emitOrientation: Quat.fromPitchYawRollDegrees(-180, 0, 0), emitRate: 10, emitSpeed: 1, lifespan: 0.8, maxParticles: 10, name: "Watch Projection Effect", parentID: watchEntity, particleRadius: 0.2, radiusFinish: 0.1, radiusStart: 0, speedSpread: 0, spinFinish: 0, spinStart: 0, textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", type: "ParticleEffect" }, true); selectionOverlay = Overlays.addOverlay("image3d", { name: "Watch Selection", url: SELECTION_ORDER[selectionIndex], alpha: 1, dimensions: { x: 0.4, y: 0.4 }, visible: true, emissive: true, ignoreRayIntersection: false, parentID: watchEntity, localPosition: { x: 0, y: 0, z: -0.3 }, localRotation: Quat.fromPitchYawRollDegrees(-90, 0, -90) }); pointer = Pointers.createPointer(PickType.Stylus, { hand: RIGHT_HAND, filter: Picks.PICK_OVERLAYS, enabled: true }); Pointers.setIncludeItems(pointer, [selectionOverlay]); Overlays.mousePressOnOverlay.connect(mousePressOnOverlay); Overlays.mouseReleaseOnOverlay.connect(mouseReleaseOnOverlay); triggerMapping.enable(); cleanupFromBefore(); watchOpen = true; } function closeWatch() { print("WATCH - CLOSE"); if (projectionEffect !== null) { Entities.deleteEntity(projectionEffect); projectionEffect = null; } if (selectionOverlay !== null) { Overlays.deleteOverlay(selectionOverlay); selectionOverlay = null; } if (pointer !== null) { Pointers.removePointer(pointer); pointer = null; } for (var i = 0; i < materialEntities.length; i++) { Entities.deleteEntity(materialEntities[i]); } Overlays.mousePressOnOverlay.disconnect(mousePressOnOverlay); Overlays.mouseReleaseOnOverlay.disconnect(mouseReleaseOnOverlay); triggerMapping.disable(); watchOpen = false; } function triggerSelection() { print("WATCH - TRIGGER SELECTION"); var model = Graphics.getModel(MyAvatar.SELF_ID); 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++) { materialEntities.push(Entities.addEntity({ name: "Hackathon_GhostMaterial", type: "Material", position: MyAvatar.position, materialURL: "materialData", priority: 1, parentID: MyAvatar.SELF_ID, parentMaterialName: partID, materialData: JSON.stringify({ materials: { albedoMap: "https://drive.google.com/file/d/1zvVO63hpdz5d4zvmT3d5pZG-12X3_KYW/view?usp=sharing", opacity: 0.2 } }) }, true)); partID++; } } } function makeClickHandler(hand) { return function(clicked) { if (clicked === 1.0) { triggerSelection(); } }; } function projectOntoOverlayXYPlane(overlayID, worldPos) { var position = Overlays.getProperty(overlayID, "position"); var rotation = Overlays.getProperty(overlayID, "rotation"); var dimensions = Overlays.getProperty(overlayID, "dimensions"); dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension var invRot = Quat.inverse(rotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position)); var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); return { x: normalizedPos.x * dimensions.x, y: (1 - normalizedPos.y) * dimensions.y // flip y-axis }; } function mousePressOnOverlay(overlayID, event) { if (overlayID === selectionOverlay && event.id === pointer) { //print("mousePressOnOverlay event.button " + event.button + " event.pos2D " + event.pos2D.x + " " + event.pos2D.y); //mousePressEventPos = event.pos2D; mousePressPickPos2D = projectOntoOverlayXYPlane(selectionOverlay, Pointers.getPrevPickResult(pointer).intersection); print("mousePressPickPos2D " + mousePressPickPos2D.x + " " + mousePressPickPos2D.y); } } function mouseReleaseOnOverlay(overlayID, event) { /* if (overlayID === selectionOverlay && event.id === pointer) { print("mouseReleaseOnOverlay event.button " + event.button + " event.pos2D " + event.pos2D.x + " " + event.pos2D.y); var mouseReleaseEventPos = event.pos2D; var posDifference = Vec3.subtract(mousePressEventPos, mouseReleaseEventPos); print("posDifference " + posDifference.x + " " + posDifference.y); } */ } function checkActivation() { var watchPosition = Entities.getEntityProperties(watchEntity, ['position']).position; var rightHandPosition = MyAvatar.getJointPosition(RIGHT_HAND_JOINT); var distance = Vec3.distance(watchPosition, rightHandPosition); if (watchOpen && distance > DEACTIVATION_DISTANCE && !debugWatchOpen) { closeWatch(); } else if (!watchOpen && distance < ACTIVATION_DISTANCE) { openWatch(); } } function keyPressEvent(event) { if (DEBUG_ACTIVATION_KEY === event.key) { print("WATCH - DEBUG_ACTIVATION_KEY triggered - watchOpen was " + watchOpen); if (watchOpen) { closeWatch(); debugWatchOpen = false; } else { openWatch(); debugWatchOpen = true; } } } function update() { /* if (watchEntity) { var handRotation = MyAvatar.getJointRotation(MyAvatar.getJointIndex(LEFT_HAND_JOINT)); var handPosition = MyAvatar.getJointPosition(MyAvatar.getJointIndex(LEFT_HAND_JOINT)); Entities.editEntity(watchEntity, { position: handPosition, rotation: handRotation }); } */ if (mousePressPickPos2D) { var pickResult = Pointers.getPrevPickResult(pointer); if (pickResult.type === Picks.INTERSECTED_NONE) { mousePressPickPos2D = null; } else { var pickPos2D = projectOntoOverlayXYPlane(selectionOverlay, Pointers.getPrevPickResult(pointer).intersection); var positionDifference = Vec3.subtract(pickPos2D, mousePressPickPos2D); var length = Vec3.length(positionDifference); if (length >= SWIPE_THRESHOLD) { var xDifference = pickPos2D.x - mousePressPickPos2D.x; if (xDifference > 0) { if (selectionIndex > 0) { selectionIndex--; Overlays.editOverlay(selectionOverlay, { url: SELECTION_ORDER[selectionIndex]} ); } print("WATCH - SWIPE LEFT - new selection is " + selectionIndex); } else { if (selectionIndex < SELECTION_ORDER.length - 1) { selectionIndex++; Overlays.editOverlay(selectionOverlay, { url: SELECTION_ORDER[selectionIndex]} ); } print("WATCH - SWIPE RIGHT - new selection is " + selectionIndex); } mousePressPickPos2D = null; } } } for (var i = 0; i < materialEntities.length; i++) { Entities.editEntity(materialEntities[i], { materialMappingPos: { x: Math.cos(t * 0.03), y: Math.sin(t * 0.02) }, materialMappingScale: {x: 2.0 * Math.sin(t * 0.01) + 0.1, y: 2.0 * Math.sin(t * 0.01) + 0.1 } }); t = t + dt; } Entities.editEntity(entities[entities.length - 2], { isEmitting: Audio.inputLevel > 0.01, emitRate: 70 * Audio.inputLevel }); Entities.editEntity(entities[entities.length - 1], { isEmitting: Audio.inputLevel > 0.01, emitRate: 70 * Audio.inputLevel }); } function shutdown() { closeWatch(); if (watchEntity !== null) { Entities.deleteEntity(watchEntity); } Controller.keyPressEvent.disconnect(keyPressEvent); } function startup() { watchEntity = Entities.addEntity({ collidesWith: "static,dynamic,kinematic,otherAvatar,", dimensions: { x: 0.1, y: 0.1, z: 0.02 }, name: "Watch Entity", parentID: MyAvatar.SELF_ID, //parentJointIndex: MyAvatar.getJointIndex(LEFT_HAND_JOINT), //localPosition: { x: 0, y: -0.3 * Vec3.distance(MyAvatar.getJointPosition(WATCH_JOINT), MyAvatar.getJointPosition(LEFT_HAND_JOINT)), z: WATCH_Z_OFFSET}, 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}, modelURL: WATCH_MODEL, type: "Model" }, true); Script.update.connect(update); activationInterval = Script.setInterval(function() { checkActivation(); }, ACTIVATION_INTERVAL); Controller.keyPressEvent.connect(keyPressEvent); triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); } Script.scriptEnding.connect(shutdown); startup(); }()); // END LOCAL_SCOPE