overte-HifiExperiments/scripts/system/fingerPaint/fingerPaint.js
Artur Gomes 83cdd1739d Add hue, saturation and brightness dynamic brushes.
Add continuous drawing mode.
Add new brushes.
Remove some brushes.
Fix animation play distance for when painting in Desktop mode.
Fix Hue animation not starting from the chosen color (would always start
from red).
Fix multiple color polyline.
2017-08-16 18:35:15 +01:00

1260 lines
No EOL
52 KiB
JavaScript

//
// 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,
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/animatedBrushes/animatedBrushScript.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/ColorUtils2.js");
Script.include("content/brushes/animatedBrushes/animatedBrushesList.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 = {
red: savedSettings.currentColor.red,
green: savedSettings.currentColor.green,
blue: savedSettings.currentColor.blue
},
dynamicColor = null,
ERASE_SEARCH_RADIUS = 0.1, // m
STROKE_DIMENSIONS = { x: 10, y: 10, z: 10 },
isDrawingLine = false,
isTriggerPressureWidthEnabled = savedSettings.currentTriggerWidthEnabled,
entityID,
basePosition,
strokePoints,
strokeNormals,
strokeColors,
strokeWidths,
timeOfLastPoint,
isContinuousLine = savedSettings.currentIsContinuous,
lastPosition = null,
shouldKeepDrawing = false,
texture = CONTENT_PATH + "/" + savedSettings.currentTexture.brushName,
dynamicEffects = savedSettings.currentDynamicBrushes;
//'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() {
if (!isMouseDrawing) {
var controllerPose = isLeftHandDominant
? getControllerWorldLocation(Controller.Standard.LeftHand, true)
: getControllerWorldLocation(Controller.Standard.RightHand, true);
var fingerTipRotation = controllerPose.rotation;
return Quat.getUp(fingerTipRotation);
} else {
return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z);
}
}
function changeStrokeColor(red, green, blue) {
STROKE_COLOR.red = red;
STROKE_COLOR.green = green;
STROKE_COLOR.blue = blue;
}
function getStrokeColor() {
return STROKE_COLOR;
}
function nextValueInRange(value, min, max, increment) {
var delta = max - min;
value += increment;
if (value > max) {
return min;
} else {
return value;
}
}
function attacthColorToProperties(properties) {
var isAnyDynamicEffectEnabled = false;
if ("dynamicHue" in dynamicEffects && dynamicEffects.dynamicHue) {
isAnyDynamicEffectEnabled = true;
var hueIncrement = 359.0 / 70.0;
dynamicColor.hue = nextValueInRange(dynamicColor.hue, 0, 359, hueIncrement);
}
if ("dynamicSaturation" in dynamicEffects && dynamicEffects.dynamicSaturation) {
isAnyDynamicEffectEnabled = true;
dynamicColor.saturation = dynamicColor.saturation == 0.2 ? 0.8 : 0.2;
/*var saturationIncrement = 1.0 / 70.0;
dynamicColor.saturation = nextValueInRange(dynamicColor.saturation, 0, 1, saturationIncrement);*/
}
if ("dynamicValue" in dynamicEffects && dynamicEffects.dynamicValue) {
isAnyDynamicEffectEnabled = true;
dynamicColor.value = dynamicColor.value == 0.2 ? 0.8 : 0.2;
/*var saturationIncrement = 1.0 / 70.0;
dynamicColor.saturation = nextValueInRange(dynamicColor.saturation, 0, 1, saturationIncrement);*/
}
if (!isAnyDynamicEffectEnabled) {
properties.color = STROKE_COLOR;
return;
}
var newRgbColor = hsv2rgb(dynamicColor);
strokeColors.push({
x: newRgbColor.red/255.0,
y: newRgbColor.green/255.0,
z: newRgbColor.blue/255.0}
);
properties.strokeColors = strokeColors;
}
function setDynamicEffects(newDynamicEffects) {
dynamicEffects = newDynamicEffects;
}
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 switchContinuousLine(isContinuous) {
isContinuousLine = isContinuous;
}
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.
}
if (shouldKeepDrawing) {
strokePoints = [Vec3.distance(basePosition, strokePoints[strokePoints.length - 1])];
} else {
strokePoints = [Vec3.ZERO];
}
basePosition = position;
strokeNormals = [strokeNormal()];
strokeColors = [];
strokeWidths = [width];
timeOfLastPoint = Date.now();
var newEntityProperties = {
type: "PolyLine",
name: "fingerPainting",
shapeType: "box",
position: position,
linePoints: strokePoints,
normals: strokeNormals,
strokeWidths: strokeWidths,
textures: texture, // Daantje
isUVModeStretch: IS_UV_MODE_STRETCH,
dimensions: STROKE_DIMENSIONS,
};
dynamicColor = rgb2hsv(STROKE_COLOR);
attacthColorToProperties(newEntityProperties);
entityID = Entities.addEntity(newEntityProperties);
isDrawingLine = true;
addAnimationToBrush(entityID);
lastPosition = position;
}
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);
//strokeColors.push({x: STROKE_COLOR.red/255.0, y: STROKE_COLOR.green/255.0, z: STROKE_COLOR.blue/255.0});
strokeNormals.push(strokeNormal());
strokeWidths.push(width);
timeOfLastPoint = Date.now();
var editItemProperties = {
color: STROKE_COLOR,
linePoints: strokePoints,
normals: strokeNormals,
strokeWidths: strokeWidths
};
attacthColorToProperties(editItemProperties);
Entities.editEntity(entityID, editItemProperties);
} else if (isContinuousLine && strokePoints.length >= MAX_POINTS_PER_LINE) {
print("restarting to draw line");
finishLine(position, width);
shouldKeepDrawing = true;
startLine(lastPosition, width);
}
if (strokePoints.length >= MAX_POINTS_PER_LINE)
print("restarting to draw linea asdasdadwe");
lastPosition = position;
}
function finishLine(position, width) {
// Finish drawing polyline; delete if it has only 1 point.
//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;
shouldKeepDrawing = 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,
setDynamicEffects: setDynamicEffects
};
}
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;
Overlays.editOverlay(inkSourceOverlay, {visible: false});
updateHandAnimations();
pauseProcessing();
}
} else {
if (isTabletFocused) {
isTabletFocused = false;
Overlays.editOverlay(inkSourceOverlay, {visible: true});
resumeProcessing();
updateHandFunctions();
}
};
}
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);
// }
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() {
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.currentAnimatedBrushes = Settings.getValue("currentAnimatedBrushes", []);
savedSettings.customColors = Settings.getValue("customColors", []);
savedSettings.currentTab = Settings.getValue("currentTab", 0);
savedSettings.currentTriggerWidthEnabled = Settings.getValue("currentTriggerWidthEnabled", true);
savedSettings.currentDynamicBrushes = Settings.getValue("currentDynamicBrushes", new Object());
savedSettings.currentIsContinuous = Settings.getValue("currentIsContinuous", false);
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";
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 "switchAnimatedBrush":
var animatedBrushes = Settings.getValue("currentAnimatedBrushes", []);
var brushSettingsIndex = animatedBrushes.indexOf(event.animatedBrushID);
if (brushSettingsIndex > -1) { //already exists so we are disabling it
animatedBrushes.splice(brushSettingsIndex, 1);
} else { //doesn't exist yet so we are just adding it
animatedBrushes.push(event.animatedBrushID);
}
Settings.setValue("currentAnimatedBrushes", animatedBrushes);
AnimatedBrushesInfo[event.animatedBrushName].isEnabled = event.enabled;
AnimatedBrushesInfo[event.animatedBrushName].settings = event.settings;
//print("SEtting animated brush" + JSON.stringify(AnimatedBrushesInfo[event.animatedBrushName]));
break;
case "switchDynamicBrush":
var dynamicBrushes = Settings.getValue("currentDynamicBrushes", new Object());
dynamicBrushes[event.dynamicBrushId] = event.enabled;
Settings.setValue("currentDynamicBrushes", dynamicBrushes);
rightBrush.setDynamicEffects(dynamicBrushes);
leftBrush.setDynamicEffects(dynamicBrushes);
break;
case "switchIsContinuous":
Settings.setValue("currentIsContinuous", event.enabled);
rightBrush.switchIsContinuous(event.enabled);
leftBrush.switchIsContinuous(event.enabled);
break;
default:
break;
}
}
function addAnimationToBrush(entityID) {
Object.keys(AnimatedBrushesInfo).forEach(function(animationName) {
print(animationName);
if (AnimatedBrushesInfo[animationName].isEnabled) {
//print("Adding animation " + animationName);
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] = animatedBrushFactory(animationName,
AnimatedBrushesInfo[animationName].settings, entityID);
Entities.editEntity(entityID, {userData: JSON.stringify(prevUserData)});
//print("Added animation: " + JSON.stringify(Entities.getEntityProperties(entityID)));
}
});
}
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});
// 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()) {
rightBrush.drawLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH);
}
}
function mouseStartLine(event){
//print(JSON.stringify(event));
if (event.isLeftButton && rightBrush) {
isMouseDrawing = true;
rightBrush.startLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH);
}
//Note: 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), MAX_LINE_WIDTH);
}
}
setUp();
Script.scriptEnding.connect(tearDown);
}());