// // fingerPaint.js // // Created by David Rowe on 15 Feb 2017 // Copyright 2017 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 () { var tablet, button, BUTTON_NAME = "PAINT", //undo vars UNDO_STACK_SIZE = 10, undoStack = []; isFingerPainting = false, isTabletFocused = false, shouldRestoreTablet = false, tabletDebugFocusLine = null, //animated brush vars lastFrameTime = Date.now(), frameDeltaSeconds = null, //time that passed between frames in ms; //end of dynamic brush vars MAX_LINE_WIDTH = 0.036, leftHand = null, rightHand = null, leftBrush = null, rightBrush = null, isBrushColored = false, isLeftHandDominant = false, isMouseDrawing = false, savedSettings = null, CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", isTabletDisplayed = false, HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable", SCRIPT_PATH = Script.resolvePath(''), CONTENT_PATH = SCRIPT_PATH.substr(0, SCRIPT_PATH.lastIndexOf('/')), ANIMATION_SCRIPT_PATH = Script.resolvePath("content/brushes/dynamicBrushes/dynamicBrushScript.js"), APP_URL = CONTENT_PATH + "/html/main.html"; // Set up the qml ui //var qml = Script.resolvePath('PaintWindow.qml'); Script.include("../libraries/controllers.js"); Script.include("content/brushes/dynamicBrushes/dynamicBrushesList.js"); //var window = null; //var inkSource = null; var inkSourceOverlay = null; // Set path for finger paint hand animations var RIGHT_ANIM_URL = Script.resourcesPath() + 'avatar/animations/touch_point_closed_right.fbx'; var LEFT_ANIM_URL = Script.resourcesPath() + 'avatar/animations/touch_point_closed_left.fbx'; var RIGHT_ANIM_URL_OPEN = Script.resourcesPath() + 'avatar/animations/touch_point_open_right.fbx'; var LEFT_ANIM_URL_OPEN = Script.resourcesPath() + 'avatar/animations/touch_point_open_left.fbx'; function paintBrush(name) { // Paints in 3D. var brushName = name, STROKE_COLOR = savedSettings.currentColor, ERASE_SEARCH_RADIUS = 0.1, // m STROKE_DIMENSIONS = { x: 10, y: 10, z: 10 }, isDrawingLine = false, isTriggerPressureWidthEnabled = savedSettings.currentTriggerWidthEnabled, entityID, basePosition, strokePoints, strokeNormals, strokeWidths, timeOfLastPoint, texture = CONTENT_PATH + "/" + savedSettings.currentTexture.brushName, //'https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Caris_Tessellation.svg/1024px-Caris_Tessellation.svg.png', // Daantje strokeWidthMultiplier = savedSettings.currentStrokeWidth * 2 + 0.1, IS_UV_MODE_STRETCH = savedSettings.currentTexture.brushType == "stretch", MIN_STROKE_LENGTH = 0.005, // m MIN_STROKE_INTERVAL = 66, // ms MAX_POINTS_PER_LINE = 70; // Hard-coded limit in PolyLineEntityItem.h. function strokeNormal() { var controllerPose = isLeftHandDominant ? getControllerWorldLocation(Controller.Standard.LeftHand, true) : getControllerWorldLocation(Controller.Standard.RightHand, true); var fingerTipRotation = controllerPose.rotation; return Quat.getUp(fingerTipRotation); } function changeStrokeColor(red, green, blue) { STROKE_COLOR.red = red; STROKE_COLOR.green = green; STROKE_COLOR.blue = blue; } function getStrokeColor() { return STROKE_COLOR; } function isDrawing() { return isDrawingLine; } function changeStrokeWidthMultiplier(multiplier) { // MIN_STROKE_LENGTH = ((multiplier * MIN_STROKE_LENGTH) / 0.25)*0.5; //print("MIN_STROKE_LENGTH: " + MIN_STROKE_LENGTH); strokeWidthMultiplier = multiplier; } function setTriggerPressureWidthEnabled(isEnabled) { isTriggerPressureWidthEnabled = isEnabled; } function changeUVMode(isUVModeStretch) { IS_UV_MODE_STRETCH = isUVModeStretch; } function getStrokeWidth() { return strokeWidthMultiplier; } function getEntityID() { return entityID; } function changeTexture(textureURL) { texture = textureURL; } function undo() { var undo = undoStack.pop(); if (undoStack.length == 0) { var undoDisableEvent = {type: "undoDisable", value: true}; tablet.emitScriptEvent(JSON.stringify(undoDisableEvent)); } if (undo.type == "deleted") { var prevEntityId = undo.data.id; var newEntity = Entities.addEntity(undo.data); //restoring a deleted entity will create a new entity with a new id therefore we need to update //the created elements id in the undo stack. For the delete elements though, it is not necessary //to update the id since you can't delete the same entity twice for (var i = 0; i < undoStack.length; i++) { if (undoStack[i].type == "created" && undoStack[i].data == prevEntityId) { undoStack[i].data = newEntity; } } } else { Entities.deleteEntity(undo.data); } } function calculateLineWidth(width) { if (isTriggerPressureWidthEnabled) { return width * strokeWidthMultiplier; } else { return MAX_LINE_WIDTH * strokeWidthMultiplier; //MAX_LINE_WIDTH } } function startLine(position, width) { // Start drawing a polyline. if (isTabletFocused) return; width = calculateLineWidth(width); if (isDrawingLine) { print("ERROR: startLine() called when already drawing line"); // Nevertheless, continue on and start a new line. } basePosition = position; strokePoints = [Vec3.ZERO]; strokeNormals = [strokeNormal()]; strokeWidths = [width]; timeOfLastPoint = Date.now(); entityID = Entities.addEntity({ type: "PolyLine", name: "fingerPainting", shapeType: "box", color: STROKE_COLOR, position: position, linePoints: strokePoints, normals: strokeNormals, strokeWidths: strokeWidths, textures: texture, // Daantje isUVModeStretch: IS_UV_MODE_STRETCH, dimensions: STROKE_DIMENSIONS, }); isDrawingLine = true; addAnimationToBrush(entityID); } function drawLine(position, width) { // Add a stroke to the polyline if stroke is a sufficient length. var localPosition, distanceToPrevious, MAX_DISTANCE_TO_PREVIOUS = 1.0; width = calculateLineWidth(width); if (!isDrawingLine) { print("ERROR: drawLine() called when not drawing line"); return; } localPosition = Vec3.subtract(position, basePosition); distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]); if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { // Ignore occasional spurious finger tip positions. return; } if (distanceToPrevious >= MIN_STROKE_LENGTH && (Date.now() - timeOfLastPoint) >= MIN_STROKE_INTERVAL && strokePoints.length < MAX_POINTS_PER_LINE) { strokePoints.push(localPosition); strokeNormals.push(strokeNormal()); strokeWidths.push(width); timeOfLastPoint = Date.now(); Entities.editEntity(entityID, { color: STROKE_COLOR, linePoints: strokePoints, normals: strokeNormals, strokeWidths: strokeWidths }); } } function finishLine(position, width) { // Finish drawing polyline; delete if it has only 1 point. //stopDynamicBrush = true; //print("Before adding script: " + JSON.stringify(Entities.getEntityProperties(entityID))); var userData = Entities.getEntityProperties(entityID).userData; if (userData && JSON.parse(userData).animations) { Entities.editEntity(entityID, { script: ANIMATION_SCRIPT_PATH, }); } //print("After adding script: " + JSON.stringify(Entities.getEntityProperties(entityID))); //setIsDrawingFingerPaint(entityID, false); //print("already stopped drawing"); width = calculateLineWidth(width); if (!isDrawingLine) { print("ERROR: finishLine() called when not drawing line"); return; } if (strokePoints.length === 1) { // Delete "empty" line. Entities.deleteEntity(entityID); } isDrawingLine = false; addElementToUndoStack({type: "created", data: entityID}); //print("After adding script 3: " + JSON.stringify(Entities.getEntityProperties(entityID))); } function cancelLine() { // Cancel any line being drawn. if (isDrawingLine) { Entities.deleteEntity(entityID); isDrawingLine = false; } } function eraseClosestLine(position) { // Erase closest line that is within search radius of finger tip. var entities, entitiesLength, properties, i, pointsLength, j, distance, found = false, foundID, foundDistance = ERASE_SEARCH_RADIUS; // Find entities with bounding box within search radius. entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS); // Fine polyline entity with closest point within search radius. for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) { properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]); if (properties.type === "PolyLine") { basePosition = properties.position; for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) { distance = Vec3.distance(position, Vec3.sum(basePosition, properties.linePoints[j])); if (distance <= foundDistance) { found = true; foundID = entities[i]; foundDistance = distance; } } } } // Delete found entity. if (found) { addElementToUndoStack({type: "deleted", data: Entities.getEntityProperties(foundID)}); Entities.deleteEntity(foundID); /*Entities.editEntity(entityID, { color: «, normals: strokeNormals, strokeWidths: strokeWidths });*/ } } function tearDown() { cancelLine(); } return { startLine: startLine, drawLine: drawLine, finishLine: finishLine, cancelLine: cancelLine, eraseClosestLine: eraseClosestLine, tearDown: tearDown, changeStrokeColor: changeStrokeColor, changeStrokeWidthMultiplier: changeStrokeWidthMultiplier, changeTexture: changeTexture, isDrawing: isDrawing, undo: undo, getStrokeColor: getStrokeColor, getStrokeWidth: getStrokeWidth, getEntityID: getEntityID, changeUVMode: changeUVMode, setTriggerPressureWidthEnabled: setTriggerPressureWidthEnabled }; } function handController(name) { // Translates controller data into application events. var handName = name, triggerPressedCallback, triggerPressingCallback, triggerReleasedCallback, gripPressedCallback, rawTriggerValue = 0.0, triggerValue = 0.0, isTriggerPressed = false, TRIGGER_SMOOTH_RATIO = 0.1, TRIGGER_OFF = 0.05, TRIGGER_ON = 0.1, TRIGGER_START_WIDTH_RAMP = 0.15, TRIGGER_FINISH_WIDTH_RAMP = 1.0, TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP, MIN_LINE_WIDTH = 0.005, RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH, rawGripValue = 0.0, gripValue = 0.0, isGripPressed = false, GRIP_SMOOTH_RATIO = 0.1, GRIP_OFF = 0.05, GRIP_ON = 0.1; function onTriggerPress(value) { // Controller values are only updated when they change so store latest for use in update. rawTriggerValue = value; } function updateTriggerPress(value) { var LASER_ALPHA = 0.5; var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var LEFT_HUD_LASER = 1; var RIGHT_HUD_LASER = 2; var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER; if (isLeftHandDominant){ HMD.setHandLasers(RIGHT_HUD_LASER, true, LASER_TRIGGER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); HMD.disableHandLasers(LEFT_HUD_LASER); }else{ HMD.setHandLasers(LEFT_HUD_LASER, true, LASER_TRIGGER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); HMD.disableHandLasers(RIGHT_HUD_LASER); } HMD.disableExtraLaser(); var wasTriggerPressed, fingerTipPosition, lineWidth; triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); wasTriggerPressed = isTriggerPressed; if (isTriggerPressed) { isTriggerPressed = triggerValue > TRIGGER_OFF; } else { isTriggerPressed = triggerValue > TRIGGER_ON; } if (wasTriggerPressed || isTriggerPressed) { fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); opositeHandPosition = MyAvatar.getJointPosition(handName === "left" ? "RightHandMiddle1" : "LeftHandMiddle1"); if (triggerValue < TRIGGER_START_WIDTH_RAMP) { lineWidth = MIN_LINE_WIDTH; } else { lineWidth = MIN_LINE_WIDTH + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; } if ((handName === "left" && isLeftHandDominant) || (handName === "right" && !isLeftHandDominant)){ if (!wasTriggerPressed && isTriggerPressed) { // TEST DAANTJE changes to a random color everytime you start a new line //leftBrush.changeStrokeColor(Math.random()*255, Math.random()*255, Math.random()*255); //rightBrush.changeStrokeColor(Math.random()*255, Math.random()*255, Math.random()*255); // TEST Stroke line width //var dim = Math.random()*4 + +0.5; //var dim2 = Math.floor( Math.random()*40 + 5); //leftBrush.changeStrokeWidthMultiplier(dim); //rightBrush.changeStrokeWidthMultiplier(dim); triggerPressedCallback(fingerTipPosition, lineWidth); } else if (wasTriggerPressed && isTriggerPressed) { triggerPressingCallback(fingerTipPosition, lineWidth); } else { triggerReleasedCallback(fingerTipPosition, lineWidth); /* // define condition to switch dominant hands if (Vec3.length(Vec3.subtract(fingerTipPosition, opositeHandPosition)) < 0.1){ isLeftHandDominant = !isLeftHandDominant; // Test DAANTJE changes texture // if (Math.random() > 0.5) { // leftBrush.changeTexture(null); // rightBrush.changeTexture(null); // }else { // leftBrush.changeTexture('http://i.imgur.com/SSWDJtd.png'); // rightBrush.changeTexture('https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Caris_Tessellation.svg/1024px-Caris_Tessellation.svg.png'); // } } */ } } } } function onGripPress(value) { // Controller values are only updated when they change so store latest for use in update. rawGripValue = value; } function updateGripPress() { var fingerTipPosition; gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); if (isGripPressed) { isGripPressed = gripValue > GRIP_OFF; } else { isGripPressed = gripValue > GRIP_ON; if (isGripPressed) { fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); if ((handName === "left" && isLeftHandDominant) || (handName === "right" && !isLeftHandDominant)){ gripPressedCallback(fingerTipPosition); } } } } function checkTabletHasFocus() { var controllerPose = isLeftHandDominant ? getControllerWorldLocation(Controller.Standard.LeftHand, true) : getControllerWorldLocation(Controller.Standard.RightHand, true); var fingerTipRotation = controllerPose.rotation; var fingerTipPosition = controllerPose.position; var pickRay = { origin: fingerTipPosition, direction: Quat.getUp(fingerTipRotation) } var overlays = Overlays.findRayIntersection(pickRay, false, [HMD.tabletID], [inkSourceOverlay.overlayID]); if (overlays.intersects && HMD.tabletID == overlays.overlayID) { if (!isTabletFocused) { isTabletFocused = true; //isFingerPainting = false; Overlays.editOverlay(inkSourceOverlay, {visible: false}); updateHandAnimations(); pauseProcessing(); //print("Hovering tablet!"); } } else { if (isTabletFocused) { //print("Unhovering tablet!"); isTabletFocused = false; //isFingerPainting = true; Overlays.editOverlay(inkSourceOverlay, {visible: true}); resumeProcessing(); updateHandFunctions(); //updateHandAnimations(); /*print("Current hand " + handName); print("isFingerPainting " + isFingerPainting); print("inkSourceOverlay " + JSON.stringify(inkSourceOverlay)); print("inkSourceOverlay " + JSON.stringify(inkSourceOverlay));*/ } }; } function onUpdate() { //update ink Source // var strokeColor = leftBrush.getStrokeColor(); // var strokeWidth = leftBrush.getStrokeWidth()*0.06; // var position = MyAvatar.getJointPosition(isLeftHandDominant ? "LeftHandIndex4" : "RightHandIndex4"); // if (inkSource){ // Entities.editEntity(inkSource, { // color : strokeColor, // position : position, // dimensions : { // x: strokeWidth, // y: strokeWidth, // z: strokeWidth} // }); // } else{ // var inkSourceProps = { // type: "Sphere", // name: "inkSource", // color: strokeColor, // position: position, // ignoreForCollisions: true, // dimensions: {x: strokeWidth, y:strokeWidth, z:strokeWidth} // } // inkSource = Entities.addEntity(inkSourceProps); // } /*frameDeltaSeconds = Date.now() - lastFrameTime; lastFrameTime = Date.now(); var nearbyEntities = Entities.findEntities(MyAvatar.position, 5.0); for (var i = 0; i < nearbyEntities.length; i++) { //Entities.editEntity(nearbyEntities[i], {userData: ""}); //clear user data (good to use in case we need to clear it) var userData = Entities.getEntityProperties(nearbyEntities[i]).userData; if (userData != "") { playAnimations(nearbyEntities[i], JSON.parse(userData).animations); } }*/ /*if (Entities.getEntityProperties(nearbyEntities[i]).name == "fingerPainting") Entities.deleteEntity(nearbyEntities[i]);*/ if (HMD.tabletID && (((leftBrush == null || rightBrush == null) || (!leftBrush.isDrawing() && !rightBrush.isDrawing())))) { checkTabletHasFocus(); } updateTriggerPress(); updateGripPress(); } function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { triggerPressedCallback = onTriggerPressed; triggerPressingCallback = onTriggerPressing; triggerReleasedCallback = onTriggerReleased; gripPressedCallback = onGripPressed; } function tearDown() { // Nothing to do. //Entities //if (inkSource){ // Entities.deleteEntity(inkSource); // inkSource = null; //} } return { onTriggerPress: onTriggerPress, onGripPress: onGripPress, onUpdate: onUpdate, setUp: setUp, tearDown: tearDown }; } function updateHandFunctions() { // Update other scripts' hand functions. var enabled = !isFingerPainting || isTabletDisplayed; Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ holdEnabled: enabled, nearGrabEnabled: enabled, farGrabEnabled: enabled }), true); Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ pointerEnabled: false }), true); // Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ // pointerEnabled: enabled //}), true); //}), true); Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ pointIndex: !enabled }), true); } function updateHandAnimations(){ var ANIM_URL = (isLeftHandDominant? LEFT_ANIM_URL: RIGHT_ANIM_URL ); var ANIM_OPEN = (isLeftHandDominant? LEFT_ANIM_URL_OPEN: RIGHT_ANIM_URL_OPEN ); var handLiteral = (isLeftHandDominant? "left": "right" ); //Clear previous hand animation override restoreAllHandAnimations(); //"rightHandGraspOpen","rightHandGraspClosed", MyAvatar.overrideRoleAnimation(handLiteral + "HandGraspOpen", ANIM_OPEN, 30, false, 19, 20); MyAvatar.overrideRoleAnimation(handLiteral + "HandGraspClosed", ANIM_URL, 30, false, 19, 20); //"rightIndexPointOpen","rightIndexPointClosed", MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointOpen", ANIM_OPEN, 30, false, 19, 20); MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointClosed", ANIM_URL, 30, false, 19, 20); //"rightThumbRaiseOpen","rightThumbRaiseClosed", MyAvatar.overrideRoleAnimation(handLiteral + "ThumbRaiseOpen", ANIM_OPEN, 30, false, 19, 20); MyAvatar.overrideRoleAnimation(handLiteral + "ThumbRaiseClosed", ANIM_URL, 30, false, 19, 20); //"rightIndexPointAndThumbRaiseOpen","rightIndexPointAndThumbRaiseClosed", MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointAndThumbRaiseOpen", ANIM_OPEN, 30, false, 19, 20); MyAvatar.overrideRoleAnimation(handLiteral + "IndexPointAndThumbRaiseClosed", ANIM_URL, 30, false, 19, 20); //turn off lasers and other interactions Messages.sendLocalMessage("Hifi-Hand-Disabler", "none"); Messages.sendLocalMessage("Hifi-Hand-Disabler", handLiteral); //update ink Source var strokeColor = leftBrush.getStrokeColor(); var strokeWidth = leftBrush.getStrokeWidth()*0.06; if (inkSourceOverlay == null){ inkSourceOverlay = Overlays.addOverlay("sphere", { parentID: MyAvatar.sessionUUID, parentJointIndex: MyAvatar.getJointIndex(handLiteral === "left" ? "LeftHandIndex4" : "RightHandIndex4"), localPosition: { x: 0, y: 0, z: 0 }, size: strokeWidth, color: strokeColor , solid: true }); } else { Overlays.editOverlay(inkSourceOverlay, { parentJointIndex: MyAvatar.getJointIndex(handLiteral === "left" ? "LeftHandIndex4" : "RightHandIndex4"), localPosition: { x: 0, y: 0, z: 0 }, size: strokeWidth, color: strokeColor }); } } function restoreAllHandAnimations(){ //"rightHandGraspOpen","rightHandGraspClosed", MyAvatar.restoreRoleAnimation("rightHandGraspOpen"); MyAvatar.restoreRoleAnimation("rightHandGraspClosed"); //"rightIndexPointOpen","rightIndexPointClosed", MyAvatar.restoreRoleAnimation("rightIndexPointOpen"); MyAvatar.restoreRoleAnimation("rightIndexPointClosed"); //"rightThumbRaiseOpen","rightThumbRaiseClosed", MyAvatar.restoreRoleAnimation("rightThumbRaiseOpen"); MyAvatar.restoreRoleAnimation("rightThumbRaiseClosed"); //"rightIndexPointAndThumbRaiseOpen","rightIndexPointAndThumbRaiseClosed", MyAvatar.restoreRoleAnimation("rightIndexPointAndThumbRaiseOpen"); MyAvatar.restoreRoleAnimation("rightIndexPointAndThumbRaiseClosed"); //"leftHandGraspOpen","leftHandGraspClosed", MyAvatar.restoreRoleAnimation("leftHandGraspOpen"); MyAvatar.restoreRoleAnimation("leftHandGraspClosed"); //"leftIndexPointOpen","leftIndexPointClosed", MyAvatar.restoreRoleAnimation("leftIndexPointOpen"); MyAvatar.restoreRoleAnimation("leftIndexPointClosed"); //"leftThumbRaiseOpen","leftThumbRaiseClosed", MyAvatar.restoreRoleAnimation("leftThumbRaiseOpen"); MyAvatar.restoreRoleAnimation("leftThumbRaiseClosed"); //"leftIndexPointAndThumbRaiseOpen","leftIndexPointAndThumbRaiseClosed", MyAvatar.restoreRoleAnimation("leftIndexPointAndThumbRaiseOpen"); MyAvatar.restoreRoleAnimation("leftIndexPointAndThumbRaiseClosed"); } function pauseProcessing() { //Script.update.disconnect(leftHand.onUpdate); //Script.update.disconnect(rightHand.onUpdate); //Controller.disableMapping(CONTROLLER_MAPPING_NAME); Messages.sendLocalMessage("Hifi-Hand-Disabler", "none"); Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); //Restores and clears hand animations restoreAllHandAnimations(); } function resumeProcessing() { //Change to finger paint hand animation updateHandAnimations(); // Messages channels for enabling/disabling other scripts' functions. Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); } function enableProcessing() { // Connect controller API to handController objects. leftHand = handController("left"); rightHand = handController("right"); // Connect handController outputs to paintBrush objects. leftBrush = paintBrush("left"); leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine); rightBrush = paintBrush("right"); rightHand.setUp(rightBrush.startLine, rightBrush.drawLine, rightBrush.finishLine, rightBrush.eraseClosestLine); var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); controllerMapping.from(Controller.Standard.LT).to(leftHand.onTriggerPress); controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress); controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); Controller.enableMapping(CONTROLLER_MAPPING_NAME); //Change to finger paint hand animation updateHandAnimations(); // Messages channels for enabling/disabling other scripts' functions. Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); // Update hand controls. Script.update.connect(leftHand.onUpdate); Script.update.connect(rightHand.onUpdate); // enable window palette /*window = new OverlayWindow({ title: 'Paint Window', source: qml, width: 600, height: 600, });*/ // 75 //50 //window.setPosition(75, 100); //window.closed.connect(function() { //Script.stop(); //}); uncomment for qml interface /*window.fromQml.connect(function(message){ if (message[0] === "color"){ leftBrush.changeStrokeColor(message[1], message[2], message[3]); rightBrush.changeStrokeColor(message[1], message[2], message[3]); Overlays.editOverlay(inkSourceOverlay, { color: {red: message[1], green: message[2], blue: message[3]} }); return; } if (message[0] === "width"){ print("changing brush width " + message[1]); var dim = message[1]*2 +0.1; print("changing brush dim " + dim); //var dim2 = Math.floor( Math.random()*40 + 5); leftBrush.changeStrokeWidthMultiplier(dim); rightBrush.changeStrokeWidthMultiplier(dim); Overlays.editOverlay(inkSourceOverlay, { size: dim * 0.06 }); return; } if (message[0] === "brush"){ print("changing brush to qml " + message[1]); //var dim2 = Math.floor( Math.random()*40 + 5); leftBrush.changeTexture(message[1]); rightBrush.changeTexture(message[1]); if (message[1] === "content/brushes/paintbrush1.png") { leftBrush.changeUVMode(true); rightBrush.changeUVMode(true); }else if (message[1] === "content/brushes/paintbrush3.png") { leftBrush.changeUVMode(true); rightBrush.changeUVMode(true); }else{ leftBrush.changeUVMode(false); rightBrush.changeUVMode(false); } return; } if (message[0] === "undo"){ leftBrush.undo(); rightBrush.undo(); return; } if (message[0] === "hand"){ isLeftHandDominant = !isLeftHandDominant; updateHandAnimations(); return; } });*/ //uncomment for qml interface } function disableProcessing() { if (leftHand && rightHand) { Script.update.disconnect(leftHand.onUpdate); Script.update.disconnect(rightHand.onUpdate); Controller.disableMapping(CONTROLLER_MAPPING_NAME); Messages.sendLocalMessage("Hifi-Hand-Disabler", "none"); leftBrush.tearDown(); leftBrush = null; leftHand.tearDown(); leftHand = null; rightBrush.tearDown(); rightBrush = null; rightHand.tearDown(); rightHand = null; Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); //Restores and clears hand animations restoreAllHandAnimations(); //clears Overlay sphere Overlays.deleteOverlay(inkSourceOverlay); inkSourceOverlay = null; // disable window palette //window.close(); //uncomment for qml interface } } //Load last fingerpaint settings function restoreLastValues() { savedSettings = new Object(); savedSettings.currentColor = Settings.getValue("currentColor", {red: 250, green: 0, blue: 0, origin: "custom"}), savedSettings.currentStrokeWidth = Settings.getValue("currentStrokeWidth", 0.25); savedSettings.currentTexture = Settings.getValue("currentTexture", {brushID: 0}); savedSettings.currentDrawingHand = Settings.getValue("currentDrawingHand", MyAvatar.getDominantHand() == "left"); savedSettings.currentDynamicBrushes = Settings.getValue("currentDynamicBrushes", []); savedSettings.customColors = Settings.getValue("customColors", []); savedSettings.currentTab = Settings.getValue("currentTab", 0); savedSettings.currentTriggerWidthEnabled = Settings.getValue("currentTriggerWidthEnabled", true); savedSettings.undoDisable = undoStack.length == 0; isLeftHandDominant = savedSettings.currentDrawingHand; } function onButtonClicked() { restoreLastValues(); //isFingerPainting = false; var wasFingerPainting = isFingerPainting; isFingerPainting = !isFingerPainting; print("isFingerPainting: " + isFingerPainting); if (!isFingerPainting) { tablet.gotoHomeScreen(); } button.editProperties({ isActive: isFingerPainting }); if (wasFingerPainting) { leftBrush.cancelLine(); rightBrush.cancelLine(); } if (isFingerPainting) { print("opening tablet fingerpaint app"); tablet.gotoWebScreen(APP_URL + "?" + encodeURIComponent(JSON.stringify(savedSettings))); enableProcessing(); savedSettings = null; } updateHandFunctions(); if (!isFingerPainting) { disableProcessing(); Controller.mousePressEvent.disconnect(mouseStartLine); Controller.mouseMoveEvent.disconnect(mouseDrawLine); Controller.mouseReleaseEvent.disconnect(mouseFinishLine); } else { Controller.mousePressEvent.connect(mouseStartLine); Controller.mouseMoveEvent.connect(mouseDrawLine); Controller.mouseReleaseEvent.connect(mouseFinishLine); } } function onTabletScreenChanged(type, url) { //var TABLET_SCREEN_CLOSED = "Closed"; //var TABLET_SCREEN_HOME = "Home"; //isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; //if (type === TABLET_SCREEN_HOME) { // if (isFingerPainting) { // isFingerPainting = false; // disableProcessing(); // } // button.editProperties({ isActive: isFingerPainting }); // print("Closing the APP"); //} //updateHandFunctions(); var TABLET_SCREEN_CLOSED = "Closed"; isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; isFingerPainting = type === "Web" && url.indexOf("fingerPaint/html/main.html") > -1; if (!isFingerPainting) { disableProcessing(); } if (shouldRestoreTablet) { shouldRestoreTablet = false; isFingerPainting = false; //in order for it to be re enabled onButtonClicked(); HMD.openTablet(); } button.editProperties({ isActive: isFingerPainting }); updateHandFunctions(); } function onWebEventReceived(event){ print("Received Web Event: " + event); if (typeof event === "string") { event = JSON.parse(event); } switch (event.type) { case "appReady": isTabletFocused = false; //make sure we can set the focus on the tablet again break; case "changeTab": Settings.setValue("currentTab", event.currentTab); break; case "changeColor": if (!isBrushColored) { var changeStrokeColorEvent = {type: "changeStrokeColor", value: event}; tablet.emitScriptEvent(JSON.stringify(changeStrokeColorEvent)); Settings.setValue("currentColor", event); //print("changing color..."); leftBrush.changeStrokeColor(event.red, event.green, event.blue); rightBrush.changeStrokeColor(event.red, event.green, event.blue); Overlays.editOverlay(inkSourceOverlay, { color: {red: event.red, green: event.green, blue: event.blue} }); } break; case "switchTriggerPressureWidth": //print("changing pressure sensitive width..."); Settings.setValue("currentTriggerWidthEnabled", event.enabled); leftBrush.setTriggerPressureWidthEnabled(event.enabled); rightBrush.setTriggerPressureWidthEnabled(event.enabled); break; case "addCustomColor": //print("Adding custom color"); var customColors = Settings.getValue("customColors", []); customColors.push({red: event.red, green: event.green, blue: event.blue}); if (customColors.length > event.maxColors) { customColors.splice(0, 1); //remove first color } Settings.setValue("customColors", customColors); break; case "changeBrush": //print("abrushType: " + event.brushType); Settings.setValue("currentTexture", event); if (event.brushType === "repeat") { print("brushType: " + event.brushType); leftBrush.changeUVMode(false); rightBrush.changeUVMode(false); } else if (event.brushType === "stretch") { print("brushType: " + event.brushType); leftBrush.changeUVMode(true); rightBrush.changeUVMode(true); } isBrushColored = event.isColored; if (event.isColored) { Settings.setValue("currentColor", {red: 255, green: 255, blue: 255}); leftBrush.changeStrokeColor(255, 255, 255); rightBrush.changeStrokeColor(255, 255, 255); Overlays.editOverlay(inkSourceOverlay, { color: {red: 255, green: 255, blue: 255} }); } //print("changing brush to " + event.brushName); event.brushName = CONTENT_PATH + "/" + event.brushName; leftBrush.changeTexture(event.brushName); rightBrush.changeTexture(event.brushName); break; case "changeLineWidth": Settings.setValue("currentStrokeWidth", event.brushWidth); var dim = event.brushWidth * 2 + 0.1; //print("changing brush width dim to " + dim); //var dim2 = Math.floor( Math.random()*40 + 5); leftBrush.changeStrokeWidthMultiplier(dim); rightBrush.changeStrokeWidthMultiplier(dim); Overlays.editOverlay(inkSourceOverlay, { size: dim * 0.06 }); break; case "undo": //print("Going to undo"); //The undo is called only on the right brush because the undo stack is global, meaning that //calling undo on both the left and right brush would cause the stack to pop twice. //Using the leftBrush instead of the rightBrush would have the exact same effect. rightBrush.undo(); break; case "changeBrushHand": Settings.setValue("currentDrawingHand", event.DrawingHand == "left"); isLeftHandDominant = event.DrawingHand == "left"; updateHandAnimations(); break; case "switchDynamicBrush": var dynamicBrushes = Settings.getValue("currentDynamicBrushes", []); var brushSettingsIndex = dynamicBrushes.indexOf(event.dynamicBrushID); if (brushSettingsIndex > -1) { //already exists so we are disabling it dynamicBrushes.splice(brushSettingsIndex, 1); } else { //doesn't exist yet so we are just adding it dynamicBrushes.push(event.dynamicBrushID); } Settings.setValue("currentDynamicBrushes", dynamicBrushes); DynamicBrushesInfo[event.dynamicBrushName].isEnabled = event.enabled; DynamicBrushesInfo[event.dynamicBrushName].settings = event.settings; //print("SEtting dynamic brush" + JSON.stringify(DynamicBrushesInfo[event.dynamicBrushName])); break; default: break; } } function addAnimationToBrush(entityID) { Object.keys(DynamicBrushesInfo).forEach(function(animationName) { if (DynamicBrushesInfo[animationName].isEnabled) { var prevUserData = Entities.getEntityProperties(entityID).userData; prevUserData = prevUserData == "" ? new Object() : JSON.parse(prevUserData); //preserve other possible user data if (prevUserData.animations == null) { prevUserData.animations = {}; } prevUserData.animations[animationName] = dynamicBrushFactory(animationName, DynamicBrushesInfo[animationName].settings); Entities.editEntity(entityID, {userData: JSON.stringify(prevUserData)}); //Entities.editEntity(entityID, {script: Script.resolvePath("content/brushes/dynamicBrushes/dynamicBrushScript.js")}); } }); } function addElementToUndoStack(item) { var undoDisableEvent = {type: "undoDisable", value: false}; tablet.emitScriptEvent(JSON.stringify(undoDisableEvent)); if (undoStack.length + 1 > UNDO_STACK_SIZE) { undoStack.splice(0, 1); } undoStack.push(item); } function onHmdChanged(isHMDActive) { var HMDInfo = Settings.getValue("wasFingerPaintingWhenHMDClosed", {isHMDActive: true, wasFingerPainting: false}); print("IS HMD " + isHMDActive + " fingerPainting: " + isFingerPainting + " was fingerPainting " + HMDInfo.wasFingerPainting + " TabletID " + HMD.tabletID); // if (isHMDActive && !isFingerPainting && HMDInfo.wasFingerPainting) { only reopen in hmd mode if (!isFingerPainting && HMDInfo.wasFingerPainting) { print("Restarting fingerPaint app!"); shouldRestoreTablet = true; HMD.openTablet(); } if (isHMDActive != HMDInfo.isHMDActive) { //check if state is different as some times it will be the same //onHmdChanged seems to be called twice (once before and once after fingerpaint is over) Settings.setValue("wasFingerPaintingWhenHMDClosed", {isHMDActive: isHMDActive, wasFingerPainting: isFingerPainting}); print("Saving finger painting value " + isFingerPainting); } } function setUp() { print("Setting button..."); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (!tablet) { return; } tablet.webEventReceived.connect(onWebEventReceived); // Tablet button. button = tablet.addButton({ icon: "icons/tablet-icons/finger-paint-i.svg", activeIcon: "icons/tablet-icons/finger-paint-a.svg", text: BUTTON_NAME, isActive: isFingerPainting }); print("button-1 : " + JSON.stringify(button.getProperties())); button.clicked.connect(onButtonClicked); // Track whether tablet is displayed or not. tablet.screenChanged.connect(onTabletScreenChanged); HMD.displayModeChanged.connect(onHmdChanged); } function tearDown() { if (!tablet) { return; } if (isFingerPainting) { isFingerPainting = false; updateHandFunctions(); disableProcessing(); } tablet.screenChanged.disconnect(onTabletScreenChanged); button.clicked.disconnect(onButtonClicked); tablet.removeButton(button); } function getFingerPosition(x, y) { var pickRay = Camera.computePickRay(x, y); return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 5)); } function mouseDrawLine(event){ if (rightBrush && rightBrush.isDrawing()) { print(JSON.stringify(event)); rightBrush.drawLine(getFingerPosition(event.x, event.y), 0.03); } } function mouseStartLine(event){ print(JSON.stringify(event)); if (event.isLeftButton && rightBrush) { rightBrush.startLine(getFingerPosition(event.x, event.y), 0.03); } //won't work until findRayIntersection works with polylines //else if (event.isMiddleButton) { // var pickRay = Camera.computePickRay(event.x, event.y); // var entityToDelete = Entities.findRayIntersection(pickRay, false, [Entities.findEntities(MyAvatar.position, 1000)], []); // print("Entity to DELETE: " + JSON.stringify(entityToDelete)); // var line3d = Overlays.addOverlay("line3d", { // start: pickRay.origin, // end: Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 100)), // color: { red: 255, green: 0, blue: 255}, // lineWidth: 5 // }); // if (entityToDelete.intersects) { // print("Entity to DELETE Properties: " + JSON.stringify(Entities.getEntityProperties(entityToDelete.entityID))); // //Entities.deleteEntity(entityToDelete.entityID); // } //} } function mouseFinishLine(event){ isMouseDrawing = false; if (rightBrush && rightBrush.isDrawing()) { rightBrush.finishLine(getFingerPosition(event.x, event.y), 0.03); } } setUp(); Script.scriptEnding.connect(tearDown); }());