1
+ import {
2
+ Camera ,
3
+ MathUtils
4
+ } from 'three' ;
5
+
6
+ class PerspectiveCameraCircular extends Camera {
7
+
8
+ constructor ( fov = 50 , aspect = 1 , near = 0.1 , far = 2000 ) {
9
+
10
+ super ( ) ;
11
+
12
+ this . isPerspectiveCamera = true ;
13
+ this . isPerspectiveCameraCircular = true ; // Specific Identifier
14
+
15
+ this . type = 'PerspectiveCameraCircular' ;
16
+
17
+ this . segments = 50 ;
18
+
19
+ this . fov = fov ;
20
+ this . zoom = 1 ;
21
+
22
+ this . near = near ;
23
+ this . far = far ;
24
+ this . focus = 10 ;
25
+
26
+ this . aspect = aspect ;
27
+ this . view = null ;
28
+
29
+ this . filmGauge = 35 ; // Diameter of the circular film (in millimeters)
30
+ this . filmOffset = 0 ; // horizontal film offset (same unit as gauge)
31
+
32
+ this . updateProjectionMatrix ( ) ;
33
+
34
+ }
35
+
36
+ copy ( source , recursive ) {
37
+
38
+ super . copy ( source , recursive ) ;
39
+
40
+ this . fov = source . fov ;
41
+ this . zoom = source . zoom ;
42
+
43
+ this . near = source . near ;
44
+ this . far = source . far ;
45
+ this . focus = source . focus ;
46
+
47
+ this . aspect = source . aspect ;
48
+ this . view = source . view === null ? null : Object . assign ( { } , source . view ) ;
49
+
50
+ this . filmGauge = source . filmGauge ;
51
+ this . filmOffset = source . filmOffset ;
52
+
53
+ this . segments = source . segments ;
54
+
55
+ if ( source . isPerspectiveCameraCircular !== undefined ) {
56
+ this . isPerspectiveCameraCircular = source . isPerspectiveCameraCircular ;
57
+ }
58
+
59
+ return this ;
60
+ }
61
+
62
+ /**
63
+ * Sets the FOV by focal length in respect to the current .filmGauge.
64
+ * For a circular camera, filmGauge is the diameter of the circular film area.
65
+ */
66
+ setFocalLength ( focalLength ) {
67
+ // fov is vertical fov
68
+ const vExtentSlope = 0.5 * this . getFilmHeight ( ) / focalLength ;
69
+ this . fov = MathUtils . RAD2DEG * 2 * Math . atan ( vExtentSlope ) ;
70
+ this . updateProjectionMatrix ( ) ;
71
+ }
72
+
73
+ /**
74
+ * Calculates the focal length from the current .fov and .filmGauge.
75
+ */
76
+ getFocalLength ( ) {
77
+ const vExtentSlope = Math . tan ( MathUtils . DEG2RAD * 0.5 * this . fov ) ;
78
+ return 0.5 * this . getFilmHeight ( ) / vExtentSlope ;
79
+ }
80
+
81
+ getEffectiveFOV ( ) {
82
+ return MathUtils . RAD2DEG * 2 * Math . atan (
83
+ Math . tan ( MathUtils . DEG2RAD * 0.5 * this . fov ) / this . zoom ) ;
84
+ }
85
+
86
+ /**
87
+ * For a circle, width and height are the same (the diameter).
88
+ */
89
+ getFilmWidth ( ) {
90
+ return this . filmGauge ;
91
+ }
92
+
93
+ /**
94
+ * For a circle, width and height are the same (the diameter).
95
+ */
96
+ getFilmHeight ( ) {
97
+ return this . filmGauge ;
98
+ }
99
+
100
+
101
+ setViewOffset ( fullWidth , fullHeight , x , y , width , height ) {
102
+
103
+ this . aspect = fullWidth / fullHeight ;
104
+
105
+ if ( this . view === null ) {
106
+ this . view = {
107
+ enabled : true ,
108
+ fullWidth : 1 ,
109
+ fullHeight : 1 ,
110
+ offsetX : 0 ,
111
+ offsetY : 0 ,
112
+ width : 1 ,
113
+ height : 1
114
+ } ;
115
+ }
116
+
117
+ this . view . enabled = true ;
118
+ this . view . fullWidth = fullWidth ;
119
+ this . view . fullHeight = fullHeight ;
120
+ this . view . offsetX = x ;
121
+ this . view . offsetY = y ;
122
+ this . view . width = width ;
123
+ this . view . height = height ;
124
+
125
+ this . updateProjectionMatrix ( ) ;
126
+ }
127
+
128
+ clearViewOffset ( ) {
129
+ if ( this . view !== null ) {
130
+ this . view . enabled = false ;
131
+ }
132
+ this . updateProjectionMatrix ( ) ;
133
+ }
134
+
135
+ updateProjectionMatrix ( ) {
136
+ const near = this . near ;
137
+
138
+ // Dimensions of the rectangular projection on the Near Plane
139
+ let top = near * Math . tan ( MathUtils . DEG2RAD * 0.5 * this . fov ) / this . zoom ;
140
+ let height = 2 * top ;
141
+ let width = this . aspect * height ;
142
+ let left = - 0.5 * width ;
143
+
144
+ const view = this . view ;
145
+
146
+ if ( this . view !== null && this . view . enabled ) {
147
+ const fullWidth = view . fullWidth , fullHeight = view . fullHeight ;
148
+ left += view . offsetX * width / fullWidth ;
149
+ top -= view . offsetY * height / fullHeight ;
150
+ width *= view . width / fullWidth ;
151
+ height *= view . height / fullHeight ;
152
+ }
153
+
154
+ const filmOffset = this . filmOffset ;
155
+ if ( filmOffset !== 0 ) left += near * filmOffset / this . getFilmWidth ( ) ;
156
+
157
+ // The frustum is still defined by a rectangle for makePerspective.
158
+ // We calculate the contained circle's dimensions to be used by the helper.
159
+ const rectWidth = width ;
160
+ const rectHeight = height ;
161
+
162
+ // The diameter of the largest circle inside the rectangle is the smaller of its sides.
163
+ const circleDiameter = Math . min ( rectWidth , rectHeight ) ;
164
+
165
+ // Store the circle's diameter on the near plane for the helper
166
+ this . circularNearPlaneDiameter = circleDiameter ;
167
+
168
+ this . projectionMatrix . makePerspective ( left , left + width , top , top - height , near , this . far , this . coordinateSystem ) ;
169
+ this . projectionMatrixInverse . copy ( this . projectionMatrix ) . invert ( ) ;
170
+ }
171
+
172
+ toJSON ( meta ) {
173
+ const data = super . toJSON ( meta ) ;
174
+
175
+ data . object . fov = this . fov ;
176
+ data . object . zoom = this . zoom ;
177
+ data . object . near = this . near ;
178
+ data . object . far = this . far ;
179
+ data . object . focus = this . focus ;
180
+ data . object . aspect = this . aspect ;
181
+
182
+ if ( this . view !== null ) data . object . view = Object . assign ( { } , this . view ) ;
183
+
184
+ data . object . filmGauge = this . filmGauge ;
185
+ data . object . filmOffset = this . filmOffset ;
186
+
187
+ data . object . segments = this . segments ;
188
+
189
+ data . object . isPerspectiveCameraCircular = this . isPerspectiveCameraCircular ;
190
+
191
+ return data ;
192
+ }
193
+ }
194
+
195
+ export { PerspectiveCameraCircular } ;
0 commit comments