@egjs/view360
Version:
360 integrated viewing solution from inside-out view to outside-in view. It provides user-friendly service by rotating 360 degrees through various user interaction such as motion sensor and touch.
1,018 lines (905 loc) • 36.6 kB
JavaScript
import Component from "@egjs/component";
import {
DeviceMotionEvent
} from "../utils/browserFeature";
import YawPitchControl from "../YawPitchControl/YawPitchControl";
import PanoImageRenderer from "../PanoImageRenderer/PanoImageRenderer";
import WebGLUtils from "../PanoImageRenderer/WebGLUtils";
import {ERROR_TYPE, EVENTS, GYRO_MODE, PROJECTION_TYPE} from "./consts";
import {glMatrix} from "../utils/math-util.js";
import {VERSION} from "../version";
import {IS_SAFARI_ON_DESKTOP} from "../utils/browser";
export default class PanoViewer extends Component {
/**
* Version info string
* @ko 버전정보 문자열
* @name VERSION
* @static
* @type {String}
* @example
* eg.view360.PanoViewer.VERSION; // ex) 3.0.1
* @memberof eg.view360.PanoViewer
*/
static VERSION = VERSION;
static ERROR_TYPE = ERROR_TYPE;
static EVENTS = EVENTS;
static PROJECTION_TYPE = PROJECTION_TYPE;
static GYRO_MODE = GYRO_MODE;
// It should be deprecated!
static ProjectionType = PROJECTION_TYPE;
/**
* Constant value for touch directions
* @ko 터치 방향에 대한 상수 값.
* @namespace
* @name TOUCH_DIRECTION
* @memberof eg.view360.PanoViewer
*/
static TOUCH_DIRECTION = {
/**
* Constant value for none direction.
* @ko none 방향에 대한 상수 값.
* @name NONE
* @memberof eg.view360.PanoViewer.TOUCH_DIRECTION
* @constant
* @type {Number}
* @default 1
*/
NONE: YawPitchControl.TOUCH_DIRECTION_NONE,
/**
* Constant value for horizontal(yaw) direction.
* @ko horizontal(yaw) 방향에 대한 상수 값.
* @name YAW
* @memberof eg.view360.PanoViewer.TOUCH_DIRECTION
* @constant
* @type {Number}
* @default 6
*/
YAW: YawPitchControl.TOUCH_DIRECTION_YAW,
/**
* Constant value for vertical direction.
* @ko vertical(pitch) 방향에 대한 상수 값.
* @name PITCH
* @memberof eg.view360.PanoViewer.TOUCH_DIRECTION
* @constant
* @type {Number}
* @default 24
*/
PITCH: YawPitchControl.TOUCH_DIRECTION_PITCH,
/**
* Constant value for all direction.
* @ko all 방향에 대한 상수 값.
* @name ALL
* @memberof eg.view360.PanoViewer.TOUCH_DIRECTION
* @constant
* @type {Number}
* @default 30
*/
ALL: YawPitchControl.TOUCH_DIRECTION_ALL
};
/**
* @classdesc 360 media viewer
* @ko 360 미디어 뷰어
* @class
* @name eg.view360.PanoViewer
* @extends eg.Component
*
* @param {HTMLElement} container The container element for the renderer. <ko>렌더러의 컨테이너 엘리먼트</ko>
* @param {Object} config
*
* @param {String|Image} config.image Input image url or element (Use only image property or video property)<ko>입력 이미지 URL 혹은 엘리먼트(image 와 video 둘 중 하나만 설정)</ko>
* @param {String|HTMLVideoElement} config.video Input video url or element(Use only image property or video property)<ko>입력 비디오 URL 혹은 엘리먼트(image 와 video 둘 중 하나만 설정)</ko>
* @param {String} [config.projectionType=equirectangular] The type of projection: equirectangular, cubemap <br/>{@link eg.view360.PanoViewer.PROJECTION_TYPE}<ko>Projection 유형 : equirectangular, cubemap <br/>{@link eg.view360.PanoViewer.PROJECTION_TYPE}</ko>
* @param {Object} config.cubemapConfig config cubemap projection layout. It is applied when projectionType is {@link eg.view360.PanoViewer.PROJECTION_TYPE.CUBEMAP} or {@link eg.view360.PanoViewer.PROJECTION_TYPE.CUBESTRIP}<ko>cubemap projection type 의 레이아웃을 설정한다. 이 설정은 ProjectionType 이 {@link eg.view360.PanoViewer.PROJECTION_TYPE.CUBEMAP} 혹은 {@link eg.view360.PanoViewer.PROJECTION_TYPE.CUBESTRIP} 인 경우에만 적용된다.</ko>
* @param {Object} [config.cubemapConfig.order = "RLUDBF"(ProjectionType === CUBEMAP) | "RLUDFB" (ProjectionType === CUBESTRIP)] Order of cubemap faces <ko>Cubemap 형태의 이미지가 배치된 순서</ko>
* @param {Object} [config.cubemapConfig.tileConfig = {flipHirozontal:false, rotation: 0}] Setting about rotation angle(degree) and whether to flip horizontal for each cubemap faces, if you put this object as a array, you can set each faces with different setting. For example, [{flipHorizontal:false, rotation:90}, {flipHorizontal: true, rotation: 180}, ...]<ko>각 Cubemap 면에 대한 회전 각도/좌우반전 여부 설정, 객체를 배열 형태로 지정하여 각 면에 대한 설정을 다르게 지정할 수도 있다. 예를 들어 [{flipHorizontal:false, rotation:90}, {flipHorizontal: true, rotation: 180}, ...]과 같이 지정할 수 있다.</ko>
* @param {Number} [config.width=width of container] the viewer's width. (in px) <ko>뷰어의 너비 (px 단위)</ko>
* @param {Number} [config.height=height of container] the viewer's height.(in px) <ko>뷰어의 높이 (px 단위)</ko>
*
* @param {Number} [config.yaw=0] Initial Yaw of camera (in degree) <ko>카메라의 초기 Yaw (degree 단위)</ko>
* @param {Number} [config.pitch=0] Initial Pitch of camera (in degree) <ko>카메라의 초기 Pitch (degree 단위)</ko>
* @param {Number} [config.fov=65] Initial vertical field of view of camera (in degree) <ko>카메라의 초기 수직 field of view (degree 단위)</ko>
* @param {Boolean} [config.showPolePoint=false] If false, the pole is not displayed inside the viewport <ko>false 인 경우, 극점은 뷰포트 내부에 표시되지 않습니다</ko>
* @param {Boolean} [config.useZoom=true] When true, enables zoom with the wheel and Pinch gesture <ko>true 일 때 휠 및 집기 제스춰로 확대 / 축소 할 수 있습니다.</ko>
* @param {Boolean} [config.useKeyboard=true] When true, enables the keyboard move key control: awsd, arrow keys <ko>true 이면 키보드 이동 키 컨트롤을 활성화합니다: awsd, 화살표 키</ko>
* @param {String} [config.gyroMode=yawPitch] Enables control through device motion. ("none", "yawPitch", "VR") <br/>{@link eg.view360.PanoViewer.GYRO_MODE} <ko>디바이스 움직임을 통한 컨트롤을 활성화 합니다. ("none", "yawPitch", "VR") <br/>{@link eg.view360.PanoViewer.GYRO_MODE} </ko>
* @param {Array} [config.yawRange=[-180, 180]] Range of controllable Yaw values <ko>제어 가능한 Yaw 값의 범위</ko>
* @param {Array} [config.pitchRange=[-90, 90]] Range of controllable Pitch values <ko>제어 가능한 Pitch 값의 범위</ko>
* @param {Array} [config.fovRange=[30, 110]] Range of controllable vertical field of view values <ko>제어 가능한 수직 field of view 값의 범위</ko>
* @param {Number} [config.touchDirection= {@link eg.view360.PanoViewer.TOUCH_DIRECTION.ALL}(6)] Direction of touch that can be controlled by user <br/>{@link eg.view360.PanoViewer.TOUCH_DIRECTION}<ko>사용자가 터치로 조작 가능한 방향 <br/>{@link eg.view360.PanoViewer.TOUCH_DIRECTION}</ko>
*
* @example
* // PanoViewer Creation
* // create PanoViewer with option
* var PanoViewer = eg.view360.PanoViewer;
* // Area where the image will be displayed(HTMLElement)
* var container = document.getElementById("myPanoViewer");
*
* var panoViewer = new PanoViewer(container, {
* // If projectionType is not specified, the default is "equirectangular".
* // Specifies an image of the "equirectangular" type.
* image: "/path/to/image/image.jpg"
*});
*
* @example
* // Cubemap Config Setting Example
* // For support Youtube EAC projection, You should set cubemapConfig as follows.
* cubemapConfig: {
* order: "LFRDBU",
* tileConfig: [
* tileConfig: [{rotation: 0}, {rotation: 0}, {rotation: 0}, {rotation: 0}, {rotation: -90}, {rotation: 180}]
* ]
* }
*/
constructor(container, options = {}) {
super();
// Raises the error event if webgl is not supported.
if (!WebGLUtils.isWebGLAvailable()) {
setTimeout(() => {
this.trigger(EVENTS.ERROR, {
type: ERROR_TYPE.NO_WEBGL,
message: "no webgl support"
});
}, 0);
return this;
}
if (!WebGLUtils.isStableWebGL()) {
setTimeout(() => {
this.trigger(EVENTS.ERROR, {
type: ERROR_TYPE.INVALID_DEVICE,
message: "blacklisted browser"
});
}, 0);
return this;
}
if (!!options.image && !!options.video) {
setTimeout(() => {
this.trigger(EVENTS.ERROR, {
type: ERROR_TYPE.INVALID_RESOURCE,
message: "Specifying multi resouces(both image and video) is not valid."
});
}, 0);
return this;
}
this._container = container;
this._image = options.image || options.video;
this._isVideo = !!options.video;
this._projectionType = options.projectionType || PROJECTION_TYPE.EQUIRECTANGULAR;
this._cubemapConfig = Object.assign({
/* RLUDBF is abnormal, we use it on CUBEMAP only for backward compatibility*/
order: this._projectionType === PROJECTION_TYPE.CUBEMAP ? "RLUDBF" : "RLUDFB",
tileConfig: {
flipHirozontal: false,
rotation: 0
}
}, options.cubemapConfig);
// If the width and height are not provided, will use the size of the container.
this._width = options.width || parseInt(window.getComputedStyle(container).width, 10);
this._height = options.height || parseInt(window.getComputedStyle(container).height, 10);
/**
* Cache the direction for the performance in renderLoop
*
* This value should be updated by "change" event of YawPitchControl.
*/
this._yaw = options.yaw || 0;
this._pitch = options.pitch || 0;
this._fov = options.fov || 65;
this._gyroMode = options.gyroMode || GYRO_MODE.YAWPITCH;
this._quaternion = null;
this._aspectRatio = this._height !== 0 ? this._width / this._height : 1;
const fovRange = options.fovRange || [30, 110];
const touchDirection = PanoViewer._isValidTouchDirection(options.touchDirection) ?
options.touchDirection : YawPitchControl.TOUCH_DIRECTION_ALL;
const yawPitchConfig = Object.assign(options, {
element: container,
yaw: this._yaw,
pitch: this._pitch,
fov: this._fov,
gyroMode: this._gyroMode,
fovRange,
aspectRatio: this._aspectRatio,
touchDirection
});
this._isReady = false;
this._initYawPitchControl(yawPitchConfig);
this._initRenderer(this._yaw, this._pitch, this._fov, this._projectionType, this._cubemapConfig);
}
/**
* Get the video element that the viewer is currently playing. You can use this for playback.
* @ko 뷰어가 현재 사용 중인 비디오 요소를 얻습니다. 이 요소를 이용해 비디오의 컨트롤을 할 수 있습니다.
* @method eg.view360.PanoViewer#getVideo
* @return {HTMLVideoElement} HTMLVideoElement<ko>HTMLVideoElement</ko>
* @example
* var videoTag = panoViewer.getVideo();
* videoTag.play(); // play video!
*/
getVideo() {
if (!this._isVideo) {
return null;
}
return this._photoSphereRenderer.getContent();
}
/**
* Set the video information to be used by the viewer.
* @ko 뷰어가 사용할 이미지 정보를 설정합니다.
* @method eg.view360.PanoViewer#setVideo
* @param {String|HTMLVideoElement|Object} video Input video url or element or config object<ko>입력 비디오 URL 혹은 엘리먼트 혹은 설정객체를 활용(image 와 video 둘 중 하나만 설정)</ko>
* @param {Object} param
* @param {String} [param.projectionType={@link eg.view360.PanoViewer.PROJECTION_TYPE.EQUIRECTANGULAR}("equirectangular")] Projection Type<ko>프로젝션 타입</ko>
* @param {Object} param.cubemapConfig config cubemap projection layout. <ko>cubemap projection type 의 레이아웃 설정</ko>
*
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* panoViewer.setVideo("/path/to/video/video.mp4", {
* projectionType: eg.view360.PanoViewer.PROJECTION_TYPE.EQUIRECTANGULAR
* });
*/
setVideo(video, param = {}) {
if (video) {
this.setImage(video, {
projectionType: param.projectionType,
isVideo: true,
cubemapConfig: param.cubemapConfig
});
}
return this;
}
/**
* Get the image information that the viewer is currently using.
* @ko 뷰어가 현재 사용하고있는 이미지 정보를 얻습니다.
* @method eg.view360.PanoViewer#getImage
* @return {Image} Image Object<ko>이미지 객체</ko>
* @example
* var imageObj = panoViewer.getImage();
*/
getImage() {
if (this._isVideo) {
return null;
}
return this._photoSphereRenderer.getContent();
}
/**
* Set the image information to be used by the viewer.
* @ko 뷰어가 사용할 이미지 정보를 설정합니다.
* @method eg.view360.PanoViewer#setImage
* @param {String|Image|Object} image Input image url or element or config object<ko>입력 이미지 URL 혹은 엘리먼트 혹은 설정객체를 활용(image 와 video 둘 중 하나만 설정한다.)</ko>
* @param {Object} param Additional information<ko>이미지 추가 정보</ko>
* @param {String} [param.projectionType="equirectangular"] Projection Type<ko>프로젝션 타입</ko>
* @param {Object} param.cubemapConfig config cubemap projection layout. <ko>cubemap projection type 레이아웃</ko>
*
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* panoViewer.setImage("/path/to/image/image.png", {
* projectionType: eg.view360.PanoViewer.PROJECTION_TYPE.CUBEMAP
* });
*/
setImage(image, param = {}) {
const cubemapConfig = Object.assign({
order: "RLUDBF",
tileConfig: {
flipHirozontal: false,
rotation: 0
}
}, param.cubemapConfig);
const isVideo = !!(param.isVideo);
if (this._image && this._isVideo !== isVideo) {
/* eslint-disable no-console */
console.warn("Currently not supporting to change content type(Image <--> Video)");
/* eslint-enable no-console */
return this;
}
if (image) {
this._image = image;
this._isVideo = isVideo;
this._projectionType = param.projectionType || PROJECTION_TYPE.EQUIRECTANGULAR;
this._cubemapConfig = cubemapConfig;
this._deactivate();
this._initRenderer(this._yaw, this._pitch, this._fov, this._projectionType, this._cubemapConfig);
}
return this;
}
/**
* Set whether the renderer always updates the texture and renders.
* @ko 렌더러가 항상 텍스쳐를 갱신하고 화면을 렌더링 할지 여부를 설정할 수 있습니다.
*
* @method eg.view360.PanoViewer#keepUpdate
* @param {Boolean} doUpdate When true viewer will always update texture and render, when false viewer will not update texture and render only camera config is changed.<ko>true면 항상 텍스쳐를 갱신하고 화면을 그리는 반면, false면 텍스쳐 갱신은 하지 않으며, 카메라 요소에 변화가 있을 때에만 화면을 그립니다.</ko>
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
*/
keepUpdate(doUpdate) {
this._photoSphereRenderer.keepUpdate(doUpdate);
return this;
}
/**
* Get projection type (equirectangular/cube)
* @ko 프로젝션 타입(Equirectangular 혹은 Cube)을 반환합니다.
*
* @method eg.view360.PanoViewer#getProjectionType
* @return {String} {@link eg.view360.PanoViewer.PROJECTION_TYPE}
*/
getProjectionType() {
return this._projectionType;
}
_initRenderer(yaw, pitch, fov, projectionType, cubemapConfig) {
this._photoSphereRenderer = new PanoImageRenderer(
this._image,
this._width,
this._height,
this._isVideo,
{
initialYaw: yaw,
initialPitch: pitch,
fieldOfView: fov,
imageType: projectionType,
cubemapConfig
}
);
this._bindRendererHandler();
this._photoSphereRenderer
.bindTexture()
.then(() => this._activate())
.catch(() => {
this._triggerEvent(EVENTS.ERROR, {
type: ERROR_TYPE.FAIL_BIND_TEXTURE,
message: "failed to bind texture"
});
});
}
/**
* update values of YawPitchControl if needed.
* For example, In Panorama mode, initial fov and pitchRange is changed by aspect ratio of image.
*
* This function should be called after isReady status is true.
*/
_updateYawPitchIfNeeded() {
if (this._projectionType === PanoViewer.ProjectionType.PANORAMA) {
// update fov by aspect ratio
const image = this._photoSphereRenderer.getContent();
let imageAspectRatio = image.naturalWidth / image.naturalHeight;
let isCircular;
let yawSize;
let maxFov;
// If height is larger than width, then we assume it's rotated by 90 degree.
if (imageAspectRatio < 1) {
// So inverse the aspect ratio.
imageAspectRatio = 1 / imageAspectRatio;
}
if (imageAspectRatio < 6) {
yawSize = glMatrix.toDegree(imageAspectRatio);
isCircular = false;
// 0.5 means ratio of half height of cylinder(0.5) and radius of cylider(1). 0.5/1 = 0.5
maxFov = glMatrix.toDegree(Math.atan(0.5)) * 2;
} else {
yawSize = 360;
isCircular = true;
maxFov = (360 / imageAspectRatio); // Make it 5 fixed as axes does.
}
// console.log("_updateYawPitchIfNeeded", maxFov, "aspectRatio", image.naturalWidth, image.naturalHeight, "yawSize", yawSize);
const minFov = (this._yawPitchControl.option("fovRange"))[0];
// this option should be called after fov is set.
this._yawPitchControl.option({
"fov": maxFov, /* parameter for internal validation for pitchrange */
"yawRange": [-yawSize / 2, yawSize / 2],
isCircular,
"pitchRange": [-maxFov / 2, maxFov / 2],
"fovRange": [minFov, maxFov]
});
this.lookAt({fov: maxFov});
}
}
_bindRendererHandler() {
this._photoSphereRenderer.on(PanoImageRenderer.EVENTS.ERROR, e => {
this.trigger(EVENTS.ERROR, e);
});
this._photoSphereRenderer.on(PanoImageRenderer.EVENTS.RENDERING_CONTEXT_LOST, e => {
this._deactivate();
this.trigger(EVENTS.ERROR, {
type: ERROR_TYPE.RENDERING_CONTEXT_LOST,
message: "webgl rendering context lost"
});
});
}
_initYawPitchControl(yawPitchConfig) {
this._yawPitchControl = new YawPitchControl(yawPitchConfig);
this._yawPitchControl.on(EVENTS.ANIMATION_END, e => {
this._triggerEvent(EVENTS.ANIMATION_END, e);
});
this._yawPitchControl.on("change", e => {
this._yaw = e.yaw;
this._pitch = e.pitch;
this._fov = e.fov;
this._quaternion = e.quaternion;
this._triggerEvent(EVENTS.VIEW_CHANGE, e);
});
}
_triggerEvent(name, param) {
const evt = param || {};
/**
* Events that is fired when error occurs
* @ko 에러 발생 시 발생하는 이벤트
* @name eg.view360.PanoViewer#error
* @event
* @param {Object} param The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
* @param {Number} param.type Error type
* 10: INVALID_DEVICE: Unsupported device
* 11: NO_WEBGL: Webgl not support
* 12, FAIL_IMAGE_LOAD: Failed to load image
* 13: FAIL_BIND_TEXTURE: Failed to bind texture
* 14: INVALID_RESOURCE: Only one resource(image or video) should be specified
* 15: RENDERING_CONTEXT_LOST: WebGL context lost occurred
* <ko>에러 종류
* 10: INVALID_DEVICE: 미지원 기기
* 11: NO_WEBGL: WEBGL 미지원
* 12, FAIL_IMAGE_LOAD: 이미지 로드 실패
* 13: FAIL_BIND_TEXTURE: 텍스쳐 바인딩 실패
* 14: INVALID_RESOURCE: 리소스 지정 오류 (image 혹은 video 중 하나만 지정되어야 함)
* 15: RENDERING_CONTEXT_LOST: WebGL context lost 발생
* </ko>
* @param {String} param.message Error message <ko>에러 메시지</ko>
* @see {@link eg.view360.PanoViewer.ERROR_TYPE}
* @example
*
* viwer.on({
* "error" : function(evt) {
* // evt.type === 13
* // evt.message === "failed to bind texture"
* });
*
* // constant can be used
* viwer.on({
* eg.view360.PanoViewer.EVENTS.ERROR : function(evt) {
* // evt.type === eg.view360.PanoViewer.ERROR_TYPE.FAIL_BIND_TEXTURE
* // evt.message === "failed to bind texture"
* });
*/
/**
* Events that is fired when PanoViewer is ready to go.
* @ko PanoViewer 가 준비된 상태에 발생하는 이벤트
* @name eg.view360.PanoViewer#ready
* @event
*
* @example
*
* viwer.on({
* "ready" : function(evt) {
* // PanoViewer is ready to show image and handle user interaction.
* });
*/
/**
* Events that is fired when direction or fov is changed.
* @ko PanoViewer 에서 바라보고 있는 방향이나 FOV(화각)가 변경되었을때 발생하는 이벤트
* @name eg.view360.PanoViewer#viewChange
* @event
* @param {Object} param The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
* @param {Number} param.yaw yaw<ko>yaw</ko>
* @param {Number} param.pitch pitch <ko>pitch</ko>
* @param {Number} param.fov Field of view (fov) <ko>화각</ko>
* @example
*
* viwer.on({
* "viewChange" : function(evt) {
* //evt.yaw, evt.pitch, evt.fov is available.
* });
*/
/**
* Events that is fired when animation which is triggered by inertia is ended.
* @ko 관성에 의한 애니메이션 동작이 완료되었을때 발생하는 이벤트
* @name eg.view360.PanoViewer#animationEnd
* @event
* @example
*
* viwer.on({
* "animationEnd" : function(evt) {
* // animation is ended.
* });
*/
return this.trigger(name, evt);
}
/**
* When set true, enables zoom with the wheel or pinch gesture. However, in the case of touch, pinch works only when the touchDirection setting is {@link eg.view360.PanoViewer.TOUCH_DIRECTION.ALL}.
* @ko true 로 설정 시 휠 혹은 집기 동작으로 확대/축소 할 수 있습니다. false 설정 시 확대/축소 기능을 비활성화 합니다. 단, 터치인 경우 touchDirection 설정이 {@link eg.view360.PanoViewer.TOUCH_DIRECTION.ALL} 인 경우에만 pinch 가 동작합니다.
* @method eg.view360.PanoViewer#setUseZoom
* @param {Boolean} useZoom
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
*/
setUseZoom(useZoom) {
typeof useZoom === "boolean" && this._yawPitchControl.option("useZoom", useZoom);
return this;
}
/**
* When true, enables the keyboard move key control: awsd, arrow keys
* @ko true이면 키보드 이동 키 컨트롤을 활성화합니다. (awsd, 화살표 키)
* @method eg.view360.PanoViewer#setUseKeyboard
* @param {Boolean} useKeyboard
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
*/
setUseKeyboard(useKeyboard) {
this._yawPitchControl.option("useKeyboard", useKeyboard);
return this;
}
/**
* Enables control through device motion. ("none", "yawPitch", "VR")
* @ko 디바이스 움직임을 통한 컨트롤을 활성화 합니다. ("none", "yawPitch", "VR")
* @method eg.view360.PanoViewer#setGyroMode
* @param {String} gyroMode {@link eg.view360.PanoViewer.GYRO_MODE}
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* panoViewer.setGyroMode("yawPitch");
* //equivalent
* panoViewer.setGyroMode(eg.view360.PanoViewer.GYRO_MODE.YAWPITCH);
*/
setGyroMode(gyroMode) {
this._yawPitchControl.option("gyroMode", gyroMode);
return this;
}
/**
* Set the range of controllable FOV values
* @ko 제어 가능한 FOV 구간을 설정합니다.
* @method eg.view360.PanoViewer#setFovRange
* @param {Array} range
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* panoViewer.setFovRange([50, 90]);
*/
setFovRange(range) {
this._yawPitchControl.option("fovRange", range);
return this;
}
/**
* Getting the range of controllable FOV values
* @ko 제어 가능한 FOV 구간을 반환합니다.
* @method eg.view360.PanoViewer#getFovRange
* @return {Array}
* @example
* var range = panoViewer.getFovRange(); //[50, 90]
*/
getFovRange() {
return this._yawPitchControl.option("fovRange");
}
/**
* Update size of canvas element by it's container element's or specified size. If size is not specified, the size of the container area is obtained and updated to that size.
* @ko 캔버스 엘리먼트의 크기를 컨테이너 엘리먼트의 크기나 지정된 크기로 업데이트합니다. 만약 size 가 지정되지 않으면 컨테이너 영역의 크기를 얻어와 해당 크기로 갱신합니다.
* @method eg.view360.PanoViewer#updateViewportDimensions
* @param {Object} [size]
* @param {Number} [size.width=width of container]
* @param {Number} [size.height=height of container]
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
*/
updateViewportDimensions(size = {width: undefined, height: undefined}) {
if (!this._isReady) {
return this;
}
let containerSize;
if (size.width === undefined || size.height === undefined) {
containerSize = window.getComputedStyle(this._container);
}
const width = size.width || parseInt(containerSize.width, 10);
const height = size.height || parseInt(containerSize.height, 10);
// Skip if viewport is not changed.
if (width === this._width && height === this._height) {
return this;
}
this._width = width;
this._height = height;
this._aspectRatio = width / height;
this._photoSphereRenderer.updateViewportDimensions(width, height);
this._yawPitchControl.option("aspectRatio", this._aspectRatio);
this._yawPitchControl.updatePanScale({height});
this.lookAt({}, 0);
return this;
}
/**
* Get the current field of view(FOV)
* @ko 현재 field of view(FOV) 값을 반환합니다.
* @method eg.view360.PanoViewer#getFov
* @return {Number}
*/
getFov() {
return this._fov;
}
/**
* Get the horizontal field of view in degree
*/
_getHFov() {
return glMatrix.toDegree(
2 * Math.atan(this._aspectRatio * Math.tan(glMatrix.toRadian(this._fov) / 2)));
}
/**
* Get current yaw value
* @ko 현재 yaw 값을 반환합니다.
* @method eg.view360.PanoViewer#getYaw
* @return {Number}
*/
getYaw() {
return this._yaw;
}
/**
* Get current pitch value
* @ko 현재 pitch 값을 반환합니다.
* @method eg.view360.PanoViewer#getPitch
* @return {Number}
*/
getPitch() {
return this._pitch;
}
/**
* Get the range of controllable Yaw values
* @ko 컨트롤 가능한 Yaw 구간을 반환합니다.
* @method eg.view360.PanoViewer#getYawRange
* @return {Array}
*/
getYawRange() {
return this._yawPitchControl.option("yawRange");
}
/**
* Get the range of controllable Pitch values
* @ko 컨트롤 가능한 Pitch 구간을 가져옵니다.
* @method eg.view360.PanoViewer#getPitchRange
* @return {Array}
*/
getPitchRange() {
return this._yawPitchControl.option("pitchRange");
}
/**
* Set the range of controllable yaw
* @ko 컨트롤 가능한 Yaw 구간을 반환합니다.
* @method eg.view360.PanoViewer#setYawRange
* @param {Array} range
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* panoViewer.setYawRange([-90, 90]);
*/
setYawRange(yawRange) {
this._yawPitchControl.option("yawRange", yawRange);
return this;
}
/**
* Set the range of controllable Pitch values
* @ko 컨트롤 가능한 Pitch 구간을 설정합니다.
* @method eg.view360.PanoViewer#setPitchRange
* @param {Array} range
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* panoViewer.setPitchRange([-40, 40]);
*/
setPitchRange(pitchRange) {
this._yawPitchControl.option("pitchRange", pitchRange);
return this;
}
/**
* Specifies whether to display the pole by limiting the pitch range. If it is true, pole point can be displayed. If it is false, it is not displayed.
* @ko pitch 범위를 제한하여 극점을 표시할지를 지정합니다. true 인 경우 극점까지 표현할 수 있으며 false 인 경우 극점까지 표시하지 않습니다.
* @method eg.view360.PanoViewer#setShowPolePoint
* @param {Boolean} showPolePoint
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
*/
setShowPolePoint(showPolePoint) {
this._yawPitchControl.option("showPolePoint", showPolePoint);
return this;
}
/**
* Set a new view by setting camera configuration. Any parameters not specified remain the same.
* @ko 카메라 설정을 지정하여 화면을 갱신합니다. 지정되지 않은 매개 변수는 동일하게 유지됩니다.
* @method eg.view360.PanoViewer#lookAt
* @param {Object} orientation
* @param {Number} orientation.yaw Target yaw in degree <ko>목표 yaw (degree 단위)</ko>
* @param {Number} orientation.pitch Target pitch in degree <ko>목표 pitch (degree 단위)</ko>
* @param {Number} orientation.fov Target vertical fov in degree <ko>목표 수직 fov (degree 단위)</ko>
* @param {Number} duration Animation duration in milliseconds <ko>애니메이션 시간 (밀리 초)</ko>
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
* @example
* // Change the yaw angle (absolute angle) to 30 degrees for one second.
* panoViewer.lookAt({yaw: 30}, 1000);
*/
lookAt(orientation, duration) {
if (!this._isReady) {
return this;
}
const yaw = orientation.yaw !== undefined ? orientation.yaw : this._yaw;
const pitch = orientation.pitch !== undefined ? orientation.pitch : this._pitch;
const pitchRange = this._yawPitchControl.option("pitchRange");
const verticalAngleOfImage = pitchRange[1] - pitchRange[0];
let fov = orientation.fov !== undefined ? orientation.fov : this._fov;
if (verticalAngleOfImage < fov) {
fov = verticalAngleOfImage;
}
this._yawPitchControl.lookAt({yaw, pitch, fov}, duration);
if (duration === 0) {
this._photoSphereRenderer.render(yaw, pitch, fov);
}
return this;
}
_activate() {
this._photoSphereRenderer.attachTo(this._container);
this._yawPitchControl.enable();
this.updateViewportDimensions();
this._isReady = true;
// update yawPitchControl after isReady status is true.
this._updateYawPitchIfNeeded();
this._triggerEvent(EVENTS.READY);
this._startRender();
}
/**
* Register the callback on the raf to call _renderLoop every frame.
*/
_startRender() {
if (IS_SAFARI_ON_DESKTOP) {
this._renderLoop = this._renderLoopForNextTick.bind(this);
} else {
this._renderLoop = this._renderLoop.bind(this);
}
this._rafId = window.requestAnimationFrame(this._renderLoop);
}
_render() {
if (this._photoSphereRenderer) {
if (this._quaternion) {
this._photoSphereRenderer.renderWithQuaternion(this._quaternion, this._fov);
} else {
this._photoSphereRenderer.render(this._yaw, this._pitch, this._fov);
}
}
}
_renderLoop() {
this._render();
this._rafId = window.requestAnimationFrame(this._renderLoop);
}
/**
* MacOS X Safari Bug Fix
* This code guarantees that rendering should be occurred.
*
* In MacOS X(10.14.2), Safari (12.0.2)
* The requestAnimationFrame(RAF) callback is called just after previous RAF callback without term
* only if requestAnimationFrame is called for next frame while updating frame is delayed (~over 2ms)
* So browser cannot render the frame and may be freezing.
*/
_renderLoopForNextTick() {
const before = performance.now();
this._render();
const diff = performance.now() - before;
if (this._rafTimer) {
clearTimeout(this._rafTimer);
this._rafTimer = null;
}
/** Use requestAnimationFrame only if current rendering could be possible over 60fps (1000/60) */
if (diff < 16) {
this._rafId = window.requestAnimationFrame(this._renderLoop);
} else {
/** Otherwise, Call setTimeout instead of requestAnimationFrame to gaurantee renering should be occurred*/
this._rafTimer = setTimeout(this._renderLoop, 0);
}
}
_stopRender() {
if (this._rafId) {
window.cancelAnimationFrame(this._rafId);
}
if (this._rafTimer) {
clearTimeout(this._rafTimer);
}
delete this._rafId;
delete this._rafTimer;
}
/**
* Destroy webgl context and block user interaction and stop rendering
*/
_deactivate() {
if (this._photoSphereRenderer) {
this._photoSphereRenderer.destroy();
this._photoSphereRenderer = null;
}
if (this._isReady) {
this._yawPitchControl.disable();
this._stopRender();
this._isReady = false;
}
}
static _isValidTouchDirection(direction) {
return direction === PanoViewer.TOUCH_DIRECTION.NONE ||
direction === PanoViewer.TOUCH_DIRECTION.YAW ||
direction === PanoViewer.TOUCH_DIRECTION.PITCH ||
direction === PanoViewer.TOUCH_DIRECTION.ALL;
}
/**
* Set touch direction by which user can control.
* @ko 사용자가 조작가능한 터치 방향을 지정합니다.
* @method eg.view360.PanoViewer#setTouchDirection
* @param {Number} direction of the touch. {@link eg.view360.PanoViewer.TOUCH_DIRECTION}<ko>컨트롤 가능한 방향 {@link eg.view360.PanoViewer.TOUCH_DIRECTION}</ko>
* @return {eg.view360.PanoViewer} PanoViewer instance
* @example
*
* panoViewer = new PanoViewer(el);
* // Limit the touch direction to the yaw direction only.
* panoViewer.setTouchDirection(eg.view360.PanoViewer.TOUCH_DIRECTION.YAW);
*/
setTouchDirection(direction) {
if (PanoViewer._isValidTouchDirection(direction)) {
this._yawPitchControl.option("touchDirection", direction);
}
return this;
}
/**
* Returns touch direction by which user can control
* @ko 사용자가 조작가능한 터치 방향을 반환한다.
* @method eg.view360.PanoViewer#getTouchDirection
* @return {Number} direction of the touch. {@link eg.view360.PanoViewer.TOUCH_DIRECTION}<ko>컨트롤 가능한 방향 {@link eg.view360.PanoViewer.TOUCH_DIRECTION}</ko>
* @example
*
* panoViewer = new PanoViewer(el);
* // Returns the current touch direction.
* var dir = panoViewer.getTouchDirection();
*/
getTouchDirection() {
return this._yawPitchControl.option("touchDirection");
}
/**
* Destroy viewer. Remove all registered event listeners and remove viewer canvas.
* @ko 뷰어 인스턴스를 해제합니다. 모든 등록된 이벤트리스너를 제거하고 뷰어 캔버스를 삭제합니다.
* @method eg.view360.PanoViewer#destroy
* @return {eg.view360.PanoViewer} PanoViewer instance<ko>PanoViewer 인스턴스</ko>
*/
destroy() {
this._deactivate();
if (this._yawPitchControl) {
this._yawPitchControl.destroy();
this._yawPitchControl = null;
}
return this;
}
/**
* Check whether the current environment can execute PanoViewer
* @ko 현재 브라우저 환경에서 PanoViewer 실행이 가능한지 여부를 반환합니다.
* @function isSupported
* @memberof eg.view360.PanoViewer
* @return {Boolean} PanoViewer executable <ko>PanoViewer 실행가능 여부</ko>
* @static
*/
static isSupported() {
return WebGLUtils.isWebGLAvailable() && WebGLUtils.isStableWebGL();
}
/**
* Check whether the current environment supports the WebGL
* @ko 현재 브라우저 환경이 WebGL 을 지원하는지 여부를 확인합니다.
* @function isWebGLAvailable
* @memberof eg.view360.PanoViewer
* @return {Boolean} WebGL support <ko>WebGL 지원여부</ko>
* @static
*/
static isWebGLAvailable() {
return WebGLUtils.isWebGLAvailable();
}
/**
* Check whether the current environment supports the gyro sensor.
* @ko 현재 브라우저 환경이 자이로 센서를 지원하는지 여부를 확인합니다.
* @function isGyroSensorAvailable
* @memberof eg.view360.PanoViewer
* @param {Function} callback Function to take the gyro sensor availability as argument <ko>자이로 센서를 지원하는지 여부를 인자로 받는 함수</ko>
* @static
*/
static isGyroSensorAvailable(callback) {
if (!DeviceMotionEvent) {
callback && callback(false);
return;
}
let onDeviceMotionChange;
function checkGyro() {
return new Promise((res, rej) => {
onDeviceMotionChange = function(deviceMotion) {
const isGyroSensorAvailable = !(deviceMotion.rotationRate.alpha == null);
res(isGyroSensorAvailable);
};
window.addEventListener("devicemotion", onDeviceMotionChange);
});
}
function timeout() {
return new Promise((res, rej) => {
setTimeout(() => res(false), 1000);
});
}
Promise.race([checkGyro(), timeout()]).then(isGyroSensorAvailable => {
window.removeEventListener("devicemotion", onDeviceMotionChange);
callback && callback(isGyroSensorAvailable);
PanoViewer.isGyroSensorAvailable = function(fb) {
fb && fb(isGyroSensorAvailable);
return isGyroSensorAvailable;
};
});
}
}