Add audio and haptic feedback

This commit is contained in:
David Rowe 2017-09-01 14:30:22 +12:00
parent 8205b0d9d2
commit 734ce66df3
12 changed files with 121 additions and 11 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -289,6 +289,10 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
setHand(side);
function otherHand(side) {
return (side + 1) % 2;
}
function getEntityIDs() {
return [palettePanelOverlay, paletteHeaderHeadingOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays);
}
@ -313,6 +317,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
// Highlight and raise new item.
if (itemIndex !== NONE && highlightedItem !== itemIndex) {
Feedback.play(side, Feedback.HOVER_BUTTON);
Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], {
localPosition: UIT.dimensions.paletteItemButtonHoveredOffset,
visible: true
@ -324,6 +329,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
isTriggerClicked = controlHand.triggerClicked();
if (highlightedItem !== NONE && isTriggerClicked && !wasTriggerClicked) {
// Create entity.
Feedback.play(otherHand(side), Feedback.CREATE_ENTITY);
properties = Object.clone(PALETTE_ITEMS[itemIndex].entity);
properties.position = Vec3.sum(controlHand.palmPosition(),
Vec3.multiplyQbyV(controlHand.orientation(),

View file

@ -0,0 +1,72 @@
//
// feedback.js
//
// Created by David Rowe on 31 Aug 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
//
/* global Feedback */
Feedback = (function () {
// Provide audio and haptic user feedback.
// Global object.
"use strict";
var DROP_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/drop.wav")),
DELETE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/delete.wav")),
SELECT_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/select.wav")),
CLONE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/clone.wav")),
CREATE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/create.wav")),
EQUIP_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/equip.wav")),
ERROR_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/error.wav")),
FEEDBACK_PARAMETERS = {
DROP_TOOL: { sound: DROP_SOUND, volume: 0.5, haptic: 0.75 },
DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 },
SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool.
CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 },
CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.2, haptic: 0.2 },
HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.2 }, // Tools menu.
HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.1 }, // Tools options and Create palette items.
EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.5, haptic: 0.6 },
APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 },
APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }
},
VOLUME_MULTPLIER = 0.5, // Resulting volume range should be within 0.0 - 1.0.
HAPTIC_STRENGTH_MULTIPLIER = 2.0, // Resulting strength range should be within 0.0 - 1.0.
HAPTIC_LENGTH_MULTIPLIER = 50.0; // Resulting length range should be within 0 - 50, say.
function play(side, item) {
var parameters = FEEDBACK_PARAMETERS[item];
if (parameters.sound) {
Audio.playSound(parameters.sound, {
position: side ? MyAvatar.getRightPalmPosition() : MyAvatar.getLeftPalmPosition(),
volume: parameters.volume * VOLUME_MULTPLIER,
localOnly: true
});
}
Controller.triggerHapticPulse(parameters.haptic * HAPTIC_STRENGTH_MULTIPLIER,
parameters.haptic * HAPTIC_LENGTH_MULTIPLIER, side);
}
return {
DROP_TOOL: "DROP_TOOL",
DELETE_ENTITY: "DELETE_ENTITY",
SELECT_ENTITY: "SELECT_ENTITY",
CLONE_ENTITY: "CLONE_ENTITY",
CREATE_ENTITY: "CREATE_ENTITY",
HOVER_MENU_ITEM: "HOVER_MENU_ITEM",
HOVER_BUTTON: "HOVER_BUTTON",
EQUIP_TOOL: "EQUIP_TOOL",
APPLY_PROPERTY: "APPLY_PROPERTY",
APPLY_ERROR: "APPLY_ERROR",
play: play
};
}());

View file

@ -365,7 +365,7 @@ Selection = function (side) {
function applyColor(color, isApplyToAll) {
// Entities without a color property simply ignore the edit.
var properties,
isError = true,
isOK = false,
i,
length;
@ -376,7 +376,7 @@ Selection = function (side) {
Entities.editEntity(selection[i].id, {
color: color
});
isError = false;
isOK = true;
}
}
} else {
@ -385,14 +385,11 @@ Selection = function (side) {
Entities.editEntity(intersectedEntityID, {
color: color
});
isError = false;
isOK = true;
}
}
if (isError) {
// TODO
print("TODO: Error beep");
}
return isOK;
}
function getColor(entityID) {
@ -401,8 +398,6 @@ Selection = function (side) {
properties = Entities.getEntityProperties(entityID, "color");
if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) === -1) {
// Some entities don't have a color property.
// TODO
print("TODO: Error beep");
return null;
}

View file

@ -2726,6 +2726,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
doCommand("clearTool");
} else if (!isOptionsHeadingRaised) {
// Hover heading.
Feedback.play(side, Feedback.HOVER_BUTTON);
Overlays.editOverlay(menuHeaderHeadingOverlay, {
localPosition: Vec3.sum(MENU_HEADER_HEADING_PROPERTIES.localPosition, MENU_HEADER_HOVER_OFFSET),
color: UIT.colors.greenHighlight,
@ -2872,6 +2873,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
// Hover new item.
switch (highlightedElementType) {
case "menuButton":
Feedback.play(side, Feedback.HOVER_MENU_ITEM);
Overlays.editOverlay(menuHoverOverlays[highlightedItem], {
localPosition: Vec3.sum(UI_ELEMENTS.menuButton.hoverButton.properties.localPosition, MENU_HOVER_DELTA),
visible: true
@ -2879,6 +2881,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
break;
case "button":
if (intersectionEnabled[highlightedItem]) {
Feedback.play(side, Feedback.HOVER_BUTTON);
localPosition = intersectionItems[highlightedItem].properties.localPosition;
Overlays.editOverlay(intersectionOverlays[highlightedItem], {
color: intersectionItems[highlightedItem].highlightColor !== undefined
@ -2890,6 +2893,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
break;
case "toggleButton":
if (intersectionEnabled[highlightedItem]) {
Feedback.play(side, Feedback.HOVER_BUTTON);
localPosition = intersectionItems[highlightedItem].properties.localPosition;
Overlays.editOverlay(intersectionOverlays[highlightedItem], {
color: optionsToggles[intersectionItems[highlightedItem].id]
@ -2900,6 +2904,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
}
break;
case "swatch":
Feedback.play(side, Feedback.HOVER_BUTTON);
localPosition = intersectionItems[highlightedItem].properties.localPosition;
if (optionsSettings[intersectionItems[highlightedItem].id].value === "") {
// Swatch is empty; highlight it with current color.
@ -2924,12 +2929,14 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
case "barSlider":
case "imageSlider":
case "colorCircle":
Feedback.play(side, Feedback.HOVER_BUTTON);
localPosition = intersectionItems[highlightedItem].properties.localPosition;
Overlays.editOverlay(intersectionOverlays[highlightedItem], {
localPosition: Vec3.sum(localPosition, OPTION_HOVER_DELTA)
});
break;
case "picklist":
Feedback.play(side, Feedback.HOVER_BUTTON);
if (!isPicklistOpen) {
localPosition = intersectionItems[highlightedItem].properties.localPosition;
Overlays.editOverlay(intersectionOverlays[highlightedItem], {
@ -2943,6 +2950,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
}
break;
case "picklistItem":
Feedback.play(side, Feedback.HOVER_BUTTON);
Overlays.editOverlay(intersectionOverlays[highlightedItem], {
color: UIT.colors.greenHighlight
});

View file

@ -73,6 +73,7 @@
// Modules
Script.include("./modules/createPalette.js");
Script.include("./modules/feedback.js");
Script.include("./modules/groups.js");
Script.include("./modules/hand.js");
Script.include("./modules/handles.js");
@ -756,6 +757,7 @@
}
function enterEditorCloning() {
Feedback.play(side, Feedback.CLONE_ENTITY);
selection.select(intersectedEntityID); // For when transitioning from EDITOR_SEARCHING.
selection.cloneEntities();
intersectedEntityID = selection.intersectedEntityID();
@ -771,6 +773,7 @@
if (!grouping.includes(rootEntityID)) {
highlights.display(false, selection.selection(), null, highlights.GROUP_COLOR);
}
Feedback.play(side, Feedback.SELECT_ENTITY);
grouping.toggle(selection.selection());
}
@ -838,6 +841,7 @@
function updateTool() {
if (!wasGripClicked && isGripClicked && (toolSelected !== TOOL_NONE)) {
Feedback.play(side, Feedback.DROP_TOOL);
toolSelected = TOOL_NONE;
grouping.clear();
ui.clearTool();
@ -900,12 +904,18 @@
setState(EDITOR_GROUPING);
} else if (toolSelected === TOOL_COLOR) {
setState(EDITOR_HIGHLIGHTING);
selection.applyColor(colorToolColor, false);
if (selection.applyColor(colorToolColor, false)) {
Feedback.play(side, Feedback.APPLY_PROPERTY);
} else {
Feedback.play(side, Feedback.APPLY_ERROR);
}
} else if (toolSelected === TOOL_PICK_COLOR) {
color = selection.getColor(intersection.entityID);
if (color) {
colorToolColor = color;
ui.doPickColor(colorToolColor);
} else {
Feedback.play(side, Feedback.APPLY_ERROR);
}
toolSelected = TOOL_COLOR;
ui.setToolIcon(ui.COLOR_TOOL);
@ -914,6 +924,7 @@
selection.applyPhysics(physicsToolPhysics);
} else if (toolSelected === TOOL_DELETE) {
setState(EDITOR_HIGHLIGHTING);
Feedback.play(side, Feedback.DELETE_ENTITY);
selection.deleteEntities();
setState(EDITOR_SEARCHING);
} else {
@ -981,18 +992,25 @@
} else if (toolSelected === TOOL_GROUP) {
setState(EDITOR_GROUPING);
} else if (toolSelected === TOOL_COLOR) {
selection.applyColor(colorToolColor, false);
if (selection.applyColor(colorToolColor, false)) {
Feedback.play(side, Feedback.APPLY_PROPERTY);
} else {
Feedback.play(side, Feedback.APPLY_ERROR);
}
} else if (toolSelected === TOOL_PICK_COLOR) {
color = selection.getColor(intersection.entityID);
if (color) {
colorToolColor = color;
ui.doPickColor(colorToolColor);
} else {
Feedback.play(side, Feedback.APPLY_ERROR);
}
toolSelected = TOOL_COLOR;
ui.setToolIcon(ui.COLOR_TOOL);
} else if (toolSelected === TOOL_PHYSICS) {
selection.applyPhysics(physicsToolPhysics);
} else if (toolSelected === TOOL_DELETE) {
Feedback.play(side, Feedback.DELETE_ENTITY);
selection.deleteEntities();
setState(EDITOR_SEARCHING);
} else {
@ -1027,6 +1045,7 @@
}
} else if (isGripClicked) {
if (!wasGripClicked) {
Feedback.play(side, Feedback.DELETE_ENTITY);
selection.deleteEntities();
setState(EDITOR_SEARCHING);
}
@ -1328,23 +1347,27 @@
function onUICommand(command, parameter) {
switch (command) {
case "scaleTool":
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
grouping.clear();
toolSelected = TOOL_SCALE;
ui.setToolIcon(ui.SCALE_TOOL);
ui.updateUIEntities();
break;
case "cloneTool":
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
grouping.clear();
toolSelected = TOOL_CLONE;
ui.setToolIcon(ui.CLONE_TOOL);
ui.updateUIEntities();
break;
case "groupTool":
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
toolSelected = TOOL_GROUP;
ui.setToolIcon(ui.GROUP_TOOL);
ui.updateUIEntities();
break;
case "colorTool":
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
grouping.clear();
toolSelected = TOOL_COLOR;
ui.setToolIcon(ui.COLOR_TOOL);
@ -1357,24 +1380,28 @@
toolSelected = TOOL_PICK_COLOR;
ui.updateUIEntities();
} else {
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
grouping.clear();
toolSelected = TOOL_COLOR;
ui.updateUIEntities();
}
break;
case "physicsTool":
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
grouping.clear();
toolSelected = TOOL_PHYSICS;
ui.setToolIcon(ui.PHYSICS_TOOL);
ui.updateUIEntities();
break;
case "deleteTool":
Feedback.play(dominantHand, Feedback.EQUIP_TOOL);
grouping.clear();
toolSelected = TOOL_DELETE;
ui.setToolIcon(ui.DELETE_TOOL);
ui.updateUIEntities();
break;
case "clearTool":
Feedback.play(dominantHand, Feedback.DROP_TOOL);
grouping.clear();
toolSelected = TOOL_NONE;
ui.clearTool();
@ -1382,9 +1409,11 @@
break;
case "groupButton":
Feedback.play(dominantHand, Feedback.APPLY_PROPERTY);
grouping.group();
break;
case "ungroupButton":
Feedback.play(dominantHand, Feedback.APPLY_PROPERTY);
grouping.ungroup();
break;