================================================
FILE: src/content/capture/canvas-filter/js/main.js
================================================
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const source = document.querySelector('#source');
// TODO(hta): Use OffscreenCanvas for the intermediate canvases.
const canvasIn = document.querySelector('#canvas-source');
const canvasOut = document.querySelector('#canvas-result');
const result = document.querySelector('#result');
const stream = canvasOut.captureStream();
let inputStream = null;
let imageData = null;
result.srcObject = stream;
function loop() {
if (source.videoWidth > 0 && source.videoHeight > 0) {
canvasIn.width = source.videoWidth;
canvasIn.height = source.videoHeight;
const ctx = canvasIn.getContext('2d');
ctx.drawImage(source, 0, 0);
// Put a red square into the image, to mark it as "processed".
ctx.fillStyle = '#FF0000';
ctx.fillRect(10, 10, 80, 80);
imageData = ctx.getImageData(0, 0, canvasIn.width, canvasIn.height);
// At this point, we have data that can be transferred.
// We paint it on the second canvas.
canvasOut.width = source.videoWidth;
canvasOut.height = source.videoHeight;
const outCtx = canvasOut.getContext('2d');
outCtx.putImageData(imageData, 0, 0);
}
window.requestAnimationFrame(loop);
}
(async () => {
inputStream = await navigator.mediaDevices.getUserMedia({video: true});
source.srcObject = inputStream;
source.play();
result.play();
window.requestAnimationFrame(loop);
})();
================================================
FILE: src/content/capture/canvas-pc/css/main.css
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
canvas {
background-color: #ccc;
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 1em;
vertical-align: top;
}
video {
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 1em;
object-fit: cover;
}
#autoplay {
display: none;
}
================================================
FILE: src/content/capture/canvas-pc/index.html
================================================
Canvas to peer connection
Due to autoplay policy the video seems not to be playing. Clicking the left teapot usually resolves this.
Click and drag on the canvas element (on the left) to move the teapot.
This demo requires Firefox 47 or Chrome 52 (or later).
The teapot is drawn on the canvas element using WebGL. A stream is captured from the canvas using its captureStream()
method and streamed via a peer connection to the video element on the right.
View the browser console to see logging.
Several variables are in global scope, so you can inspect them from the console: canvas,
video, pc1, pc2 and stream.
================================================
FILE: src/content/capture/canvas-pc/js/main.js
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* global main */
'use strict';
const canvas = document.querySelector('canvas');
const video = document.querySelector('video');
let pc1;
let pc2;
let startTime;
video.addEventListener('loadedmetadata', function() {
document.getElementById('autoplay').style.display = 'none';
console.log(`Remote video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
});
video.onresize = () => {
console.log(`Remote video size changed to ${video.videoWidth}x${video.videoHeight}`);
// We'll use the first onsize callback as an indication that video has started
// playing out.
if (startTime) {
const elapsedTime = window.performance.now() - startTime;
console.log(`Setup time: ${elapsedTime.toFixed(3)}ms`);
startTime = null;
}
};
// Call main() in demo.js
main();
const stream = canvas.captureStream();
console.log('Got stream from canvas');
call();
function call() {
console.log('Starting call');
startTime = window.performance.now();
const videoTracks = stream.getVideoTracks();
const audioTracks = stream.getAudioTracks();
if (videoTracks.length > 0) {
console.log(`Using video device: ${videoTracks[0].label}`);
}
if (audioTracks.length > 0) {
console.log(`Using audio device: ${audioTracks[0].label}`);
}
const servers = null;
pc1 = new RTCPeerConnection(servers);
console.log('Created local peer connection object pc1');
pc1.onicecandidate = e => onIceCandidate(pc1, e);
pc2 = new RTCPeerConnection(servers);
console.log('Created remote peer connection object pc2');
pc2.onicecandidate = e => onIceCandidate(pc2, e);
pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);
pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);
pc2.ontrack = gotRemoteStream;
stream.getTracks().forEach(
track => {
pc1.addTrack(
track,
stream
);
}
);
console.log('Added local stream to pc1');
console.log('pc1 createOffer start');
pc1.createOffer(onCreateOfferSuccess, onCreateSessionDescriptionError);
}
function onCreateSessionDescriptionError(error) {
console.log(`Failed to create session description: ${error.toString()}`);
}
function onCreateOfferSuccess(desc) {
console.log(`Offer from pc1\n${desc.sdp}`);
console.log('pc1 setLocalDescription start');
pc1.setLocalDescription(desc, () => onSetLocalSuccess(pc1), onSetSessionDescriptionError);
console.log('pc2 setRemoteDescription start');
pc2.setRemoteDescription(desc, () => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);
console.log('pc2 createAnswer start');
// Since the 'remote' side has no media stream we need
// to pass in the right constraints in order for it to
// accept the incoming offer of audio and video.
pc2.createAnswer(onCreateAnswerSuccess, onCreateSessionDescriptionError);
}
function onSetLocalSuccess(pc) {
console.log(`${getName(pc)} setLocalDescription complete`);
}
function onSetRemoteSuccess(pc) {
console.log(`${getName(pc)} setRemoteDescription complete`);
}
function onSetSessionDescriptionError(error) {
console.log(`Failed to set session description: ${error.toString()}`);
}
function gotRemoteStream(e) {
if (video.srcObject !== e.streams[0]) {
video.srcObject = e.streams[0];
console.log('pc2 received remote stream');
if (video.paused) {
document.getElementById('autoplay').style.display = 'block';
}
}
}
function onCreateAnswerSuccess(desc) {
console.log(`Answer from pc2:\n${desc.sdp}`);
console.log('pc2 setLocalDescription start');
pc2.setLocalDescription(desc, () => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
console.log('pc1 setRemoteDescription start');
pc1.setRemoteDescription(desc, () => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}
function onIceCandidate(pc, event) {
getOtherPc(pc).addIceCandidate(event.candidate)
.then(
() => onAddIceCandidateSuccess(pc),
err => onAddIceCandidateError(pc, err)
);
console.log(`${getName(pc)} ICE candidate: ${event.candidate ? event.candidate.candidate : '(null)'}`);
}
function onAddIceCandidateSuccess(pc) {
console.log(`${getName(pc)} addIceCandidate success`);
}
function onAddIceCandidateError(pc, error) {
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
}
function onIceStateChange(pc, event) {
if (pc) {
console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', event);
}
}
function getName(pc) {
return (pc === pc1) ? 'pc1' : 'pc2';
}
function getOtherPc(pc) {
return (pc === pc1) ? pc2 : pc1;
}
================================================
FILE: src/content/capture/canvas-record/css/main.css
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 3px 10px 0;
padding-left: 2px;
padding-right: 2px;
width: 99px;
}
button:last-of-type {
margin: 0;
}
p.borderBelow {
margin: 0 0 20px 0;
padding: 0 0 20px 0;
}
canvas {
background-color: #ccc;
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 1em;
vertical-align: top;
}
video {
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 1em;
object-fit: cover;
}
================================================
FILE: src/content/capture/canvas-record/index.html
================================================
Record canvas stream
Click and drag on the canvas (on the left) to move the teapot.
This demo requires Firefox 43 or above, Chrome 51 or above, or Chrome 50 with Experimental Web Platform
features enabled.
The teapot is drawn on the canvas element using WebGL. A stream is captured from the canvas element using its
captureStream() method then recorded using the MediaRecorder API.
The canvas, video, and stream variables are in global scope, so you can
inspect them from the browser console.
Click and drag on the canvas (on the left) to move the teapot.
This demo requires Firefox 47 or Chrome 52 (or later).
The teapot is drawn on the canvas element using WebGL. A stream is captured from the canvas element using its
captureStream() method and set as the srcObject of the video element.
The canvas, video, and stream variables are in global scope, so you can
inspect them from the browser console.
================================================
FILE: src/content/capture/canvas-video/js/main.js
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* global main */
'use strict';
// Call main() in demo.js
main();
const canvas = document.querySelector('canvas');
const video = document.querySelector('video');
const stream = canvas.captureStream();
video.srcObject = stream;
================================================
FILE: src/content/capture/video-contenthint/css/main.css
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
video {
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 9 / 16);
margin: 1em;
object-fit: cover;
}
.video-container {
border-bottom: 1px solid grey;
font-style: italic;
margin: 20px;
}
#videos {
text-align: center;
width: 100%;
}
================================================
FILE: src/content/capture/video-contenthint/index.html
================================================
MediaStreamTrack Content Hints
This demo requires Chrome 57.0.2957.0 or later with Experimental Web Platform features enabled
from chrome://flags.
A stream is captured from the source video using the captureStream() method. The stream is cloned
and transmitted via two separate PeerConnections using 50kbps of video bandwidth. This is insufficient to
generate good quality in the encoded bitstream, so trade-offs have to be made.
The transmitted stream tracks are using MediaStreamTrack
Content Hints to indicate characteristics in the video stream, which informs PeerConnection on how to encode
the track (to prefer motion or individual frame detail).
The text part of the clip shows a clear case for when 'detail' is better, and the fighting scene shows a
clear case for when 'motion' is better. The spinning model however shows a case where 'motion'
or 'detail' are not clear-cut decisions and even with good content detection what's preferred depends
on what the user prefers.
Other MediaStreamTrack consumers such as MediaStreamRecorder can also make use of this information to guide
encoding parameters for the stream without additional extensions to the MediaStreamRecorder specification, but
this is currently not implemented in Chromium.
================================================
FILE: src/content/capture/video-contenthint/js/main.js
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const srcVideo = document.getElementById('srcVideo');
const motionVideo = document.getElementById('motionVideo');
const detailVideo = document.getElementById('detailVideo');
let srcStream;
let motionStream;
let detailStream;
const offerOptions = {
offerToReceiveAudio: 0,
offerToReceiveVideo: 1
};
function maybeCreateStream() {
if (srcStream) {
return;
}
if (srcVideo.captureStream) {
srcStream = srcVideo.captureStream();
call();
} else {
console.log('captureStream() not supported');
}
}
// Video tag capture must be set up after video tracks are enumerated.
srcVideo.oncanplay = maybeCreateStream;
if (srcVideo.readyState >= 3) { // HAVE_FUTURE_DATA
// Video is already ready to play, call maybeCreateStream in case oncanplay
// fired before we registered the event handler.
maybeCreateStream();
}
srcVideo.play();
function setVideoTrackContentHints(stream, hint) {
const tracks = stream.getVideoTracks();
tracks.forEach(track => {
if ('contentHint' in track) {
track.contentHint = hint;
if (track.contentHint !== hint) {
console.log('Invalid video track contentHint: \'' + hint + '\'');
}
} else {
console.log('MediaStreamTrack contentHint attribute not supported');
}
});
}
function call() {
// This creates multiple independent PeerConnections instead of multiple
// streams on a single PeerConnection object so that b=AS (the bitrate
// constraints) can be applied independently.
motionStream = srcStream.clone();
// TODO(pbos): Remove fluid when no clients use it, motion is the newer name.
setVideoTrackContentHints(motionStream, 'fluid');
setVideoTrackContentHints(motionStream, 'motion');
establishPC(motionVideo, motionStream);
detailStream = srcStream.clone();
// TODO(pbos): Remove detailed when no clients use it, detail is the newer
// name.
setVideoTrackContentHints(detailStream, 'detailed');
setVideoTrackContentHints(detailStream, 'detail');
establishPC(detailVideo, detailStream);
}
function establishPC(videoTag, stream) {
const pc1 = new RTCPeerConnection(null);
const pc2 = new RTCPeerConnection(null);
pc1.onicecandidate = e => {
onIceCandidate(pc1, pc2, e);
};
pc2.onicecandidate = e => {
onIceCandidate(pc2, pc1, e);
};
pc2.ontrack = event => {
if (videoTag.srcObject !== event.streams[0]) {
videoTag.srcObject = event.streams[0];
}
};
stream.getTracks().forEach(track => pc1.addTrack(track, stream));
pc1.createOffer(offerOptions)
.then(desc => {
pc1.setLocalDescription(desc)
.then(() => pc2.setRemoteDescription(desc))
.then(() => pc2.createAnswer())
.then(answerDesc => onCreateAnswerSuccess(pc1, pc2, answerDesc))
.catch(onSetSessionDescriptionError);
})
.catch(e => console.log('Failed to create session description: ' + e.toString()));
}
function onSetSessionDescriptionError(error) {
console.log('Failed to set session description: ' + error.toString());
}
function onCreateAnswerSuccess(pc1, pc2, desc) {
// Hard-code video bitrate to 50kbps.
desc.sdp = desc.sdp.replace(/a=mid:(.*)\r\n/g, 'a=mid:$1\r\nb=AS:' + 50 + '\r\n');
pc2.setLocalDescription(desc)
.then(() => pc1.setRemoteDescription(desc))
.catch(onSetSessionDescriptionError);
}
function onIceCandidate(pc, otherPc, event) {
otherPc.addIceCandidate(event.candidate);
}
================================================
FILE: src/content/capture/video-pc/css/main.css
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
video {
margin: 0 10px 0 0;
width: calc(50% - 7px);
}
video:last-of-type {
margin-right: 0;
}
@media screen and (max-width: 400px) {
video {
margin: 0 5px 20px 0;
width: calc(50% - 5px);
}
}
================================================
FILE: src/content/capture/video-pc/index.html
================================================
Video to peer connection
This demo requires Firefox 47, Chrome 53 with Experimental Web Platform features enabled from
chrome://flags.
A stream is captured from the video on the left using the captureStream() method, and streamed via a
peer connection to the video element on the right.
View the browser console to see logging.
Several variables are in global scope, so you can inspect them from the console: pc1,
pc2 and stream.
================================================
FILE: src/content/capture/video-pc/js/main.js
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const leftVideo = document.getElementById('leftVideo');
const rightVideo = document.getElementById('rightVideo');
let stream;
let pc1;
let pc2;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
let startTime;
function maybeCreateStream() {
if (stream) {
return;
}
if (leftVideo.captureStream) {
stream = leftVideo.captureStream();
console.log('Captured stream from leftVideo with captureStream',
stream);
call();
} else if (leftVideo.mozCaptureStream) {
stream = leftVideo.mozCaptureStream();
console.log('Captured stream from leftVideo with mozCaptureStream()',
stream);
call();
} else {
console.log('captureStream() not supported');
}
}
// Video tag capture must be set up after video tracks are enumerated.
leftVideo.oncanplay = maybeCreateStream;
if (leftVideo.readyState >= 3) { // HAVE_FUTURE_DATA
// Video is already ready to play, call maybeCreateStream in case oncanplay
// fired before we registered the event handler.
maybeCreateStream();
}
leftVideo.play();
rightVideo.onloadedmetadata = () => {
console.log(`Remote video videoWidth: ${rightVideo.videoWidth}px, videoHeight: ${rightVideo.videoHeight}px`);
};
rightVideo.onresize = () => {
console.log(`Remote video size changed to ${rightVideo.videoWidth}x${rightVideo.videoHeight}`);
// We'll use the first onresize callback as an indication that
// video has started playing out.
if (startTime) {
const elapsedTime = window.performance.now() - startTime;
console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');
startTime = null;
}
};
function call() {
console.log('Starting call');
startTime = window.performance.now();
const videoTracks = stream.getVideoTracks();
const audioTracks = stream.getAudioTracks();
if (videoTracks.length > 0) {
console.log(`Using video device: ${videoTracks[0].label}`);
}
if (audioTracks.length > 0) {
console.log(`Using audio device: ${audioTracks[0].label}`);
}
const servers = null;
pc1 = new RTCPeerConnection(servers);
console.log('Created local peer connection object pc1');
pc1.onicecandidate = e => onIceCandidate(pc1, e);
pc2 = new RTCPeerConnection(servers);
console.log('Created remote peer connection object pc2');
pc2.onicecandidate = e => onIceCandidate(pc2, e);
pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);
pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);
pc2.ontrack = gotRemoteStream;
stream.getTracks().forEach(track => pc1.addTrack(track, stream));
console.log('Added local stream to pc1');
console.log('pc1 createOffer start');
pc1.createOffer(onCreateOfferSuccess, onCreateSessionDescriptionError, offerOptions);
}
function onCreateSessionDescriptionError(error) {
console.log(`Failed to create session description: ${error.toString()}`);
}
function onCreateOfferSuccess(desc) {
console.log(`Offer from pc1
${desc.sdp}`);
console.log('pc1 setLocalDescription start');
pc1.setLocalDescription(desc, () => onSetLocalSuccess(pc1), onSetSessionDescriptionError);
console.log('pc2 setRemoteDescription start');
pc2.setRemoteDescription(desc, () => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);
console.log('pc2 createAnswer start');
// Since the 'remote' side has no media stream we need
// to pass in the right constraints in order for it to
// accept the incoming offer of audio and video.
pc2.createAnswer(onCreateAnswerSuccess, onCreateSessionDescriptionError);
}
function onSetLocalSuccess(pc) {
console.log(`${getName(pc)} setLocalDescription complete`);
}
function onSetRemoteSuccess(pc) {
console.log(`${getName(pc)} setRemoteDescription complete`);
}
function onSetSessionDescriptionError(error) {
console.log(`Failed to set session description: ${error.toString()}`);
}
function gotRemoteStream(event) {
if (rightVideo.srcObject !== event.streams[0]) {
rightVideo.srcObject = event.streams[0];
console.log('pc2 received remote stream', event);
}
}
function onCreateAnswerSuccess(desc) {
console.log(`Answer from pc2:
${desc.sdp}`);
console.log('pc2 setLocalDescription start');
pc2.setLocalDescription(desc, () => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
console.log('pc1 setRemoteDescription start');
pc1.setRemoteDescription(desc, () => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}
function onIceCandidate(pc, event) {
getOtherPc(pc).addIceCandidate(event.candidate)
.then(
() => onAddIceCandidateSuccess(pc),
err => onAddIceCandidateError(pc, err)
);
console.log(`${getName(pc)} ICE candidate:
${event.candidate ?
event.candidate.candidate : '(null)'}`);
}
function onAddIceCandidateSuccess(pc) {
console.log(`${getName(pc)} addIceCandidate success`);
}
function onAddIceCandidateError(pc, error) {
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
}
function onIceStateChange(pc, event) {
if (pc) {
console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', event);
}
}
function getName(pc) {
return (pc === pc1) ? 'pc1' : 'pc2';
}
function getOtherPc(pc) {
return (pc === pc1) ? pc2 : pc1;
}
================================================
FILE: src/content/capture/video-video/css/main.css
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
video {
margin: 0 10px 0 0;
width: calc(50% - 7px);
}
video:last-of-type {
margin-right: 0;
}
@media screen and (max-width: 400px) {
video {
margin: 0 5px 20px 0;
width: calc(50% - 5px);
}
}
================================================
FILE: src/content/capture/video-video/index.html
================================================
captureStream(): video to video
================================================
FILE: src/content/capture/video-video/js/main.js
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const leftVideo = document.getElementById('leftVideo');
const rightVideo = document.getElementById('rightVideo');
leftVideo.addEventListener('canplay', () => {
let stream;
const fps = 0;
if (leftVideo.captureStream) {
stream = leftVideo.captureStream(fps);
} else if (leftVideo.mozCaptureStream) {
stream = leftVideo.mozCaptureStream(fps);
} else {
console.error('Stream capture is not supported');
stream = null;
}
rightVideo.srcObject = stream;
});
================================================
FILE: src/content/capture/worker-process/css/main.css
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
canvas {
background-color: #ccc;
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 1em;
vertical-align: top;
}
video {
--width: calc(45%);
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 1em;
object-fit: cover;
}
================================================
FILE: src/content/capture/worker-process/index.html
================================================
Video processing in a Worker
The camera is captured to a MediaStreamTrack, which is turned into a
WHATWG Stream of ImageData objects by means of a canvas, and a red
square is added.
The stream is sent to a Worker, which returns a new stream containing
the same video data.
This is then mapped back to a MediaStream using another canvas.
The chief purpose of the demo is to demonstrate that this is doable,
but that performance can be improved significantly.
NOTE: This works only on Chrome 76 and above with experimental Web
features enabled, since it depends on transferable Streams.
A similar demo, without the worker process, is on the canvas filter demo.
================================================
FILE: src/content/capture/worker-process/js/main.js
================================================
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const source = document.querySelector('#source');
// TODO(hta): Use OffscreenCanvas for the intermediate canvases.
const canvasIn = document.querySelector('#canvas-source');
const canvasOut = document.querySelector('#canvas-result');
const result = document.querySelector('#result');
const stream = canvasOut.captureStream();
let inputStream = null;
let imageData = null;
let transformStream = null;
let writer = null;
let reader = null;
result.srcObject = stream;
function loop() {
if (source.videoWidth > 0 && source.videoHeight > 0) {
canvasIn.width = source.videoWidth;
canvasIn.height = source.videoHeight;
const ctx = canvasIn.getContext('2d');
ctx.drawImage(source, 0, 0);
// Put a red square into the image, to mark it as "processed".
ctx.fillStyle = '#FF0000';
ctx.fillRect(10, 10, 80, 80);
imageData = ctx.getImageData(0, 0, canvasIn.width, canvasIn.height);
// At this point, we have data that can be transferred.
writer.write(imageData);
}
window.requestAnimationFrame(loop);
}
// The read function paints the incoming data on the second canvas.
const readData = async () => {
const result = await reader.read();
if (!result.done) {
canvasOut.width = source.videoWidth;
canvasOut.height = source.videoHeight;
const outCtx = canvasOut.getContext('2d');
outCtx.putImageData(result.value, 0, 0);
readData();
}
};
(async () => {
inputStream = await navigator.mediaDevices.getUserMedia({video: true});
source.srcObject = inputStream;
transformStream = new TransformStream();
writer = transformStream.writable.getWriter();
const myWorker = new Worker('js/worker.js');
myWorker.onmessage = function(e) {
reader = e.data[1].getReader();
// Start the flow of data.
readData();
window.requestAnimationFrame(loop);
};
myWorker.postMessage(['stream', transformStream.readable],
[transformStream.readable]);
source.play();
result.play();
})();
================================================
FILE: src/content/capture/worker-process/js/worker.js
================================================
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
onmessage = function(e) {
const command = e.data[0];
if (command == 'stream') {
const inputStream = e.data[1];
const transformStream = new TransformStream();
inputStream.pipeTo(transformStream.writable);
postMessage(['response', transformStream.readable],
[transformStream.readable]);
}
};
================================================
FILE: src/content/datachannel/basic/css/main.css
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 1em 1em 0;
width: 90px;
}
div#buttons {
margin: 0 0 1em 0;
}
div#send {
margin: 0 20px 1em 0;
}
div#sendReceive {
border-bottom: 1px solid #eee;
margin: 0;
padding: 0 0 10px 0;
}
div#sendReceive > div {
display: inline-block;
width: calc(50% - 20px);
}
form {
margin: 0 0 1em 0;
white-space: nowrap;
}
form span {
font-weight: 300;
margin: 0 1em 0 0;
white-space: normal;
}
textarea {
color: #444;
font-size: 0.9em;
font-weight: 300;
height: 7.0em;
padding: 5px;
width: calc(100% - 10px);
}
================================================
FILE: src/content/datachannel/basic/index.html
================================================
Transmit text
This page generates and sends the specified amount of data via WebRTC datachannels.
To accomplish this in an interoperable way, the data is split into chunks which are then transferred via the
datachannel. The datachannel is reliable and ordered by default which is well-suited to filetransfers.
Send and receive progress is monitored using HTML5 progress elements.
Send progress:
Receive progress:
View the console to see logging.
The RTCPeerConnection objects pc1 and pc2 are
in global scope, so you can inspect them in the console as well.
================================================
FILE: src/content/datachannel/datatransfer/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const MAX_CHUNK_SIZE = 262144;
let pc1;
let pc2;
let sendChannel;
let receiveChannel;
let chunkSize;
let lowWaterMark;
let highWaterMark;
let dataString;
let timeoutHandle = null;
const megsToSend = document.querySelector('input#megsToSend');
const sendButton = document.querySelector('button#sendTheData');
const orderedCheckbox = document.querySelector('input#ordered');
const sendProgress = document.querySelector('progress#sendProgress');
const receiveProgress = document.querySelector('progress#receiveProgress');
const errorMessage = document.querySelector('div#errorMsg');
const transferStatus = document.querySelector('span#transferStatus');
let bytesToSend = 0;
let totalTimeUsedInSend = 0;
let numberOfSendCalls = 0;
let maxTimeUsedInSend = 0;
let sendStartTime = 0;
let currentThroughput = 0;
sendButton.addEventListener('click', createConnection);
// Prevent data sent to be set to 0.
megsToSend.addEventListener('change', function() {
const number = this.value;
if (Number.isNaN(number)) {
errorMessage.innerHTML = `Invalid value for MB to send: ${number}`;
} else if (number <= 0) {
sendButton.disabled = true;
errorMessage.innerHTML = '
This page shows how to transfer a file via WebRTC datachannels.
To accomplish this in an interoperable way, the file is split into chunks which are then transferred via the datachannel. The datachannel is reliable and ordered by default which is well-suited to filetransfers.
Send and receive progress is monitored using HTML5 progress elements.
At the receiver, the file is reassembled using the Blob API and made available for download.
Note: real-world applications require a file transfer protocol to send metadata about the file (such as the filename, type, size, last modification date, hash, ...).This information can be conveyed either via the signaling channel or in-band. The demo elides this by assuming knowledge of the file size at the receiver and closes both the datachannel and the peerconnection when the correct amount of bytes has been received.
Send progress:
Receive progress:
View the console to see logging.
The RTCPeerConnection objects pc1 and pc2 are in global scope, so you can inspect them in the console as well.
Get available audio, video sources and audio output devices from mediaDevices.enumerateDevices()
then set the source for getUserMedia() using a deviceId constraint.
Note: without permission, the browser will restrict the available devices to at most one per type.
Note: If you hear a reverb sound your microphone is picking up the output of your
speakers/headset, lower the volume and/or move the microphone further away from your speakers/headset.
================================================
FILE: src/content/devices/multi/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const gumAudio = document.querySelector('audio.gum');
gumAudio.addEventListener('play', () => {
gumAudio.volume = 0.1;
console.log('Audio lowered to reduce feedback from local gUM stream');
});
const gumVideo = document.querySelector('video.gum');
gumVideo.addEventListener('play', () => {
gumVideo.volume = 0.1;
console.log('Audio lowered to reduce feedback from local gUM stream');
});
function gotDevices(deviceInfos) {
const masterOutputSelector = document.createElement('select');
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = document.createElement('option');
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === 'audiooutput') {
console.info('Found audio output device: ', deviceInfo.label);
option.text = deviceInfo.label || `speaker ${masterOutputSelector.length + 1}`;
masterOutputSelector.appendChild(option);
} else {
console.log('Found non audio output device: ', deviceInfo.label);
}
}
// Clone the master outputSelector and replace outputSelector placeholders.
const allOutputSelectors = document.querySelectorAll('select');
for (let selector = 0; selector < allOutputSelectors.length; selector++) {
const newOutputSelector = masterOutputSelector.cloneNode(true);
newOutputSelector.addEventListener('change', changeAudioDestination);
allOutputSelectors[selector].parentNode.replaceChild(newOutputSelector,
allOutputSelectors[selector]);
}
}
navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
// Attach audio output device to the provided media element using the deviceId.
function attachSinkId(element, sinkId, outputSelector) {
if (typeof element.sinkId !== 'undefined') {
element.setSinkId(sinkId)
.then(() => {
console.log(`Success, audio output device attached: ${sinkId} to element with ${element.title} as source.`);
})
.catch(error => {
let errorMessage = error;
if (error.name === 'SecurityError') {
errorMessage = `You need to use HTTPS for selecting audio output device: ${error}`;
}
console.error(errorMessage);
// Jump back to first output device in the list as it's the default.
outputSelector.selectedIndex = 0;
});
} else {
console.warn('Browser does not support output device selection.');
}
}
function changeAudioDestination(event) {
const deviceId = event.target.value;
const outputSelector = event.target;
// FIXME: Make the media element lookup dynamic.
const element = event.composedPath()[2].childNodes[1];
attachSinkId(element, deviceId, outputSelector);
}
function gotStream(stream) {
window.stream = stream; // make stream available to console
gumAudio.srcObject = stream;
gumVideo.srcObject = stream;
}
function start() {
if (window.stream) {
window.stream.getTracks().forEach(track => {
track.stop();
});
}
const constraints = {
audio: true,
video: true
};
navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);
}
start();
function handleError(error) {
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
================================================
FILE: src/content/extensions/multipleroutes/src/README.md
================================================
## Chrome WebRTC Network Limiter
Configures the WebRTC traffic routing options in Chrome's privacy settings.
★ What it does:
This configures WebRTC to not use certain IP addresses or protocols:
- private IP addresses not visible to the public internet (e.g. addresses like 192.168.1.2)
- any public IP addresses associated with network interfaces that are not used for web traffic (e.g. an ISP-provided address, when browsing through a VPN)
- Require WebRTC traffic to go through proxy servers as configured in Chrome. Since most of the proxy servers don't handle UDP, this effectively turns off UDP until UDP proxy support is available in Chrome and such proxies are widely deployed.
When the extension is installed on Chrome versions prior to M48, WebRTC will only use the public IP address associated with the interface used for web traffic, typically the same addresses that are already provided to sites in browser HTTP requests. For Chrome version M48 and after, this extension provides one more configuration which allows WebRTC to use both the default public address and, for machines behind a NAT, the default private address which is associated with the public one. Said behavior will be the default after a fresh installation of the extension to Chrome M48. For upgrade scenarios, the previous selected configuration should not be changed.
The extension may also disable non-proxied UDP, but this is not on by default and must be configured using the extension's Options page.
★ Notes:
This extension may affect the performance of applications that use WebRTC for audio/video or real-time data communication. Because it limits the potential network paths and protocols, WebRTC may pick a path which results in significantly longer delay or lower quality (e.g. through a VPN) or use TCP only through proxy servers which is not ideal for real-time communication. We are attempting to determine how common this is.
By installing this item, you agree to the Google Terms of Service and Privacy Policy at https://www.google.com/intl/en/policies/.
================================================
FILE: src/content/extensions/multipleroutes/src/_locales/en/messages.json
================================================
{
"NETLI_DEFAULT_RADIO": {
"message": "Give me the best media experience: This option allows Chrome to explore all network paths to find the best way to send and receive media, which may be different from normal web traffic."
},
"NETLI_DEFAULT_PUBLIC_AND_PRIVATE_INTERFACES_RADIO": {
"message": "Use my default public and private IP addresses: This option forces Chrome to use the same network path for media as for normal web traffic, except when a web proxy is present. For machines behind a NAT, Chrome will also use the default private address to enhance connectivity. To prevent degraded performance, Chrome will attempt to send media directly instead of using the proxy."
},
"NETLI_DEFAULT_PUBLIC_INTERFACE_ONLY_RADIO": {
"message": "Use only my default public IP address: This option is the same as Use my default public and private IP addresses except that Chrome will not use the private default address."
},
"NETLI_DISABLE_NON_PROXIED_UDP_RADIO": {
"message": "Use my proxy server (if present): This option forces Chrome to use the same network path for media as for normal web traffic, including use of a web proxy. Chrome will always attempt to send media through the proxy, which will typically hurt media performance and increase the load on the proxy; furthermore, this behavior may be incompatible with some applications."
},
"NETLI_OPTION_NOT_SUPPORTED": {
"message": "Grayed out options require newer verion of Chrome."
},
"NETLI_APPDESC": {
"message": "Configures how WebRTC's network traffic is routed by changing Chrome's privacy settings."
},
"NETLI_APPNAME": {
"message": "WebRTC Network Limiter"
},
"NETLI_OPTIONS": {
"message": "WebRTC Network Limiter Options"
}
}
================================================
FILE: src/content/extensions/multipleroutes/src/manifest.json
================================================
{
"default_locale": "en",
"description": "__MSG_NETLI_APPDESC__",
"icons": {
"16": "img/icon_16.png",
"128": "img/icon_128.png"
},
"manifest_version": 3,
"minimum_chrome_version": "42.0.2311.135",
"name": "__MSG_NETLI_APPNAME__",
"options_ui": {
"open_in_tab": false,
"page": "options.html"
},
"permissions": [ "privacy" ],
"version": "0.2.1.4"
}
================================================
FILE: src/content/extensions/multipleroutes/src/options.html
================================================
================================================
FILE: src/content/extensions/multipleroutes/src/options.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const pn = chrome.privacy.network;
const pi = chrome.privacy.IPHandlingPolicy;
const mapPolicyToRadioId = {};
mapPolicyToRadioId[pi.DEFAULT] = 0;
mapPolicyToRadioId[pi.DEFAULT_PUBLIC_AND_PRIVATE_INTERFACES] = 1;
mapPolicyToRadioId[pi.DEFAULT_PUBLIC_INTERFACE_ONLY] = 2;
mapPolicyToRadioId[pi.DISABLE_NON_PROXIED_UDP] = 3;
const mapRadioIdToPolicy = {};
mapRadioIdToPolicy[0] = pi.DEFAULT;
mapRadioIdToPolicy[1] = pi.DEFAULT_PUBLIC_AND_PRIVATE_INTERFACES;
mapRadioIdToPolicy[2] = pi.DEFAULT_PUBLIC_INTERFACE_ONLY;
mapRadioIdToPolicy[3] = pi.DISABLE_NON_PROXIED_UDP;
// Saves options.
function saveOptions() {
const radios = document.getElementsByName('ip_policy_selection');
let i;
for (i = 0; i < radios.length; i++) {
if (radios[i].checked) {
break;
}
}
pn.webRTCIPHandlingPolicy.set({
value: mapRadioIdToPolicy[i]
});
}
function restoreRadios(policy) {
const radios = document.getElementsByName('ip_policy_selection');
radios[mapPolicyToRadioId[policy]].checked = true;
}
function restoreOption() {
pn.webRTCIPHandlingPolicy.get({}, function(details) {
restoreRadios(details.value);
});
}
document.addEventListener('DOMContentLoaded', restoreOption);
document.getElementById('default').
addEventListener('click', saveOptions);
document.getElementById('default_public_and_private_interfaces').
addEventListener('click', saveOptions);
document.getElementById('default_public_interface_only').
addEventListener('click', saveOptions);
document.getElementById('disable_non_proxied_udp').
addEventListener('click', saveOptions);
document.title = chrome.i18n.getMessage('netli_options');
const i18nElements = document.querySelectorAll('*[i18n-content]');
for (let i = 0; i < i18nElements.length; i++) {
const elem = i18nElements[i];
const msg = elem.getAttribute('i18n-content');
elem.innerHTML = chrome.i18n.getMessage(msg);
}
function browserSupportsIPHandlingPolicy() {
return pn.webRTCIPHandlingPolicy !== undefined;
}
if (browserSupportsIPHandlingPolicy()) {
// Hide the 'not supported' banner.
document.getElementById('not_supported').innerHTML = '';
} else {
// Disable all options.
for (let i = 0; i < 4; i++) {
const key = 'Mode' + i;
const section = document.getElementById(key);
section.style.color = 'gray';
section.querySelector('input').disabled = true;
}
}
================================================
FILE: src/content/extensions/svc/css/main.css
================================================
/*
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 20px 0 0;
width: 83px;
}
button#hangupButton {
margin: 0;
}
video {
--width: 45%;
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 0 0 20px 0;
vertical-align: top;
}
video#localVideo {
margin: 0 20px 20px 0;
}
div.box {
margin: 1em;
}
@media screen and (max-width: 400px) {
button {
width: 83px;
margin: 0 11px 10px 0;
}
video {
height: 90px;
margin: 0 0 10px 0;
width: calc(50% - 7px);
}
video#localVideo {
margin: 0 10px 20px 0;
}
}
div.graph-container {
float: left;
margin: 0.5em;
width: calc(50% - 1em);
}
================================================
FILE: src/content/extensions/svc/index.html
================================================
Scalable Video Coding (SVC) Extension
================================================
FILE: src/content/getusermedia/audio/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// Put variables in global scope to make them available to the browser console.
const audio = document.querySelector('audio');
const constraints = window.constraints = {
audio: true,
video: false
};
function handleSuccess(stream) {
const audioTracks = stream.getAudioTracks();
console.log('Got stream with constraints:', constraints);
console.log('Using audio device: ' + audioTracks[0].label);
stream.oninactive = function() {
console.log('Stream ended');
};
window.stream = stream; // make variable available to browser console
audio.srcObject = stream;
}
function handleError(error) {
const errorMessage = 'navigator.MediaDevices.getUserMedia error: ' + error.message + ' ' + error.name;
document.getElementById('errorMsg').innerText = errorMessage;
console.log(errorMessage);
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
================================================
FILE: src/content/getusermedia/canvas/index.html
================================================
getUserMedia to canvas
================================================
FILE: src/content/getusermedia/canvas/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// Put variables in global scope to make them available to the browser console.
const video = document.querySelector('video');
const canvas = window.canvas = document.querySelector('canvas');
canvas.width = 480;
canvas.height = 360;
const button = document.querySelector('button');
button.onclick = function() {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
};
const constraints = {
audio: false,
video: true
};
function handleSuccess(stream) {
window.stream = stream; // make stream available to browser console
video.srcObject = stream;
}
function handleError(error) {
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
================================================
FILE: src/content/getusermedia/exposure/index.html
================================================
Control Exposure
================================================
FILE: src/content/getusermedia/exposure/js/main.js
================================================
/*
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// Put variables in global scope to make them available to the browser console.
const constraints = window.constraints = {
audio: false,
video: true
};
function handleSuccess(stream) {
const video = document.querySelector('video');
const videoTracks = stream.getVideoTracks();
console.log('Got stream with constraints:', constraints);
console.log(`Using video device: ${videoTracks[0].label}`);
video.srcObject = stream;
// make track variable available to browser console.
[window.track] = stream.getVideoTracks();
loadProperties();
document.querySelector(`button[id=refreshControls]`).disabled = false;
}
function loadProperties(refreshValuesOnly) {
const track = window.track;
const capabilities = track.getCapabilities();
const settings = track.getSettings();
console.log('Capabilities: ', capabilities);
console.log('Settings: ', settings);
for (const property of ['exposureMode', 'exposureTime', 'exposureCompensation', 'brightness', 'whiteBalanceMode']) {
// Check whether camera supports exposure.
if (!(property in settings)) {
errorMsg(`Camera does not support ${property}.`);
continue;
}
let element;
if (Array.isArray(capabilities[property])) {
// Map it to a select element.
const select = document.querySelector(`select[name=${property}]`);
element = select;
if (capabilities[property] && !refreshValuesOnly) {
for (const mode of capabilities[property]) {
select.insertAdjacentHTML('afterbegin', ``);
}
}
} else {
// Map it to a slider element.
const input = document.querySelector(`input[name=${property}]`);
element = input;
input.min = capabilities[property].min;
input.max = capabilities[property].max;
input.step = capabilities[property].step;
}
element.value = settings[property];
element.disabled = false;
if (!refreshValuesOnly) {
element.oninput = async event => {
try {
const constraints = {advanced: [{[property]: element.value}]};
await track.applyConstraints(constraints);
console.log('Did successfully apply new constraints: ', constraints);
console.log('New camera settings: ', track.getSettings());
} catch (err) {
console.error('applyConstraints() failed: ', err);
}
};
}
}
}
function handleError(error) {
if (error.name === 'NotAllowedError') {
errorMsg('Permissions have not been granted to use your camera, ' +
'you need to allow the page access to your devices in ' +
'order for the demo to work.');
}
errorMsg(`getUserMedia error: ${error.name}`, error);
}
function errorMsg(msg, error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `
================================================
FILE: src/content/getusermedia/filter/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const snapshotButton = document.querySelector('button#snapshot');
const filterSelect = document.querySelector('select#filter');
// Put variables in global scope to make them available to the browser console.
const video = window.video = document.querySelector('video');
const canvas = window.canvas = document.querySelector('canvas');
canvas.width = 480;
canvas.height = 360;
snapshotButton.onclick = function() {
canvas.className = filterSelect.value;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
};
filterSelect.onchange = function() {
video.className = filterSelect.value;
};
const constraints = {
audio: false,
video: true
};
function handleSuccess(stream) {
window.stream = stream; // make stream available to browser console
video.srcObject = stream;
}
function handleError(error) {
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
================================================
FILE: src/content/getusermedia/getdisplaymedia/index.html
================================================
getDisplayMedia
================================================
FILE: src/content/getusermedia/getdisplaymedia/js/main.js
================================================
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const preferredDisplaySurface = document.getElementById('displaySurface');
const startButton = document.getElementById('startButton');
if (adapter.browserDetails.browser === 'chrome' &&
adapter.browserDetails.version >= 107) {
// See https://developer.chrome.com/docs/web-platform/screen-sharing-controls/
document.getElementById('options').style.display = 'block';
} else if (adapter.browserDetails.browser === 'firefox') {
// Polyfill in Firefox.
// See https://blog.mozilla.org/webrtc/getdisplaymedia-now-available-in-adapter-js/
adapter.browserShim.shimGetDisplayMedia(window, 'screen');
}
function handleSuccess(stream) {
startButton.disabled = true;
preferredDisplaySurface.disabled = true;
const video = document.querySelector('video');
video.srcObject = stream;
// demonstrates how to detect that the user has stopped
// sharing the screen via the browser UI.
stream.getVideoTracks()[0].addEventListener('ended', () => {
errorMsg('The user has ended sharing the screen');
startButton.disabled = false;
preferredDisplaySurface.disabled = false;
});
}
function handleError(error) {
errorMsg(`getDisplayMedia error: ${error.name}`, error);
}
function errorMsg(msg, error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `
================================================
FILE: src/content/getusermedia/gum/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// Put variables in global scope to make them available to the browser console.
const constraints = window.constraints = {
audio: false,
video: true
};
function handleSuccess(stream) {
const video = document.querySelector('video');
const videoTracks = stream.getVideoTracks();
console.log('Got stream with constraints:', constraints);
console.log(`Using video device: ${videoTracks[0].label}`);
window.stream = stream; // make variable available to browser console
video.srcObject = stream;
}
function handleError(error) {
if (error.name === 'OverconstrainedError') {
errorMsg(`OverconstrainedError: The constraints could not be satisfied by the available devices. Constraints: ${JSON.stringify(constraints)}`);
} else if (error.name === 'NotAllowedError') {
errorMsg('NotAllowedError: Permissions have not been granted to use your camera and ' +
'microphone, you need to allow the page access to your devices in ' +
'order for the demo to work.');
}
errorMsg(`getUserMedia error: ${error.name}`, error);
}
function errorMsg(msg, error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `
${msg}
`;
if (typeof error !== 'undefined') {
console.error(error);
}
}
async function init(e) {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
e.target.disabled = true;
} catch (e) {
handleError(e);
}
}
document.querySelector('#showVideo').addEventListener('click', e => init(e));
================================================
FILE: src/content/getusermedia/gum/js/test.js
================================================
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
const webdriver = require('selenium-webdriver');
const seleniumHelpers = require('../../../../../test/webdriver');
let driver;
const path = '/src/content/getusermedia/gum/index.html';
const url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;
describe('getUserMedia', () => {
beforeAll(async () => {
driver = await seleniumHelpers.buildDriver();
});
afterAll(() => {
return driver.quit();
});
beforeEach(() => {
return driver.get(url);
});
it('opens a camera', async () => {
await driver.findElement(webdriver.By.css('button')).click();
await driver.wait(() => driver.executeScript(() =>
document.querySelector('video').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA)
);
const width = await driver.findElement(webdriver.By.css('video')).getAttribute('videoWidth');
expect(width >>> 0).toBeGreaterThan(320);
});
});
================================================
FILE: src/content/getusermedia/pan-tilt-zoom/index.html
================================================
Control camera pan, tilt, and zoom
================================================
FILE: src/content/getusermedia/pan-tilt-zoom/js/main.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// Put variables in global scope to make them available to the browser console.
const constraints = window.constraints = {
video: {
pan: true, tilt: true, zoom: true
}
};
function handleSuccess(stream) {
const video = document.querySelector('video');
const videoTracks = stream.getVideoTracks();
console.log('Got stream with constraints:', constraints);
console.log(`Using video device: ${videoTracks[0].label}`);
video.srcObject = stream;
// make track variable available to browser console.
const [track] = [window.track] = stream.getVideoTracks();
const capabilities = track.getCapabilities();
const settings = track.getSettings();
for (const ptz of ['pan', 'tilt', 'zoom']) {
// Check whether camera supports pan/tilt/zoom.
if (!(ptz in settings)) {
errorMsg(`Camera does not support ${ptz}.`);
continue;
}
// Map it to a slider element.
const input = document.querySelector(`input[name=${ptz}]`);
input.min = capabilities[ptz].min;
input.max = capabilities[ptz].max;
input.step = capabilities[ptz].step;
input.value = settings[ptz];
input.disabled = false;
input.oninput = async event => {
try {
const constraints = {advanced: [{[ptz]: input.value}]};
await track.applyConstraints(constraints);
} catch (err) {
console.error('applyConstraints() failed: ', err);
}
};
}
}
function handleError(error) {
if (error.name === 'NotAllowedError') {
errorMsg('Permissions have not been granted to use your camera, ' +
'you need to allow the page access to your devices in ' +
'order for the demo to work.');
}
errorMsg(`getUserMedia error: ${error.name}`, error);
}
function errorMsg(msg, error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `
${msg}
`;
if (typeof error !== 'undefined') {
console.error(error);
}
}
async function init(e) {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
e.target.disabled = true;
} catch (e) {
handleError(e);
}
}
document.querySelector('#showVideo').addEventListener('click', e => init(e));
================================================
FILE: src/content/getusermedia/record/css/main.css
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 3px 10px 0;
padding-left: 2px;
padding-right: 2px;
width: 120px;
}
/* copied from main button style */
.file-upload {
background-color: #d84a38;
border: none;
border-radius: 2px;
color: white;
font-family: 'Roboto', sans-serif;
font-size: 0.8em;
margin: 0 0 1em 0;
padding: 0.5em 0.7em 0.6em 0.7em;
}
.file-upload:hover {
background-color: #cf402f;
}
button:last-of-type {
margin: 0;
}
p.borderBelow {
margin: 0 0 20px 0;
padding: 0 0 20px 0;
}
video {
vertical-align: top;
--width: 25vw;
width: var(--width);
height: calc(var(--width) * 0.5625);
}
video:last-of-type {
margin: 0 0 20px 0;
}
video#gumVideo {
margin: 0 20px 20px 0;
}
input[type="file"] {
display: none;
}
================================================
FILE: src/content/getusermedia/record/index.html
================================================
MediaStream Recording
================================================
FILE: src/content/getusermedia/volume/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* global AudioContext, SoundMeter */
'use strict';
const startButton = document.getElementById('startButton');
const stopButton = document.getElementById('stopButton');
startButton.onclick = start;
stopButton.onclick = stop;
const instantMeter = document.querySelector('#instant meter');
const slowMeter = document.querySelector('#slow meter');
const clipMeter = document.querySelector('#clip meter');
const instantValueDisplay = document.querySelector('#instant .value');
const slowValueDisplay = document.querySelector('#slow .value');
const clipValueDisplay = document.querySelector('#clip .value');
// Put variables in global scope to make them available to the browser console.
const constraints = window.constraints = {
audio: true,
video: false
};
let meterRefresh = null;
function handleSuccess(stream) {
// Put variables in global scope to make them available to the
// browser console.
window.stream = stream;
const soundMeter = window.soundMeter = new SoundMeter(window.audioContext);
soundMeter.connectToSource(stream, function(e) {
if (e) {
alert(e);
return;
}
meterRefresh = setInterval(() => {
instantMeter.value = instantValueDisplay.innerText =
soundMeter.instant.toFixed(2);
slowMeter.value = slowValueDisplay.innerText =
soundMeter.slow.toFixed(2);
clipMeter.value = clipValueDisplay.innerText =
soundMeter.clip;
}, 200);
});
}
function handleError(error) {
console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
}
function start() {
console.log('Requesting local stream');
startButton.disabled = true;
stopButton.disabled = false;
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.audioContext = new AudioContext();
} catch (e) {
alert('Web Audio API not supported.');
}
navigator.mediaDevices
.getUserMedia(constraints)
.then(handleSuccess)
.catch(handleError);
}
function stop() {
console.log('Stopping local stream');
startButton.disabled = false;
stopButton.disabled = true;
window.stream.getTracks().forEach(track => track.stop());
window.soundMeter.stop();
window.audioContext.close();
clearInterval(meterRefresh);
instantMeter.value = instantValueDisplay.innerText = '';
slowMeter.value = slowValueDisplay.innerText = '';
clipMeter.value = clipValueDisplay.innerText = '';
}
================================================
FILE: src/content/getusermedia/volume/js/soundmeter.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// Meter class that generates a number correlated to audio volume.
// The meter class itself displays nothing, but it makes the
// instantaneous and time-decaying volumes available for inspection.
// It also reports on the fraction of samples that were at or near
// the top of the measurement range.
function SoundMeter(context) {
this.context = context;
this.instant = 0.0;
this.slow = 0.0;
this.clip = 0.0;
this.node = null;
}
SoundMeter.prototype.connectToSource = async function(stream, callback) {
console.log('SoundMeter connecting');
try {
await this.context.audioWorklet.addModule('js/volume-meter-processor.js');
this.mic = this.context.createMediaStreamSource(stream);
this.node = new AudioWorkletNode(this.context, 'volume-meter-processor');
this.node.port.onmessage = (event) => {
const {instant, clip} = event.data;
this.instant = instant;
this.clip = clip;
this.slow = 0.95 * this.slow + 0.05 * this.instant;
};
this.mic.connect(this.node);
if (typeof callback !== 'undefined') {
callback(null);
}
} catch (e) {
console.error(e);
if (typeof callback !== 'undefined') {
callback(e);
}
}
};
SoundMeter.prototype.stop = function() {
console.log('SoundMeter stopping');
this.mic.disconnect();
this.node.disconnect();
};
================================================
FILE: src/content/getusermedia/volume/js/volume-meter-processor.js
================================================
/*
* Copyright (c) 2025 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
// This class is used to compute the volume of the input audio stream.
class VolumeMeterProcessor extends AudioWorkletProcessor {
constructor() {
super();
this._lastUpdate = Date.now();
}
process(inputs) {
// This example only supports mono channel.
const input = inputs[0][0];
if (!input) {
return true;
}
let sum = 0.0;
let clipcount = 0;
for (let i = 0; i < input.length; ++i) {
sum += input[i] * input[i];
if (Math.abs(input[i]) > 0.99) {
clipcount += 1;
}
}
const instant = Math.sqrt(sum / input.length);
this.port.postMessage({
instant: instant,
clip: clipcount / input.length,
});
return true;
}
}
registerProcessor('volume-meter-processor', VolumeMeterProcessor);
================================================
FILE: src/content/insertable-streams/audio-processing/index.html
================================================
Insertable Streams - Audio
This sample shows how to perform processing on an audio stream using the experimental
insertable streams API.
It applies a low-pass filter in realtime to audio recorded from a microphone and plays it
back.
Warning: if you're not using headphones, pressing Start will cause feedback.
View the console to see logging. The audio, processor,
generator, transformer, stream and
processedStream variables are in global scope, so you can inspect them from the
console. You may also adjust the level of filtering by assigning to cutoff.
Note: This sample is using an experimental API that has not yet been standardized. As
of 2021-09-29, this API is available in Chrome M94.
================================================
FILE: src/content/insertable-streams/audio-processing/js/main.js
================================================
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator, AudioData */
if (typeof MediaStreamTrackProcessor === 'undefined' ||
typeof MediaStreamTrackGenerator === 'undefined') {
alert(
'Your browser does not support the experimental MediaStreamTrack API ' +
'for Insertable Streams of Media. See the note at the bottom of the ' +
'page.');
}
try {
new MediaStreamTrackGenerator('audio');
console.log('Audio insertable streams supported');
} catch (e) {
alert(
'Your browser does not support insertable audio streams. See the note ' +
'at the bottom of the page.');
}
if (typeof AudioData === 'undefined') {
alert(
'Your browser does not support WebCodecs. See the note at the bottom ' +
'of the page.');
}
// Put variables in global scope to make them available to the browser console.
// Audio element
let audio;
// Buttons
let startButton;
let stopButton;
// Transformation chain elements
let processor;
let generator;
// Stream from getUserMedia
let stream;
// Output from the transform
let processedStream;
// Worker for processing
let worker;
// Initialize on page load.
async function init() {
audio = document.getElementById('audioOutput');
startButton = document.getElementById('startButton');
stopButton = document.getElementById('stopButton');
startButton.onclick = start;
stopButton.onclick = stop;
}
const constraints = window.constraints = {
audio: true,
video: false
};
async function start() {
startButton.disabled = true;
try {
stream = await navigator.mediaDevices.getUserMedia(constraints);
const audioTracks = stream.getAudioTracks();
console.log('Using audio device: ' + audioTracks[0].label);
stream.oninactive = () => {
console.log('Stream ended');
};
processor = new MediaStreamTrackProcessor(audioTracks[0]);
generator = new MediaStreamTrackGenerator('audio');
const source = processor.readable;
const sink = generator.writable;
worker = new Worker('js/worker.js');
worker.postMessage({source: source, sink: sink}, [source, sink]);
processedStream = new MediaStream();
processedStream.addTrack(generator);
audio.srcObject = processedStream;
stopButton.disabled = false;
await audio.play();
} catch (error) {
const errorMessage =
'navigator.MediaDevices.getUserMedia error: ' + error.message + ' ' +
error.name;
document.getElementById('errorMsg').innerText = errorMessage;
console.log(errorMessage);
}
}
async function stop() {
stopButton.disabled = true;
audio.pause();
audio.srcObject = null;
stream.getTracks().forEach(track => {
track.stop();
});
worker.postMessage({command: 'abort'});
startButton.disabled = false;
}
window.onload = init;
================================================
FILE: src/content/insertable-streams/audio-processing/js/worker.js
================================================
/*
* Copyright (c) 2023 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
// Adjust this value to increase/decrease the amount of filtering.
// eslint-disable-next-line prefer-const
let cutoff = 100;
// Returns a low-pass transform function for use with TransformStream.
function lowPassFilter() {
const format = 'f32-planar';
let lastValuePerChannel = undefined;
return (data, controller) => {
const rc = 1.0 / (cutoff * 2 * Math.PI);
const dt = 1.0 / data.sampleRate;
const alpha = dt / (rc + dt);
const nChannels = data.numberOfChannels;
if (!lastValuePerChannel) {
console.log(`Audio stream has ${nChannels} channels.`);
lastValuePerChannel = Array(nChannels).fill(0);
}
const buffer = new Float32Array(data.numberOfFrames * nChannels);
for (let c = 0; c < nChannels; c++) {
const offset = data.numberOfFrames * c;
const samples = buffer.subarray(offset, offset + data.numberOfFrames);
data.copyTo(samples, {planeIndex: c, format});
let lastValue = lastValuePerChannel[c];
// Apply low-pass filter to samples.
for (let i = 0; i < samples.length; ++i) {
lastValue = lastValue + alpha * (samples[i] - lastValue);
samples[i] = lastValue;
}
lastValuePerChannel[c] = lastValue;
}
controller.enqueue(new AudioData({
format,
sampleRate: data.sampleRate,
numberOfFrames: data.numberOfFrames,
numberOfChannels: nChannels,
timestamp: data.timestamp,
data: buffer
}));
};
}
let abortController;
onmessage = async (event) => {
if (event.data.command == 'abort') {
abortController.abort();
abortController = null;
} else {
const source = event.data.source;
const sink = event.data.sink;
const transformer = new TransformStream({transform: lowPassFilter()});
abortController = new AbortController();
const signal = abortController.signal;
const promise = source.pipeThrough(transformer, {signal}).pipeTo(sink);
promise.catch((e) => {
if (signal.aborted) {
console.log('Shutting down streams after abort.');
} else {
console.error('Error from stream transform:', e);
}
source.cancel(e);
sink.abort(e);
});
}
};
================================================
FILE: src/content/insertable-streams/endtoend-encryption/css/main.css
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 20px 10px 0 0;
width: 100px;
}
div#buttons {
margin: 0 0 20px 0;
}
div#status {
height: 2em;
margin: 1em 0 0 0;
}
input#audio {
margin: 0 0.5em 0 0;
position: relative;
top: -1px;
}
video {
--width: 45%;
width: var(--width);
height: calc(var(--width) * 0.75);
}
================================================
FILE: src/content/insertable-streams/endtoend-encryption/index.html
================================================
Peer connection end to end encryption
================================================
FILE: src/content/insertable-streams/endtoend-encryption/js/main.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global RTCRtpScriptTransform */
/* global VideoPipe */
const video1 = document.querySelector('video#video1');
const video2 = document.querySelector('video#video2');
const videoMonitor = document.querySelector('#video-monitor');
const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
const cryptoKey = document.querySelector('#crypto-key');
const cryptoOffsetBox = document.querySelector('#crypto-offset');
const banner = document.querySelector('#banner');
const muteMiddleBox = document.querySelector('#mute-middlebox');
startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;
cryptoKey.addEventListener('change', setCryptoKey);
cryptoOffsetBox.addEventListener('change', setCryptoKey);
muteMiddleBox.addEventListener('change', toggleMute);
let startToMiddle;
let startToEnd;
let localStream;
// eslint-disable-next-line no-unused-vars
let remoteStream;
// Preferring a certain codec is an expert option without GUI.
// Use opus by default.
// eslint-disable-next-line prefer-const
let preferredAudioCodecMimeType = 'audio/opus';
// Use VP8 by default to limit depacketization issues.
// eslint-disable-next-line prefer-const
let preferredVideoCodecMimeType = 'video/VP8';
const supportsSetCodecPreferences = window.RTCRtpTransceiver &&
'setCodecPreferences' in window.RTCRtpTransceiver.prototype;
let hasEnoughAPIs = !!window.RTCRtpScriptTransform;
if (!hasEnoughAPIs) {
const supportsInsertableStreams =
!!RTCRtpSender.prototype.createEncodedStreams;
let supportsTransferableStreams = false;
try {
const stream = new ReadableStream();
window.postMessage(stream, '*', [stream]);
supportsTransferableStreams = true;
} catch (e) {
console.error('Transferable streams are not supported.');
}
hasEnoughAPIs = supportsInsertableStreams && supportsTransferableStreams;
}
if (!hasEnoughAPIs) {
banner.innerText = 'Your browser does not support WebRTC Encoded Transforms. ' +
'This sample will not work.';
if (adapter.browserDetails.browser === 'chrome') {
banner.innerText += ' Try with Enable experimental Web Platform features enabled from chrome://flags.';
}
startButton.disabled = true;
cryptoKey.disabled = true;
cryptoOffsetBox.disabled = true;
}
function gotStream(stream) {
console.log('Received local stream');
video1.srcObject = stream;
localStream = stream;
callButton.disabled = false;
}
function gotRemoteStream(stream) {
console.log('Received remote stream');
remoteStream = stream;
video2.srcObject = stream;
}
function start() {
console.log('Requesting local stream');
startButton.disabled = true;
const options = {audio: true, video: true};
navigator.mediaDevices
.getUserMedia(options)
.then(gotStream)
.catch(function(e) {
alert('getUserMedia() failed');
console.log('getUserMedia() error: ', e);
});
}
// We use a Worker to do the encryption and decryption.
// See
// https://developer.mozilla.org/en-US/docs/Web/API/Worker
// for basic concepts.
const worker = new Worker('./js/worker.js', {name: 'E2EE worker'});
function setupSenderTransform(sender) {
if (window.RTCRtpScriptTransform) {
sender.transform = new RTCRtpScriptTransform(worker, {operation: 'encode'});
return;
}
const senderStreams = sender.createEncodedStreams();
// Instead of creating the transform stream here, we do a postMessage to the worker. The first
// argument is an object defined by us, the second is a list of variables that will be transferred to
// the worker. See
// https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
// If you want to do the operations on the main thread instead, comment out the code below.
/*
const transformStream = new TransformStream({
transform: encodeFunction,
});
senderStreams.readable
.pipeThrough(transformStream)
.pipeTo(senderStreams.writable);
*/
const {readable, writable} = senderStreams;
worker.postMessage({
operation: 'encode',
readable,
writable,
}, [readable, writable]);
}
function setupReceiverTransform(receiver) {
if (window.RTCRtpScriptTransform) {
receiver.transform = new RTCRtpScriptTransform(worker, {operation: 'decode'});
return;
}
const receiverStreams = receiver.createEncodedStreams();
const {readable, writable} = receiverStreams;
worker.postMessage({
operation: 'decode',
readable,
writable,
}, [readable, writable]);
}
function maybeSetCodecPreferences(trackEvent) {
if (!supportsSetCodecPreferences) return;
if (trackEvent.track.kind === 'audio' && preferredAudioCodecMimeType ) {
const {codecs} = RTCRtpReceiver.getCapabilities('audio');
const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredAudioCodecMimeType);
const selectedCodec = codecs[selectedCodecIndex];
codecs.splice(selectedCodecIndex, 1);
codecs.unshift(selectedCodec);
trackEvent.transceiver.setCodecPreferences(codecs);
} else if (trackEvent.track.kind === 'video' && preferredVideoCodecMimeType) {
const {codecs} = RTCRtpReceiver.getCapabilities('video');
const selectedCodecIndex = codecs.findIndex(c => c.mimeType === preferredVideoCodecMimeType);
const selectedCodec = codecs[selectedCodecIndex];
codecs.splice(selectedCodecIndex, 1);
codecs.unshift(selectedCodec);
trackEvent.transceiver.setCodecPreferences(codecs);
}
}
function call() {
callButton.disabled = true;
hangupButton.disabled = false;
console.log('Starting call');
// The real use case is where the middle box relays the
// packets and listens in, but since we don't have
// access to raw packets, we just send the same video
// to both places.
startToMiddle = new VideoPipe(localStream, true, false, e => {
// Do not setup the receiver transform.
maybeSetCodecPreferences(e);
videoMonitor.srcObject = e.streams[0];
});
startToMiddle.pc1.getSenders().forEach(setupSenderTransform);
startToMiddle.negotiate();
startToEnd = new VideoPipe(localStream, true, true, e => {
setupReceiverTransform(e.receiver);
maybeSetCodecPreferences(e);
gotRemoteStream(e.streams[0]);
});
startToEnd.pc1.getSenders().forEach(setupSenderTransform);
startToEnd.negotiate();
console.log('Video pipes created');
}
function hangup() {
console.log('Ending call');
startToMiddle.close();
startToEnd.close();
hangupButton.disabled = true;
callButton.disabled = false;
}
function setCryptoKey(event) {
console.log('Setting crypto key to ' + cryptoKey.value);
const currentCryptoKey = cryptoKey.value;
const useCryptoOffset = !cryptoOffsetBox.checked;
if (currentCryptoKey) {
banner.innerText = 'Encryption is ON';
} else {
banner.innerText = 'Encryption is OFF';
}
worker.postMessage({
operation: 'setCryptoKey',
currentCryptoKey,
useCryptoOffset,
});
}
function toggleMute(event) {
video2.muted = muteMiddleBox.checked;
videoMonitor.muted = !muteMiddleBox.checked;
}
================================================
FILE: src/content/insertable-streams/endtoend-encryption/js/test.js
================================================
/*
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
const webdriver = require('selenium-webdriver');
const seleniumHelpers = require('../../../../../test/webdriver');
let driver;
const path = '/src/content/insertable-streams/endtoend-encryption/index.html';
const url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;
describe('insertable streams e2ee', () => {
beforeAll(async () => {
driver = await seleniumHelpers.buildDriver();
});
afterAll(() => {
return driver.quit();
});
beforeEach(() => {
return driver.get(url);
});
it('establishes a connection and hangs up', async () => {
await driver.findElement(webdriver.By.id('startButton')).click();
await driver.wait(() => driver.executeScript(() => {
return localStream !== null; // eslint-disable-line no-undef
}));
await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());
await driver.findElement(webdriver.By.id('callButton')).click();
await Promise.all([
await driver.wait(() => driver.executeScript(() => {
return startToEnd && startToEnd.pc1 && startToEnd.pc1.connectionState === 'connected'; // eslint-disable-line no-undef
})),
await driver.wait(() => driver.executeScript(() => {
return startToEnd && startToEnd.pc2 && startToEnd.pc2.connectionState === 'connected'; // eslint-disable-line no-undef
})),
]);
await driver.wait(() => driver.executeScript(() => {
return document.getElementById('video2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;
}));
await driver.findElement(webdriver.By.id('hangupButton')).click();
await driver.wait(() => driver.executeScript(() => {
return startToEnd && startToEnd.pc1 && startToEnd.pc1.connectionState === 'closed'; // eslint-disable-line no-undef
}));
});
it('establisheѕ a encrypted connection with a key set prior to connecting', async () => {
await driver.findElement(webdriver.By.id('startButton')).click();
await driver.wait(() => driver.executeScript(() => {
return localStream !== null; // eslint-disable-line no-undef
}));
await driver.findElement(webdriver.By.id('crypto-key'))
.sendKeys('secret\n');
await driver.wait(() => driver.executeScript(() => {
return document.getElementById('banner').innerText === 'Encryption is ON';
}));
await driver.wait(() => driver.findElement(webdriver.By.id('callButton')).isEnabled());
await driver.findElement(webdriver.By.id('callButton')).click();
await driver.wait(() => driver.executeScript(() => {
return document.getElementById('video2').readyState === HTMLMediaElement.HAVE_ENOUGH_DATA;
}));
});
});
================================================
FILE: src/content/insertable-streams/endtoend-encryption/js/videopipe.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
//
// A "videopipe" abstraction on top of WebRTC.
//
// The usage of this abstraction:
// var pipe = new VideoPipe(mediastream, handlerFunction);
// handlerFunction = function(MediaStreamTrackEvent) {
// do_something
// }
// pipe.close();
//
// The VideoPipe will set up 2 PeerConnections, connect them to each
// other, and call HandlerFunction when the stream's track is available
// in the second PeerConnection.
//
'use strict';
function VideoPipe(stream, forceSend, forceReceive, handler) {
this.pc1 = new RTCPeerConnection();
this.pc2 = new RTCPeerConnection();
this.pc2.ontrack = handler;
stream.getTracks().forEach((track) => this.pc1.addTrack(track, stream));
}
VideoPipe.prototype.negotiate = async function() {
this.pc1.onicecandidate = e => this.pc2.addIceCandidate(e.candidate);
this.pc2.onicecandidate = e => this.pc1.addIceCandidate(e.candidate);
await this.pc1.setLocalDescription();
await this.pc2.setRemoteDescription(this.pc1.localDescription);
await this.pc2.setLocalDescription();
await this.pc1.setRemoteDescription(this.pc2.localDescription);
};
VideoPipe.prototype.close = function() {
this.pc1.close();
this.pc2.close();
};
================================================
FILE: src/content/insertable-streams/endtoend-encryption/js/worker.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/*
* This is a worker doing the encode/decode transformations to add end-to-end
* encryption to a WebRTC PeerConnection using the Insertable Streams API.
*/
'use strict';
let currentCryptoKey;
let useCryptoOffset = true;
let currentKeyIdentifier = 0;
// If using crypto offset (controlled by a checkbox):
// Do not encrypt the first couple of bytes of the payload. This allows
// a middle to determine video keyframes or the opus mode being used.
// For VP8 this is the content described in
// https://tools.ietf.org/html/rfc6386#section-9.1
// which is 10 bytes for key frames and 3 bytes for delta frames.
// For opus (where encodedFrame.type is not set) this is the TOC byte from
// https://tools.ietf.org/html/rfc6716#section-3.1
// TODO: make this work for other codecs.
//
// It makes the (encrypted) video and audio much more fun to watch and listen to
// as the decoder does not immediately throw a fatal error.
const frameTypeToCryptoOffset = {
key: 10,
delta: 3,
undefined: 1,
};
function dump(encodedFrame, direction, max = 16) {
const data = new Uint8Array(encodedFrame.data);
let bytes = '';
for (let j = 0; j < data.length && j < max; j++) {
bytes += (data[j] < 16 ? '0' : '') + data[j].toString(16) + ' ';
}
const metadata = encodedFrame.getMetadata();
console.log(performance.now().toFixed(2), direction, bytes.trim(),
'len=' + encodedFrame.data.byteLength,
'type=' + (encodedFrame.type || 'audio'),
'ts=' + (metadata.rtpTimestamp || encodedFrame.timestamp),
'ssrc=' + metadata.synchronizationSource,
'pt=' + (metadata.payloadType || '(unknown)'),
'mimeType=' + (metadata.mimeType || '(unknown)'),
);
}
let scount = 0;
function encodeFunction(encodedFrame, controller) {
if (scount++ < 30) { // dump the first 30 packets.
dump(encodedFrame, 'send');
}
if (currentCryptoKey) {
const view = new DataView(encodedFrame.data);
// Any length that is needed can be used for the new buffer.
const newData = new ArrayBuffer(encodedFrame.data.byteLength + 5);
const newView = new DataView(newData);
const cryptoOffset = useCryptoOffset? frameTypeToCryptoOffset[encodedFrame.type] : 0;
for (let i = 0; i < cryptoOffset && i < encodedFrame.data.byteLength; ++i) {
newView.setInt8(i, view.getInt8(i));
}
// This is a bitwise xor of the key with the payload. This is not strong encryption, just a demo.
for (let i = cryptoOffset; i < encodedFrame.data.byteLength; ++i) {
const keyByte = currentCryptoKey.charCodeAt(i % currentCryptoKey.length);
newView.setInt8(i, view.getInt8(i) ^ keyByte);
}
// Append keyIdentifier.
newView.setUint8(encodedFrame.data.byteLength, currentKeyIdentifier % 0xff);
// Append checksum
newView.setUint32(encodedFrame.data.byteLength + 1, 0xDEADBEEF);
encodedFrame.data = newData;
}
controller.enqueue(encodedFrame);
}
let rcount = 0;
function decodeFunction(encodedFrame, controller) {
if (rcount++ < 30) { // dump the first 30 packets
dump(encodedFrame, 'recv');
}
const view = new DataView(encodedFrame.data);
const checksum = encodedFrame.data.byteLength > 4 ? view.getUint32(encodedFrame.data.byteLength - 4) : false;
if (currentCryptoKey) {
if (checksum !== 0xDEADBEEF) {
console.log('Corrupted frame received, checksum ' +
checksum.toString(16));
return; // This can happen when the key is set and there is an unencrypted frame in-flight.
}
const keyIdentifier = view.getUint8(encodedFrame.data.byteLength - 5);
if (keyIdentifier !== currentKeyIdentifier) {
console.log(`Key identifier mismatch, got ${keyIdentifier} expected ${currentKeyIdentifier}.`);
return;
}
const newData = new ArrayBuffer(encodedFrame.data.byteLength - 5);
const newView = new DataView(newData);
const cryptoOffset = useCryptoOffset? frameTypeToCryptoOffset[encodedFrame.type] : 0;
for (let i = 0; i < cryptoOffset; ++i) {
newView.setInt8(i, view.getInt8(i));
}
for (let i = cryptoOffset; i < encodedFrame.data.byteLength - 5; ++i) {
const keyByte = currentCryptoKey.charCodeAt(i % currentCryptoKey.length);
newView.setInt8(i, view.getInt8(i) ^ keyByte);
}
encodedFrame.data = newData;
} else if (checksum === 0xDEADBEEF) {
return; // encrypted in-flight frame but we already forgot about the key.
}
controller.enqueue(encodedFrame);
}
function handleTransform(operation, readable, writable) {
if (operation === 'encode') {
const transformStream = new TransformStream({
transform: encodeFunction,
});
readable
.pipeThrough(transformStream)
.pipeTo(writable);
} else if (operation === 'decode') {
const transformStream = new TransformStream({
transform: decodeFunction,
});
readable
.pipeThrough(transformStream)
.pipeTo(writable);
}
}
// Handler for messages, including transferable streams.
onmessage = (event) => {
if (event.data.operation === 'encode' || event.data.operation === 'decode') {
return handleTransform(event.data.operation, event.data.readable, event.data.writable);
}
if (event.data.operation === 'setCryptoKey') {
if (event.data.currentCryptoKey !== currentCryptoKey) {
currentKeyIdentifier++;
}
currentCryptoKey = event.data.currentCryptoKey;
useCryptoOffset = event.data.useCryptoOffset;
}
};
// Handler for RTCRtpScriptTransforms.
if (self.RTCTransformEvent) {
self.onrtctransform = (event) => {
const transformer = event.transformer;
handleTransform(transformer.options.operation, transformer.readable, transformer.writable);
};
}
================================================
FILE: src/content/insertable-streams/video/index.html
================================================
Page move
================================================
FILE: src/content/insertable-streams/video-analyzer/css/main.css
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 20px 0 0;
width: 83px;
}
button#hangupButton {
margin: 0;
}
video {
--width: 45%;
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 0 0 20px 0;
vertical-align: top;
}
video#localVideo {
margin: 0 20px 20px 0;
}
div.box {
margin: 1em;
}
@media screen and (max-width: 400px) {
button {
width: 83px;
margin: 0 11px 10px 0;
}
video {
height: 90px;
margin: 0 0 10px 0;
width: calc(50% - 7px);
}
video#localVideo {
margin: 0 10px 20px 0;
}
}
================================================
FILE: src/content/insertable-streams/video-analyzer/index.html
================================================
Insertable Streams Video Analyzer
This sample shows how to perform cropping on a video stream using the experimental
mediacapture-transform API
in a Worker.
Note: This sample is using an experimental API that has not yet been standardized. As
of 2022-11-21, this API is available in the latest version of Chrome based browsers.
================================================
FILE: src/content/insertable-streams/video-crop/js/main.js
================================================
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */
if (typeof MediaStreamTrackProcessor === 'undefined' ||
typeof MediaStreamTrackGenerator === 'undefined') {
alert(
'Your browser does not support the experimental MediaStreamTrack API ' +
'for Insertable Streams of Media. See the note at the bottom of the ' +
'page.');
}
const startButton = document.getElementById('startButton');
const localVideo = document.getElementById('localVideo');
const croppedVideo = document.getElementById('croppedVideo');
const worker = new Worker('./js/worker.js', {name: 'Crop worker'});
startButton.addEventListener('click', async () => {
const stream = await navigator.mediaDevices.getUserMedia({video: {width: 1280, height: 720}});
localVideo.srcObject = stream;
const [track] = stream.getTracks();
const processor = new MediaStreamTrackProcessor({track});
const {readable} = processor;
const generator = new MediaStreamTrackGenerator({kind: 'video'});
const {writable} = generator;
croppedVideo.srcObject = new MediaStream([generator]);
worker.postMessage({
operation: 'crop',
readable,
writable,
}, [readable, writable]);
});
================================================
FILE: src/content/insertable-streams/video-crop/js/worker.js
================================================
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
function transform(frame, controller) {
// Cropping from an existing video frame is supported by the API in Chrome 94+.
const newFrame = new VideoFrame(frame, {
visibleRect: {
x: 320,
width: 640,
y: 180,
height: 360,
}
});
controller.enqueue(newFrame);
frame.close();
}
onmessage = async (event) => {
const {operation} = event.data;
if (operation === 'crop') {
const {readable, writable} = event.data;
readable
.pipeThrough(new TransformStream({transform}))
.pipeTo(writable);
} else {
console.error('Unknown operation', operation);
}
};
================================================
FILE: src/content/insertable-streams/video-processing/css/main.css
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
.video {
--width: 45%;
width: var(--width);
height: calc(var(--width) * 0.75);
vertical-align: top;
}
.sourceVideo {
margin: 0 20px 20px 0;
}
.sinkVideo {
margin: 0 0 20px 0;
}
div.box {
margin: 1em;
}
@media screen and (max-width: 400px) {
.video {
height: 90px;
width: calc(50% - 7px);
}
.sourceVideo {
margin: 0 10px 20px 0;
}
.sinkVideo {
margin: 0 0 10px 0;
}
}
================================================
FILE: src/content/insertable-streams/video-processing/index.html
================================================
Insertable Streams - Video
This sample shows how to perform processing on a video stream using the experimental
insertable streams API.
There are options for the source of the input stream, the destination of the output stream,
and the API used to transform the stream. There is also the option to duplicate the source
stream to a video element on the page, which may affect the source FPS.
Source:Add to page:
Transform:
Destination:
View the console to see logging.
Note: This sample is using an experimental API that has not yet been standardized.
This API is available in Chrome 94 or later.
================================================
FILE: src/content/insertable-streams/video-processing/js/camera-source.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global VideoMirrorHelper */ // defined in video-mirror-helper.js
/**
* Opens the device's camera with getUserMedia.
* @implements {MediaStreamSource} in pipeline.js
*/
class CameraSource { // eslint-disable-line no-unused-vars
constructor() {
/**
* @private @const {!VideoMirrorHelper} manages displaying the video stream
* in the page
*/
this.videoMirrorHelper_ = new VideoMirrorHelper();
/** @private {?MediaStream} camera stream, initialized in getMediaStream */
this.stream_ = null;
/** @private {string} */
this.debugPath_ = '';
}
/** @override */
setDebugPath(path) {
this.debugPath_ = path;
this.videoMirrorHelper_.setDebugPath(`${path}.videoMirrorHelper_`);
}
/** @override */
setVisibility(visible) {
this.videoMirrorHelper_.setVisibility(visible);
}
/** @override */
async getMediaStream() {
if (this.stream_) return this.stream_;
console.log('[CameraSource] Requesting camera.');
this.stream_ =
await navigator.mediaDevices.getUserMedia({audio: false, video: true});
console.log(
'[CameraSource] Received camera stream.',
`${this.debugPath_}.stream_ =`, this.stream_);
this.videoMirrorHelper_.setStream(this.stream_);
return this.stream_;
}
/** @override */
destroy() {
console.log('[CameraSource] Stopping camera');
this.videoMirrorHelper_.destroy();
if (this.stream_) {
this.stream_.getTracks().forEach(t => t.stop());
}
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/canvas-source.js
================================================
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const TEXT_SOURCE =
'https://raw.githubusercontent.com/w3c/mediacapture-insertable-streams/main/explainer.md';
const CANVAS_ASPECT_RATIO = 16 / 9;
/**
* @param {number} x
* @return {number} x rounded to the nearest even integer
*/
function roundToEven(x) {
return 2 * Math.round(x / 2);
}
/**
* Draws text on a Canvas.
* @implements {MediaStreamSource} in pipeline.js
*/
class CanvasSource { // eslint-disable-line no-unused-vars
constructor() {
/** @private {boolean} */
this.visibility_ = false;
/**
* @private {?HTMLCanvasElement} canvas element providing the MediaStream.
*/
this.canvas_ = null;
/**
* @private {?CanvasRenderingContext2D} the 2D context used to draw the
* animation.
*/
this.ctx_ = null;
/**
* @private {?MediaStream} the MediaStream from captureStream.
*/
this.stream_ = null;
/**
* @private {?CanvasCaptureMediaStreamTrack} the capture track from
* canvas_, obtained from stream_. We manually request new animation
* frames on this track.
*/
this.captureTrack_ = null;
/** @private {number} requestAnimationFrame handle */
this.requestAnimationFrameHandle_ = 0;
/** @private {!Array} text to render */
this.text_ = ['WebRTC samples'];
/** @private {string} */
this.debugPath_ = '';
fetch(TEXT_SOURCE)
.then(response => {
if (response.ok) {
return response.text();
}
throw new Error(`Request completed with status ${response.status}.`);
})
.then(text => {
this.text_ = text.trim().split('\n');
})
.catch((e) => {
console.log(`[CanvasSource] The request to retrieve ${
TEXT_SOURCE} encountered an error: ${e}.`);
});
}
/** @override */
setDebugPath(path) {
this.debugPath_ = path;
}
/** @override */
setVisibility(visible) {
this.visibility_ = visible;
if (this.canvas_) {
this.updateCanvasVisibility();
}
}
/** @private */
updateCanvasVisibility() {
if (this.canvas_.parentNode && !this.visibility_) {
this.canvas_.parentNode.removeChild(this.canvas_);
} else if (!this.canvas_.parentNode && this.visibility_) {
console.log('[CanvasSource] Adding source canvas to page.');
const outputVideoContainer =
document.getElementById('outputVideoContainer');
outputVideoContainer.parentNode.insertBefore(
this.canvas_, outputVideoContainer);
}
}
/** @private */
requestAnimationFrame() {
this.requestAnimationFrameHandle_ =
requestAnimationFrame(now => this.animate(now));
}
/**
* @private
* @param {number} now current animation timestamp
*/
animate(now) {
this.requestAnimationFrame();
const ctx = this.ctx_;
if (!this.canvas_ || !ctx || !this.captureTrack_) {
return;
}
// Resize canvas based on displayed size; or if not visible, based on the
// output video size.
// VideoFrame prefers to have dimensions that are even numbers.
if (this.visibility_) {
this.canvas_.width = roundToEven(this.canvas_.clientWidth);
} else {
const outputVideoContainer =
document.getElementById('outputVideoContainer');
const outputVideo = outputVideoContainer.firstElementChild;
if (outputVideo) {
this.canvas_.width = roundToEven(outputVideo.clientWidth);
}
}
this.canvas_.height = roundToEven(this.canvas_.width / CANVAS_ASPECT_RATIO);
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, this.canvas_.width, this.canvas_.height);
const linesShown = 20;
const millisecondsPerLine = 1000;
const linesIncludingExtraBlank = this.text_.length + linesShown;
const totalAnimationLength = linesIncludingExtraBlank * millisecondsPerLine;
const currentFrame = now % totalAnimationLength;
const firstLineIdx = Math.floor(
linesIncludingExtraBlank * (currentFrame / totalAnimationLength) -
linesShown);
const lineFraction = (now % millisecondsPerLine) / millisecondsPerLine;
const border = 20;
const fontSize = (this.canvas_.height - 2 * border) / (linesShown + 1);
ctx.font = `${fontSize}px sansserif`;
const textWidth = this.canvas_.width - 2 * border;
// first line
if (firstLineIdx >= 0) {
const fade = Math.floor(256 * lineFraction);
ctx.fillStyle = `rgb(${fade},${fade},${fade})`;
const position = (2 - lineFraction) * fontSize;
ctx.fillText(this.text_[firstLineIdx], border, position, textWidth);
}
// middle lines
for (let line = 2; line <= linesShown - 1; line++) {
const lineIdx = firstLineIdx + line - 1;
if (lineIdx >= 0 && lineIdx < this.text_.length) {
ctx.fillStyle = 'black';
const position = (line + 1 - lineFraction) * fontSize;
ctx.fillText(this.text_[lineIdx], border, position, textWidth);
}
}
// last line
const lastLineIdx = firstLineIdx + linesShown - 1;
if (lastLineIdx >= 0 && lastLineIdx < this.text_.length) {
const fade = Math.floor(256 * (1 - lineFraction));
ctx.fillStyle = `rgb(${fade},${fade},${fade})`;
const position = (linesShown + 1 - lineFraction) * fontSize;
ctx.fillText(this.text_[lastLineIdx], border, position, textWidth);
}
this.captureTrack_.requestFrame();
}
/** @override */
async getMediaStream() {
if (this.stream_) return this.stream_;
console.log('[CanvasSource] Initializing 2D context for source animation.');
this.canvas_ =
/** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));
this.canvas_.classList.add('video', 'sourceVideo');
// Generally video frames do not have an alpha channel. Even if the browser
// supports it, there may be a performance cost, so we disable alpha.
this.ctx_ = /** @type {?CanvasRenderingContext2D} */ (
this.canvas_.getContext('2d', {alpha: false}));
if (!this.ctx_) {
throw new Error('Unable to create CanvasRenderingContext2D');
}
this.updateCanvasVisibility();
this.stream_ = this.canvas_.captureStream(0);
this.captureTrack_ = /** @type {!CanvasCaptureMediaStreamTrack} */ (
this.stream_.getTracks()[0]);
this.requestAnimationFrame();
console.log(
'[CanvasSource] Initialized canvas, context, and capture stream.',
`${this.debugPath_}.canvas_ =`, this.canvas_,
`${this.debugPath_}.ctx_ =`, this.ctx_, `${this.debugPath_}.stream_ =`,
this.stream_, `${this.debugPath_}.captureTrack_ =`, this.captureTrack_);
return this.stream_;
}
/** @override */
destroy() {
console.log('[CanvasSource] Stopping source animation');
if (this.requestAnimationFrameHandle_) {
cancelAnimationFrame(this.requestAnimationFrameHandle_);
}
if (this.canvas_) {
if (this.canvas_.parentNode) {
this.canvas_.parentNode.removeChild(this.canvas_);
}
}
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/canvas-transform.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Applies a picture-frame effect using CanvasRenderingContext2D.
* @implements {FrameTransform} in pipeline.js
*/
class CanvasTransform { // eslint-disable-line no-unused-vars
constructor() {
/**
* @private {?OffscreenCanvas} canvas used to create the 2D context.
* Initialized in init.
*/
this.canvas_ = null;
/**
* @private {?CanvasRenderingContext2D} the 2D context used to draw the
* effect. Initialized in init.
*/
this.ctx_ = null;
/** @private {string} */
this.debugPath_ = 'debug.pipeline.frameTransform_';
}
/** @override */
async init() {
console.log('[CanvasTransform] Initializing 2D context for transform');
this.canvas_ = new OffscreenCanvas(1, 1);
this.ctx_ = /** @type {?CanvasRenderingContext2D} */ (
this.canvas_.getContext('2d', {alpha: false, desynchronized: true}));
if (!this.ctx_) {
throw new Error('Unable to create CanvasRenderingContext2D');
}
console.log(
'[CanvasTransform] CanvasRenderingContext2D initialized.',
`${this.debugPath_}.canvas_ =`, this.canvas_,
`${this.debugPath_}.ctx_ =`, this.ctx_);
}
/** @override */
async transform(frame, controller) {
const ctx = this.ctx_;
if (!this.canvas_ || !ctx) {
frame.close();
return;
}
const width = frame.displayWidth;
const height = frame.displayHeight;
this.canvas_.width = width;
this.canvas_.height = height;
const timestamp = frame.timestamp;
ctx.drawImage(frame, 0, 0);
frame.close();
ctx.shadowColor = '#000';
ctx.shadowBlur = 20;
ctx.lineWidth = 50;
ctx.strokeStyle = '#000';
ctx.strokeRect(0, 0, width, height);
// alpha: 'discard' is needed in order to send frames to a PeerConnection.
controller.enqueue(new VideoFrame(this.canvas_, {timestamp, alpha: 'discard'}));
}
/** @override */
destroy() {}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/main.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global MediaStreamTrackProcessor, MediaStreamTrackGenerator */
if (typeof MediaStreamTrackProcessor === 'undefined' ||
typeof MediaStreamTrackGenerator === 'undefined') {
alert(
'Your browser does not support the experimental MediaStreamTrack API ' +
'for Insertable Streams of Media. See the note at the bottom of the ' +
'page.');
}
/* global CameraSource */ // defined in camera-source.js
/* global CanvasSource */ // defined in canvas-source.js
/* global CanvasTransform */ // defined in canvas-transform.js
/* global PeerConnectionSink */ // defined in peer-connection-sink.js
/* global PeerConnectionSource */ // defined in peer-connection-source.js
/* global Pipeline */ // defined in pipeline.js
/* global NullTransform, DropTransform, DelayTransform */ // defined in simple-transforms.js
/* global VideoSink */ // defined in video-sink.js
/* global VideoSource */ // defined in video-source.js
/* global WebGLTransform */ // defined in webgl-transform.js
/* global WebCodecTransform */ // defined in webcodec-transform.js
/**
* Allows inspecting objects in the console. See console log messages for
* attributes added to this debug object.
* @type {!Object}
*/
let debug = {};
/**
* FrameTransformFn applies a transform to a frame and queues the output frame
* (if any) using the controller. The first argument is the input frame and the
* second argument is the stream controller.
* The VideoFrame should be closed as soon as it is no longer needed to free
* resources and maintain good performance.
* @typedef {function(
* !VideoFrame,
* !TransformStreamDefaultController): !Promise}
*/
let FrameTransformFn; // eslint-disable-line no-unused-vars
/**
* Creates a pair of MediaStreamTrackProcessor and MediaStreamTrackGenerator
* that applies transform to sourceTrack. This function is the core part of the
* sample, demonstrating how to use the new API.
* @param {!MediaStreamTrack} sourceTrack the video track to be transformed. The
* track can be from any source, e.g. getUserMedia, RTCTrackEvent, or
* captureStream on HTMLMediaElement or HTMLCanvasElement.
* @param {!FrameTransformFn} transform the transform to apply to sourceTrack;
* the transformed frames are available on the returned track. See the
* implementations of FrameTransform.transform later in this file for
* examples.
* @param {!AbortSignal} signal can be used to stop processing
* @return {!MediaStreamTrack} the result of sourceTrack transformed using
* transform.
*/
// eslint-disable-next-line no-unused-vars
function createProcessedMediaStreamTrack(sourceTrack, transform, signal) {
// Create the MediaStreamTrackProcessor.
/** @type {?MediaStreamTrackProcessor} */
let processor;
try {
processor = new MediaStreamTrackProcessor(sourceTrack);
} catch (e) {
alert(`MediaStreamTrackProcessor failed: ${e}`);
throw e;
}
// Create the MediaStreamTrackGenerator.
/** @type {?MediaStreamTrackGenerator} */
let generator;
try {
generator = new MediaStreamTrackGenerator('video');
} catch (e) {
alert(`MediaStreamTrackGenerator failed: ${e}`);
throw e;
}
const source = processor.readable;
const sink = generator.writable;
// Create a TransformStream using our FrameTransformFn. (Note that the
// "Stream" in TransformStream refers to the Streams API, specified by
// https://streams.spec.whatwg.org/, not the Media Capture and Streams API,
// specified by https://w3c.github.io/mediacapture-main/.)
/** @type {!TransformStream} */
const transformer = new TransformStream({transform});
// Apply the transform to the processor's stream and send it to the
// generator's stream.
const promise = source.pipeThrough(transformer, {signal}).pipeTo(sink);
promise.catch((e) => {
if (signal.aborted) {
console.log(
'[createProcessedMediaStreamTrack] Shutting down streams after abort.');
} else {
console.error(
'[createProcessedMediaStreamTrack] Error from stream transform:', e);
}
source.cancel(e);
sink.abort(e);
});
debug['processor'] = processor;
debug['generator'] = generator;
debug['transformStream'] = transformer;
console.log(
'[createProcessedMediaStreamTrack] Created MediaStreamTrackProcessor, ' +
'MediaStreamTrackGenerator, and TransformStream.',
'debug.processor =', processor, 'debug.generator =', generator,
'debug.transformStream =', transformer);
return generator;
}
/**
* The current video pipeline. Initialized by initPipeline().
* @type {?Pipeline}
*/
let pipeline;
/**
* Sets up handlers for interacting with the UI elements on the page.
*/
function initUI() {
const sourceSelector = /** @type {!HTMLSelectElement} */ (
document.getElementById('sourceSelector'));
const sourceVisibleCheckbox = (/** @type {!HTMLInputElement} */ (
document.getElementById('sourceVisible')));
/**
* Updates the pipeline based on the current settings of the sourceSelector
* and sourceVisible UI elements. Unlike updatePipelineSource(), never
* re-initializes the pipeline.
*/
function updatePipelineSourceIfSet() {
const sourceType =
sourceSelector.options[sourceSelector.selectedIndex].value;
if (!sourceType) return;
console.log(`[UI] Selected source: ${sourceType}`);
let source;
switch (sourceType) {
case 'camera':
source = new CameraSource();
break;
case 'video':
source = new VideoSource();
break;
case 'canvas':
source = new CanvasSource();
break;
case 'pc':
source = new PeerConnectionSource(new CameraSource());
break;
default:
alert(`unknown source ${sourceType}`);
return;
}
source.setVisibility(sourceVisibleCheckbox.checked);
pipeline.updateSource(source);
}
/**
* Updates the pipeline based on the current settings of the sourceSelector
* and sourceVisible UI elements. If the "stopped" option is selected,
* reinitializes the pipeline instead.
*/
function updatePipelineSource() {
const sourceType =
sourceSelector.options[sourceSelector.selectedIndex].value;
if (!sourceType || !pipeline) {
initPipeline();
} else {
updatePipelineSourceIfSet();
}
}
sourceSelector.oninput = updatePipelineSource;
sourceSelector.disabled = false;
/**
* Updates the source visibility, if the source is already started.
*/
function updatePipelineSourceVisibility() {
console.log(`[UI] Changed source visibility: ${
sourceVisibleCheckbox.checked ? 'added' : 'removed'}`);
if (pipeline) {
const source = pipeline.getSource();
if (source) {
source.setVisibility(sourceVisibleCheckbox.checked);
}
}
}
sourceVisibleCheckbox.oninput = updatePipelineSourceVisibility;
sourceVisibleCheckbox.disabled = false;
const transformSelector = /** @type {!HTMLSelectElement} */ (
document.getElementById('transformSelector'));
/**
* Updates the pipeline based on the current settings of the transformSelector
* UI element.
*/
function updatePipelineTransform() {
if (!pipeline) {
return;
}
const transformType =
transformSelector.options[transformSelector.selectedIndex].value;
console.log(`[UI] Selected transform: ${transformType}`);
switch (transformType) {
case 'webgl':
pipeline.updateTransform(new WebGLTransform());
break;
case 'canvas2d':
pipeline.updateTransform(new CanvasTransform());
break;
case 'drop':
// Defined in simple-transforms.js.
pipeline.updateTransform(new DropTransform());
break;
case 'noop':
// Defined in simple-transforms.js.
pipeline.updateTransform(new NullTransform());
break;
case 'delay':
// Defined in simple-transforms.js.
pipeline.updateTransform(new DelayTransform());
break;
case 'webcodec':
// Defined in webcodec-transform.js
pipeline.updateTransform(new WebCodecTransform());
break;
default:
alert(`unknown transform ${transformType}`);
break;
}
}
transformSelector.oninput = updatePipelineTransform;
transformSelector.disabled = false;
const sinkSelector = (/** @type {!HTMLSelectElement} */ (
document.getElementById('sinkSelector')));
/**
* Updates the pipeline based on the current settings of the sinkSelector UI
* element.
*/
function updatePipelineSink() {
const sinkType = sinkSelector.options[sinkSelector.selectedIndex].value;
console.log(`[UI] Selected sink: ${sinkType}`);
switch (sinkType) {
case 'video':
pipeline.updateSink(new VideoSink());
break;
case 'pc':
pipeline.updateSink(new PeerConnectionSink());
break;
default:
alert(`unknown sink ${sinkType}`);
break;
}
}
sinkSelector.oninput = updatePipelineSink;
sinkSelector.disabled = false;
/**
* Initializes/reinitializes the pipeline. Called on page load and after the
* user chooses to stop the video source.
*/
function initPipeline() {
if (pipeline) pipeline.destroy();
pipeline = new Pipeline();
debug = {pipeline};
updatePipelineSourceIfSet();
updatePipelineTransform();
updatePipelineSink();
console.log(
'[initPipeline] Created new Pipeline.', 'debug.pipeline =', pipeline);
}
}
window.onload = initUI;
================================================
FILE: src/content/insertable-streams/video-processing/js/peer-connection-pipe.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Sends a MediaStream to one end of an RTCPeerConnection and provides the
* remote end as the resulting MediaStream.
* In an actual video calling app, the two RTCPeerConnection objects would be
* instantiated on different devices. However, in this sample, both sides of the
* peer connection are local to allow the sample to be self-contained.
* For more detailed samples using RTCPeerConnection, take a look at
* https://webrtc.github.io/samples/.
*/
class PeerConnectionPipe { // eslint-disable-line no-unused-vars
/**
* @param {!MediaStream} inputStream stream to pipe over the peer connection
* @param {string} debugPath the path to this object from the debug global var
*/
constructor(inputStream, debugPath) {
/**
* @private @const {!RTCPeerConnection} the calling side of the peer
* connection, connected to inputStream_.
*/
this.caller_ = new RTCPeerConnection(null);
/**
* @private @const {!RTCPeerConnection} the answering side of the peer
* connection, providing the stream returned by getMediaStream.
*/
this.callee_ = new RTCPeerConnection(null);
/** @private {string} */
this.debugPath_ = debugPath;
/**
* @private @const {!Promise} the stream containing tracks
* from callee_, returned by getMediaStream.
*/
this.outputStreamPromise_ = this.init_(inputStream);
}
/**
* Sets the path to this object from the debug global var.
* @param {string} path
*/
setDebugPath(path) {
this.debugPath_ = path;
}
/**
* @param {!MediaStream} inputStream stream to pipe over the peer connection
* @return {!Promise}
* @private
*/
async init_(inputStream) {
console.log(
'[PeerConnectionPipe] Initiating peer connection.',
`${this.debugPath_} =`, this);
this.caller_.onicecandidate = (/** !RTCPeerConnectionIceEvent*/ event) => {
if (event.candidate) this.callee_.addIceCandidate(event.candidate);
};
this.callee_.onicecandidate = (/** !RTCPeerConnectionIceEvent */ event) => {
if (event.candidate) this.caller_.addIceCandidate(event.candidate);
};
const outputStream = new MediaStream();
const receiverStreamPromise = new Promise(resolve => {
this.callee_.ontrack = (/** !RTCTrackEvent */ event) => {
outputStream.addTrack(event.track);
if (outputStream.getTracks().length == inputStream.getTracks().length) {
resolve(outputStream);
}
};
});
inputStream.getTracks().forEach(track => {
this.caller_.addTransceiver(track, {direction: 'sendonly'});
});
await this.caller_.setLocalDescription();
await this.callee_.setRemoteDescription(
/** @type {!RTCSessionDescription} */ (this.caller_.localDescription));
await this.callee_.setLocalDescription();
await this.caller_.setRemoteDescription(
/** @type {!RTCSessionDescription} */ (this.callee_.localDescription));
await receiverStreamPromise;
console.log(
'[PeerConnectionPipe] Peer connection established.',
`${this.debugPath_}.caller_ =`, this.caller_,
`${this.debugPath_}.callee_ =`, this.callee_);
return receiverStreamPromise;
}
/**
* Provides the MediaStream that has been piped through a peer connection.
* @return {!Promise}
*/
getOutputStream() {
return this.outputStreamPromise_;
}
/** Frees any resources used by this object. */
destroy() {
console.log('[PeerConnectionPipe] Closing peer connection.');
this.caller_.close();
this.callee_.close();
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/peer-connection-sink.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global PeerConnectionPipe */ // defined in peer-connection-pipe.js
/* global VideoSink */ // defined in video-sink.js
/**
* Sends the transformed video to one end of an RTCPeerConnection and displays
* the remote end in a video element. In this sample, a PeerConnectionSink
* represents processing the local user's camera input using a
* MediaStreamTrackProcessor before sending it to a remote video call
* participant. Contrast with a PeerConnectionSource.
* @implements {MediaStreamSink} in pipeline.js
*/
class PeerConnectionSink { // eslint-disable-line no-unused-vars
constructor() {
/**
* @private @const {!VideoSink} manages displaying the video stream in the
* page
*/
this.videoSink_ = new VideoSink();
/**
* @private {?PeerConnectionPipe} handles piping the MediaStream through an
* RTCPeerConnection
*/
this.pipe_ = null;
/** @private {string} */
this.debugPath_ = 'debug.pipeline.sink_';
this.videoSink_.setDebugPath(`${this.debugPath_}.videoSink_`);
}
/** @override */
async setMediaStream(stream) {
console.log(
'[PeerConnectionSink] Setting peer connection sink stream.', stream);
if (this.pipe_) this.pipe_.destroy();
this.pipe_ = new PeerConnectionPipe(stream, `${this.debugPath_}.pipe_`);
const pipedStream = await this.pipe_.getOutputStream();
console.log(
'[PeerConnectionSink] Received callee peer connection stream.',
pipedStream);
await this.videoSink_.setMediaStream(pipedStream);
}
/** @override */
destroy() {
this.videoSink_.destroy();
if (this.pipe_) this.pipe_.destroy();
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/peer-connection-source.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global PeerConnectionPipe */ // defined in peer-connection-pipe.js
/* global VideoMirrorHelper */ // defined in video-mirror-helper.js
/**
* Sends the original source video to one end of an RTCPeerConnection and
* provides the remote end as the final source.
* In this sample, a PeerConnectionSource represents receiving video from a
* remote participant and locally processing it using a
* MediaStreamTrackProcessor before displaying it on the screen. Contrast with a
* PeerConnectionSink.
* @implements {MediaStreamSource} in pipeline.js
*/
class PeerConnectionSource { // eslint-disable-line no-unused-vars
/**
* @param {!MediaStreamSource} originalSource original stream source, whose
* output is sent over the peer connection
*/
constructor(originalSource) {
/**
* @private @const {!VideoMirrorHelper} manages displaying the video stream
* in the page
*/
this.videoMirrorHelper_ = new VideoMirrorHelper();
/**
* @private @const {!MediaStreamSource} original stream source, whose output
* is sent on the sender peer connection. In an actual video calling
* app, this stream would be generated from the remote participant's
* camera. However, in this sample, both sides of the peer connection
* are local to allow the sample to be self-contained.
*/
this.originalStreamSource_ = originalSource;
/**
* @private {?PeerConnectionPipe} handles piping the MediaStream through an
* RTCPeerConnection
*/
this.pipe_ = null;
/** @private {string} */
this.debugPath_ = '';
}
/** @override */
setDebugPath(path) {
this.debugPath_ = path;
this.videoMirrorHelper_.setDebugPath(`${path}.videoMirrorHelper_`);
this.originalStreamSource_.setDebugPath(`${path}.originalStreamSource_`);
if (this.pipe_) this.pipe_.setDebugPath(`${path}.pipe_`);
}
/** @override */
setVisibility(visible) {
this.videoMirrorHelper_.setVisibility(visible);
}
/** @override */
async getMediaStream() {
if (this.pipe_) return this.pipe_.getOutputStream();
console.log(
'[PeerConnectionSource] Obtaining original source media stream.',
`${this.debugPath_}.originalStreamSource_ =`,
this.originalStreamSource_);
const originalStream = await this.originalStreamSource_.getMediaStream();
this.pipe_ =
new PeerConnectionPipe(originalStream, `${this.debugPath_}.pipe_`);
const outputStream = await this.pipe_.getOutputStream();
console.log(
'[PeerConnectionSource] Received callee peer connection stream.',
outputStream);
this.videoMirrorHelper_.setStream(outputStream);
return outputStream;
}
/** @override */
destroy() {
this.videoMirrorHelper_.destroy();
if (this.pipe_) this.pipe_.destroy();
this.originalStreamSource_.destroy();
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/pipeline.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/* global createProcessedMediaStreamTrack */ // defined in main.js
/**
* Wrapper around createProcessedMediaStreamTrack to apply transform to a
* MediaStream.
* @param {!MediaStream} sourceStream the video stream to be transformed. The
* first video track will be used.
* @param {!FrameTransformFn} transform the transform to apply to the
* sourceStream.
* @param {!AbortSignal} signal can be used to stop processing
* @return {!MediaStream} holds a single video track of the transformed video
* frames
*/
function createProcessedMediaStream(sourceStream, transform, signal) {
// For this sample, we're only dealing with video tracks.
/** @type {!MediaStreamTrack} */
const sourceTrack = sourceStream.getVideoTracks()[0];
const processedTrack =
createProcessedMediaStreamTrack(sourceTrack, transform, signal);
// Create a new MediaStream to hold our processed track.
const processedStream = new MediaStream();
processedStream.addTrack(processedTrack);
return processedStream;
}
/**
* Interface implemented by all video sources the user can select. A common
* interface allows the user to choose a source independently of the transform
* and sink.
* @interface
*/
class MediaStreamSource { // eslint-disable-line no-unused-vars
/**
* Sets the path to this object from the debug global var.
* @param {string} path
*/
setDebugPath(path) {}
/**
* Indicates if the source video should be mirrored/displayed on the page. If
* false (the default), any element producing frames will not be a child of
* the document.
* @param {boolean} visible whether to add the raw source video to the page
*/
setVisibility(visible) {}
/**
* Initializes and returns the MediaStream for this source.
* @return {!Promise}
*/
async getMediaStream() {}
/** Frees any resources used by this object. */
destroy() {}
}
/**
* Interface implemented by all video transforms that the user can select. A
* common interface allows the user to choose a transform independently of the
* source and sink.
* @interface
*/
class FrameTransform { // eslint-disable-line no-unused-vars
/** Initializes state that is reused across frames. */
async init() {}
/**
* Applies the transform to frame. Queues the output frame (if any) using the
* controller.
* @param {!VideoFrame} frame the input frame
* @param {!TransformStreamDefaultController} controller
*/
async transform(frame, controller) {}
/** Frees any resources used by this object. */
destroy() {}
}
/**
* Interface implemented by all video sinks that the user can select. A common
* interface allows the user to choose a sink independently of the source and
* transform.
* @interface
*/
class MediaStreamSink { // eslint-disable-line no-unused-vars
/**
* @param {!MediaStream} stream
*/
async setMediaStream(stream) {}
/** Frees any resources used by this object. */
destroy() {}
}
/**
* Assembles a MediaStreamSource, FrameTransform, and MediaStreamSink together.
*/
class Pipeline { // eslint-disable-line no-unused-vars
constructor() {
/** @private {?MediaStreamSource} set by updateSource*/
this.source_ = null;
/** @private {?FrameTransform} set by updateTransform */
this.frameTransform_ = null;
/** @private {?MediaStreamSink} set by updateSink */
this.sink_ = null;
/** @private {!AbortController} may used to stop all processing */
this.abortController_ = new AbortController();
/**
* @private {?MediaStream} set in maybeStartPipeline_ after all of source_,
* frameTransform_, and sink_ are set
*/
this.processedStream_ = null;
}
/** @return {?MediaStreamSource} */
getSource() {
return this.source_;
}
/**
* Sets a new source for the pipeline.
* @param {!MediaStreamSource} mediaStreamSource
*/
async updateSource(mediaStreamSource) {
if (this.source_) {
this.abortController_.abort();
this.abortController_ = new AbortController();
this.source_.destroy();
this.processedStream_ = null;
}
this.source_ = mediaStreamSource;
this.source_.setDebugPath('debug.pipeline.source_');
console.log(
'[Pipeline] Updated source.',
'debug.pipeline.source_ = ', this.source_);
await this.maybeStartPipeline_();
}
/** @private */
async maybeStartPipeline_() {
if (this.processedStream_ || !this.source_ || !this.frameTransform_ ||
!this.sink_) {
return;
}
const sourceStream = await this.source_.getMediaStream();
await this.frameTransform_.init();
try {
this.processedStream_ = createProcessedMediaStream(
sourceStream, async (frame, controller) => {
if (this.frameTransform_) {
await this.frameTransform_.transform(frame, controller);
}
}, this.abortController_.signal);
} catch (e) {
this.destroy();
return;
}
await this.sink_.setMediaStream(this.processedStream_);
console.log(
'[Pipeline] Pipeline started.',
'debug.pipeline.abortController_ =', this.abortController_);
}
/**
* Sets a new transform for the pipeline.
* @param {!FrameTransform} frameTransform
*/
async updateTransform(frameTransform) {
if (this.frameTransform_) this.frameTransform_.destroy();
this.frameTransform_ = frameTransform;
console.log(
'[Pipeline] Updated frame transform.',
'debug.pipeline.frameTransform_ = ', this.frameTransform_);
if (this.processedStream_) {
await this.frameTransform_.init();
} else {
await this.maybeStartPipeline_();
}
}
/**
* Sets a new sink for the pipeline.
* @param {!MediaStreamSink} mediaStreamSink
*/
async updateSink(mediaStreamSink) {
if (this.sink_) this.sink_.destroy();
this.sink_ = mediaStreamSink;
console.log(
'[Pipeline] Updated sink.', 'debug.pipeline.sink_ = ', this.sink_);
if (this.processedStream_) {
await this.sink_.setMediaStream(this.processedStream_);
} else {
await this.maybeStartPipeline_();
}
}
/** Frees any resources used by this object. */
destroy() {
console.log('[Pipeline] Destroying Pipeline');
this.abortController_.abort();
if (this.source_) this.source_.destroy();
if (this.frameTransform_) this.frameTransform_.destroy();
if (this.sink_) this.sink_.destroy();
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/simple-transforms.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Does nothing.
* @implements {FrameTransform} in pipeline.js
*/
class NullTransform { // eslint-disable-line no-unused-vars
/** @override */
async init() {}
/** @override */
async transform(frame, controller) {
controller.enqueue(frame);
}
/** @override */
destroy() {}
}
/**
* Drops frames at random.
* @implements {FrameTransform} in pipeline.js
*/
class DropTransform { // eslint-disable-line no-unused-vars
/** @override */
async init() {}
/** @override */
async transform(frame, controller) {
if (Math.random() < 0.5) {
controller.enqueue(frame);
} else {
frame.close();
}
}
/** @override */
destroy() {}
}
/**
* Delays all frames by 100ms.
* @implements {FrameTransform} in pipeline.js
*/
class DelayTransform { // eslint-disable-line no-unused-vars
/** @override */
async init() {}
/** @override */
async transform(frame, controller) {
await new Promise(resolve => setTimeout(resolve, 100));
controller.enqueue(frame);
}
/** @override */
destroy() {}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/video-mirror-helper.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Helper to display a MediaStream in an HTMLVideoElement, based on the
* visibility setting.
*/
class VideoMirrorHelper { // eslint-disable-line no-unused-vars
constructor() {
/** @private {boolean} */
this.visibility_ = false;
/** @private {?MediaStream} the stream to display */
this.stream_ = null;
/**
* @private {?HTMLVideoElement} video element mirroring the camera stream.
* Set if visibility_ is true and stream_ is set.
*/
this.video_ = null;
/** @private {string} */
this.debugPath_ = '';
}
/**
* Sets the path to this object from the debug global var.
* @param {string} path
*/
setDebugPath(path) {
this.debugPath_ = path;
}
/**
* Indicates if the video should be mirrored/displayed on the page.
* @param {boolean} visible whether to add the video from the source stream to
* the page
*/
setVisibility(visible) {
this.visibility_ = visible;
if (this.video_ && !this.visibility_) {
this.video_.parentNode.removeChild(this.video_);
this.video_ = null;
}
this.maybeAddVideoElement_();
}
/**
* @param {!MediaStream} stream
*/
setStream(stream) {
this.stream_ = stream;
this.maybeAddVideoElement_();
}
/** @private */
maybeAddVideoElement_() {
if (!this.video_ && this.visibility_ && this.stream_) {
this.video_ =
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
console.log(
'[VideoMirrorHelper] Adding source video mirror.',
`${this.debugPath_}.video_ =`, this.video_);
this.video_.classList.add('video', 'sourceVideo');
this.video_.srcObject = this.stream_;
const outputVideoContainer =
document.getElementById('outputVideoContainer');
outputVideoContainer.parentNode.insertBefore(
this.video_, outputVideoContainer);
this.video_.play();
}
}
/** Frees any resources used by this object. */
destroy() {
if (this.video_) {
this.video_.pause();
this.video_.srcObject = null;
this.video_.parentNode.removeChild(this.video_);
}
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/video-sink.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Displays the output stream in a video element.
* @implements {MediaStreamSink} in pipeline.js
*/
class VideoSink { // eslint-disable-line no-unused-vars
constructor() {
/**
* @private {?HTMLVideoElement} output video element
*/
this.video_ = null;
/** @private {string} */
this.debugPath_ = 'debug.pipeline.sink_';
}
/**
* Sets the path to this object from the debug global var.
* @param {string} path
*/
setDebugPath(path) {
this.debugPath_ = path;
}
/** @override */
async setMediaStream(stream) {
console.log('[VideoSink] Setting sink stream.', stream);
if (!this.video_) {
this.video_ =
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
this.video_.classList.add('video', 'sinkVideo');
document.getElementById('outputVideoContainer').appendChild(this.video_);
console.log(
'[VideoSink] Added video element to page.',
`${this.debugPath_}.video_ =`, this.video_);
}
this.video_.srcObject = stream;
this.video_.play();
}
/** @override */
destroy() {
if (this.video_) {
console.log('[VideoSink] Stopping sink video');
this.video_.pause();
this.video_.srcObject = null;
this.video_.parentNode.removeChild(this.video_);
}
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/video-source.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Decodes and plays a video.
* @implements {MediaStreamSource} in pipeline.js
*/
class VideoSource { // eslint-disable-line no-unused-vars
constructor() {
/** @private {boolean} */
this.visibility_ = false;
/** @private {?HTMLVideoElement} video element providing the MediaStream */
this.video_ = null;
/**
* @private {?Promise} a Promise that resolves to the
* MediaStream from captureStream. Set iff video_ is set.
*/
this.stream_ = null;
/** @private {string} */
this.debugPath_ = '';
}
/** @override */
setDebugPath(path) {
this.debugPath_ = path;
}
/** @override */
setVisibility(visible) {
this.visibility_ = visible;
if (this.video_) {
this.updateVideoVisibility();
}
}
/** @private */
updateVideoVisibility() {
if (this.video_.parentNode && !this.visibility_) {
if (!this.video_.paused) {
// Video playback is automatically paused when the element is removed
// from the DOM. That is not the behavior we want.
this.video_.onpause = async () => {
this.video_.onpause = null;
await this.video_.play();
};
}
this.video_.parentNode.removeChild(this.video_);
} else if (!this.video_.parentNode && this.visibility_) {
console.log(
'[VideoSource] Adding source video element to page.',
`${this.debugPath_}.video_ =`, this.video_);
const outputVideoContainer =
document.getElementById('outputVideoContainer');
outputVideoContainer.parentNode.insertBefore(
this.video_, outputVideoContainer);
}
}
/** @override */
async getMediaStream() {
if (this.stream_) return this.stream_;
console.log('[VideoSource] Loading video');
this.video_ =
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
this.video_.classList.add('video', 'sourceVideo');
this.video_.controls = true;
this.video_.loop = true;
this.video_.muted = true;
// All browsers that support insertable streams also support WebM/VP8.
this.video_.src = '../../../video/chrome.webm';
this.video_.load();
this.video_.play();
this.updateVideoVisibility();
this.stream_ = new Promise((resolve, reject) => {
this.video_.oncanplay = () => {
if (!resolve || !reject) return;
console.log('[VideoSource] Obtaining video capture stream');
if (this.video_.captureStream) {
resolve(this.video_.captureStream());
} else if (this.video_.mozCaptureStream) {
resolve(this.video_.mozCaptureStream());
} else {
const e = new Error('Stream capture is not supported');
console.error(e);
reject(e);
}
resolve = null;
reject = null;
};
});
await this.stream_;
console.log(
'[VideoSource] Received source video stream.',
`${this.debugPath_}.stream_ =`, this.stream_);
return this.stream_;
}
/** @override */
destroy() {
if (this.video_) {
console.log('[VideoSource] Stopping source video');
this.video_.pause();
if (this.video_.parentNode) {
this.video_.parentNode.removeChild(this.video_);
}
}
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/webcodec-transform.js
================================================
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Encodes and decodes frames using the WebCodec API.
* @implements {FrameTransform} in pipeline.js
*/
class WebCodecTransform { // eslint-disable-line no-unused-vars
constructor() {
// Encoder and decoder are initialized in init()
this.decoder_ = null;
this.encoder_ = null;
this.controller_ = null;
}
/** @override */
async init() {
console.log('[WebCodecTransform] Initializing encoder and decoder');
this.decoder_ = new VideoDecoder({
output: frame => this.handleDecodedFrame(frame),
error: this.error
});
this.encoder_ = new VideoEncoder({
output: frame => this.handleEncodedFrame(frame),
error: this.error
});
this.encoder_.configure({codec: 'vp8', width: 640, height: 480});
this.decoder_.configure({codec: 'vp8', width: 640, height: 480});
}
/** @override */
async transform(frame, controller) {
if (!this.encoder_) {
frame.close();
return;
}
try {
this.controller_ = controller;
this.encoder_.encode(frame);
} finally {
frame.close();
}
}
/** @override */
destroy() {}
/* Helper functions */
handleEncodedFrame(encodedFrame) {
this.decoder_.decode(encodedFrame);
}
handleDecodedFrame(videoFrame) {
if (!this.controller_) {
videoFrame.close();
return;
}
this.controller_.enqueue(videoFrame);
}
error(e) {
console.log('[WebCodecTransform] Bad stuff happened: ' + e);
}
}
================================================
FILE: src/content/insertable-streams/video-processing/js/webgl-transform.js
================================================
/*
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
/**
* Applies a warp effect using WebGL.
* @implements {FrameTransform} in pipeline.js
*/
class WebGLTransform { // eslint-disable-line no-unused-vars
constructor() {
// All fields are initialized in init()
/** @private {?OffscreenCanvas} canvas used to create the WebGL context */
this.canvas_ = null;
/** @private {?WebGLRenderingContext} */
this.gl_ = null;
/** @private {?WebGLUniformLocation} location of inSampler */
this.sampler_ = null;
/** @private {?WebGLProgram} */
this.program_ = null;
/** @private {?WebGLTexture} input texture */
this.texture_ = null;
/** @private {string} */
this.debugPath_ = 'debug.pipeline.frameTransform_';
}
/** @override */
async init() {
console.log('[WebGLTransform] Initializing WebGL.');
this.canvas_ = new OffscreenCanvas(1, 1);
const gl = /** @type {?WebGLRenderingContext} */ (
this.canvas_.getContext('webgl'));
if (!gl) {
alert(
'Failed to create WebGL context. Check that WebGL is supported ' +
'by your browser and hardware.');
return;
}
this.gl_ = gl;
const vertexShader = this.loadShader_(gl.VERTEX_SHADER, `
precision mediump float;
attribute vec3 g_Position;
attribute vec2 g_TexCoord;
varying vec2 texCoord;
void main() {
gl_Position = vec4(g_Position, 1.0);
texCoord = g_TexCoord;
}`);
const fragmentShader = this.loadShader_(gl.FRAGMENT_SHADER, `
precision mediump float;
varying vec2 texCoord;
uniform sampler2D inSampler;
void main(void) {
float boundary = distance(texCoord, vec2(0.5)) - 0.2;
if (boundary < 0.0) {
gl_FragColor = texture2D(inSampler, texCoord);
} else {
// Rotate the position
float angle = 2.0 * boundary;
vec2 rotation = vec2(sin(angle), cos(angle));
vec2 fromCenter = texCoord - vec2(0.5);
vec2 rotatedPosition = vec2(
fromCenter.x * rotation.y + fromCenter.y * rotation.x,
fromCenter.y * rotation.y - fromCenter.x * rotation.x) + vec2(0.5);
gl_FragColor = texture2D(inSampler, rotatedPosition);
}
}`);
if (!vertexShader || !fragmentShader) return;
// Create the program object
const programObject = gl.createProgram();
gl.attachShader(programObject, vertexShader);
gl.attachShader(programObject, fragmentShader);
// Link the program
gl.linkProgram(programObject);
// Check the link status
const linked = gl.getProgramParameter(programObject, gl.LINK_STATUS);
if (!linked) {
const infoLog = gl.getProgramInfoLog(programObject);
gl.deleteProgram(programObject);
throw new Error(`Error linking program:\n${infoLog}`);
}
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
this.sampler_ = gl.getUniformLocation(programObject, 'inSampler');
this.program_ = programObject;
// Bind attributes
const vertices = [1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
// Pass-through.
const txtcoords = [1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0];
// Mirror horizonally.
// const txtcoords = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
this.attributeSetFloats_('g_Position', 2, vertices);
this.attributeSetFloats_('g_TexCoord', 2, txtcoords);
// Initialize input texture
this.texture_ = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture_);
const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
console.log(
'[WebGLTransform] WebGL initialized.', `${this.debugPath_}.canvas_ =`,
this.canvas_, `${this.debugPath_}.gl_ =`, this.gl_);
}
/**
* Creates and compiles a WebGLShader from the provided source code.
* @param {number} type either VERTEX_SHADER or FRAGMENT_SHADER
* @param {string} shaderSrc
* @return {!WebGLShader}
* @private
*/
loadShader_(type, shaderSrc) {
const gl = this.gl_;
const shader = gl.createShader(type);
// Load the shader source
gl.shaderSource(shader, shaderSrc);
// Compile the shader
gl.compileShader(shader);
// Check the compile status
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const infoLog = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(`Error compiling shader:\n${infoLog}`);
}
return shader;
}
/**
* Sets a floating point shader attribute to the values in arr.
* @param {string} attrName the name of the shader attribute to set
* @param {number} vsize the number of components of the shader attribute's
* type
* @param {!Array} arr the values to set
* @private
*/
attributeSetFloats_(attrName, vsize, arr) {
const gl = this.gl_;
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(arr), gl.STATIC_DRAW);
const attr = gl.getAttribLocation(this.program_, attrName);
gl.enableVertexAttribArray(attr);
gl.vertexAttribPointer(attr, vsize, gl.FLOAT, false, 0, 0);
}
/** @override */
async transform(frame, controller) {
const gl = this.gl_;
if (!gl || !this.canvas_) {
frame.close();
return;
}
const width = frame.displayWidth;
const height = frame.displayHeight;
if (this.canvas_.width !== width || this.canvas_.height !== height) {
this.canvas_.width = width;
this.canvas_.height = height;
gl.viewport(0, 0, width, height);
}
const timestamp = frame.timestamp;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture_);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
frame.close();
gl.useProgram(this.program_);
gl.uniform1i(this.sampler_, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.bindTexture(gl.TEXTURE_2D, null);
// alpha: 'discard' is needed in order to send frames to a PeerConnection.
controller.enqueue(new VideoFrame(this.canvas_, {timestamp, alpha: 'discard'}));
}
/** @override */
destroy() {
if (this.gl_) {
console.log('[WebGLTransform] Forcing WebGL context to be lost.');
/** @type {!WEBGL_lose_context} */ (
this.gl_.getExtension('WEBGL_lose_context'))
.loseContext();
}
}
}
================================================
FILE: src/content/insertable-streams/webgpu/css/main.css
================================================
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
video {
width: 480px;
height: 270px;
}
.output {
width: 960px;
height: 540px;
margin: 0px 0px 0px 0px;
}
.error {
font-size: 20px;
color:red;
}
================================================
FILE: src/content/insertable-streams/webgpu/index.html
================================================
Integrations with WebGPU for custom video rendering
WebRTC samplesIntegrations with WebGPU for custom video rendering
This sample shows how to render multiple video streams to canvas using the insertable streams and WebGPU APIs. There are options to either process the
rendering on the main thread or on a worker thread.
Choose type of rendering:
Input:
Output:
Note: This sample is using WebGPU API that is in Origin Trial as
of 2021-09-21 and is available in Chrome M94 if the experimental code is enabled on
the command line with
--enable-unsafe-webgpu.
Data channels are not used in this example but negotiating them in the SDP (using the checkbox in supported browsers)
avoids video freezes (on the right video) if the alwaysNegotiateDataChannels configuration option is used.
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can inspect them in the console as
well.
This sample shows how to setup a connection between two peers using
RTCPeerConnection and
choose the preferred video codec to use (when that functionality is available.)
Codec preferences:
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can inspect them in the console as
well.
This demo shows ways to use constraints and statistics in WebRTC applications.
Set camera constraints, and click Get media to (re)open the camera with these included.
Click Connect to create a (local) peer connection. The RTCPeerConnection objects pc1
and pc2 can be inspected from the console.
Setting a value to zero will remove that constraint.
The lefthand video shows the output of getUserMedia(); on the right is the video after being
passed through the peer connection. The transmission bitrate is displayed below the righthand video.
This page tests the createOffer() method. It creates a peer connection, then prints out the SDP
generated by createOffer(), with the number of desired audio MediaStreamTracks and the
checked constraints. Currently, only audio tracks can be added, as there is no programmatic way to generate
video tracks. (Web Audio is used to generate the audio tracks.)
================================================
FILE: src/content/peerconnection/multiple/css/main.css
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 20px 0 0;
width: 83px;
}
button#hangupButton {
margin: 0;
}
video {
margin: 0 0 20px 0;
--width: 40%;
width: var(--width);
height: calc(var(--width) * 0.75);
}
video#video1 {
margin: 0 20px 20px 0;
}
@media screen and (max-width: 400px) {
button {
margin: 0 11px 10px 0;
}
video {
height: 90px;
margin: 0 0 10px 0;
width: calc(50% - 8px);
}
video#video1 {
margin: 0 10px 10px 0;
}
}
================================================
FILE: src/content/peerconnection/multiple/index.html
================================================
Multiple peer connections
Note: SDP "munging", i.e. modifying the SDP between createOffer/createAnswer and setLocalDescription
is a nonstandard feature which may not work as expected. While some browsers support some modifications,
the W3C standard forbids it.
Local
Offer SDP
Remote
Answer SDP
View the console to see logging.
The RTCPeerConnection objects pc1 and pc2
are in global scope, so you can inspect them in the console as well.
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can inspect them in the console as
well.
This sample shows how to setup a connection between two peers using
RTCPeerConnection.
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can inspect them in the console as
well.
Perfect Negotiation supports both endpoints sending offers. The pattern intelligently handles the
situation of "glare" (both peers making an offer at the same time, causing a collision) by having
one peer be "polite" and the other peer be "impolite". In the event of an offer collision, the
polite peer rolls back its offer in order to process the impolite peer's incoming offer. Once
back to the stable signaling state, the polite peer's onnegotiationneeded fires again and a
follow-up O/A is completed.
Click both peers' Start button to create local streams.
Then press the Swap Sending Track button to modify which transceiver is sending;
this will be negotiated and displayed as a remote track on the other peer's iframe.
The JavaScript console shows logs for the negotiation steps.
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can
inspect them in the console as well.
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can inspect them in the console as well.
This page tests the trickle ICE functionality in a WebRTC implementation. It creates a PeerConnection with
the specified ICEServers, and then starts candidate gathering for a session with a single audio stream. As
candidates are gathered, they are displayed in the text box below, along with an indication when candidate
gathering is complete.
Note that if no getUserMedia permissions for this origin are persisted only candidates from a single
interface will be gathered in Chrome. See the RTCWEB IP address handling
recommendations draft for details.You have given permission, candidate from multiple interface will be gathered.
Individual STUN and TURN servers can be added using the Add server / Remove server controls below; in
addition, the type of candidates released to the application can be controlled via the IceTransports
constraint.
If you test a STUN server, it works if you can gather a candidate with type "srflx".
If you test a TURN server, it works if you can gather a candidate with type "relay".
If you test just a single TURN/UDP server, this page even allows you to detect when you are using the wrong
credential to authenticate.
ICE servers
ICE options
allrelay
Time
Type
Foundation
Protocol
Address
Port
Priority
URL (if present)
relayProtocol (if present)
Note: errors from onicecandidateerror above are not necessarily fatal. For example an IPv6 DNS lookup may fail but relay candidates can still be gathered via IPv4.
================================================
FILE: src/content/peerconnection/trickle-ice/js/main.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
const addButton = document.querySelector('button#add');
const candidateTBody = document.querySelector('tbody#candidatesBody');
const gatherButton = document.querySelector('button#gather');
const passwordInput = document.querySelector('input#password');
const removeButton = document.querySelector('button#remove');
const resetButton = document.querySelector('button#reset');
const servers = document.querySelector('select#servers');
const urlInput = document.querySelector('input#url');
const usernameInput = document.querySelector('input#username');
const getUserMediaInput = document.querySelector('input#getUserMedia');
addButton.onclick = addServer;
gatherButton.onclick = start;
removeButton.onclick = removeServer;
resetButton.onclick = (e) => {
window.localStorage.clear();
document.querySelectorAll('select#servers option').forEach(option => option.remove());
const serversSelect = document.querySelector('select#servers');
setDefaultServer(serversSelect);
};
let begin;
let pc;
let stream;
let candidates;
const allServersKey = 'servers';
function setDefaultServer(serversSelect) {
const option = document.createElement('option');
option.value = '{"urls":["stun:stun.l.google.com:19302"]}';
option.text = 'stun:stun.l.google.com:19302';
option.ondblclick = selectServer;
serversSelect.add(option);
}
function writeServersToLocalStorage() {
const serversSelect = document.querySelector('select#servers');
const allServers = JSON.stringify(Object.values(serversSelect.options).map(o => JSON.parse(o.value)));
window.localStorage.setItem(allServersKey, allServers);
}
function readServersFromLocalStorage() {
document.querySelectorAll('select#servers option').forEach(option => option.remove());
const serversSelect = document.querySelector('select#servers');
const storedServers = window.localStorage.getItem(allServersKey);
if (storedServers === null || storedServers === '') {
setDefaultServer(serversSelect);
} else {
JSON.parse(storedServers).forEach((server, key) => {
const o = document.createElement('option');
o.value = JSON.stringify(server);
o.text = server.urls[0];
o.ondblclick = selectServer;
serversSelect.add(o);
});
}
}
function selectServer(event) {
const option = event.target;
const value = JSON.parse(option.value);
urlInput.value = value.urls[0];
usernameInput.value = value.username || '';
passwordInput.value = value.credential || '';
}
function addServer() {
if (urlInput.value === '' && usernameInput.value === '' && passwordInput.value === '') {
// Ignore since this leads to invisible items being added to the list.
console.warn('Not adding empty ICE server input');
return;
}
// Store the ICE server as a stringified JSON object in option.value.
const option = document.createElement('option');
const iceServer = {
urls: [urlInput.value],
username: usernameInput.value,
credential: passwordInput.value
};
option.value = JSON.stringify(iceServer);
option.text = `${urlInput.value} `;
const username = usernameInput.value;
const password = passwordInput.value;
if (username || password) {
option.text += (` [${username}:${password}]`);
}
option.ondblclick = selectServer;
servers.add(option);
urlInput.value = usernameInput.value = passwordInput.value = '';
writeServersToLocalStorage();
}
function removeServer() {
for (let i = servers.options.length - 1; i >= 0; --i) {
if (servers.options[i].selected) {
servers.remove(i);
}
}
writeServersToLocalStorage();
}
async function start() {
// Clean out the table.
while (candidateTBody.firstChild) {
candidateTBody.removeChild(candidateTBody.firstChild);
}
gatherButton.disabled = true;
if (getUserMediaInput.checked) {
stream = await navigator.mediaDevices.getUserMedia({audio: true});
}
getUserMediaInput.disabled = true;
// Read the values from the input boxes.
const iceServers = [];
for (let i = 0; i < servers.length; ++i) {
iceServers.push(JSON.parse(servers[i].value));
}
const transports = document.getElementsByName('transports');
let iceTransports;
for (let i = 0; i < transports.length; ++i) {
if (transports[i].checked) {
iceTransports = transports[i].value;
break;
}
}
// Create a PeerConnection with no streams, but force a m=audio line.
const config = {
iceServers: iceServers,
iceTransportPolicy: iceTransports,
};
const offerOptions = {offerToReceiveAudio: 1};
// Whether we gather IPv6 candidates.
// Whether we only gather a single set of candidates for RTP and RTCP.
console.log(`Creating new PeerConnection with config=${JSON.stringify(config)}`);
const errDiv = document.getElementById('error');
errDiv.innerText = '';
let desc;
try {
pc = new RTCPeerConnection(config);
pc.onicecandidate = iceCallback;
pc.onicegatheringstatechange = gatheringStateChange;
pc.onicecandidateerror = iceCandidateError;
if (stream) {
stream.getTracks().forEach(track => pc.addTrack(track, stream));
}
desc = await pc.createOffer(offerOptions);
} catch (err) {
errDiv.innerText = `Error creating offer: ${err}`;
gatherButton.disabled = false;
return;
}
begin = window.performance.now();
candidates = [];
pc.setLocalDescription(desc);
}
// Parse the uint32 PRIORITY field into its constituent parts from RFC 5245,
// type preference, local preference, and (256 - component ID).
// ex: 126 | 32252 | 255 (126 is host preference, 255 is component ID 1)
function formatPriority(priority) {
return [
priority >> 24,
(priority >> 8) & 0xFFFF,
priority & 0xFF
].join(' | ');
}
function appendCell(row, val) {
const cell = document.createElement('td');
cell.textContent = val;
row.appendChild(cell);
}
// Try to determine authentication failures and unreachable TURN
// servers by using heuristics on the candidate types gathered.
function getFinalResult() {
let result = 'Done';
// if more than one server is used, it can not be determined
// which server failed.
if (servers.length === 1) {
const server = JSON.parse(servers[0].value);
// get the candidates types (host, srflx, relay)
const types = candidates.map((cand) => cand.type);
// If the server is a TURN server we should have a relay candidate.
// If we did not get a relay candidate but a srflx candidate
// authentication might have failed.
// If we did not get a relay candidate or a srflx candidate
// we could not reach the TURN server. Either it is not running at
// the target address or the clients access to the port is blocked.
//
// This only works for TURN/UDP since we do not get
// srflx candidates from TURN/TCP.
if (server.urls[0].indexOf('turn:') === 0 &&
server.urls[0].indexOf('?transport=tcp') === -1) {
if (types.indexOf('relay') === -1) {
if (types.indexOf('srflx') > -1) {
// a binding response but no relay candidate suggests auth failure.
result = 'Authentication failed?';
} else {
// either the TURN server is down or the clients access is blocked.
result = 'Not reachable?';
}
}
}
}
return result;
}
async function iceCallback(event) {
const elapsed = ((window.performance.now() - begin) / 1000).toFixed(3);
const row = document.createElement('tr');
if (event.candidate) {
if (event.candidate.candidate === '') {
return;
}
appendCell(row, elapsed);
const {candidate} = event;
let url;
// Until url is available from the candidate, to to polyfill.
if (['srflx', 'relay'].includes(candidate.type) && !candidate.url) {
const stats = await pc.getStats();
stats.forEach(report => {
if (!url && report.type === 'local-candidate' &&
report.address === candidate.address &&
report.port === candidate.port) {
url = report.url;
}
});
}
appendCell(row, candidate.type);
appendCell(row, candidate.foundation);
appendCell(row, candidate.protocol);
appendCell(row, candidate.address);
appendCell(row, candidate.port);
appendCell(row, formatPriority(candidate.priority));
appendCell(row, candidate.url || url || '');
appendCell(row, candidate.relayProtocol || '');
candidates.push(candidate);
}
candidateTBody.appendChild(row);
}
function gatheringStateChange() {
if (pc.iceGatheringState !== 'complete') {
return;
}
const elapsed = ((window.performance.now() - begin) / 1000).toFixed(3);
const row = document.createElement('tr');
appendCell(row, elapsed);
appendCell(row, getFinalResult());
pc.close();
pc = null;
if (stream) {
stream.getTracks().forEach(track => track.stop());
stream = null;
}
gatherButton.disabled = false;
getUserMediaInput.disabled = false;
candidateTBody.appendChild(row);
}
function iceCandidateError(e) {
// The interesting attributes of the error are
// * the url (which allows looking up the server)
// * the errorCode and errorText
document.getElementById('error-note').style.display = 'block';
document.getElementById('error').innerText += 'The server ' + e.url +
' returned an error with code=' + e.errorCode + ':\n' +
e.errorText + '\n';
}
readServersFromLocalStorage();
// check if we have getUserMedia permissions.
navigator.mediaDevices
.enumerateDevices()
.then(function(devices) {
devices.forEach(function(device) {
if (device.label !== '') {
document.getElementById('getUserMediaPermissions').style.display = 'block';
}
});
});
================================================
FILE: src/content/peerconnection/trickle-ice/js/test.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
const webdriver = require('selenium-webdriver');
const seleniumHelpers = require('../../../../../test/webdriver');
let driver;
const path = '/src/content/peerconnection/trickle-ice/index.html';
const url = `${process.env.BASEURL ? process.env.BASEURL : ('file://' + process.cwd())}${path}`;
describe('Trickle-Ice', () => {
beforeAll(async () => {
driver = await seleniumHelpers.buildDriver();
});
afterAll(() => {
return driver.quit();
});
beforeEach(() => {
return driver.get(url);
});
afterEach(() => {
return driver.executeScript(() => localStorage.clear());
});
it('gathers a candidate', async () => {
await driver.findElement(webdriver.By.id('gather')).click();
await driver.wait(() => driver.executeScript(() => pc === null && candidates.length > 0), 30 * 1000); // eslint-disable-line no-undef
});
it('loads server data on double click', async () => {
const element = await driver.findElement(webdriver.By.css('#servers>option'));
const actions = driver.actions({async: true});
await actions.doubleClick(element).perform();
const value = await driver.findElement(webdriver.By.id('url')).getAttribute('value');
expect(value).not.toBe('');
});
it('adding a server', async () => {
await driver.findElement(webdriver.By.id('url'))
.sendKeys('stun:stun.l.google.com:19302');
await driver.findElement(webdriver.By.id('add')).click();
const length = await driver.findElement(webdriver.By.css('#servers'))
.getAttribute('length');
expect(length >>> 0).toBe(2);
});
it('removing a server', async () => {
await driver.findElement(webdriver.By.css('#servers>option')).click();
await driver.findElement(webdriver.By.id('remove')).click();
const length = await driver.findElement(webdriver.By.css('#servers'))
.getAttribute('length');
expect(length >>> 0).toBe(0);
});
});
================================================
FILE: src/content/peerconnection/upgrade/css/main.css
================================================
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
button {
margin: 0 20px 0 0;
width: 83px;
}
button#hangupButton {
margin: 0;
}
video {
--width: 45%;
width: var(--width);
height: calc(var(--width) * 0.75);
margin: 0 0 20px 0;
vertical-align: top;
}
video#localVideo {
margin: 0 20px 20px 0;
}
@media screen and (max-width: 400px) {
button {
width: 83px;
margin: 0 11px 10px 0;
}
video {
height: 90px;
margin: 0 0 10px 0;
width: calc(50% - 7px);
}
video#localVideo {
margin: 0 10px 20px 0;
}
}
================================================
FILE: src/content/peerconnection/upgrade/index.html
================================================
Peer connection - upgrade
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can inspect them in the console as
well.
View the console to see logging. The MediaStream object localStream, and the RTCPeerConnection
objects pc1 and pc2 are in global scope, so you can
inspect them in the console as well.
================================================
FILE: src/content/peerconnection/webaudio-output/js/main.js
================================================
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* globals StreamVisualizer */
'use strict';
const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
callButton.disabled = true;
hangupButton.disabled = true;
startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;
const canvas = document.querySelector('canvas');
let startTime;
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
localVideo.addEventListener('loadedmetadata', () => {
return console.log(`Local video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
});
remoteVideo.addEventListener('loadedmetadata', () => {
return console.log(`Remote video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
});
remoteVideo.addEventListener('resize', () => {
console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
// We'll use the first onsize callback as an indication that video has started
// playing out.
if (startTime) {
const elapsedTime = window.performance.now() - startTime;
console.log(`Setup time: ${elapsedTime.toFixed(3)}ms`);
startTime = null;
}
});
let localStream;
let pc1;
let pc2;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
function getName(pc) {
return (pc === pc1) ? 'pc1' : 'pc2';
}
function getOtherPc(pc) {
return (pc === pc1) ? pc2 : pc1;
}
function gotStream(stream) {
console.log('Received local stream');
localVideo.srcObject = stream;
localStream = stream;
callButton.disabled = false;
}
function start() {
console.log('Requesting local stream');
startButton.disabled = true;
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(gotStream)
.catch(e => alert(`getUserMedia() error: ${e.name}`));
}
function call() {
callButton.disabled = true;
hangupButton.disabled = false;
console.log('Starting call');
startTime = window.performance.now();
const videoTracks = localStream.getVideoTracks();
const audioTracks = localStream.getAudioTracks();
if (videoTracks.length > 0) {
console.log(`Using video device: ${videoTracks[0].label}`);
}
if (audioTracks.length > 0) {
console.log(`Using audio device: ${audioTracks[0].label}`);
}
const servers = null;
pc1 = new RTCPeerConnection(servers);
console.log('Created local peer connection object pc1');
pc1.onicecandidate = e => onIceCandidate(pc1, e);
pc2 = new RTCPeerConnection(servers);
console.log('Created remote peer connection object pc2');
pc2.onicecandidate = e => onIceCandidate(pc2, e);
pc1.oniceconnectionstatechange = e => onIceStateChange(pc1, e);
pc2.oniceconnectionstatechange = e => onIceStateChange(pc2, e);
pc2.ontrack = gotRemoteStream;
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
console.log('Added local stream to pc1');
console.log('pc1 createOffer start');
pc1.createOffer(offerOptions).then(onCreateOfferSuccess, onCreateSessionDescriptionError);
}
function onCreateSessionDescriptionError(error) {
console.log(`Failed to create session description: ${error.toString()}`);
}
function onCreateOfferSuccess(desc) {
console.log(`Offer from pc1\n${desc.sdp}`);
console.log('pc1 setLocalDescription start');
pc1.setLocalDescription(desc).then(() => onSetLocalSuccess(pc1), onSetSessionDescriptionError);
console.log('pc2 setRemoteDescription start');
pc2.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc2), onSetSessionDescriptionError);
console.log('pc2 createAnswer start');
// Since the 'remote' side has no media stream we need
// to pass in the right constraints in order for it to
// accept the incoming offer of audio and video.
pc2.createAnswer().then(onCreateAnswerSuccess, onCreateSessionDescriptionError);
}
function onSetLocalSuccess(pc) {
console.log(`${getName(pc)} setLocalDescription complete`);
}
function onSetRemoteSuccess(pc) {
console.log(`${getName(pc)} setRemoteDescription complete`);
}
function onSetSessionDescriptionError(error) {
console.log(`Failed to set session description: ${error.toString()}`);
}
function gotRemoteStream(e) {
if (remoteVideo.srcObject !== e.streams[0]) {
remoteVideo.srcObject = e.streams[0];
console.log('pc2 received remote stream');
const streamVisualizer = new StreamVisualizer(e.streams[0], canvas);
streamVisualizer.start();
}
}
function onCreateAnswerSuccess(desc) {
console.log(`Answer from pc2:\n${desc.sdp}`);
console.log('pc2 setLocalDescription start');
pc2.setLocalDescription(desc).then(() => onSetLocalSuccess(pc2), onSetSessionDescriptionError);
console.log('pc1 setRemoteDescription start');
pc1.setRemoteDescription(desc).then(() => onSetRemoteSuccess(pc1), onSetSessionDescriptionError);
}
function onIceCandidate(pc, event) {
getOtherPc(pc)
.addIceCandidate(event.candidate)
.then(() => onAddIceCandidateSuccess(pc), err => onAddIceCandidateError(pc, err));
console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}
function onAddIceCandidateSuccess(pc) {
console.log(`${getName(pc)} addIceCandidate success`);
}
function onAddIceCandidateError(pc, error) {
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
}
function onIceStateChange(pc, event) {
if (pc) {
console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', event);
}
}
function hangup() {
console.log('Ending call');
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
hangupButton.disabled = true;
callButton.disabled = false;
}
================================================
FILE: src/css/main.css
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
.hidden {
display: none;
}
.highlight {
background-color: #eee;
font-size: 1.2em;
margin: 0 0 30px 0;
padding: 0.2em 1.5em;
}
.warning {
color: red;
font-weight: 400;
}
@media screen and (min-width: 1000px) {
/* hack! to detect non-touch devices */
div#links a {
line-height: 0.8em;
}
}
audio {
max-width: 100%;
}
body {
font-family: 'Roboto', sans-serif;
font-weight: 300;
margin: 0;
padding: 1em;
word-break: break-word;
}
button {
background-color: #d84a38;
border: none;
border-radius: 2px;
color: white;
font-family: 'Roboto', sans-serif;
font-size: 0.8em;
margin: 0 0 1em 0;
padding: 0.5em 0.7em 0.6em 0.7em;
}
button:active {
background-color: #cf402f;
}
button:hover {
background-color: #cf402f;
}
button[disabled] {
color: #ccc;
}
button[disabled]:hover {
background-color: #d84a38;
}
canvas {
background-color: #ccc;
max-width: 100%;
width: 100%;
}
code {
font-family: 'Roboto', sans-serif;
font-weight: 400;
}
div#container {
margin: 0 auto 0 auto;
max-width: 60em;
padding: 1em 1.5em 1.3em 1.5em;
}
div#links {
padding: 0.5em 0 0 0;
}
h1 {
border-bottom: 1px solid #ccc;
font-family: 'Roboto', sans-serif;
font-weight: 500;
margin: 0 0 0.8em 0;
padding: 0 0 0.2em 0;
}
h2 {
color: #444;
font-weight: 500;
}
h3 {
border-top: 1px solid #eee;
color: #666;
font-weight: 500;
margin: 10px 0 10px 0;
white-space: nowrap;
}
li {
margin: 0 0 0.4em 0;
}
html {
/* avoid annoying page width change
when moving from the home page */
overflow-y: scroll;
}
img {
border: none;
max-width: 100%;
}
input[type=radio] {
position: relative;
top: -1px;
}
p {
color: #444;
font-weight: 300;
}
p#data {
border-top: 1px dotted #666;
font-family: Courier New, monospace;
line-height: 1.3em;
max-height: 1000px;
overflow-y: auto;
padding: 1em 0 0 0;
}
p.borderBelow {
border-bottom: 1px solid #aaa;
padding: 0 0 20px 0;
}
section p:last-of-type {
margin: 0;
}
section {
border-bottom: 1px solid #eee;
margin: 0 0 30px 0;
padding: 0 0 20px 0;
}
section:last-of-type {
border-bottom: none;
padding: 0 0 1em 0;
}
select {
margin: 0 1em 1em 0;
position: relative;
top: -1px;
}
h1 span {
white-space: nowrap;
}
a {
color: #1D6EEE;
font-weight: 300;
text-decoration: none;
}
h1 a {
font-weight: 300;
margin: 0 10px 0 0;
white-space: nowrap;
}
a:hover {
color: #3d85c6;
text-decoration: underline;
}
a#viewSource {
display: block;
margin: 1.3em 0 0 0;
border-top: 1px solid #999;
padding: 1em 0 0 0;
}
div#errorMsg p {
color: #F00;
}
div#links a {
display: block;
line-height: 1.3em;
margin: 0 0 1.5em 0;
}
div.outputSelector {
margin: -1.3em 0 2em 0;
}
p.description {
margin: 0 0 0.5em 0;
}
strong {
font-weight: 500;
}
textarea {
resize: none;
font-family: 'Roboto', sans-serif;
}
video {
background: #222;
margin: 0 0 20px 0;
--width: 100%;
width: var(--width);
height: calc(var(--width) * 0.75);
}
ul {
margin: 0 0 0.5em 0;
}
fieldset {
margin: 0 0 1em 0;
}
fieldset > select {
margin-top: 1em;
}
@media screen and (max-width: 650px) {
.highlight {
font-size: 1em;
margin: 0 0 20px 0;
padding: 0.2em 1em;
}
h1 {
font-size: 24px;
}
}
@media screen and (max-width: 550px) {
button:active {
background-color: darkRed;
}
h1 {
font-size: 22px;
}
}
@media screen and (max-width: 450px) {
h1 {
font-size: 20px;
}
}
================================================
FILE: src/js/lib/ga.js
================================================
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject']=r; i[r]=i[r]||function() {
(i[r].q=i[r].q||[]).push(arguments);
}, i[r].l=1*new Date(); a=s.createElement(o),
m=s.getElementsByTagName(o)[0]; a.async=1; a.src=g; m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-48530561-1', 'auto');
ga('send', 'pageview');
================================================
FILE: src/js/third_party/graph.js
================================================
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
// taken from chrome://webrtc-internals with jshint adaptions
'use strict';
/* exported TimelineDataSeries, TimelineGraphView */
// The maximum number of data points bufferred for each stats. Old data points
// will be shifted out when the buffer is full.
const MAX_STATS_DATA_POINT_BUFFER_SIZE = 1000;
const TimelineDataSeries = (function() {
/**
* @constructor
*/
function TimelineDataSeries() {
// List of DataPoints in chronological order.
this.dataPoints_ = [];
// Default color. Should always be overridden prior to display.
this.color_ = 'red';
// Whether or not the data series should be drawn.
this.isVisible_ = true;
this.cacheStartTime_ = null;
this.cacheStepSize_ = 0;
this.cacheValues_ = [];
}
TimelineDataSeries.prototype = {
/**
* @override
*/
toJSON: function() {
if (this.dataPoints_.length < 1) {
return {};
}
let values = [];
for (let i = 0; i < this.dataPoints_.length; ++i) {
values.push(this.dataPoints_[i].value);
}
return {
startTime: this.dataPoints_[0].time,
endTime: this.dataPoints_[this.dataPoints_.length - 1].time,
values: JSON.stringify(values),
};
},
/**
* Adds a DataPoint to |this| with the specified time and value.
* DataPoints are assumed to be received in chronological order.
*/
addPoint: function(timeTicks, value) {
let time = new Date(timeTicks);
this.dataPoints_.push(new DataPoint(time, value));
if (this.dataPoints_.length > MAX_STATS_DATA_POINT_BUFFER_SIZE) {
this.dataPoints_.shift();
}
},
isVisible: function() {
return this.isVisible_;
},
show: function(isVisible) {
this.isVisible_ = isVisible;
},
getColor: function() {
return this.color_;
},
setColor: function(color) {
this.color_ = color;
},
getCount: function() {
return this.dataPoints_.length;
},
/**
* Returns a list containing the values of the data series at |count|
* points, starting at |startTime|, and |stepSize| milliseconds apart.
* Caches values, so showing/hiding individual data series is fast.
*/
getValues: function(startTime, stepSize, count) {
// Use cached values, if we can.
if (this.cacheStartTime_ === startTime &&
this.cacheStepSize_ === stepSize &&
this.cacheValues_.length === count) {
return this.cacheValues_;
}
// Do all the work.
this.cacheValues_ = this.getValuesInternal_(startTime, stepSize, count);
this.cacheStartTime_ = startTime;
this.cacheStepSize_ = stepSize;
return this.cacheValues_;
},
/**
* Returns the cached |values| in the specified time period.
*/
getValuesInternal_: function(startTime, stepSize, count) {
let values = [];
let nextPoint = 0;
let currentValue = 0;
let time = startTime;
for (let i = 0; i < count; ++i) {
while (nextPoint < this.dataPoints_.length &&
this.dataPoints_[nextPoint].time < time) {
currentValue = this.dataPoints_[nextPoint].value;
++nextPoint;
}
values[i] = currentValue;
time += stepSize;
}
return values;
}
};
/**
* A single point in a data series. Each point has a time, in the form of
* milliseconds since the Unix epoch, and a numeric value.
* @constructor
*/
function DataPoint(time, value) {
this.time = time;
this.value = value;
}
return TimelineDataSeries;
})();
const TimelineGraphView = (function() {
// Maximum number of labels placed vertically along the sides of the graph.
let MAX_VERTICAL_LABELS = 6;
// Vertical spacing between labels and between the graph and labels.
let LABEL_VERTICAL_SPACING = 4;
// Horizontal spacing between vertically placed labels and the edges of the
// graph.
let LABEL_HORIZONTAL_SPACING = 3;
// Horizintal spacing between two horitonally placed labels along the bottom
// of the graph.
// var LABEL_LABEL_HORIZONTAL_SPACING = 25;
// Length of ticks, in pixels, next to y-axis labels. The x-axis only has
// one set of labels, so it can use lines instead.
let Y_AXIS_TICK_LENGTH = 10;
let GRID_COLOR = '#CCC';
let TEXT_COLOR = '#000';
let BACKGROUND_COLOR = '#FFF';
let MAX_DECIMAL_PRECISION = 3;
/**
* @constructor
*/
function TimelineGraphView(divId, canvasId) {
this.scrollbar_ = {position_: 0, range_: 0};
this.graphDiv_ = document.getElementById(divId);
this.canvas_ = document.getElementById(canvasId);
// Set the range and scale of the graph. Times are in milliseconds since
// the Unix epoch.
// All measurements we have must be after this time.
this.startTime_ = 0;
// The current rightmost position of the graph is always at most this.
this.endTime_ = 1;
this.graph_ = null;
// Horizontal scale factor, in terms of milliseconds per pixel.
this.scale_ = 1000;
// Initialize the scrollbar.
this.updateScrollbarRange_(true);
}
TimelineGraphView.prototype = {
setScale: function(scale) {
this.scale_ = scale;
},
// Returns the total length of the graph, in pixels.
getLength_: function() {
let timeRange = this.endTime_ - this.startTime_;
// Math.floor is used to ignore the last partial area, of length less
// than this.scale_.
return Math.floor(timeRange / this.scale_);
},
/**
* Returns true if the graph is scrolled all the way to the right.
*/
graphScrolledToRightEdge_: function() {
return this.scrollbar_.position_ === this.scrollbar_.range_;
},
/**
* Update the range of the scrollbar. If |resetPosition| is true, also
* sets the slider to point at the rightmost position and triggers a
* repaint.
*/
updateScrollbarRange_: function(resetPosition) {
let scrollbarRange = this.getLength_() - this.canvas_.width;
if (scrollbarRange < 0) {
scrollbarRange = 0;
}
// If we've decreased the range to less than the current scroll position,
// we need to move the scroll position.
if (this.scrollbar_.position_ > scrollbarRange) {
resetPosition = true;
}
this.scrollbar_.range_ = scrollbarRange;
if (resetPosition) {
this.scrollbar_.position_ = scrollbarRange;
this.repaint();
}
},
/**
* Sets the date range displayed on the graph, switches to the default
* scale factor, and moves the scrollbar all the way to the right.
*/
setDateRange: function(startDate, endDate) {
this.startTime_ = startDate.getTime();
this.endTime_ = endDate.getTime();
// Safety check.
if (this.endTime_ <= this.startTime_) {
this.startTime_ = this.endTime_ - 1;
}
this.updateScrollbarRange_(true);
},
/**
* Updates the end time at the right of the graph to be the current time.
* Specifically, updates the scrollbar's range, and if the scrollbar is
* all the way to the right, keeps it all the way to the right. Otherwise,
* leaves the view as-is and doesn't redraw anything.
*/
updateEndDate: function(optDate) {
this.endTime_ = optDate || (new Date()).getTime();
this.updateScrollbarRange_(this.graphScrolledToRightEdge_());
},
getStartDate: function() {
return new Date(this.startTime_);
},
/**
* Replaces the current TimelineDataSeries with |dataSeries|.
*/
setDataSeries: function(dataSeries) {
// Simply recreates the Graph.
this.graph_ = new Graph();
for (let i = 0; i < dataSeries.length; ++i) {
this.graph_.addDataSeries(dataSeries[i]);
}
this.repaint();
},
/**
* Adds |dataSeries| to the current graph.
*/
addDataSeries: function(dataSeries) {
if (!this.graph_) {
this.graph_ = new Graph();
}
this.graph_.addDataSeries(dataSeries);
this.repaint();
},
/**
* Draws the graph on |canvas_|.
*/
repaint: function() {
this.repaintTimerRunning_ = false;
let width = this.canvas_.width;
let height = this.canvas_.height;
let context = this.canvas_.getContext('2d');
// Clear the canvas.
context.fillStyle = BACKGROUND_COLOR;
context.fillRect(0, 0, width, height);
// Try to get font height in pixels. Needed for layout.
let fontHeightString = context.font.match(/([0-9]+)px/)[1];
let fontHeight = parseInt(fontHeightString);
// Safety check, to avoid drawing anything too ugly.
if (fontHeightString.length === 0 || fontHeight <= 0 ||
fontHeight * 4 > height || width < 50) {
return;
}
// Save current transformation matrix so we can restore it later.
context.save();
// The center of an HTML canvas pixel is technically at (0.5, 0.5). This
// makes near straight lines look bad, due to anti-aliasing. This
// translation reduces the problem a little.
context.translate(0.5, 0.5);
// Figure out what time values to display.
let position = this.scrollbar_.position_;
// If the entire time range is being displayed, align the right edge of
// the graph to the end of the time range.
if (this.scrollbar_.range_ === 0) {
position = this.getLength_() - this.canvas_.width;
}
let visibleStartTime = this.startTime_ + position * this.scale_;
// Make space at the bottom of the graph for the time labels, and then
// draw the labels.
let textHeight = height;
height -= fontHeight + LABEL_VERTICAL_SPACING;
this.drawTimeLabels(context, width, height, textHeight, visibleStartTime);
// Draw outline of the main graph area.
context.strokeStyle = GRID_COLOR;
context.strokeRect(0, 0, width - 1, height - 1);
if (this.graph_) {
// Layout graph and have them draw their tick marks.
this.graph_.layout(
width, height, fontHeight, visibleStartTime, this.scale_);
this.graph_.drawTicks(context);
// Draw the lines of all graphs, and then draw their labels.
this.graph_.drawLines(context);
this.graph_.drawLabels(context);
}
// Restore original transformation matrix.
context.restore();
},
/**
* Draw time labels below the graph. Takes in start time as an argument
* since it may not be |startTime_|, when we're displaying the entire
* time range.
*/
drawTimeLabels: function(context, width, height, textHeight, startTime) {
// Draw the labels 1 minute apart.
let timeStep = 1000 * 60;
// Find the time for the first label. This time is a perfect multiple of
// timeStep because of how UTC times work.
let time = Math.ceil(startTime / timeStep) * timeStep;
context.textBaseline = 'bottom';
context.textAlign = 'center';
context.fillStyle = TEXT_COLOR;
context.strokeStyle = GRID_COLOR;
// Draw labels and vertical grid lines.
while (true) {
let x = Math.round((time - startTime) / this.scale_);
if (x >= width) {
break;
}
let text = (new Date(time)).toLocaleTimeString();
context.fillText(text, x, textHeight);
context.beginPath();
context.lineTo(x, 0);
context.lineTo(x, height);
context.stroke();
time += timeStep;
}
},
getDataSeriesCount: function() {
if (this.graph_) {
return this.graph_.dataSeries_.length;
}
return 0;
},
hasDataSeries: function(dataSeries) {
if (this.graph_) {
return this.graph_.hasDataSeries(dataSeries);
}
return false;
},
};
/**
* A Graph is responsible for drawing all the TimelineDataSeries that have
* the same data type. Graphs are responsible for scaling the values, laying
* out labels, and drawing both labels and lines for its data series.
*/
const Graph = (function() {
/**
* @constructor
*/
function Graph() {
this.dataSeries_ = [];
// Cached properties of the graph, set in layout.
this.width_ = 0;
this.height_ = 0;
this.fontHeight_ = 0;
this.startTime_ = 0;
this.scale_ = 0;
// The lowest/highest values adjusted by the vertical label step size
// in the displayed range of the graph. Used for scaling and setting
// labels. Set in layoutLabels.
this.min_ = 0;
this.max_ = 0;
// Cached text of equally spaced labels. Set in layoutLabels.
this.labels_ = [];
}
/**
* A Label is the label at a particular position along the y-axis.
* @constructor
*/
/*
function Label(height, text) {
this.height = height;
this.text = text;
}
*/
Graph.prototype = {
addDataSeries: function(dataSeries) {
this.dataSeries_.push(dataSeries);
},
hasDataSeries: function(dataSeries) {
for (let i = 0; i < this.dataSeries_.length; ++i) {
if (this.dataSeries_[i] === dataSeries) {
return true;
}
}
return false;
},
/**
* Returns a list of all the values that should be displayed for a given
* data series, using the current graph layout.
*/
getValues: function(dataSeries) {
if (!dataSeries.isVisible()) {
return null;
}
return dataSeries.getValues(this.startTime_, this.scale_, this.width_);
},
/**
* Updates the graph's layout. In particular, both the max value and
* label positions are updated. Must be called before calling any of the
* drawing functions.
*/
layout: function(width, height, fontHeight, startTime, scale) {
this.width_ = width;
this.height_ = height;
this.fontHeight_ = fontHeight;
this.startTime_ = startTime;
this.scale_ = scale;
// Find largest value.
let max = 0;
let min = 0;
for (let i = 0; i < this.dataSeries_.length; ++i) {
let values = this.getValues(this.dataSeries_[i]);
if (!values) {
continue;
}
for (let j = 0; j < values.length; ++j) {
if (values[j] > max) {
max = values[j];
} else if (values[j] < min) {
min = values[j];
}
}
}
this.layoutLabels_(min, max);
},
/**
* Lays out labels and sets |max_|/|min_|, taking the time units into
* consideration. |maxValue| is the actual maximum value, and
* |max_| will be set to the value of the largest label, which
* will be at least |maxValue|. Similar for |min_|.
*/
layoutLabels_: function(minValue, maxValue) {
if (maxValue - minValue < 1024) {
this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
return;
}
// Find appropriate units to use.
let units = ['', 'k', 'M', 'G', 'T', 'P'];
// Units to use for labels. 0 is '1', 1 is K, etc.
// We start with 1, and work our way up.
let unit = 1;
minValue /= 1024;
maxValue /= 1024;
while (units[unit + 1] && maxValue - minValue >= 1024) {
minValue /= 1024;
maxValue /= 1024;
++unit;
}
// Calculate labels.
this.layoutLabelsBasic_(minValue, maxValue, MAX_DECIMAL_PRECISION);
// Append units to labels.
for (let i = 0; i < this.labels_.length; ++i) {
this.labels_[i] += ' ' + units[unit];
}
// Convert |min_|/|max_| back to unit '1'.
this.min_ *= Math.pow(1024, unit);
this.max_ *= Math.pow(1024, unit);
},
/**
* Same as layoutLabels_, but ignores units. |maxDecimalDigits| is the
* maximum number of decimal digits allowed. The minimum allowed
* difference between two adjacent labels is 10^-|maxDecimalDigits|.
*/
layoutLabelsBasic_: function(minValue, maxValue, maxDecimalDigits) {
this.labels_ = [];
let range = maxValue - minValue;
// No labels if the range is 0.
if (range === 0) {
this.min_ = this.max_ = maxValue;
return;
}
// The maximum number of equally spaced labels allowed. |fontHeight_|
// is doubled because the top two labels are both drawn in the same
// gap.
let minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING;
// The + 1 is for the top label.
let maxLabels = 1 + this.height_ / minLabelSpacing;
if (maxLabels < 2) {
maxLabels = 2;
} else if (maxLabels > MAX_VERTICAL_LABELS) {
maxLabels = MAX_VERTICAL_LABELS;
}
// Initial try for step size between conecutive labels.
let stepSize = Math.pow(10, -maxDecimalDigits);
// Number of digits to the right of the decimal of |stepSize|.
// Used for formating label strings.
let stepSizeDecimalDigits = maxDecimalDigits;
// Pick a reasonable step size.
while (true) {
// If we use a step size of |stepSize| between labels, we'll need:
//
// Math.ceil(range / stepSize) + 1
//
// labels. The + 1 is because we need labels at both at 0 and at
// the top of the graph.
// Check if we can use steps of size |stepSize|.
if (Math.ceil(range / stepSize) + 1 <= maxLabels) {
break;
}
// Check |stepSize| * 2.
if (Math.ceil(range / (stepSize * 2)) + 1 <= maxLabels) {
stepSize *= 2;
break;
}
// Check |stepSize| * 5.
if (Math.ceil(range / (stepSize * 5)) + 1 <= maxLabels) {
stepSize *= 5;
break;
}
stepSize *= 10;
if (stepSizeDecimalDigits > 0) {
--stepSizeDecimalDigits;
}
}
// Set the min/max so it's an exact multiple of the chosen step size.
this.max_ = Math.ceil(maxValue / stepSize) * stepSize;
this.min_ = Math.floor(minValue / stepSize) * stepSize;
// Create labels.
for (let label = this.max_; label >= this.min_; label -= stepSize) {
this.labels_.push(label.toFixed(stepSizeDecimalDigits));
}
},
/**
* Draws tick marks for each of the labels in |labels_|.
*/
drawTicks: function(context) {
let x1;
let x2;
x1 = this.width_ - 1;
x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH;
context.fillStyle = GRID_COLOR;
context.beginPath();
for (let i = 1; i < this.labels_.length - 1; ++i) {
// The rounding is needed to avoid ugly 2-pixel wide anti-aliased
// lines.
let y = Math.round(this.height_ * i / (this.labels_.length - 1));
context.moveTo(x1, y);
context.lineTo(x2, y);
}
context.stroke();
},
/**
* Draws a graph line for each of the data series.
*/
drawLines: function(context) {
// Factor by which to scale all values to convert them to a number from
// 0 to height - 1.
let scale = 0;
let bottom = this.height_ - 1;
if (this.max_) {
scale = bottom / (this.max_ - this.min_);
}
// Draw in reverse order, so earlier data series are drawn on top of
// subsequent ones.
for (let i = this.dataSeries_.length - 1; i >= 0; --i) {
let values = this.getValues(this.dataSeries_[i]);
if (!values) {
continue;
}
context.strokeStyle = this.dataSeries_[i].getColor();
context.beginPath();
for (let x = 0; x < values.length; ++x) {
// The rounding is needed to avoid ugly 2-pixel wide anti-aliased
// horizontal lines.
context.lineTo(
x, bottom - Math.round((values[x] - this.min_) * scale));
}
context.stroke();
}
},
/**
* Draw labels in |labels_|.
*/
drawLabels: function(context) {
if (this.labels_.length === 0) {
return;
}
let x = this.width_ - LABEL_HORIZONTAL_SPACING;
// Set up the context.
context.fillStyle = TEXT_COLOR;
context.textAlign = 'right';
// Draw top label, which is the only one that appears below its tick
// mark.
context.textBaseline = 'top';
context.fillText(this.labels_[0], x, 0);
// Draw all the other labels.
context.textBaseline = 'bottom';
let step = (this.height_ - 1) / (this.labels_.length - 1);
for (let i = 1; i < this.labels_.length; ++i) {
context.fillText(this.labels_[i], x, step * i);
}
}
};
return Graph;
})();
return TimelineGraphView;
})();
================================================
FILE: src/js/third_party/streamvisualizer.js
================================================
/*
* Copyright 2016 Boris Smus. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Adapted from Boris Smus's demo at http://webaudioapi.com/samples/visualizer
/* globals AudioContext, webkitAudioContext */
const WIDTH = 308;
const HEIGHT = 231;
// Interesting parameters to tweak!
const SMOOTHING = 0.8;
const FFT_SIZE = 2048;
function StreamVisualizer(remoteStream, canvas) {
console.log('Creating StreamVisualizer with remoteStream and canvas: ',
remoteStream, canvas);
this.canvas = canvas;
this.drawContext = this.canvas.getContext('2d');
// cope with browser differences
if (typeof AudioContext === 'function') {
this.context = new AudioContext();
} else if (typeof webkitAudioContext === 'function') {
this.context = new webkitAudioContext(); // eslint-disable-line new-cap
} else {
alert('Sorry! Web Audio is not supported by this browser');
}
// Create a MediaStreamAudioSourceNode from the remoteStream
this.source = this.context.createMediaStreamSource(remoteStream);
console.log('Created Web Audio source from remote stream: ', this.source);
this.analyser = this.context.createAnalyser();
// this.analyser.connect(this.context.destination);
this.analyser.minDecibels = -140;
this.analyser.maxDecibels = 0;
this.freqs = new Uint8Array(this.analyser.frequencyBinCount);
this.times = new Uint8Array(this.analyser.frequencyBinCount);
this.source.connect(this.analyser);
this.startTime = 0;
this.startOffset = 0;
}
StreamVisualizer.prototype.start = function() {
requestAnimationFrame(this.draw.bind(this));
};
StreamVisualizer.prototype.draw = function() {
let barWidth;
let offset;
let height;
let percent;
let value;
this.analyser.smoothingTimeConstant = SMOOTHING;
this.analyser.fftSize = FFT_SIZE;
// Get the frequency data from the currently playing music
this.analyser.getByteFrequencyData(this.freqs);
this.analyser.getByteTimeDomainData(this.times);
this.canvas.width = WIDTH;
this.canvas.height = HEIGHT;
// Draw the frequency domain chart.
for (let i = 0; i < this.analyser.frequencyBinCount; i++) {
value = this.freqs[i];
percent = value / 256;
height = HEIGHT * percent;
offset = HEIGHT - height - 1;
barWidth = WIDTH / this.analyser.frequencyBinCount;
let hue = i/this.analyser.frequencyBinCount * 360;
this.drawContext.fillStyle = 'hsl(' + hue + ', 100%, 50%)';
this.drawContext.fillRect(i * barWidth, offset, barWidth, height);
}
// Draw the time domain chart.
for (let i = 0; i < this.analyser.frequencyBinCount; i++) {
value = this.times[i];
percent = value / 256;
height = HEIGHT * percent;
offset = HEIGHT - height - 1;
barWidth = WIDTH/this.analyser.frequencyBinCount;
this.drawContext.fillStyle = 'white';
this.drawContext.fillRect(i * barWidth, offset, 1, 2);
}
requestAnimationFrame(this.draw.bind(this));
};
StreamVisualizer.prototype.getFrequencyValue = function(freq) {
let nyquist = this.context.sampleRate/2;
let index = Math.round(freq/nyquist * this.freqs.length);
return this.freqs[index];
};
================================================
FILE: src/js/third_party/webgl_teapot/cameracontroller.js
================================================
/*
* Copyright (c) 2009 The Chromium Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// A simple camera controller which uses an HTML element as the event
// source for constructing a view matrix. Assign an "onchange"
// function to the controller as follows to receive the updated X and
// Y angles for the camera:
//
// var controller = new CameraController(canvas);
// controller.onchange = function(xRot, yRot) { ... };
//
// The view matrix is computed elsewhere.
//
// opt_canvas (an HTMLCanvasElement) and opt_context (a
// WebGLRenderingContext) can be passed in to make the hit detection
// more precise -- only opaque pixels will be considered as the start
// of a drag action.
function CameraController(element, opt_canvas, opt_context) {
var controller = this;
this.onchange = null;
this.xRot = 0;
this.yRot = 0;
this.scaleFactor = 3.0;
this.dragging = false;
this.curX = 0;
this.curY = 0;
if (opt_canvas)
this.canvas_ = opt_canvas;
if (opt_context)
this.context_ = opt_context;
function mouseDown(ev) {
controller.curX = ev.clientX;
controller.curY = ev.clientY;
var dragging = false;
if (controller.canvas_ && controller.context_) {
var rect = controller.canvas_.getBoundingClientRect();
// Transform the event's x and y coordinates into the coordinate
// space of the canvas
var canvasRelativeX = ev.pageX - rect.left;
var canvasRelativeY = ev.pageY - rect.top;
var canvasWidth = controller.canvas_.width;
var canvasHeight = controller.canvas_.height;
// Read back a small portion of the frame buffer around this point
if (canvasRelativeX > 0 && canvasRelativeX < canvasWidth &&
canvasRelativeY > 0 && canvasRelativeY < canvasHeight) {
var pixels = new Uint8Array(1);
controller.context_.readPixels(canvasRelativeX,
canvasHeight - canvasRelativeY,
1,
1,
controller.context_.RGBA,
controller.context_.UNSIGNED_BYTE,
pixels);
// See whether this pixel has an alpha value of >= about 10%
if (pixels[3] > (255.0 / 10.0)) {
dragging = true;
}
}
} else {
dragging = true;
}
controller.dragging = dragging;
}
function mouseMove(ev) {
if (controller.dragging) {
// Determine how far we have moved since the last mouse move
// event.
var curX = ev.clientX;
var curY = ev.clientY;
var deltaX = (controller.curX - curX) / controller.scaleFactor;
var deltaY = (controller.curY - curY) / controller.scaleFactor;
controller.curX = curX;
controller.curY = curY;
// Update the X and Y rotation angles based on the mouse motion.
controller.yRot = (controller.yRot + deltaX) % 360;
controller.xRot = (controller.xRot + deltaY);
// Clamp the X rotation to prevent the camera from going upside
// down.
if (controller.xRot < -90) {
controller.xRot = -90;
} else if (controller.xRot > 90) {
controller.xRot = 90;
}
// Send the onchange event to any listener.
if (controller.onchange != null) {
controller.onchange(controller.xRot, controller.yRot);
}
}
}
function mouseUp(ev) {
controller.dragging = false;
}
element.addEventListener("mousedown", mouseDown, false);
element.addEventListener("mousemove", mouseMove, false);
element.addEventListener("mouseup", mouseUp, false);
var activeTouchIdentifier;
function findActiveTouch(touches) {
for (var ii = 0; ii < touches.length; ++ii) {
if (touches.item(ii).identifier == activeTouchIdentifier) {
return touches.item(ii);
}
}
return null;
}
function touchStart(ev) {
if (controller.dragging || ev.targetTouches.length == 0) {
return;
}
var touch = ev.targetTouches.item(0);
mouseDown(touch);
if (controller.dragging) {
activeTouchIdentifier = touch.identifier;
}
ev.preventDefault();
}
function touchMove(ev) {
if (!controller.dragging) {
return;
}
var touch = findActiveTouch(ev.changedTouches);
if (touch) {
mouseMove(touch);
}
ev.preventDefault();
}
function touchEnd(ev) {
var touch = findActiveTouch(ev.changedTouches);
if (touch) {
mouseUp(touch);
}
ev.preventDefault();
}
element.addEventListener("touchstart", touchStart, false);
element.addEventListener("touchmove", touchMove, false);
element.addEventListener("touchend", touchEnd, false);
element.addEventListener("touchcancel", touchEnd, false);
}
================================================
FILE: src/js/third_party/webgl_teapot/demo.js
================================================
/*
* Copyright (c) 2009 The Chromium Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var gl = null;
var g_width = 0;
var g_height = 0;
var g_bumpTexture = null;
var g_envTexture = null;
var g_programObject = null;
var g_vbo = null;
var g_elementVbo = null;
var g_normalsOffset = 0;
var g_tangentsOffset = 0;
var g_binormalsOffset = 0;
var g_texCoordsOffset = 0;
var g_numElements = 0;
// Uniform variables
var g_worldLoc = 0;
var g_worldInverseTransposeLoc = 0;
var g_worldViewProjLoc = 0;
var g_viewInverseLoc = 0;
var g_normalSamplerLoc = 0;
var g_envSamplerLoc = 0;
var g_pendingTextureLoads = 0;
// The "model" matrix is the "world" matrix in Standard Annotations
// and Semantics
var model = new Matrix4x4();
var view = new Matrix4x4();
var projection = new Matrix4x4();
var controller = null;
function main() {
var c = document.querySelector("canvas");
//c = WebGLDebugUtils.makeLostContextSimulatingCanvas(c);
// tell the simulator when to lose context.
//c.loseContextInNCalls(15);
c.addEventListener('webglcontextlost', handleContextLost, false);
c.addEventListener('webglcontextrestored', handleContextRestored, false);
var ratio = window.devicePixelRatio ? window.devicePixelRatio : 1;
// original is 480 x 270
c.width = 240 * ratio;
c.height = 180 * ratio;
gl = WebGLUtils.setupWebGL(c);
if (!gl)
return;
g_width = c.width;
g_height = c.height;
controller = new CameraController(c);
// Try the following (and uncomment the "pointer-events: none;" in
// the index.html) to try the more precise hit detection
// controller = new CameraController(document.getElementById("body"), c, gl);
controller.onchange = function(xRot, yRot) {
draw();
};
init();
}
function log(msg) {
if (window.console && window.console.log) {
console.log(msg);
}
}
function handleContextLost(e) {
log("handle context lost");
e.preventDefault();
clearLoadingImages();
}
function handleContextRestored() {
log("handle context restored");
init();
}
function output(str) {
document.body.appendChild(document.createTextNode(str));
document.body.appendChild(document.createElement("br"));
}
function checkGLError() {
var error = gl.getError();
if (error != gl.NO_ERROR && error != gl.CONTEXT_LOST_WEBGL) {
var str = "GL Error: " + error;
output(str);
throw str;
}
}
function init() {
gl.enable(gl.DEPTH_TEST);
// Can use this to make the background opaque
// gl.clearColor(0.3, 0.2, 0.2, 1.);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
initTeapot();
initShaders();
g_bumpTexture = loadTexture("../../../js/third_party/webgl_teapot/images/bump.jpg");
g_envTexture = loadCubeMap("../../../js/third_party/webgl_teapot/images/skybox", "jpg");
draw();
}
function initTeapot() {
g_vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, g_vbo);
gl.bufferData(gl.ARRAY_BUFFER,
teapotPositions.byteLength +
teapotNormals.byteLength +
teapotTangents.byteLength +
teapotBinormals.byteLength +
teapotTexCoords.byteLength,
gl.STATIC_DRAW);
g_normalsOffset = teapotPositions.byteLength;
g_tangentsOffset = g_normalsOffset + teapotNormals.byteLength;
g_binormalsOffset = g_tangentsOffset + teapotTangents.byteLength;
g_texCoordsOffset = g_binormalsOffset + teapotBinormals.byteLength;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, teapotPositions);
gl.bufferSubData(gl.ARRAY_BUFFER, g_normalsOffset, teapotNormals);
gl.bufferSubData(gl.ARRAY_BUFFER, g_tangentsOffset, teapotTangents);
gl.bufferSubData(gl.ARRAY_BUFFER, g_binormalsOffset, teapotBinormals);
gl.bufferSubData(gl.ARRAY_BUFFER, g_texCoordsOffset, teapotTexCoords);
g_elementVbo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_elementVbo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, teapotIndices, gl.STATIC_DRAW);
g_numElements = teapotIndices.length;
}
var bumpReflectVertexSource = [
"attribute vec3 g_Position;",
"attribute vec3 g_TexCoord0;",
"attribute vec3 g_Tangent;",
"attribute vec3 g_Binormal;",
"attribute vec3 g_Normal;",
"",
"uniform mat4 world;",
"uniform mat4 worldInverseTranspose;",
"uniform mat4 worldViewProj;",
"uniform mat4 viewInverse;",
"",
"varying vec2 texCoord;",
"varying vec3 worldEyeVec;",
"varying vec3 worldNormal;",
"varying vec3 worldTangent;",
"varying vec3 worldBinorm;",
"",
"void main() {",
" gl_Position = worldViewProj * vec4(g_Position.xyz, 1.);",
" texCoord.xy = g_TexCoord0.xy;",
" worldNormal = (worldInverseTranspose * vec4(g_Normal, 1.)).xyz;",
" worldTangent = (worldInverseTranspose * vec4(g_Tangent, 1.)).xyz;",
" worldBinorm = (worldInverseTranspose * vec4(g_Binormal, 1.)).xyz;",
" vec3 worldPos = (world * vec4(g_Position, 1.)).xyz;",
" worldEyeVec = normalize(worldPos - viewInverse[3].xyz);",
"}"
].join("\n");
var bumpReflectFragmentSource = [
"precision mediump float;\n",
"const float bumpHeight = 0.2;",
"",
"uniform sampler2D normalSampler;",
"uniform samplerCube envSampler;",
"",
"varying vec2 texCoord;",
"varying vec3 worldEyeVec;",
"varying vec3 worldNormal;",
"varying vec3 worldTangent;",
"varying vec3 worldBinorm;",
"",
"void main() {",
" vec2 bump = (texture2D(normalSampler, texCoord.xy).xy * 2.0 - 1.0) * bumpHeight;",
" vec3 normal = normalize(worldNormal);",
" vec3 tangent = normalize(worldTangent);",
" vec3 binormal = normalize(worldBinorm);",
" vec3 nb = normal + bump.x * tangent + bump.y * binormal;",
" nb = normalize(nb);",
" vec3 worldEye = normalize(worldEyeVec);",
" vec3 lookup = reflect(worldEye, nb);",
" vec4 color = textureCube(envSampler, lookup);",
" gl_FragColor = color;",
"}"
].join("\n");
function loadShader(type, shaderSrc) {
var shader = gl.createShader(type);
// Load the shader source
gl.shaderSource(shader, shaderSrc);
// Compile the shader
gl.compileShader(shader);
// Check the compile status
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS) &&
!gl.isContextLost()) {
var infoLog = gl.getShaderInfoLog(shader);
output("Error compiling shader:\n" + infoLog);
gl.deleteShader(shader);
return null;
}
return shader;
}
function initShaders() {
var vertexShader = loadShader(gl.VERTEX_SHADER, bumpReflectVertexSource);
var fragmentShader = loadShader(gl.FRAGMENT_SHADER, bumpReflectFragmentSource);
// Create the program object
var programObject = gl.createProgram();
gl.attachShader(programObject, vertexShader);
gl.attachShader(programObject, fragmentShader);
// Bind attributes
gl.bindAttribLocation(programObject, 0, "g_Position");
gl.bindAttribLocation(programObject, 1, "g_TexCoord0");
gl.bindAttribLocation(programObject, 2, "g_Tangent");
gl.bindAttribLocation(programObject, 3, "g_Binormal");
gl.bindAttribLocation(programObject, 4, "g_Normal");
// Link the program
gl.linkProgram(programObject);
// Check the link status
var linked = gl.getProgramParameter(programObject, gl.LINK_STATUS);
if (!linked && !gl.isContextLost()) {
var infoLog = gl.getProgramInfoLog(programObject);
output("Error linking program:\n" + infoLog);
gl.deleteProgram(programObject);
return;
}
g_programObject = programObject;
// Look up uniform locations
g_worldLoc = gl.getUniformLocation(g_programObject, "world");
g_worldInverseTransposeLoc = gl.getUniformLocation(g_programObject, "worldInverseTranspose");
g_worldViewProjLoc = gl.getUniformLocation(g_programObject, "worldViewProj");
g_viewInverseLoc = gl.getUniformLocation(g_programObject, "viewInverse");
g_normalSamplerLoc = gl.getUniformLocation(g_programObject, "normalSampler");
g_envSamplerLoc = gl.getUniformLocation(g_programObject, "envSampler");
checkGLError();
}
function draw() {
// Note: the viewport is automatically set up to cover the entire Canvas.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
checkGLError();
// For now, don't render if we have incomplete textures, just to
// avoid accidentally incurring OpenGL errors -- although we should
// be fully able to load textures in in the background
if (g_pendingTextureLoads > 0) {
return;
}
// Set up the model, view and projection matrices
projection.loadIdentity();
projection.perspective(45, g_width / g_height, 10, 500);
view.loadIdentity();
view.translate(0, -10, -100.0);
// Add in camera controller's rotation
model.loadIdentity();
model.rotate(controller.xRot, 1, 0, 0);
model.rotate(controller.yRot, 0, 1, 0);
// Correct for initial placement and orientation of model
model.translate(0, -10, 0);
model.rotate(90, 1, 0, 0);
gl.useProgram(g_programObject);
// Compute necessary matrices
var mvp = new Matrix4x4();
mvp.multiply(model);
mvp.multiply(view);
mvp.multiply(projection);
var worldInverseTranspose = model.inverse();
worldInverseTranspose.transpose();
var viewInverse = view.inverse();
// Set up uniforms
gl.uniformMatrix4fv(g_worldLoc, gl.FALSE, new Float32Array(model.elements));
gl.uniformMatrix4fv(g_worldInverseTransposeLoc, gl.FALSE, new Float32Array(worldInverseTranspose.elements));
gl.uniformMatrix4fv(g_worldViewProjLoc, gl.FALSE, new Float32Array(mvp.elements));
gl.uniformMatrix4fv(g_viewInverseLoc, gl.FALSE, new Float32Array(viewInverse.elements));
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, g_bumpTexture);
gl.uniform1i(g_normalSamplerLoc, 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, g_envTexture);
gl.uniform1i(g_envSamplerLoc, 1);
checkGLError();
// Bind and set up vertex streams
gl.bindBuffer(gl.ARRAY_BUFFER, g_vbo);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, g_texCoordsOffset);
gl.enableVertexAttribArray(1);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, g_tangentsOffset);
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(3, 3, gl.FLOAT, false, 0, g_binormalsOffset);
gl.enableVertexAttribArray(3);
gl.vertexAttribPointer(4, 3, gl.FLOAT, false, 0, g_normalsOffset);
gl.enableVertexAttribArray(4);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g_elementVbo);
checkGLError();
gl.drawElements(gl.TRIANGLES, g_numElements, gl.UNSIGNED_SHORT, 0);
}
// Array of images curently loading
var g_loadingImages = [];
// Clears all the images currently loading.
// This is used to handle context lost events.
function clearLoadingImages() {
for (var ii = 0; ii < g_loadingImages.length; ++ii) {
g_loadingImages[ii].onload = undefined;
}
g_loadingImages = [];
}
function loadTexture(src) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
++g_pendingTextureLoads;
var image = new Image();
g_loadingImages.push(image);
image.onload = function() {
g_loadingImages.splice(g_loadingImages.indexOf(image), 1);
--g_pendingTextureLoads;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
checkGLError();
draw();
};
image.src = src;
return texture;
}
function loadCubeMap(base, suffix) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
checkGLError();
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
checkGLError();
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
checkGLError();
// FIXME: TEXTURE_WRAP_R doesn't exist in OpenGL ES?!
// gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
// checkGLError();
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
checkGLError();
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
checkGLError();
var faces = [["posx", gl.TEXTURE_CUBE_MAP_POSITIVE_X],
["negx", gl.TEXTURE_CUBE_MAP_NEGATIVE_X],
["posy", gl.TEXTURE_CUBE_MAP_POSITIVE_Y],
["negy", gl.TEXTURE_CUBE_MAP_NEGATIVE_Y],
["posz", gl.TEXTURE_CUBE_MAP_POSITIVE_Z],
["negz", gl.TEXTURE_CUBE_MAP_NEGATIVE_Z]];
for (var i = 0; i < faces.length; i++) {
var url = base + "-" + faces[i][0] + "." + suffix;
var face = faces[i][1];
++g_pendingTextureLoads;
var image = new Image();
g_loadingImages.push(image);
// Javascript has function, not block, scope.
// See "JavaScript: The Good Parts", Chapter 4, "Functions",
// section "Scope".
image.onload = function(texture, face, image, url) {
return function() {
g_loadingImages.splice(g_loadingImages.indexOf(image), 1);
--g_pendingTextureLoads;
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
gl.texImage2D(
face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
checkGLError();
draw();
}
}(texture, face, image, url);
console.log(url);
image.src = url;
}
return texture;
}
================================================
FILE: src/js/third_party/webgl_teapot/matrix4x4.js
================================================
/*
* Copyright (c) 2009, Mozilla Corp
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Based on sample code from the OpenGL(R) ES 2.0 Programming Guide, which carriers
* the following header:
*
* Book: OpenGL(R) ES 2.0 Programming Guide
* Authors: Aaftab Munshi, Dan Ginsburg, Dave Shreiner
* ISBN-10: 0321502795
* ISBN-13: 9780321502797
* Publisher: Addison-Wesley Professional
* URLs: http://safari.informit.com/9780321563835
* http://www.opengles-book.com
*/
//
// A simple 4x4 Matrix utility class
//
function Matrix4x4() {
this.elements = Array(16);
this.loadIdentity();
}
Matrix4x4.prototype = {
scale: function (sx, sy, sz) {
this.elements[0*4+0] *= sx;
this.elements[0*4+1] *= sx;
this.elements[0*4+2] *= sx;
this.elements[0*4+3] *= sx;
this.elements[1*4+0] *= sy;
this.elements[1*4+1] *= sy;
this.elements[1*4+2] *= sy;
this.elements[1*4+3] *= sy;
this.elements[2*4+0] *= sz;
this.elements[2*4+1] *= sz;
this.elements[2*4+2] *= sz;
this.elements[2*4+3] *= sz;
return this;
},
translate: function (tx, ty, tz) {
this.elements[3*4+0] += this.elements[0*4+0] * tx + this.elements[1*4+0] * ty + this.elements[2*4+0] * tz;
this.elements[3*4+1] += this.elements[0*4+1] * tx + this.elements[1*4+1] * ty + this.elements[2*4+1] * tz;
this.elements[3*4+2] += this.elements[0*4+2] * tx + this.elements[1*4+2] * ty + this.elements[2*4+2] * tz;
this.elements[3*4+3] += this.elements[0*4+3] * tx + this.elements[1*4+3] * ty + this.elements[2*4+3] * tz;
return this;
},
rotate: function (angle, x, y, z) {
var mag = Math.sqrt(x*x + y*y + z*z);
var sinAngle = Math.sin(angle * Math.PI / 180.0);
var cosAngle = Math.cos(angle * Math.PI / 180.0);
if (mag > 0) {
var xx, yy, zz, xy, yz, zx, xs, ys, zs;
var oneMinusCos;
var rotMat;
x /= mag;
y /= mag;
z /= mag;
xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * sinAngle;
ys = y * sinAngle;
zs = z * sinAngle;
oneMinusCos = 1.0 - cosAngle;
rotMat = new Matrix4x4();
rotMat.elements[0*4+0] = (oneMinusCos * xx) + cosAngle;
rotMat.elements[0*4+1] = (oneMinusCos * xy) - zs;
rotMat.elements[0*4+2] = (oneMinusCos * zx) + ys;
rotMat.elements[0*4+3] = 0.0;
rotMat.elements[1*4+0] = (oneMinusCos * xy) + zs;
rotMat.elements[1*4+1] = (oneMinusCos * yy) + cosAngle;
rotMat.elements[1*4+2] = (oneMinusCos * yz) - xs;
rotMat.elements[1*4+3] = 0.0;
rotMat.elements[2*4+0] = (oneMinusCos * zx) - ys;
rotMat.elements[2*4+1] = (oneMinusCos * yz) + xs;
rotMat.elements[2*4+2] = (oneMinusCos * zz) + cosAngle;
rotMat.elements[2*4+3] = 0.0;
rotMat.elements[3*4+0] = 0.0;
rotMat.elements[3*4+1] = 0.0;
rotMat.elements[3*4+2] = 0.0;
rotMat.elements[3*4+3] = 1.0;
rotMat = rotMat.multiply(this);
this.elements = rotMat.elements;
}
return this;
},
frustum: function (left, right, bottom, top, nearZ, farZ) {
var deltaX = right - left;
var deltaY = top - bottom;
var deltaZ = farZ - nearZ;
var frust;
if ( (nearZ <= 0.0) || (farZ <= 0.0) ||
(deltaX <= 0.0) || (deltaY <= 0.0) || (deltaZ <= 0.0) )
return this;
frust = new Matrix4x4();
frust.elements[0*4+0] = 2.0 * nearZ / deltaX;
frust.elements[0*4+1] = frust.elements[0*4+2] = frust.elements[0*4+3] = 0.0;
frust.elements[1*4+1] = 2.0 * nearZ / deltaY;
frust.elements[1*4+0] = frust.elements[1*4+2] = frust.elements[1*4+3] = 0.0;
frust.elements[2*4+0] = (right + left) / deltaX;
frust.elements[2*4+1] = (top + bottom) / deltaY;
frust.elements[2*4+2] = -(nearZ + farZ) / deltaZ;
frust.elements[2*4+3] = -1.0;
frust.elements[3*4+2] = -2.0 * nearZ * farZ / deltaZ;
frust.elements[3*4+0] = frust.elements[3*4+1] = frust.elements[3*4+3] = 0.0;
frust = frust.multiply(this);
this.elements = frust.elements;
return this;
},
perspective: function (fovy, aspect, nearZ, farZ) {
var frustumH = Math.tan(fovy / 360.0 * Math.PI) * nearZ;
var frustumW = frustumH * aspect;
return this.frustum(-frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
},
ortho: function (left, right, bottom, top, nearZ, farZ) {
var deltaX = right - left;
var deltaY = top - bottom;
var deltaZ = farZ - nearZ;
var ortho = new Matrix4x4();
if ( (deltaX == 0.0) || (deltaY == 0.0) || (deltaZ == 0.0) )
return this;
ortho.elements[0*4+0] = 2.0 / deltaX;
ortho.elements[3*4+0] = -(right + left) / deltaX;
ortho.elements[1*4+1] = 2.0 / deltaY;
ortho.elements[3*4+1] = -(top + bottom) / deltaY;
ortho.elements[2*4+2] = -2.0 / deltaZ;
ortho.elements[3*4+2] = -(nearZ + farZ) / deltaZ;
ortho = ortho.multiply(this);
this.elements = ortho.elements;
return this;
},
multiply: function (right) {
var tmp = new Matrix4x4();
for (var i = 0; i < 4; i++) {
tmp.elements[i*4+0] =
(this.elements[i*4+0] * right.elements[0*4+0]) +
(this.elements[i*4+1] * right.elements[1*4+0]) +
(this.elements[i*4+2] * right.elements[2*4+0]) +
(this.elements[i*4+3] * right.elements[3*4+0]) ;
tmp.elements[i*4+1] =
(this.elements[i*4+0] * right.elements[0*4+1]) +
(this.elements[i*4+1] * right.elements[1*4+1]) +
(this.elements[i*4+2] * right.elements[2*4+1]) +
(this.elements[i*4+3] * right.elements[3*4+1]) ;
tmp.elements[i*4+2] =
(this.elements[i*4+0] * right.elements[0*4+2]) +
(this.elements[i*4+1] * right.elements[1*4+2]) +
(this.elements[i*4+2] * right.elements[2*4+2]) +
(this.elements[i*4+3] * right.elements[3*4+2]) ;
tmp.elements[i*4+3] =
(this.elements[i*4+0] * right.elements[0*4+3]) +
(this.elements[i*4+1] * right.elements[1*4+3]) +
(this.elements[i*4+2] * right.elements[2*4+3]) +
(this.elements[i*4+3] * right.elements[3*4+3]) ;
}
this.elements = tmp.elements;
return this;
},
copy: function () {
var tmp = new Matrix4x4();
for (var i = 0; i < 16; i++) {
tmp.elements[i] = this.elements[i];
}
return tmp;
},
get: function (row, col) {
return this.elements[4*row+col];
},
// In-place inversion
invert: function () {
var tmp_0 = this.get(2,2) * this.get(3,3);
var tmp_1 = this.get(3,2) * this.get(2,3);
var tmp_2 = this.get(1,2) * this.get(3,3);
var tmp_3 = this.get(3,2) * this.get(1,3);
var tmp_4 = this.get(1,2) * this.get(2,3);
var tmp_5 = this.get(2,2) * this.get(1,3);
var tmp_6 = this.get(0,2) * this.get(3,3);
var tmp_7 = this.get(3,2) * this.get(0,3);
var tmp_8 = this.get(0,2) * this.get(2,3);
var tmp_9 = this.get(2,2) * this.get(0,3);
var tmp_10 = this.get(0,2) * this.get(1,3);
var tmp_11 = this.get(1,2) * this.get(0,3);
var tmp_12 = this.get(2,0) * this.get(3,1);
var tmp_13 = this.get(3,0) * this.get(2,1);
var tmp_14 = this.get(1,0) * this.get(3,1);
var tmp_15 = this.get(3,0) * this.get(1,1);
var tmp_16 = this.get(1,0) * this.get(2,1);
var tmp_17 = this.get(2,0) * this.get(1,1);
var tmp_18 = this.get(0,0) * this.get(3,1);
var tmp_19 = this.get(3,0) * this.get(0,1);
var tmp_20 = this.get(0,0) * this.get(2,1);
var tmp_21 = this.get(2,0) * this.get(0,1);
var tmp_22 = this.get(0,0) * this.get(1,1);
var tmp_23 = this.get(1,0) * this.get(0,1);
var t0 = ((tmp_0 * this.get(1,1) + tmp_3 * this.get(2,1) + tmp_4 * this.get(3,1)) -
(tmp_1 * this.get(1,1) + tmp_2 * this.get(2,1) + tmp_5 * this.get(3,1)));
var t1 = ((tmp_1 * this.get(0,1) + tmp_6 * this.get(2,1) + tmp_9 * this.get(3,1)) -
(tmp_0 * this.get(0,1) + tmp_7 * this.get(2,1) + tmp_8 * this.get(3,1)));
var t2 = ((tmp_2 * this.get(0,1) + tmp_7 * this.get(1,1) + tmp_10 * this.get(3,1)) -
(tmp_3 * this.get(0,1) + tmp_6 * this.get(1,1) + tmp_11 * this.get(3,1)));
var t3 = ((tmp_5 * this.get(0,1) + tmp_8 * this.get(1,1) + tmp_11 * this.get(2,1)) -
(tmp_4 * this.get(0,1) + tmp_9 * this.get(1,1) + tmp_10 * this.get(2,1)));
var d = 1.0 / (this.get(0,0) * t0 + this.get(1,0) * t1 + this.get(2,0) * t2 + this.get(3,0) * t3);
var out_00 = d * t0;
var out_01 = d * t1;
var out_02 = d * t2;
var out_03 = d * t3;
var out_10 = d * ((tmp_1 * this.get(1,0) + tmp_2 * this.get(2,0) + tmp_5 * this.get(3,0)) -
(tmp_0 * this.get(1,0) + tmp_3 * this.get(2,0) + tmp_4 * this.get(3,0)));
var out_11 = d * ((tmp_0 * this.get(0,0) + tmp_7 * this.get(2,0) + tmp_8 * this.get(3,0)) -
(tmp_1 * this.get(0,0) + tmp_6 * this.get(2,0) + tmp_9 * this.get(3,0)));
var out_12 = d * ((tmp_3 * this.get(0,0) + tmp_6 * this.get(1,0) + tmp_11 * this.get(3,0)) -
(tmp_2 * this.get(0,0) + tmp_7 * this.get(1,0) + tmp_10 * this.get(3,0)));
var out_13 = d * ((tmp_4 * this.get(0,0) + tmp_9 * this.get(1,0) + tmp_10 * this.get(2,0)) -
(tmp_5 * this.get(0,0) + tmp_8 * this.get(1,0) + tmp_11 * this.get(2,0)));
var out_20 = d * ((tmp_12 * this.get(1,3) + tmp_15 * this.get(2,3) + tmp_16 * this.get(3,3)) -
(tmp_13 * this.get(1,3) + tmp_14 * this.get(2,3) + tmp_17 * this.get(3,3)));
var out_21 = d * ((tmp_13 * this.get(0,3) + tmp_18 * this.get(2,3) + tmp_21 * this.get(3,3)) -
(tmp_12 * this.get(0,3) + tmp_19 * this.get(2,3) + tmp_20 * this.get(3,3)));
var out_22 = d * ((tmp_14 * this.get(0,3) + tmp_19 * this.get(1,3) + tmp_22 * this.get(3,3)) -
(tmp_15 * this.get(0,3) + tmp_18 * this.get(1,3) + tmp_23 * this.get(3,3)));
var out_23 = d * ((tmp_17 * this.get(0,3) + tmp_20 * this.get(1,3) + tmp_23 * this.get(2,3)) -
(tmp_16 * this.get(0,3) + tmp_21 * this.get(1,3) + tmp_22 * this.get(2,3)));
var out_30 = d * ((tmp_14 * this.get(2,2) + tmp_17 * this.get(3,2) + tmp_13 * this.get(1,2)) -
(tmp_16 * this.get(3,2) + tmp_12 * this.get(1,2) + tmp_15 * this.get(2,2)));
var out_31 = d * ((tmp_20 * this.get(3,2) + tmp_12 * this.get(0,2) + tmp_19 * this.get(2,2)) -
(tmp_18 * this.get(2,2) + tmp_21 * this.get(3,2) + tmp_13 * this.get(0,2)));
var out_32 = d * ((tmp_18 * this.get(1,2) + tmp_23 * this.get(3,2) + tmp_15 * this.get(0,2)) -
(tmp_22 * this.get(3,2) + tmp_14 * this.get(0,2) + tmp_19 * this.get(1,2)));
var out_33 = d * ((tmp_22 * this.get(2,2) + tmp_16 * this.get(0,2) + tmp_21 * this.get(1,2)) -
(tmp_20 * this.get(1,2) + tmp_23 * this.get(2,2) + tmp_17 * this.get(0,2)));
this.elements[0*4+0] = out_00;
this.elements[0*4+1] = out_01;
this.elements[0*4+2] = out_02;
this.elements[0*4+3] = out_03;
this.elements[1*4+0] = out_10;
this.elements[1*4+1] = out_11;
this.elements[1*4+2] = out_12;
this.elements[1*4+3] = out_13;
this.elements[2*4+0] = out_20;
this.elements[2*4+1] = out_21;
this.elements[2*4+2] = out_22;
this.elements[2*4+3] = out_23;
this.elements[3*4+0] = out_30;
this.elements[3*4+1] = out_31;
this.elements[3*4+2] = out_32;
this.elements[3*4+3] = out_33;
return this;
},
// Returns new matrix which is the inverse of this
inverse: function () {
var tmp = this.copy();
return tmp.invert();
},
// In-place transpose
transpose: function () {
var tmp = this.elements[0*4+1];
this.elements[0*4+1] = this.elements[1*4+0];
this.elements[1*4+0] = tmp;
tmp = this.elements[0*4+2];
this.elements[0*4+2] = this.elements[2*4+0];
this.elements[2*4+0] = tmp;
tmp = this.elements[0*4+3];
this.elements[0*4+3] = this.elements[3*4+0];
this.elements[3*4+0] = tmp;
tmp = this.elements[1*4+2];
this.elements[1*4+2] = this.elements[2*4+1];
this.elements[2*4+1] = tmp;
tmp = this.elements[1*4+3];
this.elements[1*4+3] = this.elements[3*4+1];
this.elements[3*4+1] = tmp;
tmp = this.elements[2*4+3];
this.elements[2*4+3] = this.elements[3*4+2];
this.elements[3*4+2] = tmp;
return this;
},
loadIdentity: function () {
for (var i = 0; i < 16; i++)
this.elements[i] = 0;
this.elements[0*4+0] = 1.0;
this.elements[1*4+1] = 1.0;
this.elements[2*4+2] = 1.0;
this.elements[3*4+3] = 1.0;
return this;
}
};
================================================
FILE: src/js/third_party/webgl_teapot/teapot-streams.js
================================================
/*
* Copyright (c) 2009 The Chromium Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var teapotPositions = new Float32Array([ 17.83489990234375, 0, 30.573999404907227, 16.452699661254883, -7.000179767608643, 30.573999404907227, 16.223100662231445, -6.902520179748535, 31.51460075378418, 17.586000442504883, 0, 31.51460075378418, 16.48940086364746, -7.015810012817383, 31.828100204467773, 17.87470054626465, 0, 31.828100204467773, 17.031099319458008, -7.246280193328857, 31.51460075378418, 18.46190071105957, 0, 31.51460075378418, 17.62779998779297, -7.500199794769287, 30.573999404907227, 19.108800888061523, 0, 30.573999404907227, 12.662699699401855, -12.662699699401855, 30.573999404907227, 12.486100196838379, -12.486100196838379, 31.51460075378418, 12.690999984741211, -12.690999984741211, 31.828100204467773, 13.10789966583252, -13.10789966583252, 31.51460075378418, 13.56719970703125, -13.56719970703125, 30.573999404907227, 7.000179767608643, -16.452699661254883, 30.573999404907227, 6.902520179748535, -16.223100662231445, 31.51460075378418, 7.015810012817383, -16.48940086364746, 31.828100204467773, 7.246280193328857, -17.031099319458008, 31.51460075378418, 7.500199794769287, -17.62779998779297, 30.573999404907227, 0, -17.83489990234375, 30.573999404907227, 0, -17.586000442504883, 31.51460075378418, 0, -17.87470054626465, 31.828100204467773, 0, -18.46190071105957, 31.51460075378418, 0, -19.108800888061523, 30.573999404907227, 0, -17.83489990234375, 30.573999404907227, -7.483870029449463, -16.452699661254883, 30.573999404907227, -7.106579780578613, -16.223100662231445, 31.51460075378418, 0, -17.586000442504883, 31.51460075378418, -7.07627010345459, -16.48940086364746, 31.828100204467773, 0, -17.87470054626465, 31.828100204467773, -7.25383996963501, -17.031099319458008, 31.51460075378418, 0, -18.46190071105957, 31.51460075378418, -7.500199794769287, -17.62779998779297, 30.573999404907227, 0, -19.108800888061523, 30.573999404907227, -13.092700004577637, -12.662699699401855, 30.573999404907227, -12.667499542236328, -12.486100196838379, 31.51460075378418, -12.744799613952637, -12.690999984741211, 31.828100204467773, -13.11460018157959, -13.10789966583252, 31.51460075378418, -13.56719970703125, -13.56719970703125, 30.573999404907227, -16.61389923095703, -7.000179767608643, 30.573999404907227, -16.291099548339844, -6.902520179748535, 31.51460075378418, -16.50950050354004, -7.015810012817383, 31.828100204467773, -17.033599853515625, -7.246280193328857, 31.51460075378418, -17.62779998779297, -7.500199794769287, 30.573999404907227, -17.83489990234375, 0, 30.573999404907227, -17.586000442504883, 0, 31.51460075378418, -17.87470054626465, 0, 31.828100204467773, -18.46190071105957, 0, 31.51460075378418, -19.108800888061523, 0, 30.573999404907227, -17.83489990234375, 0, 30.573999404907227, -16.452699661254883, 7.000179767608643, 30.573999404907227, -16.223100662231445, 6.902520179748535, 31.51460075378418, -17.586000442504883, 0, 31.51460075378418, -16.48940086364746, 7.015810012817383, 31.828100204467773, -17.87470054626465, 0, 31.828100204467773, -17.031099319458008, 7.246280193328857, 31.51460075378418, -18.46190071105957, 0, 31.51460075378418, -17.62779998779297, 7.500199794769287, 30.573999404907227, -19.108800888061523, 0, 30.573999404907227, -12.662699699401855, 12.662699699401855, 30.573999404907227, -12.486100196838379, 12.486100196838379, 31.51460075378418, -12.690999984741211, 12.690999984741211, 31.828100204467773, -13.10789966583252, 13.10789966583252, 31.51460075378418, -13.56719970703125, 13.56719970703125, 30.573999404907227, -7.000179767608643, 16.452699661254883, 30.573999404907227, -6.902520179748535, 16.223100662231445, 31.51460075378418, -7.015810012817383, 16.48940086364746, 31.828100204467773, -7.246280193328857, 17.031099319458008, 31.51460075378418, -7.500199794769287, 17.62779998779297, 30.573999404907227, 0, 17.83489990234375, 30.573999404907227, 0, 17.586000442504883, 31.51460075378418, 0, 17.87470054626465, 31.828100204467773, 0, 18.46190071105957, 31.51460075378418, 0, 19.108800888061523, 30.573999404907227, 0, 17.83489990234375, 30.573999404907227, 7.000179767608643, 16.452699661254883, 30.573999404907227, 6.902520179748535, 16.223100662231445, 31.51460075378418, 0, 17.586000442504883, 31.51460075378418, 7.015810012817383, 16.48940086364746, 31.828100204467773, 0, 17.87470054626465, 31.828100204467773, 7.246280193328857, 17.031099319458008, 31.51460075378418, 0, 18.46190071105957, 31.51460075378418, 7.500199794769287, 17.62779998779297, 30.573999404907227, 0, 19.108800888061523, 30.573999404907227, 12.662699699401855, 12.662699699401855, 30.573999404907227, 12.486100196838379, 12.486100196838379, 31.51460075378418, 12.690999984741211, 12.690999984741211, 31.828100204467773, 13.10789966583252, 13.10789966583252, 31.51460075378418, 13.56719970703125, 13.56719970703125, 30.573999404907227, 16.452699661254883, 7.000179767608643, 30.573999404907227, 16.223100662231445, 6.902520179748535, 31.51460075378418, 16.48940086364746, 7.015810012817383, 31.828100204467773, 17.031099319458008, 7.246280193328857, 31.51460075378418, 17.62779998779297, 7.500199794769287, 30.573999404907227, 17.83489990234375, 0, 30.573999404907227, 17.586000442504883, 0, 31.51460075378418, 17.87470054626465, 0, 31.828100204467773, 18.46190071105957, 0, 31.51460075378418, 19.108800888061523, 0, 30.573999404907227, 19.108800888061523, 0, 30.573999404907227, 17.62779998779297, -7.500199794769287, 30.573999404907227, 19.785400390625, -8.418190002441406, 25.572900772094727, 21.447599411010742, 0, 25.572900772094727, 21.667600631713867, -9.218990325927734, 20.661399841308594, 23.487899780273438, 0, 20.661399841308594, 22.99880027770996, -9.785409927368164, 15.928999900817871, 24.930999755859375, 0, 15.928999900817871, 23.503799438476562, -10.000300407409668, 11.465299606323242, 25.4783992767334, 0, 11.465299606323242, 13.56719970703125, -13.56719970703125, 30.573999404907227, 15.227800369262695, -15.227800369262695, 25.572900772094727, 16.67639923095703, -16.67639923095703, 20.661399841308594, 17.701000213623047, -17.701000213623047, 15.928999900817871, 18.089599609375, -18.089599609375, 11.465299606323242, 7.500199794769287, -17.62779998779297, 30.573999404907227, 8.418190002441406, -19.785400390625, 25.572900772094727, 9.218990325927734, -21.667600631713867, 20.661399841308594, 9.785409927368164, -22.99880027770996, 15.928999900817871, 10.000300407409668, -23.503799438476562, 11.465299606323242, 0, -19.108800888061523, 30.573999404907227, 0, -21.447599411010742, 25.572900772094727, 0, -23.487899780273438, 20.661399841308594, 0, -24.930999755859375, 15.928999900817871, 0, -25.4783992767334, 11.465299606323242, 0, -19.108800888061523, 30.573999404907227, -7.500199794769287, -17.62779998779297, 30.573999404907227, -8.418190002441406, -19.785400390625, 25.572900772094727, 0, -21.447599411010742, 25.572900772094727, -9.218990325927734, -21.667600631713867, 20.661399841308594, 0, -23.487899780273438, 20.661399841308594, -9.785409927368164, -22.99880027770996, 15.928999900817871, 0, -24.930999755859375, 15.928999900817871, -10.000300407409668, -23.503799438476562, 11.465299606323242, 0, -25.4783992767334, 11.465299606323242, -13.56719970703125, -13.56719970703125, 30.573999404907227, -15.227800369262695, -15.227800369262695, 25.572900772094727, -16.67639923095703, -16.67639923095703, 20.661399841308594, -17.701000213623047, -17.701000213623047, 15.928999900817871, -18.089599609375, -18.089599609375, 11.465299606323242, -17.62779998779297, -7.500199794769287, 30.573999404907227, -19.785400390625, -8.418190002441406, 25.572900772094727, -21.667600631713867, -9.218990325927734, 20.661399841308594, -22.99880027770996, -9.785409927368164, 15.928999900817871, -23.503799438476562, -10.000300407409668, 11.465299606323242, -19.108800888061523, 0, 30.573999404907227, -21.447599411010742, 0, 25.572900772094727, -23.487899780273438, 0, 20.661399841308594, -24.930999755859375, 0, 15.928999900817871, -25.4783992767334, 0, 11.465299606323242, -19.108800888061523, 0, 30.573999404907227, -17.62779998779297, 7.500199794769287, 30.573999404907227, -19.785400390625, 8.418190002441406, 25.572900772094727, -21.447599411010742, 0, 25.572900772094727, -21.667600631713867, 9.218990325927734, 20.661399841308594, -23.487899780273438, 0, 20.661399841308594, -22.99880027770996, 9.785409927368164, 15.928999900817871, -24.930999755859375, 0, 15.928999900817871, -23.503799438476562, 10.000300407409668, 11.465299606323242, -25.4783992767334, 0, 11.465299606323242, -13.56719970703125, 13.56719970703125, 30.573999404907227, -15.227800369262695, 15.227800369262695, 25.572900772094727, -16.67639923095703, 16.67639923095703, 20.661399841308594, -17.701000213623047, 17.701000213623047, 15.928999900817871, -18.089599609375, 18.089599609375, 11.465299606323242, -7.500199794769287, 17.62779998779297, 30.573999404907227, -8.418190002441406, 19.785400390625, 25.572900772094727, -9.218990325927734, 21.667600631713867, 20.661399841308594, -9.785409927368164, 22.99880027770996, 15.928999900817871, -10.000300407409668, 23.503799438476562, 11.465299606323242, 0, 19.108800888061523, 30.573999404907227, 0, 21.447599411010742, 25.572900772094727, 0, 23.487899780273438, 20.661399841308594, 0, 24.930999755859375, 15.928999900817871, 0, 25.4783992767334, 11.465299606323242, 0, 19.108800888061523, 30.573999404907227, 7.500199794769287, 17.62779998779297, 30.573999404907227, 8.418190002441406, 19.785400390625, 25.572900772094727, 0, 21.447599411010742, 25.572900772094727, 9.218990325927734, 21.667600631713867, 20.661399841308594, 0, 23.487899780273438, 20.661399841308594, 9.785409927368164, 22.99880027770996, 15.928999900817871, 0, 24.930999755859375, 15.928999900817871, 10.000300407409668, 23.503799438476562, 11.465299606323242, 0, 25.4783992767334, 11.465299606323242, 13.56719970703125, 13.56719970703125, 30.573999404907227, 15.227800369262695, 15.227800369262695, 25.572900772094727, 16.67639923095703, 16.67639923095703, 20.661399841308594, 17.701000213623047, 17.701000213623047, 15.928999900817871, 18.089599609375, 18.089599609375, 11.465299606323242, 17.62779998779297, 7.500199794769287, 30.573999404907227, 19.785400390625, 8.418190002441406, 25.572900772094727, 21.667600631713867, 9.218990325927734, 20.661399841308594, 22.99880027770996, 9.785409927368164, 15.928999900817871, 23.503799438476562, 10.000300407409668, 11.465299606323242, 19.108800888061523, 0, 30.573999404907227, 21.447599411010742, 0, 25.572900772094727, 23.487899780273438, 0, 20.661399841308594, 24.930999755859375, 0, 15.928999900817871, 25.4783992767334, 0, 11.465299606323242, 25.4783992767334, 0, 11.465299606323242, 23.503799438476562, -10.000300407409668, 11.465299606323242, 22.5856990814209, -9.609620094299316, 7.688300132751465, 24.48310089111328, 0, 7.688300132751465, 20.565799713134766, -8.750229835510254, 4.89661979675293, 22.29360008239746, 0, 4.89661979675293, 18.54599952697754, -7.890830039978027, 3.0006699562072754, 20.104000091552734, 0, 3.0006699562072754, 17.62779998779297, -7.500199794769287, 1.9108799695968628, 19.108800888061523, 0, 1.9108799695968628, 18.089599609375, -18.089599609375, 11.465299606323242, 17.382999420166016, -17.382999420166016, 7.688300132751465, 15.828399658203125, -15.828399658203125, 4.89661979675293, 14.273900032043457, -14.273900032043457, 3.0006699562072754, 13.56719970703125, -13.56719970703125, 1.9108799695968628, 10.000300407409668, -23.503799438476562, 11.465299606323242, 9.609620094299316, -22.5856990814209, 7.688300132751465, 8.750229835510254, -20.565799713134766, 4.89661979675293, 7.890830039978027, -18.54599952697754, 3.0006699562072754, 7.500199794769287, -17.62779998779297, 1.9108799695968628, 0, -25.4783992767334, 11.465299606323242, 0, -24.48310089111328, 7.688300132751465, 0, -22.29360008239746, 4.89661979675293, 0, -20.104000091552734, 3.0006699562072754, 0, -19.108800888061523, 1.9108799695968628, 0, -25.4783992767334, 11.465299606323242, -10.000300407409668, -23.503799438476562, 11.465299606323242, -9.609620094299316, -22.5856990814209, 7.688300132751465, 0, -24.48310089111328, 7.688300132751465, -8.750229835510254, -20.565799713134766, 4.89661979675293, 0, -22.29360008239746, 4.89661979675293, -7.890830039978027, -18.54599952697754, 3.0006699562072754, 0, -20.104000091552734, 3.0006699562072754, -7.500199794769287, -17.62779998779297, 1.9108799695968628, 0, -19.108800888061523, 1.9108799695968628, -18.089599609375, -18.089599609375, 11.465299606323242, -17.382999420166016, -17.382999420166016, 7.688300132751465, -15.828399658203125, -15.828399658203125, 4.89661979675293, -14.273900032043457, -14.273900032043457, 3.0006699562072754, -13.56719970703125, -13.56719970703125, 1.9108799695968628, -23.503799438476562, -10.000300407409668, 11.465299606323242, -22.5856990814209, -9.609620094299316, 7.688300132751465, -20.565799713134766, -8.750229835510254, 4.89661979675293, -18.54599952697754, -7.890830039978027, 3.0006699562072754, -17.62779998779297, -7.500199794769287, 1.9108799695968628, -25.4783992767334, 0, 11.465299606323242, -24.48310089111328, 0, 7.688300132751465, -22.29360008239746, 0, 4.89661979675293, -20.104000091552734, 0, 3.0006699562072754, -19.108800888061523, 0, 1.9108799695968628, -25.4783992767334, 0, 11.465299606323242, -23.503799438476562, 10.000300407409668, 11.465299606323242, -22.5856990814209, 9.609620094299316, 7.688300132751465, -24.48310089111328, 0, 7.688300132751465, -20.565799713134766, 8.750229835510254, 4.89661979675293, -22.29360008239746, 0, 4.89661979675293, -18.54599952697754, 7.890830039978027, 3.0006699562072754, -20.104000091552734, 0, 3.0006699562072754, -17.62779998779297, 7.500199794769287, 1.9108799695968628, -19.108800888061523, 0, 1.9108799695968628, -18.089599609375, 18.089599609375, 11.465299606323242, -17.382999420166016, 17.382999420166016, 7.688300132751465, -15.828399658203125, 15.828399658203125, 4.89661979675293, -14.273900032043457, 14.273900032043457, 3.0006699562072754, -13.56719970703125, 13.56719970703125, 1.9108799695968628, -10.000300407409668, 23.503799438476562, 11.465299606323242, -9.609620094299316, 22.5856990814209, 7.688300132751465, -8.750229835510254, 20.565799713134766, 4.89661979675293, -7.890830039978027, 18.54599952697754, 3.0006699562072754, -7.500199794769287, 17.62779998779297, 1.9108799695968628, 0, 25.4783992767334, 11.465299606323242, 0, 24.48310089111328, 7.688300132751465, 0, 22.29360008239746, 4.89661979675293, 0, 20.104000091552734, 3.0006699562072754, 0, 19.108800888061523, 1.9108799695968628, 0, 25.4783992767334, 11.465299606323242, 10.000300407409668, 23.503799438476562, 11.465299606323242, 9.609620094299316, 22.5856990814209, 7.688300132751465, 0, 24.48310089111328, 7.688300132751465, 8.750229835510254, 20.565799713134766, 4.89661979675293, 0, 22.29360008239746, 4.89661979675293, 7.890830039978027, 18.54599952697754, 3.0006699562072754, 0, 20.104000091552734, 3.0006699562072754, 7.500199794769287, 17.62779998779297, 1.9108799695968628, 0, 19.108800888061523, 1.9108799695968628, 18.089599609375, 18.089599609375, 11.465299606323242, 17.382999420166016, 17.382999420166016, 7.688300132751465, 15.828399658203125, 15.828399658203125, 4.89661979675293, 14.273900032043457, 14.273900032043457, 3.0006699562072754, 13.56719970703125, 13.56719970703125, 1.9108799695968628, 23.503799438476562, 10.000300407409668, 11.465299606323242, 22.5856990814209, 9.609620094299316, 7.688300132751465, 20.565799713134766, 8.750229835510254, 4.89661979675293, 18.54599952697754, 7.890830039978027, 3.0006699562072754, 17.62779998779297, 7.500199794769287, 1.9108799695968628, 25.4783992767334, 0, 11.465299606323242, 24.48310089111328, 0, 7.688300132751465, 22.29360008239746, 0, 4.89661979675293, 20.104000091552734, 0, 3.0006699562072754, 19.108800888061523, 0, 1.9108799695968628, 19.108800888061523, 0, 1.9108799695968628, 17.62779998779297, -7.500199794769287, 1.9108799695968628, 17.228500366210938, -7.330269813537598, 1.2092299461364746, 18.675800323486328, 0, 1.2092299461364746, 15.093799591064453, -6.422039985656738, 0.5971490144729614, 16.361900329589844, 0, 0.5971490144729614, 9.819259643554688, -4.177840232849121, 0.16421599686145782, 10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, 0, 0, 0, 13.56719970703125, -13.56719970703125, 1.9108799695968628, 13.25979995727539, -13.25979995727539, 1.2092299461364746, 11.616900444030762, -11.616900444030762, 0.5971490144729614, 7.557370185852051, -7.557370185852051, 0.16421599686145782, 0, 0, 0, 7.500199794769287, -17.62779998779297, 1.9108799695968628, 7.330269813537598, -17.228500366210938, 1.2092299461364746, 6.422039985656738, -15.093799591064453, 0.5971490144729614, 4.177840232849121, -9.819259643554688, 0.16421599686145782, 0, 0, 0, 0, -19.108800888061523, 1.9108799695968628, 0, -18.675800323486328, 1.2092299461364746, 0, -16.361900329589844, 0.5971490144729614, 0, -10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, -19.108800888061523, 1.9108799695968628, -7.500199794769287, -17.62779998779297, 1.9108799695968628, -7.330269813537598, -17.228500366210938, 1.2092299461364746, 0, -18.675800323486328, 1.2092299461364746, -6.422039985656738, -15.093799591064453, 0.5971490144729614, 0, -16.361900329589844, 0.5971490144729614, -4.177840232849121, -9.819259643554688, 0.16421599686145782, 0, -10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, 0, 0, -13.56719970703125, -13.56719970703125, 1.9108799695968628, -13.25979995727539, -13.25979995727539, 1.2092299461364746, -11.616900444030762, -11.616900444030762, 0.5971490144729614, -7.557370185852051, -7.557370185852051, 0.16421599686145782, 0, 0, 0, -17.62779998779297, -7.500199794769287, 1.9108799695968628, -17.228500366210938, -7.330269813537598, 1.2092299461364746, -15.093799591064453, -6.422039985656738, 0.5971490144729614, -9.819259643554688, -4.177840232849121, 0.16421599686145782, 0, 0, 0, -19.108800888061523, 0, 1.9108799695968628, -18.675800323486328, 0, 1.2092299461364746, -16.361900329589844, 0, 0.5971490144729614, -10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, -19.108800888061523, 0, 1.9108799695968628, -17.62779998779297, 7.500199794769287, 1.9108799695968628, -17.228500366210938, 7.330269813537598, 1.2092299461364746, -18.675800323486328, 0, 1.2092299461364746, -15.093799591064453, 6.422039985656738, 0.5971490144729614, -16.361900329589844, 0, 0.5971490144729614, -9.819259643554688, 4.177840232849121, 0.16421599686145782, -10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, 0, 0, 0, -13.56719970703125, 13.56719970703125, 1.9108799695968628, -13.25979995727539, 13.25979995727539, 1.2092299461364746, -11.616900444030762, 11.616900444030762, 0.5971490144729614, -7.557370185852051, 7.557370185852051, 0.16421599686145782, 0, 0, 0, -7.500199794769287, 17.62779998779297, 1.9108799695968628, -7.330269813537598, 17.228500366210938, 1.2092299461364746, -6.422039985656738, 15.093799591064453, 0.5971490144729614, -4.177840232849121, 9.819259643554688, 0.16421599686145782, 0, 0, 0, 0, 19.108800888061523, 1.9108799695968628, 0, 18.675800323486328, 1.2092299461364746, 0, 16.361900329589844, 0.5971490144729614, 0, 10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, 19.108800888061523, 1.9108799695968628, 7.500199794769287, 17.62779998779297, 1.9108799695968628, 7.330269813537598, 17.228500366210938, 1.2092299461364746, 0, 18.675800323486328, 1.2092299461364746, 6.422039985656738, 15.093799591064453, 0.5971490144729614, 0, 16.361900329589844, 0.5971490144729614, 4.177840232849121, 9.819259643554688, 0.16421599686145782, 0, 10.644200325012207, 0.16421599686145782, 0, 0, 0, 0, 0, 0, 13.56719970703125, 13.56719970703125, 1.9108799695968628, 13.25979995727539, 13.25979995727539, 1.2092299461364746, 11.616900444030762, 11.616900444030762, 0.5971490144729614, 7.557370185852051, 7.557370185852051, 0.16421599686145782, 0, 0, 0, 17.62779998779297, 7.500199794769287, 1.9108799695968628, 17.228500366210938, 7.330269813537598, 1.2092299461364746, 15.093799591064453, 6.422039985656738, 0.5971490144729614, 9.819259643554688, 4.177840232849121, 0.16421599686145782, 0, 0, 0, 19.108800888061523, 0, 1.9108799695968628, 18.675800323486328, 0, 1.2092299461364746, 16.361900329589844, 0, 0.5971490144729614, 10.644200325012207, 0, 0.16421599686145782, 0, 0, 0, -20.382699966430664, 0, 25.796899795532227, -20.1835994720459, -2.149739980697632, 26.244699478149414, -26.511600494384766, -2.149739980697632, 26.192899703979492, -26.334299087524414, 0, 25.752099990844727, -31.156299591064453, -2.149739980697632, 25.830400466918945, -30.733299255371094, 0, 25.438600540161133, -34.016998291015625, -2.149739980697632, 24.846500396728516, -33.46030044555664, 0, 24.587600708007812, -34.99290084838867, -2.149739980697632, 22.930500030517578, -34.39580154418945, 0, 22.930500030517578, -19.74570083618164, -2.8663198947906494, 27.229999542236328, -26.901599884033203, -2.8663198947906494, 27.162799835205078, -32.08679962158203, -2.8663198947906494, 26.69260025024414, -35.241798400878906, -2.8663198947906494, 25.416200637817383, -36.30670166015625, -2.8663198947906494, 22.930500030517578, -19.30780029296875, -2.149739980697632, 28.215299606323242, -27.29159927368164, -2.149739980697632, 28.132699966430664, -33.017398834228516, -2.149739980697632, 27.55470085144043, -36.46649932861328, -2.149739980697632, 25.98579978942871, -37.620399475097656, -2.149739980697632, 22.930500030517578, -19.108800888061523, 0, 28.66320037841797, -27.468900680541992, 0, 28.57360076904297, -33.440399169921875, 0, 27.94659996032715, -37.02330017089844, 0, 26.244699478149414, -38.21760177612305, 0, 22.930500030517578, -19.108800888061523, 0, 28.66320037841797, -19.30780029296875, 2.149739980697632, 28.215299606323242, -27.29159927368164, 2.149739980697632, 28.132699966430664, -27.468900680541992, 0, 28.57360076904297, -33.017398834228516, 2.149739980697632, 27.55470085144043, -33.440399169921875, 0, 27.94659996032715, -36.46649932861328, 2.149739980697632, 25.98579978942871, -37.02330017089844, 0, 26.244699478149414, -37.620399475097656, 2.149739980697632, 22.930500030517578, -38.21760177612305, 0, 22.930500030517578, -19.74570083618164, 2.8663198947906494, 27.229999542236328, -26.901599884033203, 2.8663198947906494, 27.162799835205078, -32.08679962158203, 2.8663198947906494, 26.69260025024414, -35.241798400878906, 2.8663198947906494, 25.416200637817383, -36.30670166015625, 2.8663198947906494, 22.930500030517578, -20.1835994720459, 2.149739980697632, 26.244699478149414, -26.511600494384766, 2.149739980697632, 26.192899703979492, -31.156299591064453, 2.149739980697632, 25.830400466918945, -34.016998291015625, 2.149739980697632, 24.846500396728516, -34.99290084838867, 2.149739980697632, 22.930500030517578, -20.382699966430664, 0, 25.796899795532227, -26.334299087524414, 0, 25.752099990844727, -30.733299255371094, 0, 25.438600540161133, -33.46030044555664, 0, 24.587600708007812, -34.39580154418945, 0, 22.930500030517578, -34.39580154418945, 0, 22.930500030517578, -34.99290084838867, -2.149739980697632, 22.930500030517578, -34.44089889526367, -2.149739980697632, 20.082199096679688, -33.89820098876953, 0, 20.33289909362793, -32.711299896240234, -2.149739980697632, 16.81529998779297, -32.32569885253906, 0, 17.197900772094727, -29.69420051574707, -2.149739980697632, 13.590499877929688, -29.558900833129883, 0, 14.062899589538574, -25.279300689697266, -2.149739980697632, 10.8681001663208, -25.4783992767334, 0, 11.465299606323242, -36.30670166015625, -2.8663198947906494, 22.930500030517578, -35.6348991394043, -2.8663198947906494, 19.530500411987305, -33.55979919433594, -2.8663198947906494, 15.973699569702148, -29.99180030822754, -2.8663198947906494, 12.551300048828125, -24.841400146484375, -2.8663198947906494, 9.554389953613281, -37.620399475097656, -2.149739980697632, 22.930500030517578, -36.82889938354492, -2.149739980697632, 18.97879981994629, -34.408199310302734, -2.149739980697632, 15.132100105285645, -30.289499282836914, -2.149739980697632, 11.512200355529785, -24.403499603271484, -2.149739980697632, 8.240659713745117, -38.21760177612305, 0, 22.930500030517578, -37.37160110473633, 0, 18.728099822998047, -34.79389953613281, 0, 14.749600410461426, -30.424800872802734, 0, 11.039799690246582, -24.204500198364258, 0, 7.643509864807129, -38.21760177612305, 0, 22.930500030517578, -37.620399475097656, 2.149739980697632, 22.930500030517578, -36.82889938354492, 2.149739980697632, 18.97879981994629, -37.37160110473633, 0, 18.728099822998047, -34.408199310302734, 2.149739980697632, 15.132100105285645, -34.79389953613281, 0, 14.749600410461426, -30.289499282836914, 2.149739980697632, 11.512200355529785, -30.424800872802734, 0, 11.039799690246582, -24.403499603271484, 2.149739980697632, 8.240659713745117, -24.204500198364258, 0, 7.643509864807129, -36.30670166015625, 2.8663198947906494, 22.930500030517578, -35.6348991394043, 2.8663198947906494, 19.530500411987305, -33.55979919433594, 2.8663198947906494, 15.973699569702148, -29.99180030822754, 2.8663198947906494, 12.551300048828125, -24.841400146484375, 2.8663198947906494, 9.554389953613281, -34.99290084838867, 2.149739980697632, 22.930500030517578, -34.44089889526367, 2.149739980697632, 20.082199096679688, -32.711299896240234, 2.149739980697632, 16.81529998779297, -29.69420051574707, 2.149739980697632, 13.590499877929688, -25.279300689697266, 2.149739980697632, 10.8681001663208, -34.39580154418945, 0, 22.930500030517578, -33.89820098876953, 0, 20.33289909362793, -32.32569885253906, 0, 17.197900772094727, -29.558900833129883, 0, 14.062899589538574, -25.4783992767334, 0, 11.465299606323242, 21.656600952148438, 0, 18.15329933166504, 21.656600952148438, -4.729420185089111, 16.511199951171875, 28.233999252319336, -4.270359992980957, 18.339000701904297, 27.76740074157715, 0, 19.55660057067871, 31.011899948120117, -3.2604401111602783, 22.221399307250977, 30.4148006439209, 0, 22.930500030517578, 32.59560012817383, -2.2505099773406982, 26.764400482177734, 31.867900848388672, 0, 27.020999908447266, 35.5900993347168, -1.791450023651123, 30.573999404907227, 34.39580154418945, 0, 30.573999404907227, 21.656600952148438, -6.3059000968933105, 12.89840030670166, 29.260299682617188, -5.693819999694824, 15.660200119018555, 32.32569885253906, -4.347249984741211, 20.661399841308594, 34.19670104980469, -3.0006699562072754, 26.199899673461914, 38.21760177612305, -2.3886001110076904, 30.573999404907227, 21.656600952148438, -4.729420185089111, 9.285670280456543, 30.286699295043945, -4.270359992980957, 12.981499671936035, 33.639400482177734, -3.2604401111602783, 19.101299285888672, 35.79790115356445, -2.2505099773406982, 25.635400772094727, 40.845001220703125, -1.791450023651123, 30.573999404907227, 21.656600952148438, 0, 7.643509864807129, 30.75320053100586, 0, 11.763799667358398, 34.23659896850586, 0, 18.392200469970703, 36.52560043334961, 0, 25.378799438476562, 42.03929901123047, 0, 30.573999404907227, 21.656600952148438, 0, 7.643509864807129, 21.656600952148438, 4.729420185089111, 9.285670280456543, 30.286699295043945, 4.270359992980957, 12.981499671936035, 30.75320053100586, 0, 11.763799667358398, 33.639400482177734, 3.2604401111602783, 19.101299285888672, 34.23659896850586, 0, 18.392200469970703, 35.79790115356445, 2.2505099773406982, 25.635400772094727, 36.52560043334961, 0, 25.378799438476562, 40.845001220703125, 1.791450023651123, 30.573999404907227, 42.03929901123047, 0, 30.573999404907227, 21.656600952148438, 6.3059000968933105, 12.89840030670166, 29.260299682617188, 5.693819999694824, 15.660200119018555, 32.32569885253906, 4.347249984741211, 20.661399841308594, 34.19670104980469, 3.0006699562072754, 26.199899673461914, 38.21760177612305, 2.3886001110076904, 30.573999404907227, 21.656600952148438, 4.729420185089111, 16.511199951171875, 28.233999252319336, 4.270359992980957, 18.339000701904297, 31.011899948120117, 3.2604401111602783, 22.221399307250977, 32.59560012817383, 2.2505099773406982, 26.764400482177734, 35.5900993347168, 1.791450023651123, 30.573999404907227, 21.656600952148438, 0, 18.15329933166504, 27.76740074157715, 0, 19.55660057067871, 30.4148006439209, 0, 22.930500030517578, 31.867900848388672, 0, 27.020999908447266, 34.39580154418945, 0, 30.573999404907227, 34.39580154418945, 0, 30.573999404907227, 35.5900993347168, -1.791450023651123, 30.573999404907227, 36.59049987792969, -1.679479956626892, 31.137699127197266, 35.3114013671875, 0, 31.111499786376953, 37.18870162963867, -1.4331599473953247, 31.332599639892578, 35.98820114135742, 0, 31.290599822998047, 37.206600189208984, -1.1868300437927246, 31.1481990814209, 36.187198638916016, 0, 31.111499786376953, 36.46590042114258, -1.074869990348816, 30.573999404907227, 35.669700622558594, 0, 30.573999404907227, 38.21760177612305, -2.3886001110076904, 30.573999404907227, 39.40439987182617, -2.2393100261688232, 31.195499420166016, 39.829898834228516, -1.9108799695968628, 31.424999237060547, 39.44919967651367, -1.582450032234192, 31.229000091552734, 38.21760177612305, -1.4331599473953247, 30.573999404907227, 40.845001220703125, -1.791450023651123, 30.573999404907227, 42.218299865722656, -1.679479956626892, 31.25320053100586, 42.47100067138672, -1.4331599473953247, 31.51740074157715, 41.69169998168945, -1.1868300437927246, 31.309900283813477, 39.969200134277344, -1.074869990348816, 30.573999404907227, 42.03929901123047, 0, 30.573999404907227, 43.49729919433594, 0, 31.279399871826172, 43.67150115966797, 0, 31.55929946899414, 42.71110153198242, 0, 31.346599578857422, 40.76539993286133, 0, 30.573999404907227, 42.03929901123047, 0, 30.573999404907227, 40.845001220703125, 1.791450023651123, 30.573999404907227, 42.218299865722656, 1.679479956626892, 31.25320053100586, 43.49729919433594, 0, 31.279399871826172, 42.47100067138672, 1.4331599473953247, 31.51740074157715, 43.67150115966797, 0, 31.55929946899414, 41.69169998168945, 1.1868300437927246, 31.309900283813477, 42.71110153198242, 0, 31.346599578857422, 39.969200134277344, 1.074869990348816, 30.573999404907227, 40.76539993286133, 0, 30.573999404907227, 38.21760177612305, 2.3886001110076904, 30.573999404907227, 39.40439987182617, 2.2393100261688232, 31.195499420166016, 39.829898834228516, 1.9108799695968628, 31.424999237060547, 39.44919967651367, 1.582450032234192, 31.229000091552734, 38.21760177612305, 1.4331599473953247, 30.573999404907227, 35.5900993347168, 1.791450023651123, 30.573999404907227, 36.59049987792969, 1.679479956626892, 31.137699127197266, 37.18870162963867, 1.4331599473953247, 31.332599639892578, 37.206600189208984, 1.1868300437927246, 31.1481990814209, 36.46590042114258, 1.074869990348816, 30.573999404907227, 34.39580154418945, 0, 30.573999404907227, 35.3114013671875, 0, 31.111499786376953, 35.98820114135742, 0, 31.290599822998047, 36.187198638916016, 0, 31.111499786376953, 35.669700622558594, 0, 30.573999404907227, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, 4.004499912261963, -1.7077000141143799, 39.501399993896484, 4.339280128479004, 0, 39.501399993896484, 3.8207099437713623, -1.6290700435638428, 37.97869873046875, 4.140230178833008, 0, 37.97869873046875, 2.314160108566284, -0.985912024974823, 36.09769821166992, 2.5080299377441406, 0, 36.09769821166992, 2.3503799438476562, -1.0000300407409668, 34.39580154418945, 2.547840118408203, 0, 34.39580154418945, 0, 0, 40.12839889526367, 3.0849199295043945, -3.0849199295043945, 39.501399993896484, 2.943150043487549, -2.943150043487549, 37.97869873046875, 1.782039999961853, -1.782039999961853, 36.09769821166992, 1.8089599609375, -1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, 1.7077000141143799, -4.004499912261963, 39.501399993896484, 1.6290700435638428, -3.8207099437713623, 37.97869873046875, 0.985912024974823, -2.314160108566284, 36.09769821166992, 1.0000300407409668, -2.3503799438476562, 34.39580154418945, 0, 0, 40.12839889526367, 0, -4.339280128479004, 39.501399993896484, 0, -4.140230178833008, 37.97869873046875, 0, -2.5080299377441406, 36.09769821166992, 0, -2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, -1.7077000141143799, -4.004499912261963, 39.501399993896484, 0, -4.339280128479004, 39.501399993896484, -1.6290700435638428, -3.8207099437713623, 37.97869873046875, 0, -4.140230178833008, 37.97869873046875, -0.985912024974823, -2.314160108566284, 36.09769821166992, 0, -2.5080299377441406, 36.09769821166992, -1.0000300407409668, -2.3503799438476562, 34.39580154418945, 0, -2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, -3.0849199295043945, -3.0849199295043945, 39.501399993896484, -2.943150043487549, -2.943150043487549, 37.97869873046875, -1.782039999961853, -1.782039999961853, 36.09769821166992, -1.8089599609375, -1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, -4.004499912261963, -1.7077000141143799, 39.501399993896484, -3.8207099437713623, -1.6290700435638428, 37.97869873046875, -2.314160108566284, -0.985912024974823, 36.09769821166992, -2.3503799438476562, -1.0000300407409668, 34.39580154418945, 0, 0, 40.12839889526367, -4.339280128479004, 0, 39.501399993896484, -4.140230178833008, 0, 37.97869873046875, -2.5080299377441406, 0, 36.09769821166992, -2.547840118408203, 0, 34.39580154418945, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, -4.004499912261963, 1.7077000141143799, 39.501399993896484, -4.339280128479004, 0, 39.501399993896484, -3.8207099437713623, 1.6290700435638428, 37.97869873046875, -4.140230178833008, 0, 37.97869873046875, -2.314160108566284, 0.985912024974823, 36.09769821166992, -2.5080299377441406, 0, 36.09769821166992, -2.3503799438476562, 1.0000300407409668, 34.39580154418945, -2.547840118408203, 0, 34.39580154418945, 0, 0, 40.12839889526367, -3.0849199295043945, 3.0849199295043945, 39.501399993896484, -2.943150043487549, 2.943150043487549, 37.97869873046875, -1.782039999961853, 1.782039999961853, 36.09769821166992, -1.8089599609375, 1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, -1.7077000141143799, 4.004499912261963, 39.501399993896484, -1.6290700435638428, 3.8207099437713623, 37.97869873046875, -0.985912024974823, 2.314160108566284, 36.09769821166992, -1.0000300407409668, 2.3503799438476562, 34.39580154418945, 0, 0, 40.12839889526367, 0, 4.339280128479004, 39.501399993896484, 0, 4.140230178833008, 37.97869873046875, 0, 2.5080299377441406, 36.09769821166992, 0, 2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, 0, 0, 40.12839889526367, 1.7077000141143799, 4.004499912261963, 39.501399993896484, 0, 4.339280128479004, 39.501399993896484, 1.6290700435638428, 3.8207099437713623, 37.97869873046875, 0, 4.140230178833008, 37.97869873046875, 0.985912024974823, 2.314160108566284, 36.09769821166992, 0, 2.5080299377441406, 36.09769821166992, 1.0000300407409668, 2.3503799438476562, 34.39580154418945, 0, 2.547840118408203, 34.39580154418945, 0, 0, 40.12839889526367, 3.0849199295043945, 3.0849199295043945, 39.501399993896484, 2.943150043487549, 2.943150043487549, 37.97869873046875, 1.782039999961853, 1.782039999961853, 36.09769821166992, 1.8089599609375, 1.8089599609375, 34.39580154418945, 0, 0, 40.12839889526367, 4.004499912261963, 1.7077000141143799, 39.501399993896484, 3.8207099437713623, 1.6290700435638428, 37.97869873046875, 2.314160108566284, 0.985912024974823, 36.09769821166992, 2.3503799438476562, 1.0000300407409668, 34.39580154418945, 0, 0, 40.12839889526367, 4.339280128479004, 0, 39.501399993896484, 4.140230178833008, 0, 37.97869873046875, 2.5080299377441406, 0, 36.09769821166992, 2.547840118408203, 0, 34.39580154418945, 2.547840118408203, 0, 34.39580154418945, 2.3503799438476562, -1.0000300407409668, 34.39580154418945, 5.361800193786621, -2.2813100814819336, 33.261199951171875, 5.812250137329102, 0, 33.261199951171875, 9.695320129394531, -4.125110149383545, 32.484901428222656, 10.50979995727539, 0, 32.484901428222656, 13.58810043334961, -5.781400203704834, 31.708599090576172, 14.729700088500977, 0, 31.708599090576172, 15.27750015258789, -6.5001702308654785, 30.573999404907227, 16.56089973449707, 0, 30.573999404907227, 1.8089599609375, -1.8089599609375, 34.39580154418945, 4.126699924468994, -4.126699924468994, 33.261199951171875, 7.461979866027832, -7.461979866027832, 32.484901428222656, 10.458100318908691, -10.458100318908691, 31.708599090576172, 11.758299827575684, -11.758299827575684, 30.573999404907227, 1.0000300407409668, -2.3503799438476562, 34.39580154418945, 2.2813100814819336, -5.361800193786621, 33.261199951171875, 4.125110149383545, -9.695320129394531, 32.484901428222656, 5.781400203704834, -13.58810043334961, 31.708599090576172, 6.5001702308654785, -15.27750015258789, 30.573999404907227, 0, -2.547840118408203, 34.39580154418945, 0, -5.812250137329102, 33.261199951171875, 0, -10.50979995727539, 32.484901428222656, 0, -14.729700088500977, 31.708599090576172, 0, -16.56089973449707, 30.573999404907227, 0, -2.547840118408203, 34.39580154418945, -1.0000300407409668, -2.3503799438476562, 34.39580154418945, -2.2813100814819336, -5.361800193786621, 33.261199951171875, 0, -5.812250137329102, 33.261199951171875, -4.125110149383545, -9.695320129394531, 32.484901428222656, 0, -10.50979995727539, 32.484901428222656, -5.781400203704834, -13.58810043334961, 31.708599090576172, 0, -14.729700088500977, 31.708599090576172, -6.5001702308654785, -15.27750015258789, 30.573999404907227, 0, -16.56089973449707, 30.573999404907227, -1.8089599609375, -1.8089599609375, 34.39580154418945, -4.126699924468994, -4.126699924468994, 33.261199951171875, -7.461979866027832, -7.461979866027832, 32.484901428222656, -10.458100318908691, -10.458100318908691, 31.708599090576172, -11.758299827575684, -11.758299827575684, 30.573999404907227, -2.3503799438476562, -1.0000300407409668, 34.39580154418945, -5.361800193786621, -2.2813100814819336, 33.261199951171875, -9.695320129394531, -4.125110149383545, 32.484901428222656, -13.58810043334961, -5.781400203704834, 31.708599090576172, -15.27750015258789, -6.5001702308654785, 30.573999404907227, -2.547840118408203, 0, 34.39580154418945, -5.812250137329102, 0, 33.261199951171875, -10.50979995727539, 0, 32.484901428222656, -14.729700088500977, 0, 31.708599090576172, -16.56089973449707, 0, 30.573999404907227, -2.547840118408203, 0, 34.39580154418945, -2.3503799438476562, 1.0000300407409668, 34.39580154418945, -5.361800193786621, 2.2813100814819336, 33.261199951171875, -5.812250137329102, 0, 33.261199951171875, -9.695320129394531, 4.125110149383545, 32.484901428222656, -10.50979995727539, 0, 32.484901428222656, -13.58810043334961, 5.781400203704834, 31.708599090576172, -14.729700088500977, 0, 31.708599090576172, -15.27750015258789, 6.5001702308654785, 30.573999404907227, -16.56089973449707, 0, 30.573999404907227, -1.8089599609375, 1.8089599609375, 34.39580154418945, -4.126699924468994, 4.126699924468994, 33.261199951171875, -7.461979866027832, 7.461979866027832, 32.484901428222656, -10.458100318908691, 10.458100318908691, 31.708599090576172, -11.758299827575684, 11.758299827575684, 30.573999404907227, -1.0000300407409668, 2.3503799438476562, 34.39580154418945, -2.2813100814819336, 5.361800193786621, 33.261199951171875, -4.125110149383545, 9.695320129394531, 32.484901428222656, -5.781400203704834, 13.58810043334961, 31.708599090576172, -6.5001702308654785, 15.27750015258789, 30.573999404907227, 0, 2.547840118408203, 34.39580154418945, 0, 5.812250137329102, 33.261199951171875, 0, 10.50979995727539, 32.484901428222656, 0, 14.729700088500977, 31.708599090576172, 0, 16.56089973449707, 30.573999404907227, 0, 2.547840118408203, 34.39580154418945, 1.0000300407409668, 2.3503799438476562, 34.39580154418945, 2.2813100814819336, 5.361800193786621, 33.261199951171875, 0, 5.812250137329102, 33.261199951171875, 4.125110149383545, 9.695320129394531, 32.484901428222656, 0, 10.50979995727539, 32.484901428222656, 5.781400203704834, 13.58810043334961, 31.708599090576172, 0, 14.729700088500977, 31.708599090576172, 6.5001702308654785, 15.27750015258789, 30.573999404907227, 0, 16.56089973449707, 30.573999404907227, 1.8089599609375, 1.8089599609375, 34.39580154418945, 4.126699924468994, 4.126699924468994, 33.261199951171875, 7.461979866027832, 7.461979866027832, 32.484901428222656, 10.458100318908691, 10.458100318908691, 31.708599090576172, 11.758299827575684, 11.758299827575684, 30.573999404907227, 2.3503799438476562, 1.0000300407409668, 34.39580154418945, 5.361800193786621, 2.2813100814819336, 33.261199951171875, 9.695320129394531, 4.125110149383545, 32.484901428222656, 13.58810043334961, 5.781400203704834, 31.708599090576172, 15.27750015258789, 6.5001702308654785, 30.573999404907227, 2.547840118408203, 0, 34.39580154418945, 5.812250137329102, 0, 33.261199951171875, 10.50979995727539, 0, 32.484901428222656, 14.729700088500977, 0, 31.708599090576172, 16.56089973449707, 0, 30.573999404907227 ]);
var teapotNormals = new Float32Array([ -0.9667419791221619, 0, -0.25575199723243713, -0.8930140137672424, 0.3698819875717163, -0.2563450038433075, -0.8934370279312134, 0.36910200119018555, 0.2559970021247864, -0.9668239951133728, 0, 0.2554430067539215, -0.0838799998164177, 0.03550700098276138, 0.9958429932594299, -0.09205400198698044, 0, 0.9957540035247803, 0.629721999168396, -0.2604379951953888, 0.7318620085716248, 0.6820489764213562, 0, 0.7313070297241211, 0.803725004196167, -0.3325839936733246, 0.4933690130710602, 0.8703010082244873, 0, 0.4925200045108795, -0.6834070086479187, 0.6834070086479187, -0.2567310035228729, -0.6835309863090515, 0.6835309863090515, 0.25606799125671387, -0.06492599844932556, 0.06492500007152557, 0.9957759976387024, 0.48139700293540955, -0.48139700293540955, 0.7324709892272949, 0.6148040294647217, -0.6148040294647217, 0.4939970076084137, -0.3698819875717163, 0.8930140137672424, -0.2563450038433075, -0.36910200119018555, 0.8934370279312134, 0.2559959888458252, -0.03550700098276138, 0.0838790014386177, 0.9958429932594299, 0.26043900847435, -0.6297230124473572, 0.7318609952926636, 0.3325839936733246, -0.803725004196167, 0.4933690130710602, -0.002848000032827258, 0.9661769866943359, -0.25786298513412476, -0.001921999966725707, 0.9670090079307556, 0.2547360062599182, -0.00026500000967644155, 0.09227199852466583, 0.9957339763641357, 0.00002300000051036477, -0.6820600032806396, 0.7312960028648376, 0, -0.8703010082244873, 0.4925200045108795, -0.002848000032827258, 0.9661769866943359, -0.25786298513412476, 0.37905800342559814, 0.852770984172821, -0.35929998755455017, 0.37711000442504883, 0.9140909910202026, 0.14908500015735626, -0.001921999966725707, 0.9670090079307556, 0.2547360062599182, 0.0275030005723238, 0.12255500257015228, 0.9920809864997864, -0.00026500000967644155, 0.09227199852466583, 0.9957339763641357, -0.26100900769233704, -0.6353650093078613, 0.7267630100250244, 0.00002300000051036477, -0.6820600032806396, 0.7312960028648376, -0.33248499035835266, -0.8042709827423096, 0.4925459921360016, 0, -0.8703010082244873, 0.4925200045108795, 0.6635469794273376, 0.6252639889717102, -0.4107919931411743, 0.712664008140564, 0.6976209878921509, 0.07372400164604187, 0.09972699731588364, 0.12198299914598465, 0.98750901222229, -0.4873189926147461, -0.4885669946670532, 0.7237560153007507, -0.6152420043945312, -0.6154839992523193, 0.4926010072231293, 0.8800280094146729, 0.3387089967727661, -0.3329069912433624, 0.9172769784927368, 0.36149299144744873, 0.16711199283599854, 0.11358699947595596, 0.04806999862194061, 0.9923650026321411, -0.6341490149497986, -0.2618879973888397, 0.7275090217590332, -0.8041260242462158, -0.33270499110221863, 0.49263399839401245, 0.9666900038719177, -0.010453999973833561, -0.2557379901409149, 0.967441976070404, -0.00810300000011921, 0.25296199321746826, 0.0934389978647232, -0.0012799999676644802, 0.9956240057945251, -0.6821659803390503, 0.0003429999924264848, 0.7311969995498657, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, 0.9666900038719177, -0.010453999973833561, -0.2557379901409149, 0.8930140137672424, -0.3698819875717163, -0.2563450038433075, 0.8934370279312134, -0.36910200119018555, 0.2559970021247864, 0.967441976070404, -0.00810300000011921, 0.25296199321746826, 0.0838799998164177, -0.03550700098276138, 0.9958429932594299, 0.0934389978647232, -0.0012799999676644802, 0.9956240057945251, -0.629721999168396, 0.2604379951953888, 0.7318620085716248, -0.6821659803390503, 0.0003429999924264848, 0.7311969995498657, -0.803725004196167, 0.3325839936733246, 0.4933690130710602, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, 0.6834070086479187, -0.6834070086479187, -0.2567310035228729, 0.6835309863090515, -0.6835309863090515, 0.25606799125671387, 0.06492599844932556, -0.06492500007152557, 0.9957759976387024, -0.48139700293540955, 0.48139700293540955, 0.7324709892272949, -0.6148040294647217, 0.6148040294647217, 0.4939970076084137, 0.3698819875717163, -0.8930140137672424, -0.2563450038433075, 0.36910200119018555, -0.8934370279312134, 0.2559959888458252, 0.03550700098276138, -0.0838790014386177, 0.9958429932594299, -0.26043900847435, 0.6297230124473572, 0.7318609952926636, -0.3325839936733246, 0.803725004196167, 0.4933690130710602, 0, -0.9667419791221619, -0.25575199723243713, 0, -0.9668239951133728, 0.2554430067539215, 0, -0.09205400198698044, 0.9957540035247803, 0, 0.6820489764213562, 0.7313070297241211, 0, 0.8703010082244873, 0.4925200045108795, 0, -0.9667419791221619, -0.25575199723243713, -0.3698819875717163, -0.8930140137672424, -0.2563450038433075, -0.36910200119018555, -0.8934370279312134, 0.2559970021247864, 0, -0.9668239951133728, 0.2554430067539215, -0.03550700098276138, -0.0838799998164177, 0.9958429932594299, 0, -0.09205400198698044, 0.9957540035247803, 0.2604379951953888, 0.629721999168396, 0.7318620085716248, 0, 0.6820489764213562, 0.7313070297241211, 0.3325839936733246, 0.803725004196167, 0.4933690130710602, 0, 0.8703010082244873, 0.4925200045108795, -0.6834070086479187, -0.6834070086479187, -0.2567310035228729, -0.6835309863090515, -0.6835309863090515, 0.25606799125671387, -0.06492500007152557, -0.06492599844932556, 0.9957759976387024, 0.48139700293540955, 0.48139700293540955, 0.7324709892272949, 0.6148040294647217, 0.6148040294647217, 0.4939970076084137, -0.8930140137672424, -0.3698819875717163, -0.2563450038433075, -0.8934370279312134, -0.36910200119018555, 0.2559959888458252, -0.0838790014386177, -0.03550700098276138, 0.9958429932594299, 0.6297230124473572, 0.26043900847435, 0.7318609952926636, 0.803725004196167, 0.3325839936733246, 0.4933690130710602, -0.9667419791221619, 0, -0.25575199723243713, -0.9668239951133728, 0, 0.2554430067539215, -0.09205400198698044, 0, 0.9957540035247803, 0.6820489764213562, 0, 0.7313070297241211, 0.8703010082244873, 0, 0.4925200045108795, 0.8703010082244873, 0, 0.4925200045108795, 0.803725004196167, -0.3325839936733246, 0.4933690130710602, 0.8454390168190002, -0.34983500838279724, 0.40354499220848083, 0.9153209924697876, 0, 0.4027250111103058, 0.8699960112571716, -0.36004599928855896, 0.33685898780822754, 0.9418079853057861, 0, 0.33615100383758545, 0.9041929841041565, -0.37428000569343567, 0.20579099655151367, 0.9786900281906128, 0, 0.20534199476242065, 0.9218789935112, -0.38175201416015625, -0.06636899709701538, 0.9978039860725403, 0, -0.06623899936676025, 0.6148040294647217, -0.6148040294647217, 0.4939970076084137, 0.6468020081520081, -0.6468020081520081, 0.40409600734710693, 0.6656550168991089, -0.6656550168991089, 0.3373520076274872, 0.6919230222702026, -0.6919230222702026, 0.20611999928951263, 0.7055429816246033, -0.7055429816246033, -0.06647899746894836, 0.3325839936733246, -0.803725004196167, 0.4933690130710602, 0.34983500838279724, -0.8454390168190002, 0.40354499220848083, 0.36004701256752014, -0.8699960112571716, 0.33685800433158875, 0.37428000569343567, -0.9041929841041565, 0.20579099655151367, 0.38175201416015625, -0.9218789935112, -0.06636899709701538, 0, -0.8703010082244873, 0.4925200045108795, 0, -0.9153209924697876, 0.4027250111103058, 0, -0.9418079853057861, 0.33615100383758545, 0, -0.9786900281906128, 0.20534199476242065, 0, -0.9978039860725403, -0.06623899936676025, 0, -0.8703010082244873, 0.4925200045108795, -0.33248499035835266, -0.8042709827423096, 0.4925459921360016, -0.34983500838279724, -0.8454390168190002, 0.40354499220848083, 0, -0.9153209924697876, 0.4027250111103058, -0.36004599928855896, -0.8699960112571716, 0.33685898780822754, 0, -0.9418079853057861, 0.33615100383758545, -0.37428000569343567, -0.9041929841041565, 0.20579099655151367, 0, -0.9786900281906128, 0.20534199476242065, -0.38175201416015625, -0.9218789935112, -0.06636899709701538, 0, -0.9978039860725403, -0.06623899936676025, -0.6152420043945312, -0.6154839992523193, 0.4926010072231293, -0.6468020081520081, -0.6468020081520081, 0.40409600734710693, -0.6656550168991089, -0.6656550168991089, 0.3373520076274872, -0.6919230222702026, -0.6919230222702026, 0.20611999928951263, -0.7055429816246033, -0.7055429816246033, -0.06647899746894836, -0.8041260242462158, -0.33270499110221863, 0.49263399839401245, -0.8454390168190002, -0.34983500838279724, 0.40354499220848083, -0.8699960112571716, -0.36004701256752014, 0.33685800433158875, -0.9041929841041565, -0.37428000569343567, 0.20579099655151367, -0.9218789935112, -0.38175201416015625, -0.06636899709701538, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, -0.9153209924697876, 0, 0.4027250111103058, -0.9418079853057861, 0, 0.33615100383758545, -0.9786900281906128, 0, 0.20534199476242065, -0.9978039860725403, 0, -0.06623899936676025, -0.8703219890594482, 0.00005400000009103678, 0.492482990026474, -0.803725004196167, 0.3325839936733246, 0.4933690130710602, -0.8454390168190002, 0.34983500838279724, 0.40354499220848083, -0.9153209924697876, 0, 0.4027250111103058, -0.8699960112571716, 0.36004599928855896, 0.33685898780822754, -0.9418079853057861, 0, 0.33615100383758545, -0.9041929841041565, 0.37428000569343567, 0.20579099655151367, -0.9786900281906128, 0, 0.20534199476242065, -0.9218789935112, 0.38175201416015625, -0.06636899709701538, -0.9978039860725403, 0, -0.06623899936676025, -0.6148040294647217, 0.6148040294647217, 0.4939970076084137, -0.6468020081520081, 0.6468020081520081, 0.40409600734710693, -0.6656550168991089, 0.6656550168991089, 0.3373520076274872, -0.6919230222702026, 0.6919230222702026, 0.20611999928951263, -0.7055429816246033, 0.7055429816246033, -0.06647899746894836, -0.3325839936733246, 0.803725004196167, 0.4933690130710602, -0.34983500838279724, 0.8454390168190002, 0.40354499220848083, -0.36004701256752014, 0.8699960112571716, 0.33685800433158875, -0.37428000569343567, 0.9041929841041565, 0.20579099655151367, -0.38175201416015625, 0.9218789935112, -0.06636899709701538, 0, 0.8703010082244873, 0.4925200045108795, 0, 0.9153209924697876, 0.4027250111103058, 0, 0.9418079853057861, 0.33615100383758545, 0, 0.9786900281906128, 0.20534199476242065, 0, 0.9978039860725403, -0.06623899936676025, 0, 0.8703010082244873, 0.4925200045108795, 0.3325839936733246, 0.803725004196167, 0.4933690130710602, 0.34983500838279724, 0.8454390168190002, 0.40354499220848083, 0, 0.9153209924697876, 0.4027250111103058, 0.36004599928855896, 0.8699960112571716, 0.33685898780822754, 0, 0.9418079853057861, 0.33615100383758545, 0.37428000569343567, 0.9041929841041565, 0.20579099655151367, 0, 0.9786900281906128, 0.20534199476242065, 0.38175201416015625, 0.9218789935112, -0.06636899709701538, 0, 0.9978039860725403, -0.06623899936676025, 0.6148040294647217, 0.6148040294647217, 0.4939970076084137, 0.6468020081520081, 0.6468020081520081, 0.40409600734710693, 0.6656550168991089, 0.6656550168991089, 0.3373520076274872, 0.6919230222702026, 0.6919230222702026, 0.20611999928951263, 0.7055429816246033, 0.7055429816246033, -0.06647899746894836, 0.803725004196167, 0.3325839936733246, 0.4933690130710602, 0.8454390168190002, 0.34983500838279724, 0.40354499220848083, 0.8699960112571716, 0.36004701256752014, 0.33685800433158875, 0.9041929841041565, 0.37428000569343567, 0.20579099655151367, 0.9218789935112, 0.38175201416015625, -0.06636899709701538, 0.8703010082244873, 0, 0.4925200045108795, 0.9153209924697876, 0, 0.4027250111103058, 0.9418079853057861, 0, 0.33615100383758545, 0.9786900281906128, 0, 0.20534199476242065, 0.9978039860725403, 0, -0.06623899936676025, 0.9978039860725403, 0, -0.06623899936676025, 0.9218789935112, -0.38175201416015625, -0.06636899709701538, 0.8314369916915894, -0.3441790044307709, -0.4361799955368042, 0.9001820087432861, 0, -0.4355129897594452, 0.6735119819641113, -0.2785939872264862, -0.6846650242805481, 0.7296109795570374, 0, -0.6838629841804504, 0.6403989791870117, -0.26487401127815247, -0.7209240198135376, 0.6939510107040405, 0, -0.7200220227241516, 0.7329490184783936, -0.303166002035141, -0.6089959740638733, 0.7939500212669373, 0, -0.6079840064048767, 0.7055429816246033, -0.7055429816246033, -0.06647899746894836, 0.6360920071601868, -0.6360920071601868, -0.4367780089378357, 0.5149649977684021, -0.5149649977684021, -0.6852890253067017, 0.48965099453926086, -0.48965099453926086, -0.7214459776878357, 0.5605549812316895, -0.5605549812316895, -0.6095539927482605, 0.38175201416015625, -0.9218789935112, -0.06636899709701538, 0.3441790044307709, -0.8314369916915894, -0.4361799955368042, 0.2785939872264862, -0.6735119819641113, -0.6846650242805481, 0.26487401127815247, -0.6403989791870117, -0.7209240198135376, 0.303166002035141, -0.7329490184783936, -0.6089959740638733, 0, -0.9978039860725403, -0.06623899936676025, 0, -0.9001820087432861, -0.4355129897594452, 0, -0.7296109795570374, -0.6838629841804504, 0, -0.6939510107040405, -0.7200220227241516, 0, -0.7939500212669373, -0.6079840064048767, 0, -0.9978039860725403, -0.06623899936676025, -0.38175201416015625, -0.9218789935112, -0.06636899709701538, -0.3441790044307709, -0.8314369916915894, -0.4361799955368042, 0, -0.9001820087432861, -0.4355129897594452, -0.2785939872264862, -0.6735119819641113, -0.6846650242805481, 0, -0.7296109795570374, -0.6838629841804504, -0.26487401127815247, -0.6403989791870117, -0.7209240198135376, 0, -0.6939510107040405, -0.7200220227241516, -0.303166002035141, -0.7329490184783936, -0.6089959740638733, 0, -0.7939500212669373, -0.6079840064048767, -0.7055429816246033, -0.7055429816246033, -0.06647899746894836, -0.6360920071601868, -0.6360920071601868, -0.4367780089378357, -0.5149649977684021, -0.5149649977684021, -0.6852890253067017, -0.48965099453926086, -0.48965099453926086, -0.7214459776878357, -0.5605549812316895, -0.5605549812316895, -0.6095539927482605, -0.9218789935112, -0.38175201416015625, -0.06636899709701538, -0.8314369916915894, -0.3441790044307709, -0.4361799955368042, -0.6735119819641113, -0.2785939872264862, -0.6846650242805481, -0.6403989791870117, -0.26487401127815247, -0.7209240198135376, -0.7329490184783936, -0.303166002035141, -0.6089959740638733, -0.9978039860725403, 0, -0.06623899936676025, -0.9001820087432861, 0, -0.4355129897594452, -0.7296109795570374, 0, -0.6838629841804504, -0.6939510107040405, 0, -0.7200220227241516, -0.7939500212669373, 0, -0.6079840064048767, -0.9978039860725403, 0, -0.06623899936676025, -0.9218789935112, 0.38175201416015625, -0.06636899709701538, -0.8314369916915894, 0.3441790044307709, -0.4361799955368042, -0.9001820087432861, 0, -0.4355129897594452, -0.6735119819641113, 0.2785939872264862, -0.6846650242805481, -0.7296109795570374, 0, -0.6838629841804504, -0.6403989791870117, 0.26487401127815247, -0.7209240198135376, -0.6939510107040405, 0, -0.7200220227241516, -0.7329490184783936, 0.303166002035141, -0.6089959740638733, -0.7939500212669373, 0, -0.6079840064048767, -0.7055429816246033, 0.7055429816246033, -0.06647899746894836, -0.6360920071601868, 0.6360920071601868, -0.4367780089378357, -0.5149649977684021, 0.5149649977684021, -0.6852890253067017, -0.48965099453926086, 0.48965099453926086, -0.7214459776878357, -0.5605549812316895, 0.5605549812316895, -0.6095539927482605, -0.38175201416015625, 0.9218789935112, -0.06636899709701538, -0.3441790044307709, 0.8314369916915894, -0.4361799955368042, -0.2785939872264862, 0.6735119819641113, -0.6846650242805481, -0.26487401127815247, 0.6403989791870117, -0.7209240198135376, -0.303166002035141, 0.7329490184783936, -0.6089959740638733, 0, 0.9978039860725403, -0.06623899936676025, 0, 0.9001820087432861, -0.4355129897594452, 0, 0.7296109795570374, -0.6838629841804504, 0, 0.6939510107040405, -0.7200220227241516, 0, 0.7939500212669373, -0.6079840064048767, 0, 0.9978039860725403, -0.06623899936676025, 0.38175201416015625, 0.9218789935112, -0.06636899709701538, 0.3441790044307709, 0.8314369916915894, -0.4361799955368042, 0, 0.9001820087432861, -0.4355129897594452, 0.2785939872264862, 0.6735119819641113, -0.6846650242805481, 0, 0.7296109795570374, -0.6838629841804504, 0.26487401127815247, 0.6403989791870117, -0.7209240198135376, 0, 0.6939510107040405, -0.7200220227241516, 0.303166002035141, 0.7329490184783936, -0.6089959740638733, 0, 0.7939500212669373, -0.6079840064048767, 0.7055429816246033, 0.7055429816246033, -0.06647899746894836, 0.6360920071601868, 0.6360920071601868, -0.4367780089378357, 0.5149649977684021, 0.5149649977684021, -0.6852890253067017, 0.48965099453926086, 0.48965099453926086, -0.7214459776878357, 0.5605549812316895, 0.5605549812316895, -0.6095539927482605, 0.9218789935112, 0.38175201416015625, -0.06636899709701538, 0.8314369916915894, 0.3441790044307709, -0.4361799955368042, 0.6735119819641113, 0.2785939872264862, -0.6846650242805481, 0.6403989791870117, 0.26487401127815247, -0.7209240198135376, 0.7329490184783936, 0.303166002035141, -0.6089959740638733, 0.9978039860725403, 0, -0.06623899936676025, 0.9001820087432861, 0, -0.4355129897594452, 0.7296109795570374, 0, -0.6838629841804504, 0.6939510107040405, 0, -0.7200220227241516, 0.7939500212669373, 0, -0.6079840064048767, 0.7939500212669373, 0, -0.6079840064048767, 0.7329490184783936, -0.303166002035141, -0.6089959740638733, 0.576229989528656, -0.23821599781513214, -0.7818009853363037, 0.6238600015640259, 0, -0.7815359830856323, 0.16362899541854858, -0.06752700358629227, -0.9842079877853394, 0.17729100584983826, 0, -0.984158992767334, 0.04542100057005882, -0.018735000863671303, -0.9987919926643372, 0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, 0, 0, -1, 0.5605549812316895, -0.5605549812316895, -0.6095539927482605, 0.44041600823402405, -0.44041600823402405, -0.7823479771614075, 0.12490200251340866, -0.12490200251340866, -0.9842759966850281, 0.034662000834941864, -0.034662000834941864, -0.9987980127334595, 0, 0, -1, 0.303166002035141, -0.7329490184783936, -0.6089959740638733, 0.23821599781513214, -0.576229989528656, -0.7818009853363037, 0.06752700358629227, -0.16362899541854858, -0.9842079877853394, 0.018735000863671303, -0.04542100057005882, -0.9987919926643372, 0, 0, -1, 0, -0.7939500212669373, -0.6079840064048767, 0, -0.6238600015640259, -0.7815359830856323, 0, -0.17729100584983826, -0.984158992767334, 0, -0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, -0.7939500212669373, -0.6079840064048767, -0.303166002035141, -0.7329490184783936, -0.6089959740638733, -0.23821599781513214, -0.576229989528656, -0.7818009853363037, 0, -0.6238600015640259, -0.7815359830856323, -0.06752700358629227, -0.16362899541854858, -0.9842079877853394, 0, -0.17729100584983826, -0.984158992767334, -0.018735000863671303, -0.04542100057005882, -0.9987919926643372, 0, -0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, 0, -1, -0.5605549812316895, -0.5605549812316895, -0.6095539927482605, -0.44041600823402405, -0.44041600823402405, -0.7823479771614075, -0.12490200251340866, -0.12490200251340866, -0.9842759966850281, -0.034662000834941864, -0.034662000834941864, -0.9987980127334595, 0, 0, -1, -0.7329490184783936, -0.303166002035141, -0.6089959740638733, -0.576229989528656, -0.23821599781513214, -0.7818009853363037, -0.16362899541854858, -0.06752700358629227, -0.9842079877853394, -0.04542100057005882, -0.018735000863671303, -0.9987919926643372, 0, 0, -1, -0.7939500212669373, 0, -0.6079840064048767, -0.6238600015640259, 0, -0.7815359830856323, -0.17729100584983826, 0, -0.984158992767334, -0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, -0.7939500212669373, 0, -0.6079840064048767, -0.7329490184783936, 0.303166002035141, -0.6089959740638733, -0.576229989528656, 0.23821599781513214, -0.7818009853363037, -0.6238600015640259, 0, -0.7815359830856323, -0.16362899541854858, 0.06752700358629227, -0.9842079877853394, -0.17729100584983826, 0, -0.984158992767334, -0.04542100057005882, 0.018735000863671303, -0.9987919926643372, -0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, 0, 0, -1, -0.5605549812316895, 0.5605549812316895, -0.6095539927482605, -0.44041600823402405, 0.44041600823402405, -0.7823479771614075, -0.12490200251340866, 0.12490200251340866, -0.9842759966850281, -0.034662000834941864, 0.034662000834941864, -0.9987980127334595, 0, 0, -1, -0.303166002035141, 0.7329490184783936, -0.6089959740638733, -0.23821599781513214, 0.576229989528656, -0.7818009853363037, -0.06752700358629227, 0.16362899541854858, -0.9842079877853394, -0.018735000863671303, 0.04542100057005882, -0.9987919926643372, 0, 0, -1, 0, 0.7939500212669373, -0.6079840064048767, 0, 0.6238600015640259, -0.7815359830856323, 0, 0.17729100584983826, -0.984158992767334, 0, 0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, 0.7939500212669373, -0.6079840064048767, 0.303166002035141, 0.7329490184783936, -0.6089959740638733, 0.23821599781513214, 0.576229989528656, -0.7818009853363037, 0, 0.6238600015640259, -0.7815359830856323, 0.06752700358629227, 0.16362899541854858, -0.9842079877853394, 0, 0.17729100584983826, -0.984158992767334, 0.018735000863671303, 0.04542100057005882, -0.9987919926643372, 0, 0.04920699819922447, -0.9987890124320984, 0, 0, -1, 0, 0, -1, 0.5605549812316895, 0.5605549812316895, -0.6095539927482605, 0.44041600823402405, 0.44041600823402405, -0.7823479771614075, 0.12490200251340866, 0.12490200251340866, -0.9842759966850281, 0.034662000834941864, 0.034662000834941864, -0.9987980127334595, 0, 0, -1, 0.7329490184783936, 0.303166002035141, -0.6089959740638733, 0.576229989528656, 0.23821599781513214, -0.7818009853363037, 0.16362899541854858, 0.06752700358629227, -0.9842079877853394, 0.04542100057005882, 0.018735000863671303, -0.9987919926643372, 0, 0, -1, 0.7939500212669373, 0, -0.6079840064048767, 0.6238600015640259, 0, -0.7815359830856323, 0.17729100584983826, 0, -0.984158992767334, 0.04920699819922447, 0, -0.9987890124320984, 0, 0, -1, 0.007784999907016754, 0.00021499999274965376, -0.999970018863678, 0.007038000039756298, -0.5829259753227234, -0.8124949932098389, 0.0361270010471344, -0.5456140041351318, -0.837257981300354, 0.03913800045847893, 0.0009879999561235309, -0.9992330074310303, 0.16184599697589874, -0.5630490183830261, -0.8104209899902344, 0.17951199412345886, 0.0043680001981556416, -0.9837459921836853, 0.4823650121688843, -0.6427459716796875, -0.5951480269432068, 0.6122999787330627, 0.010459000244736671, -0.790556013584137, 0.7387199997901917, -0.6641989946365356, -0.11459299921989441, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, -0.0019079999765381217, -0.9867690205574036, 0.1621209979057312, 0.002761000068858266, -0.9998499751091003, 0.017105000093579292, 0.010532000102102757, -0.9972469806671143, 0.07339800149202347, -0.06604000180959702, -0.9893029928207397, 0.13006900250911713, -0.09442699700593948, -0.9953929781913757, 0.016594000160694122, -0.009201999753713608, -0.4902929961681366, 0.8715090155601501, -0.04860600084066391, -0.5394579768180847, 0.8406090140342712, -0.22329799830913544, -0.5527390241622925, 0.8028810024261475, -0.5963649749755859, -0.5751349925994873, 0.5599709749221802, -0.8033369779586792, -0.5916029810905457, 0.06823500245809555, -0.01056000031530857, -0.00010299999848939478, 0.9999439716339111, -0.05879800021648407, -0.0007089999853633344, 0.9982699751853943, -0.28071001172065735, -0.0032679999712854624, 0.9597870111465454, -0.7497230172157288, -0.004267000127583742, 0.6617379784584045, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.01056000031530857, -0.00010299999848939478, 0.9999439716339111, -0.008791999891400337, 0.49032899737358093, 0.8714929819107056, -0.04649300128221512, 0.5387560129165649, 0.8411779999732971, -0.05879800021648407, -0.0007089999853633344, 0.9982699751853943, -0.21790899336338043, 0.5491610169410706, 0.8068069815635681, -0.28071001172065735, -0.0032679999712854624, 0.9597870111465454, -0.5972909927368164, 0.5741199851036072, 0.560027003288269, -0.7497230172157288, -0.004267000127583742, 0.6617379784584045, -0.8040000200271606, 0.5912910103797913, 0.0629120022058487, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.0018050000071525574, 0.986840009689331, 0.16169099509716034, 0.0020310000982135534, 0.999891996383667, 0.014553000219166279, 0.009215000085532665, 0.9981520175933838, 0.060068998485803604, -0.059335000813007355, 0.9917230010032654, 0.11386600136756897, -0.08690100163221359, 0.9961410164833069, 0.01228999998420477, 0.006417000200599432, 0.5830950140953064, -0.812379002571106, 0.03378299996256828, 0.5453730225563049, -0.8375130295753479, 0.1571130007505417, 0.562188982963562, -0.8119469881057739, 0.4844059944152832, 0.6465290188789368, -0.5893650054931641, 0.7388700246810913, 0.6661880016326904, -0.10131999850273132, 0.007784999907016754, 0.00021499999274965376, -0.999970018863678, 0.03913800045847893, 0.0009879999561235309, -0.9992330074310303, 0.17951199412345886, 0.0043680001981556416, -0.9837459921836853, 0.6122999787330627, 0.010459000244736671, -0.790556013584137, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, 0.7387199997901917, -0.6641989946365356, -0.11459299921989441, 0.7256090044975281, -0.6373609900474548, 0.25935098528862, 0.94651198387146, 0.0033569999504834414, 0.3226499855518341, 0.6459450125694275, -0.6077200174331665, 0.46198800206184387, 0.8258299827575684, 0.007451999932527542, 0.5638700127601624, 0.5316150188446045, -0.5586140155792236, 0.6366599798202515, 0.6500110030174255, 0.006936000194400549, 0.759893000125885, 0.4249640107154846, -0.5955389738082886, 0.6817179918289185, 0.5324289798736572, 0.005243999883532524, 0.8464580178260803, -0.09442699700593948, -0.9953929781913757, 0.016594000160694122, -0.04956100136041641, -0.9985759854316711, -0.01975500024855137, -0.03781700134277344, -0.998649001121521, -0.035624999552965164, -0.0379129983484745, -0.9986140131950378, -0.03651199862360954, -0.1688539981842041, -0.9395300149917603, -0.2979460060596466, -0.8033369779586792, -0.5916029810905457, 0.06823500245809555, -0.7423409819602966, -0.5995240211486816, -0.2991659939289093, -0.6196020245552063, -0.5795029997825623, -0.5294060111045837, -0.483707994222641, -0.5438370108604431, -0.6857600212097168, -0.44529199600219727, -0.4131770133972168, -0.7943549752235413, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.9265130162239075, -0.0019950000569224358, -0.3762570023536682, -0.7539200186729431, -0.004317000042647123, -0.6569520235061646, -0.5662239789962769, -0.003461000043898821, -0.8242440223693848, -0.4818040132522583, -0.0018500000005587935, -0.8762770295143127, -0.9973509907722473, -0.0020580000709742308, 0.07271400094032288, -0.8040000200271606, 0.5912910103797913, 0.0629120022058487, -0.7446749806404114, 0.5989770293235779, -0.29442399740219116, -0.9265130162239075, -0.0019950000569224358, -0.3762570023536682, -0.6219490170478821, 0.5781649947166443, -0.5281140208244324, -0.7539200186729431, -0.004317000042647123, -0.6569520235061646, -0.48117101192474365, 0.5428280234336853, -0.6883400082588196, -0.5662239789962769, -0.003461000043898821, -0.8242440223693848, -0.43805500864982605, 0.41574400663375854, -0.7970349788665771, -0.4818040132522583, -0.0018500000005587935, -0.8762770295143127, -0.08690100163221359, 0.9961410164833069, 0.01228999998420477, -0.04433799907565117, 0.9988710284233093, -0.017055999487638474, -0.026177000254392624, 0.9992600083351135, -0.02816700004041195, -0.025293000042438507, 0.9992780089378357, -0.028332000598311424, -0.15748199820518494, 0.9441670179367065, -0.28939300775527954, 0.7388700246810913, 0.6661880016326904, -0.10131999850273132, 0.7282440066337585, 0.63714200258255, 0.25240999460220337, 0.6470540165901184, 0.6082550287246704, 0.4597249925136566, 0.5229939818382263, 0.5621700286865234, 0.6406570076942444, 0.4099780023097992, 0.6046689748764038, 0.6828569769859314, 0.9861519932746887, 0.006668999791145325, -0.16570700705051422, 0.94651198387146, 0.0033569999504834414, 0.3226499855518341, 0.8258299827575684, 0.007451999932527542, 0.5638700127601624, 0.6500110030174255, 0.006936000194400549, 0.759893000125885, 0.5324289798736572, 0.005243999883532524, 0.8464580178260803, -0.230786994099617, 0.006523000076413155, 0.9729819893836975, -0.15287800133228302, -0.7101899981498718, 0.6872109770774841, -0.31672099232673645, -0.7021129727363586, 0.6377500295639038, -0.5489360094070435, 0.0015109999803826213, 0.8358629941940308, -0.6010670065879822, -0.645330011844635, 0.471451997756958, -0.8756710290908813, -0.009891999885439873, 0.4828070104122162, -0.635890007019043, -0.629800021648407, 0.4460900127887726, -0.8775539994239807, -0.01909100078046322, 0.47909700870513916, -0.4357450008392334, -0.670009970664978, 0.6010090112686157, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, 0.11111299693584442, -0.9901599884033203, -0.08506900072097778, 0.22330999374389648, -0.9747260212898254, 0.006539999973028898, 0.19009700417518616, -0.9694579839706421, 0.15496399998664856, 0.005270000081509352, -0.9818699955940247, 0.18948200345039368, -0.011750999838113785, -0.9690240025520325, 0.24668699502944946, 0.3439059853553772, -0.5994120240211487, -0.7227950096130371, 0.5724899768829346, -0.5916270017623901, -0.5676559805870056, 0.7874360084533691, -0.5605109930038452, -0.2564600110054016, 0.6470969915390015, -0.6981409788131714, -0.3063740134239197, 0.4275279939174652, -0.7535750269889832, -0.49934399127960205, 0.4109260141849518, -0.0012839999981224537, -0.9116680026054382, 0.6715199947357178, 0.0008989999769255519, -0.7409859895706177, 0.9220259785652161, 0.00725199980661273, -0.3870599865913391, 0.8469099998474121, 0.01385399978607893, -0.5315560102462769, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.4109260141849518, -0.0012839999981224537, -0.9116680026054382, 0.3411880135536194, 0.6009309887886047, -0.7228230237960815, 0.5786640048027039, 0.591838002204895, -0.5611389875411987, 0.6715199947357178, 0.0008989999769255519, -0.7409859895706177, 0.7848690152168274, 0.5665420293807983, -0.25102001428604126, 0.9220259785652161, 0.00725199980661273, -0.3870599865913391, 0.6426810026168823, 0.7039899826049805, -0.3022570013999939, 0.8469099998474121, 0.01385399978607893, -0.5315560102462769, 0.4185889959335327, 0.7581170201301575, -0.5000420212745667, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.11580599844455719, 0.9901139736175537, -0.07913900166749954, 0.23281100392341614, 0.9724410176277161, 0.012564999982714653, 0.20666299760341644, 0.9662799835205078, 0.15360000729560852, 0.02449899911880493, 0.9865779876708984, 0.16144299507141113, 0.0033809999004006386, 0.9774550199508667, 0.2111150026321411, -0.13491199910640717, 0.7135509848594666, 0.6874909996986389, -0.31953999400138855, 0.7050619721412659, 0.6330729722976685, -0.6039019823074341, 0.6499029994010925, 0.4614419937133789, -0.6318150162696838, 0.6400719881057739, 0.43716898560523987, -0.4243049919605255, 0.6667500138282776, 0.6127070188522339, -0.230786994099617, 0.006523000076413155, 0.9729819893836975, -0.5489360094070435, 0.0015109999803826213, 0.8358629941940308, -0.8756710290908813, -0.009891999885439873, 0.4828070104122162, -0.8775539994239807, -0.01909100078046322, 0.47909700870513916, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, -0.4357450008392334, -0.670009970664978, 0.6010090112686157, -0.25985801219940186, -0.5525479912757874, 0.7919380068778992, -0.42579901218414307, -0.010804999619722366, 0.9047530293464661, 0.009537000209093094, 0.021669000387191772, 0.9997199773788452, 0.022041000425815582, -0.001623000018298626, 0.9997559785842896, 0.4101540148258209, 0.8490809798240662, 0.3329179883003235, 0.9995980262756348, -0.01155600044876337, 0.02587899938225746, 0.5415220260620117, 0.6370009779930115, -0.5486199855804443, 0.7095860242843628, -0.009670999832451344, -0.7045519948005676, -0.011750999838113785, -0.9690240025520325, 0.24668699502944946, 0.046310000121593475, -0.8891720175743103, 0.45522499084472656, -0.010688000358641148, -0.14889900386333466, 0.9887949824333191, -0.04437499865889549, 0.7291200160980225, 0.6829460263252258, 0.12282499670982361, 0.9923850297927856, 0.009232000447809696, 0.4275279939174652, -0.7535750269889832, -0.49934399127960205, 0.48183900117874146, -0.857479989528656, -0.18044300377368927, 0.45527198910713196, -0.49992498755455017, 0.7367510199546814, -0.22054199874401093, 0.3582780063152313, 0.9071930050849915, -0.23591899871826172, 0.7157959938049316, 0.6572499871253967, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.7280910015106201, 0.015584999695420265, -0.6853029727935791, 0.8887389898300171, 0.016679000109434128, 0.4581089913845062, -0.26009801030158997, -0.0007999999797903001, 0.965582013130188, -0.37161099910736084, 0.004416999872773886, 0.9283779859542847, 0.5359240174293518, 0.010503999888896942, -0.8442010283470154, 0.4185889959335327, 0.7581170201301575, -0.5000420212745667, 0.4801650047302246, 0.8588529825210571, -0.17836299538612366, 0.7280910015106201, 0.015584999695420265, -0.6853029727935791, 0.4881030023097992, 0.49794700741767883, 0.7168020009994507, 0.8887389898300171, 0.016679000109434128, 0.4581089913845062, -0.2220049947500229, -0.36189401149749756, 0.9053990244865417, -0.26009801030158997, -0.0007999999797903001, 0.965582013130188, -0.23540399968624115, -0.7104769945144653, 0.6631799936294556, -0.37161099910736084, 0.004416999872773886, 0.9283779859542847, 0.0033809999004006386, 0.9774550199508667, 0.2111150026321411, 0.058719001710414886, 0.8971999883651733, 0.437703013420105, 0.0013249999610707164, 0.164000004529953, 0.9864590167999268, -0.04418899863958359, -0.7303190231323242, 0.6816750168800354, 0.13880200684070587, -0.9897300004959106, -0.034189000725746155, -0.4243049919605255, 0.6667500138282776, 0.6127070188522339, -0.25888898968696594, 0.5453789830207825, 0.7972059845924377, 0.012268000282347202, -0.01928500086069107, 0.9997389912605286, 0.3986299932003021, -0.8456630110740662, 0.3548929989337921, 0.5375639796257019, -0.6107370257377625, -0.5813990235328674, -0.6961889863014221, -0.02449600026011467, 0.7174400091171265, -0.42579901218414307, -0.010804999619722366, 0.9047530293464661, 0.022041000425815582, -0.001623000018298626, 0.9997559785842896, 0.9995980262756348, -0.01155600044876337, 0.02587899938225746, 0.7095860242843628, -0.009670999832451344, -0.7045519948005676, 0, 0, 1, 0, 0, 1, 0.7626410126686096, -0.31482499837875366, 0.5650339722633362, 0.8245400190353394, -0.00001700000029813964, 0.5658029913902283, 0.8479819893836975, -0.3500339984893799, -0.39799800515174866, 0.917701005935669, -0.00003300000025774352, -0.397271990776062, 0.8641409873962402, -0.35644200444221497, -0.3552600145339966, 0.9352689981460571, -0.00011200000153621659, -0.3539389967918396, 0.7209920287132263, -0.29793301224708557, 0.6256250143051147, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0, 0, 1, 0.5833569765090942, -0.5833380222320557, 0.5651649832725525, 0.648485004901886, -0.6484479904174805, -0.3987259864807129, 0.6608719825744629, -0.6607480049133301, -0.35589399933815, 0.5518630146980286, -0.5517799854278564, 0.6252880096435547, 0, 0, 1, 0.31482499837875366, -0.762628972530365, 0.5650510191917419, 0.35004499554634094, -0.8479880094528198, -0.39797601103782654, 0.35647401213645935, -0.8641520142555237, -0.35519900918006897, 0.29798200726509094, -0.7210670113563538, 0.6255149841308594, 0, 0, 1, -0.00001700000029813964, -0.8245400190353394, 0.5658029913902283, -0.00003300000025774352, -0.917701005935669, -0.397271990776062, -0.00011200000153621659, -0.9352689981460571, -0.3539389967918396, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, 0, 0, 1, 0, 0, 1, -0.31482499837875366, -0.7626410126686096, 0.5650339722633362, -0.00001700000029813964, -0.8245400190353394, 0.5658029913902283, -0.3500339984893799, -0.8479819893836975, -0.39799800515174866, -0.00003300000025774352, -0.917701005935669, -0.397271990776062, -0.35644200444221497, -0.8641409873962402, -0.3552600145339966, -0.00011200000153621659, -0.9352689981460571, -0.3539389967918396, -0.29793301224708557, -0.7209920287132263, 0.6256250143051147, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, 0, 0, 1, -0.5833380222320557, -0.5833569765090942, 0.5651649832725525, -0.6484479904174805, -0.648485004901886, -0.3987259864807129, -0.6607480049133301, -0.6608719825744629, -0.35589399933815, -0.5517799854278564, -0.5518630146980286, 0.6252880096435547, 0, 0, 1, -0.762628972530365, -0.31482499837875366, 0.5650510191917419, -0.8479880094528198, -0.35004499554634094, -0.39797601103782654, -0.8641520142555237, -0.35647401213645935, -0.35519900918006897, -0.7210670113563538, -0.29798200726509094, 0.6255149841308594, 0, 0, 1, -0.8245400190353394, 0.00001700000029813964, 0.5658029913902283, -0.917701005935669, 0.00003300000025774352, -0.397271990776062, -0.9352689981460571, 0.00011200000153621659, -0.3539389967918396, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, 0, 0, 1, 0, 0, 1, -0.7626410126686096, 0.31482499837875366, 0.5650339722633362, -0.8245400190353394, 0.00001700000029813964, 0.5658029913902283, -0.8479819893836975, 0.3500339984893799, -0.39799800515174866, -0.917701005935669, 0.00003300000025774352, -0.397271990776062, -0.8641409873962402, 0.35644200444221497, -0.3552600145339966, -0.9352689981460571, 0.00011200000153621659, -0.3539389967918396, -0.7209920287132263, 0.29793301224708557, 0.6256250143051147, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, 0, 0, 1, -0.5833569765090942, 0.5833380222320557, 0.5651649832725525, -0.648485004901886, 0.6484479904174805, -0.3987259864807129, -0.6608719825744629, 0.6607480049133301, -0.35589399933815, -0.5518630146980286, 0.5517799854278564, 0.6252880096435547, 0, 0, 1, -0.31482499837875366, 0.762628972530365, 0.5650510191917419, -0.35004499554634094, 0.8479880094528198, -0.39797601103782654, -0.35647401213645935, 0.8641520142555237, -0.35519900918006897, -0.29798200726509094, 0.7210670113563538, 0.6255149841308594, 0, 0, 1, 0.00001700000029813964, 0.8245400190353394, 0.5658029913902283, 0.00003300000025774352, 0.917701005935669, -0.397271990776062, 0.00011200000153621659, 0.9352689981460571, -0.3539389967918396, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0, 0, 1, 0, 0, 1, 0.31482499837875366, 0.7626410126686096, 0.5650339722633362, 0.00001700000029813964, 0.8245400190353394, 0.5658029913902283, 0.3500339984893799, 0.8479819893836975, -0.39799800515174866, 0.00003300000025774352, 0.917701005935669, -0.397271990776062, 0.35644200444221497, 0.8641409873962402, -0.3552600145339966, 0.00011200000153621659, 0.9352689981460571, -0.3539389967918396, 0.29793301224708557, 0.7209920287132263, 0.6256250143051147, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0, 0, 1, 0.5833380222320557, 0.5833569765090942, 0.5651649832725525, 0.6484479904174805, 0.648485004901886, -0.3987259864807129, 0.6607480049133301, 0.6608719825744629, -0.35589399933815, 0.5517799854278564, 0.5518630146980286, 0.6252880096435547, 0, 0, 1, 0.762628972530365, 0.31482499837875366, 0.5650510191917419, 0.8479880094528198, 0.35004499554634094, -0.39797601103782654, 0.8641520142555237, 0.35647401213645935, -0.35519900918006897, 0.7210670113563538, 0.29798200726509094, 0.6255149841308594, 0, 0, 1, 0.8245400190353394, -0.00001700000029813964, 0.5658029913902283, 0.917701005935669, -0.00003300000025774352, -0.397271990776062, 0.9352689981460571, -0.00011200000153621659, -0.3539389967918396, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0.7209920287132263, -0.29793301224708557, 0.6256250143051147, 0.21797800064086914, -0.0902160033583641, 0.9717749953269958, 0.23658299446105957, 0, 0.9716110229492188, 0.1595889925956726, -0.06596100330352783, 0.9849770069122314, 0.17308400571346283, 0, 0.9849069714546204, 0.3504979908466339, -0.1447400003671646, 0.9253119826316833, 0.37970298528671265, 0, 0.925108015537262, 0.48558899760246277, -0.20147399604320526, 0.8506529927253723, 0.5266720056533813, 0, 0.8500679731369019, 0.5518630146980286, -0.5517799854278564, 0.6252880096435547, 0.16663099825382233, -0.16663099825382233, 0.9718379974365234, 0.12190800160169601, -0.12190800160169601, 0.9850260019302368, 0.2676680088043213, -0.2676680088043213, 0.9255849719047546, 0.37131500244140625, -0.37131500244140625, 0.8510289788246155, 0.29798200726509094, -0.7210670113563538, 0.6255149841308594, 0.0902160033583641, -0.21797800064086914, 0.9717749953269958, 0.06596100330352783, -0.1595889925956726, 0.9849770069122314, 0.1447400003671646, -0.3504979908466339, 0.9253119826316833, 0.20147399604320526, -0.48558899760246277, 0.8506529927253723, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, 0, -0.23658299446105957, 0.9716110229492188, 0, -0.17308400571346283, 0.9849069714546204, 0, -0.37970298528671265, 0.925108015537262, 0, -0.5266720056533813, 0.8500679731369019, -0.00007500000356230885, -0.7807120084762573, 0.6248900294303894, -0.29793301224708557, -0.7209920287132263, 0.6256250143051147, -0.0902160033583641, -0.21797800064086914, 0.9717749953269958, 0, -0.23658299446105957, 0.9716110229492188, -0.06596100330352783, -0.1595889925956726, 0.9849770069122314, 0, -0.17308400571346283, 0.9849069714546204, -0.1447400003671646, -0.3504979908466339, 0.9253119826316833, 0, -0.37970298528671265, 0.925108015537262, -0.20147399604320526, -0.48558899760246277, 0.8506529927253723, 0, -0.5266720056533813, 0.8500679731369019, -0.5517799854278564, -0.5518630146980286, 0.6252880096435547, -0.16663099825382233, -0.16663099825382233, 0.9718379974365234, -0.12190800160169601, -0.12190800160169601, 0.9850260019302368, -0.2676680088043213, -0.2676680088043213, 0.9255849719047546, -0.37131500244140625, -0.37131500244140625, 0.8510289788246155, -0.7210670113563538, -0.29798200726509094, 0.6255149841308594, -0.21797800064086914, -0.0902160033583641, 0.9717749953269958, -0.1595889925956726, -0.06596100330352783, 0.9849770069122314, -0.3504979908466339, -0.1447400003671646, 0.9253119826316833, -0.48558899760246277, -0.20147399604320526, 0.8506529927253723, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, -0.23658299446105957, 0, 0.9716110229492188, -0.17308400571346283, 0, 0.9849069714546204, -0.37970298528671265, 0, 0.925108015537262, -0.5266720056533813, 0, 0.8500679731369019, -0.7807120084762573, 0.00007500000356230885, 0.6248900294303894, -0.7209920287132263, 0.29793301224708557, 0.6256250143051147, -0.21797800064086914, 0.0902160033583641, 0.9717749953269958, -0.23658299446105957, 0, 0.9716110229492188, -0.1595889925956726, 0.06596100330352783, 0.9849770069122314, -0.17308400571346283, 0, 0.9849069714546204, -0.3504979908466339, 0.1447400003671646, 0.9253119826316833, -0.37970298528671265, 0, 0.925108015537262, -0.48558899760246277, 0.20147399604320526, 0.8506529927253723, -0.5266720056533813, 0, 0.8500679731369019, -0.5518630146980286, 0.5517799854278564, 0.6252880096435547, -0.16663099825382233, 0.16663099825382233, 0.9718379974365234, -0.12190800160169601, 0.12190800160169601, 0.9850260019302368, -0.2676680088043213, 0.2676680088043213, 0.9255849719047546, -0.37131500244140625, 0.37131500244140625, 0.8510289788246155, -0.29798200726509094, 0.7210670113563538, 0.6255149841308594, -0.0902160033583641, 0.21797800064086914, 0.9717749953269958, -0.06596100330352783, 0.1595889925956726, 0.9849770069122314, -0.1447400003671646, 0.3504979908466339, 0.9253119826316833, -0.20147399604320526, 0.48558899760246277, 0.8506529927253723, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0, 0.23658299446105957, 0.9716110229492188, 0, 0.17308400571346283, 0.9849069714546204, 0, 0.37970298528671265, 0.925108015537262, 0, 0.5266720056533813, 0.8500679731369019, 0.00007500000356230885, 0.7807120084762573, 0.6248900294303894, 0.29793301224708557, 0.7209920287132263, 0.6256250143051147, 0.0902160033583641, 0.21797800064086914, 0.9717749953269958, 0, 0.23658299446105957, 0.9716110229492188, 0.06596100330352783, 0.1595889925956726, 0.9849770069122314, 0, 0.17308400571346283, 0.9849069714546204, 0.1447400003671646, 0.3504979908466339, 0.9253119826316833, 0, 0.37970298528671265, 0.925108015537262, 0.20147399604320526, 0.48558899760246277, 0.8506529927253723, 0, 0.5266720056533813, 0.8500679731369019, 0.5517799854278564, 0.5518630146980286, 0.6252880096435547, 0.16663099825382233, 0.16663099825382233, 0.9718379974365234, 0.12190800160169601, 0.12190800160169601, 0.9850260019302368, 0.2676680088043213, 0.2676680088043213, 0.9255849719047546, 0.37131500244140625, 0.37131500244140625, 0.8510289788246155, 0.7210670113563538, 0.29798200726509094, 0.6255149841308594, 0.21797800064086914, 0.0902160033583641, 0.9717749953269958, 0.1595889925956726, 0.06596100330352783, 0.9849770069122314, 0.3504979908466339, 0.1447400003671646, 0.9253119826316833, 0.48558899760246277, 0.20147399604320526, 0.8506529927253723, 0.7807120084762573, -0.00007500000356230885, 0.6248909831047058, 0.23658299446105957, 0, 0.9716110229492188, 0.17308400571346283, 0, 0.9849069714546204, 0.37970298528671265, 0, 0.925108015537262, 0.5266720056533813, 0, 0.8500679731369019 ]);
var teapotTangents = new Float32Array([ 0.012897999957203865, 0.998727023601532, -0.048757001757621765, 0.3861910104751587, 0.9210079908370972, -0.016421999782323837, 0.38136398792266846, 0.9230089783668518, 0.000155999994603917, 0.012866999953985214, 0.9987300038337708, 0.04870200157165527, 0.3750790059566498, 0.9061710238456726, -0.0007169999880716205, 0.19210100173950195, 0.9812139868736267, 0.01775900088250637, 0.3782620131969452, 0.9142940044403076, -0.00011300000187475234, 0.10451500117778778, 0.9897350072860718, -0.09747499972581863, 0.3655939996242523, 0.9257190227508545, 0.028463000431656837, 0.04767199978232384, 0.9953050017356873, -0.08423800021409988, 0.7092679738998413, 0.7031199932098389, -0.016364000737667084, 0.7061989903450012, 0.7061989903450012, 0, 0.6937360167503357, 0.6937360167503357, 0, 0.6997770071029663, 0.6997770071029663, 0, 0.6924030184745789, 0.7150859832763672, 0.02822900004684925, 0.9243540167808533, 0.37810400128364563, -0.01657800003886223, 0.9230089783668518, 0.38136398792266846, -0.000155999994603917, 0.9061710238456726, 0.3750790059566498, 0.0007169999880716205, 0.9142940044403076, 0.3782620131969452, 0.00011300000187475234, 0.9133660197257996, 0.39544400572776794, 0.028490999713540077, 0.9987040162086487, 0.015853000804781914, 0.04836999997496605, 0.9987369775772095, 0.014649000018835068, -0.04806999862194061, 0.9812150001525879, 0.19211700558662415, -0.01754000037908554, 0.9897350072860718, 0.10452800244092941, 0.09745799750089645, 0.9953050017356873, 0.04767199978232384, 0.08423800021409988, 0.9988179802894592, -0.009758999571204185, -0.047600001096725464, 0.9094679951667786, -0.4095839858055115, -0.012636999599635601, 0.9240090250968933, -0.3811509907245636, -0.0003150000120513141, 0.9987890124320984, -0.01066299993544817, 0.04801800101995468, 0.9072269797325134, -0.37142300605773926, 0.0207310002297163, 0.9814350008964539, -0.19095200300216675, 0.01795700006186962, 0.914870023727417, -0.3771440088748932, -0.0011480000102892518, 0.989749014377594, -0.10442499816417694, -0.09742700308561325, 0.925815999507904, -0.3653950095176697, 0.028308000415563583, 0.9953050017356873, -0.04767199978232384, -0.08423800021409988, 0.6768929958343506, -0.7314029932022095, -0.01988700032234192, 0.6994619965553284, -0.7145140171051025, -0.00029799999902024865, 0.6940590143203735, -0.6933979988098145, 0.015560000203549862, 0.7002580165863037, -0.6996300220489502, -0.000783999974373728, 0.715142011642456, -0.6923869848251343, 0.028078999370336533, 0.351936012506485, -0.933899998664856, -0.019843999296426773, 0.36654001474380493, -0.9298419952392578, -0.0005210000090301037, 0.37116900086402893, -0.9084830284118652, 0.00152299995534122, 0.3776479959487915, -0.9147650003433228, -0.00011000000085914508, 0.39533698558807373, -0.9134349822998047, 0.028410999104380608, 0.0013210000470280647, -0.9989479780197144, 0.045830998569726944, 0.003897000104188919, -0.9988909959793091, -0.04690299928188324, 0.18705999851226807, -0.9821630120277405, -0.018818000331521034, 0.10363999754190445, -0.9898579716682434, 0.09715499728918076, 0.04757700115442276, -0.9953129887580872, 0.08418799936771393, -0.02296699956059456, -0.9986780285835266, -0.04599199816584587, -0.3861910104751587, -0.9210079908370972, -0.016421999782323837, -0.38136398792266846, -0.9230089783668518, 0.000155999994603917, -0.020431000739336014, -0.9987260103225708, 0.04614400118589401, -0.3750790059566498, -0.9061710238456726, -0.0007169999880716205, -0.19216600060462952, -0.9812189936637878, 0.01677200011909008, -0.3782620131969452, -0.9142940044403076, -0.00011300000187475234, -0.10471200197935104, -0.9897390007972717, -0.09722500294446945, -0.3655939996242523, -0.9257190227508545, 0.028463000431656837, -0.047710999846458435, -0.9953050017356873, -0.08420699834823608, -0.7092679738998413, -0.7031199932098389, -0.016364000737667084, -0.7061989903450012, -0.7061989903450012, 0, -0.6937360167503357, -0.6937360167503357, 0, -0.6997770071029663, -0.6997770071029663, 0, -0.6924030184745789, -0.7150859832763672, 0.02822900004684925, -0.9243540167808533, -0.37810400128364563, -0.01657800003886223, -0.9230089783668518, -0.38136398792266846, -0.000155999994603917, -0.9061710238456726, -0.3750790059566498, 0.0007169999880716205, -0.9142940044403076, -0.3782620131969452, 0.00011300000187475234, -0.9133660197257996, -0.39544400572776794, 0.028490999713540077, -0.998727023601532, -0.012897999957203865, 0.048757001757621765, -0.9987300038337708, -0.012866999953985214, -0.04870200157165527, -0.9812139868736267, -0.19210100173950195, -0.01775900088250637, -0.9897350072860718, -0.10451500117778778, 0.09747499972581863, -0.9953050017356873, -0.04767199978232384, 0.08423800021409988, -0.998727023601532, 0.012897999957203865, -0.048757001757621765, -0.9210079908370972, 0.3861910104751587, -0.016421999782323837, -0.9230089783668518, 0.38136398792266846, 0.000155999994603917, -0.9987300038337708, 0.012866999953985214, 0.04870200157165527, -0.9061710238456726, 0.3750790059566498, -0.0007169999880716205, -0.9812139868736267, 0.19210100173950195, 0.01775900088250637, -0.9142940044403076, 0.3782620131969452, -0.00011300000187475234, -0.9897350072860718, 0.10451500117778778, -0.09747499972581863, -0.9257190227508545, 0.3655939996242523, 0.028463000431656837, -0.9953050017356873, 0.04767199978232384, -0.08423800021409988, -0.7031199932098389, 0.7092679738998413, -0.016364000737667084, -0.7061989903450012, 0.7061989903450012, 0, -0.6937360167503357, 0.6937360167503357, 0, -0.6997770071029663, 0.6997770071029663, 0, -0.7150859832763672, 0.6924030184745789, 0.02822900004684925, -0.37810400128364563, 0.9243540167808533, -0.01657800003886223, -0.38136398792266846, 0.9230089783668518, -0.000155999994603917, -0.3750790059566498, 0.9061710238456726, 0.0007169999880716205, -0.3782620131969452, 0.9142940044403076, 0.00011300000187475234, -0.39544400572776794, 0.9133660197257996, 0.028490999713540077, -0.012897999957203865, 0.998727023601532, 0.048757001757621765, -0.012866999953985214, 0.9987300038337708, -0.04870200157165527, -0.19210100173950195, 0.9812139868736267, -0.01775900088250637, -0.10451500117778778, 0.9897350072860718, 0.09747499972581863, -0.04767199978232384, 0.9953050017356873, 0.08423800021409988, 0.04767199978232384, 0.9953050017356873, -0.08423800021409988, 0.39544400572776794, 0.9133660197257996, -0.028490999713540077, 0.38111698627471924, 0.9210190176963806, -0.000015999999959603883, 0.031922999769449234, 0.9968529939651489, -0.07255599647760391, 0.3815299868583679, 0.9219080209732056, 0.0000019999999949504854, 0.022261999547481537, 0.9978039860725403, -0.06237399950623512, 0.3821389973163605, 0.9231889843940735, 0.00001700000029813964, 0.008317999541759491, 0.9991790056228638, -0.03964800015091896, 0.38228899240493774, 0.9239469766616821, -0.004430000204592943, 0.0008660000166855752, 0.9999139904975891, 0.013048999942839146, 0.7150859832763672, 0.6924030184745789, -0.02822900004684925, 0.7048519849777222, 0.7048519849777222, 0, 0.7055330276489258, 0.7055330276489258, 0, 0.7065179944038391, 0.7065179944038391, 0, 0.7068390250205994, 0.707252025604248, -0.004379999823868275, 0.9257190227508545, 0.3655939996242523, -0.028463000431656837, 0.9210180044174194, 0.38111698627471924, 0.000015999999959603883, 0.9219080209732056, 0.3815299868583679, -0.0000019999999949504854, 0.9231889843940735, 0.3821389973163605, -0.00001700000029813964, 0.9237229824066162, 0.38283199071884155, -0.004399999976158142, 0.9953050017356873, 0.04767199978232384, 0.08423800021409988, 0.9968529939651489, 0.031922999769449234, 0.07255599647760391, 0.9978039860725403, 0.022261999547481537, 0.06237399950623512, 0.9991790056228638, 0.008317999541759491, 0.03964800015091896, 0.9999139904975891, 0.0008660000166855752, -0.013048999942839146, 0.9953050017356873, -0.04767199978232384, -0.08423800021409988, 0.9135000109672546, -0.3951619863510132, -0.02861100062727928, 0.9210190176963806, -0.38111698627471924, -0.000015999999959603883, 0.9968529939651489, -0.031922999769449234, -0.07255599647760391, 0.9219080209732056, -0.3815299868583679, 0.0000019999999949504854, 0.9978039860725403, -0.022261999547481537, -0.06237399950623512, 0.9231889843940735, -0.3821389973163605, 0.00001700000029813964, 0.9991790056228638, -0.008317999541759491, -0.03964800015091896, 0.9239469766616821, -0.38228899240493774, -0.004430000204592943, 0.9999139904975891, -0.0008660000166855752, 0.013048999942839146, 0.6925899982452393, -0.7149369716644287, -0.028262000530958176, 0.7048519849777222, -0.7048519849777222, 0, 0.7055330276489258, -0.7055330276489258, 0, 0.7065179944038391, -0.7065179944038391, 0, 0.707252025604248, -0.7068390250205994, -0.004379999823868275, 0.3656100034713745, -0.9257280230522156, -0.02841299958527088, 0.38111698627471924, -0.9210180044174194, 0.000015999999959603883, 0.3815299868583679, -0.9219080209732056, -0.0000019999999949504854, 0.3821389973163605, -0.9231889843940735, -0.00001700000029813964, 0.38283199071884155, -0.9237229824066162, -0.004399999976158142, 0.04757700115442276, -0.9953129887580872, 0.08418799936771393, 0.031922999769449234, -0.9968529939651489, 0.07255599647760391, 0.022261999547481537, -0.9978039860725403, 0.06237399950623512, 0.008317999541759491, -0.9991790056228638, 0.03964800015091896, 0.0008660000166855752, -0.9999139904975891, -0.013048999942839146, -0.047710999846458435, -0.9953050017356873, -0.08420699834823608, -0.39544400572776794, -0.9133660197257996, -0.028490999713540077, -0.38111698627471924, -0.9210190176963806, -0.000015999999959603883, -0.031922999769449234, -0.9968529939651489, -0.07255599647760391, -0.3815299868583679, -0.9219080209732056, 0.0000019999999949504854, -0.022261999547481537, -0.9978039860725403, -0.06237399950623512, -0.3821389973163605, -0.9231889843940735, 0.00001700000029813964, -0.008317999541759491, -0.9991790056228638, -0.03964800015091896, -0.38228899240493774, -0.9239469766616821, -0.004430000204592943, -0.0008660000166855752, -0.9999139904975891, 0.013048999942839146, -0.7150859832763672, -0.6924030184745789, -0.02822900004684925, -0.7048519849777222, -0.7048519849777222, 0, -0.7055330276489258, -0.7055330276489258, 0, -0.7065179944038391, -0.7065179944038391, 0, -0.7068390250205994, -0.707252025604248, -0.004379999823868275, -0.9257190227508545, -0.3655939996242523, -0.028463000431656837, -0.9210180044174194, -0.38111698627471924, 0.000015999999959603883, -0.9219080209732056, -0.3815299868583679, -0.0000019999999949504854, -0.9231889843940735, -0.3821389973163605, -0.00001700000029813964, -0.9237229824066162, -0.38283199071884155, -0.004399999976158142, -0.9953050017356873, -0.04767199978232384, 0.08423800021409988, -0.9968529939651489, -0.031922999769449234, 0.07255599647760391, -0.9978039860725403, -0.022261999547481537, 0.06237399950623512, -0.9991790056228638, -0.008317999541759491, 0.03964800015091896, -0.9999139904975891, -0.0008660000166855752, -0.013048999942839146, -0.9953050017356873, 0.04767199978232384, -0.08423800021409988, -0.9133660197257996, 0.39544400572776794, -0.028490999713540077, -0.9210190176963806, 0.38111698627471924, -0.000015999999959603883, -0.9968529939651489, 0.031922999769449234, -0.07255599647760391, -0.9219080209732056, 0.3815299868583679, 0.0000019999999949504854, -0.9978039860725403, 0.022261999547481537, -0.06237399950623512, -0.9231889843940735, 0.3821389973163605, 0.00001700000029813964, -0.9991790056228638, 0.008317999541759491, -0.03964800015091896, -0.9239469766616821, 0.38228899240493774, -0.004430000204592943, -0.9999139904975891, 0.0008660000166855752, 0.013048999942839146, -0.6924030184745789, 0.7150859832763672, -0.02822900004684925, -0.7048519849777222, 0.7048519849777222, 0, -0.7055330276489258, 0.7055330276489258, 0, -0.7065179944038391, 0.7065179944038391, 0, -0.707252025604248, 0.7068390250205994, -0.004379999823868275, -0.3655939996242523, 0.9257190227508545, -0.028463000431656837, -0.38111698627471924, 0.9210180044174194, 0.000015999999959603883, -0.3815299868583679, 0.9219080209732056, -0.0000019999999949504854, -0.3821389973163605, 0.9231889843940735, -0.00001700000029813964, -0.38283199071884155, 0.9237229824066162, -0.004399999976158142, -0.04767199978232384, 0.9953050017356873, 0.08423800021409988, -0.031922999769449234, 0.9968529939651489, 0.07255599647760391, -0.022261999547481537, 0.9978039860725403, 0.06237399950623512, -0.008317999541759491, 0.9991790056228638, 0.03964800015091896, -0.0008660000166855752, 0.9999139904975891, -0.013048999942839146, 0.0008660000166855752, 0.9999139904975891, 0.013048999942839146, 0.38283199071884155, 0.9237229824066162, 0.004399999976158142, 0.38101500272750854, 0.9204739928245544, -0.00003899999865097925, 0.03731299936771393, 0.9963229894638062, 0.07712399959564209, 0.37877199053764343, 0.9154880046844482, 0.00008399999933317304, 0.09151100367307663, 0.9910060167312622, 0.097632996737957, 0.378387987613678, 0.9145749807357788, 0.00009999999747378752, 0.10134600102901459, 0.9900450110435486, 0.09767600148916245, 0.356795996427536, 0.9266510009765625, -0.03188199922442436, 0.07246600091457367, 0.9928709864616394, 0.09463199973106384, 0.707252025604248, 0.7068390250205994, 0.004379999823868275, 0.7044739723205566, 0.7044739723205566, 0, 0.7006790041923523, 0.7006790041923523, 0, 0.6999930143356323, 0.6999930143356323, 0, 0.6847820281982422, 0.7192310094833374, -0.03167999908328056, 0.9239469766616821, 0.38228899240493774, 0.004430000204592943, 0.9204739928245544, 0.38101500272750854, 0.00003899999865097925, 0.9154880046844482, 0.37877199053764343, -0.00008399999933317304, 0.9145749807357788, 0.378387987613678, -0.00009999999747378752, 0.9078760147094727, 0.40216198563575745, -0.03206299990415573, 0.9999139904975891, 0.0008660000166855752, -0.013048999942839146, 0.9963229894638062, 0.03731299936771393, -0.07712399959564209, 0.9910060167312622, 0.09151100367307663, -0.097632996737957, 0.9900450110435486, 0.10134600102901459, -0.09767600148916245, 0.9928709864616394, 0.07246600091457367, -0.09463199973106384, 0.9999139904975891, -0.0008660000166855752, 0.013048999942839146, 0.9237229824066162, -0.38283199071884155, 0.004399999976158142, 0.9204739928245544, -0.38101500272750854, -0.00003899999865097925, 0.9963229894638062, -0.03731299936771393, 0.07712399959564209, 0.9154880046844482, -0.37877199053764343, 0.00008399999933317304, 0.9910060167312622, -0.09151100367307663, 0.097632996737957, 0.9145749807357788, -0.378387987613678, 0.00009999999747378752, 0.9900450110435486, -0.10134600102901459, 0.09767600148916245, 0.9266510009765625, -0.356795996427536, -0.03188199922442436, 0.9928709864616394, -0.07246600091457367, 0.09463199973106384, 0.7068390250205994, -0.707252025604248, 0.004379999823868275, 0.7044739723205566, -0.7044739723205566, 0, 0.7006790041923523, -0.7006790041923523, 0, 0.6999930143356323, -0.6999930143356323, 0, 0.7192310094833374, -0.6847820281982422, -0.03167999908328056, 0.38228899240493774, -0.9239469766616821, 0.004430000204592943, 0.38101500272750854, -0.9204739928245544, 0.00003899999865097925, 0.37877199053764343, -0.9154880046844482, -0.00008399999933317304, 0.378387987613678, -0.9145749807357788, -0.00009999999747378752, 0.40216198563575745, -0.9078760147094727, -0.03206299990415573, 0.0008660000166855752, -0.9999139904975891, -0.013048999942839146, 0.03731299936771393, -0.9963229894638062, -0.07712399959564209, 0.09151100367307663, -0.9910060167312622, -0.097632996737957, 0.10134600102901459, -0.9900450110435486, -0.09767600148916245, 0.07246600091457367, -0.9928709864616394, -0.09463199973106384, -0.0008660000166855752, -0.9999139904975891, 0.013048999942839146, -0.38283199071884155, -0.9237229824066162, 0.004399999976158142, -0.38101500272750854, -0.9204739928245544, -0.00003899999865097925, -0.03731299936771393, -0.9963229894638062, 0.07712399959564209, -0.37877199053764343, -0.9154880046844482, 0.00008399999933317304, -0.09151100367307663, -0.9910060167312622, 0.097632996737957, -0.378387987613678, -0.9145749807357788, 0.00009999999747378752, -0.10134600102901459, -0.9900450110435486, 0.09767600148916245, -0.356795996427536, -0.9266510009765625, -0.03188199922442436, -0.07246600091457367, -0.9928709864616394, 0.09463199973106384, -0.707252025604248, -0.7068390250205994, 0.004379999823868275, -0.7044739723205566, -0.7044739723205566, 0, -0.7006790041923523, -0.7006790041923523, 0, -0.6999930143356323, -0.6999930143356323, 0, -0.6847820281982422, -0.7192310094833374, -0.03167999908328056, -0.9239469766616821, -0.38228899240493774, 0.004430000204592943, -0.9204739928245544, -0.38101500272750854, 0.00003899999865097925, -0.9154880046844482, -0.37877199053764343, -0.00008399999933317304, -0.9145749807357788, -0.378387987613678, -0.00009999999747378752, -0.9078760147094727, -0.40216198563575745, -0.03206299990415573, -0.9999139904975891, -0.0008660000166855752, -0.013048999942839146, -0.9963229894638062, -0.03731299936771393, -0.07712399959564209, -0.9910060167312622, -0.09151100367307663, -0.097632996737957, -0.9900450110435486, -0.10134600102901459, -0.09767600148916245, -0.9928709864616394, -0.07246600091457367, -0.09463199973106384, -0.9999139904975891, 0.0008660000166855752, 0.013048999942839146, -0.9237229824066162, 0.38283199071884155, 0.004399999976158142, -0.9204739928245544, 0.38101500272750854, -0.00003899999865097925, -0.9963229894638062, 0.03731299936771393, 0.07712399959564209, -0.9154880046844482, 0.37877199053764343, 0.00008399999933317304, -0.9910060167312622, 0.09151100367307663, 0.097632996737957, -0.9145749807357788, 0.378387987613678, 0.00009999999747378752, -0.9900450110435486, 0.10134600102901459, 0.09767600148916245, -0.9266510009765625, 0.356795996427536, -0.03188199922442436, -0.9928709864616394, 0.07246600091457367, 0.09463199973106384, -0.7068390250205994, 0.707252025604248, 0.004379999823868275, -0.7044739723205566, 0.7044739723205566, 0, -0.7006790041923523, 0.7006790041923523, 0, -0.6999930143356323, 0.6999930143356323, 0, -0.7192310094833374, 0.6847820281982422, -0.03167999908328056, -0.38228899240493774, 0.9239469766616821, 0.004430000204592943, -0.38101500272750854, 0.9204739928245544, 0.00003899999865097925, -0.37877199053764343, 0.9154880046844482, -0.00008399999933317304, -0.378387987613678, 0.9145749807357788, -0.00009999999747378752, -0.40216198563575745, 0.9078760147094727, -0.03206299990415573, -0.0008660000166855752, 0.9999139904975891, -0.013048999942839146, -0.03731299936771393, 0.9963229894638062, -0.07712399959564209, -0.09151100367307663, 0.9910060167312622, -0.097632996737957, -0.10134600102901459, 0.9900450110435486, -0.09767600148916245, -0.07246600091457367, 0.9928709864616394, -0.09463199973106384, 0.07246600091457367, 0.9928709864616394, 0.09463199973106384, 0.40216198563575745, 0.9078760147094727, 0.03206299990415573, 0.37766799330711365, 0.912958025932312, 0.00018099999579135329, 0.11919300258159637, 0.9883019924163818, 0.09514500200748444, 0.37516000866889954, 0.906607985496521, 0.00016799999866634607, 0.187733992934227, 0.9816380143165588, 0.03381900116801262, 0.2823430001735687, 0.767549991607666, -0.1682250052690506, 0.12883399426937103, 0.6540690064430237, -0.32698601484298706, 0.06457000225782394, 0.32701900601387024, -0.6666669845581055, 0, 0, -1, 0.7192320227622986, 0.6847820281982422, 0.03167999908328056, 0.6987630128860474, 0.6987630128860474, 0, 0.694034993648529, 0.694034993648529, 0, 0.5551990270614624, 0.6008960008621216, -0.16825300455093384, 0.1854030042886734, 0.27701398730278015, -0.6666669845581055, 0.9266499876976013, 0.3567950129508972, 0.03188199922442436, 0.912958025932312, 0.37766799330711365, -0.00018099999579135329, 0.906607985496521, 0.37516000866889954, -0.00016799999866634607, 0.742605984210968, 0.3426159918308258, -0.1683180034160614, 0.27701398730278015, 0.1854030042886734, -0.6666669845581055, 0.9928709864616394, 0.07246600091457367, -0.09463199973106384, 0.9883019924163818, 0.11919300258159637, -0.09514500200748444, 0.9816370010375977, 0.187733992934227, -0.03381900116801262, 0.9811030030250549, 0.19325199723243713, -0.009519999846816063, 0.49052900075912476, 0.0968559980392456, -0.5, 0.9928709864616394, -0.07246600091457367, 0.09463199973106384, 0.9078760147094727, -0.40216198563575745, 0.03206299990415573, 0.912958025932312, -0.37766799330711365, 0.00018099999579135329, 0.9883019924163818, -0.11919300258159637, 0.09514500200748444, 0.906607985496521, -0.37516000866889954, 0.00016799999866634607, 0.9816380143165588, -0.187733992934227, 0.03381900116801262, 0.767549991607666, -0.2823430001735687, -0.1682250052690506, 0.6540690064430237, -0.12883399426937103, -0.32698601484298706, 0.32701900601387024, -0.06457000225782394, -0.6666669845581055, 0, 0, -1, 0.6847820281982422, -0.7192320227622986, 0.03167999908328056, 0.6987630128860474, -0.6987630128860474, 0, 0.694034993648529, -0.694034993648529, 0, 0.6008960008621216, -0.5551990270614624, -0.16825300455093384, 0.27701398730278015, -0.1854030042886734, -0.6666669845581055, 0.3567950129508972, -0.9266499876976013, 0.03188199922442436, 0.37766799330711365, -0.912958025932312, -0.00018099999579135329, 0.37516000866889954, -0.906607985496521, -0.00016799999866634607, 0.3426159918308258, -0.742605984210968, -0.1683180034160614, 0.1854030042886734, -0.27701398730278015, -0.6666669845581055, 0.07246600091457367, -0.9928709864616394, -0.09463199973106384, 0.11919300258159637, -0.9883019924163818, -0.09514500200748444, 0.187733992934227, -0.9816370010375977, -0.03381900116801262, 0.19325199723243713, -0.9811030030250549, -0.009519999846816063, 0.0968559980392456, -0.49052900075912476, -0.5, -0.07246600091457367, -0.9928709864616394, 0.09463199973106384, -0.40216198563575745, -0.9078760147094727, 0.03206299990415573, -0.37766799330711365, -0.912958025932312, 0.00018099999579135329, -0.11919300258159637, -0.9883019924163818, 0.09514500200748444, -0.37516000866889954, -0.906607985496521, 0.00016799999866634607, -0.187733992934227, -0.9816380143165588, 0.03381900116801262, -0.2823430001735687, -0.767549991607666, -0.1682250052690506, -0.12883399426937103, -0.6540690064430237, -0.32698601484298706, -0.06457000225782394, -0.32701900601387024, -0.6666669845581055, 0, 0, -1, -0.7192320227622986, -0.6847820281982422, 0.03167999908328056, -0.6987630128860474, -0.6987630128860474, 0, -0.694034993648529, -0.694034993648529, 0, -0.5551990270614624, -0.6008960008621216, -0.16825300455093384, -0.1854030042886734, -0.27701398730278015, -0.6666669845581055, -0.9266499876976013, -0.3567950129508972, 0.03188199922442436, -0.912958025932312, -0.37766799330711365, -0.00018099999579135329, -0.906607985496521, -0.37516000866889954, -0.00016799999866634607, -0.742605984210968, -0.3426159918308258, -0.1683180034160614, -0.27701398730278015, -0.1854030042886734, -0.6666669845581055, -0.9928709864616394, -0.07246600091457367, -0.09463199973106384, -0.9883019924163818, -0.11919300258159637, -0.09514500200748444, -0.9816370010375977, -0.187733992934227, -0.03381900116801262, -0.9811030030250549, -0.19325199723243713, -0.009519999846816063, -0.49052900075912476, -0.0968559980392456, -0.5, -0.9928709864616394, 0.07246600091457367, 0.09463199973106384, -0.9078760147094727, 0.40216198563575745, 0.03206299990415573, -0.912958025932312, 0.37766799330711365, 0.00018099999579135329, -0.9883019924163818, 0.11919300258159637, 0.09514500200748444, -0.906607985496521, 0.37516000866889954, 0.00016799999866634607, -0.9816380143165588, 0.187733992934227, 0.03381900116801262, -0.767549991607666, 0.2823430001735687, -0.1682250052690506, -0.6540690064430237, 0.12883399426937103, -0.32698601484298706, -0.32701900601387024, 0.06457000225782394, -0.6666669845581055, 0, 0, -1, -0.6847820281982422, 0.7192320227622986, 0.03167999908328056, -0.6987630128860474, 0.6987630128860474, 0, -0.694034993648529, 0.694034993648529, 0, -0.6008960008621216, 0.5551990270614624, -0.16825300455093384, -0.27701398730278015, 0.1854030042886734, -0.6666669845581055, -0.3567950129508972, 0.9266499876976013, 0.03188199922442436, -0.37766799330711365, 0.912958025932312, -0.00018099999579135329, -0.37516000866889954, 0.906607985496521, -0.00016799999866634607, -0.3426159918308258, 0.742605984210968, -0.1683180034160614, -0.1854030042886734, 0.27701398730278015, -0.6666669845581055, -0.07246600091457367, 0.9928709864616394, -0.09463199973106384, -0.11919300258159637, 0.9883019924163818, -0.09514500200748444, -0.187733992934227, 0.9816370010375977, -0.03381900116801262, -0.19325199723243713, 0.9811030030250549, -0.009519999846816063, -0.0968559980392456, 0.49052900075912476, -0.5, -0.006597999949008226, 0.9961680173873901, 0.0001630000042496249, -0.043907999992370605, 0.779125988483429, -0.55936598777771, 0.23287899792194366, 0.79271000623703, -0.506534993648529, 0.11139900237321854, 0.9923329949378967, 0.0053449999541044235, 0.4521920084953308, 0.7370989918708801, -0.42180201411247253, 0.17797799408435822, 0.9827970266342163, 0.036841001361608505, 0.6075379848480225, 0.7066869735717773, -0.270797997713089, 0.11894699931144714, 0.9864829778671265, 0.10517799854278564, 0.6583719849586487, 0.7438470125198364, -0.06727500259876251, 0.0010629999451339245, 0.99891597032547, 0.04653400182723999, -0.1622990071773529, -0.14869500696659088, -0.9069569706916809, 0.3020159900188446, -0.014301000162959099, -0.8847119808197021, 0.7048640251159668, -0.042514998465776443, -0.6788020133972168, 0.8948519825935364, -0.11078000068664551, -0.38824599981307983, 0.9622920155525208, -0.09367900341749191, -0.14349600672721863, -0.12511900067329407, -0.8479049801826477, -0.4783349931240082, 0.11315400153398514, -0.8153669834136963, -0.5167160034179688, 0.3956319987773895, -0.7910019755363464, -0.4345270097255707, 0.5244609713554382, -0.8012329936027527, -0.2643829882144928, 0.571465015411377, -0.7902160286903381, -0.12332800030708313, -0.0943560004234314, -0.9955379962921143, -0.0010989999864250422, 0.012040999718010426, -0.9965500235557556, 0, 0.09501499682664871, -0.9936969876289368, 0.02440500073134899, 0.03737499937415123, -0.9978089928627014, 0.035909999161958694, -0.0008800000068731606, -0.9973530173301697, -0.04031199961900711, 0.007164000067859888, -0.9961649775505066, -0.00002700000004551839, 0.043988000601530075, -0.8330309987068176, 0.4691329896450043, -0.2334270030260086, -0.7983189821243286, 0.49840399622917175, -0.10737399756908417, -0.9927549958229065, -0.007029999978840351, -0.45147499442100525, -0.7576299905776978, 0.39375001192092896, -0.15364399552345276, -0.9863160252571106, -0.048294998705387115, -0.5575600266456604, -0.7753210067749023, 0.2001740038394928, -0.07242999970912933, -0.9923030138015747, -0.08845999836921692, -0.5877019762992859, -0.8041930198669434, 0.04768599942326546, 0.0005830000154674053, -0.9997940063476562, -0.020301999524235725, 0.13663700222969055, -0.14665700495243073, 0.8966140151023865, -0.3045389950275421, -0.012237999588251114, 0.8833180069923401, -0.7020289897918701, -0.033987998962402344, 0.6724730134010315, -0.8890330195426941, -0.09636799991130829, 0.37605398893356323, -0.9668099880218506, -0.08601800352334976, 0.1358419954776764, 0.12022499740123749, 0.7918559908866882, 0.5693140029907227, -0.11313500255346298, 0.8111780285835266, 0.5236610174179077, -0.39790698885917664, 0.7734419703483582, 0.45853298902511597, -0.5793390274047852, 0.7346490025520325, 0.32973799109458923, -0.6447499990463257, 0.7340419888496399, 0.12459299713373184, 0.09378799796104431, 0.9955919981002808, 0.000944000028539449, -0.01607999950647354, 0.9964879751205444, 0.00035600000410340726, -0.11933200061321259, 0.9912199974060059, -0.01737299934029579, -0.08618299663066864, 0.9940080046653748, -0.053598999977111816, -0.004110999871045351, 0.9980229735374451, 0.015703000128269196, 0.010142000392079353, 0.9933879971504211, 0.10034400224685669, 0.6597890257835388, 0.7114480137825012, 0.12964099645614624, 0.5634239912033081, 0.7594000101089478, 0.289902001619339, -0.021227000281214714, 0.9976930022239685, 0.05189099907875061, 0.3972559869289398, 0.7709670066833496, 0.45872700214385986, -0.05054600164294243, 0.9957669973373413, 0.060869000852108, 0.11805199831724167, 0.7611619830131531, 0.5692800283432007, -0.11414600163698196, 0.9869359731674194, 0.08862999826669693, -0.0012870000209659338, 0.7195389866828918, 0.6293820142745972, -0.18971200287342072, 0.9752820134162903, 0.11328700184822083, 0.9685969948768616, -0.08966200053691864, 0.13331100344657898, 0.8902140259742737, -0.051961999386548996, 0.39323100447654724, 0.6728280186653137, -0.050324998795986176, 0.6965069770812988, 0.25133201479911804, -0.04306900128722191, 0.9169719815254211, -0.19813700020313263, -0.2512879967689514, 0.9046909809112549, 0.5937719941139221, -0.8024669885635376, 0.03307799994945526, 0.5571249723434448, -0.7907459735870361, 0.2022089958190918, 0.4313510060310364, -0.8083119988441467, 0.37996000051498413, 0.19395600259304047, -0.8197799921035767, 0.5133119821548462, -0.1517219990491867, -0.8084930181503296, 0.5055829882621765, 0.0035200000274926424, -0.9997940063476562, 0.019979000091552734, 0.01159599982202053, -0.9981369972229004, -0.02326199971139431, 0.01310999970883131, -0.9988970160484314, -0.008480999618768692, -0.02485400065779686, -0.9978809952735901, 0.021263999864459038, -0.11335399746894836, -0.9881970286369324, 0.06441199779510498, -0.0035459999926388264, -0.9954169988632202, -0.07682599872350693, -0.5816869735717773, -0.7760900259017944, -0.13957500457763672, -0.5260769724845886, -0.790789008140564, -0.2781960070133209, 0.017288999632000923, -0.9983699917793274, -0.03728000074625015, -0.36800798773765564, -0.7982890009880066, -0.4405499994754791, 0.03743100166320801, -0.9973520040512085, -0.03640099987387657, -0.09636899828910828, -0.7829139828681946, -0.5500450134277344, 0.10426300019025803, -0.9894949793815613, -0.06746900081634521, 0.10083399713039398, -0.8161320090293884, -0.48112401366233826, 0.18510299921035767, -0.9776470065116882, -0.09971100091934204, -0.9615049958229065, -0.08203399926424026, -0.14958199858665466, -0.8876789808273315, -0.04622500017285347, -0.39955899119377136, -0.6675580143928528, -0.03723999857902527, -0.7007560133934021, -0.245511993765831, -0.03216199949383736, -0.9151920080184937, 0.15477199852466583, -0.24929499626159668, -0.8975690007209778, -0.6700729727745056, 0.7402250170707703, -0.01942499913275242, -0.5923460125923157, 0.7624830007553101, -0.21566900610923767, -0.45611900091171265, 0.7868310213088989, -0.39906400442123413, -0.21001900732517242, 0.8031420111656189, -0.5333020091056824, 0.05119999870657921, 0.7096909880638123, -0.6591699719429016, -0.014175999909639359, 0.9989240169525146, -0.04416000097990036, -0.0065449997782707214, 0.9983869791030884, 0.008813999593257904, 0.0023960000835359097, 0.9989259839057922, -0.016711000353097916, 0.03813000023365021, 0.9969249963760376, -0.04171599820256233, 0.11744900047779083, 0.986670970916748, -0.0799890011548996, -0.02072799950838089, -0.997963011264801, 0.0017740000039339066, 0.10236400365829468, -0.695684015750885, -0.6961740255355835, 0.28174999356269836, -0.7065439820289612, -0.6379269957542419, -0.027713999152183533, -0.9983959794044495, -0.016395000740885735, 0.4621469974517822, -0.7501789927482605, -0.43765199184417725, -0.014942999929189682, -0.9960020184516907, -0.04751100018620491, 0.6121799945831299, -0.7355859875679016, -0.1658719927072525, 0.08200599998235703, -0.9833409786224365, 0.11102399975061417, 0.7232419848442078, -0.6012910008430481, -0.14595800638198853, 0.32238098978996277, -0.9036369919776917, 0.28197699785232544, 0.1188960000872612, 0.09661199897527695, -0.9692260026931763, 0.3230240046977997, 0.06791900098323822, -0.9069269895553589, 0.6287810206413269, 0.00962899997830391, -0.711097002029419, 0.8952469825744629, -0.060169998556375504, -0.3366979956626892, 0.9689210057258606, -0.04508800059556961, -0.13095800578594208, 0.06500200182199478, 0.7708680033683777, -0.6083509922027588, 0.1816529929637909, 0.7457069754600525, -0.593995988368988, 0.37600401043891907, 0.7467949986457825, -0.4776870012283325, 0.6288849711418152, 0.7020969986915588, -0.27160701155662537, 0.8230010271072388, 0.5295370221138, -0.09450399875640869, -0.12820099294185638, 0.9899809956550598, -0.05917999893426895, -0.11097600311040878, 0.9872509837150574, -0.09937400370836258, -0.06767299771308899, 0.9865689873695374, -0.1427209973335266, -0.0003349999897181988, 0.9967420101165771, 0.025443999096751213, 0.29019099473953247, 0.9243509769439697, 0.1957239955663681, 0.07294999808073044, 0.9949049949645996, 0.03147900104522705, -0.04948300123214722, 0.7695090174674988, 0.6163870096206665, -0.24193400144577026, 0.7750219702720642, 0.5679330229759216, 0.05620399862527847, 0.9959489703178406, 0.052143000066280365, -0.4294399917125702, 0.779321014881134, 0.41615501046180725, 0.023887999355793, 0.9943940043449402, 0.07553800195455551, -0.6655910015106201, 0.6939520239830017, 0.20106400549411774, -0.09678799659013748, 0.9791589975357056, -0.12869000434875488, -0.7716730237007141, 0.5443729758262634, 0.1793539971113205, -0.417836993932724, 0.8721759915351868, -0.2544029951095581, -0.09499499946832657, 0.08934500068426132, 0.9787889719009399, -0.3299880027770996, 0.06701900064945221, 0.9273520112037659, -0.6511250138282776, 0.023523999378085136, 0.7280719876289368, -0.9116759896278381, -0.033263999968767166, 0.34162598848342896, -0.9896330237388611, -0.013496000319719315, 0.07834099978208542, -0.07044100016355515, -0.6954740285873413, 0.7080140113830566, -0.21969600021839142, -0.6959800124168396, 0.6642320156097412, -0.4075010120868683, -0.7370589971542358, 0.5047789812088013, -0.5866039991378784, -0.7473030090332031, 0.24636299908161163, -0.799036979675293, -0.5617390275001526, 0.05794600024819374, 0.07605399936437607, -0.9967970252037048, 0.02472200058400631, 0.08756300061941147, -0.9926980137825012, 0.05929899960756302, 0.07250799983739853, -0.9901790022850037, 0.11122000217437744, 0.015556000173091888, -0.9970260262489319, -0.011235999874770641, -0.194814994931221, -0.9439409971237183, -0.22127500176429749, 0.3417310118675232, -0.8896859884262085, 0.3012309968471527, 0.8375009894371033, -0.4931910037994385, 0.05739299952983856, 0.8273029923439026, -0.4684619903564453, -0.05539099872112274, 0.5311300158500671, -0.8121910095214844, 0.24026300013065338, 0.8069959878921509, -0.47689300775527954, 0.002638000063598156, 0.644743025302887, -0.7642210125923157, -0.015455000102519989, 0.8856800198554993, -0.4464530050754547, 0.047488000243902206, -0.011536000296473503, -0.999845027923584, -0.0008730000117793679, 0.7597830295562744, -0.6229599714279175, 0.026636000722646713, 0.321245014667511, -0.8855000138282776, 0.3356960117816925, 0.998091995716095, -0.005673000123351812, 0.025262000039219856, 0.9941530227661133, 0.046904999762773514, -0.00951599981635809, 0.9838590025901794, -0.00041700000292621553, 0.010572000406682491, 0.990556001663208, 0.01886500045657158, 0.04422200098633766, 0.9921990036964417, -0.12290599942207336, 0.011202000081539154, 0.828000009059906, 0.5258169770240784, -0.0846100002527237, 0.8704839944839478, 0.4878079891204834, 0.00635599996894598, 0.7773939967155457, 0.5659670233726501, -0.09634699672460556, 0.8190580010414124, 0.4740380048751831, 0.01190400030463934, 0.9017590284347534, 0.3486430048942566, -0.05601400136947632, 0.41038599610328674, 0.870602011680603, 0.27135801315307617, 0.3019320070743561, 0.8897680044174194, 0.34101900458335876, 0.13912299275398254, 0.9423390030860901, -0.3042120039463043, 0.6167309880256653, 0.7692840099334717, 0.1667650043964386, 0.5558350086212158, 0.8010749816894531, 0.21867799758911133, -0.4410029947757721, 0.8555399775505066, -0.2693159878253937, -0.8639690279960632, 0.464356005191803, -0.019222000613808632, -0.8705710172653198, 0.4855479896068573, -0.005623999983072281, -0.33969300985336304, 0.8762779831886292, -0.34097298979759216, -0.7608209848403931, 0.5840269923210144, 0.11236599832773209, -0.16763299703598022, 0.9419429898262024, 0.29091599583625793, -0.8260639905929565, 0.47304999828338623, -0.0134699996560812, -0.6006280183792114, 0.7822970151901245, -0.1611420065164566, -0.8495870232582092, 0.4440779983997345, 0.17417700588703156, -0.5251449942588806, 0.8236340284347534, -0.21412399411201477, -0.9991480112075806, 0.0017519999528303742, 0.007890000008046627, -0.9946579933166504, 0.06129400059580803, 0.007796999998390675, -0.9840919971466064, 0.008732999674975872, -0.0001289999927394092, -0.9916059970855713, 0.015207000076770782, -0.04798699915409088, -0.9899899959564209, -0.13816699385643005, -0.019433999434113503, -0.7927820086479187, -0.5669599771499634, 0.06795799732208252, -0.8363490104675293, -0.4685719907283783, 0.048955000936985016, -0.8138830065727234, -0.4743089973926544, 0.0008379999781027436, -0.8869869709014893, -0.4417180120944977, -0.05625399947166443, -0.7898640036582947, -0.5522750020027161, -0.15016800165176392, -0.297340989112854, -0.8998129963874817, -0.3192580044269562, -0.49759799242019653, -0.8317790031433105, -0.24411599338054657, -0.6295620203018188, -0.7765420079231262, 0.01261799968779087, -0.011338000185787678, -0.9998990297317505, -0.008561000227928162, -0.3547320067882538, -0.8679590225219727, -0.3453510105609894, 0.09618999809026718, 0.49066001176834106, -0.5, 0.1851000040769577, 0.27721700072288513, -0.6666669845581055, 0.32566601037979126, 0.76139897108078, -0.18199099600315094, 0.062401000410318375, 0.9939020276069641, -0.09090700000524521, 0.3803209960460663, 0.9214360117912292, -0.00007100000220816582, 0.030918000265955925, 0.9969729781150818, 0.07133600115776062, 0.3804109990596771, 0.9220889806747437, 0.0001630000042496249, 0.02471200004220009, 0.9975799918174744, 0.06498300284147263, 0.35510900616645813, 0.926891028881073, 0.03216100111603737, 0.07657899707555771, 0.9924740195274353, -0.09555599838495255, 0.27721700072288513, 0.1851000040769577, -0.6666669845581055, 0.5929989814758301, 0.5781109929084778, -0.18205299973487854, 0.7048519849777222, 0.7048519849777222, 0, 0.7052720189094543, 0.7054179906845093, -0.00002499999936844688, 0.6835219860076904, 0.7199410200119019, 0.03204600140452385, 0.3271070122718811, 0.06412599980831146, -0.6666669845581055, 0.7694699764251709, 0.3061000108718872, -0.18225300312042236, 0.9214379787445068, 0.38033199310302734, 0.0000670000008540228, 0.9220880270004272, 0.3804430067539215, -0.00016799999866634607, 0.9071130156517029, 0.403003990650177, 0.032437000423669815, 0, 0, -1, 0.6626030206680298, 0.04157499969005585, -0.272724986076355, 0.9969789981842041, 0.03082600049674511, -0.07129299640655518, 0.9975910186767578, 0.024447999894618988, -0.06492199748754501, 0.9925040006637573, 0.07630900293588638, 0.09545700252056122, 0.49066001176834106, -0.09618999809026718, -0.5, 0.27721700072288513, -0.1851000040769577, -0.6666669845581055, 0.76139897108078, -0.32566601037979126, -0.18199099600315094, 0.9939020276069641, -0.062401000410318375, -0.09090700000524521, 0.9214360117912292, -0.3803209960460663, -0.00007100000220816582, 0.9969729781150818, -0.030918000265955925, 0.07133600115776062, 0.9220889806747437, -0.3804109990596771, 0.0001630000042496249, 0.9975799918174744, -0.02471200004220009, 0.06498300284147263, 0.926891028881073, -0.35510900616645813, 0.03216100111603737, 0.9924740195274353, -0.07657899707555771, -0.09555599838495255, 0.1851000040769577, -0.27721700072288513, -0.6666669845581055, 0.5781109929084778, -0.5929989814758301, -0.18205299973487854, 0.7048519849777222, -0.7048519849777222, 0, 0.7054179906845093, -0.7052720189094543, -0.00002499999936844688, 0.7199410200119019, -0.6835219860076904, 0.03204600140452385, 0.06412599980831146, -0.3271070122718811, -0.6666669845581055, 0.3061000108718872, -0.7694699764251709, -0.18225300312042236, 0.38033199310302734, -0.9214379787445068, 0.0000670000008540228, 0.3804430067539215, -0.9220880270004272, -0.00016799999866634607, 0.403003990650177, -0.9071130156517029, 0.032437000423669815, 0, 0, -1, 0.04157499969005585, -0.6626030206680298, -0.272724986076355, 0.03082600049674511, -0.9969789981842041, -0.07129299640655518, 0.024447999894618988, -0.9975910186767578, -0.06492199748754501, 0.07630900293588638, -0.9925040006637573, 0.09545700252056122, -0.09618999809026718, -0.49066001176834106, -0.5, -0.1851000040769577, -0.27721700072288513, -0.6666669845581055, -0.32566601037979126, -0.76139897108078, -0.18199099600315094, -0.062401000410318375, -0.9939020276069641, -0.09090700000524521, -0.3803209960460663, -0.9214360117912292, -0.00007100000220816582, -0.030918000265955925, -0.9969729781150818, 0.07133600115776062, -0.3804109990596771, -0.9220889806747437, 0.0001630000042496249, -0.02471200004220009, -0.9975799918174744, 0.06498300284147263, -0.35510900616645813, -0.926891028881073, 0.03216100111603737, -0.07657899707555771, -0.9924740195274353, -0.09555599838495255, -0.27721700072288513, -0.1851000040769577, -0.6666669845581055, -0.5929989814758301, -0.5781109929084778, -0.18205299973487854, -0.7048519849777222, -0.7048519849777222, 0, -0.7052720189094543, -0.7054179906845093, -0.00002499999936844688, -0.6835219860076904, -0.7199410200119019, 0.03204600140452385, -0.3271070122718811, -0.06412599980831146, -0.6666669845581055, -0.7694699764251709, -0.3061000108718872, -0.18225300312042236, -0.9214379787445068, -0.38033199310302734, 0.0000670000008540228, -0.9220880270004272, -0.3804430067539215, -0.00016799999866634607, -0.9071130156517029, -0.403003990650177, 0.032437000423669815, 0, 0, -1, -0.6626030206680298, -0.04157499969005585, -0.272724986076355, -0.9969789981842041, -0.03082600049674511, -0.07129299640655518, -0.9975910186767578, -0.024447999894618988, -0.06492199748754501, -0.9925040006637573, -0.07630900293588638, 0.09545700252056122, -0.49066001176834106, 0.09618999809026718, -0.5, -0.27721700072288513, 0.1851000040769577, -0.6666669845581055, -0.76139897108078, 0.32566601037979126, -0.18199099600315094, -0.9939020276069641, 0.062401000410318375, -0.09090700000524521, -0.9214360117912292, 0.3803209960460663, -0.00007100000220816582, -0.9969729781150818, 0.030918000265955925, 0.07133600115776062, -0.9220889806747437, 0.3804109990596771, 0.0001630000042496249, -0.9975799918174744, 0.02471200004220009, 0.06498300284147263, -0.926891028881073, 0.35510900616645813, 0.03216100111603737, -0.9924740195274353, 0.07657899707555771, -0.09555599838495255, -0.1851000040769577, 0.27721700072288513, -0.6666669845581055, -0.5781109929084778, 0.5929989814758301, -0.18205299973487854, -0.7048519849777222, 0.7048519849777222, 0, -0.7054179906845093, 0.7052720189094543, -0.00002499999936844688, -0.7199410200119019, 0.6835219860076904, 0.03204600140452385, -0.06412599980831146, 0.3271070122718811, -0.6666669845581055, -0.3061000108718872, 0.7694699764251709, -0.18225300312042236, -0.38033199310302734, 0.9214379787445068, 0.0000670000008540228, -0.3804430067539215, 0.9220880270004272, -0.00016799999866634607, -0.403003990650177, 0.9071130156517029, 0.032437000423669815, 0, 0, -1, -0.04157499969005585, 0.6626030206680298, -0.272724986076355, -0.03082600049674511, 0.9969789981842041, -0.07129299640655518, -0.024447999894618988, 0.9975910186767578, -0.06492199748754501, -0.07630900293588638, 0.9925040006637573, 0.09545700252056122, 0.07657899707555771, 0.9924740195274353, -0.09555599838495255, 0.40307098627090454, 0.9070649743080139, -0.03255299851298332, 0.3753640055656433, 0.9070209860801697, 0.000007000000096013537, 0.18306200206279755, 0.9820899963378906, -0.04457399994134903, 0.3751649856567383, 0.9065750241279602, -0.00007400000322377309, 0.18801499903202057, 0.9816100001335144, -0.03304100036621094, 0.3759070038795471, 0.908607006072998, -0.00026199998683296144, 0.16623400151729584, 0.983722984790802, -0.06822899729013443, 0.33324098587036133, 0.9290030002593994, 0.029803000390529633, 0.14071400463581085, 0.9862040281295776, -0.08718100190162659, 0.7198299765586853, 0.6836559772491455, -0.032017000019550323, 0.6943539977073669, 0.6943539977073669, 0, 0.694034993648529, 0.694034993648529, 0, 0.6955100297927856, 0.6955100297927856, 0, 0.6639170050621033, 0.7306150197982788, 0.029100999236106873, 0.9268649816513062, 0.35523301362991333, -0.03203999996185303, 0.9070209860801697, 0.3753649890422821, -0.000007000000096013537, 0.9065750241279602, 0.3751649856567383, 0.00007300000288523734, 0.908607006072998, 0.3759070038795471, 0.00026199998683296144, 0.8926259875297546, 0.4211460053920746, 0.028991999104619026, 0.9924740195274353, 0.07646500319242477, 0.09565100073814392, 0.9820899963378906, 0.18306200206279755, 0.04457399994134903, 0.9816100001335144, 0.18801499903202057, 0.03304100036621094, 0.983722984790802, 0.16623400151729584, 0.06822899729013443, 0.9862040281295776, 0.14071400463581085, 0.08718100190162659, 0.9924740195274353, -0.07657899707555771, -0.09555599838495255, 0.9070649743080139, -0.40307098627090454, -0.03255299851298332, 0.9070209860801697, -0.3753640055656433, 0.000007000000096013537, 0.9820899963378906, -0.18306200206279755, -0.04457399994134903, 0.9065750241279602, -0.3751649856567383, -0.00007400000322377309, 0.9816100001335144, -0.18801499903202057, -0.03304100036621094, 0.908607006072998, -0.3759070038795471, -0.00026199998683296144, 0.983722984790802, -0.16623400151729584, -0.06822899729013443, 0.9290030002593994, -0.33324098587036133, 0.029803000390529633, 0.9862040281295776, -0.14071400463581085, -0.08718100190162659, 0.6836559772491455, -0.7198299765586853, -0.032017000019550323, 0.6943539977073669, -0.6943539977073669, 0, 0.694034993648529, -0.694034993648529, 0, 0.6955100297927856, -0.6955100297927856, 0, 0.7306150197982788, -0.6639170050621033, 0.029100999236106873, 0.35523301362991333, -0.9268649816513062, -0.03203999996185303, 0.3753649890422821, -0.9070209860801697, -0.000007000000096013537, 0.3751649856567383, -0.9065750241279602, 0.00007300000288523734, 0.3759070038795471, -0.908607006072998, 0.00026199998683296144, 0.4211460053920746, -0.8926259875297546, 0.028991999104619026, 0.07646500319242477, -0.9924740195274353, 0.09565100073814392, 0.18306200206279755, -0.9820899963378906, 0.04457399994134903, 0.18801499903202057, -0.9816100001335144, 0.03304100036621094, 0.16623400151729584, -0.983722984790802, 0.06822899729013443, 0.14071400463581085, -0.9862040281295776, 0.08718100190162659, -0.07657899707555771, -0.9924740195274353, -0.09555599838495255, -0.40307098627090454, -0.9070649743080139, -0.03255299851298332, -0.3753640055656433, -0.9070209860801697, 0.000007000000096013537, -0.18306200206279755, -0.9820899963378906, -0.04457399994134903, -0.3751649856567383, -0.9065750241279602, -0.00007400000322377309, -0.18801499903202057, -0.9816100001335144, -0.03304100036621094, -0.3759070038795471, -0.908607006072998, -0.00026199998683296144, -0.16623400151729584, -0.983722984790802, -0.06822899729013443, -0.33324098587036133, -0.9290030002593994, 0.029803000390529633, -0.14071400463581085, -0.9862040281295776, -0.08718100190162659, -0.7198299765586853, -0.6836559772491455, -0.032017000019550323, -0.6943539977073669, -0.6943539977073669, 0, -0.694034993648529, -0.694034993648529, 0, -0.6955100297927856, -0.6955100297927856, 0, -0.6639170050621033, -0.7306150197982788, 0.029100999236106873, -0.9268649816513062, -0.35523301362991333, -0.03203999996185303, -0.9070209860801697, -0.3753649890422821, -0.000007000000096013537, -0.9065750241279602, -0.3751649856567383, 0.00007300000288523734, -0.908607006072998, -0.3759070038795471, 0.00026199998683296144, -0.8926259875297546, -0.4211460053920746, 0.028991999104619026, -0.9924740195274353, -0.07646500319242477, 0.09565100073814392, -0.9820899963378906, -0.18306200206279755, 0.04457399994134903, -0.9816100001335144, -0.18801499903202057, 0.03304100036621094, -0.983722984790802, -0.16623400151729584, 0.06822899729013443, -0.9862040281295776, -0.14071400463581085, 0.08718100190162659, -0.9924740195274353, 0.07657899707555771, -0.09555599838495255, -0.9070649743080139, 0.40307098627090454, -0.03255299851298332, -0.9070209860801697, 0.3753640055656433, 0.000007000000096013537, -0.9820899963378906, 0.18306200206279755, -0.04457399994134903, -0.9065750241279602, 0.3751649856567383, -0.00007400000322377309, -0.9816100001335144, 0.18801499903202057, -0.03304100036621094, -0.908607006072998, 0.3759070038795471, -0.00026199998683296144, -0.983722984790802, 0.16623400151729584, -0.06822899729013443, -0.9290030002593994, 0.33324098587036133, 0.029803000390529633, -0.9862040281295776, 0.14071400463581085, -0.08718100190162659, -0.6836559772491455, 0.7198299765586853, -0.032017000019550323, -0.6943539977073669, 0.6943539977073669, 0, -0.694034993648529, 0.694034993648529, 0, -0.6955100297927856, 0.6955100297927856, 0, -0.7306150197982788, 0.6639170050621033, 0.029100999236106873, -0.35523301362991333, 0.9268649816513062, -0.03203999996185303, -0.3753649890422821, 0.9070209860801697, -0.000007000000096013537, -0.3751649856567383, 0.9065750241279602, 0.00007300000288523734, -0.3759070038795471, 0.908607006072998, 0.00026199998683296144, -0.4211460053920746, 0.8926259875297546, 0.028991999104619026, -0.07646500319242477, 0.9924740195274353, 0.09565100073814392, -0.18306200206279755, 0.9820899963378906, 0.04457399994134903, -0.18801499903202057, 0.9816100001335144, 0.03304100036621094, -0.16623400151729584, 0.983722984790802, 0.06822899729013443, -0.14071400463581085, 0.9862040281295776, 0.08718100190162659 ]);
var teapotBinormals = new Float32Array([ 0.2554270029067993, -0.05043400079011917, -0.9655119776725769, 0.2302899956703186, -0.11379700154066086, -0.9664459824562073, -0.23653900623321533, 0.09789499640464783, -0.9666780233383179, -0.2551180124282837, 0.05037299916148186, -0.9655969738960266, -0.9201610088348389, 0.38079801201820374, -0.09108299762010574, -0.9770479798316956, 0.1929199993610382, -0.09032399952411652, -0.6762400269508362, 0.2798590064048767, 0.6814529895782471, -0.723800003528595, 0.1429159939289093, 0.6750479936599731, -0.4681990146636963, 0.1581760048866272, 0.869350016117096, -0.4902079999446869, 0.09679199755191803, 0.8662149906158447, 0.16952399909496307, -0.1934960037469864, -0.9663439989089966, -0.18106800317764282, 0.18106800317764282, -0.9666590094566345, -0.7041199803352356, 0.7041199803352356, -0.09181900322437286, -0.5179349780082703, 0.5179349780082703, 0.6807990074157715, -0.37217798829078674, 0.3260670006275177, 0.8690019845962524, 0.08221600204706192, -0.243368998169899, -0.9664430022239685, -0.09789499640464783, 0.23653900623321533, -0.9666780233383179, -0.38079801201820374, 0.9201610088348389, -0.09108199924230576, -0.2798590064048767, 0.6762400269508362, 0.6814540028572083, -0.21894000470638275, 0.44305500388145447, 0.8693490028381348, 0.050822000950574875, -0.2573910057544708, -0.9649699926376343, -0.05021600052714348, 0.25432100892066956, -0.965815007686615, -0.19291600584983826, 0.9770249724388123, -0.09059000015258789, -0.14291299879550934, 0.7237870097160339, 0.6750609874725342, -0.09679199755191803, 0.4902079999446869, 0.8662149906158447, -0.048507001250982285, -0.2576940059661865, -0.965008020401001, -0.15833300352096558, -0.3227809965610504, -0.933135986328125, 0.05656199902296066, 0.13793900609016418, -0.9888240098953247, 0.049150001257658005, 0.2545199990272522, -0.9658179879188538, 0.378387987613678, 0.9173290133476257, -0.12381099909543991, 0.1917950063943863, 0.9772530198097229, -0.09050799906253815, 0.2777239978313446, 0.6716070175170898, 0.6868870258331299, 0.14281700551509857, 0.7238019704818726, 0.6750659942626953, 0.15788200497627258, 0.4674209952354431, 0.8698220252990723, 0.09679199755191803, 0.4902079999446869, 0.8662149906158447, -0.3139069974422455, -0.2657270133495331, -0.9115110039710999, 0.05247500166296959, 0.05178600177168846, -0.9972789883613586, 0.699787974357605, 0.6969379782676697, -0.15676100552082062, 0.511929988861084, 0.5116159915924072, 0.6900550127029419, 0.32515400648117065, 0.37111398577690125, 0.8697980046272278, -0.3181929886341095, -0.09987600147724152, -0.9427499771118164, 0.1552799940109253, 0.06176299974322319, -0.9859380125999451, 0.9187250137329102, 0.3751460015773773, -0.1233299970626831, 0.6724870204925537, 0.2775439918041229, 0.6860979795455933, 0.4424299895763397, 0.21853800117969513, 0.8697689771652222, -0.255948007106781, -0.04464200139045715, -0.9656590223312378, 0.25306200981140137, 0.046362001448869705, -0.9663389921188354, 0.9778940081596375, 0.18800100684165955, -0.09153299778699875, 0.7238150238990784, 0.14205799996852875, 0.675212025642395, 0.49017900228500366, 0.0967010036110878, 0.8662409782409668, -0.25491899251937866, 0.05033399909734726, -0.9656509757041931, -0.2302899956703186, 0.11379700154066086, -0.9664459824562073, 0.23653900623321533, -0.09789499640464783, -0.9666780233383179, 0.252265989780426, -0.04980999976396561, -0.9663749933242798, 0.9201610088348389, -0.38079801201820374, -0.09108299762010574, 0.9769039750099182, -0.19289200007915497, -0.09193000197410583, 0.6762400269508362, -0.2798590064048767, 0.6814529895782471, 0.7236610054969788, -0.14288799464702606, 0.6752020120620728, 0.4681990146636963, -0.1581760048866272, 0.869350016117096, 0.4901660084724426, -0.09678400307893753, 0.8662390112876892, -0.16952399909496307, 0.1934960037469864, -0.9663439989089966, 0.18106800317764282, -0.18106800317764282, -0.9666590094566345, 0.7041199803352356, -0.7041199803352356, -0.09181900322437286, 0.5179349780082703, -0.5179349780082703, 0.6807990074157715, 0.37217798829078674, -0.3260670006275177, 0.8690019845962524, -0.08221600204706192, 0.243368998169899, -0.9664430022239685, 0.09789499640464783, -0.23653900623321533, -0.9666780233383179, 0.38079801201820374, -0.9201610088348389, -0.09108199924230576, 0.2798590064048767, -0.6762400269508362, 0.6814540028572083, 0.21894000470638275, -0.44305500388145447, 0.8693490028381348, -0.05043400079011917, 0.2554270029067993, -0.9655119776725769, 0.05037299916148186, -0.2551180124282837, -0.9655969738960266, 0.1929199993610382, -0.9770479798316956, -0.09032399952411652, 0.1429159939289093, -0.723800003528595, 0.6750479936599731, 0.09679199755191803, -0.4902079999446869, 0.8662149906158447, 0.05043400079011917, 0.2554270029067993, -0.9655119776725769, 0.11379700154066086, 0.2302899956703186, -0.9664459824562073, -0.09789499640464783, -0.23653900623321533, -0.9666780233383179, -0.05037299916148186, -0.2551180124282837, -0.9655969738960266, -0.38079801201820374, -0.9201610088348389, -0.09108299762010574, -0.1929199993610382, -0.9770479798316956, -0.09032399952411652, -0.2798590064048767, -0.6762400269508362, 0.6814529895782471, -0.1429159939289093, -0.723800003528595, 0.6750479936599731, -0.1581760048866272, -0.4681990146636963, 0.869350016117096, -0.09679199755191803, -0.4902079999446869, 0.8662149906158447, 0.1934960037469864, 0.16952399909496307, -0.9663439989089966, -0.18106800317764282, -0.18106800317764282, -0.9666590094566345, -0.7041199803352356, -0.7041199803352356, -0.09181900322437286, -0.5179349780082703, -0.5179349780082703, 0.6807990074157715, -0.3260670006275177, -0.37217798829078674, 0.8690019845962524, 0.243368998169899, 0.08221600204706192, -0.9664430022239685, -0.23653900623321533, -0.09789499640464783, -0.9666780233383179, -0.9201610088348389, -0.38079801201820374, -0.09108199924230576, -0.6762400269508362, -0.2798590064048767, 0.6814540028572083, -0.44305500388145447, -0.21894000470638275, 0.8693490028381348, 0.2554270029067993, 0.05043400079011917, -0.9655119776725769, -0.2551180124282837, -0.05037299916148186, -0.9655969738960266, -0.9770479798316956, -0.1929199993610382, -0.09032399952411652, -0.723800003528595, -0.1429159939289093, 0.6750479936599731, -0.4902079999446869, -0.09679199755191803, 0.8662149906158447, -0.4902079999446869, 0.09679199755191803, 0.8662149906158447, -0.44305500388145447, 0.21893900632858276, 0.8693490028381348, -0.37287598848342896, 0.15431199967861176, 0.9149600267410278, -0.4014579951763153, 0.07926800101995468, 0.9124410152435303, -0.3112579882144928, 0.12881100177764893, 0.9415550231933594, -0.33541300892829895, 0.0662280023097992, 0.939740002155304, -0.19015200436115265, 0.07869099825620651, 0.9785959720611572, -0.20517399907112122, 0.040511999279260635, 0.977886974811554, 0.06301800161600113, -0.021289000287652016, 0.9977849721908569, 0.06623400002717972, -0.013078000396490097, 0.9977179765701294, -0.3260670006275177, 0.37217798829078674, 0.8690019845962524, -0.285739004611969, 0.285739004611969, 0.9147170186042786, -0.23854400217533112, 0.23854400217533112, 0.9413790106773376, -0.14574900269508362, 0.14574900269508362, 0.9785270094871521, 0.05011200159788132, -0.04390300065279007, 0.9977779984474182, -0.1581760048866272, 0.4681999981403351, 0.869350016117096, -0.15431199967861176, 0.37287598848342896, 0.9149600267410278, -0.12881100177764893, 0.3112579882144928, 0.9415550231933594, -0.07869099825620651, 0.19015100598335266, 0.9785959720611572, 0.02946699969470501, -0.05963199958205223, 0.9977849721908569, -0.09679199755191803, 0.4902079999446869, 0.8662149906158447, -0.07926800101995468, 0.4014579951763153, 0.9124410152435303, -0.0662280023097992, 0.33541300892829895, 0.939740002155304, -0.040511999279260635, 0.20517399907112122, 0.977886974811554, 0.013078000396490097, -0.06623400002717972, 0.9977179765701294, 0.09679199755191803, 0.4902079999446869, 0.8662149906158447, 0.21858200430870056, 0.4423219859600067, 0.86981201171875, 0.15431199967861176, 0.37287598848342896, 0.9149600267410278, 0.07926800101995468, 0.4014579951763153, 0.9124410152435303, 0.12881100177764893, 0.3112579882144928, 0.9415550231933594, 0.0662280023097992, 0.33541300892829895, 0.939740002155304, 0.07869099825620651, 0.19015200436115265, 0.9785959720611572, 0.040511999279260635, 0.20517399907112122, 0.977886974811554, -0.021289000287652016, -0.06301800161600113, 0.9977849721908569, -0.013078000396490097, -0.06623400002717972, 0.9977179765701294, 0.3711329996585846, 0.3251489996910095, 0.8697919845581055, 0.285739004611969, 0.285739004611969, 0.9147170186042786, 0.23854400217533112, 0.23854400217533112, 0.9413790106773376, 0.14574900269508362, 0.14574900269508362, 0.9785270094871521, -0.04390300065279007, -0.05011200159788132, 0.9977779984474182, 0.46750199794769287, 0.15794099867343903, 0.8697680234909058, 0.37287598848342896, 0.15431199967861176, 0.9149600267410278, 0.3112579882144928, 0.12881100177764893, 0.9415550231933594, 0.19015100598335266, 0.07869099825620651, 0.9785959720611572, -0.05963199958205223, -0.02946699969470501, 0.9977849721908569, 0.49017900228500366, 0.0967010036110878, 0.8662409782409668, 0.4014579951763153, 0.07926800101995468, 0.9124410152435303, 0.33541300892829895, 0.0662280023097992, 0.939740002155304, 0.20517399907112122, 0.040511999279260635, 0.977886974811554, -0.06623400002717972, -0.013078000396490097, 0.9977179765701294, 0.4901660084724426, -0.09678400307893753, 0.8662390112876892, 0.44305500388145447, -0.21893900632858276, 0.8693490028381348, 0.37287598848342896, -0.15431199967861176, 0.9149600267410278, 0.4014579951763153, -0.07926800101995468, 0.9124410152435303, 0.3112579882144928, -0.12881100177764893, 0.9415550231933594, 0.33541300892829895, -0.0662280023097992, 0.939740002155304, 0.19015200436115265, -0.07869099825620651, 0.9785959720611572, 0.20517399907112122, -0.040511999279260635, 0.977886974811554, -0.06301800161600113, 0.021289000287652016, 0.9977849721908569, -0.06623400002717972, 0.013078000396490097, 0.9977179765701294, 0.3260670006275177, -0.37217798829078674, 0.8690019845962524, 0.285739004611969, -0.285739004611969, 0.9147170186042786, 0.23854400217533112, -0.23854400217533112, 0.9413790106773376, 0.14574900269508362, -0.14574900269508362, 0.9785270094871521, -0.05011200159788132, 0.04390300065279007, 0.9977779984474182, 0.1581760048866272, -0.4681999981403351, 0.869350016117096, 0.15431199967861176, -0.37287598848342896, 0.9149600267410278, 0.12881100177764893, -0.3112579882144928, 0.9415550231933594, 0.07869099825620651, -0.19015100598335266, 0.9785959720611572, -0.02946699969470501, 0.05963199958205223, 0.9977849721908569, 0.09679199755191803, -0.4902079999446869, 0.8662149906158447, 0.07926800101995468, -0.4014579951763153, 0.9124410152435303, 0.0662280023097992, -0.33541300892829895, 0.939740002155304, 0.040511999279260635, -0.20517399907112122, 0.977886974811554, -0.013078000396490097, 0.06623400002717972, 0.9977179765701294, -0.09679199755191803, -0.4902079999446869, 0.8662149906158447, -0.21893900632858276, -0.44305500388145447, 0.8693490028381348, -0.15431199967861176, -0.37287598848342896, 0.9149600267410278, -0.07926800101995468, -0.4014579951763153, 0.9124410152435303, -0.12881100177764893, -0.3112579882144928, 0.9415550231933594, -0.0662280023097992, -0.33541300892829895, 0.939740002155304, -0.07869099825620651, -0.19015200436115265, 0.9785959720611572, -0.040511999279260635, -0.20517399907112122, 0.977886974811554, 0.021289000287652016, 0.06301800161600113, 0.9977849721908569, 0.013078000396490097, 0.06623400002717972, 0.9977179765701294, -0.37217798829078674, -0.3260670006275177, 0.8690019845962524, -0.285739004611969, -0.285739004611969, 0.9147170186042786, -0.23854400217533112, -0.23854400217533112, 0.9413790106773376, -0.14574900269508362, -0.14574900269508362, 0.9785270094871521, 0.04390300065279007, 0.05011200159788132, 0.9977779984474182, -0.4681999981403351, -0.1581760048866272, 0.869350016117096, -0.37287598848342896, -0.15431199967861176, 0.9149600267410278, -0.3112579882144928, -0.12881100177764893, 0.9415550231933594, -0.19015100598335266, -0.07869099825620651, 0.9785959720611572, 0.05963199958205223, 0.02946699969470501, 0.9977849721908569, -0.4902079999446869, -0.09679199755191803, 0.8662149906158447, -0.4014579951763153, -0.07926800101995468, 0.9124410152435303, -0.33541300892829895, -0.0662280023097992, 0.939740002155304, -0.20517399907112122, -0.040511999279260635, 0.977886974811554, 0.06623400002717972, 0.013078000396490097, 0.9977179765701294, 0.06623400002717972, -0.013078000396490097, 0.9977179765701294, 0.05963199958205223, -0.02946699969470501, 0.9977849721908569, 0.40303200483322144, -0.1667889952659607, 0.8998590111732483, 0.4339120090007782, -0.08567699790000916, 0.8968719840049744, 0.6326310038566589, -0.2618109881877899, 0.7288579940795898, 0.6777120232582092, -0.13381600379943848, 0.723048985004425, 0.6661339998245239, -0.27567601203918457, 0.6930140256881714, 0.7128540277481079, -0.14075499773025513, 0.6870430111885071, 0.5777599811553955, -0.19519099593162537, 0.792523980140686, 0.6036490201950073, -0.11919199675321579, 0.7882900238037109, 0.04390300065279007, -0.05011200159788132, 0.9977779984474182, 0.30884799361228943, -0.30884799361228943, 0.8995699882507324, 0.48457300662994385, -0.48457300662994385, 0.7282710075378418, 0.510138988494873, -0.510138988494873, 0.6924710273742676, 0.4591110050678253, -0.40222999453544617, 0.7921029925346375, 0.021289000287652016, -0.06301800161600113, 0.9977849721908569, 0.16678999364376068, -0.40303200483322144, 0.8998590111732483, 0.2618109881877899, -0.6326310038566589, 0.7288579940795898, 0.27567601203918457, -0.6661339998245239, 0.6930140256881714, 0.2701770067214966, -0.546737015247345, 0.7925170063972473, 0.013078000396490097, -0.06623400002717972, 0.9977179765701294, 0.08567599952220917, -0.4339120090007782, 0.8968719840049744, 0.13381600379943848, -0.6777120232582092, 0.723048985004425, 0.14075499773025513, -0.7128540277481079, 0.6870430111885071, 0.11919199675321579, -0.6036490201950073, 0.7882900238037109, -0.013078000396490097, -0.06623400002717972, 0.9977179765701294, -0.02946699969470501, -0.05963199958205223, 0.9977849721908569, -0.1667889952659607, -0.40303200483322144, 0.8998590111732483, -0.08567699790000916, -0.4339120090007782, 0.8968719840049744, -0.2618109881877899, -0.6326310038566589, 0.7288579940795898, -0.13381600379943848, -0.6777120232582092, 0.723048985004425, -0.27567601203918457, -0.6661339998245239, 0.6930140256881714, -0.14075499773025513, -0.7128540277481079, 0.6870430111885071, -0.19519099593162537, -0.5777599811553955, 0.792523980140686, -0.11919199675321579, -0.6036490201950073, 0.7882900238037109, -0.05011200159788132, -0.04390300065279007, 0.9977779984474182, -0.30884799361228943, -0.30884799361228943, 0.8995699882507324, -0.48457300662994385, -0.48457300662994385, 0.7282710075378418, -0.510138988494873, -0.510138988494873, 0.6924710273742676, -0.40222999453544617, -0.4591110050678253, 0.7921029925346375, -0.06301800161600113, -0.021289000287652016, 0.9977849721908569, -0.40303200483322144, -0.16678999364376068, 0.8998590111732483, -0.6326310038566589, -0.2618109881877899, 0.7288579940795898, -0.6661339998245239, -0.27567601203918457, 0.6930140256881714, -0.546737015247345, -0.2701770067214966, 0.7925170063972473, -0.06623400002717972, -0.013078000396490097, 0.9977179765701294, -0.4339120090007782, -0.08567599952220917, 0.8968719840049744, -0.6777120232582092, -0.13381600379943848, 0.723048985004425, -0.7128540277481079, -0.14075499773025513, 0.6870430111885071, -0.6036490201950073, -0.11919199675321579, 0.7882900238037109, -0.06623400002717972, 0.013078000396490097, 0.9977179765701294, -0.05963199958205223, 0.02946699969470501, 0.9977849721908569, -0.40303200483322144, 0.1667889952659607, 0.8998590111732483, -0.4339120090007782, 0.08567699790000916, 0.8968719840049744, -0.6326310038566589, 0.2618109881877899, 0.7288579940795898, -0.6777120232582092, 0.13381600379943848, 0.723048985004425, -0.6661339998245239, 0.27567601203918457, 0.6930140256881714, -0.7128540277481079, 0.14075499773025513, 0.6870430111885071, -0.5777599811553955, 0.19519099593162537, 0.792523980140686, -0.6036490201950073, 0.11919199675321579, 0.7882900238037109, -0.04390300065279007, 0.05011200159788132, 0.9977779984474182, -0.30884799361228943, 0.30884799361228943, 0.8995699882507324, -0.48457300662994385, 0.48457300662994385, 0.7282710075378418, -0.510138988494873, 0.510138988494873, 0.6924710273742676, -0.4591110050678253, 0.40222999453544617, 0.7921029925346375, -0.021289000287652016, 0.06301800161600113, 0.9977849721908569, -0.16678999364376068, 0.40303200483322144, 0.8998590111732483, -0.2618109881877899, 0.6326310038566589, 0.7288579940795898, -0.27567601203918457, 0.6661339998245239, 0.6930140256881714, -0.2701770067214966, 0.546737015247345, 0.7925170063972473, -0.013078000396490097, 0.06623400002717972, 0.9977179765701294, -0.08567599952220917, 0.4339120090007782, 0.8968719840049744, -0.13381600379943848, 0.6777120232582092, 0.723048985004425, -0.14075499773025513, 0.7128540277481079, 0.6870430111885071, -0.11919199675321579, 0.6036490201950073, 0.7882900238037109, 0.013078000396490097, 0.06623400002717972, 0.9977179765701294, 0.02946699969470501, 0.05963199958205223, 0.9977849721908569, 0.1667889952659607, 0.40303200483322144, 0.8998590111732483, 0.08567699790000916, 0.4339120090007782, 0.8968719840049744, 0.2618109881877899, 0.6326310038566589, 0.7288579940795898, 0.13381600379943848, 0.6777120232582092, 0.723048985004425, 0.27567601203918457, 0.6661339998245239, 0.6930140256881714, 0.14075499773025513, 0.7128540277481079, 0.6870430111885071, 0.19519099593162537, 0.5777599811553955, 0.792523980140686, 0.11919199675321579, 0.6036490201950073, 0.7882900238037109, 0.05011200159788132, 0.04390300065279007, 0.9977779984474182, 0.30884799361228943, 0.30884799361228943, 0.8995699882507324, 0.48457300662994385, 0.48457300662994385, 0.7282710075378418, 0.510138988494873, 0.510138988494873, 0.6924710273742676, 0.40222999453544617, 0.4591110050678253, 0.7921029925346375, 0.06301800161600113, 0.021289000287652016, 0.9977849721908569, 0.40303200483322144, 0.16678999364376068, 0.8998590111732483, 0.6326310038566589, 0.2618109881877899, 0.7288579940795898, 0.6661339998245239, 0.27567601203918457, 0.6930140256881714, 0.546737015247345, 0.2701770067214966, 0.7925170063972473, 0.06623400002717972, 0.013078000396490097, 0.9977179765701294, 0.4339120090007782, 0.08567599952220917, 0.8968719840049744, 0.6777120232582092, 0.13381600379943848, 0.723048985004425, 0.7128540277481079, 0.14075499773025513, 0.6870430111885071, 0.6036490201950073, 0.11919199675321579, 0.7882900238037109, 0.6036490201950073, -0.11919199675321579, 0.7882900238037109, 0.546737015247345, -0.2701770067214966, 0.7925170063972473, 0.7223830223083496, -0.2989569902420044, 0.623528003692627, 0.7723940014839172, -0.15251100063323975, 0.616562008857727, 0.9094089865684509, -0.3763520121574402, 0.1770150065422058, 0.9660869836807251, -0.19075599312782288, 0.1740349978208542, 0.9408230185508728, -0.3353259861469269, 0.04907499998807907, 0.9843119978904724, -0.16964000463485718, 0.048493001610040665, 0.9810580015182495, -0.1937119960784912, 0, 0.7071070075035095, -0.7071070075035095, 0, 0.40222999453544617, -0.4591110050678253, 0.7921029925346375, 0.5532029867172241, -0.5532029867172241, 0.622842013835907, 0.6959879994392395, -0.6959879994392395, 0.17663900554180145, 0.7403979897499084, -0.6703829765319824, 0.048958998173475266, 0.8310419917106628, -0.5562090277671814, 0, 0.19519099593162537, -0.5777599811553955, 0.792523980140686, 0.2989560067653656, -0.7223830223083496, 0.623528003692627, 0.3763520121574402, -0.9094089865684509, 0.1770150065422058, 0.4275760054588318, -0.902646005153656, 0.04906899854540825, 0.5562090277671814, -0.8310419917106628, 0, 0.11919199675321579, -0.6036490201950073, 0.7882900238037109, 0.15251100063323975, -0.7723940014839172, 0.616562008857727, 0.19075599312782288, -0.9660869836807251, 0.1740349978208542, 0.19348600506782532, -0.9799140095710754, 0.048277001827955246, 0.1937119960784912, -0.9810580015182495, 0, -0.11919199675321579, -0.6036490201950073, 0.7882900238037109, -0.2701770067214966, -0.546737015247345, 0.7925170063972473, -0.2989569902420044, -0.7223830223083496, 0.623528003692627, -0.15251100063323975, -0.7723940014839172, 0.616562008857727, -0.3763520121574402, -0.9094089865684509, 0.1770150065422058, -0.19075599312782288, -0.9660869836807251, 0.1740349978208542, -0.3353259861469269, -0.9408230185508728, 0.04907499998807907, -0.16964000463485718, -0.9843119978904724, 0.04849399998784065, -0.1937119960784912, -0.9810580015182495, 0, 0.7071070075035095, -0.7071070075035095, 0, -0.4591110050678253, -0.40222999453544617, 0.7921029925346375, -0.5532029867172241, -0.5532029867172241, 0.622842013835907, -0.6959879994392395, -0.6959879994392395, 0.17663900554180145, -0.6703829765319824, -0.7403979897499084, 0.048958998173475266, -0.5562090277671814, -0.8310419917106628, 0, -0.5777599811553955, -0.19519099593162537, 0.792523980140686, -0.7223830223083496, -0.2989560067653656, 0.623528003692627, -0.9094089865684509, -0.3763520121574402, 0.1770150065422058, -0.902646005153656, -0.4275760054588318, 0.04906899854540825, -0.8310419917106628, -0.5562090277671814, 0, -0.6036490201950073, -0.11919199675321579, 0.7882900238037109, -0.7723940014839172, -0.15251100063323975, 0.616562008857727, -0.9660869836807251, -0.19075599312782288, 0.1740349978208542, -0.9799140095710754, -0.19348600506782532, 0.048277001827955246, -0.9810580015182495, -0.1937119960784912, 0, -0.6036490201950073, 0.11919199675321579, 0.7882900238037109, -0.546737015247345, 0.2701770067214966, 0.7925170063972473, -0.7223830223083496, 0.2989569902420044, 0.623528003692627, -0.7723940014839172, 0.15251100063323975, 0.616562008857727, -0.9094089865684509, 0.3763520121574402, 0.1770150065422058, -0.9660869836807251, 0.19075599312782288, 0.1740349978208542, -0.9408230185508728, 0.3353259861469269, 0.04907499998807907, -0.9843119978904724, 0.16964000463485718, 0.04849399998784065, -0.9810580015182495, 0.1937119960784912, 0, 0.7071070075035095, -0.7071070075035095, 0, -0.40222999453544617, 0.4591110050678253, 0.7921029925346375, -0.5532029867172241, 0.5532029867172241, 0.622842013835907, -0.6959879994392395, 0.6959879994392395, 0.17663900554180145, -0.7403979897499084, 0.6703829765319824, 0.048958998173475266, -0.8310419917106628, 0.5562090277671814, 0, -0.19519099593162537, 0.5777599811553955, 0.792523980140686, -0.2989560067653656, 0.7223830223083496, 0.623528003692627, -0.3763520121574402, 0.9094089865684509, 0.1770150065422058, -0.4275760054588318, 0.902646005153656, 0.04906899854540825, -0.5562090277671814, 0.8310419917106628, 0, -0.11919199675321579, 0.6036490201950073, 0.7882900238037109, -0.15251100063323975, 0.7723940014839172, 0.616562008857727, -0.19075599312782288, 0.9660869836807251, 0.1740349978208542, -0.19348600506782532, 0.9799140095710754, 0.048277001827955246, -0.1937119960784912, 0.9810580015182495, 0, 0.11919199675321579, 0.6036490201950073, 0.7882900238037109, 0.2701770067214966, 0.546737015247345, 0.7925170063972473, 0.2989569902420044, 0.7223830223083496, 0.623528003692627, 0.15251100063323975, 0.7723940014839172, 0.616562008857727, 0.3763520121574402, 0.9094089865684509, 0.1770150065422058, 0.19075599312782288, 0.9660869836807251, 0.1740349978208542, 0.3353259861469269, 0.9408230185508728, 0.04907499998807907, 0.16964000463485718, 0.9843119978904724, 0.04849399998784065, 0.1937119960784912, 0.9810580015182495, 0, 0.7071070075035095, -0.7071070075035095, 0, 0.4591110050678253, 0.40222999453544617, 0.7921029925346375, 0.5532029867172241, 0.5532029867172241, 0.622842013835907, 0.6959879994392395, 0.6959879994392395, 0.17663900554180145, 0.6703829765319824, 0.7403979897499084, 0.048958998173475266, 0.5562090277671814, 0.8310419917106628, 0, 0.5777599811553955, 0.19519099593162537, 0.792523980140686, 0.7223830223083496, 0.2989560067653656, 0.623528003692627, 0.9094089865684509, 0.3763520121574402, 0.1770150065422058, 0.902646005153656, 0.4275760054588318, 0.04906899854540825, 0.8310419917106628, 0.5562090277671814, 0, 0.6036490201950073, 0.11919199675321579, 0.7882900238037109, 0.7723940014839172, 0.15251100063323975, 0.616562008857727, 0.9660869836807251, 0.19075599312782288, 0.1740349978208542, 0.9799150228500366, 0.19348600506782532, 0.048277001827955246, 0.9810580015182495, 0.1937119960784912, 0, 0.9999480247497559, 0.006622000131756067, 0.007786999922245741, 0.9989290237426758, 0.04125700145959854, -0.020945999771356583, 0.9700260162353516, -0.18230900168418884, 0.1606609970331192, 0.9929869771003723, -0.1116809993982315, 0.038782998919487, 0.8677089810371399, -0.30993399024009705, 0.38861599564552307, 0.9675049781799316, -0.18179599940776825, 0.17574100196361542, 0.6127229928970337, -0.23797500133514404, 0.753616988658905, 0.781611979007721, -0.1585649996995926, 0.6032750010490417, 0.13049399852752686, -0.02585900016129017, 0.9911119937896729, 0.16583800315856934, -0.04606600105762482, 0.9850770235061646, 0.9847609996795654, -0.03004699945449829, -0.17129500210285187, 0.9463850259780884, 0.008138000033795834, 0.3229379951953888, 0.6942890286445618, 0.06011800095438957, 0.7171810269355774, 0.405923992395401, 0.09244199842214584, 0.9092199802398682, 0.1477230042219162, 0.0024739999789744616, 0.9890260100364685, 0.991798996925354, -0.11557900160551071, -0.05454900115728378, 0.9920099973678589, 0.07202500104904175, 0.10358300060033798, 0.8882240056991577, 0.2238840013742447, 0.40116599202156067, 0.6046879887580872, 0.13691100478172302, 0.7846069931983948, 0.12908099591732025, -0.06111999973654747, 0.989749014377594, 0.9954820275306702, -0.09436299651861191, 0.010502999648451805, 0.9981970191001892, 0.012060999870300293, 0.05880200117826462, 0.9550639986991882, 0.09819000214338303, 0.27966299653053284, 0.6606940031051636, 0.05169999971985817, 0.7488729953765869, 0.07273799926042557, -0.04034300148487091, 0.9965350031852722, 0.9999179840087891, 0.007191000040620565, 0.010560999624431133, 0.9989050030708313, 0.04436499997973442, -0.014883999712765217, 0.9694769978523254, -0.17860299348831177, 0.1679760068655014, 0.9924619793891907, -0.10775599628686905, 0.058378998190164566, 0.8567489981651306, -0.28829601407051086, 0.4276289939880371, 0.9473999738693237, -0.16112199425697327, 0.27653801441192627, 0.5627779960632324, -0.19747799634933472, 0.8026729822158813, 0.6577669978141785, -0.11438000202178955, 0.7444859743118286, 0.07901199907064438, 0.0013689999468624592, 0.9968730211257935, 0.07274100184440613, -0.02020600065588951, 0.9971460103988647, 0.9888780117034912, 0.025808999314904213, -0.14647500216960907, 0.9453979730606079, -0.006663000211119652, 0.3258500099182129, 0.6921399831771851, -0.04972299933433533, 0.7200480103492737, 0.3957499861717224, -0.08134900033473969, 0.9147480130195618, 0.13914500176906586, -0.00007899999764049426, 0.9902719855308533, 0.9924669861793518, -0.10311000049114227, -0.06616800278425217, 0.9926300048828125, 0.07926999777555466, 0.09165900200605392, 0.900858998298645, 0.2553130090236664, 0.3510949909687042, 0.6513699889183044, 0.18318000435829163, 0.7363160252571106, 0.15978699922561646, -0.02714099921286106, 0.9867780208587646, 0.9955620169639587, -0.09379199892282486, 0.0077309999614953995, 0.9991030097007751, 0.01610800065100193, 0.039149001240730286, 0.9764699935913086, 0.12068899720907211, 0.17871999740600586, 0.7859060168266296, 0.10103499889373779, 0.6100350022315979, 0.16579000651836395, -0.014832000248134136, 0.986050009727478, 0.1655299961566925, -0.10078699886798859, 0.9810410141944885, -0.0046790000051259995, -0.1750659942626953, 0.9845460057258606, -0.3859579861164093, -0.06494200229644775, 0.9202280044555664, -0.3219670057296753, -0.05600599944591522, 0.9450929760932922, -0.6471610069274902, -0.11495299637317657, 0.7536370158195496, -0.5616440176963806, -0.078855000436306, 0.8236119747161865, -0.8379700183868408, -0.23749999701976776, 0.4913240075111389, -0.7512590289115906, -0.1447169929742813, 0.6439470052719116, -0.9052090048789978, -0.2807050049304962, 0.3190630078315735, -0.8249419927597046, -0.2209009975194931, 0.5202630162239075, -0.13363699615001678, 0.0291920006275177, 0.9905999898910522, -0.4039649963378906, 0.0019519999623298645, 0.9147719740867615, -0.7191359996795654, 0.002443999983370304, 0.6948649883270264, -0.9637579917907715, 0.026884999126195908, 0.26541900634765625, -0.9637719988822937, 0.2207069993019104, -0.14977200329303741, 0.03522900119423866, 0.06716900318861008, 0.9971190094947815, -0.3620629906654358, -0.01676199957728386, 0.9320030212402344, -0.6534259915351868, 0.007120999973267317, 0.7569569945335388, -0.8528590202331543, 0.11686599999666214, 0.5088940262794495, -0.8814889788627625, 0.3579840064048767, 0.3079349994659424, 0.0726580023765564, 0.02018200047314167, 0.9971529841423035, -0.37608298659324646, -0.025955000892281532, 0.926222026348114, -0.6568350195884705, -0.015021000057458878, 0.7538840174674988, -0.8238760232925415, 0.03257700055837631, 0.5658339858055115, -0.8688690066337585, 0.13078700006008148, 0.4774540066719055, 0.07265599817037582, -0.07700400054454803, 0.994379997253418, -0.0343950018286705, -0.151870995759964, 0.9878020286560059, -0.40362000465393066, -0.05282000079751015, 0.9134010076522827, -0.37586501240730286, -0.041078001260757446, 0.9257640242576599, -0.6878190040588379, -0.0810059979557991, 0.721347987651825, -0.6558970212936401, -0.052101001143455505, 0.7530509829521179, -0.8708800077438354, -0.2062380015850067, 0.44613200426101685, -0.8175939917564392, -0.12448199838399887, 0.5621780157089233, -0.8926960229873657, -0.3055669963359833, 0.33124300837516785, -0.8565059900283813, -0.21024300158023834, 0.4713769853115082, -0.15155400335788727, -0.025412000715732574, 0.9881219863891602, -0.41033700108528137, -0.0026420000940561295, 0.9119300246238708, -0.7240620255470276, 0.00047400000039488077, 0.6897349953651428, -0.9655590057373047, -0.017078999429941177, 0.2596229910850525, -0.973825991153717, -0.19711799919605255, -0.1131730005145073, 0.06214199960231781, 0.08235500007867813, 0.9946640133857727, -0.3334290087223053, 0.007625999860465527, 0.9427440166473389, -0.608610987663269, 0.04885999858379364, 0.7919629812240601, -0.8253309726715088, 0.14631199836730957, 0.5453640222549438, -0.9105669856071472, 0.3146660029888153, 0.2680560052394867, 0.16523399949073792, 0.04589800164103508, 0.985185980796814, -0.32260099053382874, -0.010471000336110592, 0.9464769959449768, -0.5639140009880066, 0.015166000463068485, 0.8256940245628357, -0.758965015411377, 0.05617399886250496, 0.6487039923667908, -0.8382350206375122, 0.14245299994945526, 0.5263739824295044, 0.9727830290794373, -0.019794000312685966, 0.2308720052242279, 0.9828159809112549, -0.036465998739004135, 0.18095199763774872, 0.9050639867782593, -0.02252200059592724, 0.4246790111064911, 0.8354039788246155, -0.03220000118017197, 0.5486930012702942, 0.6465700268745422, -0.045921001583337784, 0.7614709734916687, 0.4826749861240387, -0.04895399883389473, 0.8744300007820129, 0.4453999996185303, 0.17256900668144226, 0.8785430192947388, 0.47231200337409973, 0.13768500089645386, 0.8706120252609253, 0.4824250042438507, 0.3898639976978302, 0.7843930125236511, 0.641398012638092, 0.4275979995727539, 0.6370000243186951, 0.9863939881324768, 0.0994419977068901, 0.13091400265693665, 0.9154840111732483, 0.2120320051908493, 0.34195101261138916, 0.7246469855308533, 0.245046004652977, 0.6440799832344055, 0.35685500502586365, 0.17885500192642212, 0.9168779850006104, 0.14101800322532654, 0.24263200163841248, 0.9598140120506287, 0.9366779923439026, 0.16484400629997253, 0.3089669942855835, 0.7982620000839233, 0.2441370040178299, 0.5506129860877991, 0.4769439995288849, 0.2904820144176483, 0.8295450210571289, 0.4125959873199463, -0.017246000468730927, 0.9107509851455688, 0.341374009847641, -0.37689098715782166, 0.8610560297966003, 0.9026100039482117, 0.14119599759578705, 0.40664398670196533, 0.7326020002365112, 0.1491979956626892, 0.6641039848327637, 0.38115599751472473, 0.15792299807071686, 0.9109230041503906, 0.5317370295524597, -0.02143399976193905, 0.846638023853302, 0.7915729880332947, -0.3539769947528839, 0.49810999631881714, 0.9087340235710144, -0.07959599792957306, 0.40971601009368896, 0.9386569857597351, -0.17680299282073975, 0.29607900977134705, 0.7781569957733154, -0.19467000663280487, 0.5971400141716003, 0.738847017288208, -0.07674700021743774, 0.6694890260696411, 0.43915998935699463, -0.22276799380779266, 0.870352029800415, 0.3863860070705414, -0.0790880024433136, 0.918940007686615, 0.3576120138168335, 0.07325199991464615, 0.9309930205345154, 0.5227140188217163, 0.16167999804019928, 0.8370360136032104, 0.4246380031108856, 0.3233239948749542, 0.845661997795105, 0.733618974685669, 0.48907899856567383, 0.4718089997768402, 0.9886019825935364, -0.10717800259590149, 0.10573200136423111, 0.9131960272789001, -0.22303399443626404, 0.3410690128803253, 0.7163559794425964, -0.25636500120162964, 0.6489310264587402, 0.35149699449539185, -0.15968100726604462, 0.922469973564148, 0.07999800145626068, -0.2107039988040924, 0.9742709994316101, 0.9883249998092651, 0.04732999950647354, 0.14482200145721436, 0.9210500121116638, 0.07413999736309052, 0.3823229968547821, 0.6804890036582947, 0.11895299702882767, 0.7230389714241028, 0.4935390055179596, -0.10269299894571304, 0.8636389970779419, 0.3912479877471924, -0.4752289950847626, 0.7880880236625671, 0.9700270295143127, 0.07970499992370605, 0.2295520007610321, 0.831250011920929, 0.10592100024223328, 0.5457149744033813, 0.4774230122566223, 0.13252699375152588, 0.8686220049858093, 0.47922399640083313, -0.0024129999801516533, 0.877689003944397, 0.6902980208396912, -0.29711300134658813, 0.6597059965133667, 0.6312130093574524, 0.4550989866256714, 0.628055989742279, 0.26494699716567993, 0.5426689982414246, 0.797065019607544, 0.4216960072517395, 0.6728450059890747, 0.6078259944915771, 0.7324270009994507, 0.5829970240592957, 0.35166099667549133, 0.5086709856987, 0.8606399893760681, -0.023507000878453255, 0.7640720009803772, 0.6449369788169861, -0.015798000618815422, 0.19029100239276886, 0.2773289978504181, -0.9417420029640198, 0.02588699944317341, 0.0005750000127591193, -0.9996650218963623, -0.33045700192451477, -0.4387669861316681, -0.8356329798698425, -0.6271269917488098, -0.4645389914512634, -0.6252319812774658, -0.02311599999666214, 0.2469020038843155, 0.9687650203704834, -0.012950999662280083, 0.45514100790023804, 0.8903250098228455, -0.001180000021122396, 0.9888520240783691, 0.14889399707317352, 0.019520999863743782, 0.6841210126876831, -0.7291070222854614, 0.012253000400960445, 0.007784999907016754, -0.9998949766159058, 0.3314639925956726, -0.3832260072231293, 0.8621309995651245, 0.08274699747562408, -0.16047699749469757, 0.9835649728775024, -0.381630003452301, 0.638043999671936, 0.6687729954719543, -0.44988399744033813, 0.787883996963501, -0.42052799463272095, -0.2780170142650604, 0.5983560085296631, -0.7514500021934509, 0.7378140091896057, -0.4918749928474426, 0.4622659981250763, 0.6153389811515808, -0.45540300011634827, 0.6434019804000854, -0.43678900599479675, 0.33411499857902527, 0.8352140188217163, -0.7429530024528503, 0.6388909816741943, -0.1995989978313446, -0.7432950139045715, 0.5977380275726318, -0.3003700077533722, 0.7197920083999634, 0.5168960094451904, 0.4633769989013672, 0.22183099389076233, 0.4485720098018646, 0.8657789826393127, 0.08203200250864029, 0.15848000347614288, 0.9839479923248291, 0.5953459739685059, 0.4811680018901825, 0.6434599757194519, -0.37556400895118713, -0.6215270161628723, 0.6875, -0.42666301131248474, -0.33534398674964905, 0.8399419784545898, -0.44476398825645447, -0.7887529730796814, -0.42432600259780884, -0.7557309865951538, -0.6222699880599976, -0.20408600568771362, -0.4292669892311096, -0.5361850261688232, -0.7267979979515076, -0.7655900120735168, -0.5671039819717407, -0.30375200510025024, 0.007348000071942806, -0.21113499999046326, 0.9774289727210999, -0.01990099996328354, -0.4373210072517395, 0.8990849852561951, -0.008775000460445881, -0.9864199757575989, 0.16400499641895294, 0.024855999276041985, -0.6829339861869812, -0.7300570011138916, 0.014514000155031681, 0.03655200079083443, -0.9992259740829468, 0.40192899107933044, -0.4676550030708313, 0.7872430086135864, 0.41696101427078247, -0.6813820004463196, 0.6015490293502808, 0.5033609867095947, -0.8637740015983582, -0.022839000448584557, 0.2058819979429245, -0.2945750057697296, -0.9331870079040527, -0.2351589947938919, 0.5535579919815063, -0.7989199757575989, 0.6533820033073425, -0.435588002204895, 0.619156002998352, 0.7555500268936157, -0.554410994052887, 0.3489600121974945, 0.7765160202980042, -0.6298360228538513, -0.01814199984073639, 0.025975000113248825, 0.008264999836683273, -0.9996280074119568, -0.6086519956588745, 0.49536699056625366, -0.6198019981384277, -0.9813200235366821, 0.19238099455833435, 0, -0.831650972366333, 0.5552989840507507, 0, -0.4425640106201172, 0.38308998942375183, 0.8107889890670776, -0.5623509883880615, 0.11026400327682495, 0.8195139765739441, 0.367917001247406, -0.15178599953651428, 0.917385995388031, 0.3960669934749603, -0.07774800062179565, 0.9149240255355835, 0.3283520042896271, -0.13562799990177155, 0.9347670078277588, 0.3530749976634979, -0.06952299922704697, 0.9330080151557922, -0.5935590267181396, 0.20035800337791443, 0.7794510126113892, -0.6201800107955933, 0.12245599925518036, 0.7748429775238037, -0.5552989840507507, 0.831650972366333, 0, -0.2616960108280182, 0.523730993270874, 0.8106920123100281, 0.2819640040397644, -0.2819199860095978, 0.9170699715614319, 0.25169798731803894, -0.25161200761795044, 0.9345269799232483, -0.4710330069065094, 0.41249701380729675, 0.7797269821166992, -0.19238099455833435, 0.9813200235366821, 0, -0.04031100124120712, 0.5840420126914978, 0.8107219934463501, 0.1517850011587143, -0.3678950071334839, 0.9173960089683533, 0.13561999797821045, -0.3282899856567383, 0.9347900152206421, -0.27737799286842346, 0.561601996421814, 0.779528021812439, -1, 0, 0, 0.29074999690055847, 0.5413560271263123, 0.7889220118522644, 0.07767199724912643, -0.39607399702072144, 0.9149270057678223, 0.0693729966878891, -0.3530940115451813, 0.9330130219459534, -0.12221000343561172, 0.6202139854431152, 0.7748550176620483, 0.19238099455833435, 0.9813200235366821, 0, 0.5552989840507507, 0.831650972366333, 0, 0.38308998942375183, 0.4425640106201172, 0.8107889890670776, 0.11026400327682495, 0.5623509883880615, 0.8195139765739441, -0.15178599953651428, -0.367917001247406, 0.917385995388031, -0.07774800062179565, -0.3960669934749603, 0.9149240255355835, -0.13562799990177155, -0.3283520042896271, 0.9347670078277588, -0.06952299922704697, -0.3530749976634979, 0.9330080151557922, 0.20035800337791443, 0.5935590267181396, 0.7794510126113892, 0.12245599925518036, 0.6201800107955933, 0.7748429775238037, 0.831650972366333, 0.5552989840507507, 0, 0.523730993270874, 0.2616960108280182, 0.8106920123100281, -0.2819199860095978, -0.2819640040397644, 0.9170699715614319, -0.25161200761795044, -0.25169798731803894, 0.9345269799232483, 0.41249701380729675, 0.4710330069065094, 0.7797269821166992, 0.9813200235366821, 0.19238099455833435, 0, 0.5840420126914978, 0.04031100124120712, 0.8107219934463501, -0.3678950071334839, -0.1517850011587143, 0.9173960089683533, -0.3282899856567383, -0.13561999797821045, 0.9347900152206421, 0.561601996421814, 0.27737799286842346, 0.779528021812439, -1, 0, 0, 0.5413560271263123, -0.29074999690055847, 0.7889220118522644, -0.39607399702072144, -0.07767199724912643, 0.9149270057678223, -0.3530940115451813, -0.0693729966878891, 0.9330130219459534, 0.6202139854431152, 0.12221000343561172, 0.7748550176620483, 0.9813200235366821, -0.19238099455833435, 0, 0.831650972366333, -0.5552989840507507, 0, 0.4425640106201172, -0.38308998942375183, 0.8107889890670776, 0.5623509883880615, -0.11026400327682495, 0.8195139765739441, -0.367917001247406, 0.15178599953651428, 0.917385995388031, -0.3960669934749603, 0.07774800062179565, 0.9149240255355835, -0.3283520042896271, 0.13562799990177155, 0.9347670078277588, -0.3530749976634979, 0.06952299922704697, 0.9330080151557922, 0.5935590267181396, -0.20035800337791443, 0.7794510126113892, 0.6201800107955933, -0.12245599925518036, 0.7748429775238037, 0.5552989840507507, -0.831650972366333, 0, 0.2616960108280182, -0.523730993270874, 0.8106920123100281, -0.2819640040397644, 0.2819199860095978, 0.9170699715614319, -0.25169798731803894, 0.25161200761795044, 0.9345269799232483, 0.4710330069065094, -0.41249701380729675, 0.7797269821166992, 0.19238099455833435, -0.9813200235366821, 0, 0.04031100124120712, -0.5840420126914978, 0.8107219934463501, -0.1517850011587143, 0.3678950071334839, 0.9173960089683533, -0.13561999797821045, 0.3282899856567383, 0.9347900152206421, 0.27737799286842346, -0.561601996421814, 0.779528021812439, -1, 0, 0, -0.29074999690055847, -0.5413560271263123, 0.7889220118522644, -0.07767199724912643, 0.39607399702072144, 0.9149270057678223, -0.0693729966878891, 0.3530940115451813, 0.9330130219459534, 0.12221000343561172, -0.6202139854431152, 0.7748550176620483, -0.19238099455833435, -0.9813200235366821, 0, -0.5552989840507507, -0.831650972366333, 0, -0.38308998942375183, -0.4425640106201172, 0.8107889890670776, -0.11026400327682495, -0.5623509883880615, 0.8195139765739441, 0.15178599953651428, 0.367917001247406, 0.917385995388031, 0.07774800062179565, 0.3960669934749603, 0.9149240255355835, 0.13562799990177155, 0.3283520042896271, 0.9347670078277588, 0.06952299922704697, 0.3530749976634979, 0.9330080151557922, -0.20035800337791443, -0.5935590267181396, 0.7794510126113892, -0.12245599925518036, -0.6201800107955933, 0.7748429775238037, -0.831650972366333, -0.5552989840507507, 0, -0.523730993270874, -0.2616960108280182, 0.8106920123100281, 0.2819199860095978, 0.2819640040397644, 0.9170699715614319, 0.25161200761795044, 0.25169798731803894, 0.9345269799232483, -0.41249701380729675, -0.4710330069065094, 0.7797269821166992, -0.9813200235366821, -0.19238099455833435, 0, -0.5840420126914978, -0.04031100124120712, 0.8107219934463501, 0.3678950071334839, 0.1517850011587143, 0.9173960089683533, 0.3282899856567383, 0.13561999797821045, 0.9347900152206421, -0.561601996421814, -0.27737799286842346, 0.779528021812439, -1, 0, 0, -0.5413560271263123, 0.29074999690055847, 0.7889220118522644, 0.39607399702072144, 0.07767199724912643, 0.9149270057678223, 0.3530940115451813, 0.0693729966878891, 0.9330130219459534, -0.6202139854431152, -0.12221000343561172, 0.7748550176620483, -0.6201800107955933, 0.12245599925518036, 0.7748429775238037, -0.5616469979286194, 0.27755099534988403, 0.7794349789619446, -0.8979210257530212, 0.37159600853919983, 0.23590999841690063, -0.9542099833488464, 0.18841099739074707, 0.23234599828720093, -0.9101200103759766, 0.3766449987888336, 0.17268399894237518, -0.966795027256012, 0.19089600443840027, 0.16990099847316742, -0.8549879789352417, 0.35383298993110657, 0.3792079985141754, -0.9100499749183655, 0.17969100177288055, 0.3735229969024658, -0.8064150214195251, 0.2724289894104004, 0.5248600244522095, -0.8383409976959229, 0.16553199291229248, 0.5194069743156433, -0.4125959873199463, 0.47094500064849854, 0.7797279953956604, -0.687192976474762, 0.687192976474762, 0.23565199971199036, -0.6965190172195435, 0.6965190172195435, 0.17240400612354279, -0.6544880270957947, 0.6544870138168335, 0.37853899598121643, -0.6404970288276672, 0.5611429810523987, 0.5242909789085388, -0.20047900080680847, 0.5933949947357178, 0.7795450091362, -0.3715969920158386, 0.8979210257530212, 0.23590999841690063, -0.3766449987888336, 0.9101200103759766, 0.17268399894237518, -0.35383298993110657, 0.8549879789352417, 0.3792079985141754, -0.3770729899406433, 0.7630789875984192, 0.5249059796333313, -0.12245900183916092, 0.6201940178871155, 0.7748309969902039, -0.18841099739074707, 0.9542099833488464, 0.23234599828720093, -0.19089600443840027, 0.966795027256012, 0.16990099847316742, -0.17969200015068054, 0.9100499749183655, 0.3735229969024658, -0.16553199291229248, 0.8383409976959229, 0.5194069743156433, 0.12245599925518036, 0.6201800107955933, 0.7748429775238037, 0.27755099534988403, 0.5616469979286194, 0.7794349789619446, 0.37159600853919983, 0.8979210257530212, 0.23590999841690063, 0.18841099739074707, 0.9542099833488464, 0.23234599828720093, 0.3766449987888336, 0.9101200103759766, 0.17268399894237518, 0.19089600443840027, 0.966795027256012, 0.16990099847316742, 0.35383298993110657, 0.8549879789352417, 0.3792079985141754, 0.17969100177288055, 0.9100499749183655, 0.3735229969024658, 0.2724289894104004, 0.8064150214195251, 0.5248600244522095, 0.16553199291229248, 0.8383409976959229, 0.5194069743156433, 0.47094500064849854, 0.4125959873199463, 0.7797279953956604, 0.687192976474762, 0.687192976474762, 0.23565199971199036, 0.6965190172195435, 0.6965190172195435, 0.17240400612354279, 0.6544870138168335, 0.6544880270957947, 0.37853899598121643, 0.5611429810523987, 0.6404970288276672, 0.5242909789085388, 0.5933949947357178, 0.20047900080680847, 0.7795450091362, 0.8979210257530212, 0.3715969920158386, 0.23590999841690063, 0.9101200103759766, 0.3766449987888336, 0.17268399894237518, 0.8549879789352417, 0.35383298993110657, 0.3792079985141754, 0.7630789875984192, 0.3770729899406433, 0.5249059796333313, 0.6201940178871155, 0.12245900183916092, 0.7748309969902039, 0.9542099833488464, 0.18841099739074707, 0.23234599828720093, 0.966795027256012, 0.19089600443840027, 0.16990099847316742, 0.9100499749183655, 0.17969200015068054, 0.3735229969024658, 0.8383409976959229, 0.16553199291229248, 0.5194069743156433, 0.6201800107955933, -0.12245599925518036, 0.7748429775238037, 0.5616469979286194, -0.27755099534988403, 0.7794349789619446, 0.8979210257530212, -0.37159600853919983, 0.23590999841690063, 0.9542099833488464, -0.18841099739074707, 0.23234599828720093, 0.9101200103759766, -0.3766449987888336, 0.17268399894237518, 0.966795027256012, -0.19089600443840027, 0.16990099847316742, 0.8549879789352417, -0.35383298993110657, 0.3792079985141754, 0.9100499749183655, -0.17969100177288055, 0.3735229969024658, 0.8064150214195251, -0.2724289894104004, 0.5248600244522095, 0.8383409976959229, -0.16553199291229248, 0.5194069743156433, 0.4125959873199463, -0.47094500064849854, 0.7797279953956604, 0.687192976474762, -0.687192976474762, 0.23565199971199036, 0.6965190172195435, -0.6965190172195435, 0.17240400612354279, 0.6544880270957947, -0.6544870138168335, 0.37853899598121643, 0.6404970288276672, -0.5611429810523987, 0.5242909789085388, 0.20047900080680847, -0.5933949947357178, 0.7795450091362, 0.3715969920158386, -0.8979210257530212, 0.23590999841690063, 0.3766449987888336, -0.9101200103759766, 0.17268399894237518, 0.35383298993110657, -0.8549879789352417, 0.3792079985141754, 0.3770729899406433, -0.7630789875984192, 0.5249059796333313, 0.12245900183916092, -0.6201940178871155, 0.7748309969902039, 0.18841099739074707, -0.9542099833488464, 0.23234599828720093, 0.19089600443840027, -0.966795027256012, 0.16990099847316742, 0.17969200015068054, -0.9100499749183655, 0.3735229969024658, 0.16553199291229248, -0.8383409976959229, 0.5194069743156433, -0.12245599925518036, -0.6201800107955933, 0.7748429775238037, -0.27755099534988403, -0.5616469979286194, 0.7794349789619446, -0.37159600853919983, -0.8979210257530212, 0.23590999841690063, -0.18841099739074707, -0.9542099833488464, 0.23234599828720093, -0.3766449987888336, -0.9101200103759766, 0.17268399894237518, -0.19089600443840027, -0.966795027256012, 0.16990099847316742, -0.35383298993110657, -0.8549879789352417, 0.3792079985141754, -0.17969100177288055, -0.9100499749183655, 0.3735229969024658, -0.2724289894104004, -0.8064150214195251, 0.5248600244522095, -0.16553199291229248, -0.8383409976959229, 0.5194069743156433, -0.47094500064849854, -0.4125959873199463, 0.7797279953956604, -0.687192976474762, -0.687192976474762, 0.23565199971199036, -0.6965190172195435, -0.6965190172195435, 0.17240400612354279, -0.6544870138168335, -0.6544880270957947, 0.37853899598121643, -0.5611429810523987, -0.6404970288276672, 0.5242909789085388, -0.5933949947357178, -0.20047900080680847, 0.7795450091362, -0.8979210257530212, -0.3715969920158386, 0.23590999841690063, -0.9101200103759766, -0.3766449987888336, 0.17268399894237518, -0.8549879789352417, -0.35383298993110657, 0.3792079985141754, -0.7630789875984192, -0.3770729899406433, 0.5249059796333313, -0.6201940178871155, -0.12245900183916092, 0.7748309969902039, -0.9542099833488464, -0.18841099739074707, 0.23234599828720093, -0.966795027256012, -0.19089600443840027, 0.16990099847316742, -0.9100499749183655, -0.17969200015068054, 0.3735229969024658, -0.8383409976959229, -0.16553199291229248, 0.5194069743156433 ]);
var teapotTexCoords = new Float32Array([ 2, 2, 0, 1.75, 2, 0, 1.75, 1.975000023841858, 0, 2, 1.975000023841858, 0, 1.75, 1.9500000476837158, 0, 2, 1.9500000476837158, 0, 1.75, 1.9249999523162842, 0, 2, 1.9249999523162842, 0, 1.75, 1.899999976158142, 0, 2, 1.899999976158142, 0, 1.5, 2, 0, 1.5, 1.975000023841858, 0, 1.5, 1.9500000476837158, 0, 1.5, 1.9249999523162842, 0, 1.5, 1.899999976158142, 0, 1.25, 2, 0, 1.25, 1.975000023841858, 0, 1.25, 1.9500000476837158, 0, 1.25, 1.9249999523162842, 0, 1.25, 1.899999976158142, 0, 1, 2, 0, 1, 1.975000023841858, 0, 1, 1.9500000476837158, 0, 1, 1.9249999523162842, 0, 1, 1.899999976158142, 0, 1, 2, 0, 0.75, 2, 0, 0.75, 1.975000023841858, 0, 1, 1.975000023841858, 0, 0.75, 1.9500000476837158, 0, 1, 1.9500000476837158, 0, 0.75, 1.9249999523162842, 0, 1, 1.9249999523162842, 0, 0.75, 1.899999976158142, 0, 1, 1.899999976158142, 0, 0.5, 2, 0, 0.5, 1.975000023841858, 0, 0.5, 1.9500000476837158, 0, 0.5, 1.9249999523162842, 0, 0.5, 1.899999976158142, 0, 0.25, 2, 0, 0.25, 1.975000023841858, 0, 0.25, 1.9500000476837158, 0, 0.25, 1.9249999523162842, 0, 0.25, 1.899999976158142, 0, 0, 2, 0, 0, 1.975000023841858, 0, 0, 1.9500000476837158, 0, 0, 1.9249999523162842, 0, 0, 1.899999976158142, 0, 2, 2, 0, 1.75, 2, 0, 1.75, 1.975000023841858, 0, 2, 1.975000023841858, 0, 1.75, 1.9500000476837158, 0, 2, 1.9500000476837158, 0, 1.75, 1.9249999523162842, 0, 2, 1.9249999523162842, 0, 1.75, 1.899999976158142, 0, 2, 1.899999976158142, 0, 1.5, 2, 0, 1.5, 1.975000023841858, 0, 1.5, 1.9500000476837158, 0, 1.5, 1.9249999523162842, 0, 1.5, 1.899999976158142, 0, 1.25, 2, 0, 1.25, 1.975000023841858, 0, 1.25, 1.9500000476837158, 0, 1.25, 1.9249999523162842, 0, 1.25, 1.899999976158142, 0, 1, 2, 0, 1, 1.975000023841858, 0, 1, 1.9500000476837158, 0, 1, 1.9249999523162842, 0, 1, 1.899999976158142, 0, 1, 2, 0, 0.75, 2, 0, 0.75, 1.975000023841858, 0, 1, 1.975000023841858, 0, 0.75, 1.9500000476837158, 0, 1, 1.9500000476837158, 0, 0.75, 1.9249999523162842, 0, 1, 1.9249999523162842, 0, 0.75, 1.899999976158142, 0, 1, 1.899999976158142, 0, 0.5, 2, 0, 0.5, 1.975000023841858, 0, 0.5, 1.9500000476837158, 0, 0.5, 1.9249999523162842, 0, 0.5, 1.899999976158142, 0, 0.25, 2, 0, 0.25, 1.975000023841858, 0, 0.25, 1.9500000476837158, 0, 0.25, 1.9249999523162842, 0, 0.25, 1.899999976158142, 0, 0, 2, 0, 0, 1.975000023841858, 0, 0, 1.9500000476837158, 0, 0, 1.9249999523162842, 0, 0, 1.899999976158142, 0, 2, 1.899999976158142, 0, 1.75, 1.899999976158142, 0, 1.75, 1.6749999523162842, 0, 2, 1.6749999523162842, 0, 1.75, 1.4500000476837158, 0, 2, 1.4500000476837158, 0, 1.75, 1.225000023841858, 0, 2, 1.225000023841858, 0, 1.75, 1, 0, 2, 1, 0, 1.5, 1.899999976158142, 0, 1.5, 1.6749999523162842, 0, 1.5, 1.4500000476837158, 0, 1.5, 1.225000023841858, 0, 1.5, 1, 0, 1.25, 1.899999976158142, 0, 1.25, 1.6749999523162842, 0, 1.25, 1.4500000476837158, 0, 1.25, 1.225000023841858, 0, 1.25, 1, 0, 1, 1.899999976158142, 0, 1, 1.6749999523162842, 0, 1, 1.4500000476837158, 0, 1, 1.225000023841858, 0, 1, 1, 0, 1, 1.899999976158142, 0, 0.75, 1.899999976158142, 0, 0.75, 1.6749999523162842, 0, 1, 1.6749999523162842, 0, 0.75, 1.4500000476837158, 0, 1, 1.4500000476837158, 0, 0.75, 1.225000023841858, 0, 1, 1.225000023841858, 0, 0.75, 1, 0, 1, 1, 0, 0.5, 1.899999976158142, 0, 0.5, 1.6749999523162842, 0, 0.5, 1.4500000476837158, 0, 0.5, 1.225000023841858, 0, 0.5, 1, 0, 0.25, 1.899999976158142, 0, 0.25, 1.6749999523162842, 0, 0.25, 1.4500000476837158, 0, 0.25, 1.225000023841858, 0, 0.25, 1, 0, 0, 1.899999976158142, 0, 0, 1.6749999523162842, 0, 0, 1.4500000476837158, 0, 0, 1.225000023841858, 0, 0, 1, 0, 2, 1.899999976158142, 0, 1.75, 1.899999976158142, 0, 1.75, 1.6749999523162842, 0, 2, 1.6749999523162842, 0, 1.75, 1.4500000476837158, 0, 2, 1.4500000476837158, 0, 1.75, 1.225000023841858, 0, 2, 1.225000023841858, 0, 1.75, 1, 0, 2, 1, 0, 1.5, 1.899999976158142, 0, 1.5, 1.6749999523162842, 0, 1.5, 1.4500000476837158, 0, 1.5, 1.225000023841858, 0, 1.5, 1, 0, 1.25, 1.899999976158142, 0, 1.25, 1.6749999523162842, 0, 1.25, 1.4500000476837158, 0, 1.25, 1.225000023841858, 0, 1.25, 1, 0, 1, 1.899999976158142, 0, 1, 1.6749999523162842, 0, 1, 1.4500000476837158, 0, 1, 1.225000023841858, 0, 1, 1, 0, 1, 1.899999976158142, 0, 0.75, 1.899999976158142, 0, 0.75, 1.6749999523162842, 0, 1, 1.6749999523162842, 0, 0.75, 1.4500000476837158, 0, 1, 1.4500000476837158, 0, 0.75, 1.225000023841858, 0, 1, 1.225000023841858, 0, 0.75, 1, 0, 1, 1, 0, 0.5, 1.899999976158142, 0, 0.5, 1.6749999523162842, 0, 0.5, 1.4500000476837158, 0, 0.5, 1.225000023841858, 0, 0.5, 1, 0, 0.25, 1.899999976158142, 0, 0.25, 1.6749999523162842, 0, 0.25, 1.4500000476837158, 0, 0.25, 1.225000023841858, 0, 0.25, 1, 0, 0, 1.899999976158142, 0, 0, 1.6749999523162842, 0, 0, 1.4500000476837158, 0, 0, 1.225000023841858, 0, 0, 1, 0, 2, 1, 0, 1.75, 1, 0, 1.75, 0.8500000238418579, 0, 2, 0.8500000238418579, 0, 1.75, 0.699999988079071, 0, 2, 0.699999988079071, 0, 1.75, 0.550000011920929, 0, 2, 0.550000011920929, 0, 1.75, 0.4000000059604645, 0, 2, 0.4000000059604645, 0, 1.5, 1, 0, 1.5, 0.8500000238418579, 0, 1.5, 0.699999988079071, 0, 1.5, 0.550000011920929, 0, 1.5, 0.4000000059604645, 0, 1.25, 1, 0, 1.25, 0.8500000238418579, 0, 1.25, 0.699999988079071, 0, 1.25, 0.550000011920929, 0, 1.25, 0.4000000059604645, 0, 1, 1, 0, 1, 0.8500000238418579, 0, 1, 0.699999988079071, 0, 1, 0.550000011920929, 0, 1, 0.4000000059604645, 0, 1, 1, 0, 0.75, 1, 0, 0.75, 0.8500000238418579, 0, 1, 0.8500000238418579, 0, 0.75, 0.699999988079071, 0, 1, 0.699999988079071, 0, 0.75, 0.550000011920929, 0, 1, 0.550000011920929, 0, 0.75, 0.4000000059604645, 0, 1, 0.4000000059604645, 0, 0.5, 1, 0, 0.5, 0.8500000238418579, 0, 0.5, 0.699999988079071, 0, 0.5, 0.550000011920929, 0, 0.5, 0.4000000059604645, 0, 0.25, 1, 0, 0.25, 0.8500000238418579, 0, 0.25, 0.699999988079071, 0, 0.25, 0.550000011920929, 0, 0.25, 0.4000000059604645, 0, 0, 1, 0, 0, 0.8500000238418579, 0, 0, 0.699999988079071, 0, 0, 0.550000011920929, 0, 0, 0.4000000059604645, 0, 2, 1, 0, 1.75, 1, 0, 1.75, 0.8500000238418579, 0, 2, 0.8500000238418579, 0, 1.75, 0.699999988079071, 0, 2, 0.699999988079071, 0, 1.75, 0.550000011920929, 0, 2, 0.550000011920929, 0, 1.75, 0.4000000059604645, 0, 2, 0.4000000059604645, 0, 1.5, 1, 0, 1.5, 0.8500000238418579, 0, 1.5, 0.699999988079071, 0, 1.5, 0.550000011920929, 0, 1.5, 0.4000000059604645, 0, 1.25, 1, 0, 1.25, 0.8500000238418579, 0, 1.25, 0.699999988079071, 0, 1.25, 0.550000011920929, 0, 1.25, 0.4000000059604645, 0, 1, 1, 0, 1, 0.8500000238418579, 0, 1, 0.699999988079071, 0, 1, 0.550000011920929, 0, 1, 0.4000000059604645, 0, 1, 1, 0, 0.75, 1, 0, 0.75, 0.8500000238418579, 0, 1, 0.8500000238418579, 0, 0.75, 0.699999988079071, 0, 1, 0.699999988079071, 0, 0.75, 0.550000011920929, 0, 1, 0.550000011920929, 0, 0.75, 0.4000000059604645, 0, 1, 0.4000000059604645, 0, 0.5, 1, 0, 0.5, 0.8500000238418579, 0, 0.5, 0.699999988079071, 0, 0.5, 0.550000011920929, 0, 0.5, 0.4000000059604645, 0, 0.25, 1, 0, 0.25, 0.8500000238418579, 0, 0.25, 0.699999988079071, 0, 0.25, 0.550000011920929, 0, 0.25, 0.4000000059604645, 0, 0, 1, 0, 0, 0.8500000238418579, 0, 0, 0.699999988079071, 0, 0, 0.550000011920929, 0, 0, 0.4000000059604645, 0, 2, 0.4000000059604645, 0, 1.75, 0.4000000059604645, 0, 1.75, 0.30000001192092896, 0, 2, 0.30000001192092896, 0, 1.75, 0.20000000298023224, 0, 2, 0.20000000298023224, 0, 1.75, 0.10000000149011612, 0, 2, 0.10000000149011612, 0, 1.75, 0, 0, 2, 0, 0, 1.5, 0.4000000059604645, 0, 1.5, 0.30000001192092896, 0, 1.5, 0.20000000298023224, 0, 1.5, 0.10000000149011612, 0, 1.5, 0, 0, 1.25, 0.4000000059604645, 0, 1.25, 0.30000001192092896, 0, 1.25, 0.20000000298023224, 0, 1.25, 0.10000000149011612, 0, 1.25, 0, 0, 1, 0.4000000059604645, 0, 1, 0.30000001192092896, 0, 1, 0.20000000298023224, 0, 1, 0.10000000149011612, 0, 1, 0, 0, 1, 0.4000000059604645, 0, 0.75, 0.4000000059604645, 0, 0.75, 0.30000001192092896, 0, 1, 0.30000001192092896, 0, 0.75, 0.20000000298023224, 0, 1, 0.20000000298023224, 0, 0.75, 0.10000000149011612, 0, 1, 0.10000000149011612, 0, 0.75, 0, 0, 1, 0, 0, 0.5, 0.4000000059604645, 0, 0.5, 0.30000001192092896, 0, 0.5, 0.20000000298023224, 0, 0.5, 0.10000000149011612, 0, 0.5, 0, 0, 0.25, 0.4000000059604645, 0, 0.25, 0.30000001192092896, 0, 0.25, 0.20000000298023224, 0, 0.25, 0.10000000149011612, 0, 0.25, 0, 0, 0, 0.4000000059604645, 0, 0, 0.30000001192092896, 0, 0, 0.20000000298023224, 0, 0, 0.10000000149011612, 0, 0, 0, 0, 2, 0.4000000059604645, 0, 1.75, 0.4000000059604645, 0, 1.75, 0.30000001192092896, 0, 2, 0.30000001192092896, 0, 1.75, 0.20000000298023224, 0, 2, 0.20000000298023224, 0, 1.75, 0.10000000149011612, 0, 2, 0.10000000149011612, 0, 1.75, 0, 0, 2, 0, 0, 1.5, 0.4000000059604645, 0, 1.5, 0.30000001192092896, 0, 1.5, 0.20000000298023224, 0, 1.5, 0.10000000149011612, 0, 1.5, 0, 0, 1.25, 0.4000000059604645, 0, 1.25, 0.30000001192092896, 0, 1.25, 0.20000000298023224, 0, 1.25, 0.10000000149011612, 0, 1.25, 0, 0, 1, 0.4000000059604645, 0, 1, 0.30000001192092896, 0, 1, 0.20000000298023224, 0, 1, 0.10000000149011612, 0, 1, 0, 0, 1, 0.4000000059604645, 0, 0.75, 0.4000000059604645, 0, 0.75, 0.30000001192092896, 0, 1, 0.30000001192092896, 0, 0.75, 0.20000000298023224, 0, 1, 0.20000000298023224, 0, 0.75, 0.10000000149011612, 0, 1, 0.10000000149011612, 0, 0.75, 0, 0, 1, 0, 0, 0.5, 0.4000000059604645, 0, 0.5, 0.30000001192092896, 0, 0.5, 0.20000000298023224, 0, 0.5, 0.10000000149011612, 0, 0.5, 0, 0, 0.25, 0.4000000059604645, 0, 0.25, 0.30000001192092896, 0, 0.25, 0.20000000298023224, 0, 0.25, 0.10000000149011612, 0, 0.25, 0, 0, 0, 0.4000000059604645, 0, 0, 0.30000001192092896, 0, 0, 0.20000000298023224, 0, 0, 0.10000000149011612, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.875, 0, 1, 0.875, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.625, 0, 1, 0.625, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.75, 1, 0, 0.75, 0.875, 0, 0.75, 0.75, 0, 0.75, 0.625, 0, 0.75, 0.5, 0, 0.625, 1, 0, 0.625, 0.875, 0, 0.625, 0.75, 0, 0.625, 0.625, 0, 0.625, 0.5, 0, 0.5, 1, 0, 0.5, 0.875, 0, 0.5, 0.75, 0, 0.5, 0.625, 0, 0.5, 0.5, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.875, 0, 0.5, 0.875, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.625, 0, 0.5, 0.625, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.25, 1, 0, 0.25, 0.875, 0, 0.25, 0.75, 0, 0.25, 0.625, 0, 0.25, 0.5, 0, 0.125, 1, 0, 0.125, 0.875, 0, 0.125, 0.75, 0, 0.125, 0.625, 0, 0.125, 0.5, 0, 0, 1, 0, 0, 0.875, 0, 0, 0.75, 0, 0, 0.625, 0, 0, 0.5, 0, 1, 0.5, 0, 0.875, 0.5, 0, 0.875, 0.375, 0, 1, 0.375, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0.125, 0, 1, 0.125, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 0.5, 0, 0.75, 0.375, 0, 0.75, 0.25, 0, 0.75, 0.125, 0, 0.75, 0, 0, 0.625, 0.5, 0, 0.625, 0.375, 0, 0.625, 0.25, 0, 0.625, 0.125, 0, 0.625, 0, 0, 0.5, 0.5, 0, 0.5, 0.375, 0, 0.5, 0.25, 0, 0.5, 0.125, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0.375, 0.5, 0, 0.375, 0.375, 0, 0.5, 0.375, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0.125, 0, 0.5, 0.125, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 0.5, 0, 0.25, 0.375, 0, 0.25, 0.25, 0, 0.25, 0.125, 0, 0.25, 0, 0, 0.125, 0.5, 0, 0.125, 0.375, 0, 0.125, 0.25, 0, 0.125, 0.125, 0, 0.125, 0, 0, 0, 0.5, 0, 0, 0.375, 0, 0, 0.25, 0, 0, 0.125, 0, 0, 0, 0, 0.5, 0, 0, 0.625, 0, 0, 0.625, 0.22499999403953552, 0, 0.5, 0.22499999403953552, 0, 0.625, 0.44999998807907104, 0, 0.5, 0.44999998807907104, 0, 0.625, 0.675000011920929, 0, 0.5, 0.675000011920929, 0, 0.625, 0.8999999761581421, 0, 0.5, 0.8999999761581421, 0, 0.75, 0, 0, 0.75, 0.22499999403953552, 0, 0.75, 0.44999998807907104, 0, 0.75, 0.675000011920929, 0, 0.75, 0.8999999761581421, 0, 0.875, 0, 0, 0.875, 0.22499999403953552, 0, 0.875, 0.44999998807907104, 0, 0.875, 0.675000011920929, 0, 0.875, 0.8999999761581421, 0, 1, 0, 0, 1, 0.22499999403953552, 0, 1, 0.44999998807907104, 0, 1, 0.675000011920929, 0, 1, 0.8999999761581421, 0, 0, 0, 0, 0.125, 0, 0, 0.125, 0.22499999403953552, 0, 0, 0.22499999403953552, 0, 0.125, 0.44999998807907104, 0, 0, 0.44999998807907104, 0, 0.125, 0.675000011920929, 0, 0, 0.675000011920929, 0, 0.125, 0.8999999761581421, 0, 0, 0.8999999761581421, 0, 0.25, 0, 0, 0.25, 0.22499999403953552, 0, 0.25, 0.44999998807907104, 0, 0.25, 0.675000011920929, 0, 0.25, 0.8999999761581421, 0, 0.375, 0, 0, 0.375, 0.22499999403953552, 0, 0.375, 0.44999998807907104, 0, 0.375, 0.675000011920929, 0, 0.375, 0.8999999761581421, 0, 0.5, 0, 0, 0.5, 0.22499999403953552, 0, 0.5, 0.44999998807907104, 0, 0.5, 0.675000011920929, 0, 0.5, 0.8999999761581421, 0, 0.5, 0.8999999761581421, 0, 0.625, 0.8999999761581421, 0, 0.625, 0.925000011920929, 0, 0.5, 0.925000011920929, 0, 0.625, 0.949999988079071, 0, 0.5, 0.949999988079071, 0, 0.625, 0.9750000238418579, 0, 0.5, 0.9750000238418579, 0, 0.625, 1, 0, 0.5, 1, 0, 0.75, 0.8999999761581421, 0, 0.75, 0.925000011920929, 0, 0.75, 0.949999988079071, 0, 0.75, 0.9750000238418579, 0, 0.75, 1, 0, 0.875, 0.8999999761581421, 0, 0.875, 0.925000011920929, 0, 0.875, 0.949999988079071, 0, 0.875, 0.9750000238418579, 0, 0.875, 1, 0, 1, 0.8999999761581421, 0, 1, 0.925000011920929, 0, 1, 0.949999988079071, 0, 1, 0.9750000238418579, 0, 1, 1, 0, 0, 0.8999999761581421, 0, 0.125, 0.8999999761581421, 0, 0.125, 0.925000011920929, 0, 0, 0.925000011920929, 0, 0.125, 0.949999988079071, 0, 0, 0.949999988079071, 0, 0.125, 0.9750000238418579, 0, 0, 0.9750000238418579, 0, 0.125, 1, 0, 0, 1, 0, 0.25, 0.8999999761581421, 0, 0.25, 0.925000011920929, 0, 0.25, 0.949999988079071, 0, 0.25, 0.9750000238418579, 0, 0.25, 1, 0, 0.375, 0.8999999761581421, 0, 0.375, 0.925000011920929, 0, 0.375, 0.949999988079071, 0, 0.375, 0.9750000238418579, 0, 0.375, 1, 0, 0.5, 0.8999999761581421, 0, 0.5, 0.925000011920929, 0, 0.5, 0.949999988079071, 0, 0.5, 0.9750000238418579, 0, 0.5, 1, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0, 1, 1, 0, 0.875, 1, 0, 0.875, 0.75, 0, 1, 0.75, 0, 0.875, 0.5, 0, 1, 0.5, 0, 0.875, 0.25, 0, 1, 0.25, 0, 0.875, 0, 0, 1, 0, 0, 0.75, 1, 0, 0.75, 0.75, 0, 0.75, 0.5, 0, 0.75, 0.25, 0, 0.75, 0, 0, 0.625, 1, 0, 0.625, 0.75, 0, 0.625, 0.5, 0, 0.625, 0.25, 0, 0.625, 0, 0, 0.5, 1, 0, 0.5, 0.75, 0, 0.5, 0.5, 0, 0.5, 0.25, 0, 0.5, 0, 0, 0.5, 1, 0, 0.375, 1, 0, 0.375, 0.75, 0, 0.5, 0.75, 0, 0.375, 0.5, 0, 0.5, 0.5, 0, 0.375, 0.25, 0, 0.5, 0.25, 0, 0.375, 0, 0, 0.5, 0, 0, 0.25, 1, 0, 0.25, 0.75, 0, 0.25, 0.5, 0, 0.25, 0.25, 0, 0.25, 0, 0, 0.125, 1, 0, 0.125, 0.75, 0, 0.125, 0.5, 0, 0.125, 0.25, 0, 0.125, 0, 0, 0, 1, 0, 0, 0.75, 0, 0, 0.5, 0, 0, 0.25, 0, 0, 0, 0 ]);
var teapotIndices = new Uint16Array([ 0, 1, 2, 2, 3, 0, 3, 2, 4, 4, 5, 3, 5, 4, 6, 6, 7, 5, 7, 6, 8, 8, 9, 7, 1, 10, 11, 11, 2, 1, 2, 11, 12, 12, 4, 2, 4, 12, 13, 13, 6, 4, 6, 13, 14, 14, 8, 6, 10, 15, 16, 16, 11, 10, 11, 16, 17, 17, 12, 11, 12, 17, 18, 18, 13, 12, 13, 18, 19, 19, 14, 13, 15, 20, 21, 21, 16, 15, 16, 21, 22, 22, 17, 16, 17, 22, 23, 23, 18, 17, 18, 23, 24, 24, 19, 18, 25, 26, 27, 27, 28, 25, 28, 27, 29, 29, 30, 28, 30, 29, 31, 31, 32, 30, 32, 31, 33, 33, 34, 32, 26, 35, 36, 36, 27, 26, 27, 36, 37, 37, 29, 27, 29, 37, 38, 38, 31, 29, 31, 38, 39, 39, 33, 31, 35, 40, 41, 41, 36, 35, 36, 41, 42, 42, 37, 36, 37, 42, 43, 43, 38, 37, 38, 43, 44, 44, 39, 38, 40, 45, 46, 46, 41, 40, 41, 46, 47, 47, 42, 41, 42, 47, 48, 48, 43, 42, 43, 48, 49, 49, 44, 43, 50, 51, 52, 52, 53, 50, 53, 52, 54, 54, 55, 53, 55, 54, 56, 56, 57, 55, 57, 56, 58, 58, 59, 57, 51, 60, 61, 61, 52, 51, 52, 61, 62, 62, 54, 52, 54, 62, 63, 63, 56, 54, 56, 63, 64, 64, 58, 56, 60, 65, 66, 66, 61, 60, 61, 66, 67, 67, 62, 61, 62, 67, 68, 68, 63, 62, 63, 68, 69, 69, 64, 63, 65, 70, 71, 71, 66, 65, 66, 71, 72, 72, 67, 66, 67, 72, 73, 73, 68, 67, 68, 73, 74, 74, 69, 68, 75, 76, 77, 77, 78, 75, 78, 77, 79, 79, 80, 78, 80, 79, 81, 81, 82, 80, 82, 81, 83, 83, 84, 82, 76, 85, 86, 86, 77, 76, 77, 86, 87, 87, 79, 77, 79, 87, 88, 88, 81, 79, 81, 88, 89, 89, 83, 81, 85, 90, 91, 91, 86, 85, 86, 91, 92, 92, 87, 86, 87, 92, 93, 93, 88, 87, 88, 93, 94, 94, 89, 88, 90, 95, 96, 96, 91, 90, 91, 96, 97, 97, 92, 91, 92, 97, 98, 98, 93, 92, 93, 98, 99, 99, 94, 93, 100, 101, 102, 102, 103, 100, 103, 102, 104, 104, 105, 103, 105, 104, 106, 106, 107, 105, 107, 106, 108, 108, 109, 107, 101, 110, 111, 111, 102, 101, 102, 111, 112, 112, 104, 102, 104, 112, 113, 113, 106, 104, 106, 113, 114, 114, 108, 106, 110, 115, 116, 116, 111, 110, 111, 116, 117, 117, 112, 111, 112, 117, 118, 118, 113, 112, 113, 118, 119, 119, 114, 113, 115, 120, 121, 121, 116, 115, 116, 121, 122, 122, 117, 116, 117, 122, 123, 123, 118, 117, 118, 123, 124, 124, 119, 118, 125, 126, 127, 127, 128, 125, 128, 127, 129, 129, 130, 128, 130, 129, 131, 131, 132, 130, 132, 131, 133, 133, 134, 132, 126, 135, 136, 136, 127, 126, 127, 136, 137, 137, 129, 127, 129, 137, 138, 138, 131, 129, 131, 138, 139, 139, 133, 131, 135, 140, 141, 141, 136, 135, 136, 141, 142, 142, 137, 136, 137, 142, 143, 143, 138, 137, 138, 143, 144, 144, 139, 138, 140, 145, 146, 146, 141, 140, 141, 146, 147, 147, 142, 141, 142, 147, 148, 148, 143, 142, 143, 148, 149, 149, 144, 143, 150, 151, 152, 152, 153, 150, 153, 152, 154, 154, 155, 153, 155, 154, 156, 156, 157, 155, 157, 156, 158, 158, 159, 157, 151, 160, 161, 161, 152, 151, 152, 161, 162, 162, 154, 152, 154, 162, 163, 163, 156, 154, 156, 163, 164, 164, 158, 156, 160, 165, 166, 166, 161, 160, 161, 166, 167, 167, 162, 161, 162, 167, 168, 168, 163, 162, 163, 168, 169, 169, 164, 163, 165, 170, 171, 171, 166, 165, 166, 171, 172, 172, 167, 166, 167, 172, 173, 173, 168, 167, 168, 173, 174, 174, 169, 168, 175, 176, 177, 177, 178, 175, 178, 177, 179, 179, 180, 178, 180, 179, 181, 181, 182, 180, 182, 181, 183, 183, 184, 182, 176, 185, 186, 186, 177, 176, 177, 186, 187, 187, 179, 177, 179, 187, 188, 188, 181, 179, 181, 188, 189, 189, 183, 181, 185, 190, 191, 191, 186, 185, 186, 191, 192, 192, 187, 186, 187, 192, 193, 193, 188, 187, 188, 193, 194, 194, 189, 188, 190, 195, 196, 196, 191, 190, 191, 196, 197, 197, 192, 191, 192, 197, 198, 198, 193, 192, 193, 198, 199, 199, 194, 193, 200, 201, 202, 202, 203, 200, 203, 202, 204, 204, 205, 203, 205, 204, 206, 206, 207, 205, 207, 206, 208, 208, 209, 207, 201, 210, 211, 211, 202, 201, 202, 211, 212, 212, 204, 202, 204, 212, 213, 213, 206, 204, 206, 213, 214, 214, 208, 206, 210, 215, 216, 216, 211, 210, 211, 216, 217, 217, 212, 211, 212, 217, 218, 218, 213, 212, 213, 218, 219, 219, 214, 213, 215, 220, 221, 221, 216, 215, 216, 221, 222, 222, 217, 216, 217, 222, 223, 223, 218, 217, 218, 223, 224, 224, 219, 218, 225, 226, 227, 227, 228, 225, 228, 227, 229, 229, 230, 228, 230, 229, 231, 231, 232, 230, 232, 231, 233, 233, 234, 232, 226, 235, 236, 236, 227, 226, 227, 236, 237, 237, 229, 227, 229, 237, 238, 238, 231, 229, 231, 238, 239, 239, 233, 231, 235, 240, 241, 241, 236, 235, 236, 241, 242, 242, 237, 236, 237, 242, 243, 243, 238, 237, 238, 243, 244, 244, 239, 238, 240, 245, 246, 246, 241, 240, 241, 246, 247, 247, 242, 241, 242, 247, 248, 248, 243, 242, 243, 248, 249, 249, 244, 243, 250, 251, 252, 252, 253, 250, 253, 252, 254, 254, 255, 253, 255, 254, 256, 256, 257, 255, 257, 256, 258, 258, 259, 257, 251, 260, 261, 261, 252, 251, 252, 261, 262, 262, 254, 252, 254, 262, 263, 263, 256, 254, 256, 263, 264, 264, 258, 256, 260, 265, 266, 266, 261, 260, 261, 266, 267, 267, 262, 261, 262, 267, 268, 268, 263, 262, 263, 268, 269, 269, 264, 263, 265, 270, 271, 271, 266, 265, 266, 271, 272, 272, 267, 266, 267, 272, 273, 273, 268, 267, 268, 273, 274, 274, 269, 268, 275, 276, 277, 277, 278, 275, 278, 277, 279, 279, 280, 278, 280, 279, 281, 281, 282, 280, 282, 281, 283, 283, 284, 282, 276, 285, 286, 286, 277, 276, 277, 286, 287, 287, 279, 277, 279, 287, 288, 288, 281, 279, 281, 288, 289, 289, 283, 281, 285, 290, 291, 291, 286, 285, 286, 291, 292, 292, 287, 286, 287, 292, 293, 293, 288, 287, 288, 293, 294, 294, 289, 288, 290, 295, 296, 296, 291, 290, 291, 296, 297, 297, 292, 291, 292, 297, 298, 298, 293, 292, 293, 298, 299, 299, 294, 293, 300, 301, 302, 302, 303, 300, 303, 302, 304, 304, 305, 303, 305, 304, 306, 306, 307, 305, 307, 306, 308, 308, 309, 307, 301, 310, 311, 311, 302, 301, 302, 311, 312, 312, 304, 302, 304, 312, 313, 313, 306, 304, 306, 313, 314, 314, 308, 306, 310, 315, 316, 316, 311, 310, 311, 316, 317, 317, 312, 311, 312, 317, 318, 318, 313, 312, 313, 318, 319, 319, 314, 313, 315, 320, 321, 321, 316, 315, 316, 321, 322, 322, 317, 316, 317, 322, 323, 323, 318, 317, 318, 323, 324, 324, 319, 318, 325, 326, 327, 327, 328, 325, 328, 327, 329, 329, 330, 328, 330, 329, 331, 331, 332, 330, 332, 331, 333, 333, 334, 332, 326, 335, 336, 336, 327, 326, 327, 336, 337, 337, 329, 327, 329, 337, 338, 338, 331, 329, 331, 338, 339, 339, 333, 331, 335, 340, 341, 341, 336, 335, 336, 341, 342, 342, 337, 336, 337, 342, 343, 343, 338, 337, 338, 343, 344, 344, 339, 338, 340, 345, 346, 346, 341, 340, 341, 346, 347, 347, 342, 341, 342, 347, 348, 348, 343, 342, 343, 348, 349, 349, 344, 343, 350, 351, 352, 352, 353, 350, 353, 352, 354, 354, 355, 353, 355, 354, 356, 356, 357, 355, 357, 356, 358, 358, 359, 357, 351, 360, 361, 361, 352, 351, 352, 361, 362, 362, 354, 352, 354, 362, 363, 363, 356, 354, 356, 363, 364, 364, 358, 356, 360, 365, 366, 366, 361, 360, 361, 366, 367, 367, 362, 361, 362, 367, 368, 368, 363, 362, 363, 368, 369, 369, 364, 363, 365, 370, 371, 371, 366, 365, 366, 371, 372, 372, 367, 366, 367, 372, 373, 373, 368, 367, 368, 373, 374, 374, 369, 368, 375, 376, 377, 377, 378, 375, 378, 377, 379, 379, 380, 378, 380, 379, 381, 381, 382, 380, 382, 381, 383, 383, 384, 382, 376, 385, 386, 386, 377, 376, 377, 386, 387, 387, 379, 377, 379, 387, 388, 388, 381, 379, 381, 388, 389, 389, 383, 381, 385, 390, 391, 391, 386, 385, 386, 391, 392, 392, 387, 386, 387, 392, 393, 393, 388, 387, 388, 393, 394, 394, 389, 388, 390, 395, 396, 396, 391, 390, 391, 396, 397, 397, 392, 391, 392, 397, 398, 398, 393, 392, 393, 398, 399, 399, 394, 393, 400, 401, 402, 402, 403, 400, 403, 402, 404, 404, 405, 403, 405, 404, 406, 406, 407, 405, 407, 406, 408, 408, 409, 407, 401, 410, 411, 411, 402, 401, 402, 411, 412, 412, 404, 402, 404, 412, 413, 413, 406, 404, 406, 413, 414, 414, 408, 406, 410, 415, 416, 416, 411, 410, 411, 416, 417, 417, 412, 411, 412, 417, 418, 418, 413, 412, 413, 418, 419, 419, 414, 413, 415, 420, 421, 421, 416, 415, 416, 421, 422, 422, 417, 416, 417, 422, 423, 423, 418, 417, 418, 423, 424, 424, 419, 418, 425, 426, 427, 427, 428, 425, 428, 427, 429, 429, 430, 428, 430, 429, 431, 431, 432, 430, 432, 431, 433, 433, 434, 432, 426, 435, 436, 436, 427, 426, 427, 436, 437, 437, 429, 427, 429, 437, 438, 438, 431, 429, 431, 438, 439, 439, 433, 431, 435, 440, 441, 441, 436, 435, 436, 441, 442, 442, 437, 436, 437, 442, 443, 443, 438, 437, 438, 443, 444, 444, 439, 438, 440, 445, 446, 446, 441, 440, 441, 446, 447, 447, 442, 441, 442, 447, 448, 448, 443, 442, 443, 448, 449, 449, 444, 443, 450, 451, 452, 452, 453, 450, 453, 452, 454, 454, 455, 453, 455, 454, 456, 456, 457, 455, 457, 456, 458, 458, 459, 457, 451, 460, 461, 461, 452, 451, 452, 461, 462, 462, 454, 452, 454, 462, 463, 463, 456, 454, 456, 463, 464, 464, 458, 456, 460, 465, 466, 466, 461, 460, 461, 466, 467, 467, 462, 461, 462, 467, 468, 468, 463, 462, 463, 468, 469, 469, 464, 463, 465, 470, 471, 471, 466, 465, 466, 471, 472, 472, 467, 466, 467, 472, 473, 473, 468, 467, 468, 473, 474, 474, 469, 468, 475, 476, 477, 477, 478, 475, 478, 477, 479, 479, 480, 478, 480, 479, 481, 481, 482, 480, 482, 481, 483, 483, 484, 482, 476, 485, 486, 486, 477, 476, 477, 486, 487, 487, 479, 477, 479, 487, 488, 488, 481, 479, 481, 488, 489, 489, 483, 481, 485, 490, 491, 491, 486, 485, 486, 491, 492, 492, 487, 486, 487, 492, 493, 493, 488, 487, 488, 493, 494, 494, 489, 488, 490, 495, 496, 496, 491, 490, 491, 496, 497, 497, 492, 491, 492, 497, 498, 498, 493, 492, 493, 498, 499, 499, 494, 493, 500, 501, 502, 502, 503, 500, 503, 502, 504, 504, 505, 503, 505, 504, 506, 506, 507, 505, 507, 506, 508, 508, 509, 507, 501, 510, 511, 511, 502, 501, 502, 511, 512, 512, 504, 502, 504, 512, 513, 513, 506, 504, 506, 513, 514, 514, 508, 506, 510, 515, 516, 516, 511, 510, 511, 516, 517, 517, 512, 511, 512, 517, 518, 518, 513, 512, 513, 518, 519, 519, 514, 513, 515, 520, 521, 521, 516, 515, 516, 521, 522, 522, 517, 516, 517, 522, 523, 523, 518, 517, 518, 523, 524, 524, 519, 518, 525, 526, 527, 527, 528, 525, 528, 527, 529, 529, 530, 528, 530, 529, 531, 531, 532, 530, 532, 531, 533, 533, 534, 532, 526, 535, 536, 536, 527, 526, 527, 536, 537, 537, 529, 527, 529, 537, 538, 538, 531, 529, 531, 538, 539, 539, 533, 531, 535, 540, 541, 541, 536, 535, 536, 541, 542, 542, 537, 536, 537, 542, 543, 543, 538, 537, 538, 543, 544, 544, 539, 538, 540, 545, 546, 546, 541, 540, 541, 546, 547, 547, 542, 541, 542, 547, 548, 548, 543, 542, 543, 548, 549, 549, 544, 543, 550, 551, 552, 552, 553, 550, 553, 552, 554, 554, 555, 553, 555, 554, 556, 556, 557, 555, 557, 556, 558, 558, 559, 557, 551, 560, 561, 561, 552, 551, 552, 561, 562, 562, 554, 552, 554, 562, 563, 563, 556, 554, 556, 563, 564, 564, 558, 556, 560, 565, 566, 566, 561, 560, 561, 566, 567, 567, 562, 561, 562, 567, 568, 568, 563, 562, 563, 568, 569, 569, 564, 563, 565, 570, 571, 571, 566, 565, 566, 571, 572, 572, 567, 566, 567, 572, 573, 573, 568, 567, 568, 573, 574, 574, 569, 568, 575, 576, 577, 577, 578, 575, 578, 577, 579, 579, 580, 578, 580, 579, 581, 581, 582, 580, 582, 581, 583, 583, 584, 582, 576, 585, 586, 586, 577, 576, 577, 586, 587, 587, 579, 577, 579, 587, 588, 588, 581, 579, 581, 588, 589, 589, 583, 581, 585, 590, 591, 591, 586, 585, 586, 591, 592, 592, 587, 586, 587, 592, 593, 593, 588, 587, 588, 593, 594, 594, 589, 588, 590, 595, 596, 596, 591, 590, 591, 596, 597, 597, 592, 591, 592, 597, 598, 598, 593, 592, 593, 598, 599, 599, 594, 593, 600, 601, 602, 602, 603, 600, 603, 602, 604, 604, 605, 603, 605, 604, 606, 606, 607, 605, 607, 606, 608, 608, 609, 607, 601, 610, 611, 611, 602, 601, 602, 611, 612, 612, 604, 602, 604, 612, 613, 613, 606, 604, 606, 613, 614, 614, 608, 606, 610, 615, 616, 616, 611, 610, 611, 616, 617, 617, 612, 611, 612, 617, 618, 618, 613, 612, 613, 618, 619, 619, 614, 613, 615, 620, 621, 621, 616, 615, 616, 621, 622, 622, 617, 616, 617, 622, 623, 623, 618, 617, 618, 623, 624, 624, 619, 618, 625, 626, 627, 627, 628, 625, 628, 627, 629, 629, 630, 628, 630, 629, 631, 631, 632, 630, 632, 631, 633, 633, 634, 632, 626, 635, 636, 636, 627, 626, 627, 636, 637, 637, 629, 627, 629, 637, 638, 638, 631, 629, 631, 638, 639, 639, 633, 631, 635, 640, 641, 641, 636, 635, 636, 641, 642, 642, 637, 636, 637, 642, 643, 643, 638, 637, 638, 643, 644, 644, 639, 638, 640, 645, 646, 646, 641, 640, 641, 646, 647, 647, 642, 641, 642, 647, 648, 648, 643, 642, 643, 648, 649, 649, 644, 643, 650, 651, 652, 652, 653, 650, 653, 652, 654, 654, 655, 653, 655, 654, 656, 656, 657, 655, 657, 656, 658, 658, 659, 657, 651, 660, 661, 661, 652, 651, 652, 661, 662, 662, 654, 652, 654, 662, 663, 663, 656, 654, 656, 663, 664, 664, 658, 656, 660, 665, 666, 666, 661, 660, 661, 666, 667, 667, 662, 661, 662, 667, 668, 668, 663, 662, 663, 668, 669, 669, 664, 663, 665, 670, 671, 671, 666, 665, 666, 671, 672, 672, 667, 666, 667, 672, 673, 673, 668, 667, 668, 673, 674, 674, 669, 668, 675, 676, 677, 677, 678, 675, 678, 677, 679, 679, 680, 678, 680, 679, 681, 681, 682, 680, 682, 681, 683, 683, 684, 682, 676, 685, 686, 686, 677, 676, 677, 686, 687, 687, 679, 677, 679, 687, 688, 688, 681, 679, 681, 688, 689, 689, 683, 681, 685, 690, 691, 691, 686, 685, 686, 691, 692, 692, 687, 686, 687, 692, 693, 693, 688, 687, 688, 693, 694, 694, 689, 688, 690, 695, 696, 696, 691, 690, 691, 696, 697, 697, 692, 691, 692, 697, 698, 698, 693, 692, 693, 698, 699, 699, 694, 693, 700, 701, 702, 702, 703, 700, 703, 702, 704, 704, 705, 703, 705, 704, 706, 706, 707, 705, 707, 706, 708, 708, 709, 707, 701, 710, 711, 711, 702, 701, 702, 711, 712, 712, 704, 702, 704, 712, 713, 713, 706, 704, 706, 713, 714, 714, 708, 706, 710, 715, 716, 716, 711, 710, 711, 716, 717, 717, 712, 711, 712, 717, 718, 718, 713, 712, 713, 718, 719, 719, 714, 713, 715, 720, 721, 721, 716, 715, 716, 721, 722, 722, 717, 716, 717, 722, 723, 723, 718, 717, 718, 723, 724, 724, 719, 718, 725, 726, 727, 727, 728, 725, 728, 727, 729, 729, 730, 728, 730, 729, 731, 731, 732, 730, 732, 731, 733, 733, 734, 732, 726, 735, 736, 736, 727, 726, 727, 736, 737, 737, 729, 727, 729, 737, 738, 738, 731, 729, 731, 738, 739, 739, 733, 731, 735, 740, 741, 741, 736, 735, 736, 741, 742, 742, 737, 736, 737, 742, 743, 743, 738, 737, 738, 743, 744, 744, 739, 738, 740, 745, 746, 746, 741, 740, 741, 746, 747, 747, 742, 741, 742, 747, 748, 748, 743, 742, 743, 748, 749, 749, 744, 743, 750, 751, 752, 752, 753, 750, 753, 752, 754, 754, 755, 753, 755, 754, 756, 756, 757, 755, 757, 756, 758, 758, 759, 757, 751, 760, 761, 761, 752, 751, 752, 761, 762, 762, 754, 752, 754, 762, 763, 763, 756, 754, 756, 763, 764, 764, 758, 756, 760, 765, 766, 766, 761, 760, 761, 766, 767, 767, 762, 761, 762, 767, 768, 768, 763, 762, 763, 768, 769, 769, 764, 763, 765, 770, 771, 771, 766, 765, 766, 771, 772, 772, 767, 766, 767, 772, 773, 773, 768, 767, 768, 773, 774, 774, 769, 768, 775, 776, 777, 777, 778, 775, 778, 777, 779, 779, 780, 778, 780, 779, 781, 781, 782, 780, 782, 781, 783, 783, 784, 782, 776, 785, 786, 786, 777, 776, 777, 786, 787, 787, 779, 777, 779, 787, 788, 788, 781, 779, 781, 788, 789, 789, 783, 781, 785, 790, 791, 791, 786, 785, 786, 791, 792, 792, 787, 786, 787, 792, 793, 793, 788, 787, 788, 793, 794, 794, 789, 788, 790, 795, 796, 796, 791, 790, 791, 796, 797, 797, 792, 791, 792, 797, 798, 798, 793, 792, 793, 798, 799, 799, 794, 793 ]);
================================================
FILE: src/js/third_party/webgl_teapot/webgl-debug.js
================================================
/*
** Copyright (c) 2012 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
// Various functions for helping debug WebGL apps.
WebGLDebugUtils = function() {
/**
* Wrapped logging function.
* @param {string} msg Message to log.
*/
var log = function(msg) {
if (window.console && window.console.log) {
window.console.log(msg);
}
};
/**
* Wrapped error logging function.
* @param {string} msg Message to log.
*/
var error = function(msg) {
if (window.console && window.console.error) {
window.console.error(msg);
} else {
log(msg);
}
};
/**
* Which arguments are enums based on the number of arguments to the function.
* So
* 'texImage2D': {
* 9: { 0:true, 2:true, 6:true, 7:true },
* 6: { 0:true, 2:true, 3:true, 4:true },
* },
*
* means if there are 9 arguments then 6 and 7 are enums, if there are 6
* arguments 3 and 4 are enums
*
* @type {!Object.}
*/
var glValidEnumContexts = {
// Generic setters and getters
'enable': {1: { 0:true }},
'disable': {1: { 0:true }},
'getParameter': {1: { 0:true }},
// Rendering
'drawArrays': {3:{ 0:true }},
'drawElements': {4:{ 0:true, 2:true }},
// Shaders
'createShader': {1: { 0:true }},
'getShaderParameter': {2: { 1:true }},
'getProgramParameter': {2: { 1:true }},
'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
// Vertex attributes
'getVertexAttrib': {2: { 1:true }},
'vertexAttribPointer': {6: { 2:true }},
// Textures
'bindTexture': {2: { 0:true }},
'activeTexture': {1: { 0:true }},
'getTexParameter': {2: { 0:true, 1:true }},
'texParameterf': {3: { 0:true, 1:true }},
'texParameteri': {3: { 0:true, 1:true, 2:true }},
'texImage2D': {
9: { 0:true, 2:true, 6:true, 7:true },
6: { 0:true, 2:true, 3:true, 4:true }
},
'texSubImage2D': {
9: { 0:true, 6:true, 7:true },
7: { 0:true, 4:true, 5:true }
},
'copyTexImage2D': {8: { 0:true, 2:true }},
'copyTexSubImage2D': {8: { 0:true }},
'generateMipmap': {1: { 0:true }},
'compressedTexImage2D': {7: { 0: true, 2:true }},
'compressedTexSubImage2D': {8: { 0: true, 6:true }},
// Buffer objects
'bindBuffer': {2: { 0:true }},
'bufferData': {3: { 0:true, 2:true }},
'bufferSubData': {3: { 0:true }},
'getBufferParameter': {2: { 0:true, 1:true }},
// Renderbuffers and framebuffers
'pixelStorei': {2: { 0:true, 1:true }},
'readPixels': {7: { 4:true, 5:true }},
'bindRenderbuffer': {2: { 0:true }},
'bindFramebuffer': {2: { 0:true }},
'checkFramebufferStatus': {1: { 0:true }},
'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
'getRenderbufferParameter': {2: { 0:true, 1:true }},
'renderbufferStorage': {4: { 0:true, 1:true }},
// Frame buffer operations (clear, blend, depth test, stencil)
'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}},
'depthFunc': {1: { 0:true }},
'blendFunc': {2: { 0:true, 1:true }},
'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
'blendEquation': {1: { 0:true }},
'blendEquationSeparate': {2: { 0:true, 1:true }},
'stencilFunc': {3: { 0:true }},
'stencilFuncSeparate': {4: { 0:true, 1:true }},
'stencilMaskSeparate': {2: { 0:true }},
'stencilOp': {3: { 0:true, 1:true, 2:true }},
'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
// Culling
'cullFace': {1: { 0:true }},
'frontFace': {1: { 0:true }},
// ANGLE_instanced_arrays extension
'drawArraysInstancedANGLE': {4: { 0:true }},
'drawElementsInstancedANGLE': {5: { 0:true, 2:true }},
// EXT_blend_minmax extension
'blendEquationEXT': {1: { 0:true }}
};
/**
* Map of numbers to names.
* @type {Object}
*/
var glEnums = null;
/**
* Map of names to numbers.
* @type {Object}
*/
var enumStringToValue = null;
/**
* Initializes this module. Safe to call more than once.
* @param {!WebGLRenderingContext} ctx A WebGL context. If
* you have more than one context it doesn't matter which one
* you pass in, it is only used to pull out constants.
*/
function init(ctx) {
if (glEnums == null) {
glEnums = { };
enumStringToValue = { };
for (var propertyName in ctx) {
if (typeof ctx[propertyName] == 'number') {
glEnums[ctx[propertyName]] = propertyName;
enumStringToValue[propertyName] = ctx[propertyName];
}
}
}
}
/**
* Checks the utils have been initialized.
*/
function checkInit() {
if (glEnums == null) {
throw 'WebGLDebugUtils.init(ctx) not called';
}
}
/**
* Returns true or false if value matches any WebGL enum
* @param {*} value Value to check if it might be an enum.
* @return {boolean} True if value matches one of the WebGL defined enums
*/
function mightBeEnum(value) {
checkInit();
return (glEnums[value] !== undefined);
}
/**
* Gets an string version of an WebGL enum.
*
* Example:
* var str = WebGLDebugUtil.glEnumToString(ctx.getError());
*
* @param {number} value Value to return an enum for
* @return {string} The string version of the enum.
*/
function glEnumToString(value) {
checkInit();
var name = glEnums[value];
return (name !== undefined) ? ("gl." + name) :
("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + "");
}
/**
* Returns the string version of a WebGL argument.
* Attempts to convert enum arguments to strings.
* @param {string} functionName the name of the WebGL function.
* @param {number} numArgs the number of arguments passed to the function.
* @param {number} argumentIndx the index of the argument.
* @param {*} value The value of the argument.
* @return {string} The value as a string.
*/
function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
var funcInfo = glValidEnumContexts[functionName];
if (funcInfo !== undefined) {
var funcInfo = funcInfo[numArgs];
if (funcInfo !== undefined) {
if (funcInfo[argumentIndex]) {
if (typeof funcInfo[argumentIndex] === 'object' &&
funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) {
var enums = funcInfo[argumentIndex]['enumBitwiseOr'];
var orResult = 0;
var orEnums = [];
for (var i = 0; i < enums.length; ++i) {
var enumValue = enumStringToValue[enums[i]];
if ((value & enumValue) !== 0) {
orResult |= enumValue;
orEnums.push(glEnumToString(enumValue));
}
}
if (orResult === value) {
return orEnums.join(' | ');
} else {
return glEnumToString(value);
}
} else {
return glEnumToString(value);
}
}
}
}
if (value === null) {
return "null";
} else if (value === undefined) {
return "undefined";
} else {
return value.toString();
}
}
/**
* Converts the arguments of a WebGL function to a string.
* Attempts to convert enum arguments to strings.
*
* @param {string} functionName the name of the WebGL function.
* @param {number} args The arguments.
* @return {string} The arguments as a string.
*/
function glFunctionArgsToString(functionName, args) {
// apparently we can't do args.join(",");
var argStr = "";
var numArgs = args.length;
for (var ii = 0; ii < numArgs; ++ii) {
argStr += ((ii == 0) ? '' : ', ') +
glFunctionArgToString(functionName, numArgs, ii, args[ii]);
}
return argStr;
};
function makePropertyWrapper(wrapper, original, propertyName) {
//log("wrap prop: " + propertyName);
wrapper.__defineGetter__(propertyName, function() {
return original[propertyName];
});
// TODO(gmane): this needs to handle properties that take more than
// one value?
wrapper.__defineSetter__(propertyName, function(value) {
//log("set: " + propertyName);
original[propertyName] = value;
});
}
// Makes a function that calls a function on another object.
function makeFunctionWrapper(original, functionName) {
//log("wrap fn: " + functionName);
var f = original[functionName];
return function() {
//log("call: " + functionName);
var result = f.apply(original, arguments);
return result;
};
}
/**
* Given a WebGL context returns a wrapped context that calls
* gl.getError after every command and calls a function if the
* result is not gl.NO_ERROR.
*
* @param {!WebGLRenderingContext} ctx The webgl context to
* wrap.
* @param {!function(err, funcName, args): void} opt_onErrorFunc
* The function to call when gl.getError returns an
* error. If not specified the default function calls
* console.log with a message.
* @param {!function(funcName, args): void} opt_onFunc The
* function to call when each webgl function is called.
* You can use this to log all calls for example.
* @param {!WebGLRenderingContext} opt_err_ctx The webgl context
* to call getError on if different than ctx.
*/
function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) {
opt_err_ctx = opt_err_ctx || ctx;
init(ctx);
opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {
// apparently we can't do args.join(",");
var argStr = "";
var numArgs = args.length;
for (var ii = 0; ii < numArgs; ++ii) {
argStr += ((ii == 0) ? '' : ', ') +
glFunctionArgToString(functionName, numArgs, ii, args[ii]);
}
error("WebGL error "+ glEnumToString(err) + " in "+ functionName +
"(" + argStr + ")");
};
// Holds booleans for each GL error so after we get the error ourselves
// we can still return it to the client app.
var glErrorShadow = { };
// Makes a function that calls a WebGL function and then calls getError.
function makeErrorWrapper(ctx, functionName) {
return function() {
if (opt_onFunc) {
opt_onFunc(functionName, arguments);
}
var result = ctx[functionName].apply(ctx, arguments);
var err = opt_err_ctx.getError();
if (err != 0) {
glErrorShadow[err] = true;
opt_onErrorFunc(err, functionName, arguments);
}
return result;
};
}
// Make a an object that has a copy of every property of the WebGL context
// but wraps all functions.
var wrapper = {};
for (var propertyName in ctx) {
if (typeof ctx[propertyName] == 'function') {
if (propertyName != 'getExtension') {
wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
} else {
var wrapped = makeErrorWrapper(ctx, propertyName);
wrapper[propertyName] = function () {
var result = wrapped.apply(ctx, arguments);
return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx);
};
}
} else {
makePropertyWrapper(wrapper, ctx, propertyName);
}
}
// Override the getError function with one that returns our saved results.
wrapper.getError = function() {
for (var err in glErrorShadow) {
if (glErrorShadow.hasOwnProperty(err)) {
if (glErrorShadow[err]) {
glErrorShadow[err] = false;
return err;
}
}
}
return ctx.NO_ERROR;
};
return wrapper;
}
function resetToInitialState(ctx) {
var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
var tmp = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);
for (var ii = 0; ii < numAttribs; ++ii) {
ctx.disableVertexAttribArray(ii);
ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);
ctx.vertexAttrib1f(ii, 0);
}
ctx.deleteBuffer(tmp);
var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
for (var ii = 0; ii < numTextureUnits; ++ii) {
ctx.activeTexture(ctx.TEXTURE0 + ii);
ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);
ctx.bindTexture(ctx.TEXTURE_2D, null);
}
ctx.activeTexture(ctx.TEXTURE0);
ctx.useProgram(null);
ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);
ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);
ctx.disable(ctx.BLEND);
ctx.disable(ctx.CULL_FACE);
ctx.disable(ctx.DEPTH_TEST);
ctx.disable(ctx.DITHER);
ctx.disable(ctx.SCISSOR_TEST);
ctx.blendColor(0, 0, 0, 0);
ctx.blendEquation(ctx.FUNC_ADD);
ctx.blendFunc(ctx.ONE, ctx.ZERO);
ctx.clearColor(0, 0, 0, 0);
ctx.clearDepth(1);
ctx.clearStencil(-1);
ctx.colorMask(true, true, true, true);
ctx.cullFace(ctx.BACK);
ctx.depthFunc(ctx.LESS);
ctx.depthMask(true);
ctx.depthRange(0, 1);
ctx.frontFace(ctx.CCW);
ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);
ctx.lineWidth(1);
ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);
ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);
ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);
ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
// TODO: Delete this IF.
if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {
ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);
}
ctx.polygonOffset(0, 0);
ctx.sampleCoverage(1, false);
ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);
ctx.stencilMask(0xFFFFFFFF);
ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);
ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
// TODO: This should NOT be needed but Firefox fails with 'hint'
while(ctx.getError());
}
function makeLostContextSimulatingCanvas(canvas) {
var unwrappedContext_;
var wrappedContext_;
var onLost_ = [];
var onRestored_ = [];
var wrappedContext_ = {};
var contextId_ = 1;
var contextLost_ = false;
var resourceId_ = 0;
var resourceDb_ = [];
var numCallsToLoseContext_ = 0;
var numCalls_ = 0;
var canRestore_ = false;
var restoreTimeout_ = 0;
// Holds booleans for each GL error so can simulate errors.
var glErrorShadow_ = { };
canvas.getContext = function(f) {
return function() {
var ctx = f.apply(canvas, arguments);
// Did we get a context and is it a WebGL context?
if (ctx instanceof WebGLRenderingContext) {
if (ctx != unwrappedContext_) {
if (unwrappedContext_) {
throw "got different context"
}
unwrappedContext_ = ctx;
wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_);
}
return wrappedContext_;
}
return ctx;
}
}(canvas.getContext);
function wrapEvent(listener) {
if (typeof(listener) == "function") {
return listener;
} else {
return function(info) {
listener.handleEvent(info);
}
}
}
var addOnContextLostListener = function(listener) {
onLost_.push(wrapEvent(listener));
};
var addOnContextRestoredListener = function(listener) {
onRestored_.push(wrapEvent(listener));
};
function wrapAddEventListener(canvas) {
var f = canvas.addEventListener;
canvas.addEventListener = function(type, listener, bubble) {
switch (type) {
case 'webglcontextlost':
addOnContextLostListener(listener);
break;
case 'webglcontextrestored':
addOnContextRestoredListener(listener);
break;
default:
f.apply(canvas, arguments);
}
};
}
wrapAddEventListener(canvas);
canvas.loseContext = function() {
if (!contextLost_) {
contextLost_ = true;
numCallsToLoseContext_ = 0;
++contextId_;
while (unwrappedContext_.getError());
clearErrors();
glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true;
var event = makeWebGLContextEvent("context lost");
var callbacks = onLost_.slice();
setTimeout(function() {
//log("numCallbacks:" + callbacks.length);
for (var ii = 0; ii < callbacks.length; ++ii) {
//log("calling callback:" + ii);
callbacks[ii](event);
}
if (restoreTimeout_ >= 0) {
setTimeout(function() {
canvas.restoreContext();
}, restoreTimeout_);
}
}, 0);
}
};
canvas.restoreContext = function() {
if (contextLost_) {
if (onRestored_.length) {
setTimeout(function() {
if (!canRestore_) {
throw "can not restore. webglcontestlost listener did not call event.preventDefault";
}
freeResources();
resetToInitialState(unwrappedContext_);
contextLost_ = false;
numCalls_ = 0;
canRestore_ = false;
var callbacks = onRestored_.slice();
var event = makeWebGLContextEvent("context restored");
for (var ii = 0; ii < callbacks.length; ++ii) {
callbacks[ii](event);
}
}, 0);
}
}
};
canvas.loseContextInNCalls = function(numCalls) {
if (contextLost_) {
throw "You can not ask a lost contet to be lost";
}
numCallsToLoseContext_ = numCalls_ + numCalls;
};
canvas.getNumCalls = function() {
return numCalls_;
};
canvas.setRestoreTimeout = function(timeout) {
restoreTimeout_ = timeout;
};
function isWebGLObject(obj) {
//return false;
return (obj instanceof WebGLBuffer ||
obj instanceof WebGLFramebuffer ||
obj instanceof WebGLProgram ||
obj instanceof WebGLRenderbuffer ||
obj instanceof WebGLShader ||
obj instanceof WebGLTexture);
}
function checkResources(args) {
for (var ii = 0; ii < args.length; ++ii) {
var arg = args[ii];
if (isWebGLObject(arg)) {
return arg.__webglDebugContextLostId__ == contextId_;
}
}
return true;
}
function clearErrors() {
var k = Object.keys(glErrorShadow_);
for (var ii = 0; ii < k.length; ++ii) {
delete glErrorShadow_[k];
}
}
function loseContextIfTime() {
++numCalls_;
if (!contextLost_) {
if (numCallsToLoseContext_ == numCalls_) {
canvas.loseContext();
}
}
}
// Makes a function that simulates WebGL when out of context.
function makeLostContextFunctionWrapper(ctx, functionName) {
var f = ctx[functionName];
return function() {
// log("calling:" + functionName);
// Only call the functions if the context is not lost.
loseContextIfTime();
if (!contextLost_) {
//if (!checkResources(arguments)) {
// glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true;
// return;
//}
var result = f.apply(ctx, arguments);
return result;
}
};
}
function freeResources() {
for (var ii = 0; ii < resourceDb_.length; ++ii) {
var resource = resourceDb_[ii];
if (resource instanceof WebGLBuffer) {
unwrappedContext_.deleteBuffer(resource);
} else if (resource instanceof WebGLFramebuffer) {
unwrappedContext_.deleteFramebuffer(resource);
} else if (resource instanceof WebGLProgram) {
unwrappedContext_.deleteProgram(resource);
} else if (resource instanceof WebGLRenderbuffer) {
unwrappedContext_.deleteRenderbuffer(resource);
} else if (resource instanceof WebGLShader) {
unwrappedContext_.deleteShader(resource);
} else if (resource instanceof WebGLTexture) {
unwrappedContext_.deleteTexture(resource);
}
}
}
function makeWebGLContextEvent(statusMessage) {
return {
statusMessage: statusMessage,
preventDefault: function() {
canRestore_ = true;
}
};
}
return canvas;
function makeLostContextSimulatingContext(ctx) {
// copy all functions and properties to wrapper
for (var propertyName in ctx) {
if (typeof ctx[propertyName] == 'function') {
wrappedContext_[propertyName] = makeLostContextFunctionWrapper(
ctx, propertyName);
} else {
makePropertyWrapper(wrappedContext_, ctx, propertyName);
}
}
// Wrap a few functions specially.
wrappedContext_.getError = function() {
loseContextIfTime();
if (!contextLost_) {
var err;
while (err = unwrappedContext_.getError()) {
glErrorShadow_[err] = true;
}
}
for (var err in glErrorShadow_) {
if (glErrorShadow_[err]) {
delete glErrorShadow_[err];
return err;
}
}
return wrappedContext_.NO_ERROR;
};
var creationFunctions = [
"createBuffer",
"createFramebuffer",
"createProgram",
"createRenderbuffer",
"createShader",
"createTexture"
];
for (var ii = 0; ii < creationFunctions.length; ++ii) {
var functionName = creationFunctions[ii];
wrappedContext_[functionName] = function(f) {
return function() {
loseContextIfTime();
if (contextLost_) {
return null;
}
var obj = f.apply(ctx, arguments);
obj.__webglDebugContextLostId__ = contextId_;
resourceDb_.push(obj);
return obj;
};
}(ctx[functionName]);
}
var functionsThatShouldReturnNull = [
"getActiveAttrib",
"getActiveUniform",
"getBufferParameter",
"getContextAttributes",
"getAttachedShaders",
"getFramebufferAttachmentParameter",
"getParameter",
"getProgramParameter",
"getProgramInfoLog",
"getRenderbufferParameter",
"getShaderParameter",
"getShaderInfoLog",
"getShaderSource",
"getTexParameter",
"getUniform",
"getUniformLocation",
"getVertexAttrib"
];
for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {
var functionName = functionsThatShouldReturnNull[ii];
wrappedContext_[functionName] = function(f) {
return function() {
loseContextIfTime();
if (contextLost_) {
return null;
}
return f.apply(ctx, arguments);
}
}(wrappedContext_[functionName]);
}
var isFunctions = [
"isBuffer",
"isEnabled",
"isFramebuffer",
"isProgram",
"isRenderbuffer",
"isShader",
"isTexture"
];
for (var ii = 0; ii < isFunctions.length; ++ii) {
var functionName = isFunctions[ii];
wrappedContext_[functionName] = function(f) {
return function() {
loseContextIfTime();
if (contextLost_) {
return false;
}
return f.apply(ctx, arguments);
}
}(wrappedContext_[functionName]);
}
wrappedContext_.checkFramebufferStatus = function(f) {
return function() {
loseContextIfTime();
if (contextLost_) {
return wrappedContext_.FRAMEBUFFER_UNSUPPORTED;
}
return f.apply(ctx, arguments);
};
}(wrappedContext_.checkFramebufferStatus);
wrappedContext_.getAttribLocation = function(f) {
return function() {
loseContextIfTime();
if (contextLost_) {
return -1;
}
return f.apply(ctx, arguments);
};
}(wrappedContext_.getAttribLocation);
wrappedContext_.getVertexAttribOffset = function(f) {
return function() {
loseContextIfTime();
if (contextLost_) {
return 0;
}
return f.apply(ctx, arguments);
};
}(wrappedContext_.getVertexAttribOffset);
wrappedContext_.isContextLost = function() {
return contextLost_;
};
return wrappedContext_;
}
}
return {
/**
* Initializes this module. Safe to call more than once.
* @param {!WebGLRenderingContext} ctx A WebGL context. If
* you have more than one context it doesn't matter which one
* you pass in, it is only used to pull out constants.
*/
'init': init,
/**
* Returns true or false if value matches any WebGL enum
* @param {*} value Value to check if it might be an enum.
* @return {boolean} True if value matches one of the WebGL defined enums
*/
'mightBeEnum': mightBeEnum,
/**
* Gets an string version of an WebGL enum.
*
* Example:
* WebGLDebugUtil.init(ctx);
* var str = WebGLDebugUtil.glEnumToString(ctx.getError());
*
* @param {number} value Value to return an enum for
* @return {string} The string version of the enum.
*/
'glEnumToString': glEnumToString,
/**
* Converts the argument of a WebGL function to a string.
* Attempts to convert enum arguments to strings.
*
* Example:
* WebGLDebugUtil.init(ctx);
* var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D);
*
* would return 'TEXTURE_2D'
*
* @param {string} functionName the name of the WebGL function.
* @param {number} numArgs The number of arguments
* @param {number} argumentIndx the index of the argument.
* @param {*} value The value of the argument.
* @return {string} The value as a string.
*/
'glFunctionArgToString': glFunctionArgToString,
/**
* Converts the arguments of a WebGL function to a string.
* Attempts to convert enum arguments to strings.
*
* @param {string} functionName the name of the WebGL function.
* @param {number} args The arguments.
* @return {string} The arguments as a string.
*/
'glFunctionArgsToString': glFunctionArgsToString,
/**
* Given a WebGL context returns a wrapped context that calls
* gl.getError after every command and calls a function if the
* result is not NO_ERROR.
*
* You can supply your own function if you want. For example, if you'd like
* an exception thrown on any GL error you could do this
*
* function throwOnGLError(err, funcName, args) {
* throw WebGLDebugUtils.glEnumToString(err) +
* " was caused by call to " + funcName;
* };
*
* ctx = WebGLDebugUtils.makeDebugContext(
* canvas.getContext("webgl"), throwOnGLError);
*
* @param {!WebGLRenderingContext} ctx The webgl context to wrap.
* @param {!function(err, funcName, args): void} opt_onErrorFunc The function
* to call when gl.getError returns an error. If not specified the default
* function calls console.log with a message.
* @param {!function(funcName, args): void} opt_onFunc The
* function to call when each webgl function is called. You
* can use this to log all calls for example.
*/
'makeDebugContext': makeDebugContext,
/**
* Given a canvas element returns a wrapped canvas element that will
* simulate lost context. The canvas returned adds the following functions.
*
* loseContext:
* simulates a lost context event.
*
* restoreContext:
* simulates the context being restored.
*
* lostContextInNCalls:
* loses the context after N gl calls.
*
* getNumCalls:
* tells you how many gl calls there have been so far.
*
* setRestoreTimeout:
* sets the number of milliseconds until the context is restored
* after it has been lost. Defaults to 0. Pass -1 to prevent
* automatic restoring.
*
* @param {!Canvas} canvas The canvas element to wrap.
*/
'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas,
/**
* Resets a context to the initial state.
* @param {!WebGLRenderingContext} ctx The webgl context to
* reset.
*/
'resetToInitialState': resetToInitialState
};
}();
================================================
FILE: src/js/third_party/webgl_teapot/webgl-utils.js
================================================
/*
* Copyright 2010, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @fileoverview This file contains functions every webgl program will need
* a version of one way or another.
*
* Instead of setting up a context manually it is recommended to
* use. This will check for success or failure. On failure it
* will attempt to present an approriate message to the user.
*
* gl = WebGLUtils.setupWebGL(canvas);
*
* For animated WebGL apps use of setTimeout or setInterval are
* discouraged. It is recommended you structure your rendering
* loop like this.
*
* function render() {
* window.requestAnimFrame(render, canvas);
*
* // do rendering
* ...
* }
* render();
*
* This will call your rendering function up to the refresh rate
* of your display but will stop rendering if your app is not
* visible.
*/
WebGLUtils = function() {
/**
* Creates the HTLM for a failure message
* @param {string} canvasContainerId id of container of th
* canvas.
* @return {string} The html.
*/
var makeFailHTML = function(msg) {
return '' +
'
' +
'
' +
'
' +
'
' + msg + '
' +
'
' +
'
';
};
/**
* Mesasge for getting a webgl browser
* @type {string}
*/
var GET_A_WEBGL_BROWSER = '' +
'This page requires a browser that supports WebGL. ' +
'Click here to upgrade your browser.';
/**
* Mesasge for need better hardware
* @type {string}
*/
var OTHER_PROBLEM = '' +
"It doesn't appear your computer can support WebGL. " +
'Click here for more information.';
/**
* Creates a webgl context. If creation fails it will
* change the contents of the container of the