mirror of
https://github.com/AleziaKurdis/Overte-community-apps.git
synced 2025-04-13 01:22:11 +02:00
commit
aa4bcd1097
14 changed files with 2816 additions and 0 deletions
|
@ -52,6 +52,15 @@ var metadata = { "applications": [
|
|||
"jsfile": "vr-grabscale/VRBuildGrabScale.js",
|
||||
"icon": "vr-grabscale/logo.png",
|
||||
"caption": "VR SCALE"
|
||||
},
|
||||
{
|
||||
"isActive": true,
|
||||
"directory": "radar",
|
||||
"name": "Radar",
|
||||
"description": "Show where people are and teleport in the domain.",
|
||||
"jsfile": "radar/radar.js",
|
||||
"icon": "radar/assets/radar-i.svg",
|
||||
"caption": "RADAR"
|
||||
}
|
||||
]
|
||||
};
|
3
applications/radar/LICENSE
Normal file
3
applications/radar/LICENSE
Normal file
|
@ -0,0 +1,3 @@
|
|||
Distributed under the Apache License, Version 2.0.
|
||||
|
||||
See: http://www.apache.org/licenses/LICENSE-2.0.html
|
10
applications/radar/README.md
Normal file
10
applications/radar/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# radar.js
|
||||
|
||||
Show where people are and teleport in the domain.
|
||||
|
||||
Information: http://ctrlaltstudio.com/vircadia/radar
|
||||
|
||||
The master source for this app is in the following repository:
|
||||
https://github.com/ctrlaltdavid/cas-vircadia-stuff/tree/master/apps/radar
|
||||
|
||||
[LICENSE](LICENSE)
|
11
applications/radar/assets/radar-a.svg
Normal file
11
applications/radar/assets/radar-a.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="50" height="50" viewBox="0 0 50.00 50.00" enable-background="new 0 0 50.00 50.00" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="none" stroke-width="3.44274" stroke-linejoin="miter" stroke="#000000" stroke-opacity="1" d="M 44.3755,25C 44.3755,35.7286 35.7286,44.3755 25,44.3755C 14.3515,44.3755 5.6245,35.7286 5.6245,25C 5.6245,14.3515 14.3515,5.6245 25,5.6245C 35.7286,5.6245 44.3755,14.3515 44.3755,25 Z "/>
|
||||
<line fill="none" stroke-width="3.44274" stroke-linecap="round" stroke-linejoin="miter" stroke="#000000" stroke-opacity="1" x1="36.209" y1="14.9119" x2="25.4003" y2="25.3203"/>
|
||||
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linecap="round" stroke-linejoin="miter" d="M 28.5,14C 28.5,15.371 27.3889,16.5 26.0397,16.5C 24.6111,16.5 23.5,15.371 23.5,14C 23.5,12.629 24.6111,11.5 26.0397,11.5C 27.3889,11.5 28.5,12.629 28.5,14 Z "/>
|
||||
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linecap="round" stroke-linejoin="miter" d="M 34.5,33C 34.5,34.371 33.3889,35.5 31.9603,35.5C 30.6111,35.5 29.5,34.371 29.5,33C 29.5,31.629 30.6111,30.5 31.9603,30.5C 33.3889,30.5 34.5,31.629 34.5,33 Z "/>
|
||||
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linecap="round" stroke-linejoin="miter" d="M 21.4952,33C 21.4952,34.371 20.3662,35.5 18.9952,35.5C 17.6242,35.5 16.4952,34.371 16.4952,33C 16.4952,31.629 17.6242,30.5 18.9952,30.5C 20.3662,30.5 21.4952,31.629 21.4952,33 Z "/>
|
||||
</g>
|
||||
</svg>
|
BIN
applications/radar/assets/radar-circle-overlay.png
Normal file
BIN
applications/radar/assets/radar-circle-overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 905 B |
11
applications/radar/assets/radar-i.svg
Normal file
11
applications/radar/assets/radar-i.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="50" height="50" viewBox="0 0 50.00 50.00" enable-background="new 0 0 50.00 50.00" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="none" stroke-width="3.44274" stroke-linejoin="miter" stroke="#FFFFFF" stroke-opacity="1" d="M 44.3755,25C 44.3755,35.7286 35.7286,44.3755 25,44.3755C 14.3515,44.3755 5.6245,35.7286 5.6245,25C 5.6245,14.3515 14.3515,5.6245 25,5.6245C 35.7286,5.6245 44.3755,14.3515 44.3755,25 Z "/>
|
||||
<line fill="none" stroke-width="3.44274" stroke-linecap="round" stroke-linejoin="miter" stroke="#FFFFFF" stroke-opacity="1" x1="36.209" y1="14.9119" x2="25.4003" y2="25.3203"/>
|
||||
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linecap="round" stroke-linejoin="miter" d="M 28.5,14C 28.5,15.371 27.3889,16.5 26.0397,16.5C 24.6111,16.5 23.5,15.371 23.5,14C 23.5,12.629 24.6111,11.5 26.0397,11.5C 27.3889,11.5 28.5,12.629 28.5,14 Z "/>
|
||||
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linecap="round" stroke-linejoin="miter" d="M 34.5,33C 34.5,34.371 33.3889,35.5 31.9603,35.5C 30.6111,35.5 29.5,34.371 29.5,33C 29.5,31.629 30.6111,30.5 31.9603,30.5C 33.3889,30.5 34.5,31.629 34.5,33 Z "/>
|
||||
<path fill="#FFFFFF" fill-opacity="1" stroke-width="0.2" stroke-linecap="round" stroke-linejoin="miter" d="M 21.4952,33C 21.4952,34.371 20.3662,35.5 18.9952,35.5C 17.6242,35.5 16.4952,34.371 16.4952,33C 16.4952,31.629 17.6242,30.5 18.9952,30.5C 20.3662,30.5 21.4952,31.629 21.4952,33 Z "/>
|
||||
</g>
|
||||
</svg>
|
BIN
applications/radar/html/fonts/FiraSans-Regular.ttf
Normal file
BIN
applications/radar/html/fonts/FiraSans-Regular.ttf
Normal file
Binary file not shown.
BIN
applications/radar/html/fonts/FiraSans-SemiBold.ttf
Normal file
BIN
applications/radar/html/fonts/FiraSans-SemiBold.ttf
Normal file
Binary file not shown.
BIN
applications/radar/html/fonts/Raleway-Regular.ttf
Normal file
BIN
applications/radar/html/fonts/Raleway-Regular.ttf
Normal file
Binary file not shown.
BIN
applications/radar/html/fonts/Raleway-SemiBold.ttf
Normal file
BIN
applications/radar/html/fonts/Raleway-SemiBold.ttf
Normal file
Binary file not shown.
474
applications/radar/html/radar.css
Normal file
474
applications/radar/html/radar.css
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*!
|
||||
radar.css
|
||||
|
||||
Created by David Rowe on 19 Nov 2017.
|
||||
Copyright 2017-2020 David Rowe.
|
||||
|
||||
Information: http://ctrlaltstudio.com/vircadia/radar
|
||||
|
||||
Distributed under the Apache License, Version 2.0.
|
||||
See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
Attributions:
|
||||
- Raleway font: https://github.com/impallari/Raleway. Copyright (c) 2010, Matt McInerney (matt@pixelspread.com), Copyright (c)
|
||||
2011, Pablo Impallari (www.impallari.com|impallari@gmail.com), Copyright (c) 2011, Rodrigo Fuenzalida
|
||||
(www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name < Raleway >. Licensed under the SIL Open Font License,
|
||||
Version 1.1, available at http://scripts.sil.org/OFL.
|
||||
- Fira Sans font: https://github.com/mozilla/Fira. Digitized data copyright (c) 2012-2015, The Mozilla
|
||||
Foundation and Telefonica S.A. with Reserved Font Name < Fira >. Licensed under the SIL Open Font License, Version 1.1,
|
||||
available at http://scripts.sil.org/OFL.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: Raleway-Regular;
|
||||
src: url(./fonts/Raleway-Regular.ttf)
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Raleway-SemiBold;
|
||||
src: url(./fonts/Raleway-SemiBold.ttf)
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: FiraSans-Regular;
|
||||
src: url(./fonts/FiraSans-Regular.ttf)
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: FiraSans-SemiBold;
|
||||
src: url(./fonts/FiraSans-SemiBold.ttf)
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #e3e3e3;
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
position: relative;
|
||||
top: 9px;
|
||||
font-family: "Raleway-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
color: #ffffff;
|
||||
text-shadow: 1px 1px #252525;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
border: none;
|
||||
border-top: 1px solid #252525;
|
||||
border-bottom: 1px solid #575757;
|
||||
}
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
header div {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 8px;
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header div span {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
header div span:hover {
|
||||
color: #00b4ef;
|
||||
}
|
||||
|
||||
header div #help-button {
|
||||
font-size: 21px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
header hr {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 40px 20px 40px 20px;
|
||||
}
|
||||
|
||||
#radar-circle {
|
||||
position: relative;
|
||||
width: 420px;
|
||||
height: 420px;
|
||||
margin: 0 auto 0 auto;
|
||||
border-radius: 210px;
|
||||
background-color: rgb(48, 48, 48);
|
||||
}
|
||||
|
||||
#radar-circle-display {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#radar-circle-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 214px;
|
||||
border: 1px solid #575757;
|
||||
background: radial-gradient(rgba(48, 48, 48, 0.0) 0%, rgba(48, 48, 48, 0.0) 60%, rgba(87, 87, 87, 1.0) 80%);
|
||||
pointer-events: none;
|
||||
clip-path: circle(210px at center);
|
||||
}
|
||||
|
||||
#radar-circle-overlay img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dot {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-left: -4px;
|
||||
margin-top: -4px;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
z-index: 20;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dot:hover {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-top: -6px;
|
||||
margin-left: -6px;
|
||||
border-radius: 6px;
|
||||
padding: 2px;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.dot.highlighted {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-top: -6px;
|
||||
margin-left: -6px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid #00ff00;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.dot.labeled {
|
||||
z-index: 30 !important;
|
||||
}
|
||||
|
||||
.my-dot {
|
||||
background-color: #00ff00 !important;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#avatar-label {
|
||||
position: absolute;
|
||||
height: 14px;
|
||||
padding: 0 3px 0 3px;
|
||||
border-radius: 3px;
|
||||
margin-top: -16px;
|
||||
color: #ffffff;
|
||||
background-color: rgba(48, 48, 48, 0.35);
|
||||
font-family: "Raleway-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 13px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
z-index: 40;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#teleport-circle {
|
||||
position: absolute;
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
border-radius: 41px;
|
||||
background-color: rgba(0, 255, 0, 0.2);
|
||||
display: none;
|
||||
}
|
||||
|
||||
#avatar-count {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-family: "Raleway-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#avatar-count span {
|
||||
display: inline-block;
|
||||
min-width: 15px;
|
||||
text-align: right;
|
||||
font-family: "FiraSans-Regular";
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#radar-scale {
|
||||
width: 420px;
|
||||
margin: 10px auto 10px auto;
|
||||
}
|
||||
|
||||
#radar-scale td {
|
||||
width: 33%;
|
||||
text-align: center;
|
||||
font-family: "FiraSans-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#radar-scale td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#radar-scale td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 40px 20px 0 20px;
|
||||
}
|
||||
|
||||
footer hr {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
||||
#scale {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#scale thead td {
|
||||
text-align: left;
|
||||
padding-bottom: 5px;
|
||||
font-family: "Raleway-SemiBold";
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#scale tbody td {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 7.6%;
|
||||
text-align: center;
|
||||
font-family: "FiraSans-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#scale input[type=radio]:checked + label::before {
|
||||
line-height: 11px;
|
||||
}
|
||||
|
||||
.units {
|
||||
font-family: "Raleway-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type=radio] + label::before {
|
||||
content: "";
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
padding: 0;
|
||||
background-image: linear-gradient(#7D7D7D, #6B6A6B);
|
||||
display: inline-block;
|
||||
margin: 0 10px 0 10px;
|
||||
line-height: 11px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
input[type=radio]:checked + label::before {
|
||||
font-size: 18px;
|
||||
content: "\25cf";
|
||||
color: #00b4ef;
|
||||
line-height: 9px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
input[type=radio] + label:hover::before {
|
||||
background-image: linear-gradient(#ffffff, #afafaf);
|
||||
}
|
||||
|
||||
|
||||
form.dialog {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
z-index: 100;
|
||||
border-radius: 15px;
|
||||
border: 1px solid #676767;
|
||||
box-shadow: 1px 1px #252525;
|
||||
background-color: #505050;
|
||||
font-family: "Raleway-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
color: #f3f3f3;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
form.dialog.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Raleway-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
text-shadow: 1px 1px #252525;
|
||||
}
|
||||
|
||||
#version {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
font-family: "FiraSans-Regular";
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
font-style: italic;
|
||||
color: #e3e3e3;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
li li {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00c0ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.radio {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.radio li {
|
||||
list-style-type: none;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.ok {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.ok input {
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-radius: 7.5px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #f8f8f8;
|
||||
}
|
||||
|
||||
.ok input {
|
||||
background: linear-gradient(#00b4ef, #1080b8);
|
||||
}
|
||||
|
||||
.ok input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ok input:hover {
|
||||
background: linear-gradient(#00b4ef, #00b4ef);
|
||||
}
|
||||
|
||||
#help-content {
|
||||
margin-top: 15px;
|
||||
padding-right: 4px;
|
||||
height: 502px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#help-content p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 9px;
|
||||
background: #484848;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 9px;
|
||||
background: #707070;
|
||||
}
|
179
applications/radar/html/radar.html
Normal file
179
applications/radar/html/radar.html
Normal file
|
@ -0,0 +1,179 @@
|
|||
<!--
|
||||
radar.html
|
||||
|
||||
Created by David Rowe on 19 Nov 2017.
|
||||
Copyright 2017-2020 David Rowe.
|
||||
|
||||
Information: http://ctrlaltstudio.com/vircadia/radar
|
||||
|
||||
Distributed under the Apache License, Version 2.0.
|
||||
See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Radar</title>
|
||||
<link rel="stylesheet" type="text/css" href="radar.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Radar</h1>
|
||||
<div>
|
||||
<span id="settings-button">⛭</span>
|
||||
<span id="help-button">?</span>
|
||||
</div>
|
||||
<hr />
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div id="radar-circle">
|
||||
<div id="radar-circle-overlay">
|
||||
<img src="../assets/radar-circle-overlay.png" />
|
||||
<div id="teleport-circle"></div>
|
||||
</div>
|
||||
<div id="radar-circle-display"></div>
|
||||
<div id="avatar-label"></div>
|
||||
<div id="avatar-count">Avatars: <span>0</span></div>
|
||||
</div>
|
||||
<table id="radar-scale">
|
||||
<tr><td id="radar-scale-left"></td><td>0m</td><td id="radar-scale-right"></td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<hr />
|
||||
<form id="controls">
|
||||
<table id="scale">
|
||||
<thead>
|
||||
<tr><td colspan="13">Scale <span class="units">m</span></td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="radio" name="scale" value="10" id="scale10" /><label for="scale10">10</label></td>
|
||||
<td><input type="radio" name="scale" value="20" id="scale20" /><label for="scale20">20</label></td>
|
||||
<td><input type="radio" name="scale" value="50" id="scale50" /><label for="scale50">50</label></td>
|
||||
<td><input type="radio" name="scale" value="100" id="scale100" /><label for="scale100">100</label></td>
|
||||
<td><input type="radio" name="scale" value="200" id="scale200" /><label for="scale200">200</label></td>
|
||||
<td><input type="radio" name="scale" value="500" id="scale500" /><label for="scale500">500</label></td>
|
||||
<td><input type="radio" name="scale" value="1000" id="scale1000" /><label for="scale1000">1k</label></td>
|
||||
<td><input type="radio" name="scale" value="2000" id="scale2000" /><label for="scale2000">2k</label></td>
|
||||
<td><input type="radio" name="scale" value="5000" id="scale5000" /><label for="scale5000">5k</label></td>
|
||||
<td><input type="radio" name="scale" value="10000" id="scale10000" /><label for="scale10000">10k</label></td>
|
||||
<td><input type="radio" name="scale" value="20000" id="scale20000" /><label for="scale20000">20k</label></td>
|
||||
<td><input type="radio" name="scale" value="50000" id="scale50000" /><label for="scale50000">50k</label></td>
|
||||
<td><input type="radio" name="scale" value="100000" id="scale100000" /><label for="scale100000">100k</label></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</footer>
|
||||
|
||||
<form id="settings" class="dialog">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<p>Show own avatar:</p>
|
||||
<ul class="radio">
|
||||
<li><input type="radio" name="show-own" value="2" id="show-own-always" /><label for="show-own-always">Always.</label></li>
|
||||
<li><input type="radio" name="show-own" value="1" id="show-own-third" /><label for="show-own-third">When not in first person view.</label></li>
|
||||
<li><input type="radio" name="show-own" value="0" id="show-own-never" /><label for="show-own-never">Never.</label></li>
|
||||
</ul>
|
||||
|
||||
<p>Refresh rate:</p>
|
||||
<ul class="radio">
|
||||
<li><input type="radio" name="refresh-rate" value="4" id="refresh-rate-fastest" /><label for="refresh-rate-fastest">Fast.</label></li>
|
||||
<li><input type="radio" name="refresh-rate" value="3" id="refresh-rate-faster" /><label for="refresh-rate-faster">Medium-fast.</label></li>
|
||||
<li><input type="radio" name="refresh-rate" value="2" id="refresh-rate-medium" /><label for="refresh-rate-medium">Medium.</label></li>
|
||||
<li><input type="radio" name="refresh-rate" value="1" id="refresh-rate-slower" /><label for="refresh-rate-slower">Medium-slow.</label></li>
|
||||
<li><input type="radio" name="refresh-rate" value="0" id="refresh-rate-slowest" /><label for="refresh-rate-slowest">Slow.</label></li>
|
||||
</ul>
|
||||
|
||||
<div class="ok">
|
||||
<input type="button" value="OK" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="help" class="dialog">
|
||||
<h2>About</h2>
|
||||
|
||||
<div id="version">2.3.0</div>
|
||||
|
||||
<div id="help-content">
|
||||
|
||||
<p>Shows where people are in the domain.</p>
|
||||
|
||||
<ul>
|
||||
<li>Works in both HMD and desktop.</li>
|
||||
<li>The center is your camera location.</li>
|
||||
<li>
|
||||
The orientation is per:
|
||||
<ul>
|
||||
<li>The tablet in HMD mode.</li>
|
||||
<li>Your camera in desktop mode.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Your avatar is displayed as a green dot per the settings.</li>
|
||||
<li>
|
||||
Other peoples’ avatar dots are colored:
|
||||
<ul>
|
||||
<li>Blue if above you: blueness increases with height difference.</li>
|
||||
<li>White if level with you.</li>
|
||||
<li>Red if below you: redness increases with height difference.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
To show display names:
|
||||
<ul>
|
||||
<li>Hover a dot to show the avatar’s display name.</li>
|
||||
<li>Click the dot to persist showing its display name.</li>
|
||||
<li>Stop showing a display name by clicking the dot, the display name, or the radar background.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
To teleport:
|
||||
<ul>
|
||||
<li>Click and hold on the radar circle. A green search circle is displayed.</li>
|
||||
<li>The horizontally closest avatar is highlighted as are any of similar elevation.</li>
|
||||
<li>Release the click to teleport to that position at the elevation of the closest avatar, or your
|
||||
current elevation if no avatar is in the search circle.</li>
|
||||
<li>To cancel a teleport, drag off the radar circle.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The scale sets the search radius and distances above and below.</li>
|
||||
<li>The avatars count is the number of avatars displayed.</li>
|
||||
</ul>
|
||||
|
||||
<p>Settings:</p>
|
||||
<ul>
|
||||
<li>Show own avatar: Configure when to show a dot for your own avatar.</li>
|
||||
<li>Refresh rate: Configure how often the avatar dots and radar orientation are updated.</li>
|
||||
</ul>
|
||||
|
||||
<p>Disclaimers:</p>
|
||||
<ul>
|
||||
<li>The user identification provided by this app is not guaranteed: users can set their display name to
|
||||
whatever they like.</li>
|
||||
<li>The content of the display names displayed by this app is not moderated by this app.</li>
|
||||
</ul>
|
||||
|
||||
<p>More information: <a href="http://ctrlaltstudio.com/vircadia/radar">http://ctrlaltstudio.com</a><br />
|
||||
Copyright 2017-2020 David Rowe.<br />
|
||||
Distributed under the Apache License, 2.0.</a>.<br />
|
||||
Donations appreciated.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ok">
|
||||
<input type="button" value="OK" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="radar.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
1275
applications/radar/html/radar.js
Normal file
1275
applications/radar/html/radar.js
Normal file
File diff suppressed because it is too large
Load diff
844
applications/radar/radar.js
Normal file
844
applications/radar/radar.js
Normal file
|
@ -0,0 +1,844 @@
|
|||
/*!
|
||||
radar.js
|
||||
|
||||
Created by David Rowe on 16 Nov 2017.
|
||||
Copyright 2017-2020 David Rowe.
|
||||
|
||||
Information: http://ctrlaltstudio.com/vircadia/radar
|
||||
|
||||
Disclaimers:
|
||||
1. The user identification provided by this app is not guaranteed: users can set their display name to whatever they like.
|
||||
2. The content of the display names displayed by this app is not moderated by this app.
|
||||
|
||||
Distributed under the Apache License, Version 2.0.
|
||||
See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
*/
|
||||
|
||||
/* global AvatarManager, Camera, HMD, MyAvatar, Overlays, Quat, Settings, Vec3, Window, location */
|
||||
|
||||
(function () {
|
||||
|
||||
"use strict";
|
||||
|
||||
var APP_NAME = "Radar",
|
||||
APP_VERSION = "2.3.0", // Version number also needs to be set in web page HTML and JavaScript files.
|
||||
|
||||
SIMULATE = false,
|
||||
INSTRUMENT = false,
|
||||
|
||||
// Controls.
|
||||
RADAR_RANGE_DEFAULT = 20,
|
||||
|
||||
// EventBridge ID.
|
||||
SCRIPT_ID = "cas.radar",
|
||||
|
||||
// EventBridge messages.
|
||||
READY_MESSAGE = "readyMessage", // Engine <=> Dialog
|
||||
VERSIONS_MESSAGE = "versionsMessage", // Engine <== Dialog
|
||||
GET_CONTROLS_MESSAGE = "getControlsMessage", // Engine <== Dialog
|
||||
SET_CONTROLS_MESSAGE = "setControlsMessage", // Engine <=> Dialog
|
||||
GET_SETTINGS_MESSAGE = "getSettingsMessage", // Engine <== Dialog
|
||||
SET_SETTINGS_MESSAGE = "setSettingsMessage", // Engine <=> Dialog
|
||||
ROTATION_MESSAGE = "rotationMessage", // Engine ==> Dialog
|
||||
AVATARS_MESSAGE = "avatarsMessage", // Engine <=> Dialog - World coordinates relative to camera.
|
||||
CLEAR_MESSAGE = "clearMessage", // Engine ==> Dialog
|
||||
TELEPORT_MESSAGE = "teleportMessage", // Engine <== Dialog - World coordinates relative to camera.
|
||||
OPEN_URL_MESSAGE = "openURLMessage", // Engine <== Dialog
|
||||
LOG_MESSAGE = "logMessage", // Engine <== Dialog
|
||||
|
||||
// Application objects.
|
||||
Preferences,
|
||||
Communications,
|
||||
Updates,
|
||||
App,
|
||||
|
||||
// Global objects.
|
||||
tablet = null,
|
||||
|
||||
INFO = "INFO:",
|
||||
//WARN = "WARN:",
|
||||
ERROR = "ERROR:",
|
||||
ERROR_MISSING_CASE = "Missing case:";
|
||||
|
||||
if (SIMULATE) {
|
||||
Script.include("simulate.js");
|
||||
}
|
||||
|
||||
function log() {
|
||||
var i, length, strings = [];
|
||||
|
||||
for (i = 0, length = arguments.length; i < length; i++) {
|
||||
strings.push(arguments[i]);
|
||||
}
|
||||
|
||||
print("[CtrlAltStudio radar.js] " + strings.join(" "));
|
||||
}
|
||||
|
||||
//#region Preferences ======================================================================================================
|
||||
|
||||
Preferences = (function () {
|
||||
// Manages the application preferences - "controls" and "settings" in the dialog.
|
||||
|
||||
var PREFERENCES_ROOT = "com.ctrlaltstudio.radar.",
|
||||
RADAR_RANGE_SETTING = PREFERENCES_ROOT + "range", // Must be different name to variable for obfuscation.
|
||||
radarRange = RADAR_RANGE_DEFAULT,
|
||||
SHOW_OWN_SETTING = PREFERENCES_ROOT + "ownAvatar", // Must be different name to variable for obfuscation.
|
||||
SHOW_OWN_NEVER = 0,
|
||||
SHOW_OWN_THIRD = 1,
|
||||
SHOW_OWN_ALWAYS = 2,
|
||||
SHOW_OWN_DEFAULT = SHOW_OWN_THIRD,
|
||||
showOwn = SHOW_OWN_DEFAULT,
|
||||
REFRESH_RATE_SETTING = PREFERENCES_ROOT + "updateRate", // Must be different name to variable for obfuscation.
|
||||
REFRESH_RATE_FASTEST = 4,
|
||||
REFRESH_RATE_FASTER = 3,
|
||||
REFRESH_RATE_MEDIUM = 2,
|
||||
REFRESH_RATE_SLOWER = 1,
|
||||
REFRESH_RATE_SLOWEST = 0,
|
||||
REFRESH_RATE_DEFAULT = REFRESH_RATE_MEDIUM,
|
||||
refreshRate = REFRESH_RATE_DEFAULT,
|
||||
preferencesChangedCallback = null;
|
||||
|
||||
function getPreferences() {
|
||||
return {
|
||||
radarRange: radarRange,
|
||||
showOwn: showOwn,
|
||||
refreshRate: refreshRate
|
||||
};
|
||||
}
|
||||
|
||||
function getRadarRange() {
|
||||
return radarRange;
|
||||
}
|
||||
|
||||
function setRadarRange(range) {
|
||||
radarRange = range;
|
||||
Settings.setValue(RADAR_RANGE_SETTING, radarRange);
|
||||
preferencesChangedCallback(getPreferences());
|
||||
}
|
||||
|
||||
function getShowOwn() {
|
||||
return showOwn;
|
||||
}
|
||||
|
||||
function setShowOwn(show) {
|
||||
showOwn = show;
|
||||
Settings.setValue(SHOW_OWN_SETTING, showOwn);
|
||||
preferencesChangedCallback(getPreferences());
|
||||
}
|
||||
|
||||
function getRefreshRate() {
|
||||
return refreshRate;
|
||||
}
|
||||
|
||||
function setRefreshRate(rate) {
|
||||
refreshRate = rate;
|
||||
Settings.setValue(REFRESH_RATE_SETTING, refreshRate);
|
||||
preferencesChangedCallback(getPreferences());
|
||||
}
|
||||
|
||||
function setPreferencesChangedCallback(callback) {
|
||||
preferencesChangedCallback = callback;
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
radarRange = Settings.getValue(RADAR_RANGE_SETTING, RADAR_RANGE_DEFAULT);
|
||||
showOwn = Settings.getValue(SHOW_OWN_SETTING, SHOW_OWN_DEFAULT);
|
||||
refreshRate = Settings.getValue(REFRESH_RATE_SETTING, REFRESH_RATE_DEFAULT);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
return {
|
||||
getRadarRange: getRadarRange,
|
||||
setRadarRange: setRadarRange,
|
||||
SHOW_OWN_NEVER: SHOW_OWN_NEVER,
|
||||
SHOW_OWN_THIRD: SHOW_OWN_THIRD,
|
||||
SHOW_OWN_ALWAYS: SHOW_OWN_ALWAYS,
|
||||
getShowOwn: getShowOwn,
|
||||
setShowOwn: setShowOwn,
|
||||
REFRESH_RATE_SLOWEST: REFRESH_RATE_SLOWEST,
|
||||
REFRESH_RATE_SLOWER: REFRESH_RATE_SLOWER,
|
||||
REFRESH_RATE_MEDIUM: REFRESH_RATE_MEDIUM,
|
||||
REFRESH_RATE_FASTER: REFRESH_RATE_FASTER,
|
||||
REFRESH_RATE_FASTEST: REFRESH_RATE_FASTEST,
|
||||
getRefreshRate: getRefreshRate,
|
||||
setRefreshRate: setRefreshRate,
|
||||
getPreferences: getPreferences,
|
||||
preferencesChanged: {
|
||||
connect: setPreferencesChangedCallback
|
||||
},
|
||||
setUp: setUp,
|
||||
tearDown: tearDown
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Communications ===================================================================================================
|
||||
|
||||
Communications = (function () {
|
||||
// Manages the communications with the Web page script.
|
||||
|
||||
var
|
||||
isVersionsChecked = false,
|
||||
readyCallback = null;
|
||||
|
||||
function onWebEventReceived(data) {
|
||||
var message,
|
||||
avatarPosition,
|
||||
cameraVector,
|
||||
ERROR_FILE_VERSIONS_DONT_MATCH = APP_NAME + " script file version numbers don't match",
|
||||
ERROR_PLEASE_RELOAD_SCRIPT = "Please reload " + APP_NAME + " script.";
|
||||
|
||||
try {
|
||||
message = JSON.parse(data);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.id !== SCRIPT_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case READY_MESSAGE:
|
||||
if (readyCallback) {
|
||||
readyCallback();
|
||||
}
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
id: SCRIPT_ID,
|
||||
type: READY_MESSAGE,
|
||||
hmd: HMD.active
|
||||
}));
|
||||
break;
|
||||
case GET_CONTROLS_MESSAGE:
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
id: SCRIPT_ID,
|
||||
type: SET_CONTROLS_MESSAGE,
|
||||
controls: {
|
||||
range: Preferences.getRadarRange()
|
||||
}
|
||||
}));
|
||||
break;
|
||||
case SET_CONTROLS_MESSAGE:
|
||||
Preferences.setRadarRange(message.controls.range);
|
||||
break;
|
||||
case GET_SETTINGS_MESSAGE:
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
id: SCRIPT_ID,
|
||||
type: SET_SETTINGS_MESSAGE,
|
||||
settings: {
|
||||
showOwn: Preferences.getShowOwn(),
|
||||
refreshRate: Preferences.getRefreshRate()
|
||||
}
|
||||
}));
|
||||
break;
|
||||
case SET_SETTINGS_MESSAGE:
|
||||
Preferences.setShowOwn(message.settings.showOwn);
|
||||
Preferences.setRefreshRate(message.settings.refreshRate);
|
||||
break;
|
||||
case AVATARS_MESSAGE:
|
||||
Updates.avatarsDisplayed();
|
||||
break;
|
||||
case TELEPORT_MESSAGE:
|
||||
avatarPosition = MyAvatar.position;
|
||||
cameraVector = Vec3.subtract(Camera.position, avatarPosition);
|
||||
if (message.vector.y === null) {
|
||||
message.vector.y = -cameraVector.y;
|
||||
}
|
||||
MyAvatar.goToLocation(Vec3.sum(Vec3.sum(avatarPosition, message.vector), cameraVector),
|
||||
false, undefined, message.isAvatar, true);
|
||||
break;
|
||||
case OPEN_URL_MESSAGE:
|
||||
Window.openUrl(message.url);
|
||||
break;
|
||||
case VERSIONS_MESSAGE:
|
||||
if (!isVersionsChecked) {
|
||||
if (message.scriptVersion !== APP_VERSION || message.htmlVersion !== APP_VERSION) {
|
||||
log(ERROR, ERROR_FILE_VERSIONS_DONT_MATCH + ": " + APP_VERSION + ", " + message.scriptVersion
|
||||
+ " (script), " + message.htmlVersion + " (HTML)");
|
||||
Window.alert(ERROR_FILE_VERSIONS_DONT_MATCH + "!\n" + ERROR_PLEASE_RELOAD_SCRIPT);
|
||||
}
|
||||
isVersionsChecked = true;
|
||||
}
|
||||
break;
|
||||
case LOG_MESSAGE:
|
||||
log(message.message);
|
||||
break;
|
||||
default:
|
||||
log(ERROR, ERROR_MISSING_CASE, 0, data);
|
||||
}
|
||||
}
|
||||
|
||||
function sendAvatarData(radarRange, avatarData) {
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
id: SCRIPT_ID,
|
||||
type: AVATARS_MESSAGE,
|
||||
range: radarRange,
|
||||
data: avatarData
|
||||
}));
|
||||
}
|
||||
|
||||
function clearAvatarData() {
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
id: SCRIPT_ID,
|
||||
type: CLEAR_MESSAGE
|
||||
}));
|
||||
}
|
||||
|
||||
function sendRotation(rotation) {
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
id: SCRIPT_ID,
|
||||
type: ROTATION_MESSAGE,
|
||||
rotation: rotation
|
||||
}));
|
||||
}
|
||||
|
||||
function connectReadyCallback(callback) {
|
||||
readyCallback = callback;
|
||||
}
|
||||
|
||||
function disconnectReadyCallback(callback) {
|
||||
if (readyCallback === callback) {
|
||||
readyCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
onWebEventReceived: onWebEventReceived,
|
||||
ready: {
|
||||
connect: connectReadyCallback,
|
||||
disconnect: disconnectReadyCallback
|
||||
},
|
||||
sendAvatarData: sendAvatarData,
|
||||
clearAvatarData: clearAvatarData,
|
||||
sendRotation: sendRotation
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Updates ==========================================================================================================
|
||||
|
||||
Updates = (function () {
|
||||
// Main update loop.
|
||||
|
||||
var radarRange,
|
||||
RADAR_SEARCH_MULTIPLIER = Math.sqrt(2), // Encompass search cylinder.
|
||||
radarSearchRange,
|
||||
cameraMode,
|
||||
showOwn,
|
||||
isShowOwn,
|
||||
FIRST_PERSON_CAMERA_MODES = ["first person", "first person look at"],
|
||||
refreshRate,
|
||||
|
||||
ROTATION_INTERVALS = [
|
||||
200, // Slow
|
||||
150,
|
||||
100, // Medium
|
||||
50,
|
||||
25 // Fast
|
||||
],
|
||||
rotationInterval = ROTATION_INTERVALS[Preferences.REFRESH_RATE_MEDIUM],
|
||||
rotationTimer = null,
|
||||
|
||||
AVATAR_INTERVALS = [
|
||||
2000, // Slow
|
||||
1000,
|
||||
750, // Medium
|
||||
350,
|
||||
100 // Fast
|
||||
],
|
||||
avatarsInterval = AVATAR_INTERVALS[Preferences.REFRESH_RATE_MEDIUM],
|
||||
avatarsUpdateTimer = null,
|
||||
timeToPrepareDataStart,
|
||||
timeToPrepareData = 0,
|
||||
timeToDisplayDataStart,
|
||||
timeToDisplayData = 0,
|
||||
targetSendTime = 0,
|
||||
avatarsSendTimer = null,
|
||||
isDisplayingData = false,
|
||||
|
||||
MAX_DISPLAY_NAME_LENGTH = 30,
|
||||
|
||||
mySessionUUID,
|
||||
|
||||
isRunning = false;
|
||||
|
||||
function calculateIsShowOwn() {
|
||||
isShowOwn = showOwn === Preferences.SHOW_OWN_ALWAYS
|
||||
|| showOwn === Preferences.SHOW_OWN_THIRD && FIRST_PERSON_CAMERA_MODES.indexOf(cameraMode) === -1;
|
||||
}
|
||||
|
||||
function calculateIntervals() {
|
||||
rotationInterval = ROTATION_INTERVALS[refreshRate];
|
||||
avatarsInterval = AVATAR_INTERVALS[refreshRate];
|
||||
}
|
||||
|
||||
function setCameraMode(mode) {
|
||||
cameraMode = mode;
|
||||
calculateIsShowOwn();
|
||||
}
|
||||
|
||||
function setPreferences(preferences) {
|
||||
showOwn = preferences.showOwn;
|
||||
calculateIsShowOwn();
|
||||
radarRange = preferences.radarRange;
|
||||
radarSearchRange = RADAR_SEARCH_MULTIPLIER * radarRange;
|
||||
refreshRate = preferences.refreshRate;
|
||||
calculateIntervals();
|
||||
}
|
||||
|
||||
function updateRotation() {
|
||||
var tabletOrientation,
|
||||
tabletDirection,
|
||||
tabletHorizontalDirection,
|
||||
rotation;
|
||||
|
||||
if (App.isHMDMode()) {
|
||||
tabletOrientation = Overlays.getProperty(HMD.tabletID, "orientation");
|
||||
tabletDirection = Quat.getUp(tabletOrientation); // Out the top of the tablet.
|
||||
if (Vec3.dot(tabletDirection, Vec3.UNIT_Y) > 0.5) {
|
||||
tabletDirection = Vec3.multiply(-1, Quat.getForward(tabletOrientation)); // Out the back of the tablet.
|
||||
}
|
||||
tabletHorizontalDirection = Vec3.cross(tabletDirection, Vec3.UNIT_Y);
|
||||
|
||||
rotation = Vec3.orientedAngle(Vec3.UNIT_X, tabletHorizontalDirection, Vec3.UNIT_Y);
|
||||
} else {
|
||||
rotation = Quat.safeEulerAngles(Camera.orientation).y;
|
||||
}
|
||||
|
||||
Communications.sendRotation(rotation);
|
||||
|
||||
rotationTimer = Script.setTimeout(updateRotation, rotationInterval);
|
||||
}
|
||||
|
||||
/*
|
||||
// Code for displaying avatar dots at positions and elevations for screen snap or testing elevation colours.
|
||||
var sessionIDs = [Uuid.generate(), Uuid.generate(), Uuid.generate(), Uuid.generate(), Uuid.generate(), Uuid.generate(),
|
||||
Uuid.generate(), Uuid.generate(), Uuid.generate(), Uuid.generate(), Uuid.generate(), Uuid.generate()];
|
||||
|
||||
function updateAvatars() {
|
||||
var avatarPositions,
|
||||
avatarNames,
|
||||
avatarDataRange = 10.0,
|
||||
avatarData = [],
|
||||
i;
|
||||
|
||||
avatarPositions = [
|
||||
{ x: 0, y: 0, z: 0 },
|
||||
{ x: -2.5, y: 0, z: -1 },
|
||||
{ x: -2.8, y: 0, z: -1.3 },
|
||||
{ x: -3, y: 0, z: -7 },
|
||||
{ x: -4, y: 0, z: -7.7 },
|
||||
{ x: -4.3, y: 0, z: -7.3 },
|
||||
{ x: -4.3, y: 0, z: -7.8 },
|
||||
{ x: -3.24, y: 0, z: -8.4 },
|
||||
{ x: 1, y: -7, z: 6 },
|
||||
{ x: 5, y: 8, z: -3 }
|
||||
];
|
||||
avatarNames = ["ctrlaltdavid", "", "", "", "", "", "", "", "", "wade", "", ""];
|
||||
|
||||
for (i = 0; i < avatarPositions.length; i += 1) {
|
||||
avatarData.push({
|
||||
uuid: sessionIDs[i],
|
||||
vector: avatarPositions[i],
|
||||
name: avatarNames[i]
|
||||
});
|
||||
}
|
||||
avatarData[0].isMyAvatar = true;
|
||||
|
||||
Communications.sendAvatarData(avatarDataRange, avatarData);
|
||||
avatarsUpdateTimer = Script.setTimeout(updateAvatars, avatarsInterval);
|
||||
}
|
||||
*/
|
||||
|
||||
function limitDisplayName(name) {
|
||||
// Limit display name here so that extraneous data is not sent in message to HTML code.
|
||||
if (name.length > MAX_DISPLAY_NAME_LENGTH) {
|
||||
return name.slice(0, MAX_DISPLAY_NAME_LENGTH) + "…";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function updateAvatars() {
|
||||
// Finds avatars in cylinder, not sphere.
|
||||
// Avatar data update loop:
|
||||
// - The desired update loop time is avatarsInterval.
|
||||
// - The update loop time is extended if the HTML script takes longer to display the data.
|
||||
// - The update loop comprises:
|
||||
// - Preparing the avatar data.
|
||||
// - Sending the avatar data at the target interval or as soon as the previous data has been displayed.
|
||||
// - Scheduling preparing the next avatar data so that it is ready at the anticipated send time.
|
||||
// - Displaying the avatar data in the HTML script. (Includes teleport elevation searching.)
|
||||
|
||||
var cameraPosition,
|
||||
avatarIDs,
|
||||
myAvatarIndex,
|
||||
palData,
|
||||
vector,
|
||||
sessionUUID,
|
||||
avatarDatum,
|
||||
avatarData = [],
|
||||
avatarDataRange, // The radar range that avatarData is for.
|
||||
i, length,
|
||||
MINIMUM_UPDATE_DELAY = 2,
|
||||
sendDelay,
|
||||
MINIMUM_SEND_DELAY = 2,
|
||||
SEND_RETRY_INTERVAL = 25;
|
||||
|
||||
timeToPrepareDataStart = Date.now();
|
||||
|
||||
// Timer has fired.
|
||||
avatarsUpdateTimer = null;
|
||||
|
||||
// Get avatar data.
|
||||
cameraPosition = Camera.position;
|
||||
avatarIDs = AvatarManager.getAvatarsInRange(cameraPosition, radarSearchRange);
|
||||
avatarDataRange = radarRange;
|
||||
|
||||
// Remove own avatar if necessary.
|
||||
myAvatarIndex = avatarIDs.indexOf(mySessionUUID);
|
||||
if (myAvatarIndex !== -1 && !isShowOwn) {
|
||||
avatarIDs.splice(myAvatarIndex, 1);
|
||||
}
|
||||
|
||||
// Collect avatar data.
|
||||
palData = AvatarManager.getPalData(avatarIDs)["data"]; // Property name as string to avoid obfuscation.
|
||||
for (i = 0, length = palData.length; i < length; i++) {
|
||||
// If session display name is undefined then the data is messed up (e.g., spheres problem).
|
||||
// The pal.js script also ignores items with empty name fields.
|
||||
if (!palData[i].sessionDisplayName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sessionUUID = palData[i].sessionUUID;
|
||||
|
||||
// FIXME: AvatarManager.getPalData() returns with sessionUUID === "" for own avatar.
|
||||
// Manuscript case 19693.
|
||||
if (sessionUUID === "") {
|
||||
if (isShowOwn) {
|
||||
sessionUUID = mySessionUUID;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
vector = Vec3.subtract(palData[i].position, cameraPosition);
|
||||
if (Math.abs(vector.y) <= radarRange) {
|
||||
if (Vec3.length({ x: vector.x, y: 0, z: vector.z }) <= radarRange) {
|
||||
avatarDatum = {
|
||||
uuid: sessionUUID,
|
||||
vector: vector,
|
||||
name: limitDisplayName(palData[i].sessionDisplayName)
|
||||
};
|
||||
if (sessionUUID === mySessionUUID) {
|
||||
// Don't set value for each avatar so as to reduce EventBridge message size.
|
||||
avatarDatum.isMyAvatar = true;
|
||||
}
|
||||
avatarData.push(avatarDatum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeToPrepareData = Date.now() - timeToPrepareDataStart;
|
||||
if (INSTRUMENT) {
|
||||
log("Main script : prepare : " + timeToPrepareData);
|
||||
// 2019 01 07
|
||||
// Simulation:
|
||||
// - 100 avatars: 3ms
|
||||
// - 1000 avatars: 48ms
|
||||
}
|
||||
|
||||
// Send avatar data at target time, if can.
|
||||
sendDelay = targetSendTime - Date.now(); // Should hover around 0 for a reasonably loaded radar.
|
||||
if (INSTRUMENT) {
|
||||
log("Main script : delay : " + sendDelay);
|
||||
}
|
||||
|
||||
// Schedule preparing next data set so that it is ready to send at later of target interval or display update from
|
||||
// previous data set.
|
||||
avatarsUpdateTimer = Script.setTimeout(updateAvatars,
|
||||
Math.max(Math.max(avatarsInterval, timeToDisplayData) - timeToPrepareData + sendDelay, MINIMUM_UPDATE_DELAY));
|
||||
|
||||
function sendData() {
|
||||
var instrumentTimestamp;
|
||||
|
||||
if (!isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay sending data until after previous lot has been processed.
|
||||
if (isDisplayingData) {
|
||||
if (INSTRUMENT) {
|
||||
log("Main script : reschedule send");
|
||||
}
|
||||
avatarsSendTimer = Script.setTimeout(sendData, SEND_RETRY_INTERVAL);
|
||||
return;
|
||||
}
|
||||
avatarsSendTimer = null;
|
||||
|
||||
if (INSTRUMENT) {
|
||||
instrumentTimestamp = Date.now();
|
||||
}
|
||||
|
||||
// Set target for sending next lot of data.
|
||||
targetSendTime = Date.now() + avatarsInterval;
|
||||
|
||||
// Send current lost of data.
|
||||
isDisplayingData = true;
|
||||
timeToDisplayDataStart = Date.now();
|
||||
Communications.sendAvatarData(avatarDataRange, avatarData);
|
||||
|
||||
if (INSTRUMENT) {
|
||||
log("Main script : send : " + (Date.now() - instrumentTimestamp));
|
||||
// 2019 01 07
|
||||
// Simulation:
|
||||
// - 100 avatars: 1ms
|
||||
// - 1000 avatars: 5ms
|
||||
}
|
||||
}
|
||||
|
||||
avatarsSendTimer = Script.setTimeout(sendData,
|
||||
Math.max(sendDelay, MINIMUM_SEND_DELAY)); // Caters for negative sendDelay values.
|
||||
}
|
||||
|
||||
function onSessionUUIDChanged() {
|
||||
mySessionUUID = MyAvatar.sessionUUID;
|
||||
}
|
||||
|
||||
function start() {
|
||||
isRunning = true;
|
||||
mySessionUUID = MyAvatar.sessionUUID;
|
||||
MyAvatar.sessionUUIDChanged.connect(onSessionUUIDChanged);
|
||||
if (avatarsUpdateTimer === null) {
|
||||
targetSendTime = Date.now() + avatarsInterval;
|
||||
avatarsUpdateTimer = Script.setTimeout(updateAvatars, avatarsInterval);
|
||||
}
|
||||
if (rotationTimer === null) {
|
||||
rotationTimer = Script.setTimeout(updateRotation, rotationInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function avatarsDisplayed() {
|
||||
isDisplayingData = false;
|
||||
timeToDisplayData = Date.now() - timeToDisplayDataStart;
|
||||
|
||||
if (INSTRUMENT) {
|
||||
log("Main script : display : " + timeToDisplayData);
|
||||
// 2019 01 07
|
||||
// Simulation:
|
||||
// - 100 avatars: 16ms
|
||||
// - 1000 avatars: 25ms
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
isRunning = false;
|
||||
isDisplayingData = false;
|
||||
if (avatarsSendTimer !== null) {
|
||||
Script.clearTimeout(avatarsSendTimer);
|
||||
avatarsSendTimer = null;
|
||||
}
|
||||
if (avatarsUpdateTimer !== null) {
|
||||
Script.clearTimeout(avatarsUpdateTimer);
|
||||
avatarsUpdateTimer = null;
|
||||
}
|
||||
if (rotationTimer !== null) {
|
||||
Script.clearTimeout(rotationTimer);
|
||||
rotationTimer = null;
|
||||
}
|
||||
MyAvatar.sessionUUIDChanged.disconnect(onSessionUUIDChanged);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
setPreferences(Preferences.getPreferences());
|
||||
Preferences.preferencesChanged.connect(setPreferences);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
return {
|
||||
setPreferences: setPreferences,
|
||||
setCameraMode: setCameraMode,
|
||||
start: start,
|
||||
avatarsDisplayed: avatarsDisplayed,
|
||||
stop: stop,
|
||||
setUp: setUp,
|
||||
tearDown: tearDown
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region App ==============================================================================================================
|
||||
|
||||
App = (function () {
|
||||
// Manages the interactions with the Interface app environment.
|
||||
|
||||
var APP_ICON_ACTIVE = Script.resolvePath("./assets/radar-a.svg"),
|
||||
APP_ICON_INACTIVE = Script.resolvePath("./assets/radar-i.svg"),
|
||||
APP_HTML_PAGE = Script.resolvePath("./html/radar.html"),
|
||||
APP_BUTTON_TEXT = "RADAR",
|
||||
|
||||
button = null,
|
||||
isAppActive = false,
|
||||
|
||||
HMD_TABLET_BECOMES_TOOLBAR_SETTING = "hmdTabletBecomesToolbar",
|
||||
isHMDTabletBecomesToolbar = false,
|
||||
isHMDActive = false;
|
||||
|
||||
|
||||
function onDisplayModeChanged(isHMDMode) {
|
||||
// Close app if have switched between desktop and HMD modes, else tablet can't be used or toolbar button stays on.
|
||||
if (isAppActive && isHMDMode !== isHMDActive) {
|
||||
isHMDTabletBecomesToolbar = Settings.getValue(HMD_TABLET_BECOMES_TOOLBAR_SETTING, false);
|
||||
isHMDActive = isHMDMode;
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
function onPossibleDomainChangeRequired() {
|
||||
// Clear radar display so that out-of-date display doesn't unexpectedly jump before updating to new location.
|
||||
Communications.clearAvatarData();
|
||||
}
|
||||
|
||||
function startApp() {
|
||||
|
||||
isHMDTabletBecomesToolbar = Settings.getValue(HMD_TABLET_BECOMES_TOOLBAR_SETTING, false);
|
||||
isHMDActive = HMD.active;
|
||||
|
||||
HMD.displayModeChanged.connect(onDisplayModeChanged);
|
||||
location.possibleDomainChangeRequired.connect(onPossibleDomainChangeRequired);
|
||||
location.possibleDomainChangeRequiredViaICEForID.connect(onPossibleDomainChangeRequired);
|
||||
|
||||
Camera.modeUpdated.connect(Updates.setCameraMode);
|
||||
Updates.setCameraMode(Camera.mode);
|
||||
|
||||
Communications.ready.connect(Updates.start);
|
||||
tablet.webEventReceived.connect(Communications.onWebEventReceived);
|
||||
}
|
||||
|
||||
function stopApp() {
|
||||
Updates.stop();
|
||||
|
||||
tablet.webEventReceived.disconnect(Communications.onWebEventReceived);
|
||||
Communications.ready.disconnect(Updates.start);
|
||||
|
||||
Camera.modeUpdated.disconnect(Updates.setCameraMode);
|
||||
|
||||
location.possibleDomainChangeRequiredViaICEForID.disconnect(onPossibleDomainChangeRequired);
|
||||
location.possibleDomainChangeRequired.disconnect(onPossibleDomainChangeRequired);
|
||||
HMD.displayModeChanged.disconnect(onDisplayModeChanged);
|
||||
}
|
||||
|
||||
function onButtonClicked() {
|
||||
if (!isAppActive) {
|
||||
tablet.gotoWebScreen(APP_HTML_PAGE);
|
||||
} else {
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
function onTabletScreenChanged(type, url) {
|
||||
var active;
|
||||
|
||||
active = url.slice(0, APP_HTML_PAGE.length) === APP_HTML_PAGE;
|
||||
if (active === isAppActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
isAppActive = active;
|
||||
button.editProperties({ isActive: isAppActive });
|
||||
if (isAppActive) {
|
||||
startApp();
|
||||
} else {
|
||||
stopApp();
|
||||
}
|
||||
}
|
||||
|
||||
function isHMDMode() {
|
||||
return isHMDActive && !isHMDTabletBecomesToolbar;
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
if (!tablet) {
|
||||
log(ERROR, "Tablet not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
button = tablet.addButton({
|
||||
icon: APP_ICON_INACTIVE,
|
||||
activeIcon: APP_ICON_ACTIVE,
|
||||
text: APP_BUTTON_TEXT,
|
||||
isActive: false
|
||||
});
|
||||
|
||||
if (!button) {
|
||||
log(ERROR, "Tablet button not created!");
|
||||
tablet = null;
|
||||
return;
|
||||
}
|
||||
|
||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||
button.clicked.connect(onButtonClicked);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
if (isAppActive) {
|
||||
stopApp();
|
||||
tablet.gotoHomeScreen(); // Close desktop window.
|
||||
}
|
||||
|
||||
if (button) {
|
||||
button.clicked.disconnect(onButtonClicked);
|
||||
tablet.removeButton(button);
|
||||
button = null;
|
||||
}
|
||||
|
||||
if (tablet) {
|
||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||
tablet = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isHMDMode: isHMDMode,
|
||||
setUp: setUp,
|
||||
tearDown: tearDown
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Set up and tear down =============================================================================================
|
||||
|
||||
function setUp() {
|
||||
log(INFO, APP_NAME, APP_VERSION);
|
||||
App.setUp();
|
||||
Preferences.setUp();
|
||||
Updates.setUp();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
Script.scriptEnding.disconnect(tearDown);
|
||||
App.tearDown();
|
||||
Preferences.tearDown();
|
||||
Updates.tearDown();
|
||||
}
|
||||
|
||||
setUp();
|
||||
Script.scriptEnding.connect(tearDown);
|
||||
|
||||
//#endregion
|
||||
|
||||
}());
|
Loading…
Reference in a new issue