Skip to content

Commit 8ba8f56

Browse files
author
Léna Voinchet
committed
Add circular cameras
1 parent f6320e0 commit 8ba8f56

File tree

6 files changed

+376
-6
lines changed

6 files changed

+376
-6
lines changed

js/UI/Wizard.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,8 @@ function calculateCameraConfig(trackingMode, cameraType, givenWidth, givenLength
633633

634634
// --- STEP 1 : Compute projections' dimensions
635635
let WProj, HProj;
636-
const isHexagonal = cameraType.textId === 'orbbec-femto-mega';
636+
const isHexagonal = cameraType.textId === 'orbbec-femto-mega-narrow';
637+
const isCircular = cameraType.textId === 'orbbec-femto-mega-wide';
637638

638639
if (isHexagonal) {
639640
const HMaxRect = Math.abs(Math.tan((cameraType.VFov / 2.0) * Math.PI / 180.0)) * effDist * 2;
@@ -651,6 +652,12 @@ function calculateCameraConfig(trackingMode, cameraType, givenWidth, givenLength
651652
WProj = HProj * (2 / Math.sqrt(3));
652653
}
653654

655+
} else if (isCircular) {
656+
const diameter = Math.abs(Math.tan((cameraType.HFov / 2.0) * Math.PI / 180.0)) * effDist * 2;
657+
658+
WProj = diameter;
659+
HProj = diameter;
660+
654661
} else {
655662
WProj = Math.abs(Math.tan((cameraType.HFov / 2.0) * Math.PI / 180.0)) * effDist * 2;
656663
HProj = Math.abs(Math.tan((cameraType.VFov / 2.0) * Math.PI / 180.0)) * effDist * 2;
@@ -664,6 +671,10 @@ function calculateCameraConfig(trackingMode, cameraType, givenWidth, givenLength
664671
if (isHexagonal) {
665672
pavingWidth = (1/2) * WProj;
666673
pavingHeight = HProj;
674+
} else if (isCircular) {
675+
const spacing = WProj / Math.sqrt(2);
676+
pavingWidth = spacing;
677+
pavingHeight = spacing;
667678
} else {
668679
pavingWidth = WProj;
669680
pavingHeight = HProj;
@@ -712,7 +723,7 @@ function calculateCameraConfig(trackingMode, cameraType, givenWidth, givenLength
712723

713724
// Final choice
714725
return {
715-
typeID: cameraType.id, isHexagonal: isHexagonal,
726+
typeID: cameraType.id, isHexagonal: isHexagonal, isCircular: isCircular,
716727
w: chosenWidth, h: chosenHeight,
717728
nbW: chosenTileCountWidth, nbH: chosenTileCountHeight, rot: isChosenRotated
718729
};

js/camera-data

js/lib/CameraHelperCircular.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {
2+
BufferGeometry,
3+
Float32BufferAttribute,
4+
LineBasicMaterial,
5+
LineSegments,
6+
Vector3
7+
} from 'three';
8+
9+
class CameraHelperCircular extends LineSegments {
10+
11+
constructor(camera) {
12+
const geometry = new BufferGeometry();
13+
const material = new LineBasicMaterial({ color: 0xffaa00, toneMapped: false });
14+
15+
super(geometry, material);
16+
17+
this.type = 'CameraHelperCircular';
18+
this.camera = camera;
19+
20+
this.matrix = camera.matrixWorld;
21+
this.matrixAutoUpdate = false;
22+
23+
this.structuralLineCount = 8;
24+
25+
if (this.camera.updateProjectionMatrix) {
26+
this.camera.updateProjectionMatrix();
27+
}
28+
29+
this.update();
30+
}
31+
32+
update() {
33+
const vertices = [];
34+
const P = []; // Near Plane points
35+
const F = []; // Far Plane points
36+
37+
const camera = this.camera;
38+
const near = camera.near;
39+
const far = camera.far;
40+
const segments = camera.segments;
41+
42+
const Dn = camera.circularNearPlaneDiameter; // Diameter on Near Plane
43+
44+
if (Dn === undefined) {
45+
console.warn("CameraHelperCircular: camera.circularNearPlaneDiameter is undefined. Ensure camera.updateProjectionMatrix() has been called.");
46+
this.geometry.setAttribute('position', new Float32BufferAttribute([], 3));
47+
return;
48+
}
49+
50+
const Rn = Dn / 2; // Radius on Near Plane
51+
52+
const farNearRatio = far / near;
53+
const Df = Dn * farNearRatio; // Diameter on Far Plane
54+
const Rf = Df / 2; // Radius on Far Plane
55+
56+
// Generate vertices for the circle approximation
57+
for (let i = 0; i <= segments; i++) {
58+
const angle = (i / segments) * Math.PI * 2;
59+
const xn = Rn * Math.cos(angle);
60+
const yn = Rn * Math.sin(angle);
61+
const xf = Rf * Math.cos(angle);
62+
const yf = Rf * Math.sin(angle);
63+
64+
P.push(new Vector3(xn, yn, -near));
65+
F.push(new Vector3(xf, yf, -far));
66+
}
67+
68+
// Add segments on Near Plane
69+
for (let i = 0; i < segments; i++) {
70+
vertices.push(P[i].x, P[i].y, P[i].z);
71+
vertices.push(P[i + 1].x, P[i + 1].y, P[i + 1].z);
72+
}
73+
74+
// Add segments on Far Plane
75+
for (let i = 0; i < segments; i++) {
76+
vertices.push(F[i].x, F[i].y, F[i].z);
77+
vertices.push(F[i + 1].x, F[i + 1].y, F[i + 1].z);
78+
}
79+
80+
// Add structural lines connecting camera and Far Plane
81+
for (let i = 0; i < this.structuralLineCount; i++) {
82+
const index = Math.floor(i * segments / this.structuralLineCount);
83+
84+
// Add structural lines from Near Plane to Far Plane
85+
vertices.push(P[index].x, P[index].y, P[index].z);
86+
vertices.push(F[index].x, F[index].y, F[index].z);
87+
88+
// Add structural lines from camera to Near Plane
89+
vertices.push(0, 0, 0);
90+
vertices.push(P[index].x, P[index].y, P[index].z);
91+
}
92+
93+
this.geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
94+
this.geometry.computeBoundingSphere();
95+
}
96+
97+
dispose() {
98+
this.geometry.dispose();
99+
this.material.dispose();
100+
}
101+
}
102+
103+
export { CameraHelperCircular };
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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

Comments
 (0)