Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YouTube Equi-Angular Cubemap (EAC) projection #179

Merged
merged 1 commit into from
Aug 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Maintenance Status: Stable
- [`'AUTO'`](#auto)
- [`'360_LR'`](#360_lr)
- [`'360_TB'`](#360_tb)
- [`'EAC'`](#eac)
- [`'EAC_LR'`](#eac_lr)
- [`player.mediainfo.projection`](#playermediainfoprojection)
- [`debug`](#debug)
- [Credits](#credits)
Expand Down Expand Up @@ -213,6 +215,12 @@ Used for side-by-side 360 videos
#### `'360_TB'`
Used for top-to-bottom 360 videos

#### `'EAC'`
Used for Equi-Angular Cubemap videos

#### `'EAC_LR'`
Used for side-by-side Equi-Angular Cubemap videos

### `player.mediainfo.projection`

> type: `string`
Expand Down
44 changes: 44 additions & 0 deletions examples/eac.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>videojs-vr Demo</title>
<link href="../node_modules/video.js/dist/video-js.css" rel="stylesheet">
<link href="../dist/videojs-vr.css" rel="stylesheet">
</head>
<body>
<p>EAC video</p>
<video width="640" height="300" id="videojs-vr-player-eac" class="video-js vjs-default-skin" controls playsinline>
<source src="../samples/lcs15_eac.webm" type="video/webm">
</video>
<p>Equirectangular video</p>
<video width="640" height="300" id="videojs-vr-player-rect" class="video-js vjs-default-skin" controls playsinline>
<source src="../samples/lcs15_rect.webm" type="video/webm">
</video>
<p>Credits: US Navy, CC-BY-3.0</p>

<p>LR EAC video</p>
<video width="640" height="300" id="videojs-vr-player-eac2" class="video-js vjs-default-skin" controls playsinline>
<source src="../samples/coriolis_eac.webm" type="video/webm">
</video>
<p>TB Equirectangular video</p>
<video width="640" height="300" id="videojs-vr-player-rect2" class="video-js vjs-default-skin" controls playsinline>
<source src="../samples/coriolis_rect.webm" type="video/webm">
</video>
<p>Credits: Animations for Physics and Astronomy, CC-BY-3.0</p>

<ul>
<li><a href="../">return to main example</a></li>
</ul>
<script src="../node_modules/video.js/dist/video.js"></script>
<script src="../dist/videojs-vr.js"></script>
<script>
(function(window, videojs) {
window.vr_eac = videojs('videojs-vr-player-eac').vr({projection: 'EAC', debug: true, forceCardboard: false});
window.vr_rect = videojs('videojs-vr-player-rect').vr({projection: '360', debug: true, forceCardboard: false});
window.vr_eac2 = videojs('videojs-vr-player-eac2').vr({projection: 'EAC_LR', debug: true, forceCardboard: false});
window.vr_rect2 = videojs('videojs-vr-player-rect2').vr({projection: '360_TB', debug: true, forceCardboard: false});
}(window, window.videojs));
</script>
</body>
</html>
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<ul>
<li><a href="test/debug.html">Run unit tests in browser.</a></li>
<li><a href="examples/cube.html">Cube Video example</a></li>
<li><a href="examples/eac.html">EAC video example</a></li>
<li><a href="examples/fluid.html">"Fluid" video size example</a></li>
<li><a href="examples/iframe.html">Iframe example</a></li>
<li><a href="examples/180.html">180 VR example</a></li>
Expand Down
Binary file added samples/coriolis_eac.webm
Binary file not shown.
Binary file added samples/coriolis_rect.webm
Binary file not shown.
Binary file added samples/lcs15_eac.webm
Binary file not shown.
Binary file added samples/lcs15_rect.webm
Binary file not shown.
152 changes: 144 additions & 8 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ class VR extends Plugin {
}
return this.changeProjection_('NONE');
} else if (projection === '360') {

this.movieGeometry = new THREE.SphereBufferGeometry(256, 32, 32);
this.movieMaterial = new THREE.MeshBasicMaterial({ map: this.videoTexture, overdraw: true, side: THREE.BackSide });

Expand All @@ -134,9 +133,9 @@ class VR extends Plugin {
this.movieScreen.quaternion.setFromAxisAngle({x: 0, y: 1, z: 0}, -Math.PI / 2);
this.scene.add(this.movieScreen);
} else if (projection === '360_LR' || projection === '360_TB') {
// Left eye view
let geometry = new THREE.SphereGeometry(256, 32, 32);

// Left eye view
let uvs = geometry.faceVertexUvs[ 0 ];

for (let i = 0; i < uvs.length; i++) {
Expand All @@ -149,13 +148,13 @@ class VR extends Plugin {
}
}
}
geometry.scale(-1, 1, 1);

this.movieGeometry = new THREE.BufferGeometry().fromGeometry(geometry);
this.movieMaterial = new THREE.MeshBasicMaterial({ map: this.videoTexture, overdraw: true, side: THREE.BackSide });

this.movieScreen = new THREE.Mesh(this.movieGeometry, this.movieMaterial);
this.movieScreen.rotation.y = -Math.PI / 2;
this.movieScreen.scale.x = -1;
this.movieScreen.quaternion.setFromAxisAngle({x: 0, y: 1, z: 0}, -Math.PI / 2);
// display in left eye only
this.movieScreen.layers.set(1);
this.scene.add(this.movieScreen);
Expand All @@ -175,17 +174,16 @@ class VR extends Plugin {
}
}
}
geometry.scale(-1, 1, 1);

this.movieGeometry = new THREE.BufferGeometry().fromGeometry(geometry);
this.movieMaterial = new THREE.MeshBasicMaterial({ map: this.videoTexture, overdraw: true, side: THREE.BackSide });

this.movieScreen = new THREE.Mesh(this.movieGeometry, this.movieMaterial);
this.movieScreen.rotation.y = -Math.PI / 2;
this.movieScreen.scale.x = -1;
this.movieScreen.quaternion.setFromAxisAngle({x: 0, y: 1, z: 0}, -Math.PI / 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do these changes do?

Copy link
Contributor Author

@zhuyifei1999 zhuyifei1999 Aug 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied from 360 projection. This transforms the geometry so that it maps the texture correctly. The geometry is rotated by pi/2 and flipped.

The version on master is completely broken, resulting in a blank canvas, IIRC. You can test that with a 360_LR or 360_TB video.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can test that with a 360_LR or 360_TB video.

coriolis_rect.webm is one ;)

// display in right eye only
this.movieScreen.layers.set(2);
this.scene.add(this.movieScreen);

} else if (projection === '360_CUBE') {
this.movieGeometry = new THREE.BoxGeometry(256, 256, 256);
this.movieMaterial = new THREE.MeshBasicMaterial({ map: this.videoTexture, overdraw: true, side: THREE.BackSide });
Expand Down Expand Up @@ -266,6 +264,144 @@ class VR extends Plugin {
// display in right eye only
this.movieScreen.layers.set(2);
this.scene.add(this.movieScreen);
} else if (projection === 'EAC' || projection === 'EAC_LR') {
const makeScreen = (mapMatrix, scaleMatrix) => {
// "Continuity correction?": because of discontinuous faces and aliasing,
// we truncate the 2-pixel-wide strips on all discontinuous edges,
const contCorrect = 2;

this.movieGeometry = new THREE.BoxGeometry(256, 256, 256);
this.movieMaterial = new THREE.ShaderMaterial({
overdraw: true, side: THREE.BackSide,
uniforms: {
mapped: {value: this.videoTexture},
mapMatrix: {value: mapMatrix},
contCorrect: {value: contCorrect},
faceWH: {value: new THREE.Vector2(1 / 3, 1 / 2).applyMatrix3(scaleMatrix)},
vidWH: {value: new THREE.Vector2(
this.videoTexture.image.videoWidth, this.videoTexture.image.videoHeight).applyMatrix3(scaleMatrix)}
},
vertexShader: `
varying vec2 vUv;
uniform mat3 mapMatrix;

void main() {
vUv = (mapMatrix * vec3(uv, 1.)).xy;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
}`,
fragmentShader: `
varying vec2 vUv;
uniform sampler2D mapped;
uniform vec2 faceWH;
uniform vec2 vidWH;
uniform float contCorrect;

const float PI = 3.1415926535897932384626433832795;

void main() {
vec2 corner = vUv - mod(vUv, faceWH) + vec2(0, contCorrect / vidWH.y);

vec2 faceWHadj = faceWH - vec2(0, contCorrect * 2. / vidWH.y);

vec2 p = (vUv - corner) / faceWHadj - .5;
vec2 q = 2. / PI * atan(2. * p) + .5;

vec2 eUv = corner + q * faceWHadj;

gl_FragColor = texture2D(mapped, eUv);
}`
});

const right = [new THREE.Vector2(0, 1 / 2), new THREE.Vector2(1 / 3, 1 / 2), new THREE.Vector2(1 / 3, 1), new THREE.Vector2(0, 1)];
const front = [new THREE.Vector2(1 / 3, 1 / 2), new THREE.Vector2(2 / 3, 1 / 2), new THREE.Vector2(2 / 3, 1), new THREE.Vector2(1 / 3, 1)];
const left = [new THREE.Vector2(2 / 3, 1 / 2), new THREE.Vector2(1, 1 / 2), new THREE.Vector2(1, 1), new THREE.Vector2(2 / 3, 1)];
const bottom = [new THREE.Vector2(1 / 3, 0), new THREE.Vector2(1 / 3, 1 / 2), new THREE.Vector2(0, 1 / 2), new THREE.Vector2(0, 0)];
const back = [new THREE.Vector2(1 / 3, 1 / 2), new THREE.Vector2(1 / 3, 0), new THREE.Vector2(2 / 3, 0), new THREE.Vector2(2 / 3, 1 / 2)];
const top = [new THREE.Vector2(1, 0), new THREE.Vector2(1, 1 / 2), new THREE.Vector2(2 / 3, 1 / 2), new THREE.Vector2(2 / 3, 0)];

for (const face of [right, front, left, bottom, back, top]) {
const height = this.videoTexture.image.videoHeight;
let lowY = 1;
let highY = 0;

for (const vector of face) {
if (vector.y < lowY) {
lowY = vector.y;
}
if (vector.y > highY) {
highY = vector.y;
}
}

for (const vector of face) {
if (Math.abs(vector.y - lowY) < Number.EPSILON) {
vector.y += contCorrect / height;
}
if (Math.abs(vector.y - highY) < Number.EPSILON) {
vector.y -= contCorrect / height;
}

vector.x = vector.x / height * (height - contCorrect * 2) + contCorrect / height;
}
}

this.movieGeometry.faceVertexUvs[0] = [];

this.movieGeometry.faceVertexUvs[0][0] = [ right[2], right[1], right[3] ];
this.movieGeometry.faceVertexUvs[0][1] = [ right[1], right[0], right[3] ];

this.movieGeometry.faceVertexUvs[0][2] = [ left[2], left[1], left[3] ];
this.movieGeometry.faceVertexUvs[0][3] = [ left[1], left[0], left[3] ];

this.movieGeometry.faceVertexUvs[0][4] = [ top[2], top[1], top[3] ];
this.movieGeometry.faceVertexUvs[0][5] = [ top[1], top[0], top[3] ];

this.movieGeometry.faceVertexUvs[0][6] = [ bottom[2], bottom[1], bottom[3] ];
this.movieGeometry.faceVertexUvs[0][7] = [ bottom[1], bottom[0], bottom[3] ];

this.movieGeometry.faceVertexUvs[0][8] = [ front[2], front[1], front[3] ];
this.movieGeometry.faceVertexUvs[0][9] = [ front[1], front[0], front[3] ];

this.movieGeometry.faceVertexUvs[0][10] = [ back[2], back[1], back[3] ];
this.movieGeometry.faceVertexUvs[0][11] = [ back[1], back[0], back[3] ];

this.movieScreen = new THREE.Mesh(this.movieGeometry, this.movieMaterial);
this.movieScreen.position.set(position.x, position.y, position.z);
this.movieScreen.rotation.y = -Math.PI;
return this.movieScreen;
};

if (projection === 'EAC') {
this.scene.add(makeScreen(new THREE.Matrix3(), new THREE.Matrix3()));
} else {
const scaleMatrix = new THREE.Matrix3().set(
0, 0.5, 0,
1, 0, 0,
0, 0, 1
);

makeScreen(
new THREE.Matrix3().set(
0, -0.5, 0.5,
1, 0, 0,
0, 0, 1
), scaleMatrix
);
// display in left eye only
this.movieScreen.layers.set(1);
this.scene.add(this.movieScreen);

makeScreen(
new THREE.Matrix3().set(
0, -0.5, 1,
1, 0, 0,
0, 0, 1
), scaleMatrix
);
// display in right eye only
this.movieScreen.layers.set(2);
this.scene.add(this.movieScreen);
}
}

this.currentProjection_ = projection;
Expand Down Expand Up @@ -454,7 +590,7 @@ class VR extends Plugin {
// Store vector representing the direction in which the camera is looking, in world space.
this.cameraVector = new THREE.Vector3();

if (this.currentProjection_ === '360_LR' || this.currentProjection_ === '360_TB' || this.currentProjection_ === '180') {
if (this.currentProjection_ === '360_LR' || this.currentProjection_ === '360_TB' || this.currentProjection_ === '180' || this.currentProjection_ === 'EAC_LR') {
// Render left eye when not in VR mode
this.camera.layers.enable(1);
}
Expand Down
2 changes: 2 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const validProjections = [
'360_LR',
'360_TB',
'360_CUBE',
'EAC',
'EAC_LR',
'NONE',
'AUTO',
'Sphere',
Expand Down