1470 lines
No EOL
57 KiB
JavaScript
1470 lines
No EOL
57 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 = "BODY PAINT",
|
|
// undo vars
|
|
_deleteAllStack = [],
|
|
UNDO_STACK_SIZE = 5,
|
|
_undoStack = [],
|
|
_isFingerPainting = false,
|
|
_isTabletFocused = false,
|
|
_shouldRestoreTablet = false,
|
|
MAX_LINE_WIDTH = 0.036,
|
|
_leftHand = null,
|
|
_rightHand = null,
|
|
_leftBrush = null,
|
|
_rightBrush = null,
|
|
_isBrushColored = false,
|
|
_isLeftHandDominant = false,
|
|
_isMouseDrawing = false,
|
|
_savedSettings = null,
|
|
_isTabletDisplayed = false,
|
|
_paintMode = PAINT_WORLD,
|
|
_nearestAvatarInfo = null,
|
|
_avatarID = null,
|
|
_nearestJointIndex = null,
|
|
// options
|
|
PAINT_WORLD = "paint_world",
|
|
PAINT_SELF = "paint_self",
|
|
PAINT_OTHERS = "paint_others",
|
|
CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint",
|
|
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/animatedBrushes/animatedBrushScript.js"),
|
|
APP_URL = CONTENT_PATH + "/html/main.html";
|
|
|
|
Script.include("/~/system/libraries/controllers.js");
|
|
Script.include("content/js/ColorUtils.js");
|
|
Script.include("content/animatedBrushes/animatedBrushesList.js");
|
|
|
|
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 getNearestAvatarInfo(startPosition) {
|
|
var paintingFingerPosition = startPosition;
|
|
var searchRadius = 2;
|
|
|
|
// [ {1223-123}, {1212-231} ]
|
|
var avatarUUIDList = AvatarList.getAvatarsInRange(paintingFingerPosition, searchRadius).filter(function (uuid) {
|
|
return uuid !== MyAvatar.sessionUUID; // self
|
|
});
|
|
|
|
if (avatarUUIDList.length > 0) {
|
|
|
|
var closestAvatarInfo = null;
|
|
var minDistance;
|
|
|
|
for (var i = 0; i < avatarUUIDList.length; i++) {
|
|
var avatarInfo = AvatarList.getAvatar(avatarUUIDList[i]);
|
|
var position = avatarInfo.position;
|
|
|
|
// check for closest avatar
|
|
var distance = Vec3.distance(position, startPosition);
|
|
if (!minDistance || minDistance > distance) {
|
|
minDistance = distance;
|
|
closestAvatarInfo = avatarInfo;
|
|
}
|
|
}
|
|
|
|
return closestAvatarInfo;
|
|
} else {
|
|
// no avatars found
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
function findClosestJoint(avatarInfo, startPosition) {
|
|
//
|
|
// Iterate through the avatar's joints and return the joint closest to position,
|
|
// excluding some joints so that we don't draw onto eyeballs, etc.
|
|
|
|
var jointsOfInterest = avatarInfo.jointNames;
|
|
|
|
if (avatarInfo.sessionUUID === MyAvatar.sessionUUID) {
|
|
print("DRAWING TO SELF");
|
|
jointsOfInterest = jointsOfInterest.filter(function (jointName) {
|
|
var hand = (_isLeftHandDominant ? "LeftHand" : "RightHand");
|
|
return jointName.indexOf(hand) === -1;
|
|
});
|
|
}
|
|
// var jointsOfInterest = [
|
|
// "Hips",
|
|
// "RightUpLeg",
|
|
// "RightLeg",
|
|
// "LeftUpLeg",
|
|
// "LeftLeg",
|
|
// "RightArm",
|
|
// "RightForeArm",
|
|
// "RightHand",
|
|
// "LeftArm",
|
|
// "LeftForeArm",
|
|
// "LeftHand",
|
|
// "Head",
|
|
// "Body"];
|
|
// var jointsOfInterest = ["Head"];
|
|
|
|
// SIMON
|
|
|
|
// group,
|
|
// GothicMohawkHair.Shape_LOD1,
|
|
// GothicMohawk_CAP.Shape,
|
|
// OutpostUpperWrap_10766R.Shape_LOD1,
|
|
// OutpostHandWrap_10766R.Shape_LOD1,
|
|
// Outpost_Brace_5654R.Shape_LOD1,
|
|
// OutpostShoe_33396R.Shape_LOD1,
|
|
// OutpostSuit_21706.Shape_LOD1,
|
|
// OutpostShades_10816.Shape_LOD1,
|
|
// OutpostBelt_9976.Shape_LOD1,
|
|
// OutpostUpperWrap_10766L.Shape_LOD1,
|
|
// OutpostHandWrap_10766L.Shape_LOD1,
|
|
// Outpost_Brace_5654L.Shape_LOD1,
|
|
// OutpostShoe_33396L.Shape_LOD1,
|
|
// Genesis2Male.Shape_LOD1,
|
|
// G2FSimplifiedEyes_394.Shape_LOD1,
|
|
// Hips,
|
|
// RightUpLeg,
|
|
// RightLeg,
|
|
// RightFoot,
|
|
// RightToeBase,
|
|
// RightToe_End,
|
|
// LeftUpLeg,
|
|
// LeftLeg,
|
|
// LeftFoot,
|
|
// LeftToeBase,
|
|
// LeftToe_End,
|
|
// Spine,
|
|
// Spine1,
|
|
// Spine2,
|
|
// RightShoulder,
|
|
// RightArm,
|
|
// RightForeArm,
|
|
// RightHand,
|
|
// RightHandThumb1,
|
|
// RightHandThumb2,
|
|
// RightHandThumb3,
|
|
// RightHandThumb4,
|
|
// rHandAttachmentPointR,
|
|
// RightHandRing1,
|
|
// RightHandRing2,
|
|
// RightHandRing3,
|
|
// RightHandRing4,
|
|
// RightHandPinky1,
|
|
// RightHandPinky2,
|
|
// RightHandPinky3,
|
|
// RightHandPinky4,
|
|
// RightHandIndex1,
|
|
// RightHandIndex2,
|
|
// RightHandIndex3,
|
|
// RightHandIndex4,
|
|
// RightHandMiddle1,
|
|
// RightHandMiddle2,
|
|
// RightHandMiddle3,
|
|
// RightHandMiddle4,
|
|
// Neck,
|
|
// Head,
|
|
// RightEye,
|
|
// LeftEye,
|
|
// HeadTop_End,
|
|
// LeftShoulder,
|
|
// LeftArm,
|
|
// LeftForeArm,
|
|
// LeftHand,
|
|
// LeftHandThumb1,
|
|
// LeftHandThumb2,
|
|
// LeftHandThumb3,
|
|
// LeftHandThumb4,
|
|
// lHandAttachmentPointL,
|
|
// LeftHandRing1,
|
|
// LeftHandRing2,
|
|
// LeftHandRing3,
|
|
// LeftHandRing4,
|
|
// LeftHandPinky1,
|
|
// LeftHandPinky2,
|
|
// LeftHandPinky3,
|
|
// LeftHandPinky4,
|
|
// LeftHandIndex1,
|
|
// LeftHandIndex2,
|
|
// LeftHandIndex3,
|
|
// LeftHandIndex4,
|
|
// LeftHandMiddle1,
|
|
// LeftHandMiddle2,
|
|
// LeftHandMiddle3,
|
|
// LeftHandMiddle4
|
|
|
|
var closestJointIndex = -1;
|
|
var minDistance;
|
|
|
|
var jointNamePatterns = [
|
|
"hips", "leg", "spine", "arm", "hand", "head", "shoulder", "finger", "wrist", "toe", "hair", "neck"];
|
|
|
|
for (var i = 0; i < jointsOfInterest.length; i++) {
|
|
var jointName = jointsOfInterest[i];
|
|
|
|
var index = avatarInfo.getJointIndex(jointName);
|
|
if (index === -1) {
|
|
continue;
|
|
}
|
|
|
|
var foundMatch = false;
|
|
for (var p = 0; p < jointNamePatterns.length; ++p) {
|
|
if (jointName.match(new RegExp(".*" + jointNamePatterns[p] + ".*", "i"))) {
|
|
foundMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundMatch) {
|
|
continue;
|
|
}
|
|
|
|
// check for closest joint
|
|
var position = avatarInfo.getJointPosition(index);
|
|
var distance = Vec3.distance(position, startPosition);
|
|
if (!minDistance || minDistance > distance) {
|
|
minDistance = distance;
|
|
closestJointIndex = index;
|
|
}
|
|
}
|
|
|
|
// check if collides with another polyline
|
|
|
|
// getEntityProperties() get the parentID and parentJointID
|
|
//
|
|
|
|
// which is closer to use
|
|
return closestJointIndex;
|
|
}
|
|
|
|
function paintBrush(name) {
|
|
// Paints in 3D
|
|
var brushName = name,
|
|
_strokeColor = {
|
|
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,
|
|
_isTriggerWidthEnabled = _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,
|
|
_isUvModeStretch = _savedSettings.currentTexture.brushType == "stretch",
|
|
MIN_STROKE_LENGTH = 0.002, // m
|
|
MIN_STROKE_INTERVAL = 66, // ms
|
|
MAX_POINTS_PER_LINE = 52; // Quick fix for polyline points disappearing issue.
|
|
function calculateStrokeNormal() {
|
|
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 setStrokeColor(red, green, blue) {
|
|
_strokeColor.red = red;
|
|
_strokeColor.green = green;
|
|
_strokeColor.blue = blue;
|
|
}
|
|
|
|
function getStrokeColor() {
|
|
return _strokeColor;
|
|
}
|
|
|
|
function calculateValueInRange(value, min, max, increment) {
|
|
var delta = max - min;
|
|
value += increment;
|
|
if (value > max) {
|
|
return min;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function attacthColorToProperties(properties) {
|
|
// colored brushes should always be white and no effects should be applied
|
|
if (_isBrushColored) {
|
|
properties.color = { red: 255, green: 255, blue: 255 };
|
|
return;
|
|
}
|
|
|
|
var isAnyDynamicEffectEnabled = false;
|
|
if ("dynamicHue" in _dynamicEffects && _dynamicEffects.dynamicHue) {
|
|
isAnyDynamicEffectEnabled = true;
|
|
var hueIncrement = 359.0 / MAX_POINTS_PER_LINE;
|
|
_dynamicColor.hue = calculateValueInRange(_dynamicColor.hue, 0, 359, hueIncrement);
|
|
}
|
|
|
|
if ("dynamicSaturation" in _dynamicEffects && _dynamicEffects.dynamicSaturation) {
|
|
isAnyDynamicEffectEnabled = true;
|
|
_dynamicColor.saturation = _dynamicColor.saturation == 0.5 ? 1.0 : 0.5;
|
|
// saturation along the full line
|
|
// var saturationIncrement = 1.0 / 70.0;
|
|
// _dynamicColor.saturation = calculateValueInRange(_dynamicColor.saturation, 0, 1, saturationIncrement);
|
|
}
|
|
|
|
if ("dynamicValue" in _dynamicEffects && _dynamicEffects.dynamicValue) {
|
|
isAnyDynamicEffectEnabled = true;
|
|
_dynamicColor.value = _dynamicColor.value == 0.6 ? 1.0 : 0.6;
|
|
// value along the full line
|
|
// var saturationIncrement = 1.0 / 70.0;
|
|
// _dynamicColor.saturation = calculateValueInRange(_dynamicColor.saturation, 0, 1, saturationIncrement);
|
|
}
|
|
|
|
|
|
if (!isAnyDynamicEffectEnabled) {
|
|
properties.color = _strokeColor;
|
|
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(dynamicEffects) {
|
|
_dynamicEffects = dynamicEffects;
|
|
}
|
|
|
|
function isDrawingLine() {
|
|
return _isDrawingLine;
|
|
}
|
|
|
|
function setStrokeWidthMultiplier(strokeWidthMultiplier) {
|
|
_strokeWidthMultiplier = strokeWidthMultiplier;
|
|
}
|
|
|
|
function setIsContinuousLine(isContinuousLine) {
|
|
_isContinuousLine = isContinuousLine;
|
|
}
|
|
|
|
function setIsTriggerWidthEnabled(isTriggerWidthEnabled) {
|
|
_isTriggerWidthEnabled = isTriggerWidthEnabled;
|
|
}
|
|
|
|
function setUVMode(isUvModeStretch) {
|
|
_isUvModeStretch = isUvModeStretch;
|
|
}
|
|
|
|
function getStrokeWidth() {
|
|
return _strokeWidthMultiplier;
|
|
}
|
|
|
|
function getEntityID() {
|
|
return _entityID;
|
|
}
|
|
|
|
function setTexture(texture) {
|
|
_texture = texture;
|
|
}
|
|
|
|
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") {
|
|
|
|
handleDeleteEvent(undo);
|
|
|
|
} else if (undo.type == "deleteAll") {
|
|
|
|
var recreateList = undo.data;
|
|
|
|
recreateList.forEach(function (item) {
|
|
handleDeleteEvent(item);
|
|
});
|
|
|
|
} else {
|
|
Entities.deleteEntity(undo.data);
|
|
|
|
// remove from _deleteAllStack
|
|
_deleteAllStack = _deleteAllStack.filter(function (deleteStackItem) {
|
|
return undo.data !== deleteStackItem.data.id;
|
|
});
|
|
|
|
}
|
|
|
|
function handleDeleteEvent(undo) {
|
|
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;
|
|
// _deleteAllStack add the new entity into the stack
|
|
|
|
var deleteStackItem = {
|
|
type: _undoStack[i].type,
|
|
data: Entities.getEntityProperties(newEntity)
|
|
};
|
|
|
|
_deleteAllStack.push(deleteStackItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// function deleteAllPolylines() {
|
|
// while (_undoStack.length > 0) {
|
|
// undo();
|
|
// }
|
|
// }
|
|
|
|
function calculateLineWidth(width) {
|
|
if (_isTriggerWidthEnabled) {
|
|
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 = [calculateStrokeNormal()];
|
|
_strokeColors = [];
|
|
_strokeWidths = [width];
|
|
_timeOfLastPoint = Date.now();
|
|
|
|
// ***
|
|
if (_paintMode === PAINT_OTHERS) {
|
|
_nearestAvatarInfo = getNearestAvatarInfo(position);
|
|
} else if (_paintMode === PAINT_SELF) {
|
|
_nearestAvatarInfo = AvatarList.getAvatar(MyAvatar.sessionUUID);
|
|
}
|
|
|
|
if (_nearestAvatarInfo && _nearestAvatarInfo.sessionUUID) {
|
|
_avatarID = null;
|
|
_nearestJointIndex = null;
|
|
_avatarID = _nearestAvatarInfo.sessionUUID;
|
|
_nearestJointIndex = findClosestJoint(_nearestAvatarInfo, position);
|
|
}
|
|
|
|
// ***
|
|
// add the parent
|
|
// do the joint calculation / check other polylines
|
|
var newEntityProperties = {
|
|
type: "PolyLine",
|
|
name: "fingerPainting",
|
|
shapeType: "box",
|
|
position: position,
|
|
linePoints: _strokePoints,
|
|
normals: _strokeNormals,
|
|
strokeWidths: _strokeWidths,
|
|
textures: _texture, // Daantje
|
|
isUVModeStretch: _isUvModeStretch,
|
|
dimensions: STROKE_DIMENSIONS,
|
|
};
|
|
|
|
// Set on line end
|
|
// if (_nearestAvatarInfo && _nearestAvatarInfo.sessionUUID) {
|
|
// newEntityProperties.parentID = _avatarID;
|
|
// newEntityProperties.parentJointIndex = _nearestJointIndex;
|
|
// }
|
|
|
|
_dynamicColor = rgb2hsv(_strokeColor);
|
|
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);
|
|
_strokeNormals.push(calculateStrokeNormal());
|
|
_strokeWidths.push(width);
|
|
_timeOfLastPoint = Date.now();
|
|
|
|
var editItemProperties = {
|
|
color: _strokeColor,
|
|
linePoints: _strokePoints,
|
|
normals: _strokeNormals,
|
|
strokeWidths: _strokeWidths
|
|
};
|
|
attacthColorToProperties(editItemProperties);
|
|
Entities.editEntity(_entityID, editItemProperties);
|
|
|
|
} else if (_isContinuousLine && _strokePoints.length >= MAX_POINTS_PER_LINE) {
|
|
finishLine(position, width);
|
|
_shouldKeepDrawing = true;
|
|
startLine(_lastPosition, width);
|
|
}
|
|
_lastPosition = position;
|
|
}
|
|
|
|
function finishLine(position, width) {
|
|
// Finish drawing polyline; delete if it has only 1 point.
|
|
var userData = Entities.getEntityProperties(_entityID).userData;
|
|
|
|
if (userData && JSON.parse(userData).animations && !_isBrushColored) {
|
|
Entities.editEntity(_entityID, {
|
|
script: ANIMATION_SCRIPT_PATH
|
|
});
|
|
}
|
|
|
|
// // Set parent on line end
|
|
if (_nearestAvatarInfo && _nearestAvatarInfo.sessionUUID) {
|
|
Entities.editEntity(_entityID, {
|
|
parentID: _avatarID,
|
|
parentJointIndex: _nearestJointIndex
|
|
});
|
|
}
|
|
|
|
width = calculateLineWidth(width);
|
|
|
|
// clean up for next line
|
|
_avatarID = null;
|
|
_nearestJointIndex = null;
|
|
_nearestAvatarInfo = null;
|
|
|
|
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 });
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
function tearDown() {
|
|
cancelLine();
|
|
}
|
|
|
|
return {
|
|
name: name,
|
|
startLine: startLine,
|
|
drawLine: drawLine,
|
|
finishLine: finishLine,
|
|
cancelLine: cancelLine,
|
|
eraseClosestLine: eraseClosestLine,
|
|
tearDown: tearDown,
|
|
setStrokeColor: setStrokeColor,
|
|
setStrokeWidthMultiplier: setStrokeWidthMultiplier,
|
|
setTexture: setTexture,
|
|
isDrawingLine: isDrawingLine,
|
|
undo: undo,
|
|
getStrokeColor: getStrokeColor,
|
|
getStrokeWidth: getStrokeWidth,
|
|
getEntityID: getEntityID,
|
|
setUVMode: setUVMode,
|
|
setIsTriggerWidthEnabled: setIsTriggerWidthEnabled,
|
|
setDynamicEffects: setDynamicEffects,
|
|
setIsContinuousLine: setIsContinuousLine
|
|
};
|
|
}
|
|
|
|
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;
|
|
|
|
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");
|
|
|
|
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
|
|
triggerPressedCallback(fingerTipPosition, lineWidth);
|
|
} else if (wasTriggerPressed && isTriggerPressed) {
|
|
triggerPressingCallback(fingerTipPosition, lineWidth);
|
|
} else {
|
|
triggerReleasedCallback(fingerTipPosition, lineWidth);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
if (_inkSourceOverlay) {
|
|
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() {
|
|
if (HMD.tabletID
|
|
&& (((_leftBrush == null || _rightBrush == null)
|
|
|| (!_leftBrush.isDrawingLine() && !_rightBrush.isDrawingLine())))) {
|
|
|
|
checkTabletHasFocus();
|
|
}
|
|
|
|
updateTriggerPress();
|
|
updateGripPress();
|
|
}
|
|
|
|
function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) {
|
|
triggerPressedCallback = onTriggerPressed;
|
|
triggerPressingCallback = onTriggerPressing;
|
|
triggerReleasedCallback = onTriggerReleased;
|
|
gripPressedCallback = onGripPressed;
|
|
}
|
|
|
|
function tearDown() {
|
|
// Nothing to do.
|
|
}
|
|
|
|
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_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({
|
|
pointIndex: !enabled
|
|
}), true);
|
|
}
|
|
|
|
function updateHandAnimations() {
|
|
if (_leftBrush == null) {
|
|
return;
|
|
}
|
|
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() {
|
|
print("INFO: Pause Processing");
|
|
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() {
|
|
print("INFO: Resume Processing");
|
|
// 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() {
|
|
print("INFO: Star Processing");
|
|
// Connect controller API to handController objects.
|
|
_leftHand = handController("left");
|
|
_rightHand = handController("right");
|
|
|
|
if (_savedSettings == null) {
|
|
restoreLastValues();
|
|
}
|
|
// 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);
|
|
}
|
|
|
|
function disableProcessing() {
|
|
print("INFO: Disable Processing");
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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.currentIsBrushColored = Settings.getValue("currentIsBrushColored", false);
|
|
_savedSettings.currentHeadersCollapsedStatus = Settings.getValue("currentHeadersCollapsedStatus", new Object());
|
|
_savedSettings.undoDisable = _undoStack.length == 0;
|
|
// set some global variables
|
|
_isLeftHandDominant = _savedSettings.currentDrawingHand;
|
|
_isBrushColored = _savedSettings.currentIsBrushColored;
|
|
}
|
|
|
|
function onButtonClicked() {
|
|
restoreLastValues();
|
|
var wasFingerPainting = _isFingerPainting;
|
|
_isFingerPainting = !_isFingerPainting;
|
|
|
|
if (!_isFingerPainting) {
|
|
tablet.gotoHomeScreen();
|
|
}
|
|
button.editProperties({ isActive: _isFingerPainting });
|
|
|
|
if (wasFingerPainting) {
|
|
_leftBrush.cancelLine();
|
|
_rightBrush.cancelLine();
|
|
}
|
|
|
|
if (_isFingerPainting) {
|
|
tablet.gotoWebScreen(APP_URL + "?" + encodeURIComponent(JSON.stringify(_savedSettings)));
|
|
HMD.openTablet();
|
|
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 onTabletShownChanged() {
|
|
if (_shouldRestoreTablet && tablet.tabletShown) {
|
|
_shouldRestoreTablet = false;
|
|
_isTabletFocused = false;
|
|
_isFingerPainting = false;
|
|
HMD.openTablet();
|
|
onButtonClicked();
|
|
HMD.openTablet();
|
|
}
|
|
}
|
|
|
|
function onTabletScreenChanged(type, url) {
|
|
var TABLET_SCREEN_CLOSED = "Closed";
|
|
var TABLET_SCREEN_HOME = "Home";
|
|
var TABLET_SCREEN_WEB = "Web";
|
|
|
|
_isTabletDisplayed = type !== TABLET_SCREEN_CLOSED;
|
|
_isFingerPainting = type === TABLET_SCREEN_WEB && url.indexOf("html/main.html") > -1;
|
|
if (!_isFingerPainting) {
|
|
disableProcessing();
|
|
updateHandFunctions();
|
|
}
|
|
button.editProperties({ isActive: _isFingerPainting });
|
|
}
|
|
|
|
|
|
function onWebEventReceived(event) {
|
|
|
|
if (typeof event === "string") {
|
|
event = JSON.parse(event);
|
|
}
|
|
|
|
if (_leftBrush == null) {
|
|
print("ERROR: Painting not enabled.");
|
|
return;
|
|
}
|
|
|
|
switch (event.type) {
|
|
case "appReady":
|
|
_isTabletFocused = false; // make sure we can set the focus on the tablet again
|
|
break;
|
|
|
|
case "reloadPage":
|
|
restoreLastValues();
|
|
tablet.gotoWebScreen(APP_URL + "?" + encodeURIComponent(JSON.stringify(_savedSettings)));
|
|
break;
|
|
|
|
case "changeTab":
|
|
Settings.setValue("currentTab", event.currentTab);
|
|
break;
|
|
|
|
case "changePaintMode":
|
|
var option = event.option;
|
|
|
|
if (option === PAINT_SELF || option === PAINT_OTHERS) {
|
|
_paintMode = option;
|
|
} else {
|
|
_paintMode = PAINT_WORLD;
|
|
}
|
|
_paintMode = option;
|
|
|
|
break;
|
|
|
|
case "deleteAll":
|
|
|
|
if (_deleteAllStack.length > 0) {
|
|
|
|
// delete each entity inside _deleteAllStack
|
|
var deleteList = _deleteAllStack.slice();
|
|
|
|
for (var i = 0; i < _deleteAllStack.length; i++) {
|
|
var toDelete = _deleteAllStack[i];
|
|
|
|
Entities.deleteEntity(toDelete.data.id);
|
|
}
|
|
|
|
// add deleteAll item to undo stack
|
|
addElementToUndoStack({ type: "deleteAll", data: deleteList });
|
|
_deleteAllStack = [];
|
|
|
|
}
|
|
break;
|
|
|
|
case "changeColor":
|
|
if (_leftBrush != null && _rightBrush != null) {
|
|
if (!_isBrushColored) {
|
|
var setStrokeColorEvent = { type: "changeStrokeColor", value: event };
|
|
tablet.emitScriptEvent(JSON.stringify(setStrokeColorEvent));
|
|
|
|
Settings.setValue("currentColor", event);
|
|
_leftBrush.setStrokeColor(event.red, event.green, event.blue);
|
|
_rightBrush.setStrokeColor(event.red, event.green, event.blue);
|
|
|
|
Overlays.editOverlay(_inkSourceOverlay, {
|
|
color: { red: event.red, green: event.green, blue: event.blue }
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "switchTriggerPressureWidth":
|
|
if (_leftBrush != null && _rightBrush != null) {
|
|
Settings.setValue("currentTriggerWidthEnabled", event.enabled);
|
|
_leftBrush.setIsTriggerWidthEnabled(event.enabled);
|
|
_rightBrush.setIsTriggerWidthEnabled(event.enabled);
|
|
}
|
|
break;
|
|
|
|
case "addCustomColor":
|
|
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 "switchCollapsed":
|
|
var collapsedStatus = Settings.getValue("currentHeadersCollapsedStatus", new Object());
|
|
collapsedStatus[event.sectionId] = event.isCollapsed;
|
|
Settings.setValue("currentHeadersCollapsedStatus", collapsedStatus);
|
|
break;
|
|
|
|
case "changeBrush":
|
|
if (_leftBrush != null && _rightBrush != null) {
|
|
Settings.setValue("currentTexture", event);
|
|
Settings.setValue("currentIsBrushColored", event.isColored);
|
|
|
|
if (event.brushType === "repeat") {
|
|
_leftBrush.setUVMode(false);
|
|
_rightBrush.setUVMode(false);
|
|
|
|
} else if (event.brushType === "stretch") {
|
|
_leftBrush.setUVMode(true);
|
|
_rightBrush.setUVMode(true);
|
|
}
|
|
|
|
_isBrushColored = event.isColored;
|
|
event.brushName = Script.resolvePath(event.brushName);
|
|
_leftBrush.setTexture(event.brushName);
|
|
_rightBrush.setTexture(event.brushName);
|
|
}
|
|
break;
|
|
|
|
case "changeLineWidth":
|
|
if (_leftBrush != null && _rightBrush != null) {
|
|
Settings.setValue("currentStrokeWidth", event.brushWidth);
|
|
var dim = event.brushWidth * 2 + 0.1;
|
|
_leftBrush.setStrokeWidthMultiplier(dim);
|
|
_rightBrush.setStrokeWidthMultiplier(dim);
|
|
Overlays.editOverlay(_inkSourceOverlay, {
|
|
size: dim * 0.06
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "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.
|
|
if (_leftBrush != null && _rightBrush != null) {
|
|
_rightBrush.undo();
|
|
}
|
|
break;
|
|
|
|
case "changeBrushHand":
|
|
Settings.setValue("currentDrawingHand", event.DrawingHand == "left");
|
|
_isLeftHandDominant = event.DrawingHand == "left";
|
|
_isTabletFocused = false;
|
|
updateHandAnimations();
|
|
break;
|
|
|
|
case "switchAnimatedBrush":
|
|
var animatedBrushes = Settings.getValue("currentAnimatedBrushes", []);
|
|
var brushSettingsIndex = animatedBrushes.indexOf(event.animatedBrushID);
|
|
if (!event.enabled && brushSettingsIndex > -1) { // already exists so we are disabling it
|
|
animatedBrushes.splice(brushSettingsIndex, 1);
|
|
} else if (event.enabled && brushSettingsIndex == -1) { // doesn't exist yet so we are just adding it
|
|
animatedBrushes.push(event.animatedBrushID);
|
|
}
|
|
Settings.setValue("currentAnimatedBrushes", animatedBrushes);
|
|
print("Setting animated brushes: " + JSON.stringify(animatedBrushes));
|
|
AnimatedBrushesInfo[event.animatedBrushName].isEnabled = event.enabled;
|
|
AnimatedBrushesInfo[event.animatedBrushName].settings = event.settings;
|
|
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.setIsContinuousLine(event.enabled);
|
|
_leftBrush.setIsContinuousLine(event.enabled);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
function addAnimationToBrush(entityID) {
|
|
Object.keys(AnimatedBrushesInfo).forEach(function (animationName) {
|
|
if (AnimatedBrushesInfo[animationName].isEnabled) {
|
|
var prevUserData = Entities.getEntityProperties(entityID).userData;
|
|
// preserve other possible user data
|
|
prevUserData = prevUserData == "" ? new Object() : JSON.parse(prevUserData);
|
|
if (prevUserData.animations == null) {
|
|
prevUserData.animations = {};
|
|
}
|
|
|
|
prevUserData.animations[animationName] = animatedBrushFactory(animationName,
|
|
AnimatedBrushesInfo[animationName].settings, entityID);
|
|
|
|
Entities.editEntity(entityID, { userData: JSON.stringify(prevUserData) });
|
|
}
|
|
});
|
|
}
|
|
|
|
function addElementToUndoStack(item) {
|
|
var undoDisableEvent = { type: "undoDisable", value: false };
|
|
tablet.emitScriptEvent(JSON.stringify(undoDisableEvent));
|
|
|
|
// no limit to the undo stack size
|
|
// if (_undoStack.length + 1 > UNDO_STACK_SIZE) {
|
|
// _undoStack.splice(0, 1);
|
|
// }
|
|
if (item.type == "created") {
|
|
|
|
var deleteAllStackItem = {
|
|
type: item.type,
|
|
data: Entities.getEntityProperties(item.data)
|
|
};
|
|
|
|
_deleteAllStack.push(deleteAllStackItem);
|
|
|
|
} else if (item.type == "deleted") {
|
|
|
|
_deleteAllStack = _deleteAllStack.filter(function (deleteStackItem) {
|
|
return item.data.id !== deleteStackItem.data.id;
|
|
});
|
|
|
|
}
|
|
|
|
_undoStack.push(item);
|
|
}
|
|
|
|
function onHmdChanged(isHMDActive) {
|
|
var wasHMDActive = Settings.getValue("wasHMDActive", null);
|
|
if (isHMDActive != wasHMDActive) {
|
|
Settings.setValue("wasHMDActive", isHMDActive);
|
|
if (wasHMDActive == null) {
|
|
return;
|
|
} else {
|
|
if (_isFingerPainting) {
|
|
_shouldRestoreTablet = true;
|
|
|
|
// Make sure the tablet is being shown when we try to change the window
|
|
while (!tablet.tabletShown) {
|
|
HMD.openTablet();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function setUp() {
|
|
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
|
|
});
|
|
|
|
button.clicked.connect(onButtonClicked);
|
|
// Track whether tablet is displayed or not.
|
|
tablet.screenChanged.connect(onTabletScreenChanged);
|
|
tablet.tabletShownChanged.connect(onTabletShownChanged);
|
|
HMD.displayModeChanged.connect(onHmdChanged);
|
|
}
|
|
|
|
function tearDown() {
|
|
if (!tablet) {
|
|
return;
|
|
}
|
|
|
|
if (_isFingerPainting) {
|
|
_isFingerPainting = false;
|
|
updateHandFunctions();
|
|
disableProcessing();
|
|
}
|
|
|
|
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
|
tablet.tabletShownChanged.disconnect(onTabletShownChanged);
|
|
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.isDrawingLine()) {
|
|
_rightBrush.drawLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH);
|
|
}
|
|
}
|
|
|
|
function mouseStartLine(event) {
|
|
if (event.isLeftButton && _rightBrush && !_rightBrush.isDrawingLine()) {
|
|
_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.isDrawingLine()) {
|
|
_rightBrush.finishLine(getFingerPosition(event.x, event.y), MAX_LINE_WIDTH);
|
|
}
|
|
}
|
|
|
|
setUp();
|
|
Script.scriptEnding.connect(tearDown);
|
|
}()); |