Skip to content

Commit 4ee2785

Browse files
author
Martin Sandve Alnæs
committed
Automatically adjust camera to include whole scene in preview widget.
Also changed default material from MeshStandardMaterial to MeshLambertMaterial (some objects were just flat looking).
1 parent 701ceb6 commit 4ee2785

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
@@ -12,11 +12,86 @@ var RenderableModel = Renderable.RenderableModel;
1212
var BLACK = new THREE.Color('black');
1313

1414

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

1791
initialize: function() {
1892
RenderableView.prototype.initialize.apply(this, arguments);
1993

94+
this._resetCameraNeeded = true;
2095
this._rebuildNeeded = true;
2196

2297
},
@@ -39,6 +114,10 @@ var PreviewView = RenderableView.extend({
39114
},
40115

41116
onChildChange: function() {
117+
// Enabling this line will reset the camera
118+
// when any changes are made to the child
119+
//this._resetCameraNeeded = true;
120+
42121
this._rebuildNeeded = true;
43122
},
44123

@@ -47,8 +126,6 @@ var PreviewView = RenderableView.extend({
47126
var obj = this.model.get('child').obj;
48127

49128
this.clearScene();
50-
// cameras need to be added to scene
51-
this.scene.add(this.camera);
52129

53130
if (obj instanceof THREE.Object3D) {
54131

@@ -71,7 +148,7 @@ var PreviewView = RenderableView.extend({
71148
shading: THREE.FlatShading,
72149
});
73150
} else {
74-
material = new THREE.MeshStandardMaterial({
151+
material = new THREE.MeshLambertMaterial({
75152
color: '#ffffff',
76153
});
77154
}
@@ -107,13 +184,44 @@ var PreviewView = RenderableView.extend({
107184
var mesh = new THREE.Mesh(geometry, mat);
108185
this.scene.add(mesh);
109186

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

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

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

131239
lazyRendererSetup: function() {
132-
this.camera = new THREE.PerspectiveCamera(60, 1.0); // aspect is updated by this.updateSize()
133-
this.camera.position.set(-40, 40, 40);
134-
this.camera.lookAt(new THREE.Vector3(0,0,0));
240+
this.camera = new THREE.PerspectiveCamera(60, 1.0);
241+
// aspect is updated by this.updateSize()
242+
// position and lookat target is updated to fit scene
243+
// by this.resetCamera() via constructScene()
135244

136245
// Update aspect ratio of camera:
137246
this.updateSize();
@@ -142,9 +251,7 @@ var PreviewView = RenderableView.extend({
142251
this.scene.background = BLACK;
143252

144253
// Lights
145-
this.pointLight = new THREE.PointLight('#ffffff', 1, 0);
146-
this.pointLight.position.set(-100, 100, 100);
147-
this.pointLight.lookAt(new THREE.Vector3(0,0,0));
254+
this.pointLight = new THREE.PointLight('#ffffff', 1, 0, 0);
148255
this.ambLight = new THREE.AmbientLight('#ffffff', 0.5);
149256
this.camera.add(this.ambLight);
150257
this.camera.add(this.pointLight);

0 commit comments

Comments
 (0)