-
Notifications
You must be signed in to change notification settings - Fork 85
/
CylindricalProjection.ts
123 lines (105 loc) · 3.57 KB
/
CylindricalProjection.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
* Copyright (c) 2023-present NAVER Corp.
* egjs projects are licensed under the MIT license
*/
import { quat } from "gl-matrix";
import Projection, { ProjectionOptions } from "./Projection";
import UniformTexture2D from "../uniform/UniformTexture2D";
import WebGLContext from "../core/WebGLContext";
import Texture2D from "../texture/Texture2D";
import CylinderGeometry from "../geometry/CylinderGeometry";
import Camera from "../core/Camera";
import ShaderProgram from "../core/ShaderProgram";
import { DEG_TO_RAD, RAD_TO_DEG } from "../const/internal";
import vs from "../shader/common.vert";
import fs from "../shader/common.frag";
import TriangleMesh from "../core/TriangleMesh";
/**
* Options for {@link CylindricalProjection}
* @ko {@link CylindricalProjection}의 옵션들
* @since 4.0.0
* @category Projection
*/
export interface CylindricalProjectionOptions extends ProjectionOptions {
src: string | HTMLElement;
/**
* Whether the panorama image covers full 360 degrees.
* @ko 파노라마 이미지가 360도를 전부 커버하는지 여부
* @since 4.0.0
* @default false
*/
partial?: boolean;
}
/**
* Projection based on cylindrical projection.
* This can show panorama images taken from smartphones.
* @ko 원통 투영법 기반의 프로젝션.
* 일반적인 스마트폰 파노라마 사진을 표시하는데 사용될 수 있습니다.
* @since 4.0.0
* @category Projection
*/
class CylindricalProjection extends Projection {
private _partial: boolean;
private _aspect: number;
private _halfHeight: number;
private _mesh: TriangleMesh | null;
/**
* Create new instance.
* @ko 새 인스턴스를 생성합니다.
* @param options Options {@ko Options}
*/
public constructor(options: CylindricalProjectionOptions) {
super(options);
const {
partial = false
} = options;
this._partial = partial;
this._aspect = 1;
this._halfHeight = 0;
this._mesh = null;
}
public createMesh(ctx: WebGLContext, texture: Texture2D) {
if (this._mesh) return this._mesh;
const partial = this._partial;
const { width, height } = texture;
const aspect = width / height;
const halfVFov = 180 / aspect;
const cylinderHeight = partial
? 1
: 2 * Math.tan(halfVFov * DEG_TO_RAD);
const cylinderTheta = partial
? aspect
: 2 * Math.PI;
const geometry = new CylinderGeometry(cylinderTheta);
const program = new ShaderProgram(ctx, vs, fs, {
uTexture: new UniformTexture2D(ctx, texture)
});
const vao = ctx.createVAO(geometry, program);
const mesh = new TriangleMesh(vao, program);
mesh.scale[1] = cylinderHeight;
quat.identity(mesh.rotation);
quat.rotateY(mesh.rotation, mesh.rotation, -Math.PI / 2);
mesh.updateMatrix();
this._aspect = aspect;
this._halfHeight = cylinderHeight * 0.5;
this._mesh = mesh;
return mesh;
}
public updateCamera(camera: Camera) {
super.updateCamera(camera);
const mesh = this._mesh;
const aspect = this._aspect;
const halfHeight = this._halfHeight;
if (!mesh) return;
if (this._partial) {
const restrictedYaw = 0.5 * aspect * RAD_TO_DEG;
camera.restrictYawRange(-restrictedYaw, restrictedYaw);
}
const restrictedPitch = Math.atan2(halfHeight, 1) * RAD_TO_DEG;
const minZoom = Math.tan(camera.fov * DEG_TO_RAD * 0.5) / (halfHeight * camera.aspect);
camera.restrictPitchRange(-restrictedPitch, restrictedPitch);
camera.restrictZoomRange(minZoom, Infinity);
camera.restrictRenderHeight(halfHeight * 2);
}
}
export default CylindricalProjection;