Browse Source

Merge branch 'master' into cirspecte-adoptions

pull/880/head
Nico Höllerich 5 years ago
parent
commit
57c67c7b3d
22 changed files with 655 additions and 153 deletions
  1. +1
    -0
      .github/FUNDING.yml
  2. +6
    -0
      .gitignore
  3. +13
    -0
      .travis.yml
  4. +1
    -1
      COPYING
  5. +1
    -1
      VERSION
  6. +84
    -24
      changelog.md
  7. +16
    -1
      doc/json-config-parameters.md
  8. +1
    -1
      package.json
  9. +100
    -0
      paper/paper.bib
  10. +62
    -0
      paper/paper.md
  11. +17
    -15
      readme.md
  12. +1
    -1
      src/css/pannellum.css
  13. +0
    -22
      src/js/RequestAnimationFrame.js
  14. +20
    -11
      src/js/libpannellum.js
  15. +103
    -73
      src/js/pannellum.js
  16. +0
    -1
      src/standalone/pannellum.htm
  17. BIN
      tests/cube.png
  18. BIN
      tests/equirectangular.png
  19. BIN
      tests/multires.png
  20. +141
    -0
      tests/run_tests.py
  21. +87
    -0
      tests/tests.html
  22. +1
    -2
      utils/build/build.py

+ 1
- 0
.github/FUNDING.yml View File

@@ -0,0 +1 @@
ko_fi: mpetroff

+ 6
- 0
.gitignore View File

@@ -9,3 +9,9 @@ utils/doc/generated_docs

# Ignore IntelliJ Files
.idea

# Ignore logs
tests/*.log

# Ignore tests
tests/**

+ 13
- 0
.travis.yml View File

@@ -0,0 +1,13 @@
language: generic
dist: xenial
addons:
sauce_connect: true
before_install:
- sudo apt-get install -y python3-pillow python3-numpy python3-pip
- sudo pip3 install selenium
jobs:
include:
- stage: build
script: python3 utils/build/build.py
- stage: test
script: python3 tests/run_tests.py

+ 1
- 1
COPYING View File

@@ -1,4 +1,4 @@
Copyright (c) 2011-2018 Matthew Petroff
Copyright (c) 2011-2019 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
VERSION View File

@@ -1 +1 @@
2.4.1
2.5.2

+ 84
- 24
changelog.md View File

@@ -2,8 +2,68 @@ Changelog
=========


Changes in Pannellum 2.4.1
--------------------------
Changes in Pannellum 2.5.2 (2019-07-13)
---------------------------------------

Bugfixes:
- Fixed regression in Video.js plugin


Changes in Pannellum 2.5.1 (2019-07-13)
---------------------------------------

Other:
- Fixed issue with tagging 2.5.0 release


Changes in Pannellum 2.5.0 (2019-07-13)
---------------------------------------

New Features:
- The background color can be set for partial panoramas
(`backgroundColor` parameter)
- Partial panoramas are now supported for the multiresolution format
- An author URL can now be added (`authorURL` parameter)

New API functions:
- Added `fullscreenchange`, `zoomchange`, and `animatefinished` events
- Added `stopMovement` function for stopping all viewer movement

Improvements:
- Equirectangular images are now automatically split into two separate
textures if they're too big (images up to 8192px wide should now be
widely supported)
- Improved render quality for equirectangular images on mobile (using `highp`
for fragment shader)
- Keyboard events for keys not used by the viewer are no longer captured, and
the list of captured keys is configurable (`capturedKeyNumbers` parameter)
- Multiresolution tiles can now be generated from cylindrical panoramas
- Hot spots can now be removed from scenes that aren't currently loaded
- Hot spot cursor is now set via CSS class (so it can be overridden)
- Hot spot link attributes can now be set (`attributes` parameter)
- The "friction" that slows down the viewer motion can now be configured
(`friction` parameter)
- Dynamic scenes are now properly supported for tours

Bugfixes:
- Fixed regression in fallback renderer
- Fixed bug with URL encoding
- Fixed regression in Video.js plugin
- Fixed auto-rotate bug that was manifested when using API to set view
- Fixed full screen bug in Chrome
- Fixed bug with removing event listeners
- Fixed issue with mouse dragging causing jump around yaw limits
- Fixed bug with deleting hot spots
- Fixed bug with fading between scenes

Other:
- Added limited test suite / continuous integration
- Removed `requestAnimationFrame` shim, dropping support for some
older browsers


Changes in Pannellum 2.4.1 (2018-03-03)
---------------------------------------

Bugfixes:

@@ -11,8 +71,8 @@ Bugfixes:
- The API's `loadScene` method now works when no scenes have been loaded yet


Changes in Pannellum 2.4.0
--------------------------
Changes in Pannellum 2.4.0 (2018-01-30)
---------------------------------------

New Features:

@@ -77,16 +137,16 @@ Bugfixes:
- Fixed bug related to removing hot spots


Changes in Pannellum 2.3.2
--------------------------
Changes in Pannellum 2.3.2 (2016-11-20)
---------------------------------------

Bugfixes:

- Fix Chrome fullscreen regression introduced in 2.3.1


Changes in Pannellum 2.3.1
--------------------------
Changes in Pannellum 2.3.1 (2016-11-19)
---------------------------------------

Bugfixes:

@@ -99,8 +159,8 @@ Improvements:
- Better handling of view limits when both limits are in view


Changes in Pannellum 2.3.0
--------------------------
Changes in Pannellum 2.3.0 (2016-10-30)
---------------------------------------

New Features:

@@ -169,8 +229,8 @@ Backwards-Incompatible Configuration Parameter Changes:
can be used with the `config` parameter


Changes in Pannellum 2.2.1
--------------------------
Changes in Pannellum 2.2.1 (2016-03-11)
---------------------------------------

New Features:

@@ -183,8 +243,8 @@ Improvements:
- Better restriction on yaw range


Changes in Pannellum 2.2.0
--------------------------
Changes in Pannellum 2.2.0 (2016-01-27)
---------------------------------------

New Features:

@@ -251,15 +311,15 @@ Other:
- Added JSDoc documentation


Changes in Pannellum 2.1.1
--------------------------
Changes in Pannellum 2.1.1 (2015-01-19)
---------------------------------------

Bugfixes:
- Force subpixel rendering for hot spots


Changes in Pannellum 2.1.0
--------------------------
Changes in Pannellum 2.1.0 (2015-01-14)
---------------------------------------

New Features:

@@ -301,15 +361,15 @@ Other:
removed


Changes in Pannellum 2.0.1
--------------------------
Changes in Pannellum 2.0.1 (2014-08-24)
---------------------------------------

Bugfixes:
- Fix keyboard controls in Safari


Changes in Pannellum 2.0
------------------------
Changes in Pannellum 2.0 (2014-08-22)
-------------------------------------

New Features:

@@ -334,8 +394,8 @@ Bugfixes:
- Numerous


Changes in Pannellum 1.2
------------------------
Changes in Pannellum 1.2 (2012-08-28)
-------------------------------------

New Features:



+ 16
- 1
doc/json-config-parameters.md View File

@@ -23,6 +23,13 @@ If set, the value is displayed as the panorama's author. If no author is
desired, don't set this parameter.


### `authorURL` (string)

If set, the displayed author text is hyperlinked to this URL. If no author URL
is desired, don't set this parameter. The `author` parameter must also be set
for this parameter to have an effect.


### `strings` (dictionary)

Allows user-facing strings to be changed / translated.
@@ -52,7 +59,8 @@ counter-clockwise, and negative is clockwise.

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.
`autoRotate` parameter is set. Before starting rotation, the viewer is panned
to the initial pitch.


### `autoRotateStopDelay` (number)
@@ -308,6 +316,13 @@ 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.

#### `scale` (boolean)

When `true`, the hot spot is scaled to match changes in the field of view,
relative to the initial field of view. Note that this does not account for
changes in local image scale that occur due to distortions within the viewport.
Defaults to `false`.

### `hotSpotDebug` (boolean)

When `true`, the mouse pointer's pitch and yaw are logged to the console when


+ 1
- 1
package.json View File

@@ -1,7 +1,7 @@
{
"name": "pannellum",
"description": "Pannellum is a lightweight, free, and open source panorama viewer for the web.",
"version": "2.4.1",
"version": "2.5.2",
"bugs": {
"url": "https://github.com/mpetroff/pannellum/issues"
},


+ 100
- 0
paper/paper.bib View File

@@ -0,0 +1,100 @@
@inproceedings{Chen1995,
doi = {10.1145/218380.218395},
year = {1995},
publisher = {{ACM} Press},
author = {Shenchang Eric Chen},
title = {{QuickTime} {VR}},
editor = {Susan G. Mair and Robert Cook},
booktitle = {Proceedings of the 22nd annual conference on computer graphics and interactive techniques ({SIGGRAPH} '95)},
}

@inproceedings{Gede2015,
author = {M\'{a}ty\'{a}s Gede and Zsuzsanna Ungv\'{a}ri and Klaudia Kiss and G\'{a}bor Nagy},
title = {Open-source web-based viewer application for {TLS} surveys in caves},
booktitle = "Proceedings of the 1st {ICA} {European} Symposium on Cartography ({EuroCarto} 2015)",
year = {2015},
editor = {Georg Gartner and Haosheng Huang},
pages = {321--328},
url = {https://cartography.tuwien.ac.at/eurocarto/wp-content/uploads/2015/10/6-7.pdf},
urldate = {2019-07-14},
isbn = {9781907075032},
}

@article{Srinivasan2018,
doi = {10.23925/1980-7651.2018v21;p71-83},
year = {2018},
month = jun,
publisher = {Portal de Revistas {PUC} {SP}},
volume = {21},
pages = {71},
author = {Venkat Srinivasan and T.B. Dinesh and Bhanu Prakash and A. Shalini},
title = {Thirteen ways of looking at institutional history: a model for digital exhibitions from science archives},
journal = {Circumscribere: International Journal for the History of Science},
}

@article{Herault2018,
doi = {10.1186/s40561-018-0074-x},
year = {2018},
month = oct,
publisher = {Springer Nature},
volume = {5},
number = {1},
author = {Romain Christian Herault and Alisa Lincke and Marcelo Milrad and Elin-Sofie Forsg\"{a}rde and Carina Elmqvist},
title = {Using 360-degrees interactive videos in patient trauma treatment education: design, development and evaluation aspects},
journal = {Smart Learning Environments},
}

@incollection{Mohr2018,
doi = {10.1007/978-3-030-04028-4_71},
year = {2018},
publisher = {Springer International Publishing},
pages = {613--620},
author = {Fabian Mohr and Soenke Zehle and Michael Schmitz},
editor = {Rebecca Rouse and Hartmut Koenitz and Mads Haahr},
title = {From Co-Curation to Co-Creation: Users as Collective Authors of Archive-Based Cultural Heritage Narratives},
booktitle = {Interactive Storytelling},
}

@inproceedings{Albrizio2013,
author = {Patrizia Albrizio and Francesco de Virgilio and Ginevra Panzarino and Enrica Zambetta},
title = {WebGIS e divulgazione del dato archeologico con software open source. Il progetto ``Siponto Aperta''},
booktitle = {Proceedings of {ArcheoFOSS}: free, libre and open source software e open format nei processi di ricerca archeologica},
year = {2013},
editor = {Filippo Stanco and Giovanni Gallo},
pages = {101--114},
edition = {8th},
url = {https://www.archaeopress.com/ArchaeopressShop/Public/download.asp?id=%7B14C6CFBD-3371-4DF0-8971-D4ABC24E661E%7D},
urldate = {2019-07-14},
isbn = {9781784912598},
}

@online{ESO2017,
author = {{European Southern Observatory}},
title = {A panorama view of the {VLT}},
year = {2017},
month = sep,
url = {https://www.eso.org/public/images/165309674464e758889a6_eq-ext/},
urldate = {2019-07-14},
}

@techreport{WebGL,
author = {Dean Jackson},
title = {{WebGL} Specification},
month = oct,
url = {https://www.khronos.org/registry/webgl/specs/1.0.3/},
urldate = {2019-07-14},
year = {2014},
type = {Khronos Specification},
institution = {Khronos Group},
}

@techreport{Canvas,
author = {Rik Cabanier and Jatinder Mann and Jay Munro and Tom Wiltzius and Ian Hickson},
title = {{HTML} Canvas {2D} Context},
month = nov,
url = {https://www.w3.org/TR/2015/REC-2dcontext-20151119/},
urldate = {2019-07-14},
year = {2015},
type = {{W3C} Recommendation},
institution = {World Wide Web Consortium ({W3C})},
}

+ 62
- 0
paper/paper.md View File

@@ -0,0 +1,62 @@
---
title: 'Pannellum: a lightweight web-based panorama viewer'
tags:
- panoramas
- visualization
- WebGL
authors:
- name: Matthew A. Petroff
orcid: 0000-0002-4436-4215
affiliation: 1
affiliations:
- name: Department of Physics & Astronomy, Johns Hopkins University, Baltimore, Maryland 21218, USA
index: 1
date: 15 July 2019
bibliography: paper.bib
---

# Summary

_Pannellum_ is an interactive web browser-based panorama viewer written in
JavaScript and primarily based on the WebGL web standard [@WebGL] for graphics
processing unit (GPU)-accelerated rendering to the HTML5 ``<canvas>`` element
[@Canvas]. It supports the display of panoramic images that cover the full
sphere, or only parts of it, in equirectangular format, in cube map format, or
in a tiled format that encodes the panorama in multiple resolutions, which
allows for parts of the panorama to be dynamically loaded, reducing data
transfer requirements. In addition to single panoramas, multiple panoramas can
be linked together into a virtual tour, with navigation enabled via
"hot spots," which can also be used to add annotations.

The display of interactive panoramic images on the web dates back to the
mid-1990s, with the development of Apple's QuickTime VR format and associated
web browser plug-ins [@Chen1995]. When development on _Pannellum_ started in
2011, WebGL was a nascent technology, and the majority of existing panorama
viewers for websites were then still based on Java or Adobe Flash plug-ins,
which had supplanted QuickTime as the technology of choice. Since then, both
the viewer and underlying technologies have matured immensely.

An application programming interface (API) is provided, which allows external
code to control the viewer and implement features such as custom buttons or
integration with other web page elements, e.g., maps [@Gede2015; @Albrizio2013].
Panoramic videos are supported via a bundled extension, which is built using
the API. The underlying rendering code is separate from the user interface
code, which allows for more extensive customization and tighter integration
with external code, if desired. This rendering code uses a pinhole camera model
for equirectangular panoramas implemented as a WebGL fragment shader, instead
of the more common---and less accurate---approach of mapping the panorama onto
a geometric approximation of a sphere.

_Pannellum_ has proven useful in various fields, when the display of panoramic
images is needed to help digest or present information. These research
applications range from cartography [@Gede2015] to digital humanities
[@Srinivasan2018; @Mohr2018] to archaeology [@Albrizio2013] to medical
education [@Herault2018]. It has also found use in public outreach
applications, such as its use by the European Southern Observatory to display
panoramas of their observatories [@ESO2017]. _Pannellum_ is intended to be used
any time an interactive panorama needs to be displayed in a web page, be it an
internal research application or a publicly accessible website. It may also
work with certain mobile application frameworks, but such use is not officially
supported.

# References

+ 17
- 15
readme.md View File

@@ -1,8 +1,11 @@
# Pannellum

[![Build Status](https://travis-ci.org/mpetroff/pannellum.svg?branch=master)](https://travis-ci.org/mpetroff/pannellum)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3334433.svg)](https://doi.org/10.5281/zenodo.3334433)

## About

Pannellum is a lightweight, free, and open source panorama viewer for the web. Built using HTML5, CSS3, JavaScript, and WebGL, it is plug-in free. It can be deployed easily as a single file, just 15kB gzipped, and then embedded into pages as an `<iframe>`. A configuration utility is included to generate the required code for embedding.
Pannellum is a lightweight, free, and open source panorama viewer for the web. Built using HTML5, CSS3, JavaScript, and WebGL, it is plug-in free. It can be deployed easily as a single file, just 21kB gzipped, and then embedded into pages as an `<iframe>`. A configuration utility is included to generate the required code for embedding. An API is included for more advanced integrations.

## How to use
1. Upload `build/pannellum.htm` and a full equirectangular panorama to a web server.
@@ -10,6 +13,8 @@ Pannellum is a lightweight, free, and open source panorama viewer for the web. B
2. Use the included multi-resolution generator (`utils/multires/generate.py`) or configuration tool (`utils/config/configuration.htm`).
3. Insert the generated `<iframe>` code into a page.

Configuration parameters are documented in the `doc/json-config-parameters.md` file, which is also available at [pannellum.org/documentation/reference/](https://pannellum.org/documentation/reference). API methods are documented inline with [JSDoc](https://jsdoc.app/) comments, and generated documentation is available at [pannellum.org/documentation/api/](https://pannellum.org/documentation/api/).

### Using `generate.py` to create multires panoramas
To be able to create multiresolution panoramas, you need to have the `nona` program installed, which is available as part of [Hugin](http://hugin.sourceforge.net/), as well as Python with the [Pillow](https://pillow.readthedocs.org/) package. Then, run

@@ -21,29 +26,20 @@ in the `utils/multires` directory. This will generate all the image tiles and th

## Examples

Examples using both the minified version and the version in the `src` directory are included in the `examples` directory.
Examples using both the minified version and the version in the `src` directory are included in the `examples` directory. Additional examples are available at [pannellum.org](https://pannellum.org/documentation/examples/simple-example/).

## Browser Compatibility

Since Pannellum is built with recent web standards, it requires a modern browser to function.
Since Pannellum is built with web standards, it requires a modern browser to function.

#### Full support (with appropriate graphics drivers):
* Firefox 10+
* Chrome 15+
* Firefox 23+
* Chrome 24+
* Safari 8+
* Internet Explorer 11+
* Edge

#### Almost full support (no full screen):
* Firefox 4+
* Chrome 9+

#### Partial support (WebGL support must first be enabled in preferences)

* Safari 5.1+

#### No support:
Internet Explorer 10 and previous
The support list is based on feature support. As only recent browsers are tested, there may be regressions in older browsers.

#### Not officially supported:

@@ -56,6 +52,12 @@ All user-facing strings can be changed using the `strings` configuration paramet
## Building
The `utils` folder contains the required build tools, with the exception of Python 3.2+ and Java installations. To build a minified version of Pannellum, run either `build.sh` or `build.bat` depending on your platform.

## Tests
A limited [Selenium](https://www.seleniumhq.org/)-based test suite is located in the `tests` directory. The tests can be executed by running `python3 run_tests.py`. Running the tests requires Python 3, the Selenium Python bindings, Firefox, [geckodriver](https://github.com/mozilla/geckodriver), [Pillow](https://pillow.readthedocs.io/), and [NumPy](https://www.numpy.org/).

## Contributing
Development takes place at [github.com/mpetroff/pannellum](https://github.com/mpetroff/pannellum). Issues should be opened to report bugs or suggest improvements (or ask questions), and pull requests are welcome. When reporting a bug, please try to include a minimum reproducible example (or at least some sort of example). When proposing changes, please try to match the existing code style, e.g., four space indentation and [JSHint](https://jshint.com/) validation. If your pull request adds an additional configuration parameter, please document it in `doc/json-config-parameters.md`.

## License
Pannellum is distributed under the MIT License. For more information, read the file `COPYING` or peruse the license [online](https://github.com/mpetroff/pannellum/blob/master/COPYING).



+ 1
- 1
src/css/pannellum.css View File

@@ -287,7 +287,7 @@
table-layout: fixed;
}

.pnlm-info-box a {
.pnlm-info-box a, .pnlm-author-box a {
color: #fff;
word-wrap: break-word;
overflow-wrap: break-word;


+ 0
- 22
src/js/RequestAnimationFrame.js View File

@@ -1,22 +0,0 @@
/**
* Provides requestAnimationFrame in a cross browser way.
* http://paulirish.com/2011/requestanimationframe-for-smart-animating/
*/

if ( !window.requestAnimationFrame ) {

window.requestAnimationFrame = ( function() {

return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {

window.setTimeout( callback, 1000 / 60 );

};

} )();

}

+ 20
- 11
src/js/libpannellum.js View File

@@ -1,6 +1,6 @@
/*
* libpannellum - A WebGL and CSS 3D transform based Panorama Renderer
* Copyright (c) 2012-2018 Matthew Petroff
* Copyright (c) 2012-2019 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
@@ -270,9 +270,9 @@ function Renderer(container) {
faceImg.onload = onLoad;
faceImg.onerror = incLoaded; // ignore missing face to support partial fallback image
if (imageType == 'multires') {
faceImg.src = encodeURI(path.replace('%s', sides[s]) + '.' + image.extension);
faceImg.src = path.replace('%s', sides[s]) + '.' + image.extension;
} else {
faceImg.src = encodeURI(image[s].src);
faceImg.src = image[s].src;
}
}
fillMissingFaces(fallbackImgSize);
@@ -308,9 +308,9 @@ function Renderer(container) {
}
} else if (imageType == 'cubemap') {
if (cubeImgWidth > gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)) {
console.log('Error: The image is too big; it\'s ' + width + 'px wide, '+
console.log('Error: The image is too big; it\'s ' + cubeImgWidth + 'px wide, ' +
'but this device\'s maximum supported size is ' + maxWidth + 'px.');
throw {type: 'webgl size error', width: width, maxWidth: maxWidth};
throw {type: 'webgl size error', width: cubeImgWidth, maxWidth: maxWidth};
}
}

@@ -325,6 +325,15 @@ function Renderer(container) {
// Create viewport for entire canvas
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

// Check precision support
if (gl.getShaderPrecisionFormat) {
var precision = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
if (precision && precision.precision < 1) {
// `highp` precision not supported; https://stackoverflow.com/a/33308927
fragEquiCubeBase = fragEquiCubeBase.replace('highp', 'mediump');
}
}

// Create vertex shader
vs = gl.createShader(gl.VERTEX_SHADER);
var vertexSrc = v;
@@ -761,9 +770,9 @@ function Renderer(container) {
program.nodeCache.length > program.currentNodes.length + 50) {
// Remove older nodes from cache
var removed = program.nodeCache.splice(200, program.nodeCache.length - 200);
for (var i = 0; i < removed.length; i++) {
for (var j = 0; j < removed.length; j++) {
// Explicitly delete textures
gl.deleteTexture(removed[i].texture);
gl.deleteTexture(removed[j].texture);
}
}
program.currentNodes = [];
@@ -1591,7 +1600,7 @@ function Renderer(container) {
});
this.image.addEventListener('load', loadFn);
this.image.addEventListener('error', loadFn); // ignore missing tile file to support partial image, otherwise retry loop causes high CPU load
};
}

TextureImageLoader.prototype.loadTexture = function(node, src, texture, callback) {
this.texture = texture;
@@ -1616,7 +1625,7 @@ function Renderer(container) {
this.src = src;
this.texture = texture;
this.callback = callback;
};
}

function releaseTextureImageLoader(til) {
if (pendingTextureRequests.length) {
@@ -1646,7 +1655,7 @@ function Renderer(container) {
* @param {MultiresNode} node - Input node.
*/
function processNextTile(node) {
loadTexture(node, image.loader || node.uri, function (texture, loaded) {
loadTexture(node, image.loader || node.path + '.' + image.extension, function (texture, loaded) {
node.texture = texture;
node.textureLoaded = loaded ? 2 : 1;

@@ -1844,7 +1853,7 @@ var vMulti = [

// Fragment shader
var fragEquiCubeBase = [
'precision mediump float;',
'precision highp float;', // mediump looks bad on some mobile devices

'uniform float u_aspectRatio;',
'uniform float u_psi;',


+ 103
- 73
src/js/pannellum.js View File

@@ -1,6 +1,6 @@
/*
* Pannellum - An HTML5 based Panorama Viewer
* Copyright (c) 2011-2018 Matthew Petroff
* Copyright (c) 2011-2019 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
@@ -137,7 +137,7 @@ defaultConfig.strings = {
'%spx wide. Try another device.' +
' (If you\'re the author, try scaling down the image.)', // Two substitutions: image width, max image width
unknownError: 'Unknown error. Check developer console.',
}
};

// Initialize container
container = typeof container === 'string' ? document.getElementById(container) : container;
@@ -341,6 +341,7 @@ function init() {
} else {
if (config.panorama === undefined) {
anError(config.strings.noPanoramaError);
loaded = undefined;
return;
}
panoImage = new Image();
@@ -458,6 +459,13 @@ function init() {
if (config.draggable)
uiContainer.classList.add('pnlm-grab');
uiContainer.classList.remove('pnlm-grabbing');

// Properly handle switching to dynamic scenes
update = config.dynamicUpdate === true;
if (config.dynamic && update) {
panoImage = config.panorama;
onImageLoad();
}
}

/**
@@ -469,7 +477,7 @@ function init() {
function absoluteURL(url) {
// From http://stackoverflow.com/a/19709846
return new RegExp('^(?:[a-z]+:)?//', 'i').test(url) || url[0] == '/' || url.slice(0, 5) == 'blob:';
};
}

/**
* Create renderer and initialize event listeners once image is loaded.
@@ -649,6 +657,7 @@ function clearError() {
infoDisplay.load.box.style.display = 'none';
infoDisplay.errorMsg.style.display = 'none';
error = false;
renderContainer.style.display = 'block';
fireEvent('errorcleared');
}
}
@@ -1250,12 +1259,11 @@ function keyRepeat() {
latestInteraction = Date.now();

// If auto-rotate
var inactivityInterval = Date.now() - latestInteraction;
if (config.autoRotate) {
// Pan
if (newTime - prevTime > 0.001) {
var timeDiff = (newTime - prevTime) / 1000;
var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff
var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff;
yawDiff = (-config.autoRotate > 0 ? 1 : -1) * Math.min(Math.abs(config.autoRotate * timeDiff), Math.abs(yawDiff));
config.yaw += yawDiff;
}
@@ -1439,6 +1447,17 @@ function render() {
var tmpyaw;

if (loaded) {
var canvas = renderer.getCanvas();

if (config.autoRotate !== false) {
// When auto-rotating this check needs to happen first (see issue #764)
if (config.yaw > 180) {
config.yaw -= 360;
} else if (config.yaw < -180) {
config.yaw += 360;
}
}

// Keep a tmp value of yaw for autoRotate comparison later
tmpyaw = config.yaw;

@@ -1446,8 +1465,7 @@ function render() {
var hoffcut = 0,
voffcut = 0;
if (config.avoidShowingBackground) {
var canvas = renderer.getCanvas(),
hfov2 = config.hfov / 2,
var 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) {
@@ -1473,10 +1491,14 @@ function render() {
config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw));
}
if (config.yaw > 180) {
config.yaw -= 360;
} else if (config.yaw < -180) {
config.yaw += 360;
if (!(config.autoRotate !== false)) {
// When not auto-rotating, this check needs to happen after the
// previous check (see issue #698)
if (config.yaw > 180) {
config.yaw -= 360;
} else if (config.yaw < -180) {
config.yaw += 360;
}
}

// Check if we autoRotate in a limited by min and max yaw
@@ -1487,7 +1509,6 @@ function render() {
}

// Ensure the calculated pitch is within min and max allowed
var canvas = renderer.getCanvas();
var vfov = config.vfov;
var minPitch = config.minPitch + vfov / 2,
maxPitch = config.maxPitch - vfov / 2;
@@ -1541,7 +1562,7 @@ Quaternion.prototype.multiply = function(q) {
this.x*q.w + this.w*q.x + this.y*q.z - this.z*q.y,
this.y*q.w + this.w*q.y + this.z*q.x - this.x*q.z,
this.z*q.w + this.w*q.z + this.x*q.y - this.y*q.x);
}
};

/**
* Converts quaternion to Euler angles.
@@ -1555,7 +1576,7 @@ Quaternion.prototype.toEulerAngles = function() {
psi = Math.atan2(2 * (this.w * this.z + this.x * this.y),
1 - 2 * (this.y * this.y + this.z * this.z));
return [phi, theta, psi];
}
};

/**
* Converts device orientation API Tait-Bryan angles to a quaternion.
@@ -1710,7 +1731,7 @@ function createHotSpot(hs) {
hs.yaw = Number(hs.yaw) || 0;

var div = document.createElement('div');
div.className = 'pnlm-hotspot-base'
div.className = 'pnlm-hotspot-base';
if (hs.cssClass)
div.className += ' ' + hs.cssClass;
else
@@ -1723,24 +1744,24 @@ function createHotSpot(hs) {
var a;
if (hs.video) {
var video = document.createElement('video'),
p = hs.video;
if (config.basePath && !absoluteURL(p))
p = config.basePath + p;
video.src = sanitizeURL(p);
vidp = hs.video;
if (config.basePath && !absoluteURL(vidp))
vidp = config.basePath + vidp;
video.src = sanitizeURL(vidp);
video.controls = true;
video.style.width = hs.width + 'px';
renderContainer.appendChild(div);
span.appendChild(video);
} else if (hs.image) {
var p = hs.image;
if (config.basePath && !absoluteURL(p))
p = config.basePath + p;
var imgp = hs.image;
if (config.basePath && !absoluteURL(imgp))
imgp = config.basePath + imgp;
a = document.createElement('a');
a.href = sanitizeURL(hs.URL ? hs.URL : p);
a.href = sanitizeURL(hs.URL ? hs.URL : imgp);
a.target = '_blank';
span.appendChild(a);
var image = document.createElement('img');
image.src = sanitizeURL(p);
image.src = sanitizeURL(imgp);
image.style.width = hs.width + 'px';
image.style.paddingTop = '5px';
renderContainer.appendChild(div);
@@ -1804,7 +1825,7 @@ function createHotSpot(hs) {
}
hs.div = div;
};
}

/**
*
@@ -1898,6 +1919,9 @@ function renderHotSpot(hs) {
coord[1] += (canvasHeight - hs.div.offsetHeight) / 2;
var transform = 'translate(' + coord[0] + 'px, ' + coord[1] +
'px) translateZ(9999px) rotate(' + config.roll + 'deg)';
if (hs.scale) {
transform += ' scale(' + (origHfov/config.hfov) / z + ')';
}
hs.div.style.webkitTransform = transform;
hs.div.style.MozTransform = transform;
hs.div.style.transform = transform;
@@ -2042,7 +2066,15 @@ function processOptions(isPreview) {
break;
case 'author':
infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', escapeHTML(config[key]));
var authorText = escapeHTML(config[key]);
if (config.authorURL) {
var authorLink = document.createElement('a');
authorLink.href = sanitizeURL(config['authorURL']);
authorLink.target = '_blank';
authorLink.innerHTML = escapeHTML(config[key]);
authorText = authorLink.outerHTML;
}
infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', authorText);
infoDisplay.container.style.display = 'inline';
break;
@@ -2052,7 +2084,7 @@ function processOptions(isPreview) {
link.target = '_blank';
link.textContent = 'Click here to view this panorama in an alternative viewer.';
var message = document.createElement('p');
message.textContent = 'Your browser does not support WebGL.'
message.textContent = 'Your browser does not support WebGL.';
message.appendChild(document.createElement('br'));
message.appendChild(link);
infoDisplay.errorMsg.innerHTML = ''; // Removes all children nodes
@@ -2230,7 +2262,7 @@ function constrainHfov(hfov) {
}
if (minHfov > config.maxHfov) {
// Don't change view if bounds don't make sense
console.log('HFOV bounds do not make sense (minHfov > maxHfov).')
console.log('HFOV bounds do not make sense (minHfov > maxHfov).');
return config.hfov;
}
var newHfov = config.hfov;
@@ -2246,8 +2278,7 @@ function constrainHfov(hfov) {
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);
canvas.height * canvas.width) * 360 / Math.PI);
}
return newHfov;
}
@@ -2355,6 +2386,8 @@ function load() {
* @param {boolean} [fadeDone] - If `true`, fade setup is skipped.
*/
function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) {
if (!loaded)
fadeDone = true; // Don't try to fade when there isn't a scene loaded
loaded = false;
animatedMove = {};
@@ -2419,13 +2452,6 @@ function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) {
}
fireEvent('scenechange', sceneId);
load();

// Properly handle switching to dynamic scenes
update = config.dynamicUpdate === true;
if (config.dynamic) {
panoImage = config.panorama;
onImageLoad();
}
}

/**
@@ -2538,9 +2564,9 @@ this.setPitch = function(pitch, animated, callback, callbackArgs) {
'startPosition': config.pitch,
'endPosition': pitch,
'duration': animated
}
};
if (typeof callback == 'function')
setTimeout(function(){callback(callbackArgs)}, animated);
setTimeout(function(){callback(callbackArgs);}, animated);
} else {
config.pitch = pitch;
}
@@ -2599,22 +2625,22 @@ this.setYaw = function(yaw, animated, callback, callbackArgs) {
return this;
}
animated = animated == undefined ? 1000: Number(animated);
yaw = ((yaw + 180) % 360) - 180 // Keep in bounds
yaw = ((yaw + 180) % 360) - 180; // Keep in bounds
if (animated) {
// Animate in shortest direction
if (config.yaw - yaw > 180)
yaw += 360
yaw += 360;
else if (yaw - config.yaw > 180)
yaw -= 360
yaw -= 360;

animatedMove.yaw = {
'startTime': Date.now(),
'startPosition': config.yaw,
'endPosition': yaw,
'duration': animated
}
};
if (typeof callback == 'function')
setTimeout(function(){callback(callbackArgs)}, animated);
setTimeout(function(){callback(callbackArgs);}, animated);
} else {
config.yaw = yaw;
}
@@ -2679,9 +2705,9 @@ this.setHfov = function(hfov, animated, callback, callbackArgs) {
'startPosition': config.hfov,
'endPosition': constrainHfov(hfov),
'duration': animated
}
};
if (typeof callback == 'function')
setTimeout(function(){callback(callbackArgs)}, animated);
setTimeout(function(){callback(callbackArgs);}, animated);
} else {
setHfov(hfov);
}
@@ -2796,7 +2822,7 @@ this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) {
if (typeof callback == 'function')
callback(callbackArgs);
return this;
}
};

/**
* Returns the panorama's north offset.
@@ -2871,15 +2897,19 @@ this.setHorizonPitch = function(pitch) {

/**
* Start auto rotation.
*
* Before starting rotation, the viewer is panned to `pitch`.
* @memberof Viewer
* @instance
* @param {number} [speed] - Auto rotation speed / direction. If not specified, previous value is used.
* @param {number} [pitch] - The pitch to rotate at. If not specified, inital pitch is used.
* @returns {Viewer} `this`
*/
this.startAutoRotate = function(speed) {
this.startAutoRotate = function(speed, pitch) {
speed = speed || autoRotateSpeed || 1;
pitch = pitch === undefined ? origPitch : pitch;
config.autoRotate = speed;
_this.lookAt(origPitch, undefined, origHfov, 3000);
_this.lookAt(pitch, undefined, origHfov, 3000);
animateInit();
return this;
};
@@ -2905,7 +2935,7 @@ this.stopAutoRotate = function() {
this.stopMovement = function() {
stopAnimation();
speed = {'yaw': 0, 'pitch': 0, 'hfov': 0};
}
};

/**
* Returns the panorama renderer.
@@ -2931,7 +2961,7 @@ this.setUpdate = function(bool) {
else
animateInit();
return this;
}
};

/**
* Calculate panorama pitch and yaw from location of mouse event.
@@ -2942,7 +2972,7 @@ this.setUpdate = function(bool) {
*/
this.mouseEventToCoords = function(event) {
return mouseEventToCoords(event);
}
};

/**
* Change scene being viewed.
@@ -2958,7 +2988,7 @@ this.loadScene = function(sceneId, pitch, yaw, hfov) {
if (loaded !== false)
loadScene(sceneId, pitch, yaw, hfov);
return this;
}
};

/**
* Get ID of current scene.
@@ -2968,7 +2998,7 @@ this.loadScene = function(sceneId, pitch, yaw, hfov) {
*/
this.getScene = function() {
return config.scene;
}
};

/**
* Add a new scene.
@@ -3006,7 +3036,7 @@ this.removeScene = function(sceneId) {
this.toggleFullscreen = function() {
toggleFullscreen();
return this;
}
};

/**
* Get configuration of current scene.
@@ -3016,7 +3046,7 @@ this.toggleFullscreen = function() {
*/
this.getConfig = function() {
return config;
}
};

/**
* Get viewer's container element.
@@ -3026,7 +3056,7 @@ this.getConfig = function() {
*/
this.getContainer = function() {
return container;
}
};

/**
* Add a new hot spot.
@@ -3052,7 +3082,7 @@ this.addHotSpot = function(hs, sceneId) {
}
initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config
} else {
throw 'Invalid scene ID!'
throw 'Invalid scene ID!';
}
}
if (sceneId === undefined || config.scene == sceneId) {
@@ -3062,7 +3092,7 @@ this.addHotSpot = function(hs, sceneId) {
renderHotSpot(hs);
}
return this;
}
};

/**
* Remove a hot spot.
@@ -3094,11 +3124,11 @@ this.removeHotSpot = function(hotSpotId, sceneId) {
if (initialConfig.scenes.hasOwnProperty(sceneId)) {
if (!initialConfig.scenes[sceneId].hasOwnProperty('hotSpots'))
return false;
for (var i = 0; i < initialConfig.scenes[sceneId].hotSpots.length; i++) {
if (initialConfig.scenes[sceneId].hotSpots[i].hasOwnProperty('id') &&
initialConfig.scenes[sceneId].hotSpots[i].id === hotSpotId) {
for (var j = 0; j < initialConfig.scenes[sceneId].hotSpots.length; j++) {
if (initialConfig.scenes[sceneId].hotSpots[j].hasOwnProperty('id') &&
initialConfig.scenes[sceneId].hotSpots[j].id === hotSpotId) {
// Remove hot spot from configuration
initialConfig.scenes[sceneId].hotSpots.splice(i, 1);
initialConfig.scenes[sceneId].hotSpots.splice(j, 1);
return true;
}
}
@@ -3106,7 +3136,7 @@ this.removeHotSpot = function(hotSpotId, sceneId) {
return false;
}
}
}
};

/**
* This method should be called if the viewer's container is resized.
@@ -3116,7 +3146,7 @@ this.removeHotSpot = function(hotSpotId, sceneId) {
this.resize = function() {
if (renderer)
onDocumentResize();
}
};

/**
* Check if a panorama is loaded.
@@ -3126,7 +3156,7 @@ this.resize = function() {
*/
this.isLoaded = function() {
return loaded;
}
};

/**
* Check if device orientation control is supported.
@@ -3136,7 +3166,7 @@ this.isLoaded = function() {
*/
this.isOrientationSupported = function() {
return orientationSupport || false;
}
};

/**
* Stop using device orientation.
@@ -3145,7 +3175,7 @@ this.isOrientationSupported = function() {
*/
this.stopOrientation = function() {
stopOrientation();
}
};

/**
* Start using device orientation (does nothing if not supported).
@@ -3155,7 +3185,7 @@ this.stopOrientation = function() {
this.startOrientation = function() {
if (orientationSupport)
startOrientation();
}
};

/**
* Check if device orientation control is currently activated.
@@ -3165,7 +3195,7 @@ this.startOrientation = function() {
*/
this.isOrientationActive = function() {
return Boolean(orientation);
}
};

/**
* Subscribe listener to specified event.
@@ -3179,7 +3209,7 @@ this.on = function(type, listener) {
externalEventListeners[type] = externalEventListeners[type] || [];
externalEventListeners[type].push(listener);
return this;
}
};

/**
* Remove an event listener (or listeners).
@@ -3209,7 +3239,7 @@ this.off = function(type, listener) {
delete externalEventListeners[type];
}
return this;
}
};

/**
* Fire listeners attached to specified event.
@@ -3249,7 +3279,7 @@ this.destroy = function() {
}
container.innerHTML = '';
container.classList.remove('pnlm-container');
}
};

}



+ 0
- 1
src/standalone/pannellum.htm View File

@@ -16,7 +16,6 @@
</noscript>
</div>
<script type="text/javascript" src="../js/libpannellum.js"></script>
<script type="text/javascript" src="../js/RequestAnimationFrame.js"></script>
<script type="text/javascript" src="../js/pannellum.js"></script>
<script type="text/javascript" src="standalone.js"></script>
</body>


BIN
tests/cube.png View File

Before After
Width: 300  |  Height: 200  |  Size: 6.4 KiB

BIN
tests/equirectangular.png View File

Before After
Width: 300  |  Height: 200  |  Size: 109 KiB

BIN
tests/multires.png View File

Before After
Width: 300  |  Height: 200  |  Size: 6.0 KiB

+ 141
- 0
tests/run_tests.py View File

@@ -0,0 +1,141 @@
"""
Selenium-based test suite for Pannellum

Dependencies:
Python 3, Selenium Python bindings, Firefox, geckodriver, Pillow, NumPy
"""

import http.server
import time
import threading
import io
import subprocess
import os
import numpy as np
from PIL import Image, ImageChops
from selenium import webdriver


# Set to true to create a new set of reference images
CREATE_REF = False


# Run web server
print("Starting web server...")
os.chdir(os.path.dirname(os.path.abspath(__file__))) # cd to script dir
os.chdir("..")
httpd = http.server.HTTPServer(
("localhost", 8000), http.server.SimpleHTTPRequestHandler
)
thread = threading.Thread(None, httpd.serve_forever)
thread.start()


# Create a new instance of the Firefox driver
print("Starting web driver...")
if os.environ.get("TRAVIS_JOB_NUMBER"):
# Configuration for Travis CI / Sauce Labs testing
driver = webdriver.Remote(
command_executor="https://ondemand.saucelabs.com:443/wd/hub",
desired_capabilities={
"username": os.environ["SAUCE_USERNAME"],
"accessKey": os.environ["SAUCE_ACCESS_KEY"],
"tunnel-identifier": os.environ["TRAVIS_JOB_NUMBER"],
"build": os.environ["TRAVIS_JOB_NUMBER"],
"browserName": "firefox",
"seleniumVersion": "3.141.0",
},
)
else:
fp = webdriver.FirefoxProfile()
fp.set_preference("layout.css.devPixelsPerPx", "1.0")
driver = webdriver.Firefox(firefox_profile=fp)
driver.set_window_size(800, 600)


def run_tests():
# Load page
print("Loading page...")
driver.get("http://localhost:8000/tests/tests.html")

# Make sure viewer loaded
print("Running tests...")
time.sleep(5)
viewer = driver.find_element_by_id("panorama")
assert driver.execute_script("return viewer.isLoaded()") == True

# Check equirectangular
assert driver.execute_script("return viewer.getScene() == 'equirectangular'")
if CREATE_REF:
viewer.screenshot("tests/equirectangular.png")
subprocess.call(["optipng", "-o7", "-strip", "all", "equirectangular.png"])
else:
reference = Image.open("tests/equirectangular.png")
screenshot = Image.open(io.BytesIO(viewer.screenshot_as_png)).convert("RGB")
diff = np.mean(np.array(ImageChops.difference(screenshot, reference)))
print("equirectangular difference:", diff)
assert diff < 3
print("PASS: equirectangular")

# Check movement
driver.execute_script("viewer.setPitch(30).setYaw(-20).setHfov(90)")
time.sleep(2)
assert driver.execute_script(
"return viewer.getPitch() == 30 && viewer.getYaw() == -20 && viewer.getHfov() == 90"
)
driver.find_element_by_class_name("pnlm-zoom-in").click()
time.sleep(1)
assert driver.execute_script("return viewer.getHfov() == 85")
driver.find_element_by_class_name("pnlm-zoom-out").click()
time.sleep(1)
assert driver.execute_script("return viewer.getHfov() == 90")
print("PASS: movement")

# Check look at
driver.execute_script("viewer.lookAt(-10, 90, 100)")
time.sleep(2)
assert driver.execute_script(
"return viewer.getPitch() == -10 && viewer.getYaw() == 90 && viewer.getHfov() == 100"
)
print("PASS: look at")

# Check cube
driver.execute_script("viewer.loadScene('cube')")
time.sleep(5)
assert driver.execute_script("return viewer.getScene() == 'cube'")
if CREATE_REF:
viewer.screenshot("tests/cube.png")
subprocess.call(["optipng", "-o7", "-strip", "all", "cube.png"])
else:
reference = Image.open("tests/cube.png")
screenshot = Image.open(io.BytesIO(viewer.screenshot_as_png)).convert("RGB")
diff = np.mean(np.array(ImageChops.difference(screenshot, reference)))
print("cube difference:", diff)
assert diff < 3
print("PASS: cube")

# Check hot spot
driver.find_element_by_class_name("pnlm-scene").click()
time.sleep(5)
assert driver.execute_script("return viewer.getScene() == 'multires'")
print("PASS: hot spot")

# Check multires
if CREATE_REF:
viewer.screenshot("tests/multires.png")
subprocess.call(["optipng", "-o7", "-strip", "all", "multires.png"])
else:
reference = Image.open("tests/multires.png")
screenshot = Image.open(io.BytesIO(viewer.screenshot_as_png)).convert("RGB")
diff = np.mean(np.array(ImageChops.difference(screenshot, reference)))
print("multires difference:", diff)
assert diff < 3
print("PASS: multires")


try:
run_tests()
finally:
driver.quit()
httpd.shutdown()
thread.join()

+ 87
- 0
tests/tests.html View File

@@ -0,0 +1,87 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pannellum Tests</title>
<link rel="stylesheet" href="../src/css/pannellum.css"/>
<script type="text/javascript" src="../src/js/libpannellum.js"></script>
<script type="text/javascript" src="../src/js/pannellum.js"></script>
<style>
#panorama {
width: 300px;
height: 200px;
}
</style>
</head>
<body>

<div id="panorama"></div>
<script>
viewer = pannellum.viewer('panorama', {
"default": {
"author": "testauthor",
"firstScene": "equirectangular",
"autoLoad": true
},
"scenes": {
"cube": {
"title": "cube title",
"type": "cubemap",
"cubeMap": [
"../examples/multires/1/f00.png",
"../examples/multires/1/r00.png",
"../examples/multires/1/b00.png",
"../examples/multires/1/l00.png",
"../examples/multires/1/u00.png",
"../examples/multires/1/d00.png"
],
"hotSpots": [
{
"pitch": -12,
"yaw": 170,
"type": "info",
"text": "info test"
},
{
"pitch": -10,
"yaw": -50,
"type": "info",
"text": "link test",
"URL": "https://github.com/mpetroff/pannellum"
},
{
"pitch": 0,
"yaw": -10,
"type": "scene",
"text": "scene test",
"sceneId": "multires"
}
]
},

"equirectangular": {
"title": "equirectangular title",
"panorama": "../examples/examplepano.jpg"
},

"multires": {
"title": "multires title",
"type": "multires",
"multiRes": {
"basePath": "../examples/multires",
"path": "/%l/%s%x%y",
"fallbackPath": "/fallback/%s",
"extension": "png",
"tileResolution": 256,
"maxLevel": 4,
"cubeResolution": 2048
}
}
}
});
</script>

</body>
</html>

+ 1
- 2
utils/build/build.py View File

@@ -8,7 +8,6 @@ import urllib.parse

JS = [
'js/libpannellum.js',
'js/RequestAnimationFrame.js',
'js/pannellum.js',
]

@@ -132,7 +131,6 @@ def build(files, css, html, filename, release=False):
html = merge(html)
html = html.replace('<link type="text/css" rel="Stylesheet" href="../css/pannellum.css"/>','<style type="text/css">' + standalone_css + '</style>')
html = html.replace('<script type="text/javascript" src="../js/libpannellum.js"></script>','')
html = html.replace('<script type="text/javascript" src="../js/RequestAnimationFrame.js"></script>','')
html = html.replace('<script type="text/javascript" src="../js/pannellum.js"></script>','<script type="text/javascript">' + standalone_js + '</script>')
html = html.replace('<script type="text/javascript" src="standalone.js"></script>','')
html = html.replace('<link type="text/css" rel="Stylesheet" href="standalone.css"/>', '')
@@ -143,6 +141,7 @@ def build(files, css, html, filename, release=False):
output(addHeaderJS(js, version), folder + filename)

def main():
os.chdir(os.path.dirname(os.path.abspath(__file__))) # cd to script dir
if (len(sys.argv) > 1 and sys.argv[1] == 'release'):
build(JS, CSS, HTML, 'pannellum', True)
else:


Loading…
Cancel
Save