Skip to content

Commit f1dbe27

Browse files
authored
Merge pull request #114 from martinal/auto-adjust-camera
Automatically adjust camera to include whole scene in preview widget.
2 parents 1ff4a67 + 4ee2785 commit f1dbe27

File tree

1 file changed

+116
-9
lines changed

1 file changed

+116
-9
lines changed

js/src/_base/Preview.js

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,86 @@ var OrbitControls = require("../examples/controls/OrbitControls.js").OrbitContro
1414
var BLACK = new THREE.Color('black');
1515

1616

17+
// Set camera near and far planes close to sphere with given center
18+
// and radius, assuming the camera is already oriented to look at this
19+
// sphere.
20+
function shrinkFrustumPlanes(camera, center, radius, distOffset=0.1) {
21+
// distOffset = 0.1 --> 10% of radius
22+
23+
// Find distance from camera to edges of sphere
24+
const dist = camera.position.distanceTo(center);
25+
const nearEdge = dist - radius;
26+
const farEdge = dist + radius;
27+
28+
// Set near/far sufficiently close to edges of sphere
29+
camera.near = (1 - distOffset) * nearEdge,
30+
camera.far = (1 + distOffset) * farEdge;
31+
32+
// Bound near plane away from zero
33+
camera.near = Math.max(camera.near, 0.01 * radius);
34+
}
35+
36+
// Set camera near and far planes with some headroom around sphere
37+
// with given center and radius, assuming the camera is already
38+
// oriented to look at this sphere.
39+
function safeFrustumPlanes(camera, center, radius, allowZoom=20) {
40+
// Find distance from camera to edges of sphere
41+
const dist = camera.position.distanceTo(center);
42+
const nearEdge = dist - radius;
43+
const farEdge = dist + radius;
44+
45+
// Set near/far sufficiently far from edge of sphere to allow some zooming
46+
camera.near = (1 / allowZoom) * nearEdge;
47+
camera.far = allowZoom * farEdge;
48+
49+
// Bound near plane away from zero
50+
camera.near = Math.max(camera.near, 0.001 * radius);
51+
}
52+
53+
function lookAtSphere(camera, center, radius) {
54+
if (!camera.isPerspectiveCamera) {
55+
console.error("Expecting a perspective camera.");
56+
}
57+
58+
// Compute distance based on FOV
59+
const radScale = 1.5; // Include this much more than the sphere
60+
const distance = (radScale * radius) / Math.tan(0.5 * camera.fov * Math.PI / 180);
61+
62+
// Place camera such that the model is in the -z direction from the camera
63+
camera.position.setX(center.x);
64+
camera.position.setY(center.y);
65+
camera.position.setZ(center.z + distance);
66+
67+
// Look at scene center
68+
camera.lookAt(center.clone());
69+
70+
// Set near and far planes to include sphere with a narrow margin
71+
//shrinkFrustumPlanes(camera, center, radius);
72+
73+
// Set near and far planes to include sphere with a wide margin for zooming
74+
safeFrustumPlanes(camera, center, radius);
75+
76+
// Update matrix
77+
camera.updateProjectionMatrix();
78+
}
79+
80+
// TODO: Make this available as a general utility somewhere?
81+
function computeSceneBoundingSphere(scene) {
82+
// FIXME: The Box3.setFromObject implementation is not great,
83+
// replace with something that reuses bounding box computations
84+
// of the underlying objects
85+
const box = new THREE.Box3();
86+
box.setFromObject(scene);
87+
return box.getBoundingSphere();
88+
}
89+
90+
1791
var PreviewView = RenderableView.extend({
1892

1993
initialize: function() {
2094
RenderableView.prototype.initialize.apply(this, arguments);
2195

96+
this._resetCameraNeeded = true;
2297
this._rebuildNeeded = true;
2398

2499
},
@@ -41,6 +116,10 @@ var PreviewView = RenderableView.extend({
41116
},
42117

43118
onChildChange: function() {
119+
// Enabling this line will reset the camera
120+
// when any changes are made to the child
121+
//this._resetCameraNeeded = true;
122+
44123
this._rebuildNeeded = true;
45124
},
46125

@@ -49,8 +128,6 @@ var PreviewView = RenderableView.extend({
49128
var obj = this.model.get('child').obj;
50129

51130
this.clearScene();
52-
// cameras need to be added to scene
53-
this.scene.add(this.camera);
54131

55132
if (obj.isObject3D) {
56133

@@ -73,7 +150,7 @@ var PreviewView = RenderableView.extend({
73150
shading: THREE.FlatShading,
74151
});
75152
} else {
76-
material = new THREE.MeshStandardMaterial({
153+
material = new THREE.MeshLambertMaterial({
77154
color: '#ffffff',
78155
});
79156
}
@@ -109,13 +186,44 @@ var PreviewView = RenderableView.extend({
109186
var mesh = new THREE.Mesh(geometry, mat);
110187
this.scene.add(mesh);
111188

189+
} else {
190+
191+
console.log("Unexpected object in preview, scene will be empty:", obj);
192+
193+
}
194+
195+
// Reset camera initially and on later requests
196+
if (this._resetCameraNeeded) {
197+
this.resetCamera(); // Depends on this.scene to be correctly set up
198+
this._resetCameraNeeded = false;
112199
}
113200

201+
// Cameras need to be added to scene
202+
this.scene.add(this.camera);
203+
114204
// Clear at end to ensure that any changes to obj does not
115205
// cause infinite rebuild chain.
116206
this._rebuildNeeded = false;
117207
},
118208

209+
resetCamera: function() {
210+
// Compute bounding sphere for entire scene
211+
const sphere = computeSceneBoundingSphere(this.scene);
212+
213+
// Update camera to include bounding sphere
214+
lookAtSphere(this.camera, sphere.center, sphere.radius);
215+
216+
// Update controls with new target
217+
const control = this.controls[0];
218+
control.target.copy(sphere.center);
219+
control.target0.copy(sphere.center);
220+
control.update();
221+
222+
// Position light up to the left and behind camera
223+
const dist = 2.5 * (this.camera.position.z - sphere.center.z);
224+
this.pointLight.position.set(-dist, dist, dist);
225+
},
226+
119227
clearScene: function() {
120228
// this.controls.reset();
121229
this.scene.remove.apply(this.scene, this.scene.children.slice());
@@ -131,9 +239,10 @@ var PreviewView = RenderableView.extend({
131239
},
132240

133241
lazyRendererSetup: function() {
134-
this.camera = new THREE.PerspectiveCamera(60, 1.0); // aspect is updated by this.updateSize()
135-
this.camera.position.set(-40, 40, 40);
136-
this.camera.lookAt(new THREE.Vector3(0,0,0));
242+
this.camera = new THREE.PerspectiveCamera(60, 1.0);
243+
// aspect is updated by this.updateSize()
244+
// position and lookat target is updated to fit scene
245+
// by this.resetCamera() via constructScene()
137246

138247
// Update aspect ratio of camera:
139248
this.updateSize();
@@ -144,9 +253,7 @@ var PreviewView = RenderableView.extend({
144253
this.scene.background = BLACK;
145254

146255
// Lights
147-
this.pointLight = new THREE.PointLight('#ffffff', 1, 0);
148-
this.pointLight.position.set(-100, 100, 100);
149-
this.pointLight.lookAt(new THREE.Vector3(0,0,0));
256+
this.pointLight = new THREE.PointLight('#ffffff', 1, 0, 0);
150257
this.ambLight = new THREE.AmbientLight('#ffffff', 0.5);
151258
this.camera.add(this.ambLight);
152259
this.camera.add(this.pointLight);

0 commit comments

Comments
 (0)