@@ -1,4 +1,4 @@ | |||
Copyright (c) 2011-2016 Matthew Petroff | |||
Copyright (c) 2011-2017 Matthew Petroff | |||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||
this software and associated documentation files (the "Software"), to deal in | |||
@@ -1 +1 @@ | |||
2.2.1 | |||
2.3.2 |
@@ -2,6 +2,98 @@ Changelog | |||
========= | |||
Changes in Pannellum 2.3.2 | |||
-------------------------- | |||
Bugfixes: | |||
- Fix Chrome fullscreen regression introduced in 2.3.1 | |||
Changes in Pannellum 2.3.1 | |||
-------------------------- | |||
Bugfixes: | |||
- Removed use of poorly supported ES6 `Math.sign` function | |||
- Fixed fullscreen bug in Internet Explorer | |||
- Fixed framerate issue with device orientation control enabled | |||
Improvements: | |||
- Better handling of view limits when both limits are in view | |||
Changes in Pannellum 2.3.0 | |||
-------------------------- | |||
New Features: | |||
- Device orientation support for mobile devices | |||
- Event framework for API | |||
- Partial panorama background color can now be set using | |||
`backgroundColor` parameter | |||
- Custom hot spots are now supported as are hot spot click handlers | |||
- Hot spots can now specify target HFOV (`targetHfov` parameter) | |||
- Parameter to hide all controls (`showControls`) | |||
- Parameter to disable mouse zooming (`mouseZoom`) | |||
New API functions: | |||
- Destructor (`destroy`) | |||
- Look at position (`lookAt`) | |||
- Get current scene ID (`getScene`) | |||
- Load scene (`loadScene`) | |||
- Add and remove scenes (`addScene` and `removeScene`) | |||
- Add and remove hot spots (`addHotSpot` and `removeHotSpot`) | |||
- Auto rotate start / stop (`startAutoRotate` and `stopAutoRotate`) | |||
- Retrieve current configuration (`getConfig`) | |||
- Toggle fullscreen (`toggleFullscreen`) | |||
- Get and set north offset (`getNorthOffset` and `setNorthOffset`) | |||
Improvements: | |||
- Pitch and yaw limits are now applied to edge of viewer instead of center | |||
- Panorama extents can now be set using URL parameters | |||
- Individual XMP metadata parameters can now be overridden | |||
- Horizon pitch and roll can now be manually set (was previously only | |||
supported via XMP metadata) | |||
- When auto rotate restarts, the pitch and HFOV now return to their | |||
original values | |||
- API movements can now be animated | |||
- Standalone viewer is more mobile friendly | |||
- Improved touch panning interaction | |||
- Fragments identifiers can now be used for standalone viewer configuration | |||
- Blob URLs are now supported | |||
- Added hot spot debug indicator | |||
- Video.js plugin now accepts a Pannellum configuration | |||
Bugfixes: | |||
- Fixed numerous auto rotate bugs | |||
- Auto rotate speed is now actually in degrees per second | |||
- Long error URLs are now properly wrapped | |||
- Fixed mobile device orientation change bug | |||
- Fixed Safari fullscreen bug | |||
- Fullscreen now works in IE | |||
- Fixed Chrome bug where hot spots appeared above controls | |||
- Scene fades with multires now work properly | |||
- Hot spot target pointing now works when set to zero | |||
- Hot spots without text are now properly handled | |||
- Fixed memory leaks | |||
- Fixed multires tile loading error | |||
- Fixed a few URL handling bugs | |||
- Fixed multires zoom jumping when viewer was resized | |||
- Title and author are now reset when changing scenes | |||
- Mouse handlers now work with Hi-DPI displays | |||
- Minimum and maximum HFOV can now both be set to the same value | |||
Backwards-Incompatible Configuration Parameter Changes: | |||
- The deprecated `tour` parameter was removed; tour JSON configuration files | |||
can be used with the `config` parameter | |||
Changes in Pannellum 2.2.1 | |||
-------------------------- | |||
@@ -1,4 +1,4 @@ | |||
# Events | |||
# API Events | |||
## `load` | |||
@@ -8,7 +8,13 @@ Fired when a panorama finishes loading. | |||
## `scenechange` | |||
Fired when a scene change is initiated. A `load` event will be fired when the | |||
new scene finishes loading. | |||
new scene finishes loading. Passes scene ID string to handler. | |||
## `scenechangefadedone` | |||
If a scene transition fade interval is specified, this event is fired when the | |||
fading is completed after changing scenes. | |||
## `error` | |||
@@ -20,3 +26,23 @@ event listener. | |||
## `errorcleared` | |||
Fired when an error is cleared. | |||
## `mousedown` | |||
Fired when the mouse button is pressed. Passes `MouseEvent` to handler. | |||
## `mouseup` | |||
Fired when the mouse button is released. Passes `MouseEvent` to handler. | |||
## `touchstart` | |||
Fired when a touch starts. Passes `TouchEvent` to handler. | |||
## `touchend` | |||
Fired when a touch ends. Passes `TouchEvent` to handler. |
@@ -5,190 +5,271 @@ | |||
## General options | |||
### `type` | |||
### `type` (string) | |||
This specifies the panorama type. Can be `equirectangular`, `cubemap`, or | |||
`multires`. Defaults to `equirectangular`. | |||
### `title` | |||
### `title` (string) | |||
If set, the value is displayed as the panorama's title. If no title is desired, | |||
don't set this parameter. | |||
### `author` | |||
### `author` (string) | |||
If set, the value is displayed as the panorama's author. If no author is | |||
desired, don't set this parameter. | |||
### `basePath` | |||
### `basePath` (string) | |||
This specifies a base path to load the images from. | |||
### `autoLoad` | |||
### `autoLoad` (boolean) | |||
When set to `true`, the panorama will automatically load. When `false`, the | |||
user needs to click on the load button to load the panorama. Defaults to | |||
`false`. | |||
### `autoRotate` | |||
### `autoRotate` (number) | |||
Setting this parameter causes the panorama to automatically rotate when loaded. | |||
The value specifies the rotation speed in degrees per second. Positive is | |||
counter-clockwise, and negative is clockwise. | |||
### `autoRotateInactivityDelay` | |||
### `autoRotateInactivityDelay` (number) | |||
Sets the delay, in milliseconds, to start automatically rotating the panorama | |||
after user activity ceases. This parameter only has an effect if the | |||
`autoRotate` parameter is set. | |||
### `autoRotateStopDelay` | |||
### `autoRotateStopDelay` (number) | |||
Sets the delay, in milliseconds, to stop automatically rotating the panorama | |||
after it is loaded. This parameter only has an effect if the `autoRotate` | |||
parameter is set. | |||
### `fallback` | |||
### `fallback` (string) | |||
If set, the value is used as a URL for a fallback viewer in case Pannellum is | |||
not supported by the user's device. The user will be given the option to click | |||
a link and visit this URL if Pannellum fails to work. | |||
### `showZoomCtrl` | |||
### `orientationOnByDefault` (boolean) | |||
If set to `true`, device orientation control will be used when the panorama is | |||
loaded, if the device supports it. If false, device orientation control needs | |||
to be activated by pressing a button. Defaults to `false`. | |||
### `showZoomCtrl` (boolean) | |||
If set to `false`, the zoom controls will not be displayed. Defaults to `true`. | |||
### `keyboardZoom` | |||
### `keyboardZoom` (boolean) | |||
If set to `false`, zooming with keyboard will be disabled. Defaults to `true`. | |||
### `mouseZoom` | |||
### `mouseZoom` (boolean or string) | |||
If set to `false`, zooming with mouse wheel will be disabled. Defaults to `true`. | |||
Can also be set to `fullscreenonly`, in which case it is only enabled when the | |||
viewer is fullscreen. | |||
### `draggable` (boolean) | |||
### `showFullscreenCtrl` | |||
If set to `false`, mouse and touch dragging is disabled. Defaults to `true`. | |||
### `showFullscreenCtrl` (boolean) | |||
If set to `false`, the fullscreen control will not be displayed. Defaults to | |||
`true`. The fullscreen button will only be displayed if the browser supports | |||
the fullscreen API. | |||
### `yaw` | |||
### `showControls` (boolean) | |||
If set to `false`, no controls are displayed. Defaults to `true`. | |||
### `yaw` (number) | |||
Sets the panorama's starting yaw position in degrees. Defaults to `0`. | |||
### `pitch` | |||
### `pitch` (number) | |||
Sets the panorama's starting pitch position in degrees. Defaults to `0`. | |||
### `hfov` | |||
### `hfov` (number) | |||
Sets the panorama's starting horizontal field of view in degrees. Defaults to | |||
`100`. | |||
### `minYaw` and `maxYaw` | |||
### `minYaw` and `maxYaw` (number) | |||
Sets the minimum / maximum yaw the viewer can be centered at, in degrees. | |||
Defaults to `-360` / `360`, i.e. no limit. | |||
Sets the minimum / maximum yaw the viewer edge can be at, in degrees. | |||
Defaults to `-180` / `180`, i.e. no limit. | |||
### `minPitch` and `maxPitch` | |||
### `minPitch` and `maxPitch` (number) | |||
Sets the minimum / maximum pitch the viewer can be centered at, in degrees. | |||
Defaults to `-85` / `85`. | |||
Sets the minimum / maximum pitch the viewer edge can be at, in degrees. | |||
Defaults to `undefined`, so the viewer center can reach `-90` / `90`. | |||
### `minHfov` and `maxHfov` | |||
### `minHfov` and `maxHfov` (number) | |||
Sets the minimum / maximum horizontal field of view, in degrees, that the | |||
viewer can be set to. Defaults to `50` / `120`. | |||
### `compass` | |||
### `compass` (boolean) | |||
If `true`, a compass is displayed. Defaults to `false`. | |||
If `true`, a compass is displayed. Normally defaults to `false`; defaults to | |||
`true` if heading information is present in Photo Sphere XMP metadata. | |||
### `northOffset` | |||
### `northOffset` (number) | |||
Set the offset, in degrees, of the center of the panorama from North. As this | |||
affects the compass, it only has an effect if `compass` is set to `true`. | |||
### `preview` | |||
### `preview` (string) | |||
Specifies a URL for a preview image to display before the panorama is loaded. | |||
### `hotSpots` | |||
### `previewTitle` (string) | |||
Specifies the title to be displayed while the load button is displayed. | |||
### `previewAuthor` (string) | |||
Specifies the author to be displayed while the load button is displayed. | |||
### `loadButtonLabel` (string) | |||
Label to display on load button. Defaults to `Click to Load Panorama`. | |||
### `horizonPitch` and `horizonRoll` (number) | |||
Specifies pitch / roll of image horizon, in degrees (for correcting | |||
non-leveled panoramas). | |||
### `animationTimingFunction` (function) [API only] | |||
This specifies a timing function to be used for animating movements such as | |||
when the `lookAt` method is called. The default timing function is | |||
`easeInOutQuad`. If a custom function is specified, it should take a number | |||
[0, 1] as its only argument and return a number [0, 1]. | |||
### `escapeHTML` (boolean) | |||
When true, HTML is escaped from configuration strings to help mitigate possible | |||
DOM XSS attacks. This is always `true` when using the standalone viewer since | |||
the configuration is provided via the URL; it defaults to `false` but can be | |||
set to `true` when using the API. | |||
### `hotSpots` (array) | |||
This specifies an array of hot spots that can be links to other scenes, | |||
information, or external links. Each array element has the following properties. | |||
#### `pitch` | |||
#### `pitch` (number) | |||
Specifies the pitch portion of the hot spot's location. | |||
Specifies the pitch portion of the hot spot's location, in degrees. | |||
#### `yaw` | |||
#### `yaw` (number) | |||
Specifies the yaw portion of the hot spot's location. | |||
Specifies the yaw portion of the hot spot's location, in degrees. | |||
#### `type` | |||
#### `type` (string) | |||
Specifies the type of the hot spot. Can be `scene` for scene links or `info` | |||
for information hot spots. A tour configuration file is required for `scene` | |||
hot spots. | |||
#### `text` | |||
#### `text` (string) | |||
This specifies the text that is displayed when the user hovers over the hot | |||
spot. | |||
#### `URL` | |||
#### `URL` (string) | |||
If specified for an `info` hot spot, the hot spot links to the specified URL. | |||
Not applicable for `scene` hot spots. | |||
#### `sceneId` | |||
#### `sceneId` (string) | |||
Specifies the ID of the scene to link to for `scene` hot spots. Not applicable | |||
for `info` hot spots. | |||
#### `targetPitch` | |||
#### `targetPitch` (number) | |||
Specifies the pitch of the target scene, in degrees. Can also be set to `same`, | |||
which uses the current pitch of the current scene as the initial pitch of the | |||
target scene. | |||
#### `targetYaw` (number) | |||
Specifies the pitch of the target scene. | |||
Specifies the yaw of the target scene, in degrees. Can also be set to `same` or | |||
`sameAzimuth`. These settings use the current yaw of the current scene as the | |||
initial yaw of the target scene; `same` uses the current yaw directly, while | |||
`sameAzimuth` takes into account the `northOffset` values of both scenes to | |||
maintain the same direction with regard to north. | |||
#### `targetYaw` | |||
#### `targetHfov` (number) | |||
Specifies the yaw of the target scene. | |||
Specifies the HFOV of the target scene, in degrees. | |||
#### `targetHfov` | |||
#### `cssClass` (string) | |||
Specifies the HFOV of the target scene. | |||
If specified, string is used as the CSS class for the hot spot instead of the | |||
default CSS classes. | |||
### `hotSpotDebug` | |||
#### `createTooltipFunc` (function) and `createTooltipArgs` (object) | |||
If `createTooltipFunc` is specified, this function is used to create the hot | |||
spot tooltip DOM instead of the default function. The contents of | |||
`createTooltipArgs` are passed to the function as arguments. | |||
#### `clickHandlerFunc` (function) and `clickHandlerArgs` (object) | |||
If `clickHandlerFunc` is specified, this function is added as an event handler | |||
for the hot spot's `click` event. The event object and the contents of | |||
`clickHandlerArgs` are passed to the function as arguments. | |||
### `hotSpotDebug` (boolean) | |||
When `true`, the mouse pointer's pitch and yaw are logged to the console when | |||
the mouse button is clicked. Defaults to `false`. | |||
### `sceneFadeDuration` | |||
### `sceneFadeDuration` (number) | |||
Specifies the fade duration, in milliseconds, when transitioning between | |||
scenes. Not defined by default. Only applicable for tours. Only works with | |||
@@ -198,38 +279,40 @@ WebGL renderer. | |||
## `equirectangular` specific options | |||
### `panorama` | |||
### `panorama` (string) | |||
Sets the URL to the equirectangular panorama image. This is relative to | |||
`basePath` if it is set, else it is relative to the location of | |||
`pannellum.htm`. An absolute URL can also be used. | |||
### `haov` | |||
### `haov` (number) | |||
Sets the panorama's horizontal angle of view, in degrees. Defaults to `360`. | |||
This is used if the equirectangular image does not cover a full 360 degrees in | |||
the horizontal. | |||
### `vaov` | |||
### `vaov` (number) | |||
Sets the panorama's vertical angle of view, in degrees. Defaults to `180`. This | |||
is used if the equirectangular image does not cover a full 180 degrees in the | |||
vertical. | |||
### `vOffset` | |||
### `vOffset` (number) | |||
Sets the vertical offset of the center of the equirectangular image from the | |||
horizon, in degrees. Defaults to `0`. This is used if `vaov` is less than `180` | |||
and the equirectangular image is not cropped symmetrically. | |||
### `ignoreGPanoXMP` | |||
### `ignoreGPanoXMP` (boolean) | |||
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 | |||
@@ -250,14 +333,14 @@ used. | |||
This contains information about the multiresolution panorama in sub-keys. | |||
#### `basePath` | |||
#### `basePath` (string) | |||
This is the base path of the URLs for the multiresolution tiles. It is relative | |||
to the regular `basePath` option if it is defined, else it is relative to the | |||
location of `pannellum.htm`. An absolute URL can also be used. | |||
#### `path` | |||
#### `path` (string) | |||
This is a format string for the location of the multiresolution tiles, relative | |||
to `multiRes.basePath`, which is relative to `basePath`. Format parameters are | |||
@@ -265,7 +348,7 @@ to `multiRes.basePath`, which is relative to `basePath`. Format parameters are | |||
`%y` for the y index. For each tile, `.extension` is appended. | |||
#### `fallbackPath` | |||
#### `fallbackPath` (string) | |||
This is a format string for the location of the fallback tiles for the CSS 3D | |||
transform-based renderer if the WebGL renderer is not supported, relative | |||
@@ -273,22 +356,22 @@ to `multiRes.basePath`, which is relative to `basePath`. The only format | |||
parameter is `%s`, for the cube face. For each face, `.extension` is appended. | |||
#### `extension` | |||
#### `extension` (string) | |||
Specifies the tiles' file extension. Do not include the `.`. | |||
#### `tileResolution` | |||
#### `tileResolution` (number) | |||
This specifies the size in pixels of each image tile. | |||
#### `maxLevel` | |||
#### `maxLevel` (number) | |||
This specifies the maximum zoom level. | |||
#### `cubeResolution` | |||
#### `cubeResolution` (number) | |||
This specifies the size in pixels of the full resolution cube faces the image | |||
tiles were created from. | |||
@@ -299,7 +382,7 @@ tiles were created from. | |||
Currently, only equirectangular dynamic content is supported. | |||
### `dynamic` | |||
### `dynamic` (boolean) | |||
The panorama source is considered dynamic when this is set to `true`. Defaults | |||
to `false`. This should be set to `true` for video. | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"name": "pannellum", | |||
"description": "Pannellum is a lightweight, free, and open source panorama viewer for the web.", | |||
"version": "2.2.1", | |||
"version": "2.3.2", | |||
"bugs": { | |||
"url": "https://github.com/mpetroff/pannellum/issues" | |||
}, | |||
@@ -1,7 +1,9 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="182"> | |||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="208"> | |||
<circle fill-opacity=".78" cy="117" cx="13" r="11" fill="#fff"/> | |||
<circle fill-opacity=".78" cy="143" cx="13" r="11" fill="#fff"/> | |||
<circle cy="169" cx="13" r="7" fill="none" stroke="#000" stroke-width="2"/> | |||
<circle cy="195" cx="13" r="7" fill="none" stroke="#000" stroke-width="2"/> | |||
<circle cx="13" cy="195" r="2.5"/> | |||
<path d="m5 83v6h2v-4h4v-2zm10 0v2h4v4h2v-6zm-5 5v6h6v-6zm-5 5v6h6v-2h-4v-4zm14 0v4h-4v2h6v-6z"/> | |||
<path d="m13 110a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm-1 3h2v2h-2zm0 3h2v5h-2z"/> | |||
<path d="m5 57v6h2v-4h4v-2zm10 0v2h4v4h2v-6zm-10 10v6h6v-2h-4v-4zm14 0v4h-4v2h6v-6z"/> | |||
@@ -16,6 +16,7 @@ | |||
user-select: none; | |||
outline: 0; | |||
line-height: 1.4; | |||
contain: content; | |||
} | |||
.pnlm-container * { | |||
@@ -23,10 +24,12 @@ | |||
} | |||
.pnlm-grab { | |||
cursor: grab; | |||
cursor: url('img/grab.svg') 12 8, default; | |||
} | |||
.pnlm-grabbing { | |||
cursor: grabbing; | |||
cursor: url('img/grabbing.svg') 12 8, default; | |||
} | |||
@@ -42,6 +45,10 @@ | |||
height: 100% !important; | |||
width: 100% !important; | |||
} | |||
.pnlm-container:-ms-fullscreen { | |||
height: 100% !important; | |||
width: 100% !important; | |||
} | |||
.pnlm-container:fullscreen { | |||
height: 100% !important; | |||
width: 100% !important; | |||
@@ -104,12 +111,31 @@ | |||
border-radius: 0 0 3px 3px; | |||
} | |||
.pnlm-fullscreen-toggle-button, .pnlm-orientation-button { | |||
.pnlm-fullscreen-toggle-button, .pnlm-orientation-button, .pnlm-hot-spot-debug-indicator { | |||
width: 26px; | |||
height: 26px; | |||
} | |||
.pnlm-hot-spot-debug-indicator { | |||
position: absolute; | |||
top: 50%; | |||
left: 50%; | |||
width: 26px; | |||
height: 26px; | |||
margin: -13px 0 0 -13px; | |||
background-color: rgba(255, 255, 255, 0.5); | |||
border-radius: 13px; | |||
display: none; | |||
} | |||
.pnlm-orientation-button-inactive { | |||
background-position: 0 -156px; | |||
} | |||
.pnlm-orientation-button-active { | |||
background-position: 0 -182px; | |||
} | |||
.pnlm-fullscreen-toggle-button-inactive { | |||
background-position: 0 -52px; | |||
} | |||
@@ -293,17 +319,19 @@ | |||
color: #eee; | |||
} | |||
.pnlm-hotspot { | |||
.pnlm-hotspot-base { | |||
position: absolute; | |||
height: 26px; | |||
width: 26px; | |||
visibility: hidden; | |||
cursor: default; | |||
border-radius: 13px; | |||
vertical-align: middle; | |||
top: 0; | |||
z-index: 1; | |||
} | |||
.pnlm-hotspot { | |||
height: 26px; | |||
width: 26px; | |||
border-radius: 13px; | |||
} | |||
.pnlm-hotspot:hover { | |||
background-color: rgba(255,255,255,0.2); | |||
} | |||
@@ -323,14 +351,13 @@ div.pnlm-tooltip span { | |||
text-align: center; | |||
max-width: 200px; | |||
padding: 5px 10px; | |||
margin-top: -41px; | |||
margin-left: -220px; | |||
cursor: default; | |||
} | |||
div.pnlm-tooltip:hover span{ | |||
visibility: visible; | |||
} | |||
div.pnlm-tooltip:hover:after { | |||
div.pnlm-tooltip:hover span:after { | |||
content: ''; | |||
position: absolute; | |||
width: 0; | |||
@@ -338,8 +365,9 @@ div.pnlm-tooltip:hover:after { | |||
border-width: 10px; | |||
border-style: solid; | |||
border-color: rgba(0,0,0,0.7) transparent transparent transparent; | |||
top: -12px; | |||
left: 3px; | |||
bottom: -20px; | |||
left: -10px; | |||
margin: 0 50%; | |||
} | |||
.pnlm-compass { | |||
@@ -1,6 +1,6 @@ | |||
/* | |||
* libpannellum - A WebGL and CSS 3D transform based Panorama Renderer | |||
* Copyright (c) 2012-2016 Matthew Petroff | |||
* Copyright (c) 2012-2017 Matthew Petroff | |||
* | |||
* Permission is hereby granted, free of charge, to any person obtaining a copy | |||
* of this software and associated documentation files (the "Software"), to deal | |||
@@ -59,11 +59,19 @@ function Renderer(container) { | |||
* @param {number} vaov - Initial vertical angle of view. | |||
* @param {number} voffset - Initial vertical offset angle. | |||
* @param {function} callback - Load callback function. | |||
* @param {Object} [params] - Other configuration parameters (`horizonPitch`, `horizonRoll`, `backgroundColor`). | |||
*/ | |||
this.init = function(_image, _imageType, _dynamic, haov, vaov, voffset, callback) { | |||
this.init = function(_image, _imageType, _dynamic, haov, vaov, voffset, callback, params) { | |||
// Default argument for image type | |||
if (typeof _imageType === undefined) | |||
_imageType = 'equirectangular'; | |||
if (_imageType != 'equirectangular' && _imageType != 'cubemap' && | |||
_imageType != 'multires') { | |||
console.log('Error: invalid image type specified!'); | |||
throw {type: 'config error'}; | |||
} | |||
imageType = _imageType; | |||
image = _image; | |||
dynamic = _dynamic; | |||
@@ -88,6 +96,7 @@ function Renderer(container) { | |||
gl.deleteProgram(program); | |||
program = undefined; | |||
} | |||
pose = undefined; | |||
var s; | |||
@@ -248,9 +257,9 @@ function Renderer(container) { | |||
} | |||
// Store horizon pitch and roll if applicable | |||
if (image.horizonPitch !== undefined && image.horizonRoll !== undefined) { | |||
pose = [image.horizonPitch, image.horizonRoll]; | |||
} | |||
if (params !== undefined && (params.horizonPitch !== undefined || params.horizonRoll !== undefined)) | |||
pose = [params.horizonPitch == undefined ? 0 : params.horizonPitch, | |||
params.horizonRoll == undefined ? 0 : params.horizonRoll]; | |||
// Set 2d texture binding | |||
var glBindType = gl.TEXTURE_2D; | |||
@@ -328,6 +337,13 @@ function Renderer(container) { | |||
gl.uniform1f(program.v, vaov / Math.PI); | |||
gl.uniform1f(program.vo, voffset / Math.PI * 2); | |||
// 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])); | |||
} | |||
// Create texture | |||
program.texture = gl.createTexture(); | |||
gl.bindTexture(glBindType, program.texture); | |||
@@ -408,6 +424,13 @@ function Renderer(container) { | |||
container.removeChild(world); | |||
} | |||
} | |||
if (gl) { | |||
// The spec says this is only supposed to simulate losing the WebGL | |||
// context, but in practice it tends to actually free the memory. | |||
var extension = gl.getExtension('WEBGL_lose_context'); | |||
if (extension) | |||
extension.loseContext(); | |||
} | |||
}; | |||
/** | |||
@@ -446,7 +469,41 @@ function Renderer(container) { | |||
params = {}; | |||
if (params.roll) | |||
roll = params.roll; | |||
// Apply pitch and roll transformation if applicable | |||
if (pose !== undefined) { | |||
var horizonPitch = pose[0], | |||
horizonRoll = pose[1]; | |||
// Calculate new pitch and yaw | |||
var orig_pitch = pitch, | |||
orig_yaw = yaw, | |||
x = Math.cos(horizonRoll) * Math.sin(pitch) * Math.sin(horizonPitch) + | |||
Math.cos(pitch) * (Math.cos(horizonPitch) * Math.cos(yaw) + | |||
Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.sin(yaw)), | |||
y = -Math.sin(pitch) * Math.sin(horizonRoll) + | |||
Math.cos(pitch) * Math.cos(horizonRoll) * Math.sin(yaw), | |||
z = Math.cos(horizonRoll) * Math.cos(horizonPitch) * Math.sin(pitch) + | |||
Math.cos(pitch) * (-Math.cos(yaw) * Math.sin(horizonPitch) + | |||
Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.sin(yaw)); | |||
pitch = Math.asin(Math.max(Math.min(z, 1), -1)); | |||
yaw = Math.atan2(y, x); | |||
// Calculate roll | |||
var v = [Math.cos(orig_pitch) * (Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.cos(orig_yaw) - | |||
Math.cos(horizonPitch) * Math.sin(orig_yaw)), | |||
Math.cos(orig_pitch) * Math.cos(horizonRoll) * Math.cos(orig_yaw), | |||
Math.cos(orig_pitch) * (Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.cos(orig_yaw) + | |||
Math.sin(orig_yaw) * Math.sin(horizonPitch))], | |||
w = [-Math.cos(pitch) * Math.sin(yaw), Math.cos(pitch) * Math.cos(yaw)]; | |||
var roll_adj = Math.acos(Math.max(Math.min((v[0]*w[0] + v[1]*w[1]) / | |||
(Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) * | |||
Math.sqrt(w[0]*w[0]+w[1]*w[1])), 1), -1)); | |||
if (v[2] < 0) | |||
roll_adj = 2 * Math.PI - roll_adj; | |||
roll += roll_adj; | |||
} | |||
// If no WebGL | |||
if (!gl && (imageType == 'multires' || imageType == 'cubemap')) { | |||
// Determine face transforms | |||
@@ -479,40 +536,6 @@ function Renderer(container) { | |||
var vfov = 2 * Math.atan(Math.tan(hfov * 0.5) / (canvas.width / canvas.height)); | |||
focal = 1 / Math.tan(vfov * 0.5); | |||
// Apply pitch and roll transformation if applicable | |||
if (imageType == 'equirectangular' && pose !== undefined) { | |||
var horizonPitch = pose[0], | |||
horizonRoll = pose[1]; | |||
// Calculate new pitch and yaw | |||
var orig_pitch = pitch, | |||
orig_yaw = yaw, | |||
x = Math.cos(horizonRoll) * Math.sin(pitch) * Math.sin(horizonPitch) + | |||
Math.cos(pitch) * (Math.cos(horizonPitch) * Math.cos(yaw) + | |||
Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.sin(yaw)), | |||
y = -Math.sin(pitch) * Math.sin(horizonRoll) + | |||
Math.cos(pitch) * Math.cos(horizonRoll) * Math.sin(yaw), | |||
z = Math.cos(horizonRoll) * Math.cos(horizonPitch) * Math.sin(pitch) + | |||
Math.cos(pitch) * (-Math.cos(yaw) * Math.sin(horizonPitch) + | |||
Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.sin(yaw)); | |||
pitch = Math.asin(Math.max(Math.min(z, 1), -1)); | |||
yaw = Math.atan2(y, x); | |||
// Calculate roll | |||
var v = [Math.cos(orig_pitch) * (Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.cos(orig_yaw) - | |||
Math.cos(horizonPitch) * Math.sin(orig_yaw)), | |||
Math.cos(orig_pitch) * Math.cos(horizonRoll) * Math.cos(orig_yaw), | |||
Math.cos(orig_pitch) * (Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.cos(orig_yaw) + | |||
Math.sin(orig_yaw) * Math.sin(horizonPitch))], | |||
w = [-Math.cos(pitch) * Math.sin(yaw), Math.cos(pitch) * Math.cos(yaw)]; | |||
var roll_adj = Math.acos(Math.max(Math.min((v[0]*w[0] + v[1]*w[1]) / | |||
(Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) * | |||
Math.sqrt(w[0]*w[0]+w[1]*w[1])), 1), -1)); | |||
if (v[2] < 0) | |||
roll_adj = 2 * Math.PI - roll_adj; | |||
roll += roll_adj; | |||
} | |||
// Pass psi, theta, roll, and focal length | |||
gl.uniform1f(program.psi, yaw); | |||
gl.uniform1f(program.theta, pitch); | |||
@@ -782,7 +805,7 @@ function Renderer(container) { | |||
} | |||
} | |||
// Handle small tiles that have fewer than four children | |||
if (doubleTileSize < image.tileResolution) { | |||
if (doubleTileSize <= image.tileResolution) { | |||
if (node.x == numTiles) { | |||
f1 = 0; | |||
i1 = 1; | |||
@@ -808,7 +831,7 @@ function Renderer(container) { | |||
]; | |||
ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2, image.fullpath); | |||
children.push(ntmp); | |||
if (!(node.x == numTiles && doubleTileSize < image.tileResolution)) { | |||
if (!(node.x == numTiles && doubleTileSize <= image.tileResolution)) { | |||
vtmp = [v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, | |||
v[3], v[4], v[5], | |||
v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, | |||
@@ -817,8 +840,8 @@ function Renderer(container) { | |||
ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2, image.fullpath); | |||
children.push(ntmp); | |||
} | |||
if (!(node.x == numTiles && doubleTileSize < image.tileResolution) && | |||
!(node.y == numTiles && doubleTileSize < image.tileResolution)) { | |||
if (!(node.x == numTiles && doubleTileSize <= image.tileResolution) && | |||
!(node.y == numTiles && doubleTileSize <= image.tileResolution)) { | |||
vtmp = [v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, | |||
v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, | |||
v[6], v[7], v[8], | |||
@@ -827,7 +850,7 @@ function Renderer(container) { | |||
ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2+1, image.fullpath); | |||
children.push(ntmp); | |||
} | |||
if (!(node.y == numTiles && doubleTileSize < image.tileResolution)) { | |||
if (!(node.y == numTiles && doubleTileSize <= image.tileResolution)) { | |||
vtmp = [ v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3, | |||
v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, | |||
v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3, | |||
@@ -981,10 +1004,11 @@ function Renderer(container) { | |||
var self = this; | |||
this.texture = this.callback = null; | |||
this.image = new Image(); | |||
this.image.crossOrigin = 'anonymous'; | |||
this.image.addEventListener('load', function() { | |||
processLoadedTexture(self.image, self.texture); | |||
releaseTextureImageLoader(self); | |||
self.callback(self.texture); | |||
releaseTextureImageLoader(self); | |||
}); | |||
}; | |||
@@ -1027,7 +1051,8 @@ function Renderer(container) { | |||
* @param {MultiresNode} node - Input node. | |||
*/ | |||
function processNextTile(node) { | |||
if (!node.texture) { | |||
if (!node.textureLoad) { | |||
node.textureLoad = true; | |||
loadTexture(encodeURI(node.path + '.' + image.extension), function(texture) { | |||
node.texture = texture; | |||
node.textureLoaded = true; | |||
@@ -1173,56 +1198,7 @@ var vMulti = [ | |||
].join(''); | |||
// Fragment shader | |||
var fragCube = [ | |||
'precision mediump float;', | |||
'uniform float u_aspectRatio;', | |||
'uniform float u_psi;', | |||
'uniform float u_theta;', | |||
'uniform float u_f;', | |||
'uniform float u_h;', | |||
'uniform float u_v;', | |||
'uniform float u_vo;', | |||
'uniform float u_rot;', | |||
'const float PI = 3.14159265358979323846264;', | |||
// Texture | |||
'uniform samplerCube u_image;', | |||
// Coordinates passed in from vertex shader | |||
'varying vec2 v_texCoord;', | |||
'void main() {', | |||
// Find the vector of focal point to view plane | |||
'vec3 planePos = vec3(v_texCoord.xy, 0.0);', | |||
'planePos.x *= u_aspectRatio;', | |||
'float sinrot = sin(u_rot);', | |||
'float cosrot = cos(u_rot);', | |||
'vec3 rotPos = vec3(planePos.x * cosrot - planePos.y * sinrot, planePos.x * sinrot + planePos.y * cosrot, 0.0);', | |||
'vec3 viewVector = rotPos - vec3(0.0, 0.0, -u_f);', | |||
// Rotate vector for psi (yaw) and theta (pitch) | |||
'float sinpsi = sin(-u_psi);', | |||
'float cospsi = cos(-u_psi);', | |||
'float sintheta = sin(u_theta);', | |||
'float costheta = cos(u_theta);', | |||
// Now apply the rotations | |||
'vec3 viewVectorTheta = viewVector;', | |||
'viewVectorTheta.z = viewVector.z * costheta - viewVector.y * sintheta;', | |||
'viewVectorTheta.y = viewVector.z * sintheta + viewVector.y * costheta;', | |||
'vec3 viewVectorPsi = viewVectorTheta;', | |||
'viewVectorPsi.x = viewVectorTheta.x * cospsi - viewVectorTheta.z * sinpsi;', | |||
'viewVectorPsi.z = viewVectorTheta.x * sinpsi + viewVectorTheta.z * cospsi;', | |||
// Look up color from texture | |||
'gl_FragColor = textureCube(u_image, viewVectorPsi);', | |||
'}' | |||
].join('\n'); | |||
// Fragment shader | |||
var fragEquirectangular = [ | |||
var fragEquiCubeBase = [ | |||
'precision mediump float;', | |||
'uniform float u_aspectRatio;', | |||
@@ -1238,10 +1214,14 @@ var fragEquirectangular = [ | |||
// Texture | |||
'uniform sampler2D u_image;', | |||
'uniform samplerCube u_imageCube;', | |||
// Coordinates passed in from vertex shader | |||
'varying vec2 v_texCoord;', | |||
// Background color (display for partial panoramas) | |||
'uniform vec4 u_backgroundColor;', | |||
'void main() {', | |||
// Map canvas/camera to sphere | |||
'float x = v_texCoord.x * u_aspectRatio;', | |||
@@ -1256,20 +1236,28 @@ var fragEquirectangular = [ | |||
'float root = sqrt(rot_x * rot_x + a * a);', | |||
'float lambda = atan(rot_x / root, a / root) + u_psi;', | |||
'float phi = atan((rot_y * costheta + u_f * sintheta) / root);', | |||
].join('\n'); | |||
// Fragment shader | |||
var fragCube = fragEquiCubeBase + [ | |||
// Look up color from texture | |||
'float cosphi = cos(phi);', | |||
'gl_FragColor = textureCube(u_imageCube, vec3(cosphi*sin(lambda), sin(phi), cosphi*cos(lambda)));', | |||
'}' | |||
].join('\n'); | |||
// Fragment shader | |||
var fragEquirectangular = fragEquiCubeBase + [ | |||
// Wrap image | |||
'if(lambda > PI)', | |||
'lambda = lambda - PI * 2.0;', | |||
'if(lambda < -PI)', | |||
'lambda = lambda + PI * 2.0;', | |||
'lambda = mod(lambda + PI, PI * 2.0) - PI;', | |||
// Map texture to sphere | |||
'vec2 coord = vec2(lambda / PI, phi / (PI / 2.0));', | |||
// Look up color from texture | |||
// Map from [-1,1] to [0,1] and flip y-axis | |||
'if(coord.x < -u_h || coord.x > u_h || coord.y < -u_v + u_vo || coord.y > u_v + u_vo)', | |||
'gl_FragColor = vec4(0, 0, 0, 1.0);', | |||
'gl_FragColor = u_backgroundColor;', | |||
'else', | |||
'gl_FragColor = texture2D(u_image, vec2((coord.x + u_h) / (u_h * 2.0), (-coord.y + u_v + u_vo) / (u_v * 2.0)));', | |||
'}' | |||
@@ -7,8 +7,14 @@ function anError(error) { | |||
var viewer; | |||
function parseURLParameters() { | |||
var URL = decodeURI(window.location.href).split('?'); | |||
URL.shift(); | |||
var URL; | |||
if (window.location.hash.length > 0) { | |||
// Prefered method since parameters aren't sent to server | |||
URL = [window.location.hash.slice(1)]; | |||
} else { | |||
URL = decodeURI(window.location.href).split('?'); | |||
URL.shift(); | |||
} | |||
if (URL.length < 1) { | |||
// Display error if no configuration parameters are specified | |||
anError('No configuration options were specified.'); | |||
@@ -30,23 +36,19 @@ function parseURLParameters() { | |||
case 'autoLoad': case 'ignoreGPanoXMP': | |||
configFromURL[option] = JSON.parse(value); | |||
break; | |||
case 'tour': | |||
console.log('The `tour` parameter is deprecated and will be removed. Use the `config` parameter instead.') | |||
case 'author': case 'title': case 'firstScene': case 'fallback': | |||
case 'preview': case 'panorama': case 'config': | |||
configFromURL[option] = decodeURIComponent(value); | |||
break; | |||
default: | |||
anError('An invalid configuration parameter was specified: ' + option); | |||
return; | |||
} | |||
} | |||
var request; | |||
// Check for JSON configuration file | |||
if (configFromURL.tour) { | |||
configFromURL.config = configFromURL.tour; | |||
} | |||
if (configFromURL.config) { | |||
// Get JSON configuration file | |||
request = new XMLHttpRequest(); | |||
@@ -63,7 +65,8 @@ function parseURLParameters() { | |||
var responseMap = JSON.parse(request.responseText); | |||
// Set JSON file location | |||
responseMap.basePath = configFromURL.config.substring(0, configFromURL.config.lastIndexOf('/')+1); | |||
if (responseMap.basePath === undefined) | |||
responseMap.basePath = configFromURL.config.substring(0, configFromURL.config.lastIndexOf('/')+1); | |||
// Merge options | |||
for (var key in responseMap) { | |||
@@ -78,6 +81,7 @@ function parseURLParameters() { | |||
document.title = configFromURL.title; | |||
// Create viewer | |||
configFromURL.escapeHTML = true; | |||
viewer = pannellum.viewer('container', configFromURL); | |||
}; | |||
request.open('GET', configFromURL.config); | |||
@@ -90,6 +94,7 @@ function parseURLParameters() { | |||
document.title = configFromURL.title; | |||
// Create viewer | |||
configFromURL.escapeHTML = true; | |||
pannellum.viewer('container', configFromURL); | |||
} | |||
@@ -98,7 +98,7 @@ def build(files, css, html, filename, release=False): | |||
js = merge(files) | |||
if release: | |||
version = read('../VERSION') | |||
version = read('../VERSION').strip() | |||
else: | |||
version = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('utf-8').strip() | |||
js = js.replace('"_blank">Pannellum</a>','"_blank">Pannellum</a> ' + version) | |||
@@ -4,7 +4,7 @@ | |||
# and nona (from Hugin) | |||
# generate.py - A multires tile set generator for Pannellum | |||
# Copyright (c) 2014-2016 Matthew Petroff | |||
# Copyright (c) 2014-2017 Matthew Petroff | |||
# | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documentation files (the "Software"), to deal | |||
@@ -34,8 +34,12 @@ import math | |||
from distutils.spawn import find_executable | |||
import subprocess | |||
# find external programs | |||
nona = find_executable('nona') | |||
# Find external programs | |||
try: | |||
nona = find_executable('nona') | |||
except KeyError: | |||
# Handle case of PATH not being set | |||
nona = None | |||
# Parse input | |||
parser = argparse.ArgumentParser(description='Generate a Pannellum multires tile set from an full equirectangular panorama.', | |||
@@ -1,26 +1,25 @@ | |||
/* | |||
* Video.js plugin for Pannellum | |||
* Copyright (c) 2015-2016 Matthew Petroff | |||
* Copyright (c) 2015-2017 Matthew Petroff | |||
* MIT License | |||
*/ | |||
(function(document, videojs, pannellum) { | |||
'use strict'; | |||
videojs.plugin('pannellum', function() { | |||
videojs.plugin('pannellum', function(config) { | |||
// Create Pannellum instance | |||
var player = this; | |||
var container = player.el(); | |||
var vid = container.getElementsByTagName('video')[0], | |||
pnlmContainer = document.createElement('div'), | |||
config = { | |||
'type': 'equirectangular', | |||
'dynamic': true, | |||
'showZoomCtrl': false, | |||
'showFullscreenCtrl': false, | |||
'autoLoad': true, | |||
'panorama': vid | |||
}; | |||
pnlmContainer = document.createElement('div'); | |||
config = config || {}; | |||
config.type = 'equirectangular'; | |||
config.dynamic = true; | |||
config.showZoomCtrl = false; | |||
config.showFullscreenCtrl = false; | |||
config.autoLoad = true; | |||
config.panorama = vid; | |||
pnlmContainer.style.visibility = 'hidden'; | |||
var pnlm = pannellum.viewer(pnlmContainer, config); | |||
container.insertBefore(pnlmContainer, container.firstChild); | |||