mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 19:59:28 +02:00
Merge pull request #10271 from humbletim/21281
CR Job #21281 -- Doppleganger Mirror
This commit is contained in:
commit
c088bd6891
13 changed files with 912 additions and 0 deletions
|
@ -34,6 +34,7 @@ module.exports = {
|
||||||
"Quat": false,
|
"Quat": false,
|
||||||
"Rates": false,
|
"Rates": false,
|
||||||
"Recording": false,
|
"Recording": false,
|
||||||
|
"Resource": false,
|
||||||
"Reticle": false,
|
"Reticle": false,
|
||||||
"Scene": false,
|
"Scene": false,
|
||||||
"Script": false,
|
"Script": false,
|
||||||
|
|
94
interface/resources/icons/tablet-icons/doppleganger-a.svg
Normal file
94
interface/resources/icons/tablet-icons/doppleganger-a.svg
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 50 50"
|
||||||
|
style="enable-background:new 0 0 50 50;"
|
||||||
|
xml:space="preserve"
|
||||||
|
id="svg2"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="doppleganger-a.svg"><metadata
|
||||||
|
id="metadata36"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs34"><linearGradient
|
||||||
|
id="linearGradient8353"
|
||||||
|
osb:paint="solid"><stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop8355" /></linearGradient></defs><sodipodi:namedview
|
||||||
|
pagecolor="#ff4900"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1004"
|
||||||
|
id="namedview32"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="9.44"
|
||||||
|
inkscape:cx="-3.2806499"
|
||||||
|
inkscape:cy="20.640561"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="24"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g4308" /><style
|
||||||
|
type="text/css"
|
||||||
|
id="style4">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style><g
|
||||||
|
id="Layer_2" /><g
|
||||||
|
id="Layer_1"
|
||||||
|
style="fill:#000000;fill-opacity:1"><g
|
||||||
|
id="g8375"
|
||||||
|
transform="matrix(1.0667546,0,0,1.0667546,-2.1894733,-1.7818707)"><g
|
||||||
|
id="g4308"
|
||||||
|
transform="translate(1.0333645e-6,0)"><g
|
||||||
|
id="g8"
|
||||||
|
style="fill:#000000;fill-opacity:1"
|
||||||
|
transform="matrix(1.1059001,0,0,1.1059001,-17.342989,-7.9561147)"><path
|
||||||
|
class="st0"
|
||||||
|
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||||
|
id="path10"
|
||||||
|
style="fill:#000000;fill-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
class="st0"
|
||||||
|
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||||
|
id="path12"
|
||||||
|
style="fill:#000000;fill-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /></g><g
|
||||||
|
id="g8-3"
|
||||||
|
style="opacity:0.5;fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956,0.29667956;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
transform="matrix(-1.1059001,0,0,1.1059001,67.821392,-7.9561147)"><path
|
||||||
|
class="st0"
|
||||||
|
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||||
|
id="path10-6"
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956,0.29667956;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
class="st0"
|
||||||
|
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||||
|
id="path12-7"
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956,0.29667956;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /></g></g><rect
|
||||||
|
style="opacity:0.5;fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:0.15729524;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.62918094, 1.25836187;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
id="rect4306"
|
||||||
|
width="0.12393159"
|
||||||
|
height="46.498554"
|
||||||
|
x="25.227457"
|
||||||
|
y="1.8070068"
|
||||||
|
rx="0"
|
||||||
|
ry="0.9407174" /></g></g></svg>
|
After Width: | Height: | Size: 5.8 KiB |
94
interface/resources/icons/tablet-icons/doppleganger-i.svg
Normal file
94
interface/resources/icons/tablet-icons/doppleganger-i.svg
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 50 50"
|
||||||
|
style="enable-background:new 0 0 50 50;"
|
||||||
|
xml:space="preserve"
|
||||||
|
id="svg2"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="doppleganger-i.svg"><metadata
|
||||||
|
id="metadata36"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs34"><linearGradient
|
||||||
|
id="linearGradient8353"
|
||||||
|
osb:paint="solid"><stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop8355" /></linearGradient></defs><sodipodi:namedview
|
||||||
|
pagecolor="#ff4900"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1004"
|
||||||
|
id="namedview32"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="9.44"
|
||||||
|
inkscape:cx="-3.2806499"
|
||||||
|
inkscape:cy="20.640561"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="24"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g4308" /><style
|
||||||
|
type="text/css"
|
||||||
|
id="style4">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style><g
|
||||||
|
id="Layer_2" /><g
|
||||||
|
id="Layer_1"
|
||||||
|
style="fill:#000000;fill-opacity:1"><g
|
||||||
|
id="g8375"
|
||||||
|
transform="matrix(1.0667546,0,0,1.0667546,-2.1894733,-1.7818707)"><g
|
||||||
|
id="g4308"
|
||||||
|
transform="translate(1.0333645e-6,0)"><g
|
||||||
|
id="g8"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"
|
||||||
|
transform="matrix(1.1059001,0,0,1.1059001,-17.342989,-7.9561147)"><path
|
||||||
|
class="st0"
|
||||||
|
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||||
|
id="path10"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
class="st0"
|
||||||
|
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||||
|
id="path12"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /></g><g
|
||||||
|
id="g8-3"
|
||||||
|
style="opacity:0.5;fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956, 0.29667956000000001;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
transform="matrix(-1.1059001,0,0,1.1059001,67.821392,-7.9561147)"><path
|
||||||
|
class="st0"
|
||||||
|
d="m 23.2,24.1 c -0.8,0.9 -1.5,1.8 -2.2,2.6 -0.1,0.2 -0.1,0.5 -0.1,0.7 0.1,1.7 0.2,3.4 0.2,5.1 0,0.8 -0.4,1.2 -1.1,1.3 -0.7,0.1 -1.3,-0.4 -1.4,-1.1 -0.2,-2.2 -0.3,-4.3 -0.5,-6.5 0,-0.3 0.1,-0.7 0.4,-1 1.1,-1.5 2.3,-3 3.4,-4.5 0.6,-0.7 1.6,-1.6 2.6,-1.6 0.3,0 1.1,0 1.4,0 0.8,-0.1 1.3,0.1 1.9,0.9 1,1.2 1.5,2.3 2.4,3.6 0.7,1.1 1.4,1.6 2.9,1.9 1.1,0.2 2.2,0.5 3.3,0.8 0.3,0.1 0.6,0.2 0.8,0.3 0.5,0.3 0.7,0.8 0.6,1.3 -0.1,0.5 -0.5,0.7 -1,0.8 -0.4,0 -0.9,0 -1.3,-0.1 -1.4,-0.3 -2.7,-0.6 -4.1,-0.9 -0.8,-0.2 -1.5,-0.6 -2.1,-1.1 -0.3,-0.3 -0.6,-0.5 -0.9,-0.8 0,0.3 0,0.5 0,0.7 0,1.2 0,2.4 0,3.6 0,0.4 -0.3,12.6 -0.1,16.8 0,0.5 -0.1,1 -0.2,1.5 -0.2,0.7 -0.6,1 -1.4,1.1 -0.8,0 -1.4,-0.3 -1.7,-1 C 24.8,48 24.7,47.4 24.6,46.9 24.2,42.3 23.7,34 23.5,33.1 23.4,32.3 23.3,32 23.2,31 c -0.1,-0.5 -0.1,-0.9 -0.1,-1.3 0.2,-1.8 0.1,-3.6 0.1,-5.6 z"
|
||||||
|
id="path10-6"
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956, 0.29667956000000001;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
class="st0"
|
||||||
|
d="m 28.2,14.6 c 0,1.4 -1.1,2.6 -2.6,2.6 l 0,0 C 24.2,17.2 23,16.1 23,14.6 L 23,13 c 0,-1.4 1.1,-2.6 2.6,-2.6 l 0,0 c 1.4,0 2.6,1.1 2.6,2.6 l 0,1.6 z"
|
||||||
|
id="path12-7"
|
||||||
|
style="fill:#808080;fill-opacity:1;stroke:#ffffff;stroke-width:0.59335912;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29667956, 0.29667956000000001;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /></g></g><rect
|
||||||
|
style="opacity:0.5;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.15729524;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.62918094, 1.25836187000000010;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
id="rect4306"
|
||||||
|
width="0.12393159"
|
||||||
|
height="46.498554"
|
||||||
|
x="25.227457"
|
||||||
|
y="1.8070068"
|
||||||
|
rx="0"
|
||||||
|
ry="0.9407174" /></g></g></svg>
|
After Width: | Height: | Size: 5.9 KiB |
|
@ -929,6 +929,17 @@ QVector<glm::quat> Avatar::getJointRotations() const {
|
||||||
return jointRotations;
|
return jointRotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<glm::vec3> Avatar::getJointTranslations() const {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
return AvatarData::getJointTranslations();
|
||||||
|
}
|
||||||
|
QVector<glm::vec3> jointTranslations(_skeletonModel->getJointStateCount());
|
||||||
|
for (int i = 0; i < _skeletonModel->getJointStateCount(); ++i) {
|
||||||
|
_skeletonModel->getJointTranslation(i, jointTranslations[i]);
|
||||||
|
}
|
||||||
|
return jointTranslations;
|
||||||
|
}
|
||||||
|
|
||||||
glm::quat Avatar::getJointRotation(int index) const {
|
glm::quat Avatar::getJointRotation(int index) const {
|
||||||
glm::quat rotation;
|
glm::quat rotation;
|
||||||
_skeletonModel->getJointRotation(index, rotation);
|
_skeletonModel->getJointRotation(index, rotation);
|
||||||
|
|
|
@ -112,6 +112,7 @@ public:
|
||||||
|
|
||||||
virtual QVector<glm::quat> getJointRotations() const override;
|
virtual QVector<glm::quat> getJointRotations() const override;
|
||||||
virtual glm::quat getJointRotation(int index) const override;
|
virtual glm::quat getJointRotation(int index) const override;
|
||||||
|
virtual QVector<glm::vec3> getJointTranslations() const override;
|
||||||
virtual glm::vec3 getJointTranslation(int index) const override;
|
virtual glm::vec3 getJointTranslation(int index) const override;
|
||||||
virtual int getJointIndex(const QString& name) const override;
|
virtual int getJointIndex(const QString& name) const override;
|
||||||
virtual QStringList getJointNames() const override;
|
virtual QStringList getJointNames() const override;
|
||||||
|
|
|
@ -126,6 +126,55 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
||||||
QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection,
|
QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection,
|
||||||
Q_ARG(const QVariantMap&, textureMap));
|
Q_ARG(const QVariantMap&, textureMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// relative
|
||||||
|
auto jointTranslationsValue = properties["jointTranslations"];
|
||||||
|
if (jointTranslationsValue.canConvert(QVariant::List)) {
|
||||||
|
const QVariantList& jointTranslations = jointTranslationsValue.toList();
|
||||||
|
int translationCount = jointTranslations.size();
|
||||||
|
int jointCount = _model->getJointStateCount();
|
||||||
|
if (translationCount < jointCount) {
|
||||||
|
jointCount = translationCount;
|
||||||
|
}
|
||||||
|
for (int i=0; i < jointCount; i++) {
|
||||||
|
const auto& translationValue = jointTranslations[i];
|
||||||
|
if (translationValue.isValid()) {
|
||||||
|
_model->setJointTranslation(i, true, vec3FromVariant(translationValue), 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_updateModel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// relative
|
||||||
|
auto jointRotationsValue = properties["jointRotations"];
|
||||||
|
if (jointRotationsValue.canConvert(QVariant::List)) {
|
||||||
|
const QVariantList& jointRotations = jointRotationsValue.toList();
|
||||||
|
int rotationCount = jointRotations.size();
|
||||||
|
int jointCount = _model->getJointStateCount();
|
||||||
|
if (rotationCount < jointCount) {
|
||||||
|
jointCount = rotationCount;
|
||||||
|
}
|
||||||
|
for (int i=0; i < jointCount; i++) {
|
||||||
|
const auto& rotationValue = jointRotations[i];
|
||||||
|
if (rotationValue.isValid()) {
|
||||||
|
_model->setJointRotation(i, true, quatFromVariant(rotationValue), 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_updateModel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename vectorType, typename itemType>
|
||||||
|
vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
|
||||||
|
vectorType result;
|
||||||
|
if (_model && _model->isActive()) {
|
||||||
|
const int jointCount = _model->getJointStateCount();
|
||||||
|
result.reserve(jointCount);
|
||||||
|
for (int i = 0; i < jointCount; i++) {
|
||||||
|
result << function(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ModelOverlay::getProperty(const QString& property) {
|
QVariant ModelOverlay::getProperty(const QString& property) {
|
||||||
|
@ -150,6 +199,58 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (property == "jointNames") {
|
||||||
|
if (_model && _model->isActive()) {
|
||||||
|
// note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty
|
||||||
|
const RigPointer rig = _model->getRig();
|
||||||
|
if (rig) {
|
||||||
|
return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString {
|
||||||
|
return rig->nameOfJoint(jointIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// relative
|
||||||
|
if (property == "jointRotations") {
|
||||||
|
return mapJoints<QVariantList, QVariant>(
|
||||||
|
[this](int jointIndex) -> QVariant {
|
||||||
|
glm::quat rotation;
|
||||||
|
_model->getJointRotation(jointIndex, rotation);
|
||||||
|
return quatToVariant(rotation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// relative
|
||||||
|
if (property == "jointTranslations") {
|
||||||
|
return mapJoints<QVariantList, QVariant>(
|
||||||
|
[this](int jointIndex) -> QVariant {
|
||||||
|
glm::vec3 translation;
|
||||||
|
_model->getJointTranslation(jointIndex, translation);
|
||||||
|
return vec3toVariant(translation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// absolute
|
||||||
|
if (property == "jointOrientations") {
|
||||||
|
return mapJoints<QVariantList, QVariant>(
|
||||||
|
[this](int jointIndex) -> QVariant {
|
||||||
|
glm::quat orientation;
|
||||||
|
_model->getJointRotationInWorldFrame(jointIndex, orientation);
|
||||||
|
return quatToVariant(orientation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// absolute
|
||||||
|
if (property == "jointPositions") {
|
||||||
|
return mapJoints<QVariantList, QVariant>(
|
||||||
|
[this](int jointIndex) -> QVariant {
|
||||||
|
glm::vec3 position;
|
||||||
|
_model->getJointPositionInWorldFrame(jointIndex, position);
|
||||||
|
return vec3toVariant(position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Volume3DOverlay::getProperty(property);
|
return Volume3DOverlay::getProperty(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,12 @@ public:
|
||||||
|
|
||||||
void locationChanged(bool tellPhysics) override;
|
void locationChanged(bool tellPhysics) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// helper to extract metadata from our Model's rigged joints
|
||||||
|
template <typename itemType> using mapFunction = std::function<itemType(int jointIndex)>;
|
||||||
|
template <typename vectorType, typename itemType>
|
||||||
|
vectorType mapJoints(mapFunction<itemType> function) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
ModelPointer _model;
|
ModelPointer _model;
|
||||||
|
|
|
@ -1392,6 +1392,22 @@ void AvatarData::setJointRotations(QVector<glm::quat> jointRotations) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<glm::vec3> AvatarData::getJointTranslations() const {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QVector<glm::vec3> result;
|
||||||
|
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
|
||||||
|
"getJointTranslations", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(QVector<glm::vec3>, result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
QReadLocker readLock(&_jointDataLock);
|
||||||
|
QVector<glm::vec3> jointTranslations(_jointData.size());
|
||||||
|
for (int i = 0; i < _jointData.size(); ++i) {
|
||||||
|
jointTranslations[i] = _jointData[i].translation;
|
||||||
|
}
|
||||||
|
return jointTranslations;
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarData::setJointTranslations(QVector<glm::vec3> jointTranslations) {
|
void AvatarData::setJointTranslations(QVector<glm::vec3> jointTranslations) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QVector<glm::quat> result;
|
QVector<glm::quat> result;
|
||||||
|
|
|
@ -497,6 +497,7 @@ public:
|
||||||
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
|
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
|
||||||
|
|
||||||
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
|
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
|
||||||
|
Q_INVOKABLE virtual QVector<glm::vec3> getJointTranslations() const;
|
||||||
Q_INVOKABLE virtual void setJointRotations(QVector<glm::quat> jointRotations);
|
Q_INVOKABLE virtual void setJointRotations(QVector<glm::quat> jointRotations);
|
||||||
Q_INVOKABLE virtual void setJointTranslations(QVector<glm::vec3> jointTranslations);
|
Q_INVOKABLE virtual void setJointTranslations(QVector<glm::vec3> jointTranslations);
|
||||||
|
|
||||||
|
|
|
@ -210,6 +210,13 @@ QVector<glm::quat> ScriptAvatarData::getJointRotations() const {
|
||||||
return QVector<glm::quat>();
|
return QVector<glm::quat>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
QVector<glm::vec3> ScriptAvatarData::getJointTranslations() const {
|
||||||
|
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||||
|
return sharedAvatarData->getJointTranslations();
|
||||||
|
} else {
|
||||||
|
return QVector<glm::vec3>();
|
||||||
|
}
|
||||||
|
}
|
||||||
bool ScriptAvatarData::isJointDataValid(const QString& name) const {
|
bool ScriptAvatarData::isJointDataValid(const QString& name) const {
|
||||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||||
return sharedAvatarData->isJointDataValid(name);
|
return sharedAvatarData->isJointDataValid(name);
|
||||||
|
|
|
@ -106,6 +106,7 @@ public:
|
||||||
Q_INVOKABLE glm::quat getJointRotation(const QString& name) const;
|
Q_INVOKABLE glm::quat getJointRotation(const QString& name) const;
|
||||||
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
|
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
|
||||||
Q_INVOKABLE QVector<glm::quat> getJointRotations() const;
|
Q_INVOKABLE QVector<glm::quat> getJointRotations() const;
|
||||||
|
Q_INVOKABLE QVector<glm::vec3> getJointTranslations() const;
|
||||||
Q_INVOKABLE bool isJointDataValid(const QString& name) const;
|
Q_INVOKABLE bool isJointDataValid(const QString& name) const;
|
||||||
Q_INVOKABLE int getJointIndex(const QString& name) const;
|
Q_INVOKABLE int getJointIndex(const QString& name) const;
|
||||||
Q_INVOKABLE QStringList getJointNames() const;
|
Q_INVOKABLE QStringList getJointNames() const;
|
||||||
|
|
85
scripts/system/app-doppleganger.js
Normal file
85
scripts/system/app-doppleganger.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// doppleganger-app.js
|
||||||
|
//
|
||||||
|
// Created by Timothy Dedischew on 04/21/2017.
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// This Client script creates an instance of a Doppleganger that can be toggled on/off via tablet button.
|
||||||
|
// (for more info see doppleganger.js)
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
var DopplegangerClass = Script.require('./doppleganger.js');
|
||||||
|
|
||||||
|
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'),
|
||||||
|
button = tablet.addButton({
|
||||||
|
icon: "icons/tablet-icons/doppleganger-i.svg",
|
||||||
|
activeIcon: "icons/tablet-icons/doppleganger-a.svg",
|
||||||
|
text: 'MIRROR'
|
||||||
|
});
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
tablet.removeButton(button);
|
||||||
|
button = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
var doppleganger = new DopplegangerClass({
|
||||||
|
avatar: MyAvatar,
|
||||||
|
mirrored: true,
|
||||||
|
autoUpdate: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// hide the doppleganger if this client script is unloaded
|
||||||
|
Script.scriptEnding.connect(doppleganger, 'stop');
|
||||||
|
|
||||||
|
// hide the doppleganger if the user switches domains (which might place them arbitrarily far away in world space)
|
||||||
|
function onDomainChanged() {
|
||||||
|
if (doppleganger.active) {
|
||||||
|
doppleganger.stop('domain_changed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Window.domainChanged.connect(onDomainChanged);
|
||||||
|
Window.domainConnectionRefused.connect(onDomainChanged);
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
Window.domainChanged.disconnect(onDomainChanged);
|
||||||
|
Window.domainConnectionRefused.disconnect(onDomainChanged);
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggle on/off via tablet button
|
||||||
|
button.clicked.connect(doppleganger, 'toggle');
|
||||||
|
|
||||||
|
// highlight tablet button based on current doppleganger state
|
||||||
|
doppleganger.activeChanged.connect(function(active, reason) {
|
||||||
|
if (button) {
|
||||||
|
button.editProperties({ isActive: active });
|
||||||
|
print('doppleganger.activeChanged', active, reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// alert the user if there was an error applying their skeletonModelURL
|
||||||
|
doppleganger.modelOverlayLoaded.connect(function(error, result) {
|
||||||
|
if (doppleganger.active && error) {
|
||||||
|
Window.alert('doppleganger | ' + error + '\n' + doppleganger.skeletonModelURL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// add debug indicators, but only if the user has configured the settings value
|
||||||
|
if (Settings.getValue('debug.doppleganger', false)) {
|
||||||
|
DopplegangerClass.addDebugControls(doppleganger);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserActivityLogger.logAction('doppleganger_app_load');
|
||||||
|
doppleganger.activeChanged.connect(function(active, reason) {
|
||||||
|
if (active) {
|
||||||
|
UserActivityLogger.logAction('doppleganger_enable');
|
||||||
|
} else {
|
||||||
|
if (reason === 'stop') {
|
||||||
|
// user intentionally toggled the doppleganger
|
||||||
|
UserActivityLogger.logAction('doppleganger_disable');
|
||||||
|
} else {
|
||||||
|
print('doppleganger stopped:', reason);
|
||||||
|
UserActivityLogger.logAction('doppleganger_autodisable', { reason: reason });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
494
scripts/system/doppleganger.js
Normal file
494
scripts/system/doppleganger.js
Normal file
|
@ -0,0 +1,494 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// doppleganger.js
|
||||||
|
//
|
||||||
|
// Created by Timothy Dedischew on 04/21/2017.
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
/* global module */
|
||||||
|
// @module doppleganger
|
||||||
|
//
|
||||||
|
// This module contains the `Doppleganger` class implementation for creating an inspectable replica of
|
||||||
|
// an Avatar (as a model directly in front of and facing them). Joint positions and rotations are copied
|
||||||
|
// over in an update thread, so that the model automatically mirrors the Avatar's joint movements.
|
||||||
|
// An Avatar can then for example walk around "themselves" and examine from the back, etc.
|
||||||
|
//
|
||||||
|
// This should be helpful for inspecting your own look and debugging avatars, etc.
|
||||||
|
//
|
||||||
|
// The doppleganger is created as an overlay so that others do not see it -- and this also allows for the
|
||||||
|
// highest possible update rate when keeping joint data in sync.
|
||||||
|
|
||||||
|
module.exports = Doppleganger;
|
||||||
|
|
||||||
|
// @property {bool} - when set true, Script.update will be used instead of setInterval for syncing joint data
|
||||||
|
Doppleganger.USE_SCRIPT_UPDATE = false;
|
||||||
|
|
||||||
|
// @property {int} - the frame rate to target when using setInterval for joint updates
|
||||||
|
Doppleganger.TARGET_FPS = 60;
|
||||||
|
|
||||||
|
// @property {int} - the maximum time in seconds to wait for the model overlay to finish loading
|
||||||
|
Doppleganger.MAX_WAIT_SECS = 10;
|
||||||
|
|
||||||
|
// @function - derive mirrored joint names from a list of regular joint names
|
||||||
|
// @param {Array} - list of joint names to mirror
|
||||||
|
// @return {Array} - list of mirrored joint names (note: entries for non-mirrored joints will be `undefined`)
|
||||||
|
Doppleganger.getMirroredJointNames = function(jointNames) {
|
||||||
|
return jointNames.map(function(name, i) {
|
||||||
|
if (/Left/.test(name)) {
|
||||||
|
return name.replace('Left', 'Right');
|
||||||
|
}
|
||||||
|
if (/Right/.test(name)) {
|
||||||
|
return name.replace('Right', 'Left');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// @class Doppleganger - Creates a new instance of a Doppleganger.
|
||||||
|
// @param {Avatar} [options.avatar=MyAvatar] - Avatar used to retrieve position and joint data.
|
||||||
|
// @param {bool} [options.mirrored=true] - Apply "symmetric mirroring" of Left/Right joints.
|
||||||
|
// @param {bool} [options.autoUpdate=true] - Automatically sync joint data.
|
||||||
|
function Doppleganger(options) {
|
||||||
|
options = options || {};
|
||||||
|
this.avatar = options.avatar || MyAvatar;
|
||||||
|
this.mirrored = 'mirrored' in options ? options.mirrored : true;
|
||||||
|
this.autoUpdate = 'autoUpdate' in options ? options.autoUpdate : true;
|
||||||
|
|
||||||
|
// @public
|
||||||
|
this.active = false; // whether doppleganger is currently being displayed/updated
|
||||||
|
this.overlayID = null; // current doppleganger's Overlay id
|
||||||
|
this.frame = 0; // current joint update frame
|
||||||
|
|
||||||
|
// @signal - emitted when .active state changes
|
||||||
|
this.activeChanged = signal(function(active, reason) {});
|
||||||
|
// @signal - emitted once model overlay is either loaded or errors out
|
||||||
|
this.modelOverlayLoaded = signal(function(error, result){});
|
||||||
|
// @signal - emitted each time the model overlay's joint data has been synchronized
|
||||||
|
this.jointsUpdated = signal(function(overlayID){});
|
||||||
|
}
|
||||||
|
|
||||||
|
Doppleganger.prototype = {
|
||||||
|
// @public @method - toggles doppleganger on/off
|
||||||
|
toggle: function() {
|
||||||
|
if (this.active) {
|
||||||
|
log('toggling off');
|
||||||
|
this.stop();
|
||||||
|
} else {
|
||||||
|
log('toggling on');
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
return this.active;
|
||||||
|
},
|
||||||
|
|
||||||
|
// @public @method - synchronize the joint data between Avatar / doppleganger
|
||||||
|
update: function() {
|
||||||
|
this.frame++;
|
||||||
|
try {
|
||||||
|
if (!this.overlayID) {
|
||||||
|
throw new Error('!this.overlayID');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.avatar.skeletonModelURL !== this.skeletonModelURL) {
|
||||||
|
return this.stop('avatar_changed');
|
||||||
|
}
|
||||||
|
|
||||||
|
var rotations = this.avatar.getJointRotations();
|
||||||
|
var translations = this.avatar.getJointTranslations();
|
||||||
|
var size = rotations.length;
|
||||||
|
|
||||||
|
// note: this mismatch can happen when the avatar's model is actively changing
|
||||||
|
if (size !== translations.length ||
|
||||||
|
(this.jointStateCount && size !== this.jointStateCount)) {
|
||||||
|
log('mismatched joint counts (avatar model likely changed)', size, translations.length, this.jointStateCount);
|
||||||
|
this.stop('avatar_changed_joints');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.jointStateCount = size;
|
||||||
|
|
||||||
|
if (this.mirrored) {
|
||||||
|
var mirroredIndexes = this.mirroredIndexes;
|
||||||
|
var outRotations = new Array(size);
|
||||||
|
var outTranslations = new Array(size);
|
||||||
|
for (var i=0; i < size; i++) {
|
||||||
|
var index = mirroredIndexes[i];
|
||||||
|
if (index < 0 || index === false) {
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
var rot = rotations[index];
|
||||||
|
var trans = translations[index];
|
||||||
|
trans.x *= -1;
|
||||||
|
rot.y *= -1;
|
||||||
|
rot.z *= -1;
|
||||||
|
outRotations[i] = rot;
|
||||||
|
outTranslations[i] = trans;
|
||||||
|
}
|
||||||
|
rotations = outRotations;
|
||||||
|
translations = outTranslations;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(this.overlayID, {
|
||||||
|
jointRotations: rotations,
|
||||||
|
jointTranslations: translations
|
||||||
|
});
|
||||||
|
|
||||||
|
this.jointsUpdated(this.overlayID);
|
||||||
|
} catch (e) {
|
||||||
|
log('.update error: '+ e, index);
|
||||||
|
this.stop('update_error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// @public @method - show the doppleganger (and start the update thread, if options.autoUpdate was specified).
|
||||||
|
// @param {vec3} [options.position=(in front of avatar)] - starting position
|
||||||
|
// @param {quat} [options.orientation=avatar.orientation] - starting orientation
|
||||||
|
start: function(options) {
|
||||||
|
options = options || {};
|
||||||
|
if (this.overlayID) {
|
||||||
|
log('start() called but overlay model already exists', this.overlayID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var avatar = this.avatar;
|
||||||
|
if (!avatar.jointNames.length) {
|
||||||
|
return this.stop('joints_unavailable');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frame = 0;
|
||||||
|
this.position = options.position || Vec3.sum(avatar.position, Quat.getForward(avatar.orientation));
|
||||||
|
this.orientation = options.orientation || avatar.orientation;
|
||||||
|
this.skeletonModelURL = avatar.skeletonModelURL;
|
||||||
|
this.jointStateCount = 0;
|
||||||
|
this.jointNames = avatar.jointNames;
|
||||||
|
this.mirroredNames = Doppleganger.getMirroredJointNames(this.jointNames);
|
||||||
|
this.mirroredIndexes = this.mirroredNames.map(function(name) {
|
||||||
|
return name ? avatar.getJointIndex(name) : false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.overlayID = Overlays.addOverlay('model', {
|
||||||
|
visible: false,
|
||||||
|
url: this.skeletonModelURL,
|
||||||
|
position: this.position,
|
||||||
|
rotation: this.orientation
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onModelOverlayLoaded = function(error, result) {
|
||||||
|
if (error) {
|
||||||
|
return this.stop(error);
|
||||||
|
}
|
||||||
|
log('ModelOverlay is ready; # joints == ' + result.jointNames.length);
|
||||||
|
Overlays.editOverlay(this.overlayID, { visible: true });
|
||||||
|
if (!options.position) {
|
||||||
|
this.syncVerticalPosition();
|
||||||
|
}
|
||||||
|
if (this.autoUpdate) {
|
||||||
|
this._createUpdateThread();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.modelOverlayLoaded.connect(this, 'onModelOverlayLoaded');
|
||||||
|
|
||||||
|
log('doppleganger created; overlayID =', this.overlayID);
|
||||||
|
|
||||||
|
// trigger clean up (and stop updates) if the overlay gets deleted
|
||||||
|
this.onDeletedOverlay = function(uuid) {
|
||||||
|
if (uuid === this.overlayID) {
|
||||||
|
log('onDeletedOverlay', uuid);
|
||||||
|
this.stop('overlay_deleted');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Overlays.overlayDeleted.connect(this, 'onDeletedOverlay');
|
||||||
|
|
||||||
|
if ('onLoadComplete' in avatar) {
|
||||||
|
// stop the current doppleganger if Avatar loads a different model URL
|
||||||
|
this.onLoadComplete = function() {
|
||||||
|
if (avatar.skeletonModelURL !== this.skeletonModelURL) {
|
||||||
|
this.stop('avatar_changed_load');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
avatar.onLoadComplete.connect(this, 'onLoadComplete');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeChanged(this.active = true, 'start');
|
||||||
|
this._waitForModel(ModelCache.prefetch(this.skeletonModelURL));
|
||||||
|
},
|
||||||
|
|
||||||
|
// @public @method - hide the doppleganger
|
||||||
|
// @param {String} [reason=stop] - the reason stop was called
|
||||||
|
stop: function(reason) {
|
||||||
|
reason = reason || 'stop';
|
||||||
|
if (this.onUpdate) {
|
||||||
|
Script.update.disconnect(this, 'onUpdate');
|
||||||
|
delete this.onUpdate;
|
||||||
|
}
|
||||||
|
if (this._interval) {
|
||||||
|
Script.clearInterval(this._interval);
|
||||||
|
this._interval = undefined;
|
||||||
|
}
|
||||||
|
if (this.onDeletedOverlay) {
|
||||||
|
Overlays.overlayDeleted.disconnect(this, 'onDeletedOverlay');
|
||||||
|
delete this.onDeletedOverlay;
|
||||||
|
}
|
||||||
|
if (this.onLoadComplete) {
|
||||||
|
this.avatar.onLoadComplete.disconnect(this, 'onLoadComplete');
|
||||||
|
delete this.onLoadComplete;
|
||||||
|
}
|
||||||
|
if (this.onModelOverlayLoaded) {
|
||||||
|
this.modelOverlayLoaded.disconnect(this, 'onModelOverlayLoaded');
|
||||||
|
}
|
||||||
|
if (this.overlayID) {
|
||||||
|
Overlays.deleteOverlay(this.overlayID);
|
||||||
|
this.overlayID = undefined;
|
||||||
|
}
|
||||||
|
if (this.active) {
|
||||||
|
this.activeChanged(this.active = false, reason);
|
||||||
|
} else if (reason) {
|
||||||
|
log('already stopped so not triggering another activeChanged; latest reason was:', reason);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// @public @method - Reposition the doppleganger so it sees "eye to eye" with the Avatar.
|
||||||
|
// @param {String} [byJointName=Hips] - the reference joint used to align the Doppleganger and Avatar
|
||||||
|
syncVerticalPosition: function(byJointName) {
|
||||||
|
byJointName = byJointName || 'Hips';
|
||||||
|
var names = Overlays.getProperty(this.overlayID, 'jointNames'),
|
||||||
|
positions = Overlays.getProperty(this.overlayID, 'jointPositions'),
|
||||||
|
dopplePosition = Overlays.getProperty(this.overlayID, 'position'),
|
||||||
|
doppleJointIndex = names.indexOf(byJointName),
|
||||||
|
doppleJointPosition = positions[doppleJointIndex];
|
||||||
|
|
||||||
|
var avatarPosition = this.avatar.position,
|
||||||
|
avatarJointIndex = this.avatar.getJointIndex(byJointName),
|
||||||
|
avatarJointPosition = this.avatar.getJointPosition(avatarJointIndex);
|
||||||
|
|
||||||
|
var offset = avatarJointPosition.y - doppleJointPosition.y;
|
||||||
|
log('adjusting for offset', offset);
|
||||||
|
dopplePosition.y = avatarPosition.y + offset;
|
||||||
|
this.position = dopplePosition;
|
||||||
|
Overlays.editOverlay(this.overlayID, { position: this.position });
|
||||||
|
},
|
||||||
|
|
||||||
|
// @private @method - creates the update thread to synchronize joint data
|
||||||
|
_createUpdateThread: function() {
|
||||||
|
if (Doppleganger.USE_SCRIPT_UPDATE) {
|
||||||
|
log('creating Script.update thread');
|
||||||
|
this.onUpdate = this.update;
|
||||||
|
Script.update.connect(this, 'onUpdate');
|
||||||
|
} else {
|
||||||
|
log('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps');
|
||||||
|
var timeout = 1000 / Doppleganger.TARGET_FPS;
|
||||||
|
this._interval = Script.setInterval(bind(this, 'update'), timeout);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// @private @method - waits for model to load and handles timeouts
|
||||||
|
// @param {ModelResource} resource - a prefetched resource to monitor loading state against
|
||||||
|
_waitForModel: function(resource) {
|
||||||
|
var RECHECK_MS = 50;
|
||||||
|
var id = this.overlayID,
|
||||||
|
watchdogTimer = null;
|
||||||
|
|
||||||
|
function waitForJointNames() {
|
||||||
|
var error = null, result = null;
|
||||||
|
if (!watchdogTimer) {
|
||||||
|
error = 'joints_unavailable';
|
||||||
|
} else if (resource.state === Resource.State.FAILED) {
|
||||||
|
error = 'prefetch_failed';
|
||||||
|
} else if (resource.state === Resource.State.FINISHED) {
|
||||||
|
var names = Overlays.getProperty(id, 'jointNames');
|
||||||
|
if (Array.isArray(names) && names.length) {
|
||||||
|
result = { overlayID: id, jointNames: names };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error || result !== null) {
|
||||||
|
Script.clearInterval(this._interval);
|
||||||
|
this._interval = null;
|
||||||
|
if (watchdogTimer) {
|
||||||
|
Script.clearTimeout(watchdogTimer);
|
||||||
|
}
|
||||||
|
this.modelOverlayLoaded(error, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watchdogTimer = Script.setTimeout(function() {
|
||||||
|
watchdogTimer = null;
|
||||||
|
}, Doppleganger.MAX_WAIT_SECS * 1000);
|
||||||
|
this._interval = Script.setInterval(bind(this, waitForJointNames), RECHECK_MS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// @function - bind a function to a `this` context
|
||||||
|
// @param {Object} - the `this` context
|
||||||
|
// @param {Function|String} - function or method name
|
||||||
|
function bind(thiz, method) {
|
||||||
|
method = thiz[method] || method;
|
||||||
|
return function() {
|
||||||
|
return method.apply(thiz, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// @function - Qt signal polyfill
|
||||||
|
function signal(template) {
|
||||||
|
var callbacks = [];
|
||||||
|
return Object.defineProperties(function() {
|
||||||
|
var args = [].slice.call(arguments);
|
||||||
|
callbacks.forEach(function(obj) {
|
||||||
|
obj.handler.apply(obj.scope, args);
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
connect: { value: function(scope, handler) {
|
||||||
|
callbacks.push({scope: scope, handler: scope[handler] || handler || scope});
|
||||||
|
}},
|
||||||
|
disconnect: { value: function(scope, handler) {
|
||||||
|
var match = {scope: scope, handler: scope[handler] || handler || scope};
|
||||||
|
callbacks = callbacks.filter(function(obj) {
|
||||||
|
return !(obj.scope === match.scope && obj.handler === match.handler);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @function - debug logging
|
||||||
|
function log() {
|
||||||
|
print('doppleganger | ' + [].slice.call(arguments).join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- ADVANCED DEBUGGING --
|
||||||
|
// @function - Add debug joint indicators / extra debugging info.
|
||||||
|
// @param {Doppleganger} - existing Doppleganger instance to add controls to
|
||||||
|
//
|
||||||
|
// @note:
|
||||||
|
// * rightclick toggles mirror mode on/off
|
||||||
|
// * shift-rightclick toggles the debug indicators on/off
|
||||||
|
// * clicking on an indicator displays the joint name and mirrored joint name in the debug log.
|
||||||
|
//
|
||||||
|
// Example use:
|
||||||
|
// var doppleganger = new Doppleganger();
|
||||||
|
// Doppleganger.addDebugControls(doppleganger);
|
||||||
|
Doppleganger.addDebugControls = function(doppleganger) {
|
||||||
|
DebugControls.COLOR_DEFAULT = { red: 255, blue: 255, green: 255 };
|
||||||
|
DebugControls.COLOR_SELECTED = { red: 0, blue: 255, green: 0 };
|
||||||
|
|
||||||
|
function DebugControls() {
|
||||||
|
this.enableIndicators = true;
|
||||||
|
this.selectedJointName = null;
|
||||||
|
this.debugOverlayIDs = undefined;
|
||||||
|
this.jointSelected = signal(function(result) {});
|
||||||
|
}
|
||||||
|
DebugControls.prototype = {
|
||||||
|
start: function() {
|
||||||
|
if (!this.onMousePressEvent) {
|
||||||
|
this.onMousePressEvent = this._onMousePressEvent;
|
||||||
|
Controller.mousePressEvent.connect(this, 'onMousePressEvent');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stop: function() {
|
||||||
|
this.removeIndicators();
|
||||||
|
if (this.onMousePressEvent) {
|
||||||
|
Controller.mousePressEvent.disconnect(this, 'onMousePressEvent');
|
||||||
|
delete this.onMousePressEvent;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createIndicators: function(jointNames) {
|
||||||
|
this.jointNames = jointNames;
|
||||||
|
return jointNames.map(function(name, i) {
|
||||||
|
return Overlays.addOverlay('shape', {
|
||||||
|
shape: 'Icosahedron',
|
||||||
|
scale: 0.1,
|
||||||
|
solid: false,
|
||||||
|
alpha: 0.5
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
removeIndicators: function() {
|
||||||
|
if (this.debugOverlayIDs) {
|
||||||
|
this.debugOverlayIDs.forEach(Overlays.deleteOverlay);
|
||||||
|
this.debugOverlayIDs = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onJointsUpdated: function(overlayID) {
|
||||||
|
if (!this.enableIndicators) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var jointNames = Overlays.getProperty(overlayID, 'jointNames'),
|
||||||
|
jointOrientations = Overlays.getProperty(overlayID, 'jointOrientations'),
|
||||||
|
jointPositions = Overlays.getProperty(overlayID, 'jointPositions'),
|
||||||
|
selectedIndex = jointNames.indexOf(this.selectedJointName);
|
||||||
|
|
||||||
|
if (!this.debugOverlayIDs) {
|
||||||
|
this.debugOverlayIDs = this.createIndicators(jointNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
// batch all updates into a single call (using the editOverlays({ id: {props...}, ... }) API)
|
||||||
|
var updatedOverlays = this.debugOverlayIDs.reduce(function(updates, id, i) {
|
||||||
|
updates[id] = {
|
||||||
|
position: jointPositions[i],
|
||||||
|
rotation: jointOrientations[i],
|
||||||
|
color: i === selectedIndex ? DebugControls.COLOR_SELECTED : DebugControls.COLOR_DEFAULT,
|
||||||
|
solid: i === selectedIndex
|
||||||
|
};
|
||||||
|
return updates;
|
||||||
|
}, {});
|
||||||
|
Overlays.editOverlays(updatedOverlays);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMousePressEvent: function(evt) {
|
||||||
|
if (!evt.isLeftButton || !this.enableIndicators || !this.debugOverlayIDs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ray = Camera.computePickRay(evt.x, evt.y),
|
||||||
|
hit = Overlays.findRayIntersection(ray, true, this.debugOverlayIDs);
|
||||||
|
|
||||||
|
hit.jointIndex = this.debugOverlayIDs.indexOf(hit.overlayID);
|
||||||
|
hit.jointName = this.jointNames[hit.jointIndex];
|
||||||
|
this.jointSelected(hit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('$debugControls' in doppleganger) {
|
||||||
|
throw new Error('only one set of debug controls can be added per doppleganger');
|
||||||
|
}
|
||||||
|
var debugControls = new DebugControls();
|
||||||
|
doppleganger.$debugControls = debugControls;
|
||||||
|
|
||||||
|
function onMousePressEvent(evt) {
|
||||||
|
if (evt.isRightButton) {
|
||||||
|
if (evt.isShifted) {
|
||||||
|
debugControls.enableIndicators = !debugControls.enableIndicators;
|
||||||
|
if (!debugControls.enableIndicators) {
|
||||||
|
debugControls.removeIndicators();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
doppleganger.mirrored = !doppleganger.mirrored;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doppleganger.activeChanged.connect(function(active) {
|
||||||
|
if (active) {
|
||||||
|
debugControls.start();
|
||||||
|
doppleganger.jointsUpdated.connect(debugControls, 'onJointsUpdated');
|
||||||
|
Controller.mousePressEvent.connect(onMousePressEvent);
|
||||||
|
} else {
|
||||||
|
Controller.mousePressEvent.disconnect(onMousePressEvent);
|
||||||
|
doppleganger.jointsUpdated.disconnect(debugControls, 'onJointsUpdated');
|
||||||
|
debugControls.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
debugControls.jointSelected.connect(function(hit) {
|
||||||
|
debugControls.selectedJointName = hit.jointName;
|
||||||
|
if (hit.jointIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hit.mirroredJointName = Doppleganger.getMirroredJointNames([hit.jointName])[0];
|
||||||
|
log('selected joint:', JSON.stringify(hit, 0, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(debugControls, 'removeIndicators');
|
||||||
|
|
||||||
|
return doppleganger;
|
||||||
|
};
|
Loading…
Reference in a new issue