@@ -303,6 +303,20 @@ WebGL renderer. | |||
Specifies the key numbers that are captured in key events. Defaults to the | |||
standard keys that are used by the viewer. | |||
### `backgroundColor` ([number, number, number]) | |||
Specifies an array containing RGB values [0, 1] that sets the background color | |||
shown past the edges of a partial panorama. Defaults to `[0, 0, 0]` (black). | |||
Does not work for `cubemap` panoramas. | |||
### `avoidShowingBackground` (boolean) | |||
If set to `true`, prevent displaying out-of-range areas of a partial panorama | |||
by constraining the yaw and the field-of-view. Even at the corners and edges | |||
of the canvas only areas actually belonging to the image | |||
(i.e., within [`minYaw`, `maxYaw`] and [`minPitch`, `maxPitch`]) are shown, | |||
thus setting the `backgroundColor` option is not needed if this option is set. | |||
Defaults to `false`. | |||
## `equirectangular` specific options | |||
@@ -336,11 +350,6 @@ and the equirectangular image is not cropped symmetrically. | |||
If set to `true`, any embedded Photo Sphere XMP data will be ignored; else, | |||
said data will override any existing settings. Defaults to `false`. | |||
### `backgroundColor` ([number, number, number]) | |||
Specifies an array containing RGB values [0, 1] that sets the background color | |||
shown past the edges of a partial panorama. Defaults to `[0, 0, 0]` (black). | |||
## `cubemap` specific options | |||
@@ -321,6 +321,11 @@ function Renderer(container) { | |||
program.drawInProgress = false; | |||
// Set background clear color | |||
var color = params.backgroundColor ? params.backgroundColor : [0, 0, 0]; | |||
gl.clearColor(color[0], color[1], color[2], 1.0); | |||
gl.clear(gl.COLOR_BUFFER_BIT); | |||
// Look up texture coordinates location | |||
program.texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); | |||
gl.enableVertexAttribArray(program.texCoordLocation); | |||
@@ -354,7 +359,6 @@ function Renderer(container) { | |||
// Set background color | |||
if (imageType == 'equirectangular') { | |||
program.backgroundColor = gl.getUniformLocation(program, 'u_backgroundColor'); | |||
var color = params.backgroundColor ? params.backgroundColor : [0, 0, 0]; | |||
gl.uniform4fv(program.backgroundColor, color.concat([1])); | |||
} | |||
@@ -707,8 +711,9 @@ function Renderer(container) { | |||
function multiresDraw() { | |||
if (!program.drawInProgress) { | |||
program.drawInProgress = true; | |||
gl.clear(gl.COLOR_BUFFER_BIT); | |||
for ( var i = 0; i < program.currentNodes.length; i++ ) { | |||
if (program.currentNodes[i].textureLoaded) { | |||
if (program.currentNodes[i].textureLoaded > 1) { | |||
//var color = program.currentNodes[i].color; | |||
//gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0); | |||
@@ -1035,9 +1040,12 @@ function Renderer(container) { | |||
this.image = new Image(); | |||
this.image.crossOrigin = crossOrigin ? crossOrigin : 'anonymous'; | |||
var loadFn = (function() { | |||
if (self.image.width > 0 && self.image.height > 0) // ignore missing tile to supporting partial image | |||
if (self.image.width > 0 && self.image.height > 0) { // ignore missing tile to supporting partial image | |||
processLoadedTexture(self.image, self.texture); | |||
self.callback(self.texture); | |||
self.callback(self.texture, true); | |||
} else { | |||
self.callback(self.texture, false); | |||
} | |||
releaseTextureImageLoader(self); | |||
}); | |||
this.image.addEventListener('load', loadFn); | |||
@@ -1086,9 +1094,9 @@ function Renderer(container) { | |||
function processNextTile(node) { | |||
if (!node.textureLoad) { | |||
node.textureLoad = true; | |||
loadTexture(encodeURI(node.path + '.' + image.extension), function(texture) { | |||
loadTexture(encodeURI(node.path + '.' + image.extension), function(texture, loaded) { | |||
node.texture = texture; | |||
node.textureLoaded = true; | |||
node.textureLoaded = loaded ? 2 : 1; | |||
}, globalParams.crossOrigin); | |||
} | |||
} | |||
@@ -102,6 +102,7 @@ var defaultConfig = { | |||
orientationOnByDefault: false, | |||
hotSpotDebug: false, | |||
backgroundColor: [0, 0, 0], | |||
avoidShowingBackground: false, | |||
animationTimingFunction: timingFunction, | |||
draggable: true, | |||
disableKeyboardCtrl: false, | |||
@@ -518,6 +519,7 @@ function onImageLoad() { | |||
} | |||
renderInit(); | |||
setHfov(config.hfov); // possibly adapt hfov after configuration and canvas is complete; prevents empty space on top or bottom by zomming out too much | |||
setTimeout(function(){isTimedOut = true;}, 500); | |||
} | |||
@@ -1438,23 +1440,41 @@ function render() { | |||
// Keep a tmp value of yaw for autoRotate comparison later | |||
tmpyaw = config.yaw; | |||
// Optionally avoid showing background (empty space) on left or right by adapting min/max yaw | |||
var hoffcut = 0, | |||
voffcut = 0; | |||
if (config.avoidShowingBackground) { | |||
var canvas = renderer.getCanvas(), | |||
hfov2 = config.hfov / 2, | |||
vfov2 = Math.atan2(Math.tan(hfov2 / 180 * Math.PI), (canvas.width / canvas.height)) * 180 / Math.PI, | |||
transposed = config.vaov > config.haov; | |||
if (transposed) { | |||
voffcut = vfov2 * (1 - Math.min(Math.cos((config.pitch - hfov2) / 180 * Math.PI), | |||
Math.cos((config.pitch + hfov2) / 180 * Math.PI))); | |||
} else { | |||
hoffcut = hfov2 * (1 - Math.min(Math.cos((config.pitch - vfov2) / 180 * Math.PI), | |||
Math.cos((config.pitch + vfov2) / 180 * Math.PI))); | |||
} | |||
} | |||
// Ensure the yaw is within min and max allowed | |||
var yawRange = config.maxYaw - config.minYaw, | |||
minYaw = -180, | |||
maxYaw = 180; | |||
if (yawRange < 360) { | |||
minYaw = config.minYaw + config.hfov / 2; | |||
maxYaw = config.maxYaw - config.hfov / 2; | |||
minYaw = config.minYaw + config.hfov / 2 + hoffcut; | |||
maxYaw = config.maxYaw - config.hfov / 2 - hoffcut; | |||
if (yawRange < config.hfov) { | |||
// Lock yaw to average of min and max yaw when both can be seen at once | |||
minYaw = maxYaw = (minYaw + maxYaw) / 2; | |||
} | |||
config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw)); | |||
} | |||
config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw)); | |||
// Check if we autoRotate in a limited by min and max yaw | |||
// If so reverse direction | |||
if (config.autoRotate !== false && tmpyaw != config.yaw) { | |||
if (config.autoRotate !== false && tmpyaw != config.yaw && | |||
prevTime !== undefined) { // this condition prevents changing the direction initially | |||
config.autoRotate *= -1; | |||
} | |||
@@ -2184,13 +2204,24 @@ function constrainHfov(hfov) { | |||
// Don't change view if bounds don't make sense | |||
console.log('HFOV bounds do not make sense (minHfov > maxHfov).') | |||
return config.hfov; | |||
} if (hfov < minHfov) { | |||
return minHfov; | |||
} | |||
var newHfov = config.hfov; | |||
if (hfov < minHfov) { | |||
newHfov = minHfov; | |||
} else if (hfov > config.maxHfov) { | |||
return config.maxHfov; | |||
newHfov = config.maxHfov; | |||
} else { | |||
return hfov; | |||
newHfov = hfov; | |||
} | |||
// Optionally avoid showing background (empty space) on top or bottom by adapting newHfov | |||
if (config.avoidShowingBackground && renderer) { | |||
var canvas = renderer.getCanvas(); | |||
newHfov = Math.min(newHfov, | |||
Math.atan(Math.tan((config.maxPitch - config.minPitch) / 360 * Math.PI) / | |||
canvas.height * canvas.width) | |||
* 360 / Math.PI); | |||
} | |||
return newHfov; | |||
} | |||