mirror of
https://github.com/overte-org/community-apps.git
synced 2025-04-05 21:22:00 +02:00
495 lines
20 KiB
JavaScript
495 lines
20 KiB
JavaScript
//
|
|
// Created by Luis Cuenca on 3/8/19
|
|
// Copyright 2018 High Fidelity, Inc.
|
|
// Copyright 2023 Overte e.V.
|
|
//
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
/* jslint bitwise: true */
|
|
/* global Script, MyAvatar, Tablet
|
|
*/
|
|
|
|
(function () {
|
|
// Adding some additional time before load fix an issue when updating the debugging overlays (local entities)
|
|
var MS_AFTER_LOADING = 500;
|
|
Script.setTimeout(function() {
|
|
Script.registerValue("FLOWAPP", true);
|
|
|
|
var TABLET_BUTTON_NAME = "FLOW";
|
|
var HTML_URL = Script.resolvePath("./flowAppCpp.html");
|
|
|
|
var SHOW_AVATAR = true;
|
|
var SHOW_DEBUG_SHAPES = false;
|
|
var SHOW_SOLID_SHAPES = false;
|
|
var POLYLINE_TEXTURE = Script.resolvePath("./polylineTexture.png");
|
|
|
|
var USE_COLLISIONS = false;
|
|
var IS_ACTIVE = false;
|
|
|
|
var MSG_DOCUMENT_LOADED = 0;
|
|
var MSG_JOINT_INPUT_DATA = 1;
|
|
var MSG_COLLISION_DATA = 2;
|
|
var MSG_COLLISION_INPUT_DATA = 3;
|
|
var MSG_DISPLAY_DATA = 4;
|
|
var MSG_CREATE = 5;
|
|
|
|
var avatarScale = MyAvatar.getSensorToWorldScale();
|
|
|
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
|
var tabletButton = tablet.addButton({
|
|
text: TABLET_BUTTON_NAME,
|
|
icon: Script.resolvePath("./flow-i.svg"),
|
|
activeIcon: Script.resolvePath("./flow-a.svg")
|
|
});
|
|
|
|
var FlowDebug = function() {
|
|
var self = this;
|
|
this.debugLines = {};
|
|
this.debugSpheres = {};
|
|
this.showDebugShapes = false;
|
|
this.showSolidShapes = "lines"; // wireframe
|
|
|
|
this.setDebugLine = function(lineName, norms, startPosition, endPosition, shapeColor, width, forceRendering) {
|
|
var doRender = self.showDebugShapes || forceRendering;
|
|
if (!doRender) {
|
|
return;
|
|
}
|
|
var start = startPosition ? startPosition : {x: 0, y: 0, z: 0};
|
|
var end = endPosition ? endPosition : {x: 0, y: 1, z: 0};
|
|
var color = shapeColor ? shapeColor : { red: 0, green: 255, blue: 255 };
|
|
if (self.debugLines[lineName] !== undefined) {
|
|
Entities.editEntity(self.debugLines[lineName], {
|
|
color: color,
|
|
linePoints: [start, end],
|
|
strokeWidths: [width, width],
|
|
});
|
|
} else {
|
|
self.debugLines[lineName] = Entities.addEntity({
|
|
type: "PolyLine",
|
|
textures: POLYLINE_TEXTURE,
|
|
color: color,
|
|
normals: norms,
|
|
linePoints: [start, end],
|
|
visible: true,
|
|
strokeWidths: [width, width],
|
|
collisionless: true
|
|
}, "local");
|
|
}
|
|
};
|
|
|
|
this.setDebugSphere = function(sphereName, pos, diameter, shapeColor, forceRendering) {
|
|
var doRender = self.showDebugShapes || forceRendering;
|
|
if (!doRender) {
|
|
return;
|
|
}
|
|
var DEFAULT_SPHERE_DIAMETER = 0.02;
|
|
var scale = diameter ? diameter : DEFAULT_SPHERE_DIAMETER;
|
|
var color = shapeColor ? shapeColor : { red: 255, green: 0, blue: 255 };
|
|
if (self.debugSpheres[sphereName] !== undefined) {
|
|
Entities.editEntity(self.debugSpheres[sphereName], {
|
|
color: color,
|
|
position: pos,
|
|
dimensions: {x: scale, y: scale, z: scale},
|
|
primitiveMode: self.showSolidShapes,
|
|
});
|
|
} else {
|
|
self.debugSpheres[sphereName] = Entities.addEntity({
|
|
type: "Sphere",
|
|
color: color,
|
|
position: pos,
|
|
dimensions: {x: scale, y: scale, z: scale},
|
|
primitiveMode: self.showSolidShapes,
|
|
visible: true,
|
|
collisionless: true
|
|
}, "local");
|
|
}
|
|
};
|
|
|
|
this.deleteSphere = function(name) {
|
|
Entities.deleteEntity(self.debugSpheres[name]);
|
|
self.debugSpheres[name] = undefined;
|
|
};
|
|
|
|
this.deleteLine = function(name) {
|
|
Entities.deleteEntity(self.debugLines[name]);
|
|
self.debugLines[name] = undefined;
|
|
};
|
|
|
|
this.cleanup = function() {
|
|
for (var lineName in self.debugLines) {
|
|
if (lineName !== undefined) {
|
|
self.deleteLine(lineName);
|
|
}
|
|
}
|
|
for (var sphereName in self.debugSpheres) {
|
|
if (sphereName !== undefined) {
|
|
self.deleteSphere(sphereName);
|
|
}
|
|
}
|
|
self.debugLines = {};
|
|
self.debugSpheres = {};
|
|
};
|
|
|
|
this.setVisible = function(isVisible) {
|
|
self.showDebugShapes = isVisible;
|
|
for (var lineName in self.debugLines) {
|
|
if (lineName !== undefined) {
|
|
Entities.editEntity(self.debugLines[lineName], {
|
|
visible: isVisible
|
|
});
|
|
}
|
|
}
|
|
for (var sphereName in self.debugSpheres) {
|
|
if (sphereName !== undefined) {
|
|
Entities.editEntity(self.debugSpheres[sphereName], {
|
|
visible: isVisible
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
this.setSolid = function(isSolid) {
|
|
if(isSolid) {
|
|
self.showSolidShapes = "solid"
|
|
} else {
|
|
self.showSolidShapes = "lines"
|
|
}
|
|
for (var lineName in self.debugLines) {
|
|
if (lineName !== undefined) {
|
|
Entities.editEntity(self.debugLines[lineName], {
|
|
primitiveMode: self.showSolidShapes
|
|
});
|
|
}
|
|
}
|
|
for (var sphereName in self.debugSpheres) {
|
|
if (sphereName !== undefined) {
|
|
Entities.editEntity(self.debugSpheres[sphereName], {
|
|
primitiveMode: self.showSolidShapes
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
var flowData, initActive, initColliding, initDebugging;
|
|
updateFlowData(true);
|
|
var collisionDebug = new FlowDebug();
|
|
var jointDebug = new FlowDebug();
|
|
|
|
collisionDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
collisionDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
|
|
MyAvatar.setEnableMeshVisible(SHOW_AVATAR);
|
|
jointDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
jointDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
|
|
var shown = false;
|
|
|
|
function manageClick() {
|
|
if (shown) {
|
|
MyAvatar.useFlow(initActive, initColliding);
|
|
initDebugging = SHOW_DEBUG_SHAPES;
|
|
if (SHOW_DEBUG_SHAPES) {
|
|
toggleDebugShapes();
|
|
}
|
|
tablet.gotoHomeScreen();
|
|
} else {
|
|
updateFlowData();
|
|
tablet.gotoWebScreen(HTML_URL);
|
|
}
|
|
}
|
|
|
|
tabletButton.clicked.connect(manageClick);
|
|
|
|
function onScreenChanged(type, url) {
|
|
console.log("Screen changed");
|
|
if (type === "Web" && url === HTML_URL) {
|
|
tabletButton.editProperties({isActive: true});
|
|
if (!shown) {
|
|
// hook up to event bridge
|
|
tablet.webEventReceived.connect(onWebEventReceived);
|
|
}
|
|
shown = true;
|
|
} else {
|
|
tabletButton.editProperties({isActive: false});
|
|
if (shown) {
|
|
// disconnect from event bridge
|
|
tablet.webEventReceived.disconnect(onWebEventReceived);
|
|
}
|
|
shown = false;
|
|
}
|
|
}
|
|
|
|
var toggleAvatarVisible = function() {
|
|
SHOW_AVATAR = !SHOW_AVATAR;
|
|
MyAvatar.setEnableMeshVisible(SHOW_AVATAR);
|
|
};
|
|
|
|
var toggleDebugShapes = function() {
|
|
SHOW_DEBUG_SHAPES = !SHOW_DEBUG_SHAPES;
|
|
if (USE_COLLISIONS) {
|
|
collisionDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
}
|
|
jointDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
};
|
|
|
|
var toggleSolidShapes = function() {
|
|
SHOW_SOLID_SHAPES = !SHOW_SOLID_SHAPES;
|
|
collisionDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
jointDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
};
|
|
|
|
var toggleCollisions = function() {
|
|
USE_COLLISIONS = !USE_COLLISIONS;
|
|
if (USE_COLLISIONS && SHOW_DEBUG_SHAPES) {
|
|
collisionDebug.setVisible(true);
|
|
} else {
|
|
collisionDebug.setVisible(false);
|
|
}
|
|
MyAvatar.useFlow(IS_ACTIVE, USE_COLLISIONS);
|
|
};
|
|
|
|
var getDisplayData = function() {
|
|
return {"avatar": SHOW_AVATAR,
|
|
"collisions": USE_COLLISIONS,
|
|
"debug": SHOW_DEBUG_SHAPES,
|
|
"solid": SHOW_SOLID_SHAPES};
|
|
};
|
|
|
|
var jointNames = MyAvatar.getJointNames();
|
|
|
|
function roundFloat(number, decimals) {
|
|
var DECIMALS_MULTIPLIER = 10;
|
|
var multiplier = Math.pow(DECIMALS_MULTIPLIER, decimals);
|
|
var rounded = Math.round(number * multiplier);
|
|
return rounded / multiplier;
|
|
}
|
|
|
|
function roundVector(vector, decimals) {
|
|
return {x: roundFloat(vector.x, decimals), y: roundFloat(vector.y, decimals), z: roundFloat(vector.z, decimals)};
|
|
}
|
|
|
|
function roundDataValues(decimals) {
|
|
var collisions = flowData.collisions;
|
|
var physics = flowData.physics;
|
|
Object.keys(collisions).forEach(function(key) {
|
|
var data = collisions[key];
|
|
data.radius = roundFloat(data.radius, decimals);
|
|
data.offset = roundVector(data.offset, decimals);
|
|
});
|
|
Object.keys(physics).forEach(function(key) {
|
|
var data = physics[key];
|
|
data.damping = roundFloat(data.damping, decimals);
|
|
data.delta = roundFloat(data.delta, decimals);
|
|
data.gravity = roundFloat(data.gravity, decimals);
|
|
data.inertia = roundFloat(data.inertia, decimals);
|
|
data.radius = roundFloat(data.radius, decimals);
|
|
data.stiffness = roundFloat(data.stiffness, decimals);
|
|
});
|
|
}
|
|
|
|
function updateFlowData(catchInitValues) {
|
|
flowData = MyAvatar.getFlowData();
|
|
if (typeof(flowData) !== "object" || typeof(flowData.collisions) !== "object") {
|
|
return;
|
|
}
|
|
var ROUND_DECIMALS = 4;
|
|
var collisionJoints = Object.keys(flowData.collisions);
|
|
var inverseScale = 1.0 / avatarScale;
|
|
for (var i = 0; i < collisionJoints.length; i++) {
|
|
var collision = flowData.collisions[collisionJoints[i]];
|
|
collision.radius *= inverseScale;
|
|
collision.offset = Vec3.multiply(collision.offset, inverseScale);
|
|
}
|
|
roundDataValues(ROUND_DECIMALS);
|
|
IS_ACTIVE = flowData.active;
|
|
USE_COLLISIONS = flowData.colliding;
|
|
if (catchInitValues) {
|
|
initActive = flowData.active;
|
|
initColliding = flowData.colliding;
|
|
}
|
|
}
|
|
|
|
function onWebEventReceived(msg) {
|
|
var message = JSON.parse(msg);
|
|
switch (message.type) {
|
|
case MSG_JOINT_INPUT_DATA: {
|
|
flowData.physics[message.group][message.name] = message.value;
|
|
MyAvatar.useFlow(IS_ACTIVE, USE_COLLISIONS, flowData.physics, flowData.collisions);
|
|
break;
|
|
}
|
|
case MSG_COLLISION_INPUT_DATA: {
|
|
var value = message.name === "offset" ? {x: 0.0, y: message.value, z: 0.0} : message.value;
|
|
flowData.collisions[message.group][message.name] = value;
|
|
MyAvatar.useFlow(IS_ACTIVE, USE_COLLISIONS, flowData.physics, flowData.collisions);
|
|
break;
|
|
}
|
|
case MSG_DISPLAY_DATA: {
|
|
switch (message.name) {
|
|
case "collisions":
|
|
toggleCollisions();
|
|
break;
|
|
case "debug":
|
|
toggleDebugShapes();
|
|
break;
|
|
case "solid":
|
|
toggleSolidShapes();
|
|
break;
|
|
case "avatar":
|
|
toggleAvatarVisible();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case MSG_DOCUMENT_LOADED: {
|
|
MyAvatar.useFlow(true, true);
|
|
updateFlowData();
|
|
if (initDebugging && !SHOW_DEBUG_SHAPES) {
|
|
toggleDebugShapes();
|
|
}
|
|
createHTMLMenu();
|
|
break;
|
|
}
|
|
case MSG_COLLISION_DATA: {
|
|
switch (message.name) {
|
|
case "add":
|
|
var collisionData = {"type": "sphere", "radius": 0.05, "offset": {"x": 0.0, "y": 0.0, "z": 0.0}};
|
|
flowData.collisions[message.value] = collisionData;
|
|
MyAvatar.useFlow(IS_ACTIVE, USE_COLLISIONS, flowData.physics, flowData.collisions);
|
|
updateFlowData();
|
|
tablet.emitScriptEvent(JSON.stringify({
|
|
"type": MSG_COLLISION_DATA,
|
|
"name": message.value,
|
|
"data": collisionData
|
|
}));
|
|
break;
|
|
case "remove":
|
|
var jointName = message.value;
|
|
collisionDebug.deleteSphere(jointName + "_col");
|
|
if (flowData.collisions[jointName] !== undefined) {
|
|
delete flowData.collisions[jointName];
|
|
MyAvatar.useFlow(IS_ACTIVE, USE_COLLISIONS, flowData.physics, flowData.collisions);
|
|
updateFlowData();
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
tablet.screenChanged.connect(onScreenChanged);
|
|
|
|
function createHTMLMenu() {
|
|
jointNames = MyAvatar.getJointNames();
|
|
tablet.emitScriptEvent(JSON.stringify(
|
|
{
|
|
"type": MSG_CREATE,
|
|
"data": {
|
|
"display": getDisplayData(),
|
|
"group": flowData.physics,
|
|
"collisions": flowData.collisions,
|
|
"joints": jointNames
|
|
}
|
|
}
|
|
));
|
|
}
|
|
|
|
function shutdownTabletApp() {
|
|
MyAvatar.useFlow(initActive, initColliding);
|
|
tablet.removeButton(tabletButton);
|
|
if (shown) {
|
|
tablet.webEventReceived.disconnect(onWebEventReceived);
|
|
tablet.gotoHomeScreen();
|
|
}
|
|
tablet.screenChanged.disconnect(onScreenChanged);
|
|
}
|
|
|
|
MyAvatar.skeletonChanged.connect(function() {
|
|
var MS_AFTER_AVATAR_UPDATE = 200;
|
|
collisionDebug.cleanup();
|
|
jointDebug.cleanup();
|
|
Script.setTimeout(function() {
|
|
jointNames = MyAvatar.getJointNames();
|
|
avatarScale = MyAvatar.getSensorToWorldScale();
|
|
updateFlowData(true);
|
|
if (shown) {
|
|
manageClick();
|
|
}
|
|
}, MS_AFTER_AVATAR_UPDATE);
|
|
});
|
|
|
|
MyAvatar.scaleChanged.connect(function() {
|
|
avatarScale = MyAvatar.getSensorToWorldScale();
|
|
});
|
|
|
|
Script.update.connect(function() {
|
|
if (IS_ACTIVE) {
|
|
var groupData = flowData.physics;
|
|
var collisionData = flowData.collisions;
|
|
var threads = flowData.threads;
|
|
var groups = Object.keys(groupData);
|
|
var flowPositions = Array(jointNames.length);
|
|
var flowCollisionColors = Array(jointNames.length);
|
|
var collidingJoints = MyAvatar.getCollidingFlowJoints();
|
|
for (var i = 0; i < groups.length; i++) {
|
|
var group = groups[i];
|
|
var data = groupData[group];
|
|
for (var j = 0; j < data.jointIndices.length; j++) {
|
|
var index = data.jointIndices[j];
|
|
var name = jointNames[index];
|
|
var position = MyAvatar.getJointPosition(index);
|
|
flowPositions[index] = position;
|
|
var colliding = collidingJoints.indexOf(index) > -1;
|
|
var color = { red: 255, green: 255, blue: 0 };
|
|
if (colliding) {
|
|
color.green = 0;
|
|
}
|
|
flowCollisionColors[index] = color;
|
|
var radius = 2.0 * avatarScale * data.radius;
|
|
jointDebug.setDebugSphere(name + "_flow", position, radius, color);
|
|
}
|
|
}
|
|
var names = Object.keys(collisionData);
|
|
for (i = 0; i < names.length; i++) {
|
|
name = names[i];
|
|
index = collisionData[name].jointIndex;
|
|
|
|
var offset = Vec3.multiply(collisionData[name].offset, avatarScale);
|
|
radius = avatarScale * collisionData[name].radius;
|
|
position = MyAvatar.jointToWorldPoint(offset, index);
|
|
collisionDebug.setDebugSphere(name + "_col", position, 2 * radius, {red: 200, green: 10, blue: 50});
|
|
}
|
|
var threadKeys = Object.keys(threads);
|
|
for (i = 0; i < threadKeys.length; i++) {
|
|
var thread = threads[threadKeys[i]];
|
|
for (j = 1; j < thread.length; j++) {
|
|
var index1 = thread[j-1];
|
|
var index2 = thread[j];
|
|
if (flowPositions[index1] !== undefined && flowPositions[index2] !== undefined) {
|
|
var lineName = jointNames[index1] + "_line";
|
|
color = flowCollisionColors[index1];
|
|
var DEFAULT_LINE_WIDTH = 0.004;
|
|
var lineWidth = DEFAULT_LINE_WIDTH * avatarScale;
|
|
// We are creating two PolyLines with different normals, to make them more visible from the sides.
|
|
var LINE_NORMALS_1 = [{ x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: 1 }];
|
|
var LINE_NORMALS_2 = [{ x: 1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }];
|
|
jointDebug.setDebugLine(lineName + "_1", LINE_NORMALS_1, flowPositions[index1], flowPositions[index2], color, lineWidth);
|
|
jointDebug.setDebugLine(lineName + "_2", LINE_NORMALS_2, flowPositions[index1], flowPositions[index2], color, lineWidth);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
Script.scriptEnding.connect(function () {
|
|
collisionDebug.cleanup();
|
|
jointDebug.cleanup();
|
|
shutdownTabletApp();
|
|
});
|
|
}, MS_AFTER_LOADING);
|
|
|
|
}());
|