overte-AleziaKurdis/scripts/system/+android_interface/radar.js
2023-10-10 18:48:39 +02:00

1217 lines
38 KiB
JavaScript

"use strict";
//
// radar.js
// scripts/system/+android/
//
// Created by Cristian Duarte & Gabriel Calero on 31 Jan 2018
// Copyright 2018 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
//
var radarModeInterface = {};
var logEnabled = false;
function printd(str) {
if (logEnabled) {
print("[radar.js] " + str);
}
}
var radar = false;
var RADAR_HEIGHT_INIT_DELTA = 10;
var radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; // camera position (absolute y)
var tablet;
var RADAR_CAMERA_OFFSET = -1; // 1 meter below the avatar
var ABOVE_GROUND_DROP = 2;
var MOVE_BY = 1;
// Swipe/Drag vars
var PINCH_INCREMENT_FIRST = 0.4; // 0.1 meters zoom in - out
var PINCH_INCREMENT = 0.4; // 0.1 meters zoom in - out
var RADAR_HEIGHT_MAX_PLUS_AVATAR = 80;
var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2;
var RADAR_CAMERA_DISTANCE_TO_ICONS = 1.5; // Icons are near the camera to prevent the LOD manager dismissing them
var RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE = 1; // How much above the avatar base should the icon appear
var AVATAR_DISPLAY_NAME_HEIGHT = 106;
var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 48;
var AVATAR_DISPLAY_NAME_FONT_SIZE = 50;
var lastDragAt;
var lastDeltaDrag;
var uniqueColor;
function moveTo(position) {
if (radar) {
MyAvatar.goToLocation(position, false);
Camera.position = {
x : position.x,
y : radarHeight,
z : position.z
};
}
}
function keyPressEvent(event) {
if (radar) {
switch (event.text) {
case "UP":
moveTo(Vec3.sum(MyAvatar.position, {
x : 0.0,
y : 0,
z : -1 * MOVE_BY
}));
break;
case "DOWN":
moveTo(Vec3.sum(MyAvatar.position, {
x : 0,
y : 0,
z : MOVE_BY
}));
break;
case "LEFT":
moveTo(Vec3.sum(MyAvatar.position, {
x : -1 * MOVE_BY,
y : 0,
z : 0
}));
break;
case "RIGHT":
moveTo(Vec3.sum(MyAvatar.position, {
x : MOVE_BY,
y : 0,
z : 0
}));
break;
}
}
}
function toggleRadarMode() {
if (radar) {
endRadar();
} else {
startRadar();
}
}
function fakeDoubleTap(event) {
// CLD - temporarily disable toggling mode through double tap
// * As we have a new UI for toggling between modes, it may be discarded
// completely in the future.
// toggleRadarMode();
teleporter.dragTeleportUpdate(event);
teleporter.dragTeleportRelease(event);
}
var DOUBLE_TAP_TIME = 300;
var fakeDoubleTapStart = Date.now();
var touchEndCount = 0;
/*
* Counts touchEnds and if there were 2 in the DOUBLE_TAP_TIME lapse, it
* triggers a fakeDoubleTap and returns true. Otherwise, returns false (no
* double tap yet)
*/
function analyzeDoubleTap(event) {
var fakeDoubleTapEnd = Date.now();
var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart;
if (elapsed > DOUBLE_TAP_TIME) {
touchEndCount = 0;
}
// if this is our first "up" then record time so we can
// later determine if second "up" is a double tap
if (touchEndCount == 0) {
fakeDoubleTapStart = Date.now();
}
touchEndCount++;
if (touchEndCount >= 2) {
var fakeDoubleTapEnd = Date.now();
var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart;
printd("-- fakeDoubleTapEnd:" + fakeDoubleTapEnd + "-- elapsed:"
+ elapsed)
if (elapsed <= DOUBLE_TAP_TIME) {
touchEndCount = 0;
fakeDoubleTap(event);
return true; // don't do the normal touch end processing
}
touchEndCount = 0;
}
return false;
}
function touchEnd(event) {
printd("touchEnd received " + JSON.stringify(event));
// Clean up touch variables
lastDragAt = null;
lastDeltaDrag = null;
touchStartingCoordinates = null; // maybe in special cases it should be
// setup later?
startedDraggingCamera = false;
prevTouchPinchRadius = null;
draggingCamera = false;
if (movingCamera) {
// if camera was indeed moving, we should not further process, it was
// just dragging
movingCamera = false;
dragModeFunc = null;
return;
}
// teleport release analysis
if (teleporter && teleporter.dragTeleportUpdate == dragModeFunc) {
teleporter.dragTeleportRelease(event);
dragModeFunc = null;
return;
}
dragModeFunc = null;
// if pinching or moving is still detected, cancel
if (event.isPinching) {
printd("touchEnd fail because isPinching");
return;
}
if (event.isPinchOpening) {
printd("touchEnd fail because isPinchingOpening");
return;
}
if (event.isMoved) {
printd("touchEnd fail because isMoved");
return;
}
if (analyzeDoubleTap(event))
return; // double tap detected, finish
}
/**
* Polyfill for sign(x)
*/
if (!Math.sign) {
Math.sign = function(x) {
// If x is NaN, the result is NaN.
// If x is -0, the result is -0.
// If x is +0, the result is +0.
// If x is negative and not -0, the result is -1.
// If x is positive and not +0, the result is +1.
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return Number(x);
}
return x > 0 ? 1 : -1;
};
}
/*******************************************************************************
* Line and Plane intersection methods
******************************************************************************/
/**
* findLinePlaneIntersection Given points p {x: y: z:} and q that define a line,
* and the plane of formula ax+by+cz+d = 0, returns the intersection point or
* null if none.
*/
function findLinePlaneIntersection(p, q, a, b, c, d) {
return findLinePlaneIntersectionCoords(p.x, p.y, p.z, q.x, q.y, q.z, a, b,
c, d);
}
/**
* findLineToHeightIntersection Given points p {x: y: z:} and q that define a
* line, and a planeY value that defines a plane paralel to 'the floor' xz
* plane, returns the intersection to that plane or null if none.
*/
function findLineToHeightIntersection(p, q, planeY) {
return findLinePlaneIntersection(p, q, 0, 1, 0, -planeY);
}
/**
* findLinePlaneIntersectionCoords (to avoid requiring unnecessary
* instantiation) Given points p with px py pz and q that define a line, and the
* plane of formula ax+by+cz+d = 0, returns the intersection point or null if
* none.
*/
function findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, a, b, c, d) {
var tDenom = a * (qx - px) + b * (qy - py) + c * (qz - pz);
if (tDenom == 0)
return null;
var t = -(a * px + b * py + c * pz + d) / tDenom;
return {
x : (px + t * (qx - px)),
y : (py + t * (qy - py)),
z : (pz + t * (qz - pz))
};
}
/**
* findLineToHeightIntersection Given points p with px py pz and q that define a
* line, and a planeY value that defines a plane paralel to 'the floor' xz
* plane, returns the intersection to that plane or null if none.
*/
function findLineToHeightIntersectionCoords(px, py, pz, qx, qy, qz, planeY) {
return findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, 0, 1, 0,
-planeY);
}
function findRayIntersection(pickRay) {
// Check 3D overlays and entities. Argument is an object with origin and
// direction.
var result = Overlays.findRayIntersection(pickRay);
if (!result.intersects) {
result = Entities.findRayIntersection(pickRay, true);
}
return result;
}
/**
* Given a 2d point (x,y) this function returns the intersection (x, y, z) of
* the computedPickRay for that point with the plane y = py
*/
function computePointAtPlaneY(x, y, py) {
var ray = Camera.computePickRay(x, y);
var p1 = ray.origin;
var p2 = Vec3.sum(p1, Vec3.multiply(ray.direction, 1));
return findLineToHeightIntersectionCoords(p1.x, p1.y, p1.z, p2.x, p2.y,
p2.z, py);
}
/*******************************************************************************
*
******************************************************************************/
var touchStartingCoordinates = null;
var KEEP_PRESSED_FOR_TELEPORT_MODE_TIME = 750;
var touchBeginTime;
function touchBegin(event) {
var coords = {
x : event.x,
y : event.y
};
touchStartingCoordinates = coords;
touchBeginTime = Date.now();
}
var startedDraggingCamera = false; // first time
var draggingCamera = false; // is trying
var movingCamera = false; // definitive
var MIN_DRAG_DISTANCE_TO_CONSIDER = 100; // distance by axis, not real
// distance
var prevTouchPinchRadius = null;
function pinchUpdate(event) {
if (!event.isMoved)
return;
if (event.radius <= 0)
return;
// pinch management
var avatarY = MyAvatar.position.y;
var pinchIncrement;
if (!!prevTouchPinchRadius) {
// no prev value
pinchIncrement = PINCH_INCREMENT
* Math.abs(event.radius - prevTouchPinchRadius) * 0.1;
} else {
pinchIncrement = PINCH_INCREMENT_FIRST;
}
if (event.isPinching) {
if (radarHeight + pinchIncrement > RADAR_HEIGHT_MAX_PLUS_AVATAR
+ avatarY) {
radarHeight = RADAR_HEIGHT_MAX_PLUS_AVATAR + avatarY;
} else {
radarHeight += pinchIncrement;
}
} else if (event.isPinchOpening) {
if (radarHeight - pinchIncrement < RADAR_HEIGHT_MIN_PLUS_AVATAR
+ avatarY) {
radarHeight = RADAR_HEIGHT_MIN_PLUS_AVATAR + avatarY;
} else {
radarHeight -= pinchIncrement;
}
}
Camera.position = {
x : Camera.position.x,
y : radarHeight,
z : Camera.position.z
};
if (!draggingCamera) {
startedDraggingCamera = true;
draggingCamera = true;
}
prevTouchPinchRadius = event.radius;
}
function isInsideSquare(coords0, coords1, halfside) {
return coords0 != undefined && coords1 != undefined &&
Math.abs(coords0.x - coords1.x) <= halfside
&& Math.abs(coords0.y - coords1.y) <= halfside;
}
function dragScrollUpdate(event) {
if (!event.isMoved)
return;
// drag management
var pickRay = Camera.computePickRay(event.x, event.y);
var dragAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,
radarHeight - MyAvatar.position.y));
if (lastDragAt === undefined || lastDragAt === null) {
lastDragAt = dragAt;
return;
}
var deltaDrag = {
x : (lastDragAt.x - dragAt.x),
y : 0,
z : (lastDragAt.z - dragAt.z)
};
lastDragAt = dragAt;
if (lastDeltaDrag === undefined || lastDeltaDrag === null) {
lastDeltaDrag = deltaDrag;
return;
}
if (!draggingCamera) {
startedDraggingCamera = true;
draggingCamera = true;
} else {
if (!movingCamera) {
if (!isInsideSquare(touchStartingCoordinates, event,
MIN_DRAG_DISTANCE_TO_CONSIDER)) {
movingCamera = true;
}
}
if (movingCamera) {
if (Math.sign(deltaDrag.x) == Math.sign(lastDeltaDrag.x)
&& Math.sign(deltaDrag.z) == Math.sign(lastDeltaDrag.z)) {
// Process movement if direction of the movement is the same
// than the previous frame
// process delta
var moveCameraTo = Vec3.sum(Camera.position, deltaDrag);
// move camera
Camera.position = moveCameraTo;
} else {
// Do not move camera if it's changing direction in this case,
// wait until the next direction confirmation..
}
lastDeltaDrag = deltaDrag; // save last
}
}
}
/*******************************************************************************
* Teleport feature
******************************************************************************/
function Teleporter() {
var SURFACE_DETECTION_FOR_TELEPORT = true; // true if uses teleport.js
// similar logic to detect
// surfaces. false if uses plain
// teleport to avatar same
// height.
var TELEPORT_TARGET_MODEL_URL = Script
.resolvePath("../assets/models/teleport-destination.fbx");
var TELEPORT_TOO_CLOSE_MODEL_URL = Script
.resolvePath("../assets/models/teleport-cancel.fbx");
var TELEPORT_MODEL_DEFAULT_DIMENSIONS = {
x : 0.10,
y : 0.00001,
z : 0.10
};
var teleportOverlay = Overlays.addOverlay("model", {
url : TELEPORT_TARGET_MODEL_URL,
dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS,
orientation : Quat.fromPitchYawRollDegrees(0, 180, 0),
visible : false
});
var teleportCancelOverlay = Overlays.addOverlay("model", {
url : TELEPORT_TOO_CLOSE_MODEL_URL,
dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS,
orientation : Quat.fromPitchYawRollDegrees(0, 180, 0),
visible : false
});
var TELEPORT_COLOR = {
red : 0,
green : 255,
blue : 255
};
var TELEPORT_CANCEL_COLOR = {
red : 255,
green : 255,
blue : 0
};
var teleportLine = Overlays.addOverlay("line3d", {
start : {
x : 0,
y : 0,
z : 0
},
end : {
x : 0,
y : 0,
z : 0
},
color : TELEPORT_COLOR,
alpha : 1,
lineWidth : 2,
dashed : false,
visible : false
});
// code from teleport.js
var TELEPORT_TARGET = {
NONE : 'none', // Not currently targetting anything
INVISIBLE : 'invisible', // The current target is an invvsible
// surface
INVALID : 'invalid', // The current target is invalid (wall, ceiling,
// etc.)
SURFACE : 'surface', // The current target is a valid surface
SEAT : 'seat', // The current target is a seat
}
var TELEPORT_CANCEL_RANGE = 1;
var teleportTargetType = TELEPORT_TARGET.NONE;
function parseJSON(json) {
try {
return JSON.parse(json);
} catch (e) {
return undefined;
}
}
/*
* Enhanced with intersection with terrain instead of using current avatar y
* position if SURFACE_DETECTION_FOR_TELEPORT is true
*/
function computeDestination(touchEventPos, avatarPosition, cameraPosition,
radarH) {
if (SURFACE_DETECTION_FOR_TELEPORT) {
var pickRay = Camera.computePickRay(touchEventPos.x,
touchEventPos.y);
printd("newTeleportDetect - pickRay " + JSON.stringify(pickRay));
var destination = Entities.findRayIntersection(pickRay, true, [],
[], false, true);
printd("newTeleportDetect - destination "
+ JSON.stringify(destination));
return destination;
} else {
var pickRay = Camera.computePickRay(touchEventPos.x,
touchEventPos.y);
var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(
pickRay.direction, radarH));
var destination = {
x : pointingAt.x,
y : avatarPosition.y,
z : pointingAt.z
};
return destination;
}
}
function renderTeleportOverlays(destination) {
var overlayPosition = findLineToHeightIntersection(destination,
Camera.position, Camera.position.y
- RADAR_CAMERA_DISTANCE_TO_ICONS);
printd("[newTeleport] TELEPORT ! render overlay at "
+ JSON.stringify(overlayPosition));
// CLD note Oct 11, 2017
// Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509
// doesn't allow invisible surfaces, let's allow it for now
if (teleportTargetType == TELEPORT_TARGET.SURFACE
|| teleportTargetType == TELEPORT_TARGET.INVISIBLE) {
Overlays.editOverlay(teleportOverlay, {
visible : true,
position : overlayPosition
});
Overlays.editOverlay(teleportCancelOverlay, {
visible : false
});
Overlays.editOverlay(teleportLine, {
start : MyAvatar.position,
end : destination,
color : TELEPORT_COLOR,
visible : true
});
} else if (teleportTargetType == TELEPORT_TARGET.INVALID) {
Overlays.editOverlay(teleportOverlay, {
visible : false
});
Overlays.editOverlay(teleportCancelOverlay, {
visible : true,
position : overlayPosition
});
Overlays.editOverlay(teleportLine, {
start : MyAvatar.position,
end : destination,
color : TELEPORT_CANCEL_COLOR,
visible : true
});
} else { // TELEPORT_TARGET:NONE?
Overlays.editOverlay(teleportOverlay, {
visible : false
});
Overlays.editOverlay(teleportCancelOverlay, {
visible : false
});
Overlays.editOverlay(teleportLine, {
visible : false
});
}
}
var BORDER_DISTANCE_PX = 100;
var border_top = 0;
var border_left = 0;
var border_right = Window.innerWidth;
var border_bottom = Window.innerHeight;
function moveOnBorders(event) {
var xDelta = 0;
var zDelta = 0;
if (event.y <= border_top + BORDER_DISTANCE_PX) {
zDelta = -0.1;
} else if (event.y >= border_bottom - BORDER_DISTANCE_PX) {
zDelta = 0.1;
}
if (event.x <= border_left + BORDER_DISTANCE_PX) {
xDelta = -0.1;
} else if (event.x >= border_right - BORDER_DISTANCE_PX) {
xDelta = 0.1;
}
if (xDelta == 0 && zDelta == 0) {
draggingCamera = false;
return;
}
Camera.position = Vec3.sum(Camera.position, {
x : xDelta,
y : 0,
z : zDelta
});
draggingCamera = true;
}
// When determininig whether you can teleport to a location, the normal of
// the
// point that is being intersected with is looked at. If this normal is more
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up),
// then
// you can't teleport there.
const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
function getTeleportTargetType(intersection) {
if (SURFACE_DETECTION_FOR_TELEPORT) {
if (!intersection.intersects) {
return TELEPORT_TARGET.NONE;
}
var props = Entities.getEntityProperties(intersection.entityID, [
'userData', 'visible' ]);
var data = parseJSON(props.userData);
if (data !== undefined && data.seat !== undefined) {
return TELEPORT_TARGET.SEAT;
}
if (!props.visible) {
return TELEPORT_TARGET.INVISIBLE;
}
var surfaceNormal = intersection.surfaceNormal;
var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x
+ surfaceNormal.z * surfaceNormal.z);
var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);
if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT)
|| angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT)
|| Vec3.distance(MyAvatar.position,
intersection.intersection) <= TELEPORT_CANCEL_RANGE) {
return TELEPORT_TARGET.INVALID;
} else {
return TELEPORT_TARGET.SURFACE;
}
} else {
var destination = intersection;
if (Vec3.distance(MyAvatar.position, destination) <= TELEPORT_CANCEL_RANGE) {
return TELEPORT_TARGET.INVALID;
} else {
return TELEPORT_TARGET.SURFACE;
}
}
}
;
function moveToFromEvent(event) {
var destination = computeDestination(event, MyAvatar.position,
Camera.position, radarHeight);
moveTo(SURFACE_DETECTION_FOR_TELEPORT ? Vec3.sum(
destination.intersection, {
y : 1
}) : destination);
return true;
}
return {
dragTeleportBegin : function(event) {
printd("[newTeleport] TELEPORT began");
var overlayDimensions = teleportIconModelDimensions(MyAvatar.position.y);
// var destination = computeDestination(event, MyAvatar.position,
// Camera.position, radarHeight);
// Dimension teleport and cancel overlays (not show them yet)
Overlays.editOverlay(teleportOverlay, {
dimensions : overlayDimensions
});
Overlays.editOverlay(teleportCancelOverlay, {
dimensions : overlayDimensions
});
// Position line
Overlays.editOverlay(teleportLine, {
visible : true,
start : 0,
end : 0
});
},
dragTeleportUpdate : function(event) {
// if in border, move camera
moveOnBorders(event);
var destination = computeDestination(event, MyAvatar.position,
Camera.position, radarHeight);
teleportTargetType = getTeleportTargetType(destination);
renderTeleportOverlays(SURFACE_DETECTION_FOR_TELEPORT ? destination.intersection
: destination);
},
dragTeleportRelease : function(event) {
printd("[newTeleport] TELEPORT released at "
+ JSON.stringify(event));
// CLD note Oct 11, 2017
// Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509
// doesn't allow invisible surfaces, let's allow it for now
if (teleportTargetType == TELEPORT_TARGET.SURFACE
|| teleportTargetType == TELEPORT_TARGET.INVISIBLE) {
moveToFromEvent(event);
}
teleportTargetType = TELEPORT_TARGET.NONE;
Overlays.editOverlay(teleportOverlay, {
visible : false
});
Overlays.editOverlay(teleportLine, {
visible : false
});
Overlays.editOverlay(teleportCancelOverlay, {
visible : false
});
}
};
}
var teleporter = Teleporter();
/*******************************************************************************
*
******************************************************************************/
var dragModeFunc = null; // by default is nothing
function oneFingerTouchUpdate(event) {
if (dragModeFunc) {
dragModeFunc(event);
} else {
if (!isInsideSquare(touchStartingCoordinates, event,
MIN_DRAG_DISTANCE_TO_CONSIDER)) {
dragModeFunc = dragScrollUpdate;
dragModeFunc(event);
} else {
var now = Date.now(); // check time
if (now - touchBeginTime >= KEEP_PRESSED_FOR_TELEPORT_MODE_TIME) {
teleporter.dragTeleportBegin(event);
dragModeFunc = teleporter.dragTeleportUpdate;
dragModeFunc(event);
} else {
// not defined yet, let's wait for time or movement to happen
}
}
}
}
function touchUpdate(event) {
if (event.isPinching || event.isPinchOpening) {
pinchUpdate(event);
} else {
oneFingerTouchUpdate(event);
}
}
/*******************************************************************************
* Avatar cache structure for showing avatars markers
******************************************************************************/
// by QUuid
var avatarsData = {};
var avatarsIcons = []; // a parallel list of icons (overlays) to easily run
// through
var avatarsNames = []; // a parallel list of names (overlays) to easily run
// through
function getAvatarIconForUser(uid) {
var color = uniqueColor.getColor(uid);
if (color.charAt(0) == '#') {
color = color.substring(1, color.length);
}
// FIXME: this is a temporary solution until we can use circle3d with
// lineWidth
return Script.resolvePath("assets/images/circle-" + color + ".svg");
}
var avatarIconDimensionsVal = {
x : 0,
y : 0,
z : 0.00001
};
function avatarIconPlaneDimensions() {
// given the current height, give a size
var xy = -0.003531 * (radarHeight - MyAvatar.position.y) + 0.1;
avatarIconDimensionsVal.x = Math.abs(xy);
avatarIconDimensionsVal.y = Math.abs(xy);
// reuse object
return avatarIconDimensionsVal;
}
function currentOverlayIconForAvatar(QUuid) {
if (avatarsData[QUuid] != undefined) {
return avatarsData[QUuid].icon;
} else {
return null;
}
}
function currentOverlayNameForAvatar(QUuid) {
if (avatarsData[QUuid] != undefined) {
return avatarsData[QUuid].name;
} else {
return null;
}
}
function saveAvatarData(QUuid) {
if (QUuid == null)
return;
var avat = AvatarList.getAvatar(QUuid);
printd("avatar added save avatar " + QUuid);
if (!avat)
return;
if (avatarsData[QUuid] != undefined) {
avatarsData[QUuid].position = avat.position;
} else {
var avatarIcon = Overlays.addOverlay("circle3d", {
color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)),
dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS,
rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
innerRadius: 1.8,
outerRadius: 2,
isSolid: true,
visible: false
});
var needRefresh = !avat || !avat.displayName;
var displayName = avat && avat.displayName ? avat.displayName
: "Unknown";
var textWidth = displayName.length * AVATAR_DISPLAY_NAME_CHAR_WIDTH;
var avatarName = Overlays.addOverlay("text", {
width: textWidth,
height: AVATAR_DISPLAY_NAME_HEIGHT,
color: { red: 255, green: 255, blue: 255},
backgroundAlpha: 0.0,
textRaiseColor: { red: 0, green: 0, blue: 0},
font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true},
visible: false,
text: displayName,
textAlignCenter: true
});
avatarsIcons.push(avatarIcon);
avatarsNames.push(avatarName);
avatarsData[QUuid] = {
position : avat.position,
icon : avatarIcon,
name : avatarName,
textWidth : textWidth,
needRefresh : needRefresh
};
}
}
function removeAvatarData(QUuid) {
if (QUuid == null)
return;
var itsOverlay = currentOverlayIconForAvatar(QUuid);
if (itsOverlay != null) {
Overlays.deleteOverlay(itsOverlay);
}
var itsNameOverlay = currentOverlayNameForAvatar(QUuid);
if (itsNameOverlay != null) {
Overlays.deleteOverlay(itsNameOverlay);
}
var idx = avatarsIcons.indexOf(itsOverlay);
avatarsIcons.splice(idx, 1);
idx = avatarsNames.indexOf(itsNameOverlay);
avatarsNames.splice(idx, 1);
delete avatarsData[QUuid];
}
function saveAllOthersAvatarsData() {
var avatarIds = AvatarList.getAvatarIdentifiers();
var len = avatarIds.length;
for (var i = 0; i < len; i++) {
if (avatarIds[i]) {
saveAvatarData(avatarIds[i]);
}
}
}
function avatarAdded(QUuid) {
printd("avatar added " + QUuid);// + " at " +
// JSON.stringify(AvatarList.getAvatar(QUuid).position));
saveAvatarData(QUuid);
}
function avatarRemoved(QUuid) {
printd("avatar removed " + QUuid);
removeAvatarData(QUuid);
}
/*******************************************************************************
* Avatar Icon/Markers rendering
******************************************************************************/
var myAvatarIcon;
var myAvatarName;
function distanceForCameraHeight(h) {
if (h < 30) return 1;
if (h < 40) return 2;
if (h < 50) return 2.5;
return 5;
}
function renderMyAvatarIcon() {
var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y);
var iconPos = findLineToHeightIntersectionCoords( MyAvatar.position.x,
MyAvatar.position.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE,
MyAvatar.position.z,
Camera.position.x, Camera.position.y, Camera.position.z,
commonY);
if (!iconPos) { printd("avatarmy icon pos null"); return;}
var iconDimensions = avatarIconPlaneDimensions();
var avatarPos = MyAvatar.position;
var cameraPos = Camera.position;
var borderPoints = [
computePointAtPlaneY(0, 0, commonY),
computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ];
var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y,
avatarPos.z, cameraPos.x, cameraPos.y, cameraPos.z, commonY);
var x = (p1.x - borderPoints[0].x) * (Window.innerWidth)
/ (borderPoints[1].x - borderPoints[0].x);
var y = (p1.z - borderPoints[0].z) * (Window.innerHeight)
/ (borderPoints[1].z - borderPoints[0].z);
if (!myAvatarIcon && MyAvatar.SELF_ID) {
myAvatarIcon = Overlays.addOverlay("circle3d", {
color: uniqueColor.convertHexToRGB(uniqueColor.getColor(MyAvatar.SELF_ID)),
dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS,
rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
innerRadius: 1.8,
outerRadius: 2,
isSolid: true,
visible: false
});
}
if (!myAvatarName) {
myAvatarName = Overlays.addOverlay("text", {
width: 100,
height: AVATAR_DISPLAY_NAME_HEIGHT,
textAlignCenter: true,
color: { red: 255, green: 255, blue: 255},
backgroundAlpha: 0.0,
font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true},
textRaiseColor: { red: 0, green: 0, blue: 0},
visible: false,
text: "Me"
});
}
if (myAvatarIcon) {
Overlays.editOverlay(myAvatarIcon, {
visible : true,
dimensions : iconDimensions,
position : iconPos
});
}
var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06);
Overlays.editOverlay(myAvatarName, {
visible : true,
x : x - 18 + (iconDimensions.y - 0.03) * 2 / 0.06,
y : y + iconDimensions.y * 550,
font : {
size : textSize,
bold : true
},
});
}
function hideAllAvatarIcons() {
var len = avatarsIcons.length;
for (var i = 0; i < len; i++) {
Overlays.editOverlay(avatarsIcons[i], {
visible : false
});
}
len = avatarsNames.length;
for (var j = 0; j < len; j++) {
Overlays.editOverlay(avatarsNames[j], {
visible : false
});
}
if (myAvatarIcon) {
Overlays.editOverlay(myAvatarIcon, {
visible : false
});
}
Overlays.editOverlay(myAvatarName, {
visible : false
})
}
function renderAllOthersAvatarIcons() {
var avatarPos;
var iconDimensions = avatarIconPlaneDimensions();
var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y);
var borderPoints = [
computePointAtPlaneY(0, 0, commonY),
computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ];
for ( var QUuid in avatarsData) {
if (avatarsData.hasOwnProperty(QUuid)) {
if (AvatarList.getAvatar(QUuid) != null) {
avatarPos = AvatarList.getAvatar(QUuid).position;
var cameraPos = Camera.position;
var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, avatarPos.z,
cameraPos.x, cameraPos.y, cameraPos.z,
commonY);
var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) / (borderPoints[1].x - borderPoints[0].x);
var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) / (borderPoints[1].z - borderPoints[0].z);
if (avatarsData[QUuid].icon != undefined) {
var iconPos = findLineToHeightIntersectionCoords( avatarPos.x, avatarPos.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, avatarPos.z,
Camera.position.x, Camera.position.y, Camera.position.z,
commonY);
if (!iconPos) { print ("avatar icon pos bad for " + QUuid); continue; }
if (avatarsData[QUuid].needRefresh) {
var avat = AvatarList.getAvatar(QUuid);
if (avat && avat.displayName) {
Overlays.editOverlay(avatarsData[QUuid].name, {
width : avat.displayName.length
* AVATAR_DISPLAY_NAME_CHAR_WIDTH,
text : avat.displayName,
textAlignCenter : true
});
avatarsData[QUuid].needRefresh = false;
}
}
var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06);
Overlays.editOverlay(avatarsData[QUuid].icon, {
visible : true,
dimensions : iconDimensions,
position : iconPos
});
Overlays.editOverlay(avatarsData[QUuid].name, {
visible : true,
x : x - avatarsData[QUuid].textWidth * 0.5,
y : y + iconDimensions.y * 550,
font : {
size : textSize,
bold : true
}
});
}
}
}
}
}
var ICON_ENTITY_DEFAULT_DIMENSIONS = {
x : 0.10,
y : 0.00001,
z : 0.10
};
function teleportIconModelDimensions(y) {
var teleportModelDimensions = ICON_ENTITY_DEFAULT_DIMENSIONS;
var xz = -0.002831 * (radarHeight - y) + 0.1;
teleportModelDimensions.x = xz;
teleportModelDimensions.z = xz;
// reuse object
return teleportModelDimensions;
}
/*******************************************************************************
*
******************************************************************************/
function startRadar() {
printd("avatar added my avatar is " + MyAvatar.sessionUUID);
saveAllOthersAvatarsData();
Camera.mode = "independent";
initCameraOverMyAvatar();
Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0);
radar = true;
Controller.setVPadEnabled(false); // this was done before in CompositeExtra in the DisplayPlugin (Checking for camera not independent, not radar mode)
connectRadarModeEvents();
}
function endRadar() {
printd("-- endRadar");
Camera.mode = "first person look at";
radar = false;
Controller.setVPadEnabled(true);
disconnectRadarModeEvents();
hideAllAvatarIcons();
}
function onRadarModeClicked() {
startRadar();
}
function onMyViewModeClicked() {
endRadar();
}
radarModeInterface.startRadarMode = function() {
startRadar();
};
radarModeInterface.endRadarMode = function() {
endRadar();
};
radarModeInterface.init = function() {
init();
}
radarModeInterface.setUniqueColor = function(c) {
uniqueColor = c;
};
module.exports = radarModeInterface;
function updateRadar() {
// Update avatar icons
if (startedDraggingCamera) {
hideAllAvatarIcons();
startedDraggingCamera = false;
} else if (!draggingCamera) {
renderMyAvatarIcon();
renderAllOthersAvatarIcons();
}
}
function valueIfDefined(value) {
return value !== undefined ? value : "";
}
function connectRadarModeEvents() {
Script.update.connect(updateRadar); // 60Hz loop
Controller.keyPressEvent.connect(keyPressEvent);
Controller.touchUpdateEvent.connect(touchUpdate);
Window.domainChanged.connect(domainChanged);
MyAvatar.positionGoneTo.connect(positionGoneTo);
}
function initCameraOverMyAvatar() {
radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA;
Camera.position = {
x : MyAvatar.position.x,
y : radarHeight,
z : MyAvatar.position.z
};
}
function domainChanged() {
initCameraOverMyAvatar();
}
function positionGoneTo() {
Camera.position = {
x : MyAvatar.position.x,
y : radarHeight,
z : MyAvatar.position.z
};
}
function disconnectRadarModeEvents() {
Script.update.disconnect(updateRadar);
Controller.keyPressEvent.disconnect(keyPressEvent);
Controller.touchUpdateEvent.disconnect(touchUpdate);
MyAvatar.positionGoneTo.disconnect(positionGoneTo);
Window.domainChanged.disconnect(domainChanged);
}
function init() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
Controller.touchBeginEvent.connect(touchBegin);
Controller.touchEndEvent.connect(touchEnd);
AvatarList.avatarAddedEvent.connect(avatarAdded);
AvatarList.avatarRemovedEvent.connect(avatarRemoved);
}