1 /* Copyright 2018, 2023 Paul Hänsch
3 This file is part of Serve0
5 Serve0 is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 Serve0 is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with Serve0 If not, see <http://www.gnu.org/licenses/>.
19 var contLeft = document.createElement("div");
20 var contRight = document.createElement("div");
21 var lv = document.createElement("canvas");
22 var rv = document.createElement("canvas");
23 var debug = document.createElement("p");
25 contLeft.setAttribute("style", "position: fixed; top: 0; left: 0; width: 50%; height: 100%; overflow: hidden; z-index: 100; background-color: #000;");
26 contRight.setAttribute("style", "position: fixed; top: 0; right: 0; width: 50%; height: 100%; overflow: hidden; z-index: 100; background-color: #000;");
27 lv.setAttribute("style", "position: absolute; top: 50%; left: calc(100% - 32mm); max-width: unset; max-height: unset;");
28 rv.setAttribute("style", "position: absolute; top: 50%; left: 32mm; max-width: unset; max-height: unset;");
29 debug.setAttribute("style", "display: none; position: fixed; top: 0; left: 0; z-index: 101; background: #000;");
32 function stereoview(layout, video) {
33 this.layout = layout; this.video = video;
35 let w = video.videoWidth; h = video.videoHeight;
36 let render, controlTimeout = 0;
37 let pitch = 0, roll = 0, yaw = 0;
38 let scale, vsf = 1, fov = 90, dist = 32;
40 document.body.appendChild(contLeft ).appendChild( lv );
41 document.body.appendChild(contRight).appendChild( rv );
42 document.body.appendChild( debug );
44 if ( layout == "180") {
45 lv.width = rv.width = w / 2; lv.height = rv.height = h;
46 scale = contLeft.offsetHeight / h * 2 * fov / 90;
48 lv.width = rv.width = w; lv.height = rv.height = h / 2;
49 scale = contLeft.offsetHeight / h * 4 * fov / 90;
52 lc = lv.getContext("2d"); rc = rv.getContext("2d");
55 if ( layout == "180" ) {
56 // scale = contLeft.offsetHeight / h * 2 * fov / 90;
57 lc.drawImage(video, 0, 0);
58 rc.drawImage(video, -w / 2, 0);
59 lv.style.transform = rv.style.transform =
60 "translate(" + (yaw / 180 * -w / 2 - w / 4) + "px, " +
61 (pitch / 90 * -h / 2 * scale - h / 2) + "px)" +
62 "rotate(" + roll + "deg) " + "scale(" + (scale / vsf) + ", " + scale + ")";
64 // scale = contLeft.offsetHeight / h * 4 * fov / 90;
65 lc.drawImage(video, yaw / 180 * -w / 2, 0);
66 lc.drawImage(video, yaw / 180 * -w / 2 + ((yaw>0) ? w : -w), 0);
67 rc.drawImage(video, yaw / 180 * -w / 2, -h/2);
68 rc.drawImage(video, yaw / 180 * -w / 2 + ((yaw>0) ? w : -w), -h/2);
69 lv.style.transform = rv.style.transform =
70 "translate(" + (- w / 2) + "px, " + (pitch / 90 * -h / 2 * (scale / 2) - h / 4) + "px) " +
71 "rotate(" + roll + "deg) " + "scale(" + (scale / vsf) + ", " + scale + ")";
74 // debug.textContent = "" + video.currentTime + " " + controlTimeout + " " + tx + " " + ty + " " + tz;
75 debug.textContent = "Pitch: " + pitch.toFixed(2) + " | Yaw: " + yaw.toFixed(2) + " | Roll: " + roll.toFixed(2) +
76 " | FOV: " + fov + " | Eye Distance: " + (2 * dist) + "mm";
78 requestAnimationFrame(draw);
84 let gp = navigator.getGamepads()[0];
85 let date = new Date();
87 debug.innerHTML += "<br/><br/>GamePad: " + gp.axes[0].toFixed(3) + " | " + gp.axes[1].toFixed(3)
88 for (cnt = 0; cnt < 16; cnt++) {
89 gp.buttons[cnt].pressed ? debug.textContent += " | B" + cnt + " 1" : debug.textContent += " | B" + cnt + " 0";
92 if ( gp && Date.now() < controlTimeout ) {
94 } else if (gp.buttons[0].pressed && gp.buttons[5].pressed) {
95 this.layout = layout = "180";
96 document.body.appendChild(contLeft ).appendChild( lv );
97 document.body.appendChild(contRight).appendChild( rv );
98 lv.width = rv.width = w / 2; lv.height = rv.height = h;
99 scale = contLeft.offsetHeight / h * 2 * fov / 90;
101 controlTimeout = Date.now() + 600;
102 } else if (gp.buttons[0].pressed && gp.buttons[4].pressed) {
103 this.layout = layout = "360";
104 document.body.appendChild(contLeft ).appendChild( lv );
105 document.body.appendChild(contRight).appendChild( rv );
106 lv.width = rv.width = w; lv.height = rv.height = h / 2;
107 scale = contLeft.offsetHeight / h * 4 * fov / 90;
109 controlTimeout = Date.now() + 600;
110 } else if (gp.buttons[3].pressed && gp.buttons[4].pressed) {
111 vsf = (vsf == 1) ? 2 : 1;
112 controlTimeout = Date.now() + 300;
113 } else if (gp.buttons[0].pressed) {
114 ( contLeft.parentElement)? contLeft.parentElement.removeChild(contLeft ):{};
115 (contRight.parentElement)?contRight.parentElement.removeChild(contRight):{};
117 controlTimeout = Date.now() + 300;
118 } else if (gp.buttons[1].pressed) {
119 (debug.style.display == "block") ? debug.style.display = "none" : debug.style.display = "block";
120 controlTimeout = Date.now() + 300;
121 } else if (gp.buttons[5].pressed) {
122 video.paused ? video.play() : video.pause();
123 controlTimeout = Date.now() + 300;
124 } else if (gp.buttons[4].pressed) {
125 video.currentTime += 1 / 30;
126 controlTimeout = Date.now() + 300;
127 } else if (gp.buttons[3].pressed && gp.axes[0] < -.3) {
129 lv.style.left = "calc(100% - " + dist + "mm)";
130 rv.style.left = "" + dist + "mm";
131 date.setTime(date.getTime() + 3 * 365 * 86400 * 1000)
132 document.cookie = "StereoDist=" + dist + "; expires=" + date.toUTCString() + "; path=/";
133 controlTimeout = Date.now() + 300;
134 } else if (gp.buttons[3].pressed && gp.axes[0] > .3) {
136 lv.style.left = "calc(100% - " + dist + "mm)";
137 rv.style.left = "" + dist + "mm";
138 date.setTime(date.getTime() + 3 * 365 * 86400 * 1000)
139 document.cookie = "StereoDist=" + dist + "; expires=" + date.toUTCString() + "; path=/";
140 controlTimeout = Date.now() + 300;
141 } else if (gp.buttons[3].pressed && gp.axes[1] < -.3) {
143 date.setTime(date.getTime() + 3 * 365 * 86400 * 1000)
144 document.cookie = "StereoFOV=" + fov + "; expires=" + date.toUTCString() + "; path=/";
145 controlTimeout = Date.now() + 300;
146 } else if (gp.buttons[3].pressed && gp.axes[1] > .3) {
148 date.setTime(date.getTime() + 3 * 365 * 86400 * 1000)
149 document.cookie = "StereoFOV=" + fov + "; expires=" + date.toUTCString() + "; path=/";
150 controlTimeout = Date.now() + 300;
151 } else if (gp.buttons[2].pressed && gp.axes[0] < -.3) {
152 yaw -= pitch ? ( Math.cos(pitch * Math.PI / 180) % 360 ) : 1;
153 if ( yaw > 180) yaw -= 360;
154 if ( yaw < -180) yaw += 360;
155 controlTimeout = Date.now() + 30;
156 } else if (gp.buttons[2].pressed && gp.axes[0] > .3) {
157 yaw += pitch ? ( Math.cos(pitch * Math.PI / 180) % 360 ) : 1;
158 if ( yaw > 180) yaw -= 360;
159 if ( yaw < -180) yaw += 360;
160 controlTimeout = Date.now() + 30;
161 } else if (gp.buttons[2].pressed && gp.axes[1] < -.3) {
163 controlTimeout = Date.now() + 300;
164 } else if (gp.buttons[2].pressed && gp.axes[1] > .3) {
166 controlTimeout = Date.now() + 300;
167 } else if ( gp.axes[0] > .3) {
168 video.currentTime += 10;
169 controlTimeout = Date.now() + 200;
170 } else if ( gp.axes[0] < -.3) {
171 video.currentTime -= 10;
172 controlTimeout = Date.now() + 200;
173 } else if ( gp.axes[1] > .3) {
174 video.currentTime -= 60;
175 controlTimeout = Date.now() + 300;
176 } else if ( gp.axes[1] < -.3) {
177 video.currentTime += 60;
178 controlTimeout = Date.now() + 300;
182 // mpuevent = new EventSource("http://localhost:314");
183 // var x = [], y = [], z = [], cnt = -1, inertia = 6;
185 // mpuevent.addEventListener("bearing", function(e) {
186 // bearing = e.data.split(" ");
187 // yaw = -parseFloat(bearing[0]);
189 // mpuevent.addEventListener("motion", function(e) {
190 // motion = e.data.split(" ");
192 // cnt = (cnt + 1) % inertia;
193 // x[cnt] = parseFloat(motion[0]);
194 // y[cnt] = parseFloat(motion[1]);
195 // z[cnt] = parseFloat(motion[2]);
197 // // tx = 0; x.forEach( function(n, i){ tx += n; } ); tx /= inertia;
198 // ty = 0; y.forEach( function(n, i){ ty += n; } ); ty /= inertia;
199 // tz = 0; z.forEach( function(n, i){ tz += n; } ); tz /= inertia;
201 // pitch = Math.asin((tz / 9.81 > 1)?1:(tz/9.81)) / Math.PI * 180 + 22.5;
202 // roll = - Math.asin((ty / 9.81 > 1)?1:(ty/9.81)) / Math.PI * 180;
203 // // yaw = (yaw + ty) % 360;
206 window.addEventListener("devicemotion", (function() {
207 let x = [], y = [], z = [], cnt = -1, inertia = 6;
209 cnt = (cnt + 1) % inertia;
211 x[cnt] = event.accelerationIncludingGravity.x;
212 y[cnt] = event.accelerationIncludingGravity.y;
213 z[cnt] = event.accelerationIncludingGravity.z;
215 tx = 0; x.forEach( function(n, i){ tx += n; } ); tx /= inertia;
216 ty = 0; y.forEach( function(n, i){ ty += n; } ); ty /= inertia;
217 tz = 0; z.forEach( function(n, i){ tz += n; } ); tz /= inertia;
219 pitch = Math.asin((tz / 9.81 > 1)?1:(tz/9.81)) / Math.PI * 180;
220 roll = - Math.asin((ty / 9.81 > 1)?1:(ty/9.81)) / Math.PI * 180;
222 - event.rotationRate.alpha / 1000 * event.interval * Math.cos(pitch * Math.PI / 180)
223 - event.rotationRate.gamma / 1000 * event.interval * Math.sin(pitch * Math.PI / 180)
225 if ( yaw > 180) yaw -= 360;
226 if ( yaw < -180) yaw += 360;
228 // pitch = (pitch + event.rotationRate.beta / 1000 * event.interval) % 360;
229 // roll = (roll + event.rotationRate.gamma / 1000 * event.interval) % 360;
230 // yaw = (yaw + ty) % 360;
234 window.addEventListener("click", function(event) {
235 ( contLeft.parentElement)? contLeft.parentElement.removeChild(contLeft ):{};
236 (contRight.parentElement)?contRight.parentElement.removeChild(contRight):{};
237 // video.style.display = "block";
241 dist = dist ? dist : parseInt(document.getElementById("StereoDist").getAttribute("value"));
242 fov = fov ? fov : parseInt(document.getElementById("StereoFOV" ).getAttribute("value"));
245 // video.style.display = "none";