mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-16 06:20:11 +02:00
Add audio and haptic feedback
This commit is contained in:
parent
8205b0d9d2
commit
734ce66df3
12 changed files with 121 additions and 11 deletions
BIN
scripts/vr-edit/assets/audio/clone.wav
Normal file
BIN
scripts/vr-edit/assets/audio/clone.wav
Normal file
Binary file not shown.
BIN
scripts/vr-edit/assets/audio/create.wav
Normal file
BIN
scripts/vr-edit/assets/audio/create.wav
Normal file
Binary file not shown.
BIN
scripts/vr-edit/assets/audio/delete.wav
Normal file
BIN
scripts/vr-edit/assets/audio/delete.wav
Normal file
Binary file not shown.
BIN
scripts/vr-edit/assets/audio/drop.wav
Normal file
BIN
scripts/vr-edit/assets/audio/drop.wav
Normal file
Binary file not shown.
BIN
scripts/vr-edit/assets/audio/equip.wav
Normal file
BIN
scripts/vr-edit/assets/audio/equip.wav
Normal file
Binary file not shown.
BIN
scripts/vr-edit/assets/audio/error.wav
Normal file
BIN
scripts/vr-edit/assets/audio/error.wav
Normal file
Binary file not shown.
BIN
scripts/vr-edit/assets/audio/select.wav
Normal file
BIN
scripts/vr-edit/assets/audio/select.wav
Normal file
Binary file not shown.
|
@ -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(),
|
||||
|
|
72
scripts/vr-edit/modules/feedback.js
Normal file
72
scripts/vr-edit/modules/feedback.js
Normal 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
|
||||
};
|
||||
}());
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue